diff --git a/code/Demos/CMakeLists.txt b/code/Demos/CMakeLists.txt index 84cf44e..b1e9b87 100644 --- a/code/Demos/CMakeLists.txt +++ b/code/Demos/CMakeLists.txt @@ -15,10 +15,10 @@ if(DEFINED EMSCRIPTEN) # Asset embedding. file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/assets DESTINATION ${CMAKE_BINARY_DIR}) - file(GLOB_RECURSE ASSET_FILES "${CMAKE_BINARY_DIR}/assets/*") + file(GLOB_RECURSE ASSET_FILES "${CMAKE_CURRENT_SOURCE_DIR}/assets/*") set(PRELOAD_FILES "") foreach(FILE ${ASSET_FILES}) - file(RELATIVE_PATH REL_FILE ${CMAKE_BINARY_DIR} ${FILE}) + file(RELATIVE_PATH REL_FILE ${CMAKE_CURRENT_SOURCE_DIR} ${FILE}) set(PRELOAD_FILES "${PRELOAD_FILES} --preload-file ${REL_FILE}") endforeach() message(STATUS "PRELOAD FILES: \"${PRELOAD_FILES}\"") diff --git a/code/Demos/src/Main.cpp b/code/Demos/src/Main.cpp index 06020fd..274c218 100644 --- a/code/Demos/src/Main.cpp +++ b/code/Demos/src/Main.cpp @@ -83,7 +83,7 @@ struct SpinningTeapotLogic { .on_value([&](auto& id) { data.ib_id = id; }); - renderer.add_vertex_array(Model::StaticVertexLayout {}, data.vb_id) + renderer.add_vertex_array(Model::Vertex::VBL {}, data.vb_id) .on_error(Utily::ErrorHandler::print_then_quit) .on_value([&](auto& id) { data.va_id = id; diff --git a/code/Engine/include/AppRenderer.hpp b/code/Engine/include/AppRenderer.hpp index 2f02e5d..6cd36b9 100644 --- a/code/Engine/include/AppRenderer.hpp +++ b/code/Engine/include/AppRenderer.hpp @@ -67,7 +67,7 @@ class AppRenderer offset += element.count * element.type_size; } return 0; - } + } void stop() noexcept; }; \ No newline at end of file diff --git a/code/Engine/include/Model/Types.hpp b/code/Engine/include/Model/Types.hpp index 4efd52b..aa612de 100644 --- a/code/Engine/include/Model/Types.hpp +++ b/code/Engine/include/Model/Types.hpp @@ -1,11 +1,11 @@ #pragma once +#include "Renderer/VertexBufferLayout.hpp" #include -#include #include #include #include -#include "Renderer/VertexBufferLayout.hpp" +#include namespace Model { using Vec2 = glm::vec2; @@ -22,12 +22,30 @@ namespace Model { stream << "v(" << vertex.position.x << ',' << vertex.position.y << ',' << vertex.position.z << ") \t" << "n(" << vertex.normal.x << ',' << vertex.normal.y << ',' << vertex.normal.z << ") \t" << "uv(" << vertex.uv_coord.x << ',' << vertex.uv_coord.y << ")"; - return stream; } + using VBL = Renderer::VertexBufferLayout; }; - using StaticVertexLayout = Renderer::VertexBufferLayout; + struct BatchingVertex { + Vec3 position; + Vec3 normal; + Vec2 uv_coord; + uint32_t texture_unit_index; + uint32_t model_transform_index; + + friend auto operator<<(std::ostream& stream, const BatchingVertex& vertex) -> std::ostream& { + stream << "v(" << vertex.position.x << ',' << vertex.position.y << ',' << vertex.position.z << ") \t" + << "n(" << vertex.normal.x << ',' << vertex.normal.y << ',' << vertex.normal.z << ") \t" + << "uv(" << vertex.uv_coord.x << ',' << vertex.uv_coord.y << ") \t" + << "tui(" << vertex.texture_unit_index << ") \t" + << "mti(" << vertex.model_transform_index << ")"; + + return stream; + } + + using VBL = Renderer::VertexBufferLayout; + }; using Index = uint32_t; diff --git a/code/Engine/include/Renderer/BatchDrawer.hpp b/code/Engine/include/Renderer/BatchDrawer.hpp new file mode 100644 index 0000000..2787d1c --- /dev/null +++ b/code/Engine/include/Renderer/BatchDrawer.hpp @@ -0,0 +1,102 @@ +#pragma once + +#include "Components/Components.hpp" +#include "Model/Static.hpp" +#include "Renderer/IndexBuffer.hpp" +#include "Renderer/Shader.hpp" +#include "Renderer/Texture.hpp" +#include "Renderer/VertexArray.hpp" +#include "Renderer/VertexBuffer.hpp" + +#include +#include +#include +#include +#include + +namespace Renderer { + class BatchDrawer + { + public: + static auto to_3_digit(uint32_t num) noexcept -> std::array { + assert(num < 1000); + // via stack overflow: https://stackoverflow.com/a/1861020 + std::array result = { '0', '0', '0' }; + for (int i = result.size() - 1; i >= 0; i--) { + result[i] = static_cast(num % 10) + '0'; + num /= 10; + } + // could use std::from_chars but that requires a rotate so no thanks. + return result; + } + using TexturedStaticModel = std::tuple; +#if 1 + template + void batch(VertexBuffer& vb, IndexBuffer& ib, VertexArray& va, Shader& shader, std::array& textured_models) { + static std::vector vertices_buffer; + static std::vector indices_buffer; + + // Predetermine size for only one alloc. + const size_t total_vertex_count = std::reduce( + textured_models.begin(), + textured_models.end(), + size_t(0), + [](const size_t& agg, const auto& tm) { return agg + std::get<0>(tm).vertices.size(); }); + const size_t total_index_count = std::reduce( + textured_models.begin(), + textured_models.end(), + size_t(0), + [](const size_t& agg, const auto& tm) { return agg + std::get<0>(tm).indices.size(); }); + vertices_buffer.resize(total_vertex_count); + indices_buffer.resize(total_index_count); + + shader.bind(); + va.bind(); + vb.bind(); + ib.bind(); + + // Move replacement info to compile time. + constexpr static std::string_view mm_uniform_string = "u_model_transfrom[$$$]"; + constexpr static size_t mm_unifrom_string_size = mm_uniform_string.size(); + constexpr static std::ptrdiff_t mm_uniform_replacement_offset = std::distance(mm_uniform_string.begin(), std::ranges::find(mm_uniform_string, '$')); + + // Copy and ensure null ended string. + std::array mm_uniform = { mm_uniform_string.begin(), mm_uniform_string.end() }; + mm_uniform.back() = '\0'; + + uint32_t i = 0; + auto vert_iter = vertices_buffer.begin(); + auto indi_iter = vertices_buffer.begin(); + for (auto& [model, transform, texture] : textured_models) { + const auto tex_unit = texture.bind(true).on_error(Utily::ErrorHandler::print_then_quit).value(); + const auto index_offset = static_cast(std::distance(vertices_buffer.begin(), vert_iter)); + + // Pass model transform as uniform. + auto i_chars = BatchDrawer::to_3_digit(i); + std::ranges::copy(i_chars, mm_uniform.data() + mm_uniform_replacement_offset); + shader.set_uniform(std::string_view { mm_uniform.data() }, transform.calc_transform_mat()); + + // Account for index offset + auto add_index_offset = [&](Model::Index index) { return index + index_offset; }; + auto indi_iter = std::ranges::copy(model.indices | add_index_offset, indi_iter); + + // Add texture index and model transform index + auto add_tex_unit = [&](const Model::Vertex& v) { return Model::BatchingVertex { v.position, v.normal, v.uv_coord, tex_unit, i }; }; + auto vert_iter = std::ranges::copy(model.vertices | add_tex_unit, vert_iter); + + ++i; + } + + // upload and draw. + ib.load_indices(indices_buffer); + vb.load_vertices(vertices_buffer); + glDrawElements(GL_TRIANGLES, ib.get_count(), GL_UNSIGNED_INT, (void*)0); + + // unlock the locked-bound textures. + for (auto& [model, texture] : textured_models) { + auto tex_unit = texture.bind(false).on_error(Utily::ErrorHandler::print_then_quit).value(); + } + } +#endif + }; +} \ No newline at end of file diff --git a/code/Engine/include/Renderer/IndexBuffer.hpp b/code/Engine/include/Renderer/IndexBuffer.hpp index d0bc069..f14f379 100644 --- a/code/Engine/include/Renderer/IndexBuffer.hpp +++ b/code/Engine/include/Renderer/IndexBuffer.hpp @@ -26,7 +26,12 @@ namespace Renderer { void load_indices(const Range& indices) noexcept { this->bind(); size_t size_in_bytes = indices.size() * sizeof(uint32_t); +#if defined(CONFIG_TARGET_NATIVE) glBufferData(GL_ELEMENT_ARRAY_BUFFER, size_in_bytes, &(*indices.begin()), GL_DYNAMIC_DRAW); +#elif defined(CONFIG_TARGET_WEB) + // for some reason, GL_DYNAMIC_DRAW has a very small buffer capacity. + glBufferData(GL_ELEMENT_ARRAY_BUFFER, size_in_bytes, &(*indices.begin()), GL_STATIC_DRAW); +#endif _count = indices.size(); } diff --git a/code/Engine/include/Renderer/VertexBuffer.hpp b/code/Engine/include/Renderer/VertexBuffer.hpp index cef09c9..75e9553 100644 --- a/code/Engine/include/Renderer/VertexBuffer.hpp +++ b/code/Engine/include/Renderer/VertexBuffer.hpp @@ -23,7 +23,7 @@ namespace Renderer { void bind() noexcept; void unbind() noexcept; - ~VertexBuffer(); + ~VertexBuffer() noexcept; template requires std::ranges::range diff --git a/code/Engine/src/Renderer/Framebuffer.cpp b/code/Engine/src/Renderer/Framebuffer.cpp index 8ef959e..3d3a9fb 100644 --- a/code/Engine/src/Renderer/Framebuffer.cpp +++ b/code/Engine/src/Renderer/Framebuffer.cpp @@ -40,7 +40,9 @@ namespace Renderer { FrameBuffer::FrameBuffer(FrameBuffer&& other) noexcept : _id(std::exchange(other._id, std::nullopt)) - , _colour_attachment_index(std::exchange(other._id, std::nullopt)) { } + , _colour_attachment_index(std::exchange(other._id, std::nullopt)) { + last_bound = nullptr; + } auto FrameBuffer::init(uint32_t width, uint32_t height) noexcept -> Utily::Result { if (_id) { diff --git a/code/Engine/src/Renderer/IndexBuffer.cpp b/code/Engine/src/Renderer/IndexBuffer.cpp index 821bf59..803e08d 100644 --- a/code/Engine/src/Renderer/IndexBuffer.cpp +++ b/code/Engine/src/Renderer/IndexBuffer.cpp @@ -4,10 +4,13 @@ namespace Renderer { constexpr static uint32_t INVALID_BUFFER_ID = 0; + static IndexBuffer* last_bound = nullptr; IndexBuffer::IndexBuffer(IndexBuffer&& other) noexcept : _id(std::exchange(other._id, std::nullopt)) - , _count(std::exchange(other._count, 0)) { } + , _count(std::exchange(other._count, 0)) { + last_bound = nullptr; + } auto IndexBuffer::init() noexcept -> Utily::Result { if (_id.has_value()) { @@ -22,21 +25,18 @@ namespace Renderer { } return {}; } - - static IndexBuffer* last_bound = nullptr; - + void IndexBuffer::stop() noexcept { if (_id.value_or(INVALID_BUFFER_ID) != INVALID_BUFFER_ID) { glDeleteBuffers(1, &_id.value()); } _id = std::nullopt; _count = 0; - if(last_bound == this) { + if (last_bound == this) { last_bound = nullptr; } } - void IndexBuffer::bind() noexcept { if constexpr (Config::DEBUG_LEVEL != Config::DebugInfo::none) { if (_id.value_or(INVALID_BUFFER_ID) == INVALID_BUFFER_ID) { @@ -44,7 +44,7 @@ namespace Renderer { assert(false); } } - if(last_bound != this) { + if (last_bound != this) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _id.value_or(INVALID_BUFFER_ID)); last_bound = this; } diff --git a/code/Engine/src/Renderer/Shader.cpp b/code/Engine/src/Renderer/Shader.cpp index 52b7a02..c37e0da 100644 --- a/code/Engine/src/Renderer/Shader.cpp +++ b/code/Engine/src/Renderer/Shader.cpp @@ -7,9 +7,14 @@ using namespace std::literals; namespace Renderer { + + static Shader* last_bound = nullptr; + Shader::Shader(Shader&& other) : _program_id(std::exchange(other._program_id, std::nullopt)) - , _cached_uniforms(std::move(other._cached_uniforms)) { } + , _cached_uniforms(std::move(other._cached_uniforms)) { + last_bound = nullptr; + } auto Shader::compile_shader(Type type, const std::string_view& source) -> Utily::Result { constexpr static auto shader_verison = @@ -103,7 +108,6 @@ namespace Renderer { } // cache it so we dont query the GPU over already bound stuff. - static Shader* last_bound = nullptr; void Shader::bind() noexcept { if constexpr (Config::DEBUG_LEVEL != Config::DebugInfo::none) { diff --git a/code/Engine/src/Renderer/Texture.cpp b/code/Engine/src/Renderer/Texture.cpp index 57c321b..d108c41 100644 --- a/code/Engine/src/Renderer/Texture.cpp +++ b/code/Engine/src/Renderer/Texture.cpp @@ -48,6 +48,7 @@ namespace Renderer { } return {}; } + auto Texture::upload_image( Media::Image& image, Filter filter, diff --git a/code/Engine/src/Renderer/VertexArray.cpp b/code/Engine/src/Renderer/VertexArray.cpp index 9b6c5f2..7183871 100644 --- a/code/Engine/src/Renderer/VertexArray.cpp +++ b/code/Engine/src/Renderer/VertexArray.cpp @@ -4,9 +4,12 @@ namespace Renderer { constexpr static uint32_t INVALID_ARRAY_OBJECT_ID = 0; + static VertexArray* last_bound = nullptr; VertexArray::VertexArray(VertexArray&& other) - : _id(std::exchange(other._id, std::nullopt)) { } + : _id(std::exchange(other._id, std::nullopt)) { + last_bound = nullptr; + } auto VertexArray::init() noexcept -> Utily::Result { if (_id) { @@ -21,14 +24,12 @@ namespace Renderer { return {}; } - static VertexArray* last_bound = nullptr; - void VertexArray::stop() noexcept { if (_id.value_or(INVALID_ARRAY_OBJECT_ID) == INVALID_ARRAY_OBJECT_ID) { glDeleteVertexArrays(1, &_id.value()); } _id = std::nullopt; - if(last_bound == this) { + if (last_bound == this) { last_bound = nullptr; } } diff --git a/code/Engine/src/Renderer/VertexBuffer.cpp b/code/Engine/src/Renderer/VertexBuffer.cpp index 7a3dbe2..549bb6d 100644 --- a/code/Engine/src/Renderer/VertexBuffer.cpp +++ b/code/Engine/src/Renderer/VertexBuffer.cpp @@ -4,9 +4,12 @@ namespace Renderer { constexpr static uint32_t INVALID_BUFFER_ID = 0; + static VertexBuffer* last_bound = nullptr; VertexBuffer::VertexBuffer(VertexBuffer&& other) noexcept - : _id(std::exchange(other._id, std::nullopt)) { } + : _id(std::exchange(other._id, std::nullopt)) { + last_bound = nullptr; + } auto VertexBuffer::init() noexcept -> Utily::Result { if (_id) { @@ -21,16 +24,13 @@ namespace Renderer { return {}; } - static VertexBuffer* last_bound = nullptr; - - void VertexBuffer::stop() noexcept { if (_id.value_or(INVALID_BUFFER_ID) != INVALID_BUFFER_ID) { glDeleteBuffers(1, &_id.value()); } _id = std::nullopt; - - if(last_bound == this) { + + if (last_bound == this) { last_bound = this; } } @@ -63,7 +63,7 @@ namespace Renderer { } } - VertexBuffer::~VertexBuffer() { + VertexBuffer::~VertexBuffer() noexcept { stop(); } } \ No newline at end of file diff --git a/code/Test/CMakeLists.txt b/code/Test/CMakeLists.txt index 4e69048..9cfa9f1 100644 --- a/code/Test/CMakeLists.txt +++ b/code/Test/CMakeLists.txt @@ -13,11 +13,11 @@ if(DEFINED EMSCRIPTEN) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXPORTED_FUNCTIONS='_main' --closure 1") # Asset embedding. - file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/assets DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) - file(GLOB_RECURSE ASSET_FILES "${CMAKE_CURRENT_BINARY_DIR}/assets/*") + file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/assets DESTINATION ${CMAKE_BINARY_DIR}) + file(GLOB_RECURSE ASSET_FILES "${CMAKE_CURRENT_SOURCE_DIR}/assets/*") set(PRELOAD_FILES "") foreach(FILE ${ASSET_FILES}) - file(RELATIVE_PATH REL_FILE ${CMAKE_CURRENT_BINARY_DIR} ${FILE}) + file(RELATIVE_PATH REL_FILE ${CMAKE_CURRENT_SOURCE_DIR} ${FILE}) set(PRELOAD_FILES "${PRELOAD_FILES} --preload-file ${REL_FILE}") endforeach() message(STATUS "PRELOAD FILES: \"${PRELOAD_FILES}\"") diff --git a/code/Test/assets/text.txt b/code/Test/assets/text.txt index e69de29..95d09f2 100644 --- a/code/Test/assets/text.txt +++ b/code/Test/assets/text.txt @@ -0,0 +1 @@ +hello world \ No newline at end of file diff --git a/code/Test/include/Integration/BasicApps.hpp b/code/Test/include/Integration/BasicApps.hpp index 455c50f..38b88a0 100644 --- a/code/Test/include/Integration/BasicApps.hpp +++ b/code/Test/include/Integration/BasicApps.hpp @@ -233,7 +233,7 @@ struct SpinningTeapotLogic { .on_value([&](auto& id) { data.ib_id = id; }); - renderer.add_vertex_array(Model::StaticVertexLayout {}, data.vb_id) + renderer.add_vertex_array(Model::Vertex::VBL {}, data.vb_id) .on_error(Utily::ErrorHandler::print_then_quit) .on_value([&](auto& id) { data.va_id = id; diff --git a/code/Test/include/Unit/UnitBenchDrawer.hpp b/code/Test/include/Unit/UnitBenchDrawer.hpp new file mode 100644 index 0000000..6dcc6ee --- /dev/null +++ b/code/Test/include/Unit/UnitBenchDrawer.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "Renderer/BatchDrawer.hpp" +#include "Utily/Utily.hpp" +#include + +#include +#include +#include + +using namespace std::literals; + +TEST(Unit, Renderer_batch_drawer_to_3_digits) { + + for (uint32_t i = 0; i <= 999; ++i) { + auto result = Renderer::BatchDrawer::to_3_digit(i); + auto expected = std::to_string(i); + while (expected.size() < 3) { + expected = '0' + expected; + } + + EXPECT_EQ(result[0], expected[0]); + EXPECT_EQ(result[1], expected[1]); + EXPECT_EQ(result[2], expected[2]); + } +} diff --git a/code/Test/src/Main.cpp b/code/Test/src/Main.cpp index 2e32f6a..c01acb6 100644 --- a/code/Test/src/Main.cpp +++ b/code/Test/src/Main.cpp @@ -1,6 +1,7 @@ #include #include "Unit/UnitModelStatic.hpp" +#include "Unit/UnitBenchDrawer.hpp" #include "Integration/AssetLoading.hpp" #include "Integration/BasicApps.hpp"