diff --git a/model/AnimModel.cpp b/model/AnimModel.cpp index 2f9f8e71..72e14745 100644 --- a/model/AnimModel.cpp +++ b/model/AnimModel.cpp @@ -658,6 +658,11 @@ void TAnimModel::on_season_update() { void TAnimModel::RaPrepare() { // 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) on_season_update(); for (int i = 0; i < iNumLights; ++i) diff --git a/model/Model3d.cpp b/model/Model3d.cpp index 054f9eb7..3db543fb 100644 --- a/model/Model3d.cpp +++ b/model/Model3d.cpp @@ -1617,6 +1617,7 @@ void TModel3d::AddTo(TSubModel *tmp, TSubModel *SubModel) ++iSubModelsCount; // teraz jest o 1 submodel więcej iFlags |= 0x0200; // submodele są oddzielne m_lodboundsfound = false; // the new submodel may have its own range + state_changed(); }; TSubModel *TModel3d::GetFromName(std::string const &Name) const diff --git a/model/Model3d.h b/model/Model3d.h index f3023208..4ca7e345 100644 --- a/model/Model3d.h +++ b/model/Model3d.h @@ -294,6 +294,7 @@ private: bool m_instancesubmodelslocated { false }; std::vector m_lodbounds; // filled on first use, see lod_bounds() bool m_lodboundsfound { false }; + std::uint32_t m_stateversion { 0 }; // see state_version() public: TModel3d() = default; @@ -308,6 +309,13 @@ public: // 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() std::vector 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); nameoffset_sequence const & find_smoke_sources(); void AddTo(TSubModel *tmp, TSubModel *SubModel); diff --git a/model/Texture.cpp b/model/Texture.cpp index f8def211..71820861 100644 --- a/model/Texture.cpp +++ b/model/Texture.cpp @@ -46,6 +46,8 @@ struct texture_request { // handed to the workers (opengl_texture::prefetch()), and when: the main thread only bool submitted { false }; 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 std::vector data; bool failed { false }; @@ -1052,6 +1054,12 @@ opengl_texture::create( bool const Static ) { components_hint = GL_SRGB_ALPHA; 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); @@ -1090,7 +1098,8 @@ opengl_texture::create( bool const Static ) { } 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 data = std::vector(); data_state = resource_state::none; @@ -1133,17 +1142,40 @@ opengl_texture::complete_data() { 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 -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; } request->submitted = true; request->submittime = std::chrono::steady_clock::now(); + request->ahead = ( Origin != nullptr ); + if( Origin != nullptr ) { + request->origin = *Origin; + } 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 void opengl_texture::release() { @@ -1151,7 +1183,13 @@ opengl_texture::release() { if( id == -1 ) { 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(); + 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 // 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 @@ -1552,19 +1590,19 @@ texture_manager::memory() const { // has the data of a texture read, if it's waiting for that void -texture_manager::prefetch( texture_handle const Texture ) { +texture_manager::prefetch( texture_handle const Texture, glm::dvec3 const &Origin ) { if( ( Texture <= 0 ) || ( Texture >= static_cast( m_textures.size() ) ) ) { 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 -// a texture is needed +// 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. it's read again when a texture is needed 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() }; std::size_t count { 0 }; @@ -1574,8 +1612,11 @@ texture_manager::drop_read_ahead( std::chrono::steady_clock::duration const Age continue; } auto &request { *texture.first->request }; - if( ( false == request.submitted ) - || ( now - request.submittime < Age ) ) { + if( false == request.submitted ) { + continue; + } + if( ( now - request.submittime < Age ) + && ( ( false == request.ahead ) || ( glm::length2( Camera - request.origin ) <= Distance * Distance ) ) ) { continue; } if( true == texture_loader::instance().drop( request ) ) { diff --git a/model/Texture.h b/model/Texture.h index 87a9e046..8791a9f2 100644 --- a/model/Texture.h +++ b/model/Texture.h @@ -118,8 +118,11 @@ public: // takes over the data read by a worker thread, waiting for it if needed void complete_data(); - // has a worker thread read the data of the texture, if it's waiting for that (gfx.textures.streaming) - void prefetch(); + // has a worker thread read the data of the texture, if it's waiting for that (gfx.textures.streaming). Origin: the camera, + // 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 std::shared_ptr request; // data being read by a worker thread @@ -199,10 +202,11 @@ public: memory() const; // has the data of a texture read, if it's waiting for that (gfx.textures.streaming) void - prefetch( texture_handle const Texture ); - // gives back the data read for textures which nothing drew within the specified time since it was asked for + 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, or read ahead of + // the camera which went farther than the specified distance from there since 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: // types: diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index eddd180c..5cdc3cba 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -714,6 +714,7 @@ bool opengl46_renderer::Render() m_sleeperrecords.clear(); m_modelrecords.clear(); m_cascaderecords.clear(); + m_walkrecords.clear(); } // generate new frame opengl_texture::reset_unit_cache(); @@ -1139,6 +1140,14 @@ void opengl46_renderer::SwapBuffers() culltext += m_reflectioncull.info( "gpu cull reflections, last face", 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( { "GPU culling", std::move( culltext ), false } ); 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 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("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); 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; for( auto const &group : Cull.draw_groups() ) { auto *model { group.key->pModel }; 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 }; auto alpha { 0x30300030 }; 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 } ); } } - else { + else if( kept == nullptr ) { prepare(); } 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( walk.count ); continue; } - if( false == prepared ) { - prepare(); - } auto const walkfirst { m_indirectdraws.size() }; - ::glPushMatrix(); - ::glLoadIdentity(); - model->Root->fSquareDist = bandmin; - model->Root->ReplacableSet( skins, alpha ); - model->Root->pRoot = model; - Render( model->Root ); - ::glPopMatrix(); + auto *keptwalk { kept != nullptr ? &kept->bands[ shadows ? 1 : 0 ][ band ] : nullptr }; + if( ( keptwalk != nullptr ) + && ( keptwalk->valid ) + && ( keptwalk->bandmin == bandmin ) + && ( keptwalk->modelversion == modelversion ) + && ( keptwalk->blending == m_blendingenabled ) ) { + // 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 ) { walks->bands[ band ] = cascade_band { static_cast( m_cascadedraws.size() ), @@ -5138,6 +5212,8 @@ void opengl46_renderer::Record_Indirect_Draws( opengl46_instance_cull &Cull ) } } m_indirectrecording = false; + auto const orderstart { std::chrono::steady_clock::now() }; + m_walkcounts.walktime += std::chrono::duration( 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 // 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 ); + m_walkcounts.ordertime += std::chrono::duration( 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( 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( Walk.draws.size() ); + m_renderpass.draw_stats.drawcalls += static_cast( Walk.draws.size() ); } 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 range{static_cast(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) { return; @@ -6146,24 +6250,24 @@ void opengl46_renderer::Update_Texture_Prefetch() auto const &material{m_materials.material(Material)}; 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 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) { return; } 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 - // while - if (now - m_prefetchdroptime >= std::chrono::seconds(10)) + // 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 + // 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(2)) { 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); 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 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) model_ubs.emission = Submodel->f4Emision.a; @@ -7185,6 +7296,7 @@ void opengl46_renderer::Render(TSubModel *Submodel) case rendermode::color: case rendermode::reflections: { + m_indirectchanging = true; if (Global.fLuminance < Submodel->fLight) { Bind_Material(Submodel->m_material, Submodel); @@ -8662,6 +8774,8 @@ void opengl46_renderer::Update(double const Deltatime) if( changed ) { // the heightmap terrain keeps copies of the textures of its layer materials m_terrainmaterials.on_environment_change(); + // the seasonal variants of the models are shown and hidden anew + m_walkrecords.clear(); } } diff --git a/rendering/opengl46/opengl46renderer.h b/rendering/opengl46/opengl46renderer.h index 488a546b..8a6fa0b5 100644 --- a/rendering/opengl46/opengl46renderer.h +++ b/rendering/opengl46/opengl46renderer.h @@ -765,6 +765,39 @@ class opengl46_renderer : public gfx_renderer { std::vector m_cascadedraws; std::vector m_cascadecommands; 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 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, 2> bands; // of the colour passes (the main view, the environment map) and of the shadows + }; + std::unordered_map 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 // the blocks are bool m_indirectmultidraw { false }; diff --git a/utilities/Globals.cpp b/utilities/Globals.cpp index 0b0f1cfc..6866c235 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -1547,6 +1547,11 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token Parser.getTokens(1); Parser >> gfx_gpucull_bucketrange; } + else if (Token == "gfx.gpucull.walkcache") + { + Parser.getTokens(1); + Parser >> gfx_gpucull_walkcache; + } else if (Token == "gfx.depthprepass.scene") { 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.models", gfx_gpucull_models ); 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.replay", gfx_depthprepass_replay ); export_as_text( Output, "gfx.grass", gfx_grass ); diff --git a/utilities/Globals.h b/utilities/Globals.h index 85b8e11a..c12bee16 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -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 // each of their instances anyway 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 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 diff --git a/vehicle/DynObj.cpp b/vehicle/DynObj.cpp index af171d51..ca10bfcf 100644 --- a/vehicle/DynObj.cpp +++ b/vehicle/DynObj.cpp @@ -824,6 +824,18 @@ void TDynamicObject::ABuLittleUpdate(double ObjSqrDist) // NBMX wrzesien, MC listopad: zuniwersalnione 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 (vFloor.z > 0.0) mdLoad->GetSMRoot()->SetTranslate(modelShake + vFloor);