diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index 81b401b3..ffc889a5 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -86,6 +86,7 @@ namespace MobileGL { void (*DrawElements)(GLenum mode, GLsizei count, GLenum type, const void* indices); void (*DrawElementsBaseVertex)(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex); + void (*MultiDrawArrays)(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount); void (*MultiDrawElements)(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, GLsizei drawcount); void (*MultiDrawElementsBaseVertex)(GLenum mode, const GLsizei* count, GLenum type, diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index ffa5aea2..df2ae21b 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -874,6 +874,7 @@ namespace MobileGL::MG_Backend::DirectGLES { funcsTable.GL.DrawArrays = DrawArrays; funcsTable.GL.DrawElements = DrawElements; funcsTable.GL.DrawElementsBaseVertex = DrawElementsBaseVertex; + funcsTable.GL.MultiDrawArrays = MultiDrawArrays; funcsTable.GL.MultiDrawElements = MultiDrawElements; funcsTable.GL.MultiDrawElementsBaseVertex = MultiDrawElementsBaseVertex; funcsTable.GL.MultiDrawElementsIndirect = MultiDrawElementsIndirect; diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 0fa8df53..25ce3809 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -1363,6 +1363,26 @@ namespace MobileGL::MG_Backend::DirectGLES { g_GLESFuncs.glDrawElementsBaseVertex(mode, count, type, indices, basevertex); } + void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) { +#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER + DebugImpl::OpenGLScopeMarker marker(__func__); +#endif + DrawSyncBit syncBit = DrawSyncBit::None; + PrepareForDraw(syncBit); + + const auto& currentVAO = MG_State::pGLContext->GetBoundVertexArray(); + for (GLsizei i = 0; i < drawcount; ++i) { + // Client-side arrays are uploaded per sub-draw range, like the single DrawArrays path. + if (currentVAO) { + const auto& backendVAOIt = VertexArrayImpl::g_backendVertexArrayObjects.find(currentVAO.get()); + if (backendVAOIt != VertexArrayImpl::g_backendVertexArrayObjects.end()) { + backendVAOIt->second->SyncClientSideAttributesForDrawArrays(currentVAO, first[i], count[i]); + } + } + g_GLESFuncs.glDrawArrays(mode, first[i], count[i]); + } + } + void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, GLsizei drawcount) { #if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h index b31f964b..7495f20d 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h @@ -27,6 +27,7 @@ namespace MobileGL::MG_Backend::DirectGLES { void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices); void DrawArrays(GLenum mode, GLint first, GLsizei count); void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex); + void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount); void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, GLsizei drawcount); void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index 772c2a31..114c89e5 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -555,6 +555,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { funcsTable.GL.DrawArrays = DrawArrays; funcsTable.GL.DrawElements = DrawElements; funcsTable.GL.DrawElementsBaseVertex = DrawElementsBaseVertex; + funcsTable.GL.MultiDrawArrays = MultiDrawArrays; funcsTable.GL.MultiDrawElements = MultiDrawElements; funcsTable.GL.MultiDrawElementsBaseVertex = MultiDrawElementsBaseVertex; funcsTable.GL.MultiDrawElementsIndirect = MultiDrawElementsIndirect; diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index 7c78cdbd..ce664e7f 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -1266,6 +1266,33 @@ namespace MobileGL::MG_Backend::DirectVulkan { pVulkanRenderer->DrawElements(payload); } + void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) { + MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawArrays called with null VulkanRenderer"); + MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::MultiDrawArrays called with null GL context"); + if (drawcount <= 0) { + return; + } + + MultiDrawCmd payload{}; + payload.mode = mode; + + // TODO: allocate draw cmd buf elsewhere + static Vector params; + params.clear(); + params.resize(drawcount); + + for (GLsizei i = 0; i < drawcount; ++i) { + auto& param = params[i]; + param.vertexCount = count[i] > 0 ? static_cast(count[i]) : 0; + param.instanceCount = 1; + param.firstVertex = first[i] > 0 ? static_cast(first[i]) : 0; + param.firstInstance = 0; + } + payload.drawCount = static_cast(drawcount); + payload.pParams = params.data(); + pVulkanRenderer->MultiDrawArrays(payload); + } + void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, GLsizei drawcount) { MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElements called with null VulkanRenderer"); diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h index 7eae65c9..27bb4f96 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h @@ -35,6 +35,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices); void DrawArrays(GLenum mode, GLint first, GLsizei count); void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex); + void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount); void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, GLsizei drawcount); void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 2ca37004..d4729e61 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -5195,6 +5195,36 @@ void main() { payload.params.firstInstance); } + void VulkanRenderer::MultiDrawArrays(const MultiDrawCmd& payload) { + auto& frame = m_frameContext.GetCurrent(); + + // One state/pipeline setup covering the union of all sub-draw vertex ranges, then a vkCmdDraw + // per range -- mirrors MultiDrawElements. + DrawCmdParam vertexRange{}; + for (Uint32 idraw = 0; idraw < payload.drawCount; ++idraw) { + vertexRange.vertexCount = std::max(vertexRange.vertexCount, + payload.pParams[idraw].firstVertex + payload.pParams[idraw].vertexCount); + vertexRange.instanceCount = std::max(vertexRange.instanceCount, payload.pParams[idraw].instanceCount); + vertexRange.firstInstance = std::max(vertexRange.firstInstance, payload.pParams[idraw].firstInstance); + } + + if (!SetupDraw(frame, payload.mode, 0, vertexRange)) { + return; + } + + MOBILEGL_ASSERT(frame.isCommandRecording, "%s: frame recording was not started", __func__); + + VkCommandBuffer& commandBuffer = frame.commandBuffer; + + for (Uint32 idraw = 0; idraw < payload.drawCount; ++idraw) { + vkCmdDraw(commandBuffer, + payload.pParams[idraw].vertexCount, + payload.pParams[idraw].instanceCount, + payload.pParams[idraw].firstVertex, + payload.pParams[idraw].firstInstance); + } + } + void VulkanRenderer::MultiDrawElements(const MultiDrawIndexedCmd& payload) { auto& frame = m_frameContext.GetCurrent(); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 3dc65b02..002a7ac1 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -87,6 +87,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { DrawIndexedCmdParam* pParams = nullptr; }; + struct MultiDrawCmd { + GLenum mode = GL_TRIANGLES; + Uint32 drawCount = 0; + DrawCmdParam* pParams = nullptr; + }; + struct QueueFamilyIndices { Int32 graphicsFamily = -1; Int32 presentFamily = -1; @@ -161,6 +167,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { static VkMemoryBarrier BuildMemoryBarrierForGlBarriers(GLbitfield barriers); void DrawArrays(const DrawCmd& payload); void DrawElements(const DrawIndexedCmd& payload); + void MultiDrawArrays(const MultiDrawCmd& payload); void MultiDrawElements(const MultiDrawIndexedCmd& payloads); void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride); diff --git a/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp b/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp index f978bc0e..efcffa76 100644 --- a/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp +++ b/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp @@ -799,6 +799,55 @@ namespace MobileGL::MG_Impl::GLImpl { bufferObject->UploadSubData({(void*)data, (SizeT)size}, offset); } + void GetBufferSubData_State(GLenum target, GLintptr offset, GLsizeiptr size, void* data) { + MGLOG_D("%s: target = %s, offset = %d, size = %d, data = %p", __func__, + MG_Util::ConvertGLEnumToString(target).c_str(), offset, size, data); + if (!data) { + // Match BufferSubData_State: a null pointer is a caller bug, not a GL-specified error. + return; + } + + if (size < 0 || offset < 0) { + MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", "GetBufferSubData_State", + "Offset and size must be non-negative.")); + return; + } + + BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target); + if (!BufferImpl::ValidateBufferTarget(bufferTarget)) return; + auto& bindingSlot = GetBufferBindingSlot(bufferTarget); + + auto& bufferObject = bindingSlot.GetBoundObject(); + if (!bufferObject) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", "GetBufferSubData_State", + "Buffer target is bound to no buffer object.")); + return; + } + + SizeT bufferSize = bufferObject->GetSize(); + if (static_cast(offset) + static_cast(size) > bufferSize) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", "GetBufferSubData_State", + "Offset and size exceed buffer size.")); + return; + } + + if (bufferObject->IsMapped() && + !(bufferObject->GetMappingAccess() & BufferMappingAccessBit::Persistent)) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", "GetBufferSubData_State", + "Cannot read from a buffer object mapped without GL_MAP_PERSISTENT_BIT.")); + return; + } + + bufferObject->DownloadSubData(data, static_cast(offset), static_cast(size)); + } + void BufferData_State(GLenum target, GLsizeiptr size, const void* data, GLenum usage) { MGLOG_D("%s: %s, size = %d, data = %p, usage = %s", __func__, MG_Util::ConvertGLEnumToString(target).c_str(), size, data, MG_Util::ConvertGLEnumToString(usage).c_str()); @@ -1425,6 +1474,10 @@ namespace MobileGL::MG_Impl::GLImpl { BufferSubData_State(target, offset, size, data); } + void GetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, void* data) { + GetBufferSubData_State(target, offset, size, data); + } + void BufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage) { BufferData_State(target, size, data, usage); } diff --git a/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.h b/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.h index 5e2dac74..a408189c 100644 --- a/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.h +++ b/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.h @@ -40,6 +40,7 @@ namespace MobileGL::MG_Impl::GLImpl { void CopyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); void BufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void* data); + void GetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, void* data); void BufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage); void BindBuffer(GLenum target, GLuint buffer); void GenBuffers(GLsizei n, GLuint* buffers); diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index 78ec3116..6a4aa3be 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -116,6 +116,13 @@ namespace MobileGL::MG_Impl::GLImpl { MG_Backend::gBackendFunctionsTable.GL.DrawArrays(mode, first, count); } + void MultiDrawArrays_Backend(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) { +#ifdef TRACY_ENABLE + ZoneScopedC(TRACY_ZONECOLOR_BACKEND); +#endif + MG_Backend::gBackendFunctionsTable.GL.MultiDrawArrays(mode, first, count, drawcount); + } + void DrawElementsBaseVertex_Backend(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex) { #ifdef TRACY_ENABLE @@ -426,6 +433,18 @@ namespace MobileGL::MG_Impl::GLImpl { DrawArrays_Backend(mode, first, count); } + void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) { + if (!ValidateCurrentProgramForExecution(__func__)) return; + if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; + if (drawcount < 0) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, "drawcount must be non-negative.")); + return; + } + MultiDrawArrays_Backend(mode, first, count, drawcount); + } + void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices, GLsizei drawcount) { if (!ValidateCurrentProgramForExecution(__func__)) return; diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h index e6273147..122d668f 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h @@ -38,6 +38,7 @@ namespace MobileGL::MG_Impl::GLImpl { void DrawArraysIndirect(GLenum mode, const void* indirect); void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex); void DrawArrays(GLenum mode, GLint first, GLsizei count); + void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount); void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices, GLsizei drawcount); void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices, diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index 02f8649a..343e4399 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -785,7 +785,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, LoadTransposeMatrixf, const GLfloat* m) DECL DECLARE_GL_FUNCTION_STUB_HEAD(void, LoadTransposeMatrixd, const GLdouble* m) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, LoadTransposeMatrixd, m) DECLARE_GL_FUNCTION_STUB_HEAD(void, MultTransposeMatrixf, const GLfloat* m) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultTransposeMatrixf, m) DECLARE_GL_FUNCTION_STUB_HEAD(void, MultTransposeMatrixd, const GLdouble* m) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultTransposeMatrixd, m) -DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiDrawArrays, GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiDrawArrays, mode, first, count, drawcount) +DECLARE_GL_FUNCTION_HEAD(void, MultiDrawArrays, GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MultiDrawArrays, mode, first, count, drawcount) DECLARE_GL_FUNCTION_HEAD(void, MultiDrawElements, GLenum mode, const GLsizei* count, GLenum type, const void* const* indices, GLsizei drawcount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MultiDrawElements, mode, count, type, indices, drawcount) DECLARE_GL_FUNCTION_HEAD(void, PointParameterf, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PointParameterf, pname, param) DECLARE_GL_FUNCTION_HEAD(void, PointParameterfv, GLenum pname, const GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PointParameterfv, pname, params) @@ -830,7 +830,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, WindowPos3iv, const GLint* v) DECLARE_GL_FUN DECLARE_GL_FUNCTION_STUB_HEAD(void, WindowPos3s, GLshort x, GLshort y, GLshort z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, WindowPos3s, x, y, z) DECLARE_GL_FUNCTION_STUB_HEAD(void, WindowPos3sv, const GLshort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, WindowPos3sv, v) DECLARE_GL_FUNCTION_HEAD(void, GetQueryObjectiv, GLuint id, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryObjectiv, id, pname, params) -DECLARE_GL_FUNCTION_STUB_HEAD(void, GetBufferSubData, GLenum target, GLintptr offset, GLsizeiptr size, void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetBufferSubData, target, offset, size, data) +DECLARE_GL_FUNCTION_HEAD(void, GetBufferSubData, GLenum target, GLintptr offset, GLsizeiptr size, void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetBufferSubData, target, offset, size, data) DECLARE_GL_FUNCTION_HEAD(void, GetVertexAttribdv, GLuint index, GLenum pname, GLdouble* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexAttribdv, index, pname, params) DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib1d, GLuint index, GLdouble x) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib1d, index, x) DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib1dv, GLuint index, const GLdouble* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib1dv, index, v) diff --git a/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp b/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp index 4c9005d3..7937cf69 100644 --- a/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp +++ b/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp @@ -185,6 +185,13 @@ namespace MobileGL::MG_State::GLState { NotifySubData(atOffset, data.size); } + void BufferObject::DownloadSubData(void* dst, SizeT atOffset, SizeT size) const { + MOBILEGL_ASSERT(atOffset + size <= m_size, + "DownloadSubData out of bounds: atOffset (%zu) + size (%zu) > m_size (%zu)", atOffset, size, + m_size); + Memcpy(dst, m_dataPtr->data() + atOffset, size); + } + void BufferObject::CopyDataFrom(const SharedPtr& src, SizeT srcOffset, SizeT dstOffset, SizeT size) { MOBILEGL_ASSERT(!m_isMapped || (m_mappingAccess & BufferMappingAccessBit::Persistent), "Cannot copy data while destination buffer is non-persistently mapped."); diff --git a/MobileGL/MG_State/GLState/BufferState/BufferObject.h b/MobileGL/MG_State/GLState/BufferState/BufferObject.h index 09854726..e7ff5848 100644 --- a/MobileGL/MG_State/GLState/BufferState/BufferObject.h +++ b/MobileGL/MG_State/GLState/BufferState/BufferObject.h @@ -117,6 +117,10 @@ namespace MobileGL { void UploadData(DataPtr data, SizeT atOffset); void UploadSubData(DataPtr data, SizeT atOffset); + // Reads `size` bytes from the CPU shadow at `atOffset` into `dst` (glGetBufferSubData). + // The shadow reflects CPU writes (BufferData/SubData/maps) and backend write-backs, but not + // arbitrary GPU-side writes. + void DownloadSubData(void* dst, SizeT atOffset, SizeT size) const; void CopyDataFrom(const SharedPtr& src, SizeT srcOffset, SizeT dstOffset, SizeT size); void* AcquireMemory(Bool markMapped, Bool read, Bool write); diff --git a/MobileGL/MG_Test/Buffer/BufferTest.cpp b/MobileGL/MG_Test/Buffer/BufferTest.cpp index 0e4dd14d..f2038004 100644 --- a/MobileGL/MG_Test/Buffer/BufferTest.cpp +++ b/MobileGL/MG_Test/Buffer/BufferTest.cpp @@ -238,6 +238,47 @@ TEST_F(BufferTest, CopyBufferSubData) { ASSERT_EQ(srcObj->GetChangeSerial(), srcSerial); } +TEST_F(BufferTest, GetBufferSubDataRoundTrip) { + using namespace MobileGL::MG_Impl::GLImpl; + GLuint buf; + GenBuffers(1, &buf); + BindBuffer(GL_ARRAY_BUFFER, buf); + + const Vector src{10, 20, 30, 40, 50, 60, 70, 80}; + const SizeT bytes = src.size() * sizeof(Int); + BufferData(GL_ARRAY_BUFFER, static_cast(bytes), src.data(), GL_STATIC_DRAW); + EXPECT_EQ(GetError(), GL_NO_ERROR); + + // Read a middle range [2..6). + Vector mid(4, -1); + GetBufferSubData(GL_ARRAY_BUFFER, 2 * sizeof(Int), 4 * sizeof(Int), mid.data()); + EXPECT_EQ(GetError(), GL_NO_ERROR); + EXPECT_EQ(mid, (Vector{30, 40, 50, 60})); + + // Read the whole buffer back. + Vector whole(src.size(), 0); + GetBufferSubData(GL_ARRAY_BUFFER, 0, static_cast(bytes), whole.data()); + EXPECT_EQ(whole, src); + + // Out-of-range range -> GL_INVALID_VALUE, destination untouched. + Vector guard(2, 999); + GetBufferSubData(GL_ARRAY_BUFFER, static_cast(bytes) - sizeof(Int), 2 * sizeof(Int), guard.data()); + EXPECT_EQ(GetError(), GL_INVALID_VALUE); + EXPECT_EQ(guard, (Vector{999, 999})); + + // Negative offset -> GL_INVALID_VALUE. + GetBufferSubData(GL_ARRAY_BUFFER, -1, sizeof(Int), guard.data()); + EXPECT_EQ(GetError(), GL_INVALID_VALUE); +} + +TEST_F(BufferTest, GetBufferSubDataNoBufferBound) { + using namespace MobileGL::MG_Impl::GLImpl; + BindBuffer(GL_ARRAY_BUFFER, 0); // ensure nothing is bound + Int dst = 0; + GetBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(Int), &dst); + EXPECT_EQ(GetError(), GL_INVALID_OPERATION); +} + TEST_F(BufferTest, WriteWhileMapped) { auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::ShaderStorage); Vector bufferNames;