17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-30 17:50:29 +01:00

Record the opaque scene once and replay it for the depth prepass and the colour pass

This commit is contained in:
2026-10-18 21:00:54 +02:00
parent 2e30366235
commit 64dedb9d53
7 changed files with 616 additions and 51 deletions

View File

@@ -214,6 +214,9 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
m_alpha_shadow_multidraw_shader = std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{shadowvertex, alphashadowfragment});
m_drawblockstream = std::make_unique<gl::frame_stream>(
static_cast<GLsizeiptr>(4096 * sizeof(gl::model_ubs)), static_cast<GLsizeiptr>(sizeof(gl::model_ubs)), Global.gfx_bufferstreaming);
// and the commands of the multi draws of the recorded depth pass of the scene (Render_Scene_Prepass())
m_scenecommandstream = std::make_unique<gl::frame_stream>(
static_cast<GLsizeiptr>(4096 * sizeof(gl::instance_draw_command)), static_cast<GLsizeiptr>(sizeof(gl::instance_draw_command)), Global.gfx_bufferstreaming);
}
catch (gl::shader_exception const &e)
{
@@ -224,6 +227,7 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
m_shadow_multidraw_shader.reset();
m_alpha_shadow_multidraw_shader.reset();
m_drawblockstream.reset();
m_scenecommandstream.reset();
}
try
@@ -712,6 +716,8 @@ bool opengl46_renderer::Render()
model_ubo->next_frame();
if (m_drawblockstream)
m_drawblockstream->next_frame();
if (m_scenecommandstream)
m_scenecommandstream->next_frame();
m_renderpass.draw_mode = rendermode::none; // force setup anew
m_renderpass.draw_stats = debug_stats();
@@ -851,7 +857,12 @@ void opengl46_renderer::SwapBuffers()
+ " =" + to_string( m_colorpass.draw_stats.instances + shadowstats.instances, 7 )
+ " (" + std::to_string( m_colorpass.draw_stats.instanced_drawcalls + shadowstats.instanced_drawcalls ) + " batches)\n"
+ ( ( Global.gfx_depthprepass_scene || m_contactshadowslast ) ?
" scene depth: " + std::to_string( m_scenedepthstats.drawcalls ) + " draws, cpu " + to_string( m_scenedepthcputime, 2 ) + " ms\n" :
( m_scenereplayed ?
" scene depth: " + std::to_string( m_sceneprepassdraws ) + " of " + std::to_string( m_scenerecordedparts ) + " recorded parts in "
+ std::to_string( m_sceneprepasscalls ) + " calls"
+ ( m_scenebeyondreach > 0 ? ", " + std::to_string( m_scenebeyondreach ) + " beyond the contact shadows" : std::string{} )
+ ", cpu " + to_string( m_scenedepthcputime, 2 ) + " ms with the walk of the colour pass\n" :
" scene depth: " + std::to_string( m_scenedepthstats.drawcalls ) + " draws, cpu " + to_string( m_scenedepthcputime, 2 ) + " ms\n" ) :
std::string{} )
+ ( Global.gfx_contactshadows > 0 ?
" contact shadows: " + std::string{ Global.gfx_contactshadows == 3 ? "high" : Global.gfx_contactshadows == 2 ? "medium" : "low" }
@@ -1014,6 +1025,7 @@ void opengl46_renderer::draw_debug_ui()
ImGui::Checkbox("draw the depth of the whole scene before its colour (gfx.depthprepass.scene)", &Global.gfx_depthprepass_scene);
if (Global.gfx_contactshadows > 0)
ImGui::TextUnformatted("(drawn anyway for the contact shadows, gfx.contactshadows)");
ImGui::Checkbox("walk the scene once for its depth and colour, drawing both from a record (gfx.depthprepass.replay)", &Global.gfx_depthprepass_replay);
auto info{m_instancecull.info()};
if (Global.gfx_gpucull_shadows)
{
@@ -1279,12 +1291,13 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
// the depth of the whole opaque scene before its colour, which the contact shadows need
m_scenedepthdrawn = false;
m_indirectcolourpending = false;
m_scenerecorded = false;
auto const contactshadows{Contact_Shadows_Wanted(vp)};
if ((Global.gfx_depthprepass_scene || contactshadows) && vp.main && (false == Global.bWireFrame))
{
glDebug("render scene depth");
gpupart("scene depth");
Render_Scene_Depth(future);
Render_Scene_Depth(future, contactshadows && (false == Global.gfx_depthprepass_scene));
if (contactshadows)
{
gpupart("contact shadows depth");
@@ -1298,27 +1311,37 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
gpupart("colour, opaque");
setup_drawing(false);
// without rain/snow we can render the cab early to limit the overdraw
// precipitation happens when overcast is in 1-2 range
if (!FreeFlyModeFlag && Global.Overcast <= 1.0f && Global.render_cab)
if (m_scenerecorded)
{
glDebug("render opaque cab");
model_ubs.future = glm::mat4();
auto const *vehicle{ simulation::Train->Dynamic() };
// the lamp lighting the shadows of the sun, unless it is a light of its own
if( ( vehicle->InteriorLightLevel > 0.f ) && ( false == m_cablamplit ) ) {
setup_shadow_color( glm::min( colors::white, m_shadowcolor + glm::vec4( vehicle->InteriorLight * vehicle->InteriorLightLevel, 1.f ) ) );
}
Render_cab( vehicle, vehicle->InteriorLightLevel, false );
// the depth pass walked the scene for this pass, which draws what it recorded (the cab drawn early included)
glDebug("render recorded opaque scene");
Render_Scene_Colour();
m_scenerecorded = false;
}
else
{
// without rain/snow we can render the cab early to limit the overdraw
// precipitation happens when overcast is in 1-2 range
if (!FreeFlyModeFlag && Global.Overcast <= 1.0f && Global.render_cab)
{
glDebug("render opaque cab");
model_ubs.future = glm::mat4();
auto const *vehicle{ simulation::Train->Dynamic() };
// the lamp lighting the shadows of the sun, unless it is a light of its own
if( ( vehicle->InteriorLightLevel > 0.f ) && ( false == m_cablamplit ) ) {
setup_shadow_color( glm::min( colors::white, m_shadowcolor + glm::vec4( vehicle->InteriorLight * vehicle->InteriorLightLevel, 1.f ) ) );
}
Render_cab( vehicle, vehicle->InteriorLightLevel, false );
}
setup_shadow_color( m_shadowcolor );
setup_shadow_color( m_shadowcolor );
glDebug("render opaque region");
glDebug("render opaque region");
model_ubs.future = future;
model_ubs.future = future;
Render(simulation::Region);
Render(simulation::Region);
}
m_scenedepthdrawn = false;
m_indirectcolourpending = false;
@@ -2327,6 +2350,13 @@ void opengl46_renderer::setup_sunlight_intensity( float const Factor ) {
// the header of the light buffer and the sun in its first slot. the headlights are written once per pass by Update_Lights()
void opengl46_renderer::upload_light_header()
{
if( m_scenerecording ) {
// the recorded walk of Render_Scene_Depth() draws nothing lit: the draws which follow go with this header, uploaded
// when the colour pass draws them
auto &header { m_sceneheaders.emplace_back() };
std::memcpy( header.data(), &light_data, header.size() );
return;
}
light_ssbo->update( &light_data, 0, offsetof( gl::light_data, lights ) + sizeof( gl::light_element ) );
}
@@ -2568,6 +2598,7 @@ bool opengl46_renderer::Replace(gfx::vertex_array &Vertices, gfx::userdata_array
{
gfx::calculate_tangents(Vertices, gfx::index_array(), Type);
m_chunkspheres.erase(static_cast<std::uint64_t>(Geometry));
return m_geometry.replace(Vertices, Userdata, Geometry, Offset);
}
@@ -2576,6 +2607,7 @@ bool opengl46_renderer::Append(gfx::vertex_array &Vertices, gfx::userdata_array
{
gfx::calculate_tangents(Vertices, gfx::index_array(), Type);
m_chunkspheres.erase(static_cast<std::uint64_t>(Geometry));
return m_geometry.append(Vertices, Userdata, Geometry);
}
@@ -2736,6 +2768,15 @@ void opengl46_renderer::Bind_Material( material_handle const Material, TSubModel
m_indirectsubmodel = sm;
return;
}
if (m_scenerecording)
{
// the recorded walk of Render_Scene_Depth(): the draws which follow are made with this material, bound later
m_scenematerial = Material;
m_scenesubmodel = sm;
m_sceneseed = TSubModel::iVariantSeed;
m_scenevariant = (material.texture_variants.empty() ? 0 : TSubModel::VariantId(sm));
return;
}
// materials which declare texture variants provide a different texture set per model instance,
// picked from the seed of the rendered instance and the variant group of the submodel
@@ -3010,6 +3051,11 @@ void opengl46_renderer::Render(scene::basic_region *Region)
// when editor mode is active calculate world position of the cursor
// at this stage the z-buffer is filled with only ground geometry
// (with the depth pass of the whole scene it's done there, at the same stage)
if (m_scenerecording)
{
// the depth of what the walk recorded so far
Render_Scene_Prepass();
}
Update_Mouse_Position();
}
// draw opaque cells front-to-back: with the depth test enabled this
@@ -3405,25 +3451,7 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
// (For pickscenery/pickcontrols modes the map stays empty, so this is a no-op.)
if( m_indirectcolourpending ) {
// the instances culled and recorded by the depth pass of the whole scene, the buckets weren't queued for the cpu
m_indirectcolourpending = false;
indirect_counts colourcounts;
if( false == m_indirectdraws.empty() ) {
auto const cpustart { std::chrono::steady_clock::now() };
m_indirectcull = &m_instancecull;
m_instancecull.bind_draw();
colourcounts = Render_Instances_Indirect_Colour( m_indirectcolourset );
::glBindBuffer( GL_DRAW_INDIRECT_BUFFER, 0 );
Bind_Instance_Stream();
m_instancecull.draw_result(
colourcounts.draws,
m_instancecull.prepass_draws(),
colourcounts.calls,
m_instancecull.prepass_calls(),
m_instancecull.cpu_time() + std::chrono::duration<double, std::milli>( std::chrono::steady_clock::now() - cpustart ).count() );
}
m_renderpass.draw_stats.instanced_drawcalls += static_cast<int>( colourcounts.calls );
m_renderpass.draw_stats.instances += static_cast<int>( m_instancecull.last_count() );
m_renderpass.draw_stats.models += static_cast<int>( m_instancecull.last_count() );
Render_Instances_Indirect_Pending();
}
if( m_instancecullpass ) {
if( m_passcull->drawing() ) {
@@ -3492,6 +3520,11 @@ void opengl46_renderer::draw(const gfx::geometry_handle &handle)
}
return;
}
if( m_scenerecording ) {
// the recorded walk of Render_Scene_Depth(): the depth pass and the colour pass draw it later, with this state
Record_Scene_Draw( &handle, 1, false );
return;
}
if( m_scenedepthpass && ( false == Scene_Depth_Draws( handle ) ) ) {
return;
}
@@ -3510,6 +3543,12 @@ void opengl46_renderer::draw(const gfx::geometry_handle &handle)
void opengl46_renderer::draw(std::vector<gfx::geometrybank_handle>::iterator it, std::vector<gfx::geometrybank_handle>::iterator end)
{
model_ubs.set_modelview(OpenGLMatrices.data(GL_MODELVIEW));
if( m_scenerecording ) {
if( it != end ) {
Record_Scene_Draw( &( *it ), static_cast<std::size_t>( std::distance( it, end ) ), true );
}
return;
}
if( m_scenedepthpass && ( ( it == end ) || ( false == Scene_Depth_Draws( *it ) ) ) ) {
return;
}
@@ -4045,6 +4084,11 @@ void opengl46_renderer::Render_Instances_Indirect()
auto const cpustart { std::chrono::steady_clock::now() };
auto *viewport { m_current_viewport };
if( m_scenerecording ) {
// the depth of what the recorded walk of the depth pass of the scene went through so far, for the occlusion test
// and for the depth of the instances to be tested against
Render_Scene_Prepass();
}
// occlusion test against the depth drawn so far in the pass: cab, terrain, shapes, tracks, vehicles, other models
opengl46_instance_cull::occlusion_setup occlusion;
auto const useocclusion {
@@ -4114,6 +4158,12 @@ void opengl46_renderer::Render_Instances_Indirect()
m_indirectcolourpending = true;
m_indirectcolourset = colourset;
m_indirectcoloursleepers = m_sleeperspass;
if( m_scenerecording ) {
// where the colour pass, which draws the record, draws them, in the light the walk had here
auto &marker { m_scenedraws.emplace_back() };
marker.instances = true;
marker.header = static_cast<std::uint32_t>( m_sceneheaders.size() - 1 );
}
m_renderpass.draw_stats.drawcalls += static_cast<int>( prepasscounts.calls );
colourcounts.draws = static_cast<std::uint32_t>( m_indirectdraws.size() );
}
@@ -4647,7 +4697,7 @@ gl::program *opengl46_renderer::Multidraw_Program( gl::program const *Program )
// them (Bind_Material(), draw()), with the depth a little farther than the surface like the prepass of the instances, so the
// colour pass, which tests and writes its depth as usual, passes where it's the same surface. the gpu culling of the
// instances runs here, against the same depth as without this pass, and the colour pass draws what it recorded
void opengl46_renderer::Render_Scene_Depth( glm::mat4 const &Future )
void opengl46_renderer::Render_Scene_Depth( glm::mat4 const &Future, bool const Contactonly )
{
auto const cpustart { std::chrono::steady_clock::now() };
m_scenedepthtimer.begin();
@@ -4661,14 +4711,63 @@ void opengl46_renderer::Render_Scene_Depth( glm::mat4 const &Future )
::glEnable( GL_POLYGON_OFFSET_FILL );
::glPolygonOffset( -1.f, -1.f );
// the conditions of the cab drawn early by the colour pass
if( ( false == FreeFlyModeFlag ) && ( Global.Overcast <= 1.0f ) && ( Global.render_cab ) ) {
model_ubs.future = glm::mat4();
auto const *vehicle { simulation::Train->Dynamic() };
Render_cab( vehicle, vehicle->InteriorLightLevel, false );
m_scenereplayed = Global.gfx_depthprepass_replay;
if( m_scenereplayed ) {
// the walk of the colour pass, recorded: the same steps as in Render_pass(), the shadow tone of the cab lamp included
m_scenerecording = true;
m_scenedraws.clear();
m_scenechunks.clear();
m_scenegeometry.clear();
m_sceneheaders.clear();
std::memcpy( m_sceneheaders.emplace_back().data(), &light_data, std::tuple_size_v<light_header> );
m_sceneflushed = 0;
m_scenematerial = null_handle;
m_scenesubmodel = nullptr;
m_sceneseed = TSubModel::iVariantSeed;
m_scenevariant = 0;
m_sceneprepassdraws = 0;
m_sceneprepasscalls = 0;
m_scenebeyondreach = 0;
// the contact shadows alone need the depth only within their reach: a ray of a pixel there runs up to their length
// towards the light, and a surface counts as in its way up to 0.3 m and 4 pixels behind it (see contact_shadow()).
// past that the colour pass draws with no depth before it, as without the depth pass. the cursor of the editor reads
// the depth of the ground anywhere
m_scenedepthreach = ( Contactonly && ( false == EditorModeFlag ) );
if( m_scenedepthreach ) {
auto const reach { Global.gfx_contactshadows_range + Global.gfx_contactshadows_length };
auto const scale { scene_ubs.projection[ 1 ][ 1 ] };
auto const height { ( m_current_viewport != nullptr ? m_current_viewport->height : Global.gfx_framebuffer_height ) };
auto const pixel { ( ( scale > 0.f ) && ( height > 0 ) ) ? 2.f * reach / ( scale * static_cast<float>( height ) ) : reach };
m_scenedepthlimit = reach + 0.3f + 4.f * pixel + 1.f;
}
if( ( false == FreeFlyModeFlag ) && ( Global.Overcast <= 1.0f ) && ( Global.render_cab ) ) {
model_ubs.future = glm::mat4();
auto const *vehicle { simulation::Train->Dynamic() };
if( ( vehicle->InteriorLightLevel > 0.f ) && ( false == m_cablamplit ) ) {
setup_shadow_color( glm::min( colors::white, m_shadowcolor + glm::vec4( vehicle->InteriorLight * vehicle->InteriorLightLevel, 1.f ) ) );
}
Render_cab( vehicle, vehicle->InteriorLightLevel, false );
}
setup_shadow_color( m_shadowcolor );
model_ubs.future = Future;
Render( simulation::Region );
// the depth of what's left
Render_Scene_Prepass();
m_scenerecording = false;
m_scenerecorded = true;
m_scenerecordedparts = static_cast<std::uint32_t>( m_scenechunks.size() );
}
else {
// the conditions of the cab drawn early by the colour pass
if( ( false == FreeFlyModeFlag ) && ( Global.Overcast <= 1.0f ) && ( Global.render_cab ) ) {
model_ubs.future = glm::mat4();
auto const *vehicle { simulation::Train->Dynamic() };
Render_cab( vehicle, vehicle->InteriorLightLevel, false );
}
model_ubs.future = Future;
Render( simulation::Region );
}
model_ubs.future = Future;
Render( simulation::Region );
::glDisable( GL_POLYGON_OFFSET_FILL );
::glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
@@ -4678,10 +4777,351 @@ void opengl46_renderer::Render_Scene_Depth( glm::mat4 const &Future )
m_scenedepthstats = m_renderpass.draw_stats;
m_renderpass.draw_stats = colourstats;
if( m_scenereplayed ) {
// the walk was the one of the colour pass, which counts it
m_renderpass.draw_stats += m_scenedepthstats;
}
m_scenedepthtimer.end();
m_scenedepthcputime = std::chrono::duration<double, std::milli>( std::chrono::steady_clock::now() - cpustart ).count();
}
void opengl46_renderer::Record_Scene_Draw( gfx::geometry_handle const *First, std::size_t const Count, bool const Batch )
{
auto const index { static_cast<std::uint32_t>( m_scenedraws.size() ) };
auto &draw { m_scenedraws.emplace_back() };
draw.ubs = model_ubs;
draw.material = m_scenematerial;
draw.submodel = m_scenesubmodel;
draw.seed = m_sceneseed;
draw.variant = m_scenevariant;
draw.chunkfirst = static_cast<std::uint32_t>( m_scenechunks.size() );
draw.chunkcount = static_cast<std::uint32_t>( Count );
// a batch is drawn without instances, as Draw_Geometry() draws it
draw.instancecount = ( Batch ? 0 : static_cast<std::uint32_t>( m_current_instance_count ) );
draw.instancebase = ( draw.instancecount > 0 ? m_current_instance_base : 0 );
draw.instancebuffer = ( draw.instancecount > 0 ? m_instancestream->buffer() : 0 );
draw.header = static_cast<std::uint32_t>( m_sceneheaders.size() - 1 );
draw.blending = m_blendingenabled;
draw.batch = Batch;
// what the depth pass leaves out, as Bind_Material() and draw() do without the record (V41): the blended draws, those of
// negative opacity, of materials the depth programs can't stand for, and the ones with the alpha test without a texture
draw.prepassclass = 0;
draw.prepasstexture = null_handle;
if( ( false == m_blendingenabled ) && ( model_ubs.opacity >= 0.f ) ) {
draw.prepassclass = Prepass_Class( m_scenematerial );
if( draw.prepassclass == 2 ) {
// the diffuse texture of the colour pass, the same choice as Bind_Material() makes
auto const &material { m_materials.material( m_scenematerial ) };
auto const &textures { material.texture_variants.empty() ? material.textures : material.GetTextures( m_scenevariant ) };
draw.prepasstexture = textures[ 0 ];
if( draw.prepasstexture == null_handle ) {
draw.prepassclass = 0;
}
}
}
// the sphere of a chunk of a draw which isn't instanced, in the view: centre and the radius scaled with the model
glm::mat3 const rotationscale { draw.ubs.modelview };
auto const scale { std::sqrt( std::max( { glm::length2( rotationscale[ 0 ] ), glm::length2( rotationscale[ 1 ] ), glm::length2( rotationscale[ 2 ] ) } ) ) };
for( std::size_t chunk = 0; chunk < Count; ++chunk ) {
auto const &geometry { First[ chunk ] };
auto depth { draw.prepassclass != 0 };
if( depth ) {
// the program of the prepass doesn't size points, and lines aren't surfaces
auto const type { m_geometry.type( geometry ) };
depth = ( ( type == GL_TRIANGLES ) || ( type == GL_TRIANGLE_STRIP ) || ( type == GL_TRIANGLE_FAN ) );
}
if( depth && m_scenedepthreach && ( draw.instancecount == 0 ) ) {
auto const &sphere { Chunk_Sphere( geometry ) };
if( sphere.w >= 0.f ) {
auto const centre { glm::vec3{ draw.ubs.modelview * glm::vec4{ glm::vec3{ sphere }, 1.f } } };
if( glm::length( centre ) - sphere.w * scale > m_scenedepthlimit ) {
depth = false;
++m_scenebeyondreach;
}
}
}
m_scenechunks.push_back( { geometry, index, depth } );
m_scenegeometry.push_back( geometry );
}
}
void opengl46_renderer::Render_Scene_Prepass()
{
auto const end { m_scenechunks.size() };
if( m_sceneflushed >= end ) { return; }
// the chunks the depth is drawn for, in runs of the same program, texture of the alpha test, bank, primitive and instance
// buffer. the depth doesn't depend on the order of the draws; within a bank the chunks keep the order of the walk
m_sceneprepassitems.clear();
for( auto chunkindex { m_sceneflushed }; chunkindex < end; ++chunkindex ) {
auto const &chunk { m_scenechunks[ chunkindex ] };
if( false == chunk.depth ) { continue; }
auto const &draw { m_scenedraws[ chunk.draw ] };
gfx::indirect_source source;
if( false == m_geometry.indirect_source( chunk.geometry, source ) ) { continue; }
auto &item { m_sceneprepassitems.emplace_back() };
item.prepassclass = draw.prepassclass;
item.texture = ( draw.prepassclass == 2 ? draw.prepasstexture : null_handle );
item.bank = chunk.geometry.bank;
item.type = source.type;
item.indexed = ( source.indexed ? 1u : 0u );
item.instancebuffer = draw.instancebuffer;
item.chunk = static_cast<std::uint32_t>( chunkindex );
// count, instances, first, base vertex (the base instance of an array draw), base instance
auto const instances { std::max<std::uint32_t>( draw.instancecount, 1 ) };
item.command = {
source.count,
instances,
source.first,
( source.indexed ? static_cast<std::uint32_t>( source.basevertex ) : draw.instancebase ),
( source.indexed ? draw.instancebase : 0u ),
0, item.indexed, 0 };
}
m_sceneflushed = end;
if( m_sceneprepassitems.empty() ) { return; }
std::sort(
std::begin( m_sceneprepassitems ), std::end( m_sceneprepassitems ),
[]( scene_prepass_item const &Left, scene_prepass_item const &Right ) { return Left.tie() < Right.tie(); } );
auto const multidraw { ( m_drawblockstream != nullptr ) && ( m_scenecommandstream != nullptr ) };
GLuint const frameinstances { m_instancestream->buffer() };
GLuint boundinstances { frameinstances };
auto const bind_instances = [ & ]( GLuint const Buffer ) {
// a draw which isn't instanced reads the identity at the start of any of the buffers
if( ( Buffer == 0 ) || ( Buffer == boundinstances ) ) { return; }
::glBindBuffersBase( GL_SHADER_STORAGE_BUFFER, gl::INSTANCE_SSBO_BINDING, 1, &Buffer );
boundinstances = Buffer;
};
auto const count { m_sceneprepassitems.size() };
auto const samerun = []( scene_prepass_item const &Left, scene_prepass_item const &Right ) {
return ( Left.prepassclass == Right.prepassclass )
&& ( Left.texture == Right.texture )
&& ( Left.bank == Right.bank )
&& ( Left.type == Right.type )
&& ( Left.indexed == Right.indexed )
&& ( Left.instancebuffer == Right.instancebuffer ); };
if( multidraw ) {
// the model blocks and the commands of all of them go to the buffers of the frame at once, a run is one call
m_sceneprepassblocks.clear();
m_sceneprepasscommands.clear();
for( auto const &item : m_sceneprepassitems ) {
m_sceneprepassblocks.push_back( m_scenedraws[ m_scenechunks[ item.chunk ].draw ].ubs );
m_sceneprepasscommands.push_back( item.command );
}
auto const blockoffset { m_drawblockstream->push( m_sceneprepassblocks.data(), static_cast<GLsizeiptr>( count * sizeof( gl::model_ubs ) ) ) };
GLuint const blockbuffer { m_drawblockstream->buffer() };
auto const commandoffset { m_scenecommandstream->push( m_sceneprepasscommands.data(), static_cast<GLsizeiptr>( count * sizeof( gl::instance_draw_command ) ) ) };
::glBindBuffer( GL_DRAW_INDIRECT_BUFFER, m_scenecommandstream->buffer() );
auto boundclass { 0 };
for( std::size_t first = 0; first < count; ) {
auto const &item { m_sceneprepassitems[ first ] };
auto last { first + 1 };
while( ( last < count ) && samerun( item, m_sceneprepassitems[ last ] ) ) {
++last;
}
auto const drawcount { last - first };
auto const run { first };
first = last;
if( ( item.prepassclass == 2 ) && ( false == m_textures.mark_as_used( item.texture ).bind( 0 ) ) ) {
// a texture which isn't there (yet) would leave the one bound before to the alpha test
continue;
}
if( item.prepassclass != boundclass ) {
( item.prepassclass == 2 ? m_prepass_alpha_multidraw_shader : m_prepass_multidraw_shader )->bind();
boundclass = item.prepassclass;
}
bind_instances( item.instancebuffer );
GLintptr const offset { blockoffset + static_cast<GLintptr>( run * sizeof( gl::model_ubs ) ) };
GLsizeiptr const size { static_cast<GLsizeiptr>( drawcount * sizeof( gl::model_ubs ) ) };
::glBindBuffersRange( GL_SHADER_STORAGE_BUFFER, gl::MODEL_DRAW_SSBO_BINDING, 1, &blockbuffer, &offset, &size );
m_geometry.draw_indirect_multi(
m_scenechunks[ item.chunk ].geometry,
commandoffset + static_cast<std::intptr_t>( run * sizeof( gl::instance_draw_command ) ),
static_cast<std::uint32_t>( drawcount ),
sizeof( gl::instance_draw_command ) );
m_sceneprepassdraws += static_cast<std::uint32_t>( drawcount );
++m_sceneprepasscalls;
}
::glBindBuffer( GL_DRAW_INDIRECT_BUFFER, 0 );
}
else {
// one by one, with the model block of each draw, as without the record
auto const walkubs { model_ubs };
auto boundclass { 0 };
auto drawn { false };
for( std::size_t first = 0; first < count; ) {
auto const &item { m_sceneprepassitems[ first ] };
auto last { first + 1 };
while( ( last < count ) && samerun( item, m_sceneprepassitems[ last ] ) ) {
++last;
}
auto const run { first };
first = last;
if( ( item.prepassclass == 2 ) && ( false == m_textures.mark_as_used( item.texture ).bind( 0 ) ) ) {
continue;
}
if( item.prepassclass != boundclass ) {
( item.prepassclass == 2 ? m_prepass_alpha_shader : m_prepass_shader )->bind();
boundclass = item.prepassclass;
}
bind_instances( item.instancebuffer );
for( auto index { run }; index < last; ++index ) {
auto const &chunk { m_scenechunks[ m_sceneprepassitems[ index ].chunk ] };
auto const &draw { m_scenedraws[ chunk.draw ] };
model_ubs = draw.ubs;
model_ubo->update( model_ubs );
if( draw.instancecount > 0 ) {
m_geometry.draw_instanced( chunk.geometry, draw.instancecount, draw.instancebase );
}
else {
m_geometry.draw( chunk.geometry );
}
++m_sceneprepassdraws;
++m_sceneprepasscalls;
drawn = true;
}
}
// the walk goes on with its own state
model_ubs = walkubs;
if( drawn ) {
model_ubo->update( model_ubs );
}
}
if( boundinstances != frameinstances ) {
Bind_Instance_Stream();
}
}
void opengl46_renderer::Render_Scene_Colour()
{
// what the walk left behind, which the passes after this one go on with
auto const walkubs { model_ubs };
auto const walkseed { TSubModel::iVariantSeed };
// the light buffer has the header the walk began with (it uploaded none while recording)
std::uint32_t header { 0 };
auto blending { false };
auto bound { false };
material_handle boundmaterial { null_handle };
std::uint32_t boundvariant { 0 };
GLuint const frameinstances { m_instancestream->buffer() };
GLuint boundinstances { frameinstances };
for( auto const &draw : m_scenedraws ) {
if( draw.instances ) {
// the instances culled in the walk, where the colour pass would draw them
if( blending ) {
setup_drawing( false );
blending = false;
}
if( draw.header != header ) {
light_ssbo->update( m_sceneheaders[ draw.header ].data(), 0, std::tuple_size_v<light_header> );
header = draw.header;
}
if( m_indirectcolourpending ) {
Render_Instances_Indirect_Pending();
}
// they bind their own programs, textures and the instance buffer of the frame
bound = false;
boundinstances = m_instancestream->buffer();
continue;
}
if( draw.header != header ) {
light_ssbo->update( m_sceneheaders[ draw.header ].data(), 0, std::tuple_size_v<light_header> );
header = draw.header;
}
if( draw.blending != blending ) {
// the transparency hack of submodels faded out
setup_drawing( draw.blending );
blending = draw.blending;
}
if( ( false == bound ) || ( draw.material != boundmaterial ) || ( draw.variant != boundvariant ) ) {
// the textures of the variant follow from the seed of the vehicle and the submodel
TSubModel::iVariantSeed = draw.seed;
Bind_Material( draw.material, draw.submodel );
bound = true;
boundmaterial = draw.material;
boundvariant = draw.variant;
}
model_ubs = draw.ubs;
model_ubo->update( model_ubs );
auto const *chunks { m_scenegeometry.data() + draw.chunkfirst };
if( draw.instancecount > 0 ) {
if( draw.instancebuffer != boundinstances ) {
::glBindBuffersBase( GL_SHADER_STORAGE_BUFFER, gl::INSTANCE_SSBO_BINDING, 1, &draw.instancebuffer );
boundinstances = draw.instancebuffer;
}
m_geometry.draw_instanced( chunks[ 0 ], draw.instancecount, draw.instancebase );
}
else if( draw.batch ) {
m_geometry.draw_batch( chunks, chunks + draw.chunkcount );
}
else {
m_geometry.draw( chunks[ 0 ] );
}
}
if( blending ) {
setup_drawing( false );
}
if( header + 1 != m_sceneheaders.size() ) {
// the header the walk ended with
light_ssbo->update( m_sceneheaders.back().data(), 0, std::tuple_size_v<light_header> );
}
if( boundinstances != m_instancestream->buffer() ) {
Bind_Instance_Stream();
}
model_ubs = walkubs;
TSubModel::iVariantSeed = walkseed;
}
void opengl46_renderer::Render_Instances_Indirect_Pending()
{
m_indirectcolourpending = false;
indirect_counts colourcounts;
if( false == m_indirectdraws.empty() ) {
auto const cpustart { std::chrono::steady_clock::now() };
m_indirectcull = &m_instancecull;
m_instancecull.bind_draw();
colourcounts = Render_Instances_Indirect_Colour( m_indirectcolourset );
::glBindBuffer( GL_DRAW_INDIRECT_BUFFER, 0 );
Bind_Instance_Stream();
m_instancecull.draw_result(
colourcounts.draws,
m_instancecull.prepass_draws(),
colourcounts.calls,
m_instancecull.prepass_calls(),
m_instancecull.cpu_time() + std::chrono::duration<double, std::milli>( std::chrono::steady_clock::now() - cpustart ).count() );
}
m_renderpass.draw_stats.instanced_drawcalls += static_cast<int>( colourcounts.calls );
m_renderpass.draw_stats.instances += static_cast<int>( m_instancecull.last_count() );
m_renderpass.draw_stats.models += static_cast<int>( m_instancecull.last_count() );
}
glm::vec4 const &opengl46_renderer::Chunk_Sphere( gfx::geometry_handle const &Geometry )
{
auto const key { static_cast<std::uint64_t>( Geometry ) };
if( auto const lookup { m_chunkspheres.find( key ) }; lookup != m_chunkspheres.end() ) {
return lookup->second;
}
glm::vec4 sphere { 0.f, 0.f, 0.f, -1.f };
auto const &vertices { m_geometry.vertices( Geometry ) };
if( false == vertices.empty() ) {
glm::vec3 low { std::numeric_limits<float>::max() };
glm::vec3 high { std::numeric_limits<float>::lowest() };
for( auto const &vertex : vertices ) {
low = glm::min( low, vertex.position );
high = glm::max( high, vertex.position );
}
glm::vec3 const centre { ( low + high ) * 0.5f };
auto radius { 0.f };
for( auto const &vertex : vertices ) {
radius = std::max( radius, glm::length2( vertex.position - centre ) );
}
sphere = glm::vec4{ centre, std::sqrt( radius ) };
}
return m_chunkspheres.emplace( key, sphere ).first->second;
}
bool opengl46_renderer::Scene_Depth_Draws( gfx::geometry_handle const &Geometry ) const
{
if( m_scenedepthskip ) { return false; }

View File

@@ -404,8 +404,23 @@ class opengl46_renderer : public gfx_renderer {
int Prepass_Class( material_handle const Material );
// depth of the whole opaque scene of the colour pass, before its colour (Global.gfx_depthprepass_scene): the colour pass
// walks the scene a second time, Bind_Material() binds the programs of the depth prepass and draw() leaves out what they
// can't draw. Future: the motion matrix the colour pass draws the region with
void Render_Scene_Depth( glm::mat4 const &Future );
// can't draw. with Global.gfx_depthprepass_replay the walk of the colour pass is made here, once: its opaque draws are
// recorded (Record_Scene_Draw()), their depth is drawn from the record (Render_Scene_Prepass()) and the colour pass draws
// them from it (Render_Scene_Colour()). Future: the motion matrix the colour pass draws the region with. Contactonly: the
// depth is drawn only for the contact shadows, which need it within their reach
void Render_Scene_Depth( glm::mat4 const &Future, bool const Contactonly );
// notes a draw of the walk recorded by Render_Scene_Depth(): the chunks drawn, with the state the walk left for them
void Record_Scene_Draw( gfx::geometry_handle const *First, std::size_t const Count, bool const Batch );
// draws the depth of the recorded draws not drawn yet, in runs of the same program, bank and texture of the alpha test,
// with multi draws when there are any. the depth pyramid of the occlusion test and the cursor of the editor take the depth
// drawn so far, and the end of the walk the rest
void Render_Scene_Prepass();
// the colour of the recorded draws, in the order of the walk, with the programs of their materials
void Render_Scene_Colour();
// the colour of the instances culled and recorded by the depth pass of the whole scene (m_indirectcolourpending)
void Render_Instances_Indirect_Pending();
// the bounding sphere of a geometry chunk in the space of its vertices, w the radius (negative if there's none)
glm::vec4 const &Chunk_Sphere( gfx::geometry_handle const &Geometry );
// whether the colour pass of the viewport gets contact shadows, which need the depth pass of the scene
bool Contact_Shadows_Wanted( viewport_config const &Viewport ) const;
// resolves the depth drawn by Render_Scene_Depth() for the contact shadows and sets them up for the colour pass
@@ -683,6 +698,71 @@ class opengl46_renderer : public gfx_renderer {
debug_stats m_scenedepthstats; // of its last run
double m_scenedepthcputime { 0.0 }; // ms
gl::gpu_timer m_scenedepthtimer;
// the recorded walk of the colour pass (Global.gfx_depthprepass_replay): a draw with the state it was made with
struct scene_draw {
gl::model_ubs ubs;
material_handle material { null_handle }; // bound last before it (Bind_Material())
TSubModel const *submodel { nullptr };
std::uint32_t seed { 0 }; // TSubModel::iVariantSeed then
std::uint32_t variant { 0 }; // TSubModel::VariantId() then: which textures of the material were bound
std::uint32_t chunkfirst { 0 }; // in m_scenechunks
std::uint32_t chunkcount { 0 };
std::uint32_t instancecount { 0 }; // instanced draw (Render_Instanced(), Render_Sleepers()), 0 if not
std::uint32_t instancebase { 0 };
GLuint instancebuffer { 0 }; // the instance buffer of the frame the matrices of the instances went to
std::uint32_t header { 0 }; // the light header (m_sceneheaders) it is lit with
texture_handle prepasstexture { null_handle }; // of the alpha test of the depth pass
int prepassclass { 0 }; // Prepass_Class(), 0 as well for what the depth pass leaves out (blending, opacity < 0)
bool blending { false };
bool batch { false }; // drawn with draw_batch() (neighbouring tracks)
bool instances { false }; // no draw: the colour of the instances culled in the walk goes here
};
struct scene_chunk {
gfx::geometry_handle geometry;
std::uint32_t draw;
bool depth; // drawn in the depth pass
};
using light_header = std::array<std::uint8_t, offsetof( gl::light_data, lights ) + sizeof( gl::light_element )>;
bool m_scenerecording { false }; // inside the walk of Render_Scene_Depth(): draw(), Bind_Material(), upload_light_header() record
bool m_scenerecorded { false }; // the colour pass under way draws the record
bool m_scenereplayed { false }; // the last depth pass of the scene was drawn from a record
bool m_scenedepthreach { false }; // the depth pass leaves out the draws lying whole farther than m_scenedepthlimit
float m_scenedepthlimit { 0.f };
material_handle m_scenematerial { null_handle }; // what Bind_Material() bound last in the walk
TSubModel const *m_scenesubmodel { nullptr };
std::uint32_t m_sceneseed { 0 };
std::uint32_t m_scenevariant { 0 };
std::vector<scene_draw> m_scenedraws;
std::vector<scene_chunk> m_scenechunks;
std::vector<gfx::geometry_handle> m_scenegeometry; // the chunks of m_scenechunks, for draw_batch()
std::vector<light_header> m_sceneheaders; // the first one is in the light buffer when the walk begins
std::size_t m_sceneflushed { 0 }; // chunks Render_Scene_Prepass() went through
// working space of Render_Scene_Prepass(): a chunk drawn and what its run has in common
struct scene_prepass_item {
int prepassclass;
texture_handle texture;
std::uint32_t bank;
unsigned int type;
std::uint32_t indexed;
GLuint instancebuffer; // 0 for a draw which isn't instanced, which reads the identity every buffer starts with
std::uint32_t chunk;
gl::instance_draw_command command;
auto tie() const {
return std::tie( prepassclass, texture, bank, type, indexed, instancebuffer, chunk ); }
};
std::vector<scene_prepass_item> m_sceneprepassitems;
std::vector<gl::model_ubs> m_sceneprepassblocks;
std::vector<gl::instance_draw_command> m_sceneprepasscommands;
// the commands of the multi draws of the depth pass, one buffer for each of the last frames (nullptr: no multi draws)
std::unique_ptr<gl::frame_stream> m_scenecommandstream;
// of the last recorded walk: parts drawn (chunks of geometry; neighbouring tracks are drawn together), those of them drawn in
// the depth pass and its gl calls, and those left out of it as farther than the reach of the contact shadows
std::uint32_t m_scenerecordedparts { 0 };
std::uint32_t m_sceneprepassdraws { 0 };
std::uint32_t m_sceneprepasscalls { 0 };
std::uint32_t m_scenebeyondreach { 0 };
// Chunk_Sphere() of the chunks asked about, dropped when their vertices change (Replace(), Append())
std::unordered_map<std::uint64_t, glm::vec4> m_chunkspheres;
// contact shadows: set up for the colour pass under way, and whether the last main view had them
bool m_contactshadows { false };
bool m_contactshadowslast { false };