diff --git a/src/esengine/bindings/GeometryBindings.cpp b/src/esengine/bindings/GeometryBindings.cpp index 7453ad65..d6adea28 100644 --- a/src/esengine/bindings/GeometryBindings.cpp +++ b/src/esengine/bindings/GeometryBindings.cpp @@ -81,7 +81,7 @@ void geometry_init(u32 handle, uintptr_t verticesPtr, u32 vertexCount, attrs.emplace_back(static_cast(layoutData[i]), ATTR_NAMES[i]); } - geom->init(vertices, vertexCount, VertexLayout(std::move(attrs)), dynamic); + geom->init(ctx().require(), vertices, vertexCount, VertexLayout(std::move(attrs)), dynamic); } void geometry_setIndices16(u32 handle, uintptr_t indicesPtr, u32 indexCount) { diff --git a/src/esengine/renderer/Buffer.cpp b/src/esengine/renderer/Buffer.cpp index b012cac4..ac39368c 100644 --- a/src/esengine/renderer/Buffer.cpp +++ b/src/esengine/renderer/Buffer.cpp @@ -1,8 +1,8 @@ /** * @file Buffer.cpp - * @brief GPU buffer implementations for vertex and index data - * @details Implements VertexBuffer, IndexBuffer, and VertexArray classes - * for OpenGL/WebGL buffer management. + * @brief GPU buffer implementations (device-backed) + * @details VertexBuffer, IndexBuffer and VertexArray delegate every GPU + * operation to GfxDevice. This file contains no GL calls. * * @author ESEngine Team * @date 2025 @@ -12,8 +12,8 @@ */ #include "Buffer.hpp" +#include "GfxDevice.hpp" #include "../core/Log.hpp" -#include "OpenGLHeaders.hpp" namespace esengine { @@ -49,30 +49,46 @@ u32 shaderDataTypeComponentCount(ShaderDataType type) { } } +namespace { + +GfxDataType toGfxDataType(ShaderDataType type) { + switch (type) { + case ShaderDataType::Int: + case ShaderDataType::Int2: + case ShaderDataType::Int3: + case ShaderDataType::Int4: + return GfxDataType::Int; + case ShaderDataType::Bool: + case ShaderDataType::UByte4N: + return GfxDataType::UnsignedByte; + default: + return GfxDataType::Float; + } +} + +} // namespace + // ======================================== // VertexBuffer // ======================================== VertexBuffer::~VertexBuffer() { -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - if (bufferId_ != 0) { - glDeleteBuffers(1, &bufferId_); + if (bufferId_ != 0 && device_) { + device_->deleteBuffer(bufferId_); } -#endif } VertexBuffer::VertexBuffer(VertexBuffer&& other) noexcept - : bufferId_(other.bufferId_), layout_(std::move(other.layout_)) { + : device_(other.device_), bufferId_(other.bufferId_), layout_(std::move(other.layout_)) { other.bufferId_ = 0; } VertexBuffer& VertexBuffer::operator=(VertexBuffer&& other) noexcept { if (this != &other) { -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - if (bufferId_ != 0) { - glDeleteBuffers(1, &bufferId_); + if (bufferId_ != 0 && device_) { + device_->deleteBuffer(bufferId_); } -#endif + device_ = other.device_; bufferId_ = other.bufferId_; layout_ = std::move(other.layout_); other.bufferId_ = 0; @@ -80,62 +96,42 @@ VertexBuffer& VertexBuffer::operator=(VertexBuffer&& other) noexcept { return *this; } -Unique VertexBuffer::createRaw(const void* data, u32 sizeBytes) { +Unique VertexBuffer::createRaw(GfxDevice& device, const void* data, u32 sizeBytes) { auto buffer = makeUnique(); -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - glGenBuffers(1, &buffer->bufferId_); - glBindBuffer(GL_ARRAY_BUFFER, buffer->bufferId_); - glBufferData(GL_ARRAY_BUFFER, sizeBytes, data, GL_STATIC_DRAW); -#else - (void)data; - (void)sizeBytes; -#endif + buffer->device_ = &device; + buffer->bufferId_ = device.createBuffer(); + device.bindVertexBuffer(buffer->bufferId_); + device.bufferData(GfxBufferTarget::Vertex, data, sizeBytes, false); return buffer; } -Unique VertexBuffer::create(u32 size) { +Unique VertexBuffer::create(GfxDevice& device, u32 size) { auto buffer = makeUnique(); -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - glGenBuffers(1, &buffer->bufferId_); - glBindBuffer(GL_ARRAY_BUFFER, buffer->bufferId_); - glBufferData(GL_ARRAY_BUFFER, size, nullptr, GL_DYNAMIC_DRAW); -#else - (void)size; -#endif + buffer->device_ = &device; + buffer->bufferId_ = device.createBuffer(); + device.bindVertexBuffer(buffer->bufferId_); + device.bufferData(GfxBufferTarget::Vertex, nullptr, size, true); return buffer; } void VertexBuffer::bind() const { -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - glBindBuffer(GL_ARRAY_BUFFER, bufferId_); -#endif + if (device_) device_->bindVertexBuffer(bufferId_); } void VertexBuffer::unbind() const { -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - glBindBuffer(GL_ARRAY_BUFFER, 0); -#endif + if (device_) device_->bindVertexBuffer(0); } void VertexBuffer::setDataRaw(const void* data, u32 sizeBytes) { -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - glBindBuffer(GL_ARRAY_BUFFER, bufferId_); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeBytes, data); -#else - (void)data; - (void)sizeBytes; -#endif + if (!device_) return; + device_->bindVertexBuffer(bufferId_); + device_->bufferSubData(GfxBufferTarget::Vertex, 0, data, sizeBytes); } void VertexBuffer::setSubDataRaw(const void* data, u32 sizeBytes, u32 offsetBytes) { -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - glBindBuffer(GL_ARRAY_BUFFER, bufferId_); - glBufferSubData(GL_ARRAY_BUFFER, offsetBytes, sizeBytes, data); -#else - (void)data; - (void)sizeBytes; - (void)offsetBytes; -#endif + if (!device_) return; + device_->bindVertexBuffer(bufferId_); + device_->bufferSubData(GfxBufferTarget::Vertex, offsetBytes, data, sizeBytes); } // ======================================== @@ -143,26 +139,23 @@ void VertexBuffer::setSubDataRaw(const void* data, u32 sizeBytes, u32 offsetByte // ======================================== IndexBuffer::~IndexBuffer() { -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - if (bufferId_ != 0) { - glDeleteBuffers(1, &bufferId_); + if (bufferId_ != 0 && device_) { + device_->deleteBuffer(bufferId_); } -#endif } IndexBuffer::IndexBuffer(IndexBuffer&& other) noexcept - : bufferId_(other.bufferId_), count_(other.count_), is16Bit_(other.is16Bit_) { + : device_(other.device_), bufferId_(other.bufferId_), count_(other.count_), is16Bit_(other.is16Bit_) { other.bufferId_ = 0; other.count_ = 0; } IndexBuffer& IndexBuffer::operator=(IndexBuffer&& other) noexcept { if (this != &other) { -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - if (bufferId_ != 0) { - glDeleteBuffers(1, &bufferId_); + if (bufferId_ != 0 && device_) { + device_->deleteBuffer(bufferId_); } -#endif + device_ = other.device_; bufferId_ = other.bufferId_; count_ = other.count_; is16Bit_ = other.is16Bit_; @@ -172,66 +165,49 @@ IndexBuffer& IndexBuffer::operator=(IndexBuffer&& other) noexcept { return *this; } -Unique IndexBuffer::create(const u32* indices, u32 count) { +Unique IndexBuffer::create(GfxDevice& device, const u32* indices, u32 count) { auto buffer = makeUnique(); + buffer->device_ = &device; buffer->count_ = count; buffer->is16Bit_ = false; -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - glGenBuffers(1, &buffer->bufferId_); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer->bufferId_); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * sizeof(u32), indices, GL_STATIC_DRAW); -#else - (void)indices; -#endif + buffer->bufferId_ = device.createBuffer(); + device.bindIndexBuffer(buffer->bufferId_); + device.bufferData(GfxBufferTarget::Index, indices, count * sizeof(u32), false); return buffer; } -Unique IndexBuffer::create(const u16* indices, u32 count) { +Unique IndexBuffer::create(GfxDevice& device, const u16* indices, u32 count) { auto buffer = makeUnique(); + buffer->device_ = &device; buffer->count_ = count; buffer->is16Bit_ = true; -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - glGenBuffers(1, &buffer->bufferId_); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer->bufferId_); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * sizeof(u16), indices, GL_STATIC_DRAW); -#else - (void)indices; -#endif + buffer->bufferId_ = device.createBuffer(); + device.bindIndexBuffer(buffer->bufferId_); + device.bufferData(GfxBufferTarget::Index, indices, count * sizeof(u16), false); return buffer; } void IndexBuffer::bind() const { -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferId_); -#endif + if (device_) device_->bindIndexBuffer(bufferId_); } void IndexBuffer::unbind() const { -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); -#endif + if (device_) device_->bindIndexBuffer(0); } // ======================================== // VertexArray // ======================================== -VertexArray::VertexArray() { -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - glGenVertexArrays(1, &arrayId_); -#endif -} - VertexArray::~VertexArray() { -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - if (arrayId_ != 0) { - glDeleteVertexArrays(1, &arrayId_); + if (arrayId_ != 0 && device_) { + device_->deleteVertexArray(arrayId_); } -#endif } VertexArray::VertexArray(VertexArray&& other) noexcept - : arrayId_(other.arrayId_) + : device_(other.device_) + , arrayId_(other.arrayId_) , vertexAttribIndex_(other.vertexAttribIndex_) , vertexBuffers_(std::move(other.vertexBuffers_)) , indexBuffer_(std::move(other.indexBuffer_)) { @@ -241,11 +217,10 @@ VertexArray::VertexArray(VertexArray&& other) noexcept VertexArray& VertexArray::operator=(VertexArray&& other) noexcept { if (this != &other) { -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - if (arrayId_ != 0) { - glDeleteVertexArrays(1, &arrayId_); + if (arrayId_ != 0 && device_) { + device_->deleteVertexArray(arrayId_); } -#endif + device_ = other.device_; arrayId_ = other.arrayId_; vertexAttribIndex_ = other.vertexAttribIndex_; vertexBuffers_ = std::move(other.vertexBuffers_); @@ -256,79 +231,51 @@ VertexArray& VertexArray::operator=(VertexArray&& other) noexcept { return *this; } -Unique VertexArray::create() { - return makeUnique(); +Unique VertexArray::create(GfxDevice& device) { + auto vao = makeUnique(); + vao->device_ = &device; + vao->arrayId_ = device.createVertexArray(); + return vao; } void VertexArray::bind() const { -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - glBindVertexArray(arrayId_); -#endif + if (device_) device_->bindVertexArray(arrayId_); } void VertexArray::unbind() const { -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - glBindVertexArray(0); -#endif + if (device_) device_->bindVertexArray(0); } void VertexArray::addVertexBuffer(Shared buffer) { -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) ES_ASSERT(!buffer->getLayout().getAttributes().empty(), "Vertex buffer has no layout"); - bind(); - buffer->bind(); - - const auto& layout = buffer->getLayout(); - for (const auto& attr : layout) { - glEnableVertexAttribArray(vertexAttribIndex_); - - GLenum glType = GL_FLOAT; - switch (attr.type) { - case ShaderDataType::Float: - case ShaderDataType::Float2: - case ShaderDataType::Float3: - case ShaderDataType::Float4: - glType = GL_FLOAT; - break; - case ShaderDataType::Int: - case ShaderDataType::Int2: - case ShaderDataType::Int3: - case ShaderDataType::Int4: - glType = GL_INT; - break; - case ShaderDataType::Bool: - case ShaderDataType::UByte4N: - glType = GL_UNSIGNED_BYTE; - break; - default: - break; - } - - GLboolean normalized = attr.normalized ? GL_TRUE : GL_FALSE; - if (attr.type == ShaderDataType::UByte4N) { - normalized = GL_TRUE; + if (device_) { + device_->bindVertexArray(arrayId_); + buffer->bind(); + + const auto& layout = buffer->getLayout(); + for (const auto& attr : layout) { + bool normalized = attr.normalized || attr.type == ShaderDataType::UByte4N; + device_->enableVertexAttrib(vertexAttribIndex_); + device_->vertexAttribPointer( + vertexAttribIndex_, + static_cast(shaderDataTypeComponentCount(attr.type)), + toGfxDataType(attr.type), + normalized, + static_cast(layout.getStride()), + attr.offset + ); + ++vertexAttribIndex_; } - - glVertexAttribPointer( - vertexAttribIndex_, - shaderDataTypeComponentCount(attr.type), - glType, - normalized, - layout.getStride(), - reinterpret_cast(static_cast(attr.offset)) - ); - ++vertexAttribIndex_; } -#endif vertexBuffers_.push_back(std::move(buffer)); } void VertexArray::setIndexBuffer(Shared buffer) { -#if defined(ES_PLATFORM_WEB) || defined(ES_PLATFORM_NATIVE) - bind(); - buffer->bind(); -#endif + if (device_) { + device_->bindVertexArray(arrayId_); + buffer->bind(); + } indexBuffer_ = std::move(buffer); } diff --git a/src/esengine/renderer/Buffer.hpp b/src/esengine/renderer/Buffer.hpp index 0f4d2cb7..29a893ee 100644 --- a/src/esengine/renderer/Buffer.hpp +++ b/src/esengine/renderer/Buffer.hpp @@ -1,8 +1,9 @@ /** * @file Buffer.hpp * @brief GPU buffer abstractions for vertex and index data - * @details Provides cross-platform abstractions for OpenGL/WebGL buffer - * objects including VertexBuffer, IndexBuffer, and VertexArray. + * @details VertexBuffer, IndexBuffer and VertexArray are thin RAII handles over + * GfxDevice. They hold no GL state directly — every GPU operation is + * delegated to the device, so this layer has zero GL dependency. * * @author ESEngine Team * @date 2026 @@ -25,6 +26,8 @@ namespace esengine { +class GfxDevice; + // ============================================================================= // Shader Data Types // ============================================================================= @@ -178,14 +181,14 @@ class VertexLayout { /** * @brief GPU buffer for vertex data * - * @details Wraps OpenGL/WebGL Vertex Buffer Objects (VBOs). Supports both - * static and dynamic buffer usage. + * @details Wraps a GfxDevice vertex buffer object. Supports both static and + * dynamic usage. All GPU work is delegated to the device. * * @code * struct Vertex { float x, y, u, v; }; * std::vector vertices = {...}; * - * auto vbo = VertexBuffer::create(std::span(vertices)); + * auto vbo = VertexBuffer::create(device, std::span(vertices)); * vbo->setLayout({ * {ShaderDataType::Float2, "a_position"}, * {ShaderDataType::Float2, "a_texCoord"} @@ -210,45 +213,40 @@ class VertexBuffer { /** * @brief Creates a static buffer from a span * @tparam T Vertex type + * @param device Graphics device * @param data Span of vertex data * @return Unique pointer to the buffer */ template - static Unique create(std::span data) { - return createRaw(data.data(), static_cast(data.size_bytes())); + static Unique create(GfxDevice& device, std::span data) { + return createRaw(device, data.data(), static_cast(data.size_bytes())); } /** * @brief Creates a static buffer from a vector - * @tparam T Vertex type - * @param data Vector of vertex data - * @return Unique pointer to the buffer */ template - static Unique create(const std::vector& data) { - return create(std::span(data)); + static Unique create(GfxDevice& device, const std::vector& data) { + return create(device, std::span(data)); } /** * @brief Creates a static buffer from a C array - * @tparam T Vertex type - * @tparam N Array size - * @param data Array of vertex data - * @return Unique pointer to the buffer */ template - static Unique create(const T (&data)[N]) { - return create(std::span(data, N)); + static Unique create(GfxDevice& device, const T (&data)[N]) { + return create(device, std::span(data, N)); } /** * @brief Creates a dynamic buffer of specified size + * @param device Graphics device * @param sizeBytes Buffer size in bytes * @return Unique pointer to the buffer * * @details Use setData() to upload data later. */ - static Unique create(u32 sizeBytes); + static Unique create(GfxDevice& device, u32 sizeBytes); // ========================================================================= // Operations @@ -262,8 +260,6 @@ class VertexBuffer { /** * @brief Updates buffer data from a span - * @tparam T Vertex type - * @param data New vertex data */ template void setData(std::span data) { @@ -272,8 +268,6 @@ class VertexBuffer { /** * @brief Updates buffer data from a vector - * @tparam T Vertex type - * @param data New vertex data */ template void setData(const std::vector& data) { @@ -282,9 +276,6 @@ class VertexBuffer { /** * @brief Updates buffer data from a C array - * @tparam T Vertex type - * @tparam N Array size - * @param data New vertex data */ template void setData(const T (&data)[N]) { @@ -309,7 +300,7 @@ class VertexBuffer { /** * @brief Gets the GPU buffer ID - * @return OpenGL buffer handle + * @return GPU buffer handle */ u32 getId() const { return bufferId_; } @@ -319,11 +310,12 @@ class VertexBuffer { /** * @brief Creates a buffer from raw data pointer for internal use + * @param device Graphics device * @param data Pointer to vertex data * @param sizeBytes Size of data in bytes * @return Unique pointer to the buffer */ - static Unique createRaw(const void* data, u32 sizeBytes); + static Unique createRaw(GfxDevice& device, const void* data, u32 sizeBytes); /** * @brief Updates buffer data from raw pointer (internal use) @@ -341,6 +333,7 @@ class VertexBuffer { void setSubDataRaw(const void* data, u32 sizeBytes, u32 offsetBytes); private: + GfxDevice* device_ = nullptr; ///< Set by the create* factories; all GL goes through it. u32 bufferId_ = 0; VertexLayout layout_; }; @@ -352,12 +345,12 @@ class VertexBuffer { /** * @brief GPU buffer for index data * - * @details Wraps OpenGL/WebGL Element Buffer Objects (EBOs). Supports - * both 16-bit and 32-bit indices. + * @details Wraps a GfxDevice element buffer object. Supports both 16-bit and + * 32-bit indices. * * @code * std::vector indices = {0, 1, 2, 2, 3, 0}; - * auto ebo = IndexBuffer::create(std::span(indices)); + * auto ebo = IndexBuffer::create(device, std::span(indices)); * @endcode */ class IndexBuffer { @@ -375,60 +368,35 @@ class IndexBuffer { // Type-Safe Creation Methods // ========================================================================= - /** - * @brief Creates an index buffer from a span of 32-bit indices - * @param indices Span of index data - * @return Unique pointer to the buffer - */ - static Unique create(std::span indices) { - return create(indices.data(), static_cast(indices.size())); + /** @brief Creates an index buffer from a span of 32-bit indices */ + static Unique create(GfxDevice& device, std::span indices) { + return create(device, indices.data(), static_cast(indices.size())); } - /** - * @brief Creates an index buffer from a span of 16-bit indices - * @param indices Span of index data - * @return Unique pointer to the buffer - */ - static Unique create(std::span indices) { - return create(indices.data(), static_cast(indices.size())); + /** @brief Creates an index buffer from a span of 16-bit indices */ + static Unique create(GfxDevice& device, std::span indices) { + return create(device, indices.data(), static_cast(indices.size())); } - /** - * @brief Creates an index buffer from a vector of 32-bit indices - * @param indices Vector of index data - * @return Unique pointer to the buffer - */ - static Unique create(const std::vector& indices) { - return create(std::span(indices)); + /** @brief Creates an index buffer from a vector of 32-bit indices */ + static Unique create(GfxDevice& device, const std::vector& indices) { + return create(device, std::span(indices)); } - /** - * @brief Creates an index buffer from a vector of 16-bit indices - * @param indices Vector of index data - * @return Unique pointer to the buffer - */ - static Unique create(const std::vector& indices) { - return create(std::span(indices)); + /** @brief Creates an index buffer from a vector of 16-bit indices */ + static Unique create(GfxDevice& device, const std::vector& indices) { + return create(device, std::span(indices)); } - /** - * @brief Creates an index buffer with 32-bit indices from pointer - * @param indices Pointer to index data - * @param count Number of indices - * @return Unique pointer to the buffer - */ - static Unique create(const u32* indices, u32 count); + /** @brief Creates an index buffer with 32-bit indices from pointer */ + static Unique create(GfxDevice& device, const u32* indices, u32 count); /** * @brief Creates an index buffer with 16-bit indices from pointer - * @param indices Pointer to index data - * @param count Number of indices - * @return Unique pointer to the buffer - * * @details Use 16-bit indices for better performance when vertex * count is under 65536. */ - static Unique create(const u16* indices, u32 count); + static Unique create(GfxDevice& device, const u16* indices, u32 count); // ========================================================================= // Operations @@ -450,6 +418,7 @@ class IndexBuffer { bool is16Bit() const { return is16Bit_; } private: + GfxDevice* device_ = nullptr; ///< Set by the create* factories; all GL goes through it. u32 bufferId_ = 0; u32 count_ = 0; bool is16Bit_ = false; @@ -462,27 +431,27 @@ class IndexBuffer { /** * @brief Encapsulates vertex attribute configuration * - * @details Wraps OpenGL/WebGL Vertex Array Objects. Stores the association + * @details Wraps a GfxDevice vertex array object. Stores the association * between vertex buffers and their attribute layouts. * * @code - * auto vao = VertexArray::create(); + * auto vao = VertexArray::create(device); * - * auto vbo = VertexBuffer::create(vertices); + * auto vbo = VertexBuffer::create(device, vertices); * vbo->setLayout({...}); * vao->addVertexBuffer(std::move(vbo)); * - * auto ebo = IndexBuffer::create(indices, 6); + * auto ebo = IndexBuffer::create(device, indices, 6); * vao->setIndexBuffer(std::move(ebo)); * * // Rendering * vao->bind(); - * device.drawElements(ebo->getCount(), GfxDataType::UnsignedShort, 0); + * device.drawElements(ebo->getCount(), GfxDataType::UnsignedInt, 0); * @endcode */ class VertexArray { public: - VertexArray(); + VertexArray() = default; ~VertexArray(); // Non-copyable, movable @@ -493,9 +462,10 @@ class VertexArray { /** * @brief Creates a new vertex array + * @param device Graphics device * @return Unique pointer to the VAO */ - static Unique create(); + static Unique create(GfxDevice& device); /** @brief Binds the VAO for rendering */ void bind() const; @@ -508,8 +478,6 @@ class VertexArray { * @param buffer Shared pointer to the vertex buffer * * @details The buffer's layout must be set before adding. - * Multiple vertex buffers can be added for interleaved - * or separate attribute streams. */ void addVertexBuffer(Shared buffer); @@ -532,6 +500,7 @@ class VertexArray { const Shared& getIndexBuffer() const { return indexBuffer_; } private: + GfxDevice* device_ = nullptr; ///< Set by create(); all GL goes through it. u32 arrayId_ = 0; u32 vertexAttribIndex_ = 0; std::vector> vertexBuffers_; diff --git a/src/esengine/renderer/CustomGeometry.cpp b/src/esengine/renderer/CustomGeometry.cpp index 0730e962..a14e52a7 100644 --- a/src/esengine/renderer/CustomGeometry.cpp +++ b/src/esengine/renderer/CustomGeometry.cpp @@ -16,18 +16,20 @@ namespace esengine { -void CustomGeometry::init(const f32* vertices, u32 vertexCount, const VertexLayout& layout, bool dynamic) { +void CustomGeometry::init(GfxDevice& device, const f32* vertices, u32 vertexCount, + const VertexLayout& layout, bool dynamic) { + device_ = &device; dynamic_ = dynamic; stride_ = layout.getStride(); vertexCount_ = vertexCount * sizeof(f32) / stride_; - vao_ = VertexArray::create(); + vao_ = VertexArray::create(device); if (dynamic) { - vbo_ = Shared(VertexBuffer::create(vertexCount * sizeof(f32))); + vbo_ = Shared(VertexBuffer::create(device, vertexCount * sizeof(f32))); vbo_->setDataRaw(vertices, vertexCount * sizeof(f32)); } else { - vbo_ = Shared(VertexBuffer::createRaw(vertices, vertexCount * sizeof(f32))); + vbo_ = Shared(VertexBuffer::createRaw(device, vertices, vertexCount * sizeof(f32))); } vbo_->setLayout(layout); @@ -35,16 +37,16 @@ void CustomGeometry::init(const f32* vertices, u32 vertexCount, const VertexLayo } void CustomGeometry::setIndices(const u16* indices, u32 indexCount) { - if (!vao_) return; + if (!vao_ || !device_) return; - ibo_ = Shared(IndexBuffer::create(indices, indexCount)); + ibo_ = Shared(IndexBuffer::create(*device_, indices, indexCount)); vao_->setIndexBuffer(ibo_); } void CustomGeometry::setIndices(const u32* indices, u32 indexCount) { - if (!vao_) return; + if (!vao_ || !device_) return; - ibo_ = Shared(IndexBuffer::create(indices, indexCount)); + ibo_ = Shared(IndexBuffer::create(*device_, indices, indexCount)); vao_->setIndexBuffer(ibo_); } diff --git a/src/esengine/renderer/CustomGeometry.hpp b/src/esengine/renderer/CustomGeometry.hpp index b6d5897d..777bcc38 100644 --- a/src/esengine/renderer/CustomGeometry.hpp +++ b/src/esengine/renderer/CustomGeometry.hpp @@ -42,7 +42,7 @@ class CustomGeometry { * @param layout Vertex attribute layout * @param dynamic If true, allows vertex updates */ - void init(const f32* vertices, u32 vertexCount, const VertexLayout& layout, bool dynamic = false); + void init(GfxDevice& device, const f32* vertices, u32 vertexCount, const VertexLayout& layout, bool dynamic = false); /** * @brief Sets indices for indexed rendering @@ -108,6 +108,7 @@ class CustomGeometry { bool isValid() const { return vao_ != nullptr; } private: + GfxDevice* device_ = nullptr; ///< Set in init(); used to create index buffers later. Unique vao_; Shared vbo_; Shared ibo_; diff --git a/src/esengine/renderer/RenderContext.cpp b/src/esengine/renderer/RenderContext.cpp index 8ef383b5..19d48c75 100644 --- a/src/esengine/renderer/RenderContext.cpp +++ b/src/esengine/renderer/RenderContext.cpp @@ -71,15 +71,15 @@ void RenderContext::initQuadData() { u16 indices[] = { 0, 1, 2, 2, 3, 0 }; - quadVAO_ = VertexArray::create(); + quadVAO_ = VertexArray::create(device_); - auto vbo = VertexBuffer::createRaw(vertices, sizeof(vertices)); + auto vbo = VertexBuffer::createRaw(device_, vertices, sizeof(vertices)); vbo->setLayout({ { ShaderDataType::Float2, "a_position" }, { ShaderDataType::Float2, "a_texCoord" } }); - auto ibo = IndexBuffer::create(indices, 6); + auto ibo = IndexBuffer::create(device_, indices, 6); quadVAO_->addVertexBuffer(Shared(std::move(vbo))); quadVAO_->setIndexBuffer(Shared(std::move(ibo))); diff --git a/src/esengine/renderer/Renderer.cpp b/src/esengine/renderer/Renderer.cpp index 624ac782..be6ea7e0 100644 --- a/src/esengine/renderer/Renderer.cpp +++ b/src/esengine/renderer/Renderer.cpp @@ -233,10 +233,10 @@ void BatchRenderer2D::init() { data_->vertices.reserve(MAX_VERTICES); data_->triVertices.reserve(MAX_VERTICES); - data_->vao = VertexArray::create(); + data_->vao = VertexArray::create(device_); data_->vbo = makeShared(); - *data_->vbo = std::move(*VertexBuffer::create(MAX_VERTICES * sizeof(BatchVertex))); + *data_->vbo = std::move(*VertexBuffer::create(device_, MAX_VERTICES * sizeof(BatchVertex))); data_->vbo->setLayout({ { ShaderDataType::Float2, "a_position" }, { ShaderDataType::UByte4N, "a_color" }, @@ -256,7 +256,7 @@ void BatchRenderer2D::init() { indices[i + 5] = offset + 0; offset += 4; } - auto ibo = IndexBuffer::create(indices.data(), MAX_INDICES); + auto ibo = IndexBuffer::create(device_, indices.data(), MAX_INDICES); data_->vao->setIndexBuffer(Shared(std::move(ibo))); #ifndef ES_PLATFORM_WEB diff --git a/src/esengine/resource/ResourceManager.cpp b/src/esengine/resource/ResourceManager.cpp index 46d31beb..b1a938f5 100644 --- a/src/esengine/resource/ResourceManager.cpp +++ b/src/esengine/resource/ResourceManager.cpp @@ -382,7 +382,7 @@ void ResourceManager::removeTextureMetadata(TextureHandle handle) { // ============================================================================= VertexBufferHandle ResourceManager::createVertexBuffer(u32 sizeBytes) { - auto buffer = VertexBuffer::create(sizeBytes); + auto buffer = VertexBuffer::create(*device_, sizeBytes); if (!buffer) { ES_LOG_ERROR("Failed to create dynamic vertex buffer"); return VertexBufferHandle(); @@ -409,7 +409,7 @@ void ResourceManager::releaseVertexBuffer(VertexBufferHandle handle) { // ============================================================================= IndexBufferHandle ResourceManager::createIndexBuffer(ConstSpan indices) { - auto buffer = IndexBuffer::create(indices.data(), static_cast(indices.size())); + auto buffer = IndexBuffer::create(*device_, indices.data(), static_cast(indices.size())); if (!buffer) { ES_LOG_ERROR("Failed to create index buffer (u32)"); return IndexBufferHandle(); @@ -418,7 +418,7 @@ IndexBufferHandle ResourceManager::createIndexBuffer(ConstSpan indices) { } IndexBufferHandle ResourceManager::createIndexBuffer(ConstSpan indices) { - auto buffer = IndexBuffer::create(indices.data(), static_cast(indices.size())); + auto buffer = IndexBuffer::create(*device_, indices.data(), static_cast(indices.size())); if (!buffer) { ES_LOG_ERROR("Failed to create index buffer (u16)"); return IndexBufferHandle(); diff --git a/src/esengine/resource/ResourceManager.hpp b/src/esengine/resource/ResourceManager.hpp index 50277773..95a112d7 100644 --- a/src/esengine/resource/ResourceManager.hpp +++ b/src/esengine/resource/ResourceManager.hpp @@ -514,7 +514,7 @@ class ResourceManager { template VertexBufferHandle ResourceManager::createVertexBuffer(ConstSpan data) { - auto buffer = VertexBuffer::create(data); + auto buffer = VertexBuffer::create(*device_, data); if (!buffer) return VertexBufferHandle(); return vertexBuffers_.add(std::move(buffer)); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2b3bd353..c4317784 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -82,3 +82,15 @@ target_include_directories(test_texture_fb PRIVATE ${CMAKE_SOURCE_DIR}/third_party/stb ) add_test(NAME test_texture_fb COMMAND test_texture_fb) + +# Buffer + CustomGeometry decoupling: compiles the converted classes against a +# MockGfxDevice. Linking proves they contain no GL; asserts confirm create/ +# upload/attribute-setup/bind/delete route through GfxDevice. +add_executable(test_buffer_device + renderer/test_buffer_device.cpp + ${CMAKE_SOURCE_DIR}/src/esengine/renderer/Buffer.cpp + ${CMAKE_SOURCE_DIR}/src/esengine/renderer/CustomGeometry.cpp + ${CMAKE_SOURCE_DIR}/src/esengine/core/Log.cpp +) +target_include_directories(test_buffer_device PRIVATE ${CMAKE_SOURCE_DIR}/src) +add_test(NAME test_buffer_device COMMAND test_buffer_device) diff --git a/tests/renderer/MockGfxDevice.hpp b/tests/renderer/MockGfxDevice.hpp index aa0c8a74..4fc719be 100644 --- a/tests/renderer/MockGfxDevice.hpp +++ b/tests/renderer/MockGfxDevice.hpp @@ -36,6 +36,16 @@ struct MockGfxDevice final : GfxDevice { int deleteFramebufferCalls = 0; int framebufferTexture2DCalls = 0; u32 nextFramebufferId = 500; + int createBufferCalls = 0; + int deleteBufferCalls = 0; + int bufferDataCalls = 0; + int bufferSubDataCalls = 0; + int createVertexArrayCalls = 0; + int deleteVertexArrayCalls = 0; + int enableVertexAttribCalls = 0; + int vertexAttribPointerCalls = 0; + u32 nextBufferId = 200; + u32 nextVaoId = 800; // last args u32 lastProgram = 0, lastVao = 0, lastVbo = 0, lastIbo = 0, lastFbo = 0; i32 lastUniform1iLoc = -999, lastUniform1iVal = 0; @@ -87,18 +97,18 @@ struct MockGfxDevice final : GfxDevice { void setUniformMat4(i32, const f32*) override {} std::vector getActiveUniforms(u32) override { ++getActiveUniformsCalls; return {}; } - u32 createBuffer() override { return 0; } - void deleteBuffer(u32) override {} + u32 createBuffer() override { ++createBufferCalls; return nextBufferId++; } + void deleteBuffer(u32) override { ++deleteBufferCalls; } void bindVertexBuffer(u32 bufferId) override { ++bindVertexBufferCalls; lastVbo = bufferId; } void bindIndexBuffer(u32 bufferId) override { ++bindIndexBufferCalls; lastIbo = bufferId; } - void bufferData(GfxBufferTarget, const void*, u32, bool) override {} - void bufferSubData(GfxBufferTarget, u32, const void*, u32) override {} + void bufferData(GfxBufferTarget, const void*, u32, bool) override { ++bufferDataCalls; } + void bufferSubData(GfxBufferTarget, u32, const void*, u32) override { ++bufferSubDataCalls; } - u32 createVertexArray() override { return 0; } - void deleteVertexArray(u32) override {} + u32 createVertexArray() override { ++createVertexArrayCalls; return nextVaoId++; } + void deleteVertexArray(u32) override { ++deleteVertexArrayCalls; } void bindVertexArray(u32 vaoId) override { ++bindVertexArrayCalls; lastVao = vaoId; } - void enableVertexAttrib(u32) override {} - void vertexAttribPointer(u32, i32, GfxDataType, bool, i32, u32) override {} + void enableVertexAttrib(u32) override { ++enableVertexAttribCalls; } + void vertexAttribPointer(u32, i32, GfxDataType, bool, i32, u32) override { ++vertexAttribPointerCalls; } void vertexAttribDivisor(u32, u32) override {} void drawElements(u32, GfxDataType, u32) override {} diff --git a/tests/renderer/test_buffer_device.cpp b/tests/renderer/test_buffer_device.cpp new file mode 100644 index 00000000..747069df --- /dev/null +++ b/tests/renderer/test_buffer_device.cpp @@ -0,0 +1,98 @@ +// Native MSVC/CTest harness for Buffer + CustomGeometry (RC5-GfxDevice). +// +// Compiles the converted Buffer.cpp + CustomGeometry.cpp against MockGfxDevice. +// Linking proves they no longer touch GL; the asserts confirm create/upload/ +// attribute-setup/bind/delete all route through GfxDevice. + +#include "MockGfxDevice.hpp" +#include "esengine/renderer/Buffer.hpp" +#include "esengine/renderer/CustomGeometry.hpp" + +#include +#include + +using namespace esengine; + +static int g_failures = 0; +#define CHECK(cond, msg) \ + do { \ + if (!(cond)) { std::printf("FAIL: %s\n", msg); ++g_failures; } \ + else { std::printf("ok: %s\n", msg); } \ + } while (0) + +int main() { + // --- VertexBuffer --- + { + MockGfxDevice d; + float verts[] = { 0, 0, 1, 1, 2, 2, 3, 3 }; + { + auto vbo = VertexBuffer::createRaw(d, verts, sizeof(verts)); + CHECK(vbo != nullptr, "VertexBuffer::createRaw returns a buffer"); + CHECK(d.createBufferCalls == 1, "createRaw -> device.createBuffer"); + CHECK(d.bufferDataCalls == 1, "createRaw -> device.bufferData"); + CHECK(vbo->getId() == 200, "buffer id is device-assigned"); + vbo->setDataRaw(verts, sizeof(verts)); + CHECK(d.bufferSubDataCalls == 1, "setDataRaw -> device.bufferSubData"); + } + CHECK(d.deleteBufferCalls == 1, "destructor -> device.deleteBuffer"); + } + + // --- IndexBuffer --- + { + MockGfxDevice d; + u32 idx[] = { 0, 1, 2, 2, 3, 0 }; + auto ibo = IndexBuffer::create(d, idx, 6); + CHECK(ibo != nullptr, "IndexBuffer::create returns a buffer"); + CHECK(d.createBufferCalls == 1 && d.bufferDataCalls == 1, "create -> device.createBuffer + bufferData"); + CHECK(d.bindIndexBufferCalls >= 1, "create binds via device.bindIndexBuffer"); + CHECK(ibo->getCount() == 6, "index count stored"); + CHECK(!ibo->is16Bit(), "u32 indices not flagged 16-bit"); + } + + // --- VertexArray + attribute setup --- + { + MockGfxDevice d; + float verts[] = { 0, 0, 0, 0 }; + auto vao = VertexArray::create(d); + CHECK(d.createVertexArrayCalls == 1, "VertexArray::create -> device.createVertexArray"); + + auto vbo = Shared(VertexBuffer::createRaw(d, verts, sizeof(verts))); + vbo->setLayout({ + { ShaderDataType::Float2, "a_position" }, + { ShaderDataType::Float2, "a_texCoord" }, + }); + vao->addVertexBuffer(vbo); + CHECK(d.enableVertexAttribCalls == 2, "addVertexBuffer enables one attrib per layout entry"); + CHECK(d.vertexAttribPointerCalls == 2, "addVertexBuffer sets one pointer per layout entry"); + CHECK(d.bindVertexArrayCalls >= 1, "addVertexBuffer binds the VAO via device"); + } + + // --- CustomGeometry end-to-end (init + indices + bind, all via device) --- + { + MockGfxDevice d; + float verts[] = { 0, 0, 0, 0, 1, 1, 1, 1 }; // 2 verts, stride 16 + u16 idx[] = { 0, 1, 2 }; + CustomGeometry geom; + geom.init(d, verts, 8, VertexLayout{ + { ShaderDataType::Float2, "a_position" }, + { ShaderDataType::Float2, "a_texCoord" }, + }); + CHECK(geom.isValid(), "CustomGeometry initialized"); + CHECK(d.createVertexArrayCalls == 1, "geom.init creates a VAO via device"); + CHECK(d.createBufferCalls == 1, "geom.init creates a VBO via device"); + + geom.setIndices(idx, 3); + CHECK(geom.hasIndices() && geom.getIndexCount() == 3, "geom.setIndices stored an index buffer"); + CHECK(d.createBufferCalls == 2, "geom.setIndices creates an IBO via device"); + + geom.bind(d); + CHECK(d.bindVertexArrayCalls >= 1, "geom.bind routes through device"); + } + + if (g_failures == 0) { + std::printf("\nALL BUFFER/GEOMETRY TESTS PASSED\n"); + return 0; + } + std::printf("\n%d FAILURE(S)\n", g_failures); + return 1; +}