17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-18 07:59: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

@@ -11,13 +11,13 @@
_global Global;
bool check_shader(std::string filename)
bool check_shader(std::string filename, std::string const &defines = {})
{
gl::shader shader_processor;
shader_processor.name = filename;
gl::glsl_common_setup();
std::pair<GLuint, std::string> source = shader_processor.process_source(filename, "../shaders/");
std::pair<GLuint, std::string> source = shader_processor.process_source(filename, "../shaders/", defines);
std::string stage;
if (source.first == GL_VERTEX_SHADER)
@@ -70,6 +70,21 @@ bool check_shader_allopts(std::string filename)
if (!check_shader(filename))
suc = false;
// the variant of the vertex and fragment stages the multi draws of the instances use (model blocks by gl_DrawID)
auto const ext{std::filesystem::path(filename).extension()};
if (ext == ".vert" || ext == ".frag")
{
std::cout << "done, model draws..." << std::flush;
Global.gfx_shadowmap_enabled = true;
Global.gfx_envmap_enabled = true;
Global.gfx_postfx_motionblur_enabled = true;
Global.gfx_skippipeline = false;
Global.gfx_extraeffects = true;
Global.gfx_shadergamma = true;
if (!check_shader(filename, "#define MODEL_DRAWS 1\n"))
suc = false;
}
std::cout << "done" << std::flush;
return suc;

View File

@@ -273,6 +273,7 @@ views go as before.
| `gfx.gpucull.occlusion yes/no` | `yes` | with `gfx.gpucull yes`: models hidden whole behind what was drawn before them (the cab, the terrain, buildings and other models drawn on their own, vehicles) are left out |
| `gfx.gpucull.occlusion.instances yes/no` | `yes` | with `gfx.gpucull.occlusion yes`: the models drawn this way hide one another too (see below) |
| `gfx.depthprepass yes/no` | `yes` | with `gfx.gpucull yes`: the depth of the models is drawn first, so the colour is worked out only for what stays in view (dense foliage) |
| `gfx.gpucull.multidraw yes/no` | `yes` | with `gfx.gpucull yes`: parts of models with the same material (and texture variant) in the same model are drawn with one call (multi draw), see below |
| `gfx.depthprepass.scene yes/no` | `no` | the depth of the whole opaque scene of the main view (terrain, tracks, buildings, models, vehicles, the cab) is drawn before its colour, see below |
With `gfx.gpucull.occlusion.instances yes` the test goes in two phases: the models seen in the previous frame are drawn
@@ -286,6 +287,16 @@ is not worked out with the depth prepass on). Models whose material is not one o
like) are left out of the depth prepass and drawn as before; with the depth prepass on, they don't hide the others either.
All of these can be changed while the simulator runs, in the **Instance culling** debug window.
With `gfx.gpucull.multidraw yes` the draws of the models drawn from the GPU are put in an order in which the parts with the
same material, texture variant and model (the model keeps its geometry in a buffer of its own) follow one another, and each
such run goes in one call (`glMultiDrawElementsIndirect`); the data of every part (its position in the model, colours,
parameters) is read by the shaders from a buffer, by the number of the part in the call. The parts of one model at one level
of detail keep their order (parts of a model lying on one another are drawn as before), only the parts of different copies
and levels of detail are brought together: a trunk seen in two levels of detail, the six sides of a block in the same
material. The depth prepass joins the parts of a model with the same alpha-tested texture, whatever their material. The
picture is the same either way; `no` draws them one by one, as before. Materials whose shader can't be built in this form
(a message in the log) are drawn one by one.
With `gfx.depthprepass.scene yes` the scene is gone through twice: first only its depth is drawn, then its colour, worked
out once per pixel for the surface which stays in view. That costs the processor a second walk through the scene (fewer
draws than the colour pass: what the depth programs can't draw, like parallax materials and faded parts, is left to the
@@ -363,8 +374,8 @@ editor goes through the first `dynamiclights` headlights, the reflections throug
main view), `clouds panorama:` (a band of the panorama), `cloud shadows:` and `fog volume:`.
- **Instance culling** window: the mode of `gfx.gpucull` and the switches above. With `gfx.gpucull yes` Shift+F12 shows
`gpu cull:` (models drawn, in two phases how many of them first, and hidden by the occlusion test, as counted on the
graphics card a few frames before, the levels of detail, the draw calls with those of the depth prepass, and the processor
time of the whole step) and the timings `instance cull:`, `occlusion depth:` (the depth pyramid of the test) and
graphics card a few frames before, the levels of detail, the draws with those of the depth prepass and the calls which
made them — fewer than the draws with `gfx.gpucull.multidraw yes` — and the processor time of the whole step) and the timings `instance cull:`, `occlusion depth:` (the depth pyramid of the test) and
`depth prepass:`; in two phases each of them adds in brackets the time of the second phase, of the second pyramid and of
the depth of the models drawn first. With `compare` it shows how many models both counted and how many levels of detail
differ.
@@ -378,6 +389,8 @@ editor goes through the first `dynamiclights` headlights, the reflections throug
`model blocks:` row: how many blocks of model data the frame wrote, with `gfx.bufferstreaming yes` also how much of the
buffer of the frame they took, how many times a frame had to wait for the graphics card to finish with a buffer before
writing to it (`waits`, should stay at 0) and how many times a buffer had to grow (`grown`, a few times after the start).
With `gfx.gpucull yes` and `gfx.gpucull.multidraw yes` the `multi draw blocks:` row shows the same for the data of the
parts of models drawn with multi draws (one block per draw, written once a frame).
- `gfx.gldebug yes` turns on the OpenGL debug output (`KHR_debug`); messages go to the log.
## Building

