17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-19 03:49:19 +02:00

Draw the instances from the GPU with multi draws, the model block of each draw read by its number

This commit is contained in:
2026-10-18 00:47:05 +02:00
parent 7221c153dd
commit 494304903f
18 changed files with 609 additions and 64 deletions

View File

@@ -353,6 +353,13 @@ geometry_bank::draw_indirect( gfx::geometry_handle const &Geometry, std::intptr_
draw_indirect_( Geometry, Offset, Streams );
}
// draws chunks of the bank with Count commands from Offset in the bound draw indirect buffer, in one call
bool
geometry_bank::draw_indirect_multi( gfx::geometry_handle const &Geometry, std::intptr_t const Offset, std::uint32_t const Count, std::uint32_t const Stride ) {
// template method implementation
return draw_indirect_multi_( Geometry, Offset, Count, Stride );
}
// frees subclass-specific resources associated with the bank, typically called when the bank wasn't in use for a period of time
void
geometry_bank::release() {
@@ -515,6 +522,18 @@ geometrybank_manager::draw_indirect( gfx::geometry_handle const &Geometry, std::
bankrecord.first->draw_indirect( Geometry, Offset, Streams );
}
// draws chunks of the bank of the specified one with Count commands from Offset in the bound draw indirect buffer, in one call
bool
geometrybank_manager::draw_indirect_multi( gfx::geometry_handle const &Geometry, std::intptr_t const Offset, std::uint32_t const Count, std::uint32_t const Stride ) {
if( Geometry == null_handle ) { return false; }
auto &bankrecord = bank( Geometry );
bankrecord.second = m_garbagecollector.timestamp();
return bankrecord.first->draw_indirect_multi( Geometry, Offset, Count, Stride );
}
// provides direct access to index data of specfied chunk
gfx::index_array const &
geometrybank_manager::indices( gfx::geometry_handle const &Geometry ) const {

View File

@@ -138,6 +138,10 @@ public:
// draws geometry stored in specified chunk with the command at Offset (bytes) in the bound draw indirect buffer,
// a glDrawElementsIndirect one for an indexed chunk, glDrawArraysIndirect otherwise
void draw_indirect( gfx::geometry_handle const &Geometry, std::intptr_t const Offset, unsigned int const Streams = basic_streams );
// draws chunks of the bank with Count commands from Offset (bytes) in the bound draw indirect buffer, Stride bytes apart,
// with a single glMultiDrawElementsIndirect (or glMultiDrawArraysIndirect). the chunks have to be of the type of the
// specified one and indexed alike. returns: false if the bank can't, nothing is drawn then
auto draw_indirect_multi( gfx::geometry_handle const &Geometry, std::intptr_t const Offset, std::uint32_t const Count, std::uint32_t const Stride ) -> bool;
// draws geometry stored in supplied list of chunks
template <typename Iterator_>
auto draw( Iterator_ First, Iterator_ Last, gfx::stream_units const &Units, unsigned int const Streams = basic_streams ) ->std::size_t {
@@ -214,6 +218,7 @@ private:
// indirect_source() and draw_indirect() subclass details. by default the bank can't draw indirectly
virtual auto indirect_source_( gfx::geometry_handle const & /*Geometry*/, gfx::indirect_source & /*Source*/ ) -> bool { return false; }
virtual void draw_indirect_( gfx::geometry_handle const & /*Geometry*/, std::intptr_t const /*Offset*/, unsigned int const /*Streams*/ ) {}
virtual auto draw_indirect_multi_( gfx::geometry_handle const & /*Geometry*/, std::intptr_t const /*Offset*/, std::uint32_t const /*Count*/, std::uint32_t const /*Stride*/ ) -> bool { return false; }
// resource release subclass details
virtual void release_() = 0;
};
@@ -251,6 +256,9 @@ public:
auto indirect_source( gfx::geometry_handle const &Geometry, gfx::indirect_source &Source ) -> bool;
// draws geometry stored in specified chunk with the command at Offset (bytes) in the bound draw indirect buffer
void draw_indirect( gfx::geometry_handle const &Geometry, std::intptr_t const Offset, unsigned int const Streams = basic_streams );
// draws chunks of the bank of the specified one, of its type and indexed alike, with Count commands from Offset in the bound
// draw indirect buffer, Stride bytes apart, in one call. returns: false if the bank can't, nothing is drawn then
auto draw_indirect_multi( gfx::geometry_handle const &Geometry, std::intptr_t const Offset, std::uint32_t const Count, std::uint32_t const Stride ) -> bool;
template <typename Iterator_>
void draw( Iterator_ First, Iterator_ Last, unsigned int const Streams = basic_streams ) {
while( First != Last ) {

View File

@@ -274,6 +274,28 @@ opengl46_vaogeometrybank::draw_indirect_( gfx::geometry_handle const &Geometry,
}
}
// draw_indirect_multi() subclass details — the commands come from the bound draw indirect buffer, the chunk tells the type
// and whether they're indexed
bool
opengl46_vaogeometrybank::draw_indirect_multi_( gfx::geometry_handle const &Geometry, std::intptr_t const Offset, std::uint32_t const Count, std::uint32_t const Stride )
{
setup_buffer();
// the counts of the chunks are in the commands, which may be for chunks other than this one
if( ( false == m_vao.has_value() ) || ( Count == 0 ) ) { return true; }
auto const &chunkrecord = m_chunkrecords.at(Geometry.chunk - 1);
auto const &chunk = gfx::geometry_bank::chunk( Geometry );
m_vao->bind();
if( chunkrecord.index_count > 0 ) {
::glMultiDrawElementsIndirect( chunk.type, GL_UNSIGNED_INT, reinterpret_cast<void const *>( Offset ), static_cast<GLsizei>( Count ), static_cast<GLsizei>( Stride ) );
}
else {
::glMultiDrawArraysIndirect( chunk.type, reinterpret_cast<void const *>( Offset ), static_cast<GLsizei>( Count ), static_cast<GLsizei>( Stride ) );
}
return true;
}
// draw_batch() subclass details — neighbouring non-indexed chunks of the same type are sent with a single GL call.
// The chunks are drawn in the supplied order, same as with a draw_() call for each of them.
std::size_t

View File

@@ -65,6 +65,9 @@ private:
// draw_indirect() subclass details — issues glDrawElementsIndirect or glDrawArraysIndirect
void
draw_indirect_( gfx::geometry_handle const &Geometry, std::intptr_t const Offset, unsigned int const Streams ) override;
// draw_indirect_multi() subclass details — issues glMultiDrawElementsIndirect or glMultiDrawArraysIndirect
auto
draw_indirect_multi_( gfx::geometry_handle const &Geometry, std::intptr_t const Offset, std::uint32_t const Count, std::uint32_t const Stride ) -> bool override;
// release() subclass details
void
release_() override;

View File

@@ -360,10 +360,12 @@ void opengl46_instance_cull::bind_draw()
glBindBuffersBase(GL_SHADER_STORAGE_BUFFER, gl::INSTANCE_SSBO_BINDING, 1, &output);
}
void opengl46_instance_cull::draw_result(std::uint32_t const Draws, std::uint32_t const Prepassdraws, double const Cputime)
void opengl46_instance_cull::draw_result(std::uint32_t const Draws, std::uint32_t const Prepassdraws, std::uint32_t const Calls, std::uint32_t const Prepasscalls, double const Cputime)
{
m_draws = Draws;
m_prepassdraws = Prepassdraws;
m_calls = Calls;
m_prepasscalls = Prepasscalls;
m_cputime = Cputime;
}
@@ -452,8 +454,8 @@ std::string opengl46_instance_cull::info()
return "\ngpu cull: " + std::to_string(m_last.gputotal) + " instances drawn" +
(m_twophase ? " (" + std::to_string(m_last.first) + " of them first, seen in the last frame)" : std::string{}) + ", " + std::to_string(m_last.occluded) +
" hidden by occlusion" + (m_occlusion ? (m_twophase ? " (also by one another)" : "") : " (test off)") + ", " + std::to_string(m_last.entries) + " bands; " +
std::to_string(m_draws) + " indirect draws (+" +
std::to_string(m_prepassdraws) + " depth prepass), cpu " + to_string(m_cputime, 3) + " ms";
std::to_string(m_draws) + " indirect draws in " + std::to_string(m_calls) + " calls (+" + std::to_string(m_prepassdraws) + " depth prepass in " +
std::to_string(m_prepasscalls) + "), cpu " + to_string(m_cputime, 3) + " ms";
}
if (m_comparedframes == 0)
return "\ngpu cull: no result yet";

View File

@@ -140,13 +140,18 @@ class opengl46_instance_cull
}
// binds the commands to GL_DRAW_INDIRECT_BUFFER and the output in place of the instance buffer of the frame
void bind_draw();
// counts of the last result which came back from the gpu, with the draws of the pass, for the debug panel
void draw_result(std::uint32_t const Draws, std::uint32_t const Prepassdraws, double const Cputime);
// counts of the last result which came back from the gpu, with the draws of the pass and the gl calls which made them (fewer
// with multi draws), for the debug panel
void draw_result(std::uint32_t const Draws, std::uint32_t const Prepassdraws, std::uint32_t const Calls, std::uint32_t const Prepasscalls, double const Cputime);
// what draw_result() was given last
std::uint32_t prepass_draws() const
{
return m_prepassdraws;
}
std::uint32_t prepass_calls() const
{
return m_prepasscalls;
}
double cpu_time() const
{
return m_cputime;
@@ -243,6 +248,8 @@ class opengl46_instance_cull
// draws of the last pass drawn from the gpu
std::uint32_t m_draws{0};
std::uint32_t m_prepassdraws{0};
std::uint32_t m_calls{0};
std::uint32_t m_prepasscalls{0};
std::uint32_t m_commandcount{0};
double m_cputime{0.0};
bool m_occlusion{false}; // the last dispatch tested occlusion

View File

@@ -191,6 +191,33 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
static_cast<GLsizeiptr>(16384 * sizeof(glm::mat4)), static_cast<GLsizeiptr>(sizeof(glm::mat4)), Global.gfx_bufferstreaming, &identity, static_cast<GLsizeiptr>(sizeof(identity)));
}
WriteLog(std::string("Model data streaming: ") + (model_ubo->streamed() ? "persistently mapped buffers" : "glBufferSubData"));
// multi draws of the instances drawn from the gpu culling (Render_Instances_Indirect()): the model blocks of their draws in a
// storage buffer bound at whole blocks, and the variants of the programs which read them there. without them the draws are
// made one by one, as before
try
{
GLint alignment{256};
glGetIntegerv(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT, &alignment);
if ((alignment <= 0) || (sizeof(gl::model_ubs) % static_cast<std::size_t>(alignment) != 0))
throw gl::shader_exception("storage buffer offset alignment of " + std::to_string(alignment) + " bytes");
std::string const defines{"#define MODEL_DRAWS 1\n"};
m_vertex_multidraw_shader = std::make_unique<gl::shader>("vertex.vert", defines);
gl::shader prepassvertex("depthprepass.vert", defines);
gl::shader prepassfragment("depthprepass.frag", defines);
gl::shader prepassalphafragment("depthprepass_alpha.frag", defines);
m_prepass_multidraw_shader = std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{prepassvertex, prepassfragment});
m_prepass_alpha_multidraw_shader = std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{prepassvertex, prepassalphafragment});
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);
}
catch (gl::shader_exception const &e)
{
ErrorLog("multi draws of the instances not available: " + std::string(e.what()));
m_vertex_multidraw_shader.reset();
m_prepass_multidraw_shader.reset();
m_prepass_alpha_multidraw_shader.reset();
m_drawblockstream.reset();
}
try
{
@@ -661,6 +688,8 @@ bool opengl46_renderer::Render()
m_instancestream->next_frame();
Bind_Instance_Stream();
model_ubo->next_frame();
if (m_drawblockstream)
m_drawblockstream->next_frame();
m_renderpass.draw_mode = rendermode::none; // force setup anew
m_renderpass.draw_stats = debug_stats();
@@ -797,6 +826,11 @@ void opengl46_renderer::SwapBuffers()
+ std::to_string( model_ubo->stream()->waits() + m_instancestream->waits() ) + ", grown "
+ std::to_string( model_ubo->stream()->grows() + m_instancestream->grows() ) + ")\n" :
", glBufferSubData\n" )
+ ( ( m_drawblockstream && ( Global.gfx_gpucull == 1 ) && Global.gfx_gpucull_multidraw ) ?
" multi draw blocks: " + std::to_string( m_drawblockstream->used() / static_cast<GLsizeiptr>( sizeof( gl::model_ubs ) ) ) + ", "
+ std::to_string( m_drawblockstream->size() / 1024 ) + " KiB (waits " + std::to_string( m_drawblockstream->waits() ) + ", grown "
+ std::to_string( m_drawblockstream->grows() ) + ")\n" :
std::string{} )
+ " inst-pool:" + std::to_string( TAnimModel::s_instanceable_total )
+ "/" + std::to_string( TAnimModel::s_classified_total )
+ " rej(noModel/lights/anim/animSubM): "
@@ -914,6 +948,10 @@ void opengl46_renderer::draw_debug_ui()
if (Global.gfx_gpucull_occlusion)
ImGui::Checkbox("draw the instances seen in the last frame first, to hide the others (gfx.gpucull.occlusion.instances)", &Global.gfx_gpucull_occlusion_instances);
ImGui::Checkbox("draw the depth of the instances before their colour (gfx.depthprepass)", &Global.gfx_depthprepass);
if (m_drawblockstream)
ImGui::Checkbox("draw the instances of the same material and model with one multi draw (gfx.gpucull.multidraw)", &Global.gfx_gpucull_multidraw);
else
ImGui::TextUnformatted("(multi draws not available, see the log)");
}
ImGui::Checkbox("draw the depth of the whole scene before its colour (gfx.depthprepass.scene)", &Global.gfx_depthprepass_scene);
if (Global.gfx_contactshadows > 0)
@@ -2474,6 +2512,9 @@ std::shared_ptr<gl::program> opengl46_renderer::Fetch_Shader(const std::string &
gl::shader fragment("mat_" + name + ".frag");
gl::program *program = new gl::program({fragment, *m_vertex_shader.get()});
m_shaders.insert({name, std::shared_ptr<gl::program>(program)});
// the variant for the multi draws of the instances, made now rather than when the instances first come into view
if ((Global.gfx_gpucull == 1) && Global.gfx_gpucull_multidraw)
Multidraw_Program(program);
}
return m_shaders[name];
@@ -2648,7 +2689,14 @@ void opengl46_renderer::Bind_Material( material_handle const Material, TSubModel
if (lastdiff)
glBindTextures(0, lastdiff, &opengl_texture::units[0]);
material.shader->bind();
auto *program{material.shader.get()};
if (m_bindmultidraw)
{
// the replay of the instances drawn from the gpu culling with multi draws (Render_Instances_Indirect_Colour())
if (auto *multidraw{Multidraw_Program(program)})
program = multidraw;
}
program->bind();
}
else if (Material != m_invalid_material)
Bind_Material(m_invalid_material);
@@ -3224,18 +3272,21 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
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_instancecull.bind_draw();
Render_Instances_Indirect_Colour( m_indirectcolourset );
colourcounts = Render_Instances_Indirect_Colour( m_indirectcolourset );
::glBindBuffer( GL_DRAW_INDIRECT_BUFFER, 0 );
Bind_Instance_Stream();
m_instancecull.draw_result(
static_cast<std::uint32_t>( m_indirectdraws.size() ),
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>( m_indirectdraws.size() );
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() );
}
@@ -3892,6 +3943,14 @@ void opengl46_renderer::Render_Instances_Indirect()
}
m_indirectrecording = false;
// 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
m_indirectmultidraw = ( Global.gfx_gpucull_multidraw && ( m_drawblockstream != nullptr ) );
if( m_indirectmultidraw && ( false == m_indirectdraws.empty() ) ) {
Order_Indirect_Draws();
Upload_Indirect_Blocks();
}
m_instancecull.fill_commands( m_indirectcommands );
using command_set = opengl46_instance_cull::command_set;
auto const twophase { m_instancecull.two_phase() };
@@ -3901,19 +3960,18 @@ void opengl46_renderer::Render_Instances_Indirect()
// view. the depth goes a little farther than the surface, so the colour pass, which tests and writes its depth as usual,
// is sure to pass where it's the same
auto const depthfirst { m_scenedepthpass || ( Global.gfx_depthprepass && ( false == Global.bWireFrame ) ) };
std::uint32_t prepassdraws { 0 };
std::uint32_t colourdraws { 0 };
indirect_counts prepasscounts;
indirect_counts colourcounts;
auto colourset { command_set::first };
if( twophase ) {
if( draws ) {
// the instances seen in the previous frame, then the pyramid again, with them in it
m_instancecull.bind_draw();
if( depthfirst ) {
prepassdraws += Render_Instances_Indirect_Prepass( command_set::first, m_prepassfirsttimer );
prepasscounts += Render_Instances_Indirect_Prepass( command_set::first, m_prepassfirsttimer );
}
else {
Render_Instances_Indirect_Colour( command_set::first );
colourdraws += static_cast<std::uint32_t>( m_indirectdraws.size() );
colourcounts += Render_Instances_Indirect_Colour( command_set::first );
}
m_hiz.build( *viewport->msaa_fb, viewport->width, viewport->height, true );
occlusion.pyramid = m_hiz.texture();
@@ -3925,11 +3983,10 @@ void opengl46_renderer::Render_Instances_Indirect()
if( draws ) {
m_instancecull.bind_draw();
if( depthfirst ) {
prepassdraws += Render_Instances_Indirect_Prepass( ( twophase ? command_set::second : command_set::first ), m_prepasstimer );
prepasscounts += Render_Instances_Indirect_Prepass( ( twophase ? command_set::second : command_set::first ), m_prepasstimer );
}
if( false == m_scenedepthpass ) {
Render_Instances_Indirect_Colour( colourset );
colourdraws += static_cast<std::uint32_t>( m_indirectdraws.size() );
colourcounts += Render_Instances_Indirect_Colour( colourset );
}
::glBindBuffer( GL_DRAW_INDIRECT_BUFFER, 0 );
Bind_Instance_Stream();
@@ -3938,23 +3995,25 @@ void opengl46_renderer::Render_Instances_Indirect()
// the colour pass takes the draws from here, and counts them
m_indirectcolourpending = true;
m_indirectcolourset = colourset;
m_renderpass.draw_stats.drawcalls += static_cast<int>( prepassdraws );
colourdraws = static_cast<std::uint32_t>( m_indirectdraws.size() );
m_renderpass.draw_stats.drawcalls += static_cast<int>( prepasscounts.calls );
colourcounts.draws = static_cast<std::uint32_t>( m_indirectdraws.size() );
}
else {
m_renderpass.draw_stats.instanced_drawcalls += static_cast<int>( colourdraws );
m_renderpass.draw_stats.drawcalls += static_cast<int>( prepassdraws );
m_renderpass.draw_stats.instanced_drawcalls += static_cast<int>( colourcounts.calls );
m_renderpass.draw_stats.drawcalls += static_cast<int>( prepasscounts.calls );
// the count of the frame the gpu reported last
m_renderpass.draw_stats.instances += static_cast<int>( m_instancecull.last_count() );
m_renderpass.draw_stats.models += static_cast<int>( m_instancecull.last_count() );
}
m_instancecull.draw_result(
colourdraws,
prepassdraws,
colourcounts.draws,
prepasscounts.draws,
colourcounts.calls,
prepasscounts.calls,
std::chrono::duration<double, std::milli>( std::chrono::steady_clock::now() - cpustart ).count() );
}
std::uint32_t 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 )
{
if( m_scenedepthpass ) {
return Render_Instances_Indirect_Depth( Set );
@@ -3964,34 +4023,80 @@ std::uint32_t opengl46_renderer::Render_Instances_Indirect_Prepass( opengl46_ins
::glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
::glEnable( GL_POLYGON_OFFSET_FILL );
::glPolygonOffset( -1.f, -1.f );
auto const draws { Render_Instances_Indirect_Depth( Set ) };
auto const counts { Render_Instances_Indirect_Depth( Set ) };
::glDisable( GL_POLYGON_OFFSET_FILL );
::glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
Timer.end();
return draws;
return counts;
}
std::uint32_t opengl46_renderer::Render_Instances_Indirect_Depth( opengl46_instance_cull::command_set const Set )
opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_Depth( opengl46_instance_cull::command_set const Set )
{
std::uint32_t draws { 0 };
indirect_counts counts;
auto const count { m_indirectdraws.size() };
// whether the prepass of the class draws the draw, and with which diffuse texture (class 2)
auto const prepass_texture = [ this ]( std::size_t const Index, int const Prepassclass, texture_handle &Texture ) -> bool {
auto const &draw { m_indirectdraws[ Index ] };
if( ( draw.blending ) || ( draw.ubs.opacity < 0.f ) ) { return false; }
if( ( draw.type != GL_TRIANGLES ) && ( draw.type != GL_TRIANGLE_STRIP ) && ( draw.type != GL_TRIANGLE_FAN ) ) { return false; }
if( Prepass_Class( draw.material ) != Prepassclass ) { return false; }
Texture = null_handle;
if( Prepassclass == 2 ) {
// the diffuse texture of the colour pass, the same choice as Bind_Material() makes
auto const &material { m_materials.material( draw.material ) };
auto const &textures {
material.texture_variants.empty() ?
material.textures :
material.GetTextures( TSubModel::VariantId( draw.submodel ) ) };
if( textures[ 0 ] == null_handle ) { return false; }
Texture = textures[ 0 ];
}
return true;
};
auto lastdrawn { count };
// the draws without the alpha test first, then the ones with it, one program each
for( auto prepassclass = 1; prepassclass <= 2; ++prepassclass ) {
auto &program { prepassclass == 1 ? *m_prepass_shader : *m_prepass_alpha_shader };
auto *multidrawprogram {
m_indirectmultidraw ?
( prepassclass == 1 ? m_prepass_multidraw_shader.get() : m_prepass_alpha_multidraw_shader.get() ) :
nullptr };
auto bound { false };
for( std::size_t index = 0; index < m_indirectdraws.size(); ++index ) {
auto const &draw { m_indirectdraws[ index ] };
if( ( draw.blending ) || ( draw.ubs.opacity < 0.f ) ) { continue; }
if( ( draw.type != GL_TRIANGLES ) && ( draw.type != GL_TRIANGLE_STRIP ) && ( draw.type != GL_TRIANGLE_FAN ) ) { continue; }
if( Prepass_Class( draw.material ) != prepassclass ) { continue; }
for( std::size_t index = 0; index < count; ) {
texture_handle texture;
if( false == prepass_texture( index, prepassclass, texture ) ) {
++index;
continue;
}
if( prepassclass == 2 ) {
// the diffuse texture of the colour pass, the same choice as Bind_Material() makes
auto const &material { m_materials.material( draw.material ) };
auto const &textures {
material.texture_variants.empty() ?
material.textures :
material.GetTextures( TSubModel::VariantId( draw.submodel ) ) };
if( textures[ 0 ] == null_handle ) { continue; }
m_textures.bind( 0, textures[ 0 ] );
m_textures.bind( 0, texture );
}
auto const &draw { m_indirectdraws[ index ] };
if( multidrawprogram != nullptr ) {
// the draws which follow in the same bank and of the same primitive, with the same texture for the alpha test.
// the material doesn't matter to the depth
auto last { index + 1 };
texture_handle othertexture;
while( ( last < count )
&& ( m_indirectdraws[ last ].geometry.bank == draw.geometry.bank )
&& ( m_indirectdraws[ last ].type == draw.type )
&& ( m_indirectcommands[ last ].indexed == m_indirectcommands[ index ].indexed )
&& ( prepass_texture( last, prepassclass, othertexture ) )
&& ( othertexture == texture ) ) {
++last;
}
if( false == bound ) {
multidrawprogram->bind();
bound = true;
}
auto const drawcount { static_cast<std::uint32_t>( last - index ) };
Bind_Indirect_Blocks( index, drawcount );
m_geometry.draw_indirect_multi( draw.geometry, m_instancecull.draw_command_offset( Set, index ), drawcount, sizeof( gl::instance_draw_command ) );
counts.draws += drawcount;
++counts.calls;
lastdrawn = last - 1;
index = last;
continue;
}
if( false == bound ) {
program.bind();
@@ -4000,24 +4105,239 @@ std::uint32_t opengl46_renderer::Render_Instances_Indirect_Depth( opengl46_insta
model_ubs = draw.ubs;
model_ubo->update( model_ubs );
m_geometry.draw_indirect( draw.geometry, m_instancecull.draw_command_offset( Set, index ) );
++draws;
++counts.draws;
++counts.calls;
++index;
}
}
return draws;
if( lastdrawn < count ) {
// the model block the draws made one by one leave behind
model_ubs = m_indirectdraws[ lastdrawn ].ubs;
model_ubo->update( model_ubs );
}
return counts;
}
void opengl46_renderer::Render_Instances_Indirect_Colour( opengl46_instance_cull::command_set const Set )
opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_Colour( opengl46_instance_cull::command_set const Set )
{
for( std::size_t index = 0; index < m_indirectdraws.size(); ++index ) {
indirect_counts counts;
auto const count { m_indirectdraws.size() };
for( std::size_t index = 0; index < count; ) {
auto const &draw { m_indirectdraws[ index ] };
auto *multidrawprogram { m_indirectmultidraw ? Multidraw_Program( Indirect_Program( draw.material ) ) : nullptr };
if( draw.blending ) { setup_drawing( true ); }
Bind_Material( draw.material, draw.submodel );
model_ubs = draw.ubs;
model_ubo->update( model_ubs );
m_geometry.draw_indirect( draw.geometry, m_instancecull.draw_command_offset( Set, index ) );
if( multidrawprogram != nullptr ) {
// the draws which follow with the same state, in one call
auto last { index + 1 };
while( ( last < count ) && Indirect_Mergeable( index, last ) ) {
++last;
}
m_bindmultidraw = true;
Bind_Material( draw.material, draw.submodel );
m_bindmultidraw = false;
auto const drawcount { static_cast<std::uint32_t>( last - index ) };
Bind_Indirect_Blocks( index, drawcount );
m_geometry.draw_indirect_multi( draw.geometry, m_instancecull.draw_command_offset( Set, index ), drawcount, sizeof( gl::instance_draw_command ) );
counts.draws += drawcount;
index = last;
}
else {
Bind_Material( draw.material, draw.submodel );
model_ubs = draw.ubs;
model_ubo->update( model_ubs );
m_geometry.draw_indirect( draw.geometry, m_instancecull.draw_command_offset( Set, index ) );
++counts.draws;
++index;
}
++counts.calls;
if( draw.blending ) { setup_drawing( false ); }
}
if( m_indirectmultidraw && ( count > 0 ) ) {
// the model block the draws made one by one leave behind
model_ubs = m_indirectdraws.back().ubs;
model_ubo->update( model_ubs );
}
model_ubs.emission = 0.0f;
return counts;
}
// the order of the recorded draws for multi draws. draws go in one multi draw when they follow one another with the same
// state (material and its texture variant, geometry bank, primitive, blending; Indirect_Mergeable()). the draws of one entry,
// the walk of one submodel tree in one lod band, keep their order: submodels of a model may lie on one another and rely on
// the later one being drawn over the earlier one. the draws of different entries are drawn for different instances, which
// the gpu draws in no particular order anyway, so they're free to move. the entries are queues of draws; the draws at the
// heads of the queues which have the state taken next all go, each queue as far as its draws keep that state, then the
// queues wait for the state of their next draw. the states go in the order in which the queues first reach them
void opengl46_renderer::Order_Indirect_Draws()
{
auto const count { m_indirectdraws.size() };
if( count < 2 ) { return; }
// a number for each state the draws have, by sorting the states (the buffers are kept between frames)
m_indirectstatekeys.clear();
for( std::size_t index = 0; index < count; ++index ) {
auto const &draw { m_indirectdraws[ index ] };
auto variant { 0u };
if( draw.material != null_handle ) {
auto const &material { m_materials.material( draw.material ) };
if( false == material.texture_variants.empty() ) {
variant = TSubModel::VariantId( draw.submodel ) % static_cast<std::uint32_t>( material.texture_variants.size() );
}
}
m_indirectstatekeys.push_back( { draw.material, variant, draw.geometry.bank, draw.type, m_indirectcommands[ index ].indexed, draw.blending, static_cast<std::uint32_t>( index ) } );
}
std::sort(
std::begin( m_indirectstatekeys ), std::end( m_indirectstatekeys ),
[]( indirect_state_key const &Left, indirect_state_key const &Right ) {
return Left.tie() < Right.tie(); } );
m_indirectdrawstates.resize( count );
std::uint32_t statecount { 0 };
for( std::size_t index = 0; index < count; ++index ) {
if( ( index > 0 ) && ( m_indirectstatekeys[ index ].tie() != m_indirectstatekeys[ index - 1 ].tie() ) ) {
++statecount;
}
m_indirectdrawstates[ m_indirectstatekeys[ index ].draw ] = statecount;
}
++statecount;
if( statecount == count ) {
// no two draws share a state, nothing would go together
return;
}
auto constexpr none { std::numeric_limits<std::uint32_t>::max() };
m_indirectstates.assign( statecount, indirect_state { none, none } );
m_indirectstateorder.clear();
m_indirectqueues.clear();
// the queue waits for the state of its next draw, behind the queues waiting for it already
auto const enqueue = [ this, none ]( std::uint32_t const Queue ) {
auto &queue { m_indirectqueues[ Queue ] };
auto &state { m_indirectstates[ m_indirectdrawstates[ queue.next ] ] };
queue.link = none;
if( state.head == none ) {
state.head = Queue;
m_indirectstateorder.push_back( m_indirectdrawstates[ queue.next ] );
}
else {
m_indirectqueues[ state.tail ].link = Queue;
}
state.tail = Queue;
};
for( std::size_t index = 0; index < count; ) {
auto end { index + 1 };
while( ( end < count ) && ( m_indirectcommands[ end ].entry == m_indirectcommands[ index ].entry ) ) { ++end; }
m_indirectqueues.push_back( { static_cast<std::uint32_t>( index ), static_cast<std::uint32_t>( end ), none } );
index = end;
}
for( std::uint32_t queue = 0; queue < m_indirectqueues.size(); ++queue ) {
enqueue( queue );
}
m_indirectorder.clear();
for( std::size_t taken = 0; taken < m_indirectstateorder.size(); ++taken ) {
auto const stateindex { m_indirectstateorder[ taken ] };
auto queueindex { m_indirectstates[ stateindex ].head };
m_indirectstates[ stateindex ] = { none, none };
while( queueindex != none ) {
auto &queue { m_indirectqueues[ queueindex ] };
auto const nextqueue { queue.link };
while( ( queue.next < queue.end ) && ( m_indirectdrawstates[ queue.next ] == stateindex ) ) {
m_indirectorder.push_back( queue.next );
++queue.next;
}
if( queue.next < queue.end ) {
enqueue( queueindex );
}
queueindex = nextqueue;
}
}
m_indirectdrawsordered.clear();
m_indirectcommandsordered.clear();
for( auto const index : m_indirectorder ) {
m_indirectdrawsordered.push_back( m_indirectdraws[ index ] );
m_indirectcommandsordered.push_back( m_indirectcommands[ index ] );
}
m_indirectdraws.swap( m_indirectdrawsordered );
m_indirectcommands.swap( m_indirectcommandsordered );
}
void opengl46_renderer::Upload_Indirect_Blocks()
{
m_indirectblocks.clear();
for( auto const &draw : m_indirectdraws ) {
m_indirectblocks.push_back( draw.ubs );
}
m_indirectblockoffset = m_drawblockstream->push( m_indirectblocks.data(), static_cast<GLsizeiptr>( m_indirectblocks.size() * sizeof( gl::model_ubs ) ) );
m_indirectblockbuffer = m_drawblockstream->buffer();
m_indirectblockgeneration = m_drawblockstream->generation();
}
void opengl46_renderer::Bind_Indirect_Blocks( std::size_t const First, std::size_t const Count )
{
if( m_drawblockstream->generation() != m_indirectblockgeneration ) {
// the buffer the blocks went to was replaced since (it shouldn't, the draws are recorded once a frame)
Upload_Indirect_Blocks();
}
GLuint const buffer { m_indirectblockbuffer };
GLintptr const offset { m_indirectblockoffset + static_cast<GLintptr>( First * sizeof( gl::model_ubs ) ) };
GLsizeiptr const size { static_cast<GLsizeiptr>( Count * sizeof( gl::model_ubs ) ) };
// the plural form leaves the general GL_SHADER_STORAGE_BUFFER binding alone, as gl::ssbo does
::glBindBuffersRange( GL_SHADER_STORAGE_BUFFER, gl::MODEL_DRAW_SSBO_BINDING, 1, &buffer, &offset, &size );
}
bool opengl46_renderer::Indirect_Mergeable( std::size_t const First, std::size_t const Other ) const
{
auto const &first { m_indirectdraws[ First ] };
auto const &other { m_indirectdraws[ Other ] };
if( ( first.material != other.material )
|| ( first.blending != other.blending )
|| ( first.type != other.type )
|| ( first.geometry.bank != other.geometry.bank )
|| ( m_indirectcommands[ First ].indexed != m_indirectcommands[ Other ].indexed ) ) {
return false;
}
if( first.material == null_handle ) { return true; }
// the texture set Bind_Material() binds
auto const &material { m_materials.material( first.material ) };
if( material.texture_variants.empty() ) { return true; }
return ( &material.GetTextures( TSubModel::VariantId( first.submodel ) ) == &material.GetTextures( TSubModel::VariantId( other.submodel ) ) );
}
gl::program const *opengl46_renderer::Indirect_Program( material_handle Material ) const
{
// the same choice as Bind_Material() makes: what can't be drawn is drawn with the material of failures, if that one can
if( Material == null_handle ) { Material = m_invalid_material; }
if( Material == null_handle ) { return nullptr; }
auto const *material { &m_materials.material( Material ) };
if( false == material->is_good ) {
if( Material == m_invalid_material ) { return nullptr; }
material = &m_materials.material( m_invalid_material );
if( false == material->is_good ) { return nullptr; }
}
return material->shader.get();
}
gl::program *opengl46_renderer::Multidraw_Program( gl::program const *Program )
{
if( ( Program == nullptr ) || ( m_vertex_multidraw_shader == nullptr ) || ( m_drawblockstream == nullptr ) ) { return nullptr; }
auto const lookup { m_multidrawprograms.find( Program ) };
if( lookup != m_multidrawprograms.end() ) { return lookup->second.get(); }
// the fragment shader of the program by its name, mat_<name>.frag (Fetch_Shader())
std::unique_ptr<gl::program> variant;
for( auto const &shader : m_shaders ) {
if( shader.second.get() != Program ) { continue; }
try {
gl::shader fragment( "mat_" + shader.first + ".frag", "#define MODEL_DRAWS 1\n" );
variant = std::make_unique<gl::program>( std::vector<std::reference_wrapper<const gl::shader>>{ fragment, *m_vertex_multidraw_shader } );
}
catch( gl::shader_exception const &e ) {
ErrorLog( "material " + shader.first + " drawn without multi draws: " + std::string( e.what() ) );
}
break;
}
auto *result { variant.get() };
m_multidrawprograms.emplace( Program, std::move( variant ) );
return result;
}
// depth of the whole opaque scene of the colour pass, before its colour. the same walk as the colour pass makes (the cab drawn

View File

@@ -355,13 +355,37 @@ class opengl46_renderer : public gfx_renderer {
// (Global.gfx_gpucull_occlusion_instances) the instances seen in the previous frame are drawn first (their depth, or
// their colour without the prepass), the pyramid is made again and the other instances are culled against it
void Render_Instances_Indirect();
// draws of the instances made, and the gl calls which made them
struct indirect_counts {
std::uint32_t draws { 0 };
std::uint32_t calls { 0 };
indirect_counts &operator+=( indirect_counts const &Other ) {
draws += Other.draws;
calls += Other.calls;
return *this; }
};
// the recorded draws of the instances, with the commands of the given set: their depth with the programs of the depth
// prepass, with the state of the prepass already set (returns the number of draws), the same with the state set for it
// and the time measured outside the depth pass of the whole scene, and their colour. the command buffer and the
// instances are bound by the caller
std::uint32_t Render_Instances_Indirect_Depth( opengl46_instance_cull::command_set const Set );
std::uint32_t Render_Instances_Indirect_Prepass( opengl46_instance_cull::command_set const Set, gl::gpu_timer &Timer );
void Render_Instances_Indirect_Colour( opengl46_instance_cull::command_set const Set );
// prepass, with the state of the prepass already set, the same with the state set for it and the time measured outside
// the depth pass of the whole scene, and their colour. the command buffer and the instances are bound by the caller.
// with multi draws (m_indirectmultidraw) the draws which follow one another with the same state go in one call
indirect_counts Render_Instances_Indirect_Depth( opengl46_instance_cull::command_set const Set );
indirect_counts Render_Instances_Indirect_Prepass( opengl46_instance_cull::command_set const Set, gl::gpu_timer &Timer );
indirect_counts Render_Instances_Indirect_Colour( opengl46_instance_cull::command_set const Set );
// multi draws of the recorded draws (Global.gfx_gpucull_multidraw): puts the draws in an order in which the ones with the
// same state follow one another, the draws of one entry kept in the order of the walk, then places their model blocks in
// the buffer of the frame (m_drawblockstream), in the same order
void Order_Indirect_Draws();
void Upload_Indirect_Blocks();
// binds the model blocks of Count recorded draws from First for the programs of the multi draws (MODEL_DRAW_SSBO_BINDING)
void Bind_Indirect_Blocks( std::size_t const First, std::size_t const Count );
// whether two recorded draws can go in one multi draw of the colour pass: the same material and texture variant, geometry
// bank, primitive and blending
bool Indirect_Mergeable( std::size_t const First, std::size_t const Other ) const;
// the program Bind_Material() binds for the material, nullptr when it binds none
gl::program const *Indirect_Program( material_handle Material ) const;
// the variant of a material program for multi draws (built with MODEL_DRAWS), made the first time it's asked for.
// nullptr if there's none (a program not fetched by Fetch_Shader(), or one which didn't compile)
gl::program *Multidraw_Program( gl::program const *Program );
// how the depth prepass treats the material: 0 left out, 1 drawn without alpha test, 2 with the alpha test of the diffuse texture
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
@@ -548,6 +572,51 @@ class opengl46_renderer : public gfx_renderer {
};
std::vector<indirect_draw> m_indirectdraws;
std::vector<gl::instance_draw_command> m_indirectcommands; // in the order of m_indirectdraws
// 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 };
GLuint m_indirectblockbuffer { 0 };
GLintptr m_indirectblockoffset { 0 };
std::uint64_t m_indirectblockgeneration { 0 };
// working space of Order_Indirect_Draws() and Upload_Indirect_Blocks(), kept between frames
struct indirect_queue {
std::uint32_t next; // draw
std::uint32_t end;
std::uint32_t link; // next queue waiting for the same state
};
struct indirect_state {
std::uint32_t head;
std::uint32_t tail;
};
// what a multi draw has in common, and the draw which has it
struct indirect_state_key {
material_handle material;
std::uint32_t variant; // of the textures of the material
std::uint32_t bank;
unsigned int type;
std::uint32_t indexed;
bool blending;
std::uint32_t draw;
auto tie() const {
return std::tie( material, variant, bank, type, indexed, blending ); }
};
std::vector<indirect_state_key> m_indirectstatekeys;
std::vector<std::uint32_t> m_indirectdrawstates;
std::vector<indirect_queue> m_indirectqueues;
std::vector<indirect_state> m_indirectstates;
std::vector<std::uint32_t> m_indirectstateorder;
std::vector<std::uint32_t> m_indirectorder;
std::vector<indirect_draw> m_indirectdrawsordered;
std::vector<gl::instance_draw_command> m_indirectcommandsordered;
std::vector<gl::model_ubs> m_indirectblocks;
// the model blocks of the multi draws, one buffer for each of the last frames like model_ubo (nullptr: no multi draws)
std::unique_ptr<gl::frame_stream> m_drawblockstream;
// programs of the multi draws: the vertex shader of the materials, variants of the material programs, of the depth prepass
std::unique_ptr<gl::shader> m_vertex_multidraw_shader;
std::unordered_map<gl::program const *, std::unique_ptr<gl::program>> m_multidrawprograms;
std::unique_ptr<gl::program> m_prepass_multidraw_shader;
std::unique_ptr<gl::program> m_prepass_alpha_multidraw_shader;
bool m_bindmultidraw { false }; // Bind_Material() binds the variant of the material program for multi draws
bool m_indirectrecording { false }; // draw() and Bind_Material() record instead of drawing
std::uint32_t m_indirectentry { 0 }; // entry (group and lod band) recorded
material_handle m_indirectmaterial { null_handle }; // what the last Bind_Material() bound while recording