mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-03 17:50:21 +01:00
Stream model data through persistently mapped buffers
This commit is contained in:
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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
221
gl/streambuffer.cpp
Normal 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 ¤t{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
142
gl/streambuffer.h
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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() );
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user