View File

@@ -22,6 +22,7 @@ void gl::glsl_common_setup()
"#define INSTANCE_COUNTER_SSBO_BINDING " + std::to_string(INSTANCE_COUNTER_SSBO_BINDING) + "\n" +
"#define INSTANCE_OUTPUT_SSBO_BINDING " + std::to_string(INSTANCE_OUTPUT_SSBO_BINDING) + "\n" +
"#define INSTANCE_COMMAND_SSBO_BINDING " + std::to_string(INSTANCE_COMMAND_SSBO_BINDING) + "\n" +
"#define MODEL_DRAW_SSBO_BINDING " + std::to_string(MODEL_DRAW_SSBO_BINDING) + "\n" +
"#define INSTANCE_CULL_GROUP_SIZE " + std::to_string(INSTANCE_CULL_GROUP_SIZE) + "\n" +
"#define INSTANCE_OFFSETS_GROUP_SIZE " + std::to_string(INSTANCE_OFFSETS_GROUP_SIZE) + "\n" +
"#define INSTANCE_COMMAND_GROUP_SIZE " + std::to_string(INSTANCE_COMMAND_GROUP_SIZE) + "\n" +
@@ -187,6 +188,49 @@ void gl::glsl_common_setup()
};
#endif
#ifdef MODEL_DRAWS
// programs of the multi draws of the instances drawn from the gpu culling: the model block of every draw comes from a
// storage buffer, picked by the number of the draw in the multi draw. the fragment stage gets the number from the vertex
// stage (which writes f_model_draw), and the names of the uniform block below stand for the fields of the draw
struct model_draw_s
{
mat4 modelview;
mat3 modelviewnormal;
vec4 param[MAX_PARAMS];
mat4 future;
float opacity;
float emission;
float fog_density;
float alpha_mult;
float shadow_tone;
};
layout(std430, binding = MODEL_DRAW_SSBO_BINDING) readonly buffer model_draw_ssbo
{
model_draw_s model_draws[];
};
#if defined(STAGE_VERTEX)
flat out uint f_model_draw;
#define MODEL_DRAW gl_DrawID
#elif defined(STAGE_FRAGMENT)
flat in uint f_model_draw;
#define MODEL_DRAW f_model_draw
#else
#define MODEL_DRAW 0
#endif
#define modelview model_draws[MODEL_DRAW].modelview
#define modelviewnormal model_draws[MODEL_DRAW].modelviewnormal
#define param model_draws[MODEL_DRAW].param
#define future model_draws[MODEL_DRAW].future
#define opacity model_draws[MODEL_DRAW].opacity
#define emission model_draws[MODEL_DRAW].emission
#define fog_density model_draws[MODEL_DRAW].fog_density
#define alpha_mult model_draws[MODEL_DRAW].alpha_mult
#define shadow_tone model_draws[MODEL_DRAW].shadow_tone
#else
layout (std140) uniform model_ubo
{
mat4 modelview;
@@ -200,6 +244,7 @@ void gl::glsl_common_setup()
float alpha_mult;
float shadow_tone;
};
#endif
layout (std140) uniform scene_ubo
{

View File

@@ -74,7 +74,7 @@ std::unordered_map<std::string, gl::shader::defaultparam_e> gl::shader::defaultp
{ "glossiness", defaultparam_e::glossiness }
};
std::pair<GLuint, std::string> gl::shader::process_source(const std::string &filename, const std::string &basedir)
std::pair<GLuint, std::string> gl::shader::process_source(const std::string &filename, const std::string &basedir, const std::string &defines)
{
std::string str;
@@ -101,6 +101,7 @@ std::pair<GLuint, std::string> gl::shader::process_source(const std::string &fil
str += "vec4 FBOUT(vec4 x) { return " + (Global.gfx_shadergamma ? std::string("vec4(pow(x.rgb, vec3(1.0 / 2.2)), x.a)") : std::string("x")) + "; }\n";
// whether the colour written to the window is encoded by the shader (FBOUT) or left to the sRGB framebuffer (dither.glsl)
str += std::string("#define OUTPUT_GAMMA ") + (Global.gfx_shadergamma ? "1" : "0") + "\n";
str += defines;
str += read_file(basedir + filename);
@@ -231,10 +232,10 @@ void gl::shader::log_error(const std::string &str) const
ErrorLog("bad shader: " + name + ": " + str, logtype::shader);
}
gl::shader::shader(const std::string &filename) : name(filename)
gl::shader::shader(const std::string &filename, const std::string &defines) : name(filename)
{
auto [shadertype, shadersource] = process_source(filename, "shaders/");
auto [shadertype, shadersource] = process_source(filename, "shaders/", defines);
const GLchar *cstr = shadersource.c_str();

View File

@@ -21,7 +21,8 @@ namespace gl
#ifdef SHADERVALIDATOR_STANDALONE
shader() = default;
#endif
explicit shader(const std::string &filename);
// Defines: lines put before the source (after the version), e.g. "#define MODEL_DRAWS 1\n" for a variant of the shader
explicit shader(const std::string &filename, const std::string &defines = {});
~shader();
enum class components_e
@@ -67,7 +68,7 @@ namespace gl
#ifndef SHADERVALIDATOR_STANDALONE
private:
#endif
std::pair<GLuint, std::string> process_source(const std::string &filename, const std::string &basedir);
std::pair<GLuint, std::string> process_source(const std::string &filename, const std::string &basedir, const std::string &defines = {});
void expand_includes(std::string &str, const std::string &basedir);
void parse_texture_entries(std::string &str);

View File

@@ -472,6 +472,10 @@ namespace gl
const GLuint INSTANCE_COUNTER_SSBO_BINDING = 13; // header, then count[entries], cursor[entries] and first[entries]
const GLuint INSTANCE_OUTPUT_SSBO_BINDING = 14; // modelview matrices of the instances which passed, by entry
const GLuint INSTANCE_COMMAND_SSBO_BINDING = 15; // indirect draw commands of the entries (instance_cull_commands.comp)
// the model blocks of the draws of a multi draw of the instances drawn from the gpu culling, one gl::model_ubs per draw
// (std430, the same layout), read by gl_DrawID in programs built with MODEL_DRAWS. the point is the one of the jobs of the
// instance culling, which binds its buffers before every use; the draws bind theirs before every multi draw
const GLuint MODEL_DRAW_SSBO_BINDING = 10;
const GLuint INSTANCE_CULL_GROUP_SIZE = 64;
const GLuint INSTANCE_OFFSETS_GROUP_SIZE = 1024;
const GLuint INSTANCE_COMMAND_GROUP_SIZE = 64;

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

View File

@@ -17,4 +17,7 @@ void main()
vec4 position = mv * vec4(v_vert, 1.0);
gl_Position = projection * position;
f_coord = v_coord;
#ifdef MODEL_DRAWS
f_model_draw = uint(gl_DrawID);
#endif
}

View File

@@ -48,4 +48,8 @@ void main()
// TangentViewPos = TBN * vec3(0.0, 0.0, 0.0);
TangentFragPos = TBN * f_pos.xyz;
UserData = v_userdata;
#ifdef MODEL_DRAWS
// which model block of the multi draw the fragments read (glsl_common)
f_model_draw = uint(gl_DrawID);
#endif
}

