16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-18 00:49:19 +02:00

Build the spawn surroundings on the loading screen, centred on the spawn

Two bugs left the terrain and models around the camera unbuilt on a big scenery:
- the first visual pass centred its section set on Global.pCamera.Pos, which the
  driver hasn't positioned yet during load (reads 0,0,0) -> it built empty
  sections at the origin and the spawn stayed bare. Centre on the sampled eye
  (player / first vehicle) instead.
- the first (spawn) pass ran in the driver at a small per-frame budget on a live,
  low-fps scene, so it crawled and never finished. Run it on the loading screen
  (generous budget, nothing rendering), then hand to the driver once the spawn is
  ready; the driver streams the rest as the camera moves.

Also drops the per-node capture index: capturing params/paths for a million nodes
cost more than the rescans it saved. Every cycle is now a plain scan-and-build of
the wanted sections (it finishes; a bake-time section index is the real cure for
the remaining first-pass scan time).

Result on tomaszewo: spawn now builds + renders (41k instances, ~hundreds of fps
once loaded). Remaining: load time is long (the 1M-node scan) -- next targets are
the getToken scan, the 380us/model build, and the 13s create_map_geometry finalize.

Adds load + frame profilers (behind WriteLog) used to find all of the above.
This commit is contained in:
maj00r
2026-06-25 22:13:27 +02:00
parent ef4e99a582
commit 04c0a03659
2 changed files with 30 additions and 39 deletions

View File

@@ -681,6 +681,10 @@ void opengl33_renderer::SwapBuffers()
+ " traction: " + to_string( m_colorpass.draw_stats.traction, 7 ) + "\n"
+ " lines: " + to_string( m_colorpass.draw_stats.lines, 7 ) + "\n"
+ "particles: " + to_string( m_colorpass.draw_stats.particles, 7 );
// profile: dump the steady-state frame breakdown so the fps bottleneck is measured, not guessed
{ static int s_f = 0; if( ( ++s_f % 120 ) == 0 ) {
WriteLog( "=== FRAME PROFILE ===\n" + m_debugtimestext + "\n" + m_debugstatstext ); } }
}
void opengl33_renderer::draw_debug_ui()

View File

@@ -252,35 +252,21 @@ state_serializer::deserialize_continue(std::shared_ptr<deserializer_state> state
m_ringeye = state->ringeye;
m_tobuild = &state->tobuild;
// section streaming. the first pass replays the whole twin once, indexing every deferred
// node under its section while building the spawn area. afterwards (state->indexed) the
// sections the camera moves into are rebuilt by seeking straight to their nodes -- no more
// whole-twin re-scans, which is what was tanking fps / dragging on a million-node scenery.
// section streaming: each cycle replays the visual twin once, building the nodes whose
// section is wanted (within range of the centre) and skipping the rest in O(1). a new
// cycle starts whenever the camera has moved into sections not yet built. NOTE: capturing
// a per-node index to avoid these re-scans was tried but the capture itself (params/paths
// for a million nodes) cost more than the scans it saved -- a bake-time section index is
// the real fix; for now the plain scan at least finishes and shows the spawn.
if( true == state->sectionmode ) {
if( true == state->indexed ) {
if( true == state->tobuild.empty() ) {
if( 0 == wanted_sections( Global.pCamera.Pos, state->built, state->tobuild ) ) {
return true; // surroundings already built; stay alive for camera moves
}
}
m_rebuilding = true;
m_state = state.get();
auto streamstart { std::chrono::steady_clock::now() };
while( false == state->tobuild.empty() ) {
int const sec = *state->tobuild.begin();
state->tobuild.erase( state->tobuild.begin() );
rebuild_section( *state, sec );
state->built.insert( sec );
if( std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::steady_clock::now() - streamstart ).count() >= VISUAL_BUDGET_MS ) { break; }
}
m_rebuilding = false;
return true;
}
// first pass: index every deferred node while building the spawn area
m_indexing = true;
m_state = state.get();
if( false == state->pass_active ) {
wanted_sections( Global.pCamera.Pos, state->built, state->tobuild );
// centre on the spawn (player/first vehicle) until the game starts, then follow the
// live driver camera. NOT the live camera during the first pass -- it isn't
// positioned yet (reads (0,0,0)), which built empty sections and left the spawn bare.
glm::dvec3 const eye { ( false == state->initial_done ) ? state->ringeye : Global.pCamera.Pos };
if( 0 == wanted_sections( eye, state->built, state->tobuild ) ) {
return true; // nothing new in range; stay alive for camera moves
}
Input.restartReplay( scene::scenery_load_pass::visual );
resettransform();
state->pass_active = true;
@@ -304,10 +290,10 @@ state_serializer::deserialize_continue(std::shared_ptr<deserializer_state> state
{ "terrain", "endterrain" },
};
// deserialize content from the provided input. modest budget while streaming visuals in the
// driver (rendering is live, so a big slice would tank fps), generous budget while the loading
// screen is up (infrastructure pass, nothing rendering yet).
int const budget { state->visualphase ? VISUAL_BUDGET_MS : 200 };
// deserialize content from the provided input. generous budget until the spawn is built (the
// loading screen is up: infra pass + first visual pass, nothing rendering yet), then a modest
// budget once streaming in the driver, where a big slice would tank fps on a live scene.
int const budget { state->indexed ? VISUAL_BUDGET_MS : 200 };
auto timelast { std::chrono::steady_clock::now() };
std::string token { Input.getToken<std::string>() };
while( false == token.empty() ) {
@@ -400,8 +386,11 @@ state_serializer::deserialize_continue(std::shared_ptr<deserializer_state> state
resettransform();
g_profile.log( "infrastructure" );
g_profile.reset(); // measure the visual phase separately
WriteLog( "Progressive visual load: infrastructure ready, streaming visuals from the driver" );
return false; // infrastructure ready -> go to driver; visuals continue there
WriteLog( "Progressive visual load: infrastructure ready, building spawn surroundings" );
// stay on the loading screen for the first (spawn) pass: it's budgeted generously there
// (nothing rendering) so the surroundings are built fast, instead of crawling in the driver
// at a small budget on a low-fps scene. control passes to the driver once the spawn is ready.
return true;
}
// section streaming: a build cycle's replay pass just finished. mark its sections built so
@@ -413,19 +402,17 @@ state_serializer::deserialize_continue(std::shared_ptr<deserializer_state> state
state->tobuild.clear();
state->pass_active = false;
state->shapes_built = true; // explicit shapes + eager models are built in the first pass
m_indexing = false;
if( false == state->initial_done ) {
finalize( /*Closegroups*/ false ); // keep groups open for later cycles
state->initial_done = true;
state->indexed = true; // the first pass has indexed every deferred node by section
std::size_t refs = 0; for( auto const &s : state->index ) { refs += s.second.size(); }
WriteLog( "Progressive visual load: spawn ready (" + std::to_string( simulation::Instances.sequence().size() )
+ " instances), " + std::to_string( refs ) + " nodes indexed across " + std::to_string( state->index.size() ) + " sections" );
state->indexed = true; // first (spawn) pass done -> switch to the modest driver budget
WriteLog( "Progressive visual load: spawn ready (" + std::to_string( simulation::Instances.sequence().size() ) + " instances)" );
g_profile.log( "visual first pass" );
g_profile.reset();
return false; // spawn built on the loading screen -> hand to the driver; stream the rest there
}
}
return true; // keep streaming alive; sections the camera enters are served from the index
return true; // keep streaming alive; sections the camera enters are rebuilt as it moves
}
// build-all (no camera centre, e.g. ghostview): everything was built in this single pass.