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:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
4
gl/ubo.h
4
gl/ubo.h
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 ) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user