mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-08 23:07:38 +01:00
Walk the shadow passes and the culled model groups on worker threads in the Vulkan renderer
This commit is contained in:
@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/common/instancecull.h"
|
||||
#include "rendering/common/walksync.h"
|
||||
|
||||
#include "model/AnimModel.h"
|
||||
#include "model/Model3d.h"
|
||||
@@ -96,9 +97,14 @@ void instance_cull::add(scene::basic_cell::instance_bucket_key const &Key, scene
|
||||
{
|
||||
// lod bounds of the model, the same set Render_Instanced() splits the distance into bands with, found once for the model
|
||||
auto const lodfirst{static_cast<std::uint32_t>(m_lodlist.size())};
|
||||
auto const &lodbounds{Key.pModel->lod_bounds()};
|
||||
m_lodlist.insert(m_lodlist.end(), lodbounds.begin(), lodbounds.end());
|
||||
auto const lodcount{static_cast<std::uint32_t>(lodbounds.size())};
|
||||
std::uint32_t lodcount{0};
|
||||
{
|
||||
// found by the first pass which asks, of the passes walked at the same time (walk_sync)
|
||||
gfx::walk_sync::model_hold hold{Key.pModel};
|
||||
auto const &lodbounds{Key.pModel->lod_bounds()};
|
||||
m_lodlist.insert(m_lodlist.end(), lodbounds.begin(), lodbounds.end());
|
||||
lodcount = static_cast<std::uint32_t>(lodbounds.size());
|
||||
}
|
||||
|
||||
group_data const group{static_cast<std::uint32_t>(m_grouplist.size()), m_entries, lodcount + 1, distancemin, distancemax};
|
||||
m_grouplist.push_back({lodfirst, lodcount, m_entries, 0});
|
||||
@@ -142,7 +148,12 @@ void instance_cull::add(scene::basic_cell::instance_bucket_key const &Key, scene
|
||||
}
|
||||
}
|
||||
|
||||
place(Bucket);
|
||||
{
|
||||
// the places in the record buffer the passes share are given by one walk at a time (walk_sync)
|
||||
auto const &shared{*m_records};
|
||||
gfx::walk_sync::change change{(Bucket.gpugeneration != shared.recordgeneration) || Bucket.gpustale};
|
||||
place(Bucket);
|
||||
}
|
||||
|
||||
gl::instance_cull_job job{};
|
||||
job.origin = packed_vec3{Bucket.recordorigin - m_setup.camera};
|
||||
@@ -222,6 +233,24 @@ void instance_cull::place(scene::basic_cell::instance_bucket const &Bucket)
|
||||
allocate(Bucket);
|
||||
}
|
||||
|
||||
void instance_cull::refresh_places()
|
||||
{
|
||||
auto const &shared{*m_records};
|
||||
auto const moved{std::any_of(m_buckets.begin(), m_buckets.end(), [&](scene::basic_cell::instance_bucket const *Bucket) { return (Bucket->gpugeneration != shared.recordgeneration) || Bucket->gpustale; })};
|
||||
if (moved)
|
||||
{
|
||||
for (std::size_t job = 0; job < m_buckets.size(); ++job)
|
||||
{
|
||||
place(*m_buckets[job]);
|
||||
}
|
||||
}
|
||||
// another pass which started the buffer over placed the buckets it shares with this one anew, current again
|
||||
for (std::size_t job = 0; job < m_buckets.size(); ++job)
|
||||
{
|
||||
m_joblist[job].first = m_buckets[job]->gpufirst;
|
||||
}
|
||||
}
|
||||
|
||||
bool instance_cull::dispatch_begin()
|
||||
{
|
||||
m_occlusion = false;
|
||||
|
||||
@@ -205,6 +205,10 @@ class instance_cull
|
||||
void frustum_planes(glm::vec4 (&Planes)[6]) const;
|
||||
// the end of the dispatch of the first phase
|
||||
void dispatch_end();
|
||||
// the buckets of the pass placed again where the record buffer was started over by another pass since they were placed, and the
|
||||
// places of the jobs taken again where another pass placed their buckets anew (the passes walked by several threads at once,
|
||||
// walk_sync). before the dispatch, with the change held by the caller while the walks are parallel
|
||||
void refresh_places();
|
||||
// with a culling made for another pass: shares the records with it
|
||||
void share_records(instance_cull const &Shared)
|
||||
{
|
||||
|
||||
@@ -188,6 +188,12 @@ class local_shadows
|
||||
// the timing of the drawing of the maps of the headlights in this frame
|
||||
void headlights_timer_begin();
|
||||
void headlights_timer_end();
|
||||
// the draws of the maps of lamps and of headlights the walkers of the vulkan renderer made in the frame (M2), counted once they're done
|
||||
void add_drawcalls(std::uint32_t const Lamps, std::uint32_t const Headlights)
|
||||
{
|
||||
m_lampsdrawcalls += Lamps;
|
||||
m_headlightsdrawcalls += Headlights;
|
||||
}
|
||||
// the slot of the headlights with their map ready in this frame, -1 if there is none
|
||||
int headlight_map(TDynamicObject const *Owner, int const Index) const;
|
||||
// the headlight_shadow_params of the map of the slot, see gl::scene_ubs
|
||||
|
||||
@@ -13,6 +13,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/common/scenewalk.h"
|
||||
#include "rendering/common/walksync.h"
|
||||
#include "utilities/color.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "vehicle/Train.h"
|
||||
@@ -99,6 +100,7 @@ std::size_t scene_walk::pick_index(glm::ivec3 const &Color)
|
||||
}
|
||||
|
||||
bool scene_walk::Render_interior( bool const Alpha ) {
|
||||
gfx::walk_sync::vehicle_hold hold;
|
||||
// TODO: early frustum based cull, camera might be pointing elsewhere
|
||||
std::vector< std::pair<float, TDynamicObject *> > dynamics;
|
||||
auto *dynamic { simulation::Train->Dynamic() };
|
||||
@@ -135,6 +137,7 @@ bool scene_walk::Render_interior( bool const Alpha ) {
|
||||
bool scene_walk::Render_lowpoly( TDynamicObject *Dynamic, float const Squaredistance, bool const Setup, bool const Alpha ) {
|
||||
|
||||
if( Dynamic->mdLowPolyInt == nullptr ) { return false; }
|
||||
gfx::walk_sync::vehicle_hold hold;
|
||||
|
||||
// low poly interior
|
||||
if( Setup ) {
|
||||
@@ -209,6 +212,7 @@ bool scene_walk::Render_lowpoly( TDynamicObject *Dynamic, float const Squaredist
|
||||
bool scene_walk::Render_coupler_adapter( TDynamicObject *Dynamic, float const Squaredistance, int const End, bool const Alpha ) {
|
||||
|
||||
if( Dynamic->m_coupleradapters[ End ] == nullptr ) { return false; }
|
||||
gfx::walk_sync::vehicle_hold hold;
|
||||
|
||||
auto const position { glm::dvec3 {
|
||||
0.f,
|
||||
@@ -352,7 +356,12 @@ void scene_walk::Render(section_sequence::iterator First, section_sequence::iter
|
||||
{
|
||||
|
||||
auto *section = *First;
|
||||
section->create_geometry();
|
||||
gfx::walk_sync::yield();
|
||||
{
|
||||
// made by the first walk which needs it, one at a time (walk_sync)
|
||||
gfx::walk_sync::change change{section->geometry_pending()};
|
||||
section->create_geometry();
|
||||
}
|
||||
|
||||
// render shapes held by the section
|
||||
switch (m_renderpass.draw_mode)
|
||||
@@ -453,8 +462,12 @@ void scene_walk::Render(cell_sequence::iterator First, cell_sequence::iterator L
|
||||
{
|
||||
|
||||
auto *cell = First->second;
|
||||
// przeliczenia animacji torów w sektorze
|
||||
cell->RaAnimate(m_framestamp);
|
||||
gfx::walk_sync::yield();
|
||||
{
|
||||
// przeliczenia animacji torów w sektorze
|
||||
gfx::walk_sync::change change{cell->animation_pending(m_framestamp)};
|
||||
cell->RaAnimate(m_framestamp);
|
||||
}
|
||||
bool const tracks_only{editor_far_tracks_only(far_paths, cell, camera, scenery_limit)};
|
||||
|
||||
switch (m_renderpass.draw_mode)
|
||||
@@ -561,6 +574,7 @@ void scene_walk::Render(cell_sequence::iterator First, cell_sequence::iterator L
|
||||
{
|
||||
|
||||
auto const *cell = first->second;
|
||||
gfx::walk_sync::yield();
|
||||
|
||||
switch (m_renderpass.draw_mode)
|
||||
{
|
||||
@@ -698,6 +712,7 @@ void scene_walk::Render(cell_sequence::iterator First, cell_sequence::iterator L
|
||||
auto const compare { walk_instances_begin() };
|
||||
for( auto &bucket : m_frame_instance_buckets ) {
|
||||
if( bucket.second.empty() ) { continue; }
|
||||
gfx::walk_sync::yield();
|
||||
Render_Instanced( bucket.first.pModel, bucket.second, ( compare ? &m_instance_bandcounts : nullptr ) );
|
||||
if( compare ) {
|
||||
walk_instances_compare( bucket.first, m_instance_bandcounts );
|
||||
@@ -853,6 +868,8 @@ void scene_walk::Render(TAnimModel *Instance)
|
||||
}
|
||||
}
|
||||
|
||||
// the animations and the lights of the instance are set in the model the instances share, held till it's drawn (walk_sync)
|
||||
gfx::walk_sync::model_hold hold{Instance->pModel};
|
||||
Instance->RaAnimate(m_framestamp); // jednorazowe przeliczenie animacji
|
||||
Instance->RaPrepare();
|
||||
if (Instance->pModel)
|
||||
@@ -913,9 +930,15 @@ void scene_walk::Queue_Instances( scene::basic_cell const *Cell )
|
||||
|
||||
for( auto const &bucket : Cell->m_instancebuckets_opaque ) {
|
||||
auto const &instancebucket { bucket.second };
|
||||
if( ( instancebucket.recordsversion != m_instancerecordsversion )
|
||||
|| ( instancebucket.records.size() != instancebucket.instances.size() ) ) {
|
||||
Update_Instance_Records( bucket.first.pModel, instancebucket, Cell->m_area.center );
|
||||
auto const stale = [ & ]() {
|
||||
return ( instancebucket.recordsversion != m_instancerecordsversion )
|
||||
|| ( instancebucket.records.size() != instancebucket.instances.size() ); };
|
||||
if( stale() ) {
|
||||
// the records the passes share are made by one walk at a time (walk_sync)
|
||||
gfx::walk_sync::change change;
|
||||
if( stale() ) {
|
||||
Update_Instance_Records( bucket.first.pModel, instancebucket, Cell->m_area.center );
|
||||
}
|
||||
}
|
||||
if( buckettests && Bucket_Culled( instancebucket ) ) {
|
||||
walk_bucket_culled( instancebucket );
|
||||
@@ -1086,6 +1109,8 @@ void scene_walk::Render_Instanced( TModel3d *Model, std::vector<queued_instance>
|
||||
if( Bandcounts != nullptr ) { Bandcounts->clear(); }
|
||||
if( Model == nullptr ) { return; }
|
||||
if( Instances.empty() ) { return; }
|
||||
// the first instance drawn prepares the model the instances share, held till the bands are drawn (walk_sync)
|
||||
gfx::walk_sync::model_hold hold { Model };
|
||||
|
||||
// LOD: a submodel is drawn only while fSquareDist lies inside its [fSquareMinDist, fSquareMaxDist)
|
||||
// range, so the distinct range bounds split distance into bands with constant LOD. The bucket
|
||||
@@ -1394,6 +1419,7 @@ void scene_walk::Render_Sleepers( TTrack *Track )
|
||||
// draw uses an in-band fSquareDist, so each sleeper renders at the LOD its own
|
||||
// distance selects while instancing stays fully in effect.
|
||||
auto *Model = Track->m_sleeper_model;
|
||||
gfx::walk_sync::model_hold hold { Model };
|
||||
std::size_t const total = instance_modelviews.size();
|
||||
auto const baseinstance { walk_instances( instance_modelviews.data(), total ) };
|
||||
std::size_t band_start = 0;
|
||||
@@ -1457,9 +1483,15 @@ void scene_walk::Render_Timbers( scene::basic_cell::path_sequence::const_iterato
|
||||
auto const camera = ( shadows ? m_renderpass.viewport_camera.position() : m_renderpass.pass_camera.position() );
|
||||
auto const range = static_cast<double>( shadows ? std::min( Global.SleeperDistance, 50.f ) : Global.SleeperDistance );
|
||||
if( m_renderpass.draw_mode == rendermode::color && m_timbersweepframe != m_framestamp ) {
|
||||
// once a frame the beams of the tracks well past where they're laid and worked out ahead are let go
|
||||
// once a frame the beams of the tracks well past where they're laid and worked out ahead are let go. with the passes walked
|
||||
// by several threads, after they're done (Release_Deferred())
|
||||
m_timbersweepframe = m_framestamp;
|
||||
track_sleepers::release_far( camera, range + 600.0 );
|
||||
if( gfx::walk_sync::parallel() ) {
|
||||
m_deferredrelease = std::make_pair( camera, range + 600.0 );
|
||||
}
|
||||
else {
|
||||
track_sleepers::release_far( camera, range + 600.0 );
|
||||
}
|
||||
}
|
||||
glm::mat4 const originmodelview = OpenGLMatrices.data( GL_MODELVIEW );
|
||||
for( auto &bucket : m_timberbuckets ) {
|
||||
@@ -1504,12 +1536,21 @@ void scene_walk::Render_Timbers( scene::basic_cell::path_sequence::const_iterato
|
||||
if( glm::length( track->location() - camera ) > range + track->radius() ) {
|
||||
// worked out ahead, in the background, before it comes in range
|
||||
if( false == shadows && track->m_sleepers3d.dirty && glm::length( track->location() - camera ) < range + track->radius() + 300.0 ) {
|
||||
track_sleepers::prefetch( *track );
|
||||
if( gfx::walk_sync::parallel() ) {
|
||||
m_deferredprefetch.push_back( track );
|
||||
}
|
||||
else {
|
||||
track_sleepers::prefetch( *track );
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if( track->m_sleepers3d.dirty ) {
|
||||
track_sleepers::lay( *track );
|
||||
// laid by one walk at a time (walk_sync)
|
||||
gfx::walk_sync::change change;
|
||||
if( track->m_sleepers3d.dirty ) {
|
||||
track_sleepers::lay( *track );
|
||||
}
|
||||
}
|
||||
auto const &laid = track->m_sleepers3d;
|
||||
if( laid.timbers == nullptr ) { continue; }
|
||||
@@ -1545,6 +1586,8 @@ void scene_walk::Render_Timbers( scene::basic_cell::path_sequence::const_iterato
|
||||
total += instances.size();
|
||||
auto const &beammodel = *bucket.model;
|
||||
auto *model = beammodel.model;
|
||||
// the parts of the model shown and hidden for the bucket, held till it's drawn (walk_sync)
|
||||
gfx::walk_sync::model_hold hold { model };
|
||||
if( bucket.part >= 0 ) {
|
||||
// only the part of the model is visible, its siblings are skipped as the model is walked
|
||||
for( int part = 0; part < track_sleepers::part_count; ++part ) {
|
||||
@@ -1587,11 +1630,11 @@ bool scene_walk::Render(TDynamicObject *Dynamic)
|
||||
if (!Global.render_cab && Global.pCamera.m_owner == Dynamic)
|
||||
return false;
|
||||
|
||||
Dynamic->renderme = (
|
||||
set_renderme( Dynamic,
|
||||
( Global.pCamera.m_owner == Dynamic && !FreeFlyModeFlag )
|
||||
|| ( m_renderpass.pass_camera.visible( Dynamic ) ) );
|
||||
|
||||
if (false == Dynamic->renderme) { return false; }
|
||||
if (false == renderme( Dynamic )) { return false; }
|
||||
|
||||
// setup
|
||||
TSubModel::iInstance = reinterpret_cast<std::uintptr_t>(Dynamic); //żeby nie robić cudzych animacji
|
||||
@@ -1642,14 +1685,16 @@ bool scene_walk::Render(TDynamicObject *Dynamic)
|
||||
}
|
||||
// second stage visibility cull, reject vehicles too far away to be noticeable
|
||||
auto const squareradius{ Dynamic->radius() * Dynamic->radius() };
|
||||
Dynamic->renderme = ( squareradius * Global.ZoomFactor / squaredistance > 0.003 * 0.003 );
|
||||
if( false == Dynamic->renderme ) {
|
||||
set_renderme( Dynamic, ( squareradius * Global.ZoomFactor / squaredistance > 0.003 * 0.003 ) );
|
||||
if( false == renderme( Dynamic ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// debug data
|
||||
++m_renderpass.draw_stats.dynamics;
|
||||
|
||||
// the submodels of the models the vehicle shares, set up for it and held till it's drawn (walk_sync)
|
||||
gfx::walk_sync::vehicle_hold hold;
|
||||
Dynamic->ABuLittleUpdate(squaredistance); // ustawianie zmiennych submodeli dla wspólnego modelu
|
||||
|
||||
glm::mat4 future_stack = model_ubs.future;
|
||||
@@ -1761,6 +1806,8 @@ bool scene_walk::Render(TDynamicObject *Dynamic)
|
||||
// rendering kabiny gdy jest oddzielnym modelem i ma byc wyswietlana
|
||||
bool scene_walk::Render_cab(TDynamicObject const *Dynamic, float const Lightlevel, bool const Alpha)
|
||||
{
|
||||
// the cab models of the vehicles are held with the vehicles (walk_sync)
|
||||
gfx::walk_sync::vehicle_hold hold;
|
||||
|
||||
if (Dynamic == nullptr)
|
||||
{
|
||||
@@ -2524,6 +2571,7 @@ void scene_walk::Render_Alpha(cell_sequence::reverse_iterator First, cell_sequen
|
||||
{
|
||||
|
||||
auto const *cell = first->second;
|
||||
gfx::walk_sync::yield();
|
||||
|
||||
// translucent parts of instanced models
|
||||
for (auto *instance : cell->m_instancetranslucent)
|
||||
@@ -2612,6 +2660,7 @@ void scene_walk::Render_Alpha(TAnimModel *Instance)
|
||||
return;
|
||||
}
|
||||
|
||||
gfx::walk_sync::model_hold hold{Instance->pModel};
|
||||
Instance->RaPrepare();
|
||||
if (Instance->pModel)
|
||||
{
|
||||
@@ -2733,12 +2782,66 @@ void scene_walk::Render_Alpha(scene::lines_node const &Lines)
|
||||
++m_renderpass.draw_stats.drawcalls;
|
||||
}
|
||||
|
||||
// TDynamicObject::renderme, which only the walks read: kept by the walk itself while the passes are walked by several threads at once
|
||||
// (walk_sync), where it falls back on the value of the vehicle for a vehicle it didn't set (as when the passes come one after another)
|
||||
void scene_walk::set_renderme(TDynamicObject *Dynamic, bool const Value)
|
||||
{
|
||||
if (gfx::walk_sync::parallel())
|
||||
{
|
||||
m_renderme[Dynamic] = Value;
|
||||
return;
|
||||
}
|
||||
Dynamic->renderme = Value;
|
||||
}
|
||||
|
||||
bool scene_walk::renderme(TDynamicObject const *Dynamic) const
|
||||
{
|
||||
if (false == m_renderme.empty())
|
||||
{
|
||||
auto const lookup{m_renderme.find(Dynamic)};
|
||||
if (lookup != m_renderme.end())
|
||||
{
|
||||
return lookup->second;
|
||||
}
|
||||
}
|
||||
return Dynamic->renderme;
|
||||
}
|
||||
|
||||
void scene_walk::Settle_Renderme(bool const Keep)
|
||||
{
|
||||
if (Keep)
|
||||
{
|
||||
for (auto const &[dynamic, value] : m_renderme)
|
||||
{
|
||||
const_cast<TDynamicObject *>(dynamic)->renderme = value;
|
||||
}
|
||||
}
|
||||
m_renderme.clear();
|
||||
}
|
||||
|
||||
void scene_walk::Release_Deferred()
|
||||
{
|
||||
if (m_deferredrelease)
|
||||
{
|
||||
track_sleepers::release_far(m_deferredrelease->first, m_deferredrelease->second);
|
||||
m_deferredrelease.reset();
|
||||
}
|
||||
for (auto *track : m_deferredprefetch)
|
||||
{
|
||||
if (track->m_sleepers3d.dirty)
|
||||
{
|
||||
track_sleepers::prefetch(*track);
|
||||
}
|
||||
}
|
||||
m_deferredprefetch.clear();
|
||||
}
|
||||
|
||||
bool scene_walk::Render_Alpha(TDynamicObject *Dynamic)
|
||||
{
|
||||
if (!Global.render_cab && Global.pCamera.m_owner == Dynamic)
|
||||
return false;
|
||||
|
||||
if (false == Dynamic->renderme)
|
||||
if (false == renderme(Dynamic))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -2758,6 +2861,7 @@ bool scene_walk::Render_Alpha(TDynamicObject *Dynamic)
|
||||
break;
|
||||
}
|
||||
}
|
||||
gfx::walk_sync::vehicle_hold hold;
|
||||
Dynamic->ABuLittleUpdate(squaredistance); // ustawianie zmiennych submodeli dla wspólnego modelu
|
||||
|
||||
glm::mat4 future_stack = model_ubs.future;
|
||||
|
||||
@@ -29,6 +29,8 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "rendering/common/instancecull.h"
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
@@ -185,6 +187,15 @@ class scene_walk
|
||||
void Render_Alpha(TSubModel *Submodel);
|
||||
void Render_Alpha_text(TSubModel *Submodel); // characters of a text display
|
||||
|
||||
// TDynamicObject::renderme as the walk sets and reads it: the walk keeps its own while the passes are walked by several threads at
|
||||
// once (walk_sync), which the main thread puts in the vehicles once they're done (Keep) or drops
|
||||
void set_renderme(TDynamicObject *Dynamic, bool const Value);
|
||||
bool renderme(TDynamicObject const *Dynamic) const;
|
||||
void Settle_Renderme(bool const Keep);
|
||||
// what the walks put off while the passes are walked by several threads at once, done by the main thread once they're done: the
|
||||
// beams of the far tracks let go, the sleepers of the tracks coming near worked out ahead (Render_Timbers())
|
||||
void Release_Deferred();
|
||||
|
||||
// the colour of the picking of the item of the index, and the index of a colour read back
|
||||
static glm::vec3 pick_color(std::size_t const Index);
|
||||
static std::size_t pick_index(glm::ivec3 const &Color);
|
||||
@@ -252,6 +263,21 @@ class scene_walk
|
||||
virtual void walk_instances_end() {}
|
||||
// the geometry of the backend and whether it draws blended now, for the draws recorded for the gpu culling
|
||||
virtual gfx::geometrybank_manager &walk_geometry() = 0;
|
||||
// the walks of the groups of a culling split among several threads (the stage M3 of claude/vulkan14-renderer-plan.md): whether the
|
||||
// backend does it for this many groups (by default not), and the tasks run, each by a helper given the state of this walk (its
|
||||
// pass, its model block and blending, its recording of the draws), which Work walks a part of the groups with; returns once all of
|
||||
// them are done
|
||||
virtual bool walk_groups_parallel(std::size_t const Groups)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual void walk_groups_run(std::size_t const Tasks, std::function<void(scene_walk &Helper, std::size_t const Task)> const &Work) {}
|
||||
// where the chunk is in its bank, for a draw recorded for the gpu culling (by default geometrybank_manager::indirect_source(), which
|
||||
// marks the bank as used)
|
||||
virtual bool walk_indirect_source(gfx::geometry_handle const &Geometry, gfx::indirect_source &Source)
|
||||
{
|
||||
return walk_geometry().indirect_source(Geometry, Source);
|
||||
}
|
||||
virtual bool walk_blending_enabled() const = 0;
|
||||
|
||||
// the gpu culling of the instances (Global.gfx_gpucull, stage N6c): what the walk queues in the culling of the pass and the
|
||||
@@ -278,6 +304,9 @@ class scene_walk
|
||||
// the walk of the submodel trees of the groups culled by Cull, once per lod band, which records their draws
|
||||
// (m_indirectdraws, m_indirectcommands). Cascade: the pass is one of the cascades of the sun shadows, which share the walks
|
||||
void Walk_Indirect_Draws(gfx::instance_cull const &Cull, bool const Cascade);
|
||||
// the walks of the groups split among the helpers of the backend (walk_groups_run()): what they share made first, the groups walked
|
||||
// by them, their draws put in the order of the groups as Walk_Indirect_Draws() would record them
|
||||
void Walk_Groups_Parallel(gfx::instance_cull const &Cull, bool const Cascade, bool const Walkcache, bool const Shadows);
|
||||
// a draw of the walk of Walk_Indirect_Draws(), with the state the walk left for it (model_ubs with the modelview set)
|
||||
void Record_Indirect_Draw(gfx::geometry_handle const &Geometry);
|
||||
// multi draws of the recorded draws (Global.gfx_gpucull_multidraw): puts the draws in an order in which the ones with the
|
||||
@@ -463,6 +492,32 @@ class scene_walk
|
||||
walk_counts m_walkcounts;
|
||||
walk_counts m_lastwalkcounts;
|
||||
unsigned int m_walkcountsframe{0};
|
||||
// the working space of Walk_Groups_Parallel(): for each group what its walks share, and for each task of the helpers the draws it
|
||||
// recorded with the bands they're of
|
||||
struct group_plan
|
||||
{
|
||||
walk_record *kept{nullptr};
|
||||
cascade_record *walks{nullptr};
|
||||
bool skip{true};
|
||||
};
|
||||
struct group_span
|
||||
{
|
||||
std::uint32_t group;
|
||||
std::uint32_t band;
|
||||
std::uint32_t first;
|
||||
std::uint32_t count;
|
||||
};
|
||||
struct group_task
|
||||
{
|
||||
std::vector<indirect_draw> draws;
|
||||
std::vector<gl::instance_draw_command> commands;
|
||||
std::vector<group_span> spans;
|
||||
walk_counts counts;
|
||||
int submodels{0};
|
||||
int drawcalls{0};
|
||||
};
|
||||
std::vector<group_plan> m_groupplans;
|
||||
std::vector<group_task> m_grouptasks;
|
||||
// appends the draws of a walk kept from an earlier frame to the recorded draws, with the commands of the entry under way
|
||||
void Replay_Walk(walk_band const &Walk, bool const Shadows);
|
||||
// working space of Order_Indirect_Draws(), kept between frames
|
||||
@@ -528,6 +583,10 @@ class scene_walk
|
||||
// the quad of the glare of lamps and its texture
|
||||
gfx::geometry_handle m_billboardgeometry{0, 0};
|
||||
texture_handle m_glaretexture{-1};
|
||||
// the walk's own TDynamicObject::renderme, and what it put off (set_renderme(), Release_Deferred())
|
||||
std::unordered_map<TDynamicObject const *, bool> m_renderme;
|
||||
std::optional<std::pair<glm::dvec3, double>> m_deferredrelease;
|
||||
std::vector<TTrack *> m_deferredprefetch;
|
||||
|
||||
private:
|
||||
static float editor_ortho_reach();
|
||||
|
||||
@@ -13,12 +13,15 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/common/scenewalk.h"
|
||||
#include "rendering/common/walksync.h"
|
||||
|
||||
#include "model/AnimModel.h"
|
||||
#include "model/Model3d.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/utilities.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
@@ -434,13 +437,22 @@ void scene_walk::Walk_Indirect_Draws(gfx::instance_cull const &Cull, bool const
|
||||
}
|
||||
auto const shadows{m_renderpass.draw_mode == rendermode::shadows};
|
||||
m_indirectrecording = true;
|
||||
for (auto const &group : Cull.draw_groups())
|
||||
// the groups walked by several threads (the backend decides), or one after another below
|
||||
auto const parallel{walk_groups_parallel(Cull.draw_groups().size())};
|
||||
if (parallel)
|
||||
{
|
||||
Walk_Groups_Parallel(Cull, cascade, walkcache, shadows);
|
||||
}
|
||||
static std::vector<gfx::instance_cull::draw_group> const walkedalready;
|
||||
for (auto const &group : (parallel ? walkedalready : Cull.draw_groups()))
|
||||
{
|
||||
auto *model{group.key->pModel};
|
||||
if ((model == nullptr) || (model->Root == nullptr) || ((group.instance == nullptr) && (false == group.sleepers)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// the model the instances share is prepared and walked for the group, held till the walks are made (walk_sync)
|
||||
gfx::walk_sync::model_hold hold{model};
|
||||
// a model drawn on its own is prepared with its lights and animations each time; the others are prepared only for a walk.
|
||||
// the version is taken before, so a walk prepared by something which changes the model isn't taken up again
|
||||
walk_record *kept{nullptr};
|
||||
@@ -585,12 +597,246 @@ void scene_walk::Walk_Indirect_Draws(gfx::instance_cull const &Cull, bool const
|
||||
m_walkcounts.walktime += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - walkstart).count();
|
||||
}
|
||||
|
||||
void scene_walk::Walk_Groups_Parallel(gfx::instance_cull const &Cull, bool const Cascade, bool const Walkcache, bool const Shadows)
|
||||
{
|
||||
auto const &groups{Cull.draw_groups()};
|
||||
auto const &lodbounds{Cull.lod_bounds()};
|
||||
auto constexpr nowalk{std::numeric_limits<std::uint32_t>::max()};
|
||||
// what the walks of the groups share, made before they're split: the records of the walks of the earlier frames and those of the
|
||||
// cascades of this one
|
||||
m_groupplans.assign(groups.size(), group_plan{});
|
||||
for (std::size_t index = 0; index < groups.size(); ++index)
|
||||
{
|
||||
auto const &group{groups[index]};
|
||||
auto *model{group.key->pModel};
|
||||
auto &plan{m_groupplans[index]};
|
||||
plan.skip = (model == nullptr) || (model->Root == nullptr) || ((group.instance == nullptr) && (false == group.sleepers));
|
||||
if (plan.skip)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (Walkcache && (group.key->instance == nullptr))
|
||||
{
|
||||
plan.kept = &m_walkrecords[*group.key];
|
||||
plan.kept->frame = m_framestamp;
|
||||
auto &bands{plan.kept->bands[Shadows ? 1 : 0]};
|
||||
if (bands.size() != group.lodcount + 1)
|
||||
{
|
||||
bands.clear();
|
||||
bands.resize(group.lodcount + 1);
|
||||
}
|
||||
}
|
||||
if (Cascade)
|
||||
{
|
||||
plan.walks = &m_cascaderecords[*group.key];
|
||||
if ((plan.walks->frame != m_framestamp) || (plan.walks->bands.size() != group.lodcount + 1))
|
||||
{
|
||||
plan.walks->frame = m_framestamp;
|
||||
plan.walks->bands.assign(group.lodcount + 1, cascade_band{nowalk, 0});
|
||||
}
|
||||
}
|
||||
}
|
||||
// the bands of a group: those within the distances its instances are at
|
||||
auto const band_range = [&](gfx::instance_cull::draw_group const &Group, std::uint32_t const Band, float &Bandmin) {
|
||||
Bandmin = (Band == 0 ? 0.f : lodbounds[Group.lodfirst + Band - 1]);
|
||||
auto const bandmax{Band == Group.lodcount ? std::numeric_limits<float>::max() : lodbounds[Group.lodfirst + Band]};
|
||||
return (Bandmin <= Group.distancemax) && (bandmax > Group.distancemin);
|
||||
};
|
||||
// the groups split among the tasks one run after another, each walked by a helper as Walk_Indirect_Draws() walks it (the walks the
|
||||
// cascades made before are copied below)
|
||||
auto const tasks{std::max<std::size_t>(1, std::min<std::size_t>(groups.size() / 8, 4))};
|
||||
if (m_grouptasks.size() < tasks)
|
||||
{
|
||||
m_grouptasks.resize(tasks);
|
||||
}
|
||||
walk_groups_run(tasks, [&](scene_walk &Helper, std::size_t const Task) {
|
||||
auto &result{m_grouptasks[Task]};
|
||||
result.spans.clear();
|
||||
result.counts = {};
|
||||
Helper.m_indirectdraws.clear();
|
||||
Helper.m_indirectcommands.clear();
|
||||
Helper.m_renderpass.draw_stats = {};
|
||||
for (auto index = groups.size() * Task / tasks; index < groups.size() * (Task + 1) / tasks; ++index)
|
||||
{
|
||||
auto const &plan{m_groupplans[index]};
|
||||
if (plan.skip)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto const &group{groups[index]};
|
||||
auto *model{group.key->pModel};
|
||||
// the model the instances share is prepared and walked for the group, held till the walks are made (walk_sync)
|
||||
gfx::walk_sync::model_hold hold{model};
|
||||
auto *kept{plan.kept};
|
||||
auto const modelversion{model->state_version()};
|
||||
material_handle const *skins{nullptr};
|
||||
auto alpha{0x30300030};
|
||||
auto prepared{false};
|
||||
auto const prepare = [&]() {
|
||||
prepared = true;
|
||||
if (group.sleepers)
|
||||
{
|
||||
if (group.key->skins[1] != null_handle)
|
||||
{
|
||||
skins = group.key->skins.data();
|
||||
}
|
||||
TSubModel::iVariantSeed = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (group.key->instance != nullptr)
|
||||
{
|
||||
group.instance->RaAnimate(m_framestamp);
|
||||
}
|
||||
group.instance->RaPrepare();
|
||||
auto const *material{group.instance->Material()};
|
||||
if (material != nullptr)
|
||||
{
|
||||
skins = material->replacable_skins;
|
||||
alpha = material->textures_alpha;
|
||||
}
|
||||
}
|
||||
alpha ^= 0x0F0F000F;
|
||||
};
|
||||
if ((plan.walks == nullptr) && (kept == nullptr))
|
||||
{
|
||||
prepare();
|
||||
}
|
||||
for (std::uint32_t band = 0; band <= group.lodcount; ++band)
|
||||
{
|
||||
float bandmin;
|
||||
if (false == band_range(group, band, bandmin))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if ((plan.walks != nullptr) && (plan.walks->bands[band].first != nowalk))
|
||||
{
|
||||
// walked by another cascade
|
||||
continue;
|
||||
}
|
||||
Helper.m_indirectentry = group.entryfirst + band;
|
||||
auto const walkfirst{Helper.m_indirectdraws.size()};
|
||||
auto *keptwalk{kept != nullptr ? &kept->bands[Shadows ? 1 : 0][band] : nullptr};
|
||||
if ((keptwalk != nullptr) && (keptwalk->valid) && (keptwalk->bandmin == bandmin) && (keptwalk->modelversion == modelversion) &&
|
||||
(keptwalk->blending == Helper.walk_blending_enabled()))
|
||||
{
|
||||
Helper.Replay_Walk(*keptwalk, Shadows);
|
||||
++result.counts.reused;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (false == prepared)
|
||||
{
|
||||
prepare();
|
||||
}
|
||||
auto const blending{Helper.walk_blending_enabled()};
|
||||
Helper.m_indirectchanging = false;
|
||||
::glPushMatrix();
|
||||
::glLoadIdentity();
|
||||
model->Root->fSquareDist = bandmin;
|
||||
model->Root->ReplacableSet(skins, alpha);
|
||||
model->Root->pRoot = model;
|
||||
Helper.Render(model->Root);
|
||||
::glPopMatrix();
|
||||
++result.counts.walked;
|
||||
if (Helper.m_indirectchanging)
|
||||
{
|
||||
++result.counts.changing;
|
||||
}
|
||||
if (keptwalk != nullptr)
|
||||
{
|
||||
keptwalk->valid = (false == Helper.m_indirectchanging);
|
||||
if (keptwalk->valid)
|
||||
{
|
||||
keptwalk->draws.assign(Helper.m_indirectdraws.begin() + walkfirst, Helper.m_indirectdraws.end());
|
||||
keptwalk->bandmin = bandmin;
|
||||
keptwalk->modelversion = modelversion;
|
||||
keptwalk->blending = blending;
|
||||
}
|
||||
else
|
||||
{
|
||||
keptwalk->draws.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
result.spans.push_back(group_span{static_cast<std::uint32_t>(index), band, static_cast<std::uint32_t>(walkfirst), static_cast<std::uint32_t>(Helper.m_indirectdraws.size() - walkfirst)});
|
||||
}
|
||||
}
|
||||
result.draws.swap(Helper.m_indirectdraws);
|
||||
result.commands.swap(Helper.m_indirectcommands);
|
||||
result.submodels = Helper.m_renderpass.draw_stats.submodels;
|
||||
result.drawcalls = Helper.m_renderpass.draw_stats.drawcalls;
|
||||
});
|
||||
// the draws in the order of the groups and their bands, with the walks of the cascades copied, as one walk would record them
|
||||
std::size_t task{0};
|
||||
std::size_t cursor{0};
|
||||
for (std::size_t index = 0; index < groups.size(); ++index)
|
||||
{
|
||||
// the task the group was in, and where its walks are among those of the task
|
||||
while (index >= groups.size() * (task + 1) / tasks)
|
||||
{
|
||||
++task;
|
||||
cursor = 0;
|
||||
}
|
||||
auto const &plan{m_groupplans[index]};
|
||||
if (plan.skip)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto const &group{groups[index]};
|
||||
for (std::uint32_t band = 0; band <= group.lodcount; ++band)
|
||||
{
|
||||
float bandmin;
|
||||
if (false == band_range(group, band, bandmin))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
m_indirectentry = group.entryfirst + band;
|
||||
if ((plan.walks != nullptr) && (plan.walks->bands[band].first != nowalk))
|
||||
{
|
||||
// walked by another cascade: its draws, for the instances of this entry
|
||||
auto const &walk{plan.walks->bands[band]};
|
||||
m_indirectdraws.insert(m_indirectdraws.end(), m_cascadedraws.begin() + walk.first, m_cascadedraws.begin() + walk.first + walk.count);
|
||||
for (std::uint32_t draw = 0; draw < walk.count; ++draw)
|
||||
{
|
||||
auto &command{m_indirectcommands.emplace_back(m_cascadecommands[walk.first + draw])};
|
||||
command.entry = m_indirectentry;
|
||||
}
|
||||
m_renderpass.draw_stats.submodels += static_cast<int>(walk.count);
|
||||
m_renderpass.draw_stats.drawcalls += static_cast<int>(walk.count);
|
||||
continue;
|
||||
}
|
||||
auto &result{m_grouptasks[task]};
|
||||
auto const &span{result.spans[cursor++]};
|
||||
assert((span.group == index) && (span.band == band));
|
||||
auto const walkfirst{m_indirectdraws.size()};
|
||||
m_indirectdraws.insert(m_indirectdraws.end(), result.draws.begin() + span.first, result.draws.begin() + span.first + span.count);
|
||||
m_indirectcommands.insert(m_indirectcommands.end(), result.commands.begin() + span.first, result.commands.begin() + span.first + span.count);
|
||||
if (plan.walks != nullptr)
|
||||
{
|
||||
plan.walks->bands[band] = cascade_band{static_cast<std::uint32_t>(m_cascadedraws.size()), static_cast<std::uint32_t>(m_indirectdraws.size() - walkfirst)};
|
||||
m_cascadedraws.insert(m_cascadedraws.end(), m_indirectdraws.begin() + walkfirst, m_indirectdraws.end());
|
||||
m_cascadecommands.insert(m_cascadecommands.end(), m_indirectcommands.begin() + walkfirst, m_indirectcommands.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
for (std::size_t index = 0; index < tasks; ++index)
|
||||
{
|
||||
auto const &result{m_grouptasks[index]};
|
||||
m_walkcounts.walked += result.counts.walked;
|
||||
m_walkcounts.changing += result.counts.changing;
|
||||
m_walkcounts.reused += result.counts.reused;
|
||||
m_renderpass.draw_stats.submodels += result.submodels;
|
||||
m_renderpass.draw_stats.drawcalls += result.drawcalls;
|
||||
}
|
||||
}
|
||||
|
||||
void scene_walk::Record_Indirect_Draw(gfx::geometry_handle const &Geometry)
|
||||
{
|
||||
// the walk of Walk_Indirect_Draws(): the draw is made later, from the gpu culling, with this state
|
||||
auto &geometry{walk_geometry()};
|
||||
gfx::indirect_source source;
|
||||
if (geometry.indirect_source(Geometry, source))
|
||||
if (walk_indirect_source(Geometry, source))
|
||||
{
|
||||
m_indirectcommands.push_back({source.count, 0, source.first, static_cast<std::uint32_t>(source.basevertex), 0, m_indirectentry, (source.indexed ? 1u : 0u), 0});
|
||||
m_indirectdraws.push_back({model_ubs, m_indirectmaterial, m_indirectsubmodel, Geometry, source.type, walk_blending_enabled(), geometry.draw_group(Geometry)});
|
||||
@@ -605,7 +851,7 @@ void scene_walk::Replay_Walk(walk_band const &Walk, bool const Shadows)
|
||||
// the place of the chunk in its bank is asked for again: the bank may have been let go since and made anew, which the
|
||||
// question (as the walk) takes care of, uploading the chunk
|
||||
gfx::indirect_source source;
|
||||
if (false == geometry.indirect_source(walkdraw.geometry, source))
|
||||
if (false == walk_indirect_source(walkdraw.geometry, source))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
304
rendering/common/walksync.cpp
Normal file
304
rendering/common/walksync.cpp
Normal file
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/common/walksync.h"
|
||||
|
||||
#include "utilities/Logs.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
namespace walk_sync
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// the walk lock: shared by the walks, exclusive for a change. a change waiting goes before the walks asking for the lock again
|
||||
// (yield()), which the locks of the standard library don't promise
|
||||
class walk_lock
|
||||
{
|
||||
public:
|
||||
void lock_shared()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_wake.wait(lock, [this]() { return (false == m_writer) && (m_waiting.load(std::memory_order_relaxed) == 0); });
|
||||
++m_readers;
|
||||
}
|
||||
void unlock_shared()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (--m_readers == 0)
|
||||
{
|
||||
m_wake.notify_all();
|
||||
}
|
||||
}
|
||||
void lock()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_waiting.fetch_add(1, std::memory_order_relaxed);
|
||||
m_wake.wait(lock, [this]() { return (false == m_writer) && (m_readers == 0); });
|
||||
m_waiting.fetch_sub(1, std::memory_order_relaxed);
|
||||
m_writer = true;
|
||||
}
|
||||
void unlock()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_writer = false;
|
||||
m_wake.notify_all();
|
||||
}
|
||||
bool waiting() const
|
||||
{
|
||||
return m_waiting.load(std::memory_order_relaxed) > 0;
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_wake;
|
||||
int m_readers{0};
|
||||
bool m_writer{false};
|
||||
std::atomic<int> m_waiting{0};
|
||||
};
|
||||
|
||||
constexpr std::size_t stripe_count{64};
|
||||
|
||||
struct sync_state
|
||||
{
|
||||
std::atomic<bool> parallel{false};
|
||||
walk_lock walk;
|
||||
// the vehicles exclusive, the scenery models shared with a stripe each
|
||||
std::shared_mutex vehicles;
|
||||
std::array<std::mutex, stripe_count> models;
|
||||
std::atomic<bool> reported{false};
|
||||
};
|
||||
|
||||
sync_state &state()
|
||||
{
|
||||
static sync_state instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
// what the thread holds
|
||||
thread_local bool t_shared{false};
|
||||
thread_local bool t_borrowed{false}; // the hold of another thread covers this one (borrowed_scope)
|
||||
thread_local int t_exclusive{0};
|
||||
thread_local int t_pattern{0}; // shared states of models held
|
||||
thread_local bool t_vehicles{false};
|
||||
thread_local std::uint64_t t_stripes{0}; // the stripes held, a bit each
|
||||
|
||||
std::size_t stripe_of(void const *Pointer)
|
||||
{
|
||||
auto const value{static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(Pointer))};
|
||||
return static_cast<std::size_t>(((value >> 4) * 0x9E3779B97F4A7C15ull) >> 58) % stripe_count;
|
||||
}
|
||||
|
||||
void report(char const *What)
|
||||
{
|
||||
if (false == state().reported.exchange(true))
|
||||
{
|
||||
ErrorLog(std::string("parallel walks of the scene: ") + What + " (reported once)");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool parallel()
|
||||
{
|
||||
return state().parallel.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void open()
|
||||
{
|
||||
auto &sync{state()};
|
||||
if (sync.parallel.load(std::memory_order_relaxed))
|
||||
{
|
||||
return;
|
||||
}
|
||||
sync.parallel.store(true, std::memory_order_release);
|
||||
sync.walk.lock_shared();
|
||||
t_shared = true;
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
auto &sync{state()};
|
||||
if (t_shared)
|
||||
{
|
||||
sync.walk.unlock_shared();
|
||||
t_shared = false;
|
||||
}
|
||||
sync.parallel.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
void leave()
|
||||
{
|
||||
if (t_shared)
|
||||
{
|
||||
state().walk.unlock_shared();
|
||||
t_shared = false;
|
||||
}
|
||||
}
|
||||
|
||||
walk_scope::walk_scope()
|
||||
{
|
||||
if (parallel() && (false == t_shared) && (false == t_borrowed))
|
||||
{
|
||||
state().walk.lock_shared();
|
||||
t_shared = true;
|
||||
m_held = true;
|
||||
}
|
||||
}
|
||||
|
||||
walk_scope::~walk_scope()
|
||||
{
|
||||
if (m_held)
|
||||
{
|
||||
state().walk.unlock_shared();
|
||||
t_shared = false;
|
||||
}
|
||||
}
|
||||
|
||||
change::change(bool const Wanted)
|
||||
{
|
||||
if ((false == Wanted) || (false == parallel()) || (t_exclusive > 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if ((t_pattern > 0) || t_borrowed)
|
||||
{
|
||||
// the walks waiting for the model held would wait for the change, which waits for them; the walk a helper works for waits for
|
||||
// it: not taken
|
||||
report(t_borrowed ? "a change made by a helper of a walk" : "a change made while the shared state of a model was held");
|
||||
return;
|
||||
}
|
||||
auto &sync{state()};
|
||||
if (t_shared)
|
||||
{
|
||||
sync.walk.unlock_shared();
|
||||
t_shared = false;
|
||||
m_shared = true;
|
||||
}
|
||||
sync.walk.lock();
|
||||
++t_exclusive;
|
||||
m_exclusive = true;
|
||||
}
|
||||
|
||||
change::~change()
|
||||
{
|
||||
if (false == m_exclusive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto &sync{state()};
|
||||
--t_exclusive;
|
||||
sync.walk.unlock();
|
||||
if (m_shared)
|
||||
{
|
||||
sync.walk.lock_shared();
|
||||
t_shared = true;
|
||||
}
|
||||
}
|
||||
|
||||
borrowed_scope::borrowed_scope()
|
||||
{
|
||||
t_borrowed = true;
|
||||
}
|
||||
|
||||
borrowed_scope::~borrowed_scope()
|
||||
{
|
||||
t_borrowed = false;
|
||||
}
|
||||
|
||||
void yield()
|
||||
{
|
||||
if ((false == t_shared) || (t_exclusive > 0) || (t_pattern > 0) || t_borrowed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto &sync{state()};
|
||||
if (sync.walk.waiting())
|
||||
{
|
||||
sync.walk.unlock_shared();
|
||||
sync.walk.lock_shared();
|
||||
}
|
||||
}
|
||||
|
||||
model_hold::model_hold(TModel3d const *Model)
|
||||
{
|
||||
if ((Model == nullptr) || t_vehicles || (false == parallel()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_stripe = stripe_of(Model);
|
||||
auto const bit{std::uint64_t{1} << m_stripe};
|
||||
if ((t_stripes & bit) != 0)
|
||||
{
|
||||
// held already by this thread (a model of the same stripe drawn inside the one held)
|
||||
return;
|
||||
}
|
||||
auto &sync{state()};
|
||||
sync.vehicles.lock_shared();
|
||||
sync.models[m_stripe].lock();
|
||||
t_stripes |= bit;
|
||||
++t_pattern;
|
||||
m_held = true;
|
||||
}
|
||||
|
||||
model_hold::~model_hold()
|
||||
{
|
||||
if (false == m_held)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto &sync{state()};
|
||||
--t_pattern;
|
||||
t_stripes &= ~(std::uint64_t{1} << m_stripe);
|
||||
sync.models[m_stripe].unlock();
|
||||
sync.vehicles.unlock_shared();
|
||||
}
|
||||
|
||||
vehicle_hold::vehicle_hold()
|
||||
{
|
||||
if (t_vehicles || (false == parallel()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (t_pattern > 0)
|
||||
{
|
||||
// a scenery model held, which the vehicles would wait for
|
||||
report("a vehicle drawn while the shared state of a model was held");
|
||||
return;
|
||||
}
|
||||
state().vehicles.lock();
|
||||
t_vehicles = true;
|
||||
++t_pattern;
|
||||
m_held = true;
|
||||
}
|
||||
|
||||
vehicle_hold::~vehicle_hold()
|
||||
{
|
||||
if (false == m_held)
|
||||
{
|
||||
return;
|
||||
}
|
||||
--t_pattern;
|
||||
t_vehicles = false;
|
||||
state().vehicles.unlock();
|
||||
}
|
||||
|
||||
} // namespace walk_sync
|
||||
} // namespace gfx
|
||||
113
rendering/common/walksync.h
Normal file
113
rendering/common/walksync.h
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// the walks of the scene made by several threads at once (the stage M2 of claude/vulkan14-renderer-plan.md). only the vulkan renderer
|
||||
// opens the window in which they are (gfx.vulkan.parallelpasses); outside of it everything here does nothing, and the opengl renderer
|
||||
// never opens it. while it's open:
|
||||
// - every thread walking the scene holds the walk lock shared (a pass of a walker from the start to the end, the main thread from the
|
||||
// opening to the closing). what the walks make the first time they need it, and what's shared by them (the geometry of the
|
||||
// sections, the animated tracks, the records of the instances in the cells and in the gpu culling, the sleepers laid, the
|
||||
// resources a lazily loaded model brings: geometry banks, materials, textures) is made with the lock held exclusive (change): the
|
||||
// thread lets go of its shared hold meanwhile and takes it again after. the walks let go of it at the points where they hold
|
||||
// nothing which such a change could move (yield()), so a change waits only a little for them
|
||||
// - the shared state of a model (the submodels the instances and the vehicles set up just before they're drawn: animations,
|
||||
// lights, the variants of the seasons) is held by the thread drawing it: a scenery model by a lock of its own (a stripe of
|
||||
// them), the vehicles all at once, as their models may be those of the scenery as well. nothing is changed while it's held
|
||||
// - the static fields of TSubModel and the matrix stack of OpenGLMatrices are per thread
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
class TModel3d;
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
namespace walk_sync
|
||||
{
|
||||
|
||||
// the window is open
|
||||
bool parallel();
|
||||
// the main thread opens the window (it holds the walk lock shared from now on), lets go of its hold before it waits for the walkers
|
||||
// (leave()) and closes the window once they're done
|
||||
void open();
|
||||
void leave();
|
||||
void close();
|
||||
|
||||
// the shared hold of a thread walking a pass (a walker); none outside of the window
|
||||
class walk_scope
|
||||
{
|
||||
public:
|
||||
walk_scope();
|
||||
~walk_scope();
|
||||
walk_scope(walk_scope const &) = delete;
|
||||
walk_scope &operator=(walk_scope const &) = delete;
|
||||
|
||||
private:
|
||||
bool m_held{false};
|
||||
};
|
||||
|
||||
// a change of what the walks share: made with the walk lock held exclusive, while the window is open. Wanted: false when nothing
|
||||
// is to be changed after all (the change isn't waited for)
|
||||
class change
|
||||
{
|
||||
public:
|
||||
explicit change(bool Wanted = true);
|
||||
~change();
|
||||
change(change const &) = delete;
|
||||
change &operator=(change const &) = delete;
|
||||
|
||||
private:
|
||||
bool m_exclusive{false};
|
||||
bool m_shared{false}; // the thread held the lock shared before
|
||||
};
|
||||
|
||||
// lets a change waiting for the walk lock go first; the caller holds nothing it could move
|
||||
void yield();
|
||||
|
||||
// a thread working for a walk which holds the walk lock and waits for it (the walks of the groups of a culling split among helpers,
|
||||
// the stage M3): it's covered by the hold of that walk, takes no hold of its own and makes no changes
|
||||
class borrowed_scope
|
||||
{
|
||||
public:
|
||||
borrowed_scope();
|
||||
~borrowed_scope();
|
||||
borrowed_scope(borrowed_scope const &) = delete;
|
||||
borrowed_scope &operator=(borrowed_scope const &) = delete;
|
||||
};
|
||||
|
||||
// the shared state of a scenery model held by the thread, while the window is open
|
||||
class model_hold
|
||||
{
|
||||
public:
|
||||
explicit model_hold(TModel3d const *Model);
|
||||
~model_hold();
|
||||
model_hold(model_hold const &) = delete;
|
||||
model_hold &operator=(model_hold const &) = delete;
|
||||
|
||||
private:
|
||||
std::size_t m_stripe{0};
|
||||
bool m_held{false};
|
||||
};
|
||||
|
||||
// the shared state of the vehicles held by the thread (all of them, with the scenery models)
|
||||
class vehicle_hold
|
||||
{
|
||||
public:
|
||||
vehicle_hold();
|
||||
~vehicle_hold();
|
||||
vehicle_hold(vehicle_hold const &) = delete;
|
||||
vehicle_hold &operator=(vehicle_hold const &) = delete;
|
||||
|
||||
private:
|
||||
bool m_held{false};
|
||||
};
|
||||
|
||||
} // namespace walk_sync
|
||||
} // namespace gfx
|
||||
Reference in New Issue
Block a user