17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-05 15:42:31 +01:00

perf(gpucull): keep the walks of the submodel trees of the instances from frame to frame and make them again only when the model changes (gfx.gpucull.walkcache)

This commit is contained in:
2026-10-24 01:01:43 +02:00
parent 794ac578de
commit 3e2d292709
10 changed files with 264 additions and 36 deletions

View File

@@ -658,6 +658,11 @@ void TAnimModel::on_season_update() {
void TAnimModel::RaPrepare() void TAnimModel::RaPrepare()
{ // ustawia światła i animacje we wzorcu modelu przed renderowaniem egzemplarza { // ustawia światła i animacje we wzorcu modelu przed renderowaniem egzemplarza
if ((pModel != nullptr) && ((iNumLights > 0) || (false == m_animlist.empty())))
{
// the lights and animations of this instance are set in the submodels the other instances share
pModel->state_changed();
}
if (Global.UpdateMaterials) if (Global.UpdateMaterials)
on_season_update(); on_season_update();
for (int i = 0; i < iNumLights; ++i) for (int i = 0; i < iNumLights; ++i)

View File

@@ -1617,6 +1617,7 @@ void TModel3d::AddTo(TSubModel *tmp, TSubModel *SubModel)
++iSubModelsCount; // teraz jest o 1 submodel więcej ++iSubModelsCount; // teraz jest o 1 submodel więcej
iFlags |= 0x0200; // submodele są oddzielne iFlags |= 0x0200; // submodele są oddzielne
m_lodboundsfound = false; // the new submodel may have its own range m_lodboundsfound = false; // the new submodel may have its own range
state_changed();
}; };
TSubModel *TModel3d::GetFromName(std::string const &Name) const TSubModel *TModel3d::GetFromName(std::string const &Name) const

View File

@@ -294,6 +294,7 @@ private:
bool m_instancesubmodelslocated { false }; bool m_instancesubmodelslocated { false };
std::vector<float> m_lodbounds; // filled on first use, see lod_bounds() std::vector<float> m_lodbounds; // filled on first use, see lod_bounds()
bool m_lodboundsfound { false }; bool m_lodboundsfound { false };
std::uint32_t m_stateversion { 0 }; // see state_version()
public: public:
TModel3d() = default; TModel3d() = default;
@@ -308,6 +309,13 @@ public:
// returns the distinct bounds of the visibility ranges of the submodels (fSquareMinDist and fSquareMaxDist, squared), sorted: // returns the distinct bounds of the visibility ranges of the submodels (fSquareMinDist and fSquareMaxDist, squared), sorted:
// they split the distance into bands in which the same submodels are drawn. found once for the model, like instance_parts() // they split the distance into bands in which the same submodels are drawn. found once for the model, like instance_parts()
std::vector<float> const & lod_bounds(); std::vector<float> const & lod_bounds();
// the submodels of the model are shared by everything drawn with it, which sets their state (lights, animations, visibility)
// before it's drawn. the version changes when something which may set it differently than the last time did, or the submodels
// changed, so the renderer can tell whether what it worked out from them still holds
std::uint32_t state_version() const {
return m_stateversion; }
void state_changed() {
++m_stateversion; }
TSubModel * AddToNamed(const char *Name, TSubModel *SubModel); TSubModel * AddToNamed(const char *Name, TSubModel *SubModel);
nameoffset_sequence const & find_smoke_sources(); nameoffset_sequence const & find_smoke_sources();
void AddTo(TSubModel *tmp, TSubModel *SubModel); void AddTo(TSubModel *tmp, TSubModel *SubModel);

View File

