diff --git a/CMakeLists.txt b/CMakeLists.txt index 2534c64a..e69b44bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -258,6 +258,7 @@ set(SOURCES "gl/fence.cpp" "gl/pbo.cpp" "gl/query.cpp" +"gl/ssbo.cpp" "imgui/imgui.cpp" "imgui/imgui_demo.cpp" diff --git a/gl/query.cpp b/gl/query.cpp index 99825fa4..3ee4347c 100644 --- a/gl/query.cpp +++ b/gl/query.cpp @@ -61,3 +61,64 @@ GLenum gl::query::glenum_target(targets Target) const } thread_local gl::query* gl::query::active_queries[6]; + +gl::gpu_timer::~gpu_timer() +{ + if (m_queries[0] != 0) + glDeleteQueries(static_cast(m_queries.size()), m_queries.data()); +} + +void gl::gpu_timer::begin() +{ + if (m_queries[0] == 0) + glCreateQueries(GL_TIMESTAMP, static_cast(m_queries.size()), m_queries.data()); + + collect(); + m_active = !m_pending[m_slot]; + if (m_active) + glQueryCounter(m_queries[m_slot * 2], GL_TIMESTAMP); +} + +void gl::gpu_timer::end() +{ + if (!m_active) + return; + + glQueryCounter(m_queries[m_slot * 2 + 1], GL_TIMESTAMP); + m_pending[m_slot] = true; + m_slot = (m_slot + 1) % SLOTS; + m_active = false; +} + +std::optional gl::gpu_timer::result() +{ + if (m_queries[0] != 0) + collect(); + + return m_last; +} + +void gl::gpu_timer::collect() +{ + // slots finish in the order they were issued, oldest first + for (size_t i = 0; i < SLOTS; ++i) + { + size_t const slot = (m_slot + i) % SLOTS; + if (!m_pending[slot]) + continue; + + GLint startready = GL_FALSE; + GLint endready = GL_FALSE; + glGetQueryObjectiv(m_queries[slot * 2], GL_QUERY_RESULT_AVAILABLE, &startready); + glGetQueryObjectiv(m_queries[slot * 2 + 1], GL_QUERY_RESULT_AVAILABLE, &endready); + if (!startready || !endready) + break; + + GLuint64 start = 0; + GLuint64 end = 0; + glGetQueryObjectui64v(m_queries[slot * 2], GL_QUERY_RESULT, &start); + glGetQueryObjectui64v(m_queries[slot * 2 + 1], GL_QUERY_RESULT, &end); + m_last = end >= start ? end - start : 0; + m_pending[slot] = false; + } +} diff --git a/gl/query.h b/gl/query.h index 12445790..4960c272 100644 --- a/gl/query.h +++ b/gl/query.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include "object.h" namespace gl @@ -33,4 +36,33 @@ public: std::optional result(); }; + +// gpu time of a section of commands, from a pair of GL_TIMESTAMP queries. unlike GL_TIME_ELAPSED, timestamps +// can be taken while another time query runs (e.g. the whole-frame query of the renderer). results are read +// a few frames later without stalling; if the gpu falls further behind, the measurement is skipped +class gpu_timer +{ + public: + gpu_timer() = default; + ~gpu_timer(); + gpu_timer(const gpu_timer &) = delete; + gpu_timer &operator=(const gpu_timer &) = delete; + + void begin(); + void end(); + // duration of the most recent finished measurement, in nanoseconds + std::optional result(); + + private: + void collect(); + + static constexpr size_t SLOTS = 4; + // start and end timestamp per slot, created on first use + std::array m_queries{}; + std::array m_pending{}; + // slot written by the next begin(), also the oldest one still pending + size_t m_slot = 0; + bool m_active = false; + std::optional m_last; +}; } diff --git a/gl/shader.cpp b/gl/shader.cpp index c40c79dc..ca073433 100644 --- a/gl/shader.cpp +++ b/gl/shader.cpp @@ -336,3 +336,9 @@ void gl::program::bind(GLuint i) { glUseProgram(i); } + +void gl::program::dispatch(GLuint groups_x, GLuint groups_y, GLuint groups_z) +{ + bind(); + glDispatchCompute(groups_x, groups_y, groups_z); +} diff --git a/gl/shader.h b/gl/shader.h index f1735a0e..c92f7b28 100644 --- a/gl/shader.h +++ b/gl/shader.h @@ -94,6 +94,9 @@ namespace gl void attach(const shader &); void link(); + // for a program linked from a compute shader only: binds it and runs the given number of work groups + void dispatch(GLuint groups_x, GLuint groups_y = 1, GLuint groups_z = 1); + std::unordered_map texture_conf; std::unordered_map param_conf; diff --git a/gl/ssbo.cpp b/gl/ssbo.cpp new file mode 100644 index 00000000..387fddc6 --- /dev/null +++ b/gl/ssbo.cpp @@ -0,0 +1,49 @@ +#include "stdafx.h" +#include "ssbo.h" + +gl::ssbo::ssbo(GLsizeiptr size, GLuint index) : m_index(index) +{ + create(size); +} + +gl::ssbo::~ssbo() +{ + glDeleteBuffers(1, *this); +} + +void gl::ssbo::create(GLsizeiptr size) +{ + glCreateBuffers(1, *this); + // written only through glNamedBufferSubData and by shaders + glNamedBufferStorage(*this, size, nullptr, GL_DYNAMIC_STORAGE_BIT); + m_size = size; +} + +void gl::ssbo::bind_base() const +{ + glBindBuffersBase(GL_SHADER_STORAGE_BUFFER, m_index, 1, *this); +} + +void gl::ssbo::resize(GLsizeiptr size) +{ + if (size == m_size) + return; + + glDeleteBuffers(1, *this); + create(size); +} + +void gl::ssbo::update(const void *data, GLintptr offset, GLsizeiptr size) +{ + glNamedBufferSubData(*this, offset, size, data); +} + +void gl::ssbo::download(void *data, GLintptr offset, GLsizeiptr size) const +{ + glGetNamedBufferSubData(*this, offset, size, data); +} + +void gl::memory_barrier(GLbitfield barriers) +{ + glMemoryBarrier(barriers); +} diff --git a/gl/ssbo.h b/gl/ssbo.h new file mode 100644 index 00000000..f8e5f964 --- /dev/null +++ b/gl/ssbo.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include "object.h" + +namespace gl +{ + // shader storage buffer (std430 data, compute shader input and output), on 4.6 core direct state access. + // the storage is immutable: resize() to a different size replaces the buffer object, drops its contents and + // leaves it unbound (call bind_base() again). + // binding goes through glBindBuffersBase, which leaves the general GL_SHADER_STORAGE_BUFFER binding point + // alone, so the binding tracker of gl::buffer stays valid + class ssbo : public object + { + public: + // size in bytes, > 0. index is the binding point used by the shaders: layout(std430, binding = index) + ssbo(GLsizeiptr size, GLuint index); + ~ssbo(); + + void bind_base() const; + void resize(GLsizeiptr size); + GLsizeiptr size() const + { + return m_size; + } + + void update(const void *data, GLintptr offset, GLsizeiptr size); + template void update(const T &data, GLintptr offset = 0) + { + static_assert(std::is_trivially_copyable_v, "ssbo data must be trivially copyable"); + update(&data, offset, sizeof(data)); + } + // synchronous read back, waits for the gpu; for debug data, not for per-frame use + void download(void *data, GLintptr offset, GLsizeiptr size) const; + + private: + void create(GLsizeiptr size); + + GLuint m_index; + GLsizeiptr m_size = 0; + }; + + // makes writes done by shaders (compute dispatches, storage buffer stores) visible to the operations named + // by barriers, e.g. GL_SHADER_STORAGE_BARRIER_BIT before a draw that reads what a dispatch wrote + void memory_barrier(GLbitfield barriers); +} // namespace gl diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index dceb9e28..d73f33af 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -651,6 +651,8 @@ void opengl46_renderer::SwapBuffers() m_debugtimestext += " swap: " + to_string( Timer::subsystem.gfx_swap.average(), 2 ) + " ms\n"; if (m_gllasttime) m_debugtimestext += "gpu frame total: " + to_string((double)(m_gllasttime / 1000ULL) / 1000.0, 3) + " ms\n"; + if (auto const clusterstime = m_clusterstimer.result()) + m_debugtimestext += " clusters: " + to_string(static_cast(*clusterstime) / 1000000.0, 3) + " ms\n"; m_debugtimestext += "uilayer: " + to_string( Timer::subsystem.gfx_gui.average(), 2 ) + " ms\n"; if( DebugModeFlag ) m_debugtimestext += m_textures.info(); diff --git a/rendering/opengl46/opengl46renderer.h b/rendering/opengl46/opengl46renderer.h index 97f0ab0b..f8c37f39 100644 --- a/rendering/opengl46/opengl46renderer.h +++ b/rendering/opengl46/opengl46renderer.h @@ -432,6 +432,8 @@ class opengl46_renderer : public gfx_renderer { std::optional m_timequery; GLuint64 m_gllasttime = 0; + // gpu time of the light cluster compute passes; the debug panel shows it once a pass measures it + gl::gpu_timer m_clusterstimer; double m_precipitationrotation;