17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-03 16:40:21 +01:00

Stream model data through persistently mapped buffers

This commit is contained in:
2026-10-17 01:24:33 +02:00
parent 8dd80c4d2e
commit f352718e4a
10 changed files with 441 additions and 39 deletions

View File

@@ -262,6 +262,7 @@ set(SOURCES
"gl/pbo.cpp" "gl/pbo.cpp"
"gl/query.cpp" "gl/query.cpp"
"gl/ssbo.cpp" "gl/ssbo.cpp"
"gl/streambuffer.cpp"
"gl/cluster_grid.cpp" "gl/cluster_grid.cpp"
"gl/readback.cpp" "gl/readback.cpp"

View File

@@ -246,6 +246,18 @@ drawn (its colour is not worked out with the depth prepass on). Models whose mat
water and the like) are left out of the depth prepass and drawn as before. All three can be changed while the simulator water and the like) are left out of the depth prepass and drawn as before. All three can be changed while the simulator
runs, in the **Instance culling** debug window. runs, in the **Instance culling** debug window.
### Model data of the draws
Before every draw the renderer hands the graphics card the data of the drawn part of the model (its position in the view, its
colours and parameters), and before batches of models the positions of the copies in the batch. With `gfx.bufferstreaming`
on, these go to buffers the processor writes to directly, a new place for every draw, one buffer for each of the last three
frames; before, every draw replaced the data of the one block with a separate copy through the driver. The picture is the
same either way.
| Entry | Default | Meaning |
|---|---|---|
| `gfx.bufferstreaming yes/no` | `yes` | `yes`: the data of the draws is written to persistently mapped buffers. `no`: as before (`glBufferSubData`), for comparison or when a driver misbehaves. Read at the start |
### Changed option ### Changed option
`dynamiclights` (0..7) used to limit the number of headlight sets that light the surroundings. It is still `dynamiclights` (0..7) used to limit the number of headlight sets that light the surroundings. It is still
@@ -305,6 +317,10 @@ editor goes through the first `dynamiclights` headlights, the reflections throug
of detail, the draw calls with those of the depth prepass, and the processor time of the whole step) and the timings 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 `depth prepass:`. With `compare` it shows how many `instance cull:`, `occlusion depth:` (the depth pyramid of the test) and `depth prepass:`. With `compare` it shows how many
models both counted and how many levels of detail differ. models both counted and how many levels of detail differ.
- Shift+F12 shows the `instance buffer:` row (positions of copies written in the frame and the size of the buffer) and the
`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).
- `gfx.gldebug yes` turns on the OpenGL debug output (`KHR_debug`); messages go to the log. - `gfx.gldebug yes` turns on the OpenGL debug output (`KHR_debug`); messages go to the log.
## Building ## Building

221
gl/streambuffer.cpp Normal file
View File

@@ -0,0 +1,221 @@
#include "stdafx.h"
#include "streambuffer.h"
#include <algorithm>
#include <cstring>
#include <string>
#include "utilities/Logs.h"
namespace
{
// a single frame isn't given more than this; past it the frame waits for the gpu to finish everything and starts the buffer over
constexpr GLsizeiptr STREAM_SIZE_MAX = 256 * 1024 * 1024;
constexpr GLbitfield STREAM_MAP_FLAGS = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT;
GLsizeiptr align_up(GLsizeiptr const Value, GLsizeiptr const Alignment)
{
return (Value + Alignment - 1) / Alignment * Alignment;
}
} // namespace
gl::frame_stream::frame_stream(GLsizeiptr Size, GLsizeiptr Alignment, bool Mapped, void const *Header, GLsizeiptr Headersize)
: m_alignment(std::max<GLsizeiptr>(Alignment, 1)), m_mapped(Mapped)
{
if (Header != nullptr && Headersize > 0)
{
auto const *bytes{static_cast<std::uint8_t const *>(Header)};
m_header.assign(bytes, bytes + Headersize);
}
m_start = align_up(static_cast<GLsizeiptr>(m_header.size()), m_alignment);
m_fill = m_start;
Size = std::max(Size, m_start + m_alignment);
for (auto &slot : m_slots)
{
create(slot, Size);
}
}
gl::frame_stream::~frame_stream()
{
for (auto &slot : m_slots)
{
release(slot);
}
}
void gl::frame_stream::create(slot &Slot, GLsizeiptr Size)
{
++m_generation;
glCreateBuffers(1, &Slot.buffer);
Slot.size = Size;
Slot.data = nullptr;
if (m_mapped)
{
glNamedBufferStorage(Slot.buffer, Size, nullptr, STREAM_MAP_FLAGS);
Slot.data = static_cast<std::uint8_t *>(glMapNamedBufferRange(Slot.buffer, 0, Size, STREAM_MAP_FLAGS));
if (Slot.data == nullptr)
{
// the driver wouldn't map it: this buffer and the ones made later are written the plain way
ErrorLog("Bad buffer: persistent mapping failed, buffer streaming falls back to glNamedBufferSubData");
m_mapped = false;
glDeleteBuffers(1, &Slot.buffer);
glCreateBuffers(1, &Slot.buffer);
}
}
if (!m_mapped)
{
glNamedBufferStorage(Slot.buffer, Size, nullptr, GL_DYNAMIC_STORAGE_BIT);
}
if (!m_header.empty())
{
if (Slot.data != nullptr)
std::memcpy(Slot.data, m_header.data(), m_header.size());
else
glNamedBufferSubData(Slot.buffer, 0, static_cast<GLsizeiptr>(m_header.size()), m_header.data());
}
}
void gl::frame_stream::release(slot &Slot)
{
if (Slot.fence != nullptr)
{
glDeleteSync(Slot.fence);
Slot.fence = nullptr;
}
if (Slot.buffer != 0)
{
// a mapped buffer is unmapped by its deletion. draws issued before which read it keep its storage until they are done
glDeleteBuffers(1, &Slot.buffer);
Slot.buffer = 0;
}
Slot.data = nullptr;
Slot.size = 0;
}
void gl::frame_stream::wait(slot &Slot)
{
if (Slot.fence == nullptr)
return;
auto result{glClientWaitSync(Slot.fence, 0, 0)};
if (result == GL_TIMEOUT_EXPIRED)
{
++m_waits;
do
{
result = glClientWaitSync(Slot.fence, GL_SYNC_FLUSH_COMMANDS_BIT, 1000000000);
} while (result == GL_TIMEOUT_EXPIRED);
}
glDeleteSync(Slot.fence);
Slot.fence = nullptr;
}
void gl::frame_stream::next_frame()
{
auto &current{m_slots[m_slot]};
if (current.data != nullptr)
{
// the cpu writes to a mapped buffer behind the back of the driver, the frames take turns by the fences
if (current.fence != nullptr)
glDeleteSync(current.fence);
current.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
// the fence may be waited for in another context, whose flush wouldn't send it
glFlush();
}
m_slot = (m_slot + 1) % FRAMES;
wait(m_slots[m_slot]);
m_fill = m_start;
}
GLintptr gl::frame_stream::push(void const *Data, GLsizeiptr Size)
{
auto *current{&m_slots[m_slot]};
auto offset{align_up(m_fill, m_alignment)};
if (offset + Size > current->size)
{
auto const needed{m_start + align_up(Size, m_alignment)};
auto const size{needed > STREAM_SIZE_MAX ? needed : std::min(std::max(needed, 2 * current->size), STREAM_SIZE_MAX)};
if (size > current->size)
{
// a bigger buffer for the rest of the frame, starting after its header again. the fence of the slot (if any) was
// waited for when the frame took the slot over, the old buffer is only read by the draws of this frame
release(*current);
create(*current, size);
++m_grows;
}
else
{
// the frame wrote as much as a buffer may hold: start it over once the gpu read all of it
ErrorLog("Bad buffer: a frame streamed more than " + std::to_string(STREAM_SIZE_MAX / (1024 * 1024)) + " MiB, waiting for the gpu");
glFinish();
++m_waits;
}
current = &m_slots[m_slot];
offset = m_start;
}
if (current->data != nullptr)
std::memcpy(current->data + offset, Data, static_cast<std::size_t>(Size));
else
glNamedBufferSubData(current->buffer, offset, Size, Data);
m_fill = offset + Size;
return offset;
}
gl::stream_ubo::stream_ubo(GLsizeiptr Blocksize, GLuint Index, bool Streamed) : m_index(Index), m_blocksize(Blocksize)
{
if (!Streamed)
{
m_plain = std::make_unique<ubo>(static_cast<size_t>(Blocksize), static_cast<int>(Index), GL_STREAM_DRAW);
return;
}
GLint alignment{256};
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &alignment);
// a frame draws some thousands of times; the buffers grow when it draws more
m_stream = std::make_unique<frame_stream>(align_up(Blocksize, std::max<GLsizeiptr>(alignment, 1)) * 8192, std::max<GLint>(alignment, 1), true);
m_last.resize(static_cast<std::size_t>(Blocksize));
}
void gl::stream_ubo::update(void const *Data, GLsizeiptr Size)
{
++m_updates;
if (m_plain)
{
m_plain->update(static_cast<uint8_t const *>(Data), 0, Size);
return;
}
m_boundoffset = m_stream->push(Data, Size);
m_boundbuffer = m_stream->buffer();
m_lastsize = std::min(Size, m_blocksize);
if (Data != m_last.data())
std::memcpy(m_last.data(), Data, static_cast<std::size_t>(m_lastsize));
// the plural form leaves the general GL_UNIFORM_BUFFER binding alone, which the binding tracker of gl::buffer relies on
GLsizeiptr const size{Size};
glBindBuffersRange(GL_UNIFORM_BUFFER, m_index, 1, &m_boundbuffer, &m_boundoffset, &size);
}
void gl::stream_ubo::bind_uniform()
{
if (m_plain)
{
m_plain->bind_uniform();
return;
}
if (m_boundbuffer == 0)
return;
GLsizeiptr const size{m_lastsize};
glBindBuffersRange(GL_UNIFORM_BUFFER, m_index, 1, &m_boundbuffer, &m_boundoffset, &size);
}
void gl::stream_ubo::next_frame()
{
m_updates = 0;
if (m_plain)
return;
m_stream->next_frame();
if (m_lastsize > 0)
{
update(m_last.data(), m_lastsize);
m_updates = 0;
}
}

142
gl/streambuffer.h Normal file
View File

@@ -0,0 +1,142 @@
#pragma once
#include <array>
#include <cstdint>
#include <memory>
#include <type_traits>
#include <vector>
#include "object.h"
#include "ubo.h"
namespace gl
{
// data the cpu writes for the draws of one frame (the uniform block of every draw, matrices of instances), appended to the
// buffer of the frame. the frames use FRAMES buffers in turn. mapped: the buffers are persistently mapped and written with
// memcpy, a fence marks the end of each frame, and a frame waits for the fence of the buffer it takes over (written
// FRAMES - 1 frames before; normally the gpu is long done with it). not mapped: written with glNamedBufferSubData, the driver
// keeps it in step with the gpu, as with any other buffer
class frame_stream
{
public:
static constexpr std::size_t FRAMES = 3;
// Size: initial size of the buffer of a frame, it grows when a frame needs more. Alignment: of the offsets push()
// returns. Header: bytes placed at the start of every buffer, never overwritten (nullptr: none)
frame_stream(GLsizeiptr Size, GLsizeiptr Alignment, bool Mapped, void const *Header = nullptr, GLsizeiptr Headersize = 0);
~frame_stream();
frame_stream(frame_stream const &) = delete;
frame_stream &operator=(frame_stream const &) = delete;
// ends the frame: what was written so far goes with its fence, the next frame writes to the next buffer
void next_frame();
// appends Size bytes to the buffer of the frame and returns their offset. may replace the buffer of the frame with a
// bigger one (generation() changes, buffer() tells which to bind); the draws issued before keep the old one until the
// gpu is done with them
GLintptr push(void const *Data, GLsizeiptr Size);
GLuint buffer() const
{
return m_slots[m_slot].buffer;
}
// changes whenever buffer() is replaced by a new buffer object. the name alone can't tell: gl hands the name of a
// deleted buffer to the next one made
std::uint64_t generation() const
{
return m_generation;
}
// size of the buffer of the frame, and bytes written to it in this frame (after the header)
GLsizeiptr size() const
{
return m_slots[m_slot].size;
}
GLsizeiptr used() const
{
return m_fill - m_start;
}
// the buffer of the frame is persistently mapped
bool mapped() const
{
return m_slots[m_slot].data != nullptr;
}
// frames which had to wait for the gpu before writing, buffers grown, since the start
std::uint64_t waits() const
{
return m_waits;
}
std::uint64_t grows() const
{
return m_grows;
}
private:
struct slot
{
GLuint buffer = 0;
GLsizeiptr size = 0;
std::uint8_t *data = nullptr; // mapped
GLsync fence = nullptr; // mapped: end of the last frame which wrote to the buffer
};
void create(slot &Slot, GLsizeiptr Size);
void release(slot &Slot);
void wait(slot &Slot);
std::array<slot, FRAMES> m_slots;
std::size_t m_slot = 0;
GLsizeiptr m_alignment;
bool m_mapped; // buffers made from now on are mapped
std::vector<std::uint8_t> m_header;
GLsizeiptr m_start; // the header, aligned
GLsizeiptr m_fill;
std::uint64_t m_waits = 0;
std::uint64_t m_grows = 0;
std::uint64_t m_generation = 0;
};
// uniform block written anew for (nearly) every draw, the model block of the renderer. streamed: every update() goes to
// its own place in a frame_stream and is bound there with glBindBuffersRange. not streamed: one plain ubo, updated with
// glBufferSubData, as before
class stream_ubo
{
public:
stream_ubo(GLsizeiptr Blocksize, GLuint Index, bool Streamed);
void update(void const *Data, GLsizeiptr Size);
template <typename T> void update(T const &Data)
{
static_assert(std::is_trivially_copyable_v<T>, "ubo data must be trivially copyable");
update(&Data, static_cast<GLsizeiptr>(sizeof(Data)));
}
// binds the block written last (to a new context)
void bind_uniform();
// call at the start of a frame: the last block is copied to the buffer of the new frame and bound, so nothing drawn in
// it reads a buffer the frame doesn't fence
void next_frame();
bool streamed() const
{
return m_stream != nullptr;
}
frame_stream const *stream() const
{
return m_stream.get();
}
// blocks written in this frame
std::uint64_t updates() const
{
return m_updates;
}
private:
GLuint m_index;
GLsizeiptr m_blocksize;
std::unique_ptr<ubo> m_plain;
std::unique_ptr<frame_stream> m_stream;
std::vector<std::uint8_t> m_last; // streamed: copy of the last block
GLsizeiptr m_lastsize = 0;
GLuint m_boundbuffer = 0;
GLintptr m_boundoffset = 0;
std::uint64_t m_updates = 0;
};
} // namespace gl

View File

@@ -116,7 +116,7 @@ void opengl46_exposure::measure(opengl_texture &Image, double const Deltatime)
opengl_texture::reset_unit_cache(); opengl_texture::reset_unit_cache();
} }
opengl_texture *opengl46_exposure::bloom(bloom_chain &Chain, opengl_texture &Image, gl::ubo &Modelubo, gl::model_ubs &Model) opengl_texture *opengl46_exposure::bloom(bloom_chain &Chain, opengl_texture &Image, gl::stream_ubo &Modelubo, gl::model_ubs &Model)
{ {
m_bloom_timer.begin(); m_bloom_timer.begin();
// down, the first step from the image itself // down, the first step from the image itself

View File

@@ -15,6 +15,7 @@ http://mozilla.org/MPL/2.0/.
#include "gl/ubo.h" #include "gl/ubo.h"
#include "gl/ssbo.h" #include "gl/ssbo.h"
#include "gl/streambuffer.h"
#include "gl/shader.h" #include "gl/shader.h"
#include "gl/postfx.h" #include "gl/postfx.h"
#include "gl/framebuffer.h" #include "gl/framebuffer.h"
@@ -56,7 +57,7 @@ class opengl46_exposure
void measure(opengl_texture &Image, double const Deltatime); void measure(opengl_texture &Image, double const Deltatime);
// takes the image down and up the chain; returns the result, which the tonemapping blends in. leaves the viewport // takes the image down and up the chain; returns the result, which the tonemapping blends in. leaves the viewport
// and the framebuffer binding changed. Model is the model_ubs the post processing shaders read the parameters from // and the framebuffer binding changed. Model is the model_ubs the post processing shaders read the parameters from
opengl_texture *bloom(bloom_chain &Chain, opengl_texture &Image, gl::ubo &Modelubo, gl::model_ubs &Model); opengl_texture *bloom(bloom_chain &Chain, opengl_texture &Image, gl::stream_ubo &Modelubo, gl::model_ubs &Model);
// parameters of the tonemapping (postfx_tonemapping.frag), with bloom from a chain of Bloomlevels levels (0: none) // parameters of the tonemapping (postfx_tonemapping.frag), with bloom from a chain of Bloomlevels levels (0: none)
glm::vec4 tonemap_params(std::size_t const Bloomlevels) const; glm::vec4 tonemap_params(std::size_t const Bloomlevels) const;
// lines for the gpu times of the debug panel // lines for the gpu times of the debug panel

View File

@@ -178,14 +178,19 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
} }
scene_ubo = std::make_unique<gl::ubo>(sizeof(gl::scene_ubs), 0); scene_ubo = std::make_unique<gl::ubo>(sizeof(gl::scene_ubs), 0);
model_ubo = std::make_unique<gl::ubo>(sizeof(gl::model_ubs), 1, GL_STREAM_DRAW); model_ubo = std::make_unique<gl::stream_ubo>(sizeof(gl::model_ubs), 1, Global.gfx_bufferstreaming);
light_ssbo = std::make_unique<gl::ssbo>(sizeof(gl::light_data), gl::LIGHT_SSBO_BINDING); light_ssbo = std::make_unique<gl::ssbo>(sizeof(gl::light_data), gl::LIGHT_SSBO_BINDING);
light_ssbo->bind_base(); light_ssbo->bind_base();
for (auto &buffer : m_instancebuffers)
{ {
// grows when a frame needs more // element 0 of the instance buffers is identity. This is the matrix sampled by
buffer = std::make_unique<gl::ssbo>(static_cast<GLsizeiptr>(16384 * sizeof(glm::mat4)), gl::INSTANCE_SSBO_BINDING); // non-instanced draws, so existing rendering paths
// continue to multiply effective_modelview = identity * modelview = modelview.
// the buffers grow when a frame needs more
glm::mat4 const identity(1.0f);
m_instancestream = std::make_unique<gl::frame_stream>(
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"));
try try
{ {
@@ -213,15 +218,7 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
model_ubo->update(model_ubs); model_ubo->update(model_ubs);
scene_ubo->update(scene_ubs); scene_ubo->update(scene_ubs);
// element 0 of the instance buffers is identity. This is the matrix sampled by Bind_Instance_Stream();
// non-instanced draws, so existing rendering paths
// continue to multiply effective_modelview = identity * modelview = modelview.
for (auto &buffer : m_instancebuffers)
{
glm::mat4 const identity(1.0f);
buffer->update(identity);
}
m_instancebuffers[m_instancebufferindex]->bind_base();
int samples = 1 << Global.iMultisampling; int samples = 1 << Global.iMultisampling;
if (samples > 1) if (samples > 1)
@@ -659,10 +656,11 @@ bool opengl46_renderer::Render()
// scenery lamps which get shadows in this frame. their maps hold the scenery as of the version of the instance records // scenery lamps which get shadows in this frame. their maps hold the scenery as of the version of the instance records
if (simulation::is_ready && (false == Global.gfx_skiprendering)) if (simulation::is_ready && (false == Global.gfx_skiprendering))
m_localshadows.update_lamps(m_locallights.lights(), Global.pCamera.Pos, m_instancerecordsversion, m_framestamp); m_localshadows.update_lamps(m_locallights.lights(), Global.pCamera.Pos, m_instancerecordsversion, m_framestamp);
// instanced draws of the frame place their matrices in the next of the instance buffers, the one used the longest ago // instanced draws of the frame place their matrices in the next of the instance buffers, the one used the longest ago;
m_instancebufferindex = ( m_instancebufferindex + 1 ) % m_instancebuffers.size(); // the same for the model blocks of the draws
m_instancebufferfill = 1; m_instancestream->next_frame();
m_instancebuffers[ m_instancebufferindex ]->bind_base(); Bind_Instance_Stream();
model_ubo->next_frame();
m_renderpass.draw_mode = rendermode::none; // force setup anew m_renderpass.draw_mode = rendermode::none; // force setup anew
m_renderpass.draw_stats = debug_stats(); m_renderpass.draw_stats = debug_stats();
@@ -676,6 +674,8 @@ bool opengl46_renderer::Render()
} }
glfwMakeContextCurrent(m_window); glfwMakeContextCurrent(m_window);
model_ubo->bind_uniform();
Bind_Instance_Stream();
gl::framebuffer::unbind(); gl::framebuffer::unbind();
@@ -766,8 +766,14 @@ void opengl46_renderer::SwapBuffers()
+ " instanced:" + to_string( m_colorpass.draw_stats.instances, 7 ) + " +" + to_string( shadowstats.instances, 7 ) + " instanced:" + to_string( m_colorpass.draw_stats.instances, 7 ) + " +" + to_string( shadowstats.instances, 7 )
+ " =" + to_string( m_colorpass.draw_stats.instances + shadowstats.instances, 7 ) + " =" + to_string( m_colorpass.draw_stats.instances + shadowstats.instances, 7 )
+ " (" + std::to_string( m_colorpass.draw_stats.instanced_drawcalls + shadowstats.instanced_drawcalls ) + " batches)\n" + " (" + std::to_string( m_colorpass.draw_stats.instanced_drawcalls + shadowstats.instanced_drawcalls ) + " batches)\n"
+ " instance buffer: " + std::to_string( m_instancebufferfill - 1 ) + " matrices, " + " instance buffer: " + std::to_string( m_instancestream->used() / static_cast<GLsizeiptr>( sizeof( glm::mat4 ) ) ) + " matrices, "
+ std::to_string( m_instancebuffers[ m_instancebufferindex ]->size() / 1024 ) + " KiB\n" + std::to_string( m_instancestream->size() / 1024 ) + " KiB\n"
+ " model blocks: " + std::to_string( model_ubo->updates() )
+ ( model_ubo->streamed() ?
", " + std::to_string( model_ubo->stream()->used() / 1024 ) + "/" + std::to_string( model_ubo->stream()->size() / 1024 ) + " KiB, streamed (waits "
+ std::to_string( model_ubo->stream()->waits() + m_instancestream->waits() ) + ", grown "
+ std::to_string( model_ubo->stream()->grows() + m_instancestream->grows() ) + ")\n" :
", glBufferSubData\n" )
+ " inst-pool:" + std::to_string( TAnimModel::s_instanceable_total ) + " inst-pool:" + std::to_string( TAnimModel::s_instanceable_total )
+ "/" + std::to_string( TAnimModel::s_classified_total ) + "/" + std::to_string( TAnimModel::s_classified_total )
+ " rej(noModel/lights/anim/animSubM): " + " rej(noModel/lights/anim/animSubM): "
@@ -900,6 +906,9 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
{ {
glfwMakeContextCurrent(vp.window); glfwMakeContextCurrent(vp.window);
m_current_viewport = &vp; m_current_viewport = &vp;
// the streamed model block and the instance buffer of the frame move with every update, bindings are per context
model_ubo->bind_uniform();
Bind_Instance_Stream();
} }
if ((!simulation::is_ready) || (Global.gfx_skiprendering)) if ((!simulation::is_ready) || (Global.gfx_skiprendering))
@@ -3432,20 +3441,19 @@ void opengl46_renderer::Update_Instance_Records( TModel3d const *Model, scene::b
// places supplied modelview matrices in the instance buffer of the frame. returns: base instance of the first of them // places supplied modelview matrices in the instance buffer of the frame. returns: base instance of the first of them
std::uint32_t opengl46_renderer::Upload_Instance_Modelviews( glm::mat4 const *Modelviews, std::size_t const Count ) std::uint32_t opengl46_renderer::Upload_Instance_Modelviews( glm::mat4 const *Modelviews, std::size_t const Count )
{ {
auto &buffer { m_instancebuffers[ m_instancebufferindex ] }; auto const generation { m_instancestream->generation() };
auto const needed { static_cast<GLsizeiptr>( ( m_instancebufferfill + Count ) * sizeof( glm::mat4 ) ) }; auto const offset { m_instancestream->push( Modelviews, static_cast<GLsizeiptr>( Count * sizeof( glm::mat4 ) ) ) };
if( needed > buffer->size() ) { if( m_instancestream->generation() != generation ) {
// the draws issued with the replaced buffer keep it until the gpu is done with them. the new one starts with identity // the draws issued with the replaced buffer keep it until the gpu is done with them. the new one starts with identity
buffer->resize( std::max( needed, 2 * buffer->size() ) ); Bind_Instance_Stream();
glm::mat4 const identity( 1.0f );
buffer->update( identity );
buffer->bind_base();
m_instancebufferfill = 1;
} }
auto const base { static_cast<std::uint32_t>( m_instancebufferfill ) }; return static_cast<std::uint32_t>( offset / static_cast<GLintptr>( sizeof( glm::mat4 ) ) );
buffer->update( Modelviews, static_cast<GLintptr>( base * sizeof( glm::mat4 ) ), static_cast<GLsizeiptr>( Count * sizeof( glm::mat4 ) ) ); }
m_instancebufferfill += Count;
return base; void opengl46_renderer::Bind_Instance_Stream() const
{
GLuint const buffer { m_instancestream->buffer() };
::glBindBuffersBase( GL_SHADER_STORAGE_BUFFER, gl::INSTANCE_SSBO_BINDING, 1, &buffer );
} }
// True GPU-instanced render path for a group of TAnimModel instances sharing the same // True GPU-instanced render path for a group of TAnimModel instances sharing the same
@@ -3792,7 +3800,7 @@ void opengl46_renderer::Render_Instances_Indirect()
model_ubs.emission = 0.0f; model_ubs.emission = 0.0f;
::glBindBuffer( GL_DRAW_INDIRECT_BUFFER, 0 ); ::glBindBuffer( GL_DRAW_INDIRECT_BUFFER, 0 );
m_instancebuffers[ m_instancebufferindex ]->bind_base(); Bind_Instance_Stream();
} }
m_renderpass.draw_stats.instanced_drawcalls += static_cast<int>( m_indirectdraws.size() ); m_renderpass.draw_stats.instanced_drawcalls += static_cast<int>( m_indirectdraws.size() );

View File

@@ -22,6 +22,7 @@ http://mozilla.org/MPL/2.0/.
#include "vr/vr_interface.h" #include "vr/vr_interface.h"
#include "gl/ubo.h" #include "gl/ubo.h"
#include "gl/ssbo.h" #include "gl/ssbo.h"
#include "gl/streambuffer.h"
#include "gl/cluster_grid.h" #include "gl/cluster_grid.h"
#include "gl/framebuffer.h" #include "gl/framebuffer.h"
#include "gl/renderbuffer.h" #include "gl/renderbuffer.h"
@@ -343,6 +344,8 @@ class opengl46_renderer : public gfx_renderer {
void Update_Instance_Records( TModel3d const *Model, scene::basic_cell::instance_bucket const &Bucket, glm::dvec3 const &Origin ); void Update_Instance_Records( TModel3d const *Model, scene::basic_cell::instance_bucket const &Bucket, glm::dvec3 const &Origin );
// places supplied modelview matrices in the instance buffer of the frame. returns: base instance of the first of them // places supplied modelview matrices in the instance buffer of the frame. returns: base instance of the first of them
std::uint32_t Upload_Instance_Modelviews( glm::mat4 const *Modelviews, std::size_t const Count ); std::uint32_t Upload_Instance_Modelviews( glm::mat4 const *Modelviews, std::size_t const Count );
// binds the instance buffer of the frame for the vertex shaders (INSTANCE_SSBO_BINDING)
void Bind_Instance_Stream() const;
// draws the instances queued in the pass from the result of the gpu culling (Global.gfx_gpucull 1): makes the depth // draws the instances queued in the pass from the result of the gpu culling (Global.gfx_gpucull 1): makes the depth
// pyramid of the occlusion test, runs the culling, walks the submodel trees of the groups once per lod band to record // pyramid of the occlusion test, runs the culling, walks the submodel trees of the groups once per lod band to record
// the draws (m_indirectdraws, m_indirectcommands), has their instances filled in, then draws them with indirect draws, // the draws (m_indirectdraws, m_indirectcommands), has their instances filled in, then draws them with indirect draws,
@@ -587,16 +590,17 @@ class opengl46_renderer : public gfx_renderer {
std::unique_ptr<gl::shader> m_vertex_shader; std::unique_ptr<gl::shader> m_vertex_shader;
std::unique_ptr<gl::ubo> scene_ubo; std::unique_ptr<gl::ubo> scene_ubo;
std::unique_ptr<gl::ubo> model_ubo; // the model block of every draw, streamed (Global.gfx_bufferstreaming): each update to its own place in the buffer of
// the frame instead of glBufferSubData of the one block
std::unique_ptr<gl::stream_ubo> model_ubo;
std::unique_ptr<gl::ssbo> light_ssbo; std::unique_ptr<gl::ssbo> light_ssbo;
// per-instance modelview matrices of GPU-instanced draws, one buffer for each of the last frames, used in turn so // per-instance modelview matrices of GPU-instanced draws, one buffer for each of the last frames, used in turn so
// a frame doesn't write to the buffer the gpu may still read for an earlier one. element 0 of each is identity // a frame doesn't write to the buffer the gpu may still read for an earlier one. element 0 of each is identity
// and never overwritten: draws which aren't instanced (gl_BaseInstance == gl_InstanceID == 0) compute // and never overwritten: draws which aren't instanced (gl_BaseInstance == gl_InstanceID == 0) compute
// identity*modelview in the vertex shader. Render_Instanced and Render_Sleepers append the camera-space root // identity*modelview in the vertex shader. Render_Instanced and Render_Sleepers append the camera-space root
// matrices of their instances past the ones placed earlier in the frame, and draw them from that base instance // matrices of their instances past the ones placed earlier in the frame, and draw them from that base instance.
std::array<std::unique_ptr<gl::ssbo>, 3> m_instancebuffers; // persistently mapped with Global.gfx_bufferstreaming
std::size_t m_instancebufferindex { 0 }; // the buffer of the current frame std::unique_ptr<gl::frame_stream> m_instancestream;
std::size_t m_instancebufferfill { 1 }; // matrices placed in it so far, identity included
// when > 0, the renderer is inside a Render_Instanced() call. The draw(handle) // when > 0, the renderer is inside a Render_Instanced() call. The draw(handle)
// method routes to glDrawElementsInstanced* with this many instances from // method routes to glDrawElementsInstanced* with this many instances from
// m_current_instance_base onwards rather than a single regular draw. The vertex shader reads // m_current_instance_base onwards rather than a single regular draw. The vertex shader reads

View File

@@ -1444,6 +1444,11 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token
Parser.getTokens(1); Parser.getTokens(1);
Parser >> gfx_depthprepass; Parser >> gfx_depthprepass;
} }
else if (Token == "gfx.bufferstreaming")
{
Parser.getTokens(1);
Parser >> gfx_bufferstreaming;
}
else if (Token == "gfx.lights.clusterheatmap") else if (Token == "gfx.lights.clusterheatmap")
{ {
Parser.getTokens(1); Parser.getTokens(1);
@@ -1913,6 +1918,7 @@ global_settings::export_as_text( std::ostream &Output ) const {
export_as_text( Output, "gfx.gpucull", std::string{ gfx_gpucull == 1 ? "yes" : gfx_gpucull == 2 ? "compare" : "no" } ); export_as_text( Output, "gfx.gpucull", std::string{ gfx_gpucull == 1 ? "yes" : gfx_gpucull == 2 ? "compare" : "no" } );
export_as_text( Output, "gfx.gpucull.occlusion", gfx_gpucull_occlusion ); export_as_text( Output, "gfx.gpucull.occlusion", gfx_gpucull_occlusion );
export_as_text( Output, "gfx.depthprepass", gfx_depthprepass ); export_as_text( Output, "gfx.depthprepass", gfx_depthprepass );
export_as_text( Output, "gfx.bufferstreaming", gfx_bufferstreaming );
export_as_text( Output, "gfx.postfx.motionblur.enabled", gfx_postfx_motionblur_enabled ); export_as_text( Output, "gfx.postfx.motionblur.enabled", gfx_postfx_motionblur_enabled );
export_as_text( Output, "gfx.postfx.motionblur.shutter", gfx_postfx_motionblur_shutter ); export_as_text( Output, "gfx.postfx.motionblur.shutter", gfx_postfx_motionblur_shutter );
// TODO: export gfx_postfx_motionblur_format // TODO: export gfx_postfx_motionblur_format

View File

@@ -290,6 +290,9 @@ struct global_settings {
int gfx_gpucull = 0; int gfx_gpucull = 0;
bool gfx_gpucull_occlusion = true; // with gfx_gpucull 1: instances hidden behind the depth drawn before them are left out bool gfx_gpucull_occlusion = true; // with gfx_gpucull 1: instances hidden behind the depth drawn before them are left out
bool gfx_depthprepass = true; // with gfx_gpucull 1: the depth of the instances is drawn before their colour bool gfx_depthprepass = true; // with gfx_gpucull 1: the depth of the instances is drawn before their colour
// (opengl 4.6) the model block of every draw and the matrices of instances are written to persistently mapped buffers,
// one for each of the last frames, instead of with glBufferSubData. read at the start
bool gfx_bufferstreaming = true;
bool gfx_postfx_motionblur_enabled = true; bool gfx_postfx_motionblur_enabled = true;
float gfx_postfx_motionblur_shutter = 0.01f; float gfx_postfx_motionblur_shutter = 0.01f;
GLenum gfx_postfx_motionblur_format = GL_RG16F; GLenum gfx_postfx_motionblur_format = GL_RG16F;