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:
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 ) ) {
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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 );
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user