diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 5cfa87a6..18362046 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -863,6 +863,13 @@ namespace MobileGL::MG_Backend::DirectGLES { #endif DrawSyncBit syncBit = DrawSyncBit::None; PrepareForDraw(syncBit); + const auto& currentVAO = MG_State::pGLContext->GetBoundVertexArray(); + if (currentVAO) { + const auto& backendVAOIt = VertexArrayImpl::g_backendVertexArrayObjects.find(currentVAO.get()); + if (backendVAOIt != VertexArrayImpl::g_backendVertexArrayObjects.end()) { + backendVAOIt->second->SyncClientSideAttributesForDrawArrays(currentVAO, first, count); + } + } g_GLESFuncs.glDrawArrays(mode, first, count); } diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 2c6aa0b9..bb284c22 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -194,10 +194,34 @@ namespace MobileGL::MG_Backend::DirectGLES { } // namespace BufferImpl namespace VertexArrayImpl { + namespace { + SizeT GetDataTypeSize(DataType type) { + switch (type) { + case DataType::Int8: + case DataType::Uint8: + return 1; + case DataType::Int16: + case DataType::Uint16: + case DataType::Float16: + return 2; + case DataType::Int32: + case DataType::Uint32: + case DataType::Float32: + case DataType::Fixed32: + return 4; + case DataType::Float64: + return 8; + default: + return 0; + } + } + } // namespace + BackendVertexArrayObject::BackendVertexArrayObject() { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif + m_clientAttributeBufferIds.fill(0); g_GLESFuncs.glGenVertexArrays(1, &m_backendVAOId); if (m_backendVAOId == 0) { MGLOG_E("Failed to generate vertex array object."); @@ -207,6 +231,19 @@ namespace MobileGL::MG_Backend::DirectGLES { } } + BackendVertexArrayObject::~BackendVertexArrayObject() { + if (m_backendVAOId != 0) { + g_GLESFuncs.glDeleteVertexArrays(1, &m_backendVAOId); + m_backendVAOId = 0; + } + for (auto& bufferId : m_clientAttributeBufferIds) { + if (bufferId != 0) { + g_GLESFuncs.glDeleteBuffers(1, &bufferId); + bufferId = 0; + } + } + } + void BackendVertexArrayObject::Bind() const { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); @@ -312,6 +349,58 @@ namespace MobileGL::MG_Backend::DirectGLES { m_syncedAttributeVersions = allAttributeVersions; } + void BackendVertexArrayObject::SyncClientSideAttributesForDrawArrays( + const SharedPtr& stateVAOObject, GLint first, GLsizei count) { + if (!stateVAOObject || count <= 0 || first < 0) { + return; + } + + Bind(); + + const auto& allAttributes = stateVAOObject->GetAllAttributes(); + for (Uint attribIndex = 0; attribIndex < allAttributes.size(); ++attribIndex) { + const auto& attrib = allAttributes[attribIndex]; + if (!attrib.Enabled || attrib.Buffer) { + continue; + } + + const auto* clientData = reinterpret_cast(attrib.Offset); + const SizeT componentSize = GetDataTypeSize(attrib.Type); + if (!clientData || componentSize == 0 || attrib.Size <= 0) { + continue; + } + + const SizeT elementSize = componentSize * static_cast(attrib.Size); + const SizeT stride = attrib.Stride > 0 ? static_cast(attrib.Stride) : elementSize; + const SizeT uploadSize = static_cast(first + count - 1) * stride + elementSize; + + auto& bufferId = m_clientAttributeBufferIds[attribIndex]; + if (bufferId == 0) { + g_GLESFuncs.glGenBuffers(1, &bufferId); + if (bufferId == 0) { + MGLOG_E("Failed to create client-side vertex attribute upload buffer."); + continue; + } + } + + g_GLESFuncs.glBindBuffer(GL_ARRAY_BUFFER, bufferId); + g_GLESFuncs.glBufferData(GL_ARRAY_BUFFER, static_cast(uploadSize), clientData, + GL_STREAM_DRAW); + + if (!attrib.IsInteger) { + g_GLESFuncs.glVertexAttribPointer( + attribIndex, attrib.Size, MG_Util::ConvertDataTypeToGLEnum(attrib.Type), + attrib.Normalized ? GL_TRUE : GL_FALSE, static_cast(stride), nullptr); + } else { + g_GLESFuncs.glVertexAttribIPointer(attribIndex, attrib.Size, + MG_Util::ConvertDataTypeToGLEnum(attrib.Type), + static_cast(stride), nullptr); + } + } + + BufferImpl::g_boundVertexBufferObject = nullptr; + } + StateBackendObjectRegistry g_backendVertexArrayObjects; } // namespace VertexArrayImpl diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 59ffb7a5..fccdc41a 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -137,12 +137,16 @@ namespace MobileGL::MG_Backend::DirectGLES { class BackendVertexArrayObject { public: BackendVertexArrayObject(); + ~BackendVertexArrayObject(); void SyncToBackend(const SharedPtr& stateVAOObject); + void SyncClientSideAttributesForDrawArrays( + const SharedPtr& stateVAOObject, GLint first, GLsizei count); Uint GetBackendVertexArrayId() const { return m_backendVAOId; } void Bind() const; private: Uint m_backendVAOId = 0; + Array m_clientAttributeBufferIds; Bool m_isInitialized = false; Uint16 m_syncedIndexBufferVersion = 0; Array diff --git a/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp b/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp index 0a45e892..1218237a 100644 --- a/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp +++ b/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp @@ -60,12 +60,6 @@ namespace MobileGL::MG_Impl::GLImpl { auto& vboSlot = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Vertex); auto& vbo = vboSlot.GetBoundObject(); - if (!vbo) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", "VertexAttribPointer_State", - "No buffer is bound to GL_ARRAY_BUFFER.")); - return; - } auto offset = reinterpret_cast(pointer); @@ -91,12 +85,6 @@ namespace MobileGL::MG_Impl::GLImpl { auto& vboSlot = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Vertex); auto& vbo = vboSlot.GetBoundObject(); - if (!vbo) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", "VertexAttribPointer_State", - "No buffer is bound to GL_ARRAY_BUFFER.")); - return; - } SizeT offset = reinterpret_cast(pointer); diff --git a/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp b/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp index 78fd245a..c73dadd6 100644 --- a/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp +++ b/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp @@ -363,6 +363,24 @@ TEST_F(GeneralVertexArrayTest, General_ElementArrayBufferBindingIsVaoLocalAndZer EXPECT_EQ(GetError(), GL_NO_ERROR); } +TEST_F(GeneralVertexArrayTest, General_ClientSideVertexAttribPointerIsAccepted) { + GLuint vao = CreateVAO(); + BindBuffer(GL_ARRAY_BUFFER, 0); + + float vertices[] = {0.0f, 0.0f, 1.0f, 1.0f}; + VertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices); + EnableVertexAttribArray(0); + + auto vaoObj = MG_State::pGLContext->GetVertexArrayObject(vao); + ASSERT_NE(vaoObj, nullptr); + + const auto& attr = vaoObj->GetAttribute(0); + EXPECT_TRUE(attr.Enabled); + EXPECT_EQ(attr.Buffer, nullptr); + EXPECT_EQ(attr.Offset, reinterpret_cast(vertices)); + EXPECT_EQ(GetError(), GL_NO_ERROR); +} + TEST_F(GeneralVertexArrayTest, General_IntegerAttributes) { GLuint vao = CreateVAO(); GLuint vbo = CreateVBO(GL_ARRAY_BUFFER, 128);