Skip to content

Commit

Permalink
Merge pull request #6 from ZephyrEngine/unified-geometry-buffers
Browse files Browse the repository at this point in the history
Unified Geometry Buffers
  • Loading branch information
fleroviux authored May 9, 2024
2 parents d9e2ceb + 2df5412 commit e2abd3b
Show file tree
Hide file tree
Showing 10 changed files with 422 additions and 86 deletions.
3 changes: 3 additions & 0 deletions app/next/src/main_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ namespace zephyr {
gltf_scene_1->GetTransform().GetRotation().SetFromEuler(1.5f, 0.0f, 0.0f);
m_scene_root->Add(std::move(gltf_scene_1));

m_scene_root->Add(gltf_loader.Parse("models/triangleWithoutIndices/TriangleWithoutIndices.gltf"));
//m_scene_root->Add(gltf_loader.Parse("models/triangle/Triangle.gltf"));

m_behemoth_scene = gltf_loader.Parse("models/Behemoth/scene.gltf");
m_behemoth_scene->GetTransform().GetPosition() = Vector3{-1.0f, 0.0f, -5.0f};
m_behemoth_scene->GetTransform().GetRotation().SetFromEuler(-M_PI * 0.5, M_PI, 0.0f);
Expand Down
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
54 changes: 54 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,54 @@

#pragma once

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

namespace zephyr {

/**
* TODO:
* - improve grow strategy (i.e. configurable grow factor)
* - implement shrinking
*/

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
29 changes: 23 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,13 @@ 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>();
}

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 +38,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 @@ -58,10 +62,23 @@ namespace zephyr {
glBindBufferBase(GL_UNIFORM_BUFFER, 0, m_gl_ubo);
glNamedBufferSubData(m_gl_ubo, 0, sizeof(Matrix4), &view_projection);

glBindBuffer(GL_DRAW_INDIRECT_BUFFER, m_render_geometry_manager->GetDrawCommandBuffer());

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();

// TODO(fleroviux): avoid constant rebinding of VAO
auto render_geometry = dynamic_cast<OpenGLRenderGeometry*>(render_object.render_geometry);
auto indirect_buffer_offset = (const void*)(render_geometry->GetDrawCommandID() * sizeof(OpenGLDrawElementsIndirectCommand));
glBindVertexArray(m_render_geometry_manager->GetVAOFromLayout(render_geometry->GetLayout()));
if(render_geometry->GetNumberOfIndices() > 0) {
glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, indirect_buffer_offset);
} else {
glDrawArraysIndirect(GL_TRIANGLES, indirect_buffer_offset);
}
}

glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0);
}

void OpenGLRenderBackend::SwapBuffers() {
Expand Down
3 changes: 2 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,7 @@ 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<RenderBackend> CreateOpenGLRenderBackendForSDL2(SDL_Window* sdl2_window) {
Expand Down
Loading

0 comments on commit e2abd3b

Please sign in to comment.