Skip to content

Commit

Permalink
Zephyr: Renderer: initial unified geometry buffer implementation in O…
Browse files Browse the repository at this point in the history
…penGL
  • Loading branch information
fleroviux committed May 6, 2024
1 parent d9e2ceb commit e01a68e
Show file tree
Hide file tree
Showing 8 changed files with 385 additions and 81 deletions.
4 changes: 3 additions & 1 deletion zephyr/renderer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# TODO: remove dependency on SDL2 if possible

set(SOURCES
src/backend/opengl/dynamic_gpu_array.cpp
src/backend/opengl/render_backend.cpp
src/backend/opengl/render_geometry.cpp
src/backend/vulkan/render_backend.cpp
Expand All @@ -12,8 +13,9 @@ set(SOURCES
)

set(HEADERS
src/backend/opengl/render_geometry.hpp
src/backend/opengl/dynamic_gpu_array.hpp
src/backend/opengl/render_backend.hpp
src/backend/opengl/render_geometry.hpp
src/backend/vulkan/shader/triangle.frag.h
src/backend/vulkan/shader/triangle.vert.h
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ namespace zephyr {
virtual RenderGeometry* CreateRenderGeometry(RenderGeometryLayout layout, size_t number_of_vertices, size_t number_of_indices) = 0;
virtual void UpdateRenderGeometryIndices(RenderGeometry* render_geometry, std::span<const u8> data) = 0;
virtual void UpdateRenderGeometryVertices(RenderGeometry* render_geometry, std::span<const u8> data) = 0;
virtual void DestroyRenderGeometry(RenderGeometry* geometry) = 0;
virtual void DestroyRenderGeometry(RenderGeometry* render_geometry) = 0;

/// Just a quick thing for testing the rendering.
virtual void Render(const Matrix4& view_projection, std::span<const RenderObject> render_objects) = 0;
Expand Down
170 changes: 170 additions & 0 deletions zephyr/renderer/src/backend/opengl/dynamic_gpu_array.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@

#include <zephyr/float.hpp>
#include <zephyr/panic.hpp>
#include <algorithm>

#include <fmt/format.h>

#include "dynamic_gpu_array.hpp"

namespace zephyr {

OpenGLDynamicGPUArray::OpenGLDynamicGPUArray(size_t byte_stride) : m_byte_stride{byte_stride} {
ResizeBuffer(k_capacity_increment);

// const auto PrintAlloc = [](const BufferRange& range) {
// fmt::print("allocated: base_element={} \tnumber_of_elements={}\n", range.base_element, range.number_of_elements);
// };
//
// const auto range_a = AllocateRange(512);
// PrintAlloc(range_a);
// PrintFreeRanges();
//
// const auto range_b = AllocateRange(16385);
// PrintAlloc(range_b);
// PrintFreeRanges();
//
// const auto range_c = AllocateRange(100000);
// PrintAlloc(range_c);
// PrintFreeRanges();
//
// ReleaseRange(range_b);
// PrintFreeRanges();
//
// ReleaseRange(range_c);
// PrintFreeRanges();
//
// ReleaseRange(range_a);
// PrintFreeRanges();
}

OpenGLDynamicGPUArray::~OpenGLDynamicGPUArray() {
glDeleteBuffers(1u, &m_gpu_buffer);
}

OpenGLDynamicGPUArray::BufferRange OpenGLDynamicGPUArray::AllocateRange(size_t number_of_elements) {
const auto end = m_free_buffer_ranges.end();

for(auto it = m_free_buffer_ranges.begin(); it != end; ++it) {
BufferRange& free_buffer_range = *it;

if(free_buffer_range.number_of_elements >= number_of_elements) {
const BufferRange allocated_range{free_buffer_range.base_element, number_of_elements};

if(free_buffer_range.number_of_elements == number_of_elements) {
m_free_buffer_ranges.erase(it);
} else {
free_buffer_range.base_element += number_of_elements;
free_buffer_range.number_of_elements -= number_of_elements;
}

return allocated_range;
}
}

const size_t required_capacity = m_current_capacity + number_of_elements - m_free_buffer_ranges.back().number_of_elements;
const size_t rounded_capacity = (required_capacity + k_capacity_increment - 1u) / k_capacity_increment * k_capacity_increment;
ResizeBuffer(rounded_capacity);

BufferRange& free_buffer_range = m_free_buffer_ranges.back();
const BufferRange allocated_range{free_buffer_range.base_element, number_of_elements};

if(free_buffer_range.number_of_elements == number_of_elements) {
m_free_buffer_ranges.pop_back();
} else {
free_buffer_range.base_element += number_of_elements;
free_buffer_range.number_of_elements -= number_of_elements;
}

return allocated_range;
}

void OpenGLDynamicGPUArray::ReleaseRange(BufferRange free_buffer_range) {
fmt::print("release: base={} \tnumber_of_elements={}\n", free_buffer_range.base_element, free_buffer_range.number_of_elements);

auto neighbour_r_it = std::ranges::find_if(m_free_buffer_ranges, [&](const BufferRange& buffer_range) {
return buffer_range.base_element > free_buffer_range.base_element; });

std::vector<BufferRange>::iterator free_range_it;

if(neighbour_r_it != m_free_buffer_ranges.end()) {
if(neighbour_r_it->base_element == free_buffer_range.base_element + free_buffer_range.number_of_elements) {
neighbour_r_it->base_element = free_buffer_range.base_element;
neighbour_r_it->number_of_elements += free_buffer_range.number_of_elements;
free_range_it = neighbour_r_it;
} else {
free_range_it = m_free_buffer_ranges.insert(neighbour_r_it, free_buffer_range);
}
} else {
m_free_buffer_ranges.push_back(free_buffer_range);
free_range_it = m_free_buffer_ranges.end();
}

if(free_range_it != m_free_buffer_ranges.begin()) {
const auto neighbour_l_it = std::prev(free_range_it);

if(free_range_it->base_element == neighbour_l_it->base_element + neighbour_l_it->number_of_elements) {
neighbour_l_it->number_of_elements += free_range_it->number_of_elements;
m_free_buffer_ranges.erase(free_range_it);
}
}

// TODO(fleroviux): shrink array if possible
}

void OpenGLDynamicGPUArray::Write(std::span<const u8> data, size_t base_element, size_t byte_offset) {
const size_t buffer_write_start = base_element * m_byte_stride + byte_offset;
const size_t buffer_write_end = buffer_write_start + data.size();
const size_t buffer_byte_size = m_current_capacity * m_byte_stride;

if(buffer_write_end < buffer_write_start || buffer_write_end > buffer_byte_size) {
ZEPHYR_PANIC("Out-of-range dynamic GPU array write");
}

glNamedBufferSubData(m_gpu_buffer, (GLintptr)buffer_write_start, (GLsizeiptr)data.size(), data.data());
}

void OpenGLDynamicGPUArray::PrintFreeRanges() {
fmt::print("----------------------------------------\n");

for(const BufferRange& free_buffer_range : m_free_buffer_ranges) {
fmt::print("free: \tbase={} \tnumber_of_elements={}\n", free_buffer_range.base_element, free_buffer_range.number_of_elements);
}

fmt::print("----------------------------------------\n");
}

void OpenGLDynamicGPUArray::ResizeBuffer(size_t new_capacity) {
if(new_capacity == m_current_capacity) {
return;
}

// Create a new VBO that fits the new capacity
GLuint new_gpu_buffer;
glCreateBuffers(1u, &new_gpu_buffer);
glNamedBufferData(new_gpu_buffer, (GLsizeiptr)(new_capacity * m_byte_stride), nullptr, GL_DYNAMIC_DRAW);

if(m_current_capacity != 0u) {
// Copy the contents of the old buffer into the new buffer and delete the old buffer.
const size_t copy_size = std::min(new_capacity, m_current_capacity) * m_byte_stride;
glCopyNamedBufferSubData(m_gpu_buffer, new_gpu_buffer, 0u, 0u, (GLsizeiptr)copy_size);
glDeleteBuffers(1u, &m_gpu_buffer);
}

if(new_capacity > m_current_capacity) {
const size_t capacity_increment = new_capacity - m_current_capacity;

if(m_free_buffer_ranges.empty()) {
m_free_buffer_ranges.emplace_back(m_current_capacity, capacity_increment);
} else {
m_free_buffer_ranges.back().number_of_elements += capacity_increment;
}
}

m_gpu_buffer = new_gpu_buffer;
m_current_capacity = new_capacity;

// TODO(fleroviux): emit event for handling rebinding of the new buffer to i.e. a VAO?
}

} // namespace zephyr
48 changes: 48 additions & 0 deletions zephyr/renderer/src/backend/opengl/dynamic_gpu_array.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

#pragma once

#include <zephyr/renderer/backend/render_backend.hpp>
#include <GL/glew.h>
#include <GL/gl.h>
#include <span>
#include <vector>

namespace zephyr {

class OpenGLDynamicGPUArray {
public:
struct BufferRange {
BufferRange() = default;
BufferRange(size_t base_element, size_t number_of_elements) : base_element{base_element}, number_of_elements{number_of_elements} {}
size_t base_element;
size_t number_of_elements;
};

explicit OpenGLDynamicGPUArray(size_t byte_stride);
~OpenGLDynamicGPUArray();

[[nodiscard]] GLuint GetBufferHandle() {
return m_gpu_buffer;
}

[[nodiscard]] size_t GetByteStride() const {
return m_byte_stride;
}

BufferRange AllocateRange(size_t number_of_elements);
void ReleaseRange(BufferRange buffer_range);
void Write(std::span<const u8> data, size_t base_element, size_t byte_offset = 0u);

private:
static constexpr size_t k_capacity_increment = 16384u;

void ResizeBuffer(size_t new_capacity);
void PrintFreeRanges();

size_t m_byte_stride{};
GLuint m_gpu_buffer{};
size_t m_current_capacity{0u};
std::vector<BufferRange> m_free_buffer_ranges{};
};

} // namespace zephyr
30 changes: 24 additions & 6 deletions zephyr/renderer/src/backend/opengl/render_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,15 @@ namespace zephyr {
glNamedBufferData(m_gl_ubo, sizeof(Matrix4) * 2, nullptr, GL_DYNAMIC_DRAW);

glEnable(GL_DEPTH_TEST);

m_render_geometry_manager = std::make_unique<OpenGLRenderGeometryManager>();

m_test_dyn_gpu_array = std::make_unique<OpenGLDynamicGPUArray>(sizeof(f32) * 3u);
}

void OpenGLRenderBackend::DestroyContext() {
m_render_geometry_manager.reset();

glDeleteBuffers(1u, &m_gl_ubo);
glDeleteVertexArrays(1u, &m_gl_vao);
glDeleteProgram(m_gl_shader_program);
Expand All @@ -34,19 +40,19 @@ namespace zephyr {
}

RenderGeometry* OpenGLRenderBackend::CreateRenderGeometry(RenderGeometryLayout layout, size_t number_of_vertices, size_t number_of_indices) {
return OpenGLRenderGeometry::Build(layout, number_of_vertices, number_of_indices);
return m_render_geometry_manager->CreateRenderGeometry(layout, number_of_vertices, number_of_indices);
}

void OpenGLRenderBackend::UpdateRenderGeometryIndices(RenderGeometry* render_geometry, std::span<const u8> data) {
dynamic_cast<OpenGLRenderGeometry*>(render_geometry)->UpdateIndices(data);
m_render_geometry_manager->UpdateRenderGeometryIndices(render_geometry, data);
}

void OpenGLRenderBackend::UpdateRenderGeometryVertices(RenderGeometry* render_geometry, std::span<const u8> data) {
dynamic_cast<OpenGLRenderGeometry*>(render_geometry)->UpdateVertices(data);
m_render_geometry_manager->UpdateRenderGeometryVertices(render_geometry, data);
}

void OpenGLRenderBackend::DestroyRenderGeometry(RenderGeometry* geometry) {
delete geometry;
void OpenGLRenderBackend::DestroyRenderGeometry(RenderGeometry* render_geometry) {
m_render_geometry_manager->DestroyRenderGeometry(render_geometry);
}

void OpenGLRenderBackend::Render(const Matrix4& view_projection, std::span<const RenderObject> render_objects) {
Expand All @@ -60,7 +66,19 @@ namespace zephyr {

for(const RenderObject& render_object : render_objects) {
glNamedBufferSubData(m_gl_ubo, sizeof(Matrix4), sizeof(Matrix4), &render_object.local_to_world);
dynamic_cast<OpenGLRenderGeometry*>(render_object.render_geometry)->Draw();
//dynamic_cast<OpenGLRenderGeometry*>(render_object.render_geometry)->Draw();

// TODO(fleroviux): avoid constant rebinding of VAO
auto render_geometry = dynamic_cast<OpenGLRenderGeometry*>(render_object.render_geometry);
glBindVertexArray(m_render_geometry_manager->GetVAOFromLayout(render_geometry->GetLayout()));
if(render_geometry->GetNumberOfIndices() > 0) {
const auto command = render_geometry->GetDrawElementsIndirectCommand();
//glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, &command);
glDrawElementsInstancedBaseVertexBaseInstance(GL_TRIANGLES, command.count, GL_UNSIGNED_INT, (void*)(sizeof(u32) * command.first_index), 1u, command.base_vertex, 0u);
} else {
// TODO
ZEPHYR_PANIC("unimplemented");
}
}
}

Expand Down
5 changes: 4 additions & 1 deletion zephyr/renderer/src/backend/opengl/render_backend.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace zephyr {
RenderGeometry* CreateRenderGeometry(RenderGeometryLayout layout, size_t number_of_vertices, size_t number_of_indices) override;
void UpdateRenderGeometryIndices(RenderGeometry* render_geometry, std::span<const u8> data) override;
void UpdateRenderGeometryVertices(RenderGeometry* render_geometry, std::span<const u8> data) override;
void DestroyRenderGeometry(RenderGeometry* geometry) override;
void DestroyRenderGeometry(RenderGeometry* render_geometry) override;

void Render(const Matrix4& view_projection, std::span<const RenderObject> render_objects) override;

Expand All @@ -39,6 +39,9 @@ namespace zephyr {
GLuint m_gl_shader_program{};
GLuint m_gl_vao{};
GLuint m_gl_ubo{};
std::unique_ptr<OpenGLRenderGeometryManager> m_render_geometry_manager{};

std::unique_ptr<OpenGLDynamicGPUArray> m_test_dyn_gpu_array{};
};

std::unique_ptr<RenderBackend> CreateOpenGLRenderBackendForSDL2(SDL_Window* sdl2_window) {
Expand Down
Loading

0 comments on commit e01a68e

Please sign in to comment.