View File

@@ -1449,6 +1449,11 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token
Parser.getTokens(1);
Parser >> gfx_depthprepass;
}
else if (Token == "gfx.gpucull.multidraw")
{
Parser.getTokens(1);
Parser >> gfx_gpucull_multidraw;
}
else if (Token == "gfx.depthprepass.scene")
{
Parser.getTokens(1);
@@ -1944,6 +1949,7 @@ global_settings::export_as_text( std::ostream &Output ) const {
export_as_text( Output, "gfx.gpucull.occlusion", gfx_gpucull_occlusion );
export_as_text( Output, "gfx.gpucull.occlusion.instances", gfx_gpucull_occlusion_instances );
export_as_text( Output, "gfx.depthprepass", gfx_depthprepass );
export_as_text( Output, "gfx.gpucull.multidraw", gfx_gpucull_multidraw );
export_as_text( Output, "gfx.depthprepass.scene", gfx_depthprepass_scene );
export_as_text( Output, "gfx.bufferstreaming", gfx_bufferstreaming );
export_as_text( Output, "gfx.postfx.motionblur.enabled", gfx_postfx_motionblur_enabled );

View File

@@ -291,6 +291,9 @@ struct global_settings {
bool gfx_gpucull_occlusion = true; // with gfx_gpucull 1: instances hidden behind the depth drawn before them are left out
bool gfx_gpucull_occlusion_instances = true; // with gfx_gpucull_occlusion: in two phases, so that instances hide one another too
bool gfx_depthprepass = true; // with gfx_gpucull 1: the depth of the instances is drawn before their colour
// with gfx_gpucull 1: the draws of the instances which follow one another with the same material and geometry bank are
// made with one multi draw, their model blocks read from a storage buffer by the number of the draw
bool gfx_gpucull_multidraw = true;
// (opengl 4.6) the depth of the whole opaque scene of the main view is drawn before its colour, the instances included
bool gfx_depthprepass_scene = false;
// (opengl 4.6) the model block of every draw and the matrices of instances are written to persistently mapped buffers,