@@ -46,6 +46,8 @@ struct texture_request {
// handed to the workers (opengl_texture::prefetch()), and when: the main thread only // handed to the workers (opengl_texture::prefetch()), and when: the main thread only
bool submitted { false }; bool submitted { false };
std::chrono::steady_clock::time_point submittime; std::chrono::steady_clock::time_point submittime;
bool ahead { false }; // read ahead of the camera, which was at the origin then
glm::dvec3 origin { 0.0 };
// results, written by the thread reading the data // results, written by the thread reading the data
std::vector<unsigned char> data; std::vector<unsigned char> data;
bool failed { false }; bool failed { false };
@@ -1052,6 +1054,12 @@ opengl_texture::create( bool const Static ) {
components_hint = GL_SRGB_ALPHA; components_hint = GL_SRGB_ALPHA;
GLint internal_format = mapping[components][components_hint]; GLint internal_format = mapping[components][components_hint];
if( ( internal_format == 0 )
&& ( f_it != precompressed_formats.end() ) ) {
// data read back from the card (Global.ResourceMove) is in the format it was made in, which can be one the
// mapping doesn't start from (srgb)
internal_format = data_format;
}
auto blocksize_it = precompressed_formats.find(internal_format); auto blocksize_it = precompressed_formats.find(internal_format);
@@ -1090,7 +1098,8 @@ opengl_texture::create( bool const Static ) {
} }
if( true == Global.ResourceMove if( true == Global.ResourceMove
|| false == Global.ResourceSweep ) { || false == Global.ResourceSweep
|| true == streamed() ) {
// if garbage collection is disabled we don't expect having to upload the texture more than once // if garbage collection is disabled we don't expect having to upload the texture more than once
data = std::vector<unsigned char>(); data = std::vector<unsigned char>();
data_state = resource_state::none; data_state = resource_state::none;
@@ -1133,17 +1142,40 @@ opengl_texture::complete_data() {
size = data.size() / 1024; size = data.size() / 1024;
} }
// has a worker thread read the data of the texture, if it's waiting for that // has a worker thread read the data of the texture, if it's waiting for that. Origin: the camera, for data read ahead of it
void void
opengl_texture::prefetch() { opengl_texture::prefetch( glm::dvec3 const *Origin ) {
if( ( request == nullptr )
&& ( id == invalid_id )
&& ( data_state == resource_state::none )
&& ( true == reload_on_use )
&& ( true == streamed() ) ) {
// the data went with the gl texture, it's read from the file again
reload_on_use = false;
load();
}
if( ( request == nullptr ) || ( true == request->submitted ) ) { return; } if( ( request == nullptr ) || ( true == request->submitted ) ) { return; }
request->submitted = true; request->submitted = true;
request->submittime = std::chrono::steady_clock::now(); request->submittime = std::chrono::steady_clock::now();
request->ahead = ( Origin != nullptr );
if( Origin != nullptr ) {
request->origin = *Origin;
}
texture_loader::instance().submit( request ); texture_loader::instance().submit( request );
} }
// the data of the texture is read from its file by worker threads when it's needed (gfx.textures.streaming), and not kept
// after the upload: it can be read again
bool
opengl_texture::streamed() const {
return ( ( true == Global.gfx_texture_streaming )
&& ( type == ".dds" )
&& ( false == is_rendertarget ) );
}
// releases resources allocated on the opengl end, storing local copy if requested // releases resources allocated on the opengl end, storing local copy if requested
void void
opengl_texture::release() { opengl_texture::release() {
@@ -1151,7 +1183,13 @@ opengl_texture::release() {
if( id == -1 ) { return; } if( id == -1 ) { return; }
if( is_static ) { return; } if( is_static ) { return; }
if( true == Global.ResourceMove && !is_rendertarget ) { if( true == streamed() ) {
// no copy of the data is kept, it's read from the file again when the texture is needed (prefetch(), create())
data = std::vector<unsigned char>();
data_state = resource_state::none;
reload_on_use = true;
}
else if( true == Global.ResourceMove && !is_rendertarget ) {
// if resource move is enabled we don't keep a cpu side copy after upload // if resource move is enabled we don't keep a cpu side copy after upload
// so need to re-acquire the data before release // so need to re-acquire the data before release
// TBD, TODO: instead of vram-ram transfer fetch the data 'normally' from the disk using worker thread // TBD, TODO: instead of vram-ram transfer fetch the data 'normally' from the disk using worker thread
@@ -1552,19 +1590,19 @@ texture_manager::memory() const {
// has the data of a texture read, if it's waiting for that // has the data of a texture read, if it's waiting for that
void void
texture_manager::prefetch( texture_handle const Texture ) { texture_manager::prefetch( texture_handle const Texture, glm::dvec3 const &Origin ) {
if( ( Texture <= 0 ) if( ( Texture <= 0 )
|| ( Texture >= static_cast<texture_handle>( m_textures.size() ) ) ) { || ( Texture >= static_cast<texture_handle>( m_textures.size() ) ) ) {
return; return;
} }
m_textures[ Texture ].first->prefetch(); m_textures[ Texture ].first->prefetch( &Origin );
} }
// gives back the data read for textures which nothing drew within the specified time since it was asked for. it's read again when // gives back the data read for textures which nothing drew within the specified time since it was asked for, or read ahead of the
// a texture is needed // camera which went farther than the specified distance from there since. it's read again when a texture is needed
std::size_t std::size_t
texture_manager::drop_read_ahead( std::chrono::steady_clock::duration const Age ) { texture_manager::drop_read_ahead( std::chrono::steady_clock::duration const Age, glm::dvec3 const &Camera, double const Distance ) {
auto const now { std::chrono::steady_clock::now() }; auto const now { std::chrono::steady_clock::now() };
std::size_t count { 0 }; std::size_t count { 0 };
@@ -1574,8 +1612,11 @@ texture_manager::drop_read_ahead( std::chrono::steady_clock::duration const Age
continue; continue;
} }
auto &request { *texture.first->request }; auto &request { *texture.first->request };
if( ( false == request.submitted ) if( false == request.submitted ) {
|| ( now - request.submittime < Age ) ) { continue;
}
if( ( now - request.submittime < Age )
&& ( ( false == request.ahead ) || ( glm::length2( Camera - request.origin ) <= Distance * Distance ) ) ) {
continue; continue;
} }
if( true == texture_loader::instance().drop( request ) ) { if( true == texture_loader::instance().drop( request ) ) {

View File

@@ -118,8 +118,11 @@ public:
// takes over the data read by a worker thread, waiting for it if needed // takes over the data read by a worker thread, waiting for it if needed
void complete_data(); void complete_data();
// has a worker thread read the data of the texture, if it's waiting for that (gfx.textures.streaming) // has a worker thread read the data of the texture, if it's waiting for that (gfx.textures.streaming). Origin: the camera,
void prefetch(); // for data read ahead of it
void prefetch( glm::dvec3 const *Origin = nullptr );
// the data is read from the file when it's needed and not kept after the upload (gfx.textures.streaming)
bool streamed() const;
// members // members
std::shared_ptr<texture_request> request; // data being read by a worker thread std::shared_ptr<texture_request> request; // data being read by a worker thread
@@ -199,10 +202,11 @@ public:
memory() const; memory() const;
// has the data of a texture read, if it's waiting for that (gfx.textures.streaming) // has the data of a texture read, if it's waiting for that (gfx.textures.streaming)
void void
prefetch( texture_handle const Texture ); prefetch( texture_handle const Texture, glm::dvec3 const &Origin );
// gives back the data read for textures which nothing drew within the specified time since it was asked for // gives back the data read for textures which nothing drew within the specified time since it was asked for, or read ahead of
// the camera which went farther than the specified distance from there since
std::size_t std::size_t
drop_read_ahead( std::chrono::steady_clock::duration const Age ); drop_read_ahead( std::chrono::steady_clock::duration const Age, glm::dvec3 const &Camera, double const Distance );
private: private:
// types: // types:

View File

@@ -714,6 +714,7 @@ bool opengl46_renderer::Render()
m_sleeperrecords.clear(); m_sleeperrecords.clear();
m_modelrecords.clear(); m_modelrecords.clear();
m_cascaderecords.clear(); m_cascaderecords.clear();
m_walkrecords.clear();
} }
// generate new frame // generate new frame
opengl_texture::reset_unit_cache(); opengl_texture::reset_unit_cache();
@@ -1139,6 +1140,14 @@ void opengl46_renderer::SwapBuffers()
culltext += m_reflectioncull.info( "gpu cull reflections, last face", false ); culltext += m_reflectioncull.info( "gpu cull reflections, last face", false );
} }
culltext += Light_Shadow_Cull_Info( false ); culltext += Light_Shadow_Cull_Info( false );
if( Global.gfx_gpucull == 1 ) {
// all passes of the last frame
auto const &walks { m_lastwalkcounts };
culltext +=
"walks of the submodel trees in the frame: " + std::to_string( walks.walked ) + " made (" + std::to_string( walks.changing ) + " not kept), "
+ std::to_string( walks.reused ) + " kept from earlier frames (" + std::to_string( m_walkrecords.size() ) + " groups), cpu " + to_string( walks.walktime, 3 )
+ " ms; the draws put in order and sent, cpu " + to_string( walks.ordertime, 3 ) + " ms\n";
}
m_debugsections.push_back( { "Scene statistics", std::move( statstext ), false } ); m_debugsections.push_back( { "Scene statistics", std::move( statstext ), false } );
m_debugsections.push_back( { "GPU culling", std::move( culltext ), false } ); m_debugsections.push_back( { "GPU culling", std::move( culltext ), false } );
m_debugsections.push_back( { "Memory", m_memorytext, true } ); m_debugsections.push_back( { "Memory", m_memorytext, true } );
@@ -1514,6 +1523,7 @@ void opengl46_renderer::draw_debug_ui()
ImGui::Checkbox("the same for the sleepers of the tracks of the view (gfx.gpucull.sleepers)", &Global.gfx_gpucull_sleepers); ImGui::Checkbox("the same for the sleepers of the tracks of the view (gfx.gpucull.sleepers)", &Global.gfx_gpucull_sleepers);
ImGui::Checkbox("the same for the models drawn on their own, with lights, animations or skins of their own (gfx.gpucull.models)", &Global.gfx_gpucull_models); ImGui::Checkbox("the same for the models drawn on their own, with lights, animations or skins of their own (gfx.gpucull.models)", &Global.gfx_gpucull_models);
ImGui::Checkbox("leave out the models of a cell lying wholly out of view, beyond their range or too small before the culling (gfx.gpucull.bucketrange)", &Global.gfx_gpucull_bucketrange); ImGui::Checkbox("leave out the models of a cell lying wholly out of view, beyond their range or too small before the culling (gfx.gpucull.bucketrange)", &Global.gfx_gpucull_bucketrange);
ImGui::Checkbox("keep the walks of the submodel trees of the instances from frame to frame (gfx.gpucull.walkcache)", &Global.gfx_gpucull_walkcache);
} }
ImGui::Checkbox("draw the depth of the whole scene before its colour (gfx.depthprepass.scene)", &Global.gfx_depthprepass_scene); ImGui::Checkbox("draw the depth of the whole scene before its colour (gfx.depthprepass.scene)", &Global.gfx_depthprepass_scene);
if (Global.gfx_contactshadows > 0) if (Global.gfx_contactshadows > 0)
@@ -5055,10 +5065,44 @@ void opengl46_renderer::Record_Indirect_Draws( opengl46_instance_cull &Cull )
} }
} }
} }
if( m_walkcountsframe != m_framestamp ) {
m_walkcountsframe = m_framestamp;
m_lastwalkcounts = m_walkcounts;
m_walkcounts = {};
}
auto const walkstart { std::chrono::steady_clock::now() };
// the walks of earlier frames. they go when the switch is off, as the models may change meanwhile without the walks noticing
// it the next time (the season), and when the shadows take other submodels
auto const walkcache { Global.gfx_gpucull_walkcache && ( false == m_previewing ) };
if( ( false == Global.gfx_gpucull_walkcache ) || ( m_walkrankcutoff != Global.gfx_shadow_rank_cutoff ) ) {
m_walkrecords.clear();
m_walkrankcutoff = Global.gfx_shadow_rank_cutoff;
}
if( walkcache && ( ( m_framestamp % 256 ) == 0 ) ) {
// the groups not drawn for a while
for( auto records { m_walkrecords.begin() }; records != m_walkrecords.end(); ) {
if( m_framestamp - records->second.frame > 256 ) { records = m_walkrecords.erase( records ); }
else { ++records; }
}
}
auto const shadows { m_renderpass.draw_mode == rendermode::shadows };
m_indirectrecording = true; m_indirectrecording = true;
for( auto const &group : Cull.draw_groups() ) { for( auto const &group : Cull.draw_groups() ) {
auto *model { group.key->pModel }; auto *model { group.key->pModel };
if( ( model == nullptr ) || ( model->Root == nullptr ) || ( ( group.instance == nullptr ) && ( false == group.sleepers ) ) ) { continue; } if( ( model == nullptr ) || ( model->Root == nullptr ) || ( ( group.instance == nullptr ) && ( false == group.sleepers ) ) ) { continue; }
// 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 };
if( walkcache && ( group.key->instance == nullptr ) ) {
kept = &m_walkrecords[ *group.key ];
kept->frame = m_framestamp;
auto &bands { kept->bands[ shadows ? 1 : 0 ] };
if( bands.size() != group.lodcount + 1 ) {
bands.clear();
bands.resize( group.lodcount + 1 );
}
}
auto const modelversion { model->state_version() };
material_handle const *skins { nullptr }; material_handle const *skins { nullptr };
auto alpha { 0x30300030 }; auto alpha { 0x30300030 };
auto prepared { false }; auto prepared { false };
@@ -5095,7 +5139,7 @@ void opengl46_renderer::Record_Indirect_Draws( opengl46_instance_cull &Cull )
walks->bands.assign( group.lodcount + 1, cascade_band { nowalk, 0 } ); walks->bands.assign( group.lodcount + 1, cascade_band { nowalk, 0 } );
} }
} }
else { else if( kept == nullptr ) {
prepare(); prepare();
} }
for( std::uint32_t band = 0; band <= group.lodcount; ++band ) { for( std::uint32_t band = 0; band <= group.lodcount; ++band ) {
@@ -5117,17 +5161,47 @@ void opengl46_renderer::Record_Indirect_Draws( opengl46_instance_cull &Cull )
m_renderpass.draw_stats.drawcalls += static_cast<int>( walk.count ); m_renderpass.draw_stats.drawcalls += static_cast<int>( walk.count );
continue; continue;
} }
if( false == prepared ) {
prepare();
}
auto const walkfirst { m_indirectdraws.size() }; auto const walkfirst { m_indirectdraws.size() };
::glPushMatrix(); auto *keptwalk { kept != nullptr ? &kept->bands[ shadows ? 1 : 0 ][ band ] : nullptr };
::glLoadIdentity(); if( ( keptwalk != nullptr )
model->Root->fSquareDist = bandmin; && ( keptwalk->valid )
model->Root->ReplacableSet( skins, alpha ); && ( keptwalk->bandmin == bandmin )
model->Root->pRoot = model; && ( keptwalk->modelversion == modelversion )
Render( model->Root ); && ( keptwalk->blending == m_blendingenabled ) ) {
::glPopMatrix(); // walked in an earlier frame, with the model as it is now
Replay_Walk( *keptwalk, shadows );
++m_walkcounts.reused;
}
else {
if( false == prepared ) {
prepare();
}
auto const blending { m_blendingenabled };
m_indirectchanging = false;
::glPushMatrix();
::glLoadIdentity();
model->Root->fSquareDist = bandmin;
model->Root->ReplacableSet( skins, alpha );
model->Root->pRoot = model;
Render( model->Root );
::glPopMatrix();
++m_walkcounts.walked;
if( m_indirectchanging ) {
++m_walkcounts.changing;
}
if( keptwalk != nullptr ) {
keptwalk->valid = ( false == m_indirectchanging );
if( keptwalk->valid ) {
keptwalk->draws.assign( m_indirectdraws.begin() + walkfirst, m_indirectdraws.end() );
keptwalk->bandmin = bandmin;
keptwalk->modelversion = modelversion;
keptwalk->blending = blending;
}
else {
keptwalk->draws.clear();
}
}
}
if( walks != nullptr ) { if( walks != nullptr ) {
walks->bands[ band ] = cascade_band { walks->bands[ band ] = cascade_band {
static_cast<std::uint32_t>( m_cascadedraws.size() ), static_cast<std::uint32_t>( m_cascadedraws.size() ),
@@ -5138,6 +5212,8 @@ void opengl46_renderer::Record_Indirect_Draws( opengl46_instance_cull &Cull )
} }
} }
m_indirectrecording = false; m_indirectrecording = false;
auto const orderstart { std::chrono::steady_clock::now() };
m_walkcounts.walktime += std::chrono::duration<double, std::milli>( orderstart - walkstart ).count();
// with multi draws the draws with the same state follow one another, and their model blocks are in a buffer the programs // with multi draws the draws with the same state follow one another, and their model blocks are in a buffer the programs
// of the multi draws read them from. the order goes into the commands, so it comes first // of the multi draws read them from. the order goes into the commands, so it comes first
@@ -5148,6 +5224,34 @@ void opengl46_renderer::Record_Indirect_Draws( opengl46_instance_cull &Cull )
} }
Cull.fill_commands( m_indirectcommands ); Cull.fill_commands( m_indirectcommands );
m_walkcounts.ordertime += std::chrono::duration<double, std::milli>( std::chrono::steady_clock::now() - orderstart ).count();
}
void opengl46_renderer::Replay_Walk( walk_band const &Walk, bool const Shadows )
{
for( auto const &walkdraw : Walk.draws ) {
// 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 == m_geometry.indirect_source( walkdraw.geometry, source ) ) { continue; }
m_indirectcommands.push_back( { source.count, 0, source.first, static_cast<std::uint32_t>( source.basevertex ), 0, m_indirectentry, ( source.indexed ? 1u : 0u ), 0 } );
auto &draw { m_indirectdraws.emplace_back( walkdraw ) };
draw.type = source.type;
// the walk sets the placement of the submodel, and before each draw of the colour passes the parameters of its material
// (Bind_Material()); the rest of the block is what the pass has at the moment, as the walk would leave it
auto const &walked { walkdraw.ubs };
draw.ubs = model_ubs;
draw.ubs.modelview = walked.modelview;
draw.ubs.modelviewnormal = walked.modelviewnormal;
if( false == Shadows ) {
std::memcpy( &draw.ubs.param[ 0 ], &walked.param[ 0 ], sizeof( draw.ubs.param ) );
draw.ubs.opacity = walked.opacity;
draw.ubs.alpha_mult = walked.alpha_mult;
}
}
// as the walk would count them
m_renderpass.draw_stats.submodels += static_cast<int>( Walk.draws.size() );
m_renderpass.draw_stats.drawcalls += static_cast<int>( Walk.draws.size() );
} }
opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_Prepass( opengl46_instance_cull::command_set const Set, gl::gpu_timer &Timer ) opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_Prepass( opengl46_instance_cull::command_set const Set, gl::gpu_timer &Timer )
@@ -6138,7 +6242,7 @@ void opengl46_renderer::Update_Texture_Prefetch()
auto const camera{m_colorpass.pass_camera.position()}; auto const camera{m_colorpass.pass_camera.position()};
auto const range{static_cast<double>(m_colorpass.draw_range) * Global.fDistanceFactor + margin}; auto const range{static_cast<double>(m_colorpass.draw_range) * Global.fDistanceFactor + margin};
auto const prefetch_material = [this](material_handle const Material) { auto const prefetch_material = [this, &camera](material_handle const Material) {
if (Material <= 0) if (Material <= 0)
{ {
return; return;
@@ -6146,24 +6250,24 @@ void opengl46_renderer::Update_Texture_Prefetch()
auto const &material{m_materials.material(Material)}; auto const &material{m_materials.material(Material)};
for (auto const texture : material.textures) for (auto const texture : material.textures)
{ {
m_textures.prefetch(texture); m_textures.prefetch(texture, camera);
} }
for (auto const &variant : material.texture_variants) for (auto const &variant : material.texture_variants)
{ {
for (auto const texture : variant) for (auto const texture : variant)
{ {
m_textures.prefetch(texture); m_textures.prefetch(texture, camera);
} }
} }
}; };
auto const prefetch_model = [this](TModel3d *Model) { auto const prefetch_model = [this, &camera](TModel3d *Model) {
if (Model == nullptr) if (Model == nullptr)
{ {
return; return;
} }
for (auto const texture : Model_Textures(Model)) for (auto const texture : Model_Textures(Model))
{ {
m_textures.prefetch(texture); m_textures.prefetch(texture, camera);
} }
}; };
@@ -6280,12 +6384,12 @@ void opengl46_renderer::Update_Texture_Prefetch()
} }
} }
} }
// the data read for what wasn't drawn after all (the details of models seen only from close, or a turn not taken) goes after a // the data read for what wasn't drawn after all goes: when the camera went away from where it was read for (the instances which
// while // wanted it are out of reach then), or after a while (the details of models seen only from close, a turn not taken)
if (now - m_prefetchdroptime >= std::chrono::seconds(10)) if (now - m_prefetchdroptime >= std::chrono::seconds(2))
{ {
m_prefetchdroptime = now; m_prefetchdroptime = now;
m_textures.drop_read_ahead(std::chrono::minutes(5)); m_textures.drop_read_ahead(std::chrono::minutes(5), camera, 2.0 * range);
} }
} }
@@ -7071,6 +7175,8 @@ void opengl46_renderer::Render(TSubModel *Submodel)
glm::mat4 mv = OpenGLMatrices.data(GL_MODELVIEW); glm::mat4 mv = OpenGLMatrices.data(GL_MODELVIEW);
model_ubs.future *= (mv * Submodel->future_transform) * glm::inverse(mv); model_ubs.future *= (mv * Submodel->future_transform) * glm::inverse(mv);
// the placement may be different in the next frame: the walk isn't kept (Record_Indirect_Draws())
m_indirectchanging = true;
} }
} }
@@ -7108,6 +7214,11 @@ void opengl46_renderer::Render(TSubModel *Submodel)
// ...luminance // ...luminance
auto const isemissive { ( Submodel->f4Emision.a > 0.f ) && ( Global.fLuminance < Submodel->fLight ) }; auto const isemissive { ( Submodel->f4Emision.a > 0.f ) && ( Global.fLuminance < Submodel->fLight ) };
if( ( Submodel->f4Emision.a > 0.f ) && ( Submodel->fLight > -1.f ) )
{
// lit or not by the luminance of the moment: the walk isn't kept (Record_Indirect_Draws())
m_indirectchanging = true;
}
if (isemissive) if (isemissive)
model_ubs.emission = Submodel->f4Emision.a; model_ubs.emission = Submodel->f4Emision.a;
@@ -7185,6 +7296,7 @@ void opengl46_renderer::Render(TSubModel *Submodel)
case rendermode::color: case rendermode::color:
case rendermode::reflections: case rendermode::reflections:
{ {
m_indirectchanging = true;
if (Global.fLuminance < Submodel->fLight) if (Global.fLuminance < Submodel->fLight)
{ {
Bind_Material(Submodel->m_material, Submodel); Bind_Material(Submodel->m_material, Submodel);
@@ -8662,6 +8774,8 @@ void opengl46_renderer::Update(double const Deltatime)
if( changed ) { if( changed ) {
// the heightmap terrain keeps copies of the textures of its layer materials // the heightmap terrain keeps copies of the textures of its layer materials
m_terrainmaterials.on_environment_change(); m_terrainmaterials.on_environment_change();
// the seasonal variants of the models are shown and hidden anew
m_walkrecords.clear();
} }
} }

View File

@@ -765,6 +765,39 @@ class opengl46_renderer : public gfx_renderer {
std::vector<indirect_draw> m_cascadedraws; std::vector<indirect_draw> m_cascadedraws;
std::vector<gl::instance_draw_command> m_cascadecommands; std::vector<gl::instance_draw_command> m_cascadecommands;
unsigned int m_cascaderecordframe { 0 }; unsigned int m_cascaderecordframe { 0 };
// the walks Record_Indirect_Draws() made in earlier frames (Global.gfx_gpucull_walkcache). what the walk of a group of instances
// in a lod band records depends on the model with its skins, the band and whether the pass draws shadows, not on the camera,
// so it's kept while the shared state of the model stays the same (TModel3d::state_version()) and used instead of walking the
// tree again. a walk meeting an animated submodel, one lit by the luminance or the stars isn't kept. the models drawn on their
// own (with an instance in the key) are walked every time
struct walk_band {
std::vector<indirect_draw> draws; // as recorded
float bandmin { 0.f };
std::uint32_t modelversion { 0 };
bool blending { false }; // the state the walk began in
bool valid { false };
};
struct walk_record {
unsigned int frame { 0 }; // used last
std::array<std::vector<walk_band>, 2> bands; // of the colour passes (the main view, the environment map) and of the shadows
};
std::unordered_map<scene::basic_cell::instance_bucket_key, walk_record, scene::basic_cell::instance_bucket_key_hash> m_walkrecords;
int m_walkrankcutoff { 0 }; // Global.gfx_shadow_rank_cutoff the walks were made with
bool m_indirectchanging { false }; // the walk under way met a submodel which may look different in the next frame
// what the walks of the frame did, for the panel: walks made, of them not kept, walks taken from earlier frames, and the cpu time
// of the walks and of putting the draws in order with their blocks uploaded
struct walk_counts {
std::uint32_t walked { 0 };
std::uint32_t changing { 0 };
std::uint32_t reused { 0 };
double walktime { 0.0 };
double ordertime { 0.0 };
};
walk_counts m_walkcounts;
walk_counts m_lastwalkcounts;
unsigned int m_walkcountsframe { 0 };
// 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 );
// multi draws of the recorded draws: whether they were recorded for them (ordered, their model blocks uploaded), and where // multi draws of the recorded draws: whether they were recorded for them (ordered, their model blocks uploaded), and where
// the blocks are // the blocks are
bool m_indirectmultidraw { false }; bool m_indirectmultidraw { false };

View File

@@ -1547,6 +1547,11 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token
Parser.getTokens(1); Parser.getTokens(1);
Parser >> gfx_gpucull_bucketrange; Parser >> gfx_gpucull_bucketrange;
} }
else if (Token == "gfx.gpucull.walkcache")
{
Parser.getTokens(1);
Parser >> gfx_gpucull_walkcache;
}
else if (Token == "gfx.depthprepass.scene") else if (Token == "gfx.depthprepass.scene")
{ {
Parser.getTokens(1); Parser.getTokens(1);
@@ -2124,6 +2129,7 @@ global_settings::export_as_text( std::ostream &Output ) const {
export_as_text( Output, "gfx.gpucull.sleepers", gfx_gpucull_sleepers ); export_as_text( Output, "gfx.gpucull.sleepers", gfx_gpucull_sleepers );
export_as_text( Output, "gfx.gpucull.models", gfx_gpucull_models ); export_as_text( Output, "gfx.gpucull.models", gfx_gpucull_models );
export_as_text( Output, "gfx.gpucull.bucketrange", gfx_gpucull_bucketrange ); export_as_text( Output, "gfx.gpucull.bucketrange", gfx_gpucull_bucketrange );
export_as_text( Output, "gfx.gpucull.walkcache", gfx_gpucull_walkcache );
export_as_text( Output, "gfx.depthprepass.scene", gfx_depthprepass_scene ); export_as_text( Output, "gfx.depthprepass.scene", gfx_depthprepass_scene );
export_as_text( Output, "gfx.depthprepass.replay", gfx_depthprepass_replay ); export_as_text( Output, "gfx.depthprepass.replay", gfx_depthprepass_replay );
export_as_text( Output, "gfx.grass", gfx_grass ); export_as_text( Output, "gfx.grass", gfx_grass );

View File

@@ -323,6 +323,10 @@ struct global_settings {
// the submodels of their model) are left out of a pass before the culling, the gpu one included, which would leave out // the submodels of their model) are left out of a pass before the culling, the gpu one included, which would leave out
// each of their instances anyway // each of their instances anyway
bool gfx_gpucull_bucketrange = true; bool gfx_gpucull_bucketrange = true;
// the walks of the submodel trees which record the draws of the instances culled on the gpu are kept from frame to frame and
// made again only when the model changes: the trees of animated models, ones lit by the luminance and the models drawn on their
// own are walked every time
bool gfx_gpucull_walkcache = true;
// (opengl 4.6) the depth of the whole opaque scene of the main view is drawn before its colour, the instances included // (opengl 4.6) the depth of the whole opaque scene of the main view is drawn before its colour, the instances included
bool gfx_depthprepass_scene = false; bool gfx_depthprepass_scene = false;
// (opengl 4.6) the depth pass of the scene (gfx_depthprepass_scene, or the one of the contact shadows) walks the scene for // (opengl 4.6) the depth pass of the scene (gfx_depthprepass_scene, or the one of the contact shadows) walks the scene for

View File

@@ -824,6 +824,18 @@ void TDynamicObject::ABuLittleUpdate(double ObjSqrDist)
// NBMX wrzesien, MC listopad: zuniwersalnione // NBMX wrzesien, MC listopad: zuniwersalnione
btnOn = false; // czy przywrócić stan domyślny po renderowaniu btnOn = false; // czy przywrócić stan domyślny po renderowaniu
// the submodels set below are shared with whatever else is drawn with the same models
for( auto *model : { mdModel, mdLoad, mdLowPolyInt, mdKabina } ) {
if( model != nullptr ) {
model->state_changed();
}
}
for( auto *model : mdAttachments ) {
if( model != nullptr ) {
model->state_changed();
}
}
if (mdLoad) // tymczasowo ładunek na poziom podłogi if (mdLoad) // tymczasowo ładunek na poziom podłogi
if (vFloor.z > 0.0) if (vFloor.z > 0.0)
mdLoad->GetSMRoot()->SetTranslate(modelShake + vFloor); mdLoad->GetSMRoot()->SetTranslate(modelShake + vFloor);