From b3794f4e6a00b352559d27933831929a09568948 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 16 Aug 2026 01:09:55 -0400 Subject: [PATCH] [Feat] (MG_Impl, MG_State, MG_Util, DirectGLES, DirectVulkan, MG_Test): implement ARB_clear_buffer_object correctly --- .../DirectGLES/BackendObject_DirectGLES.cpp | 2 +- MobileGL/MG_Backend/DirectGLES/Managers.cpp | 7 +- .../BackendObject_DirectVulkan.cpp | 2 +- MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp | 115 +++++++++++++----- MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.h | 3 + .../MG_Impl/GLImpl/Exporting/Definitions.cpp | 4 +- .../GLState/BufferState/BufferObject.cpp | 28 +++++ .../GLState/BufferState/BufferObject.h | 3 + MobileGL/MG_Test/Buffer/BufferTest.cpp | 112 +++++++++++++++++ .../MG_Util/Texture/PixelStoreProcessor.cpp | 46 +++++++ .../MG_Util/Texture/PixelStoreProcessor.h | 6 + 11 files changed, 293 insertions(+), 35 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index f8a3de4b..7d23f02e 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -932,7 +932,7 @@ namespace MobileGL::MG_Backend::DirectGLES { Vector extensions = { V_OpenGL30, V_OpenGL31, V_OpenGL32, V_OpenGL33, V_OpenGL40, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader, E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store, - E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object, E_GL_EXT_framebuffer_object, + E_GL_ARB_clear_buffer_object, E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object, E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage, E_GL_ARB_texture_storage, E_GL_ARB_texture_storage_multisample, E_GL_ARB_clear_texture, E_GL_ARB_direct_state_access, E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters, E_GL_ARB_shader_draw_parameters, diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index df87f205..d5245e70 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -729,8 +729,13 @@ namespace MobileGL::MG_Backend::DirectGLES { void Ops_ReadbackFromGpu(BufferObject& bufferObject) { auto* resource = ResourceOf(bufferObject); if (!resource || resource->id == 0 || !resource->storageInitialized) return; - if (resource->persistentMapped) return; // shadow already IS the GPU storage if (!CanTouchGLNow() || resource->contextGeneration != g_bufferContextGeneration) return; + if (resource->persistentMapped) { + // Host writes to a persistent map must not race shader writes already queued + // on this context. There is no backend copy to read back in this case. + if (g_GLESFuncs.glFinish) g_GLESFuncs.glFinish(); + return; + } if (!g_GLESFuncs.glMapBufferRange || !g_GLESFuncs.glUnmapBuffer) return; const SizeT size = std::min(bufferObject.GetSize(), resource->storageSize); if (size == 0) return; diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index 04e679fe..0ec1bf03 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -511,7 +511,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vector extensions = { V_OpenGL30, V_OpenGL31, V_OpenGL32, V_OpenGL33, V_OpenGL40, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader, E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store, - E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object, E_GL_ARB_draw_indirect, + E_GL_ARB_clear_buffer_object, E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object, E_GL_ARB_draw_indirect, E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters, E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage, E_GL_ARB_texture_storage, E_GL_ARB_texture_storage_multisample, E_GL_ARB_texture_multisample, diff --git a/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp b/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp index 110cb5d6..fef2853e 100644 --- a/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp +++ b/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp @@ -18,6 +18,7 @@ #include #include #include +#include namespace MobileGL::MG_Impl::GLImpl { namespace { @@ -31,6 +32,8 @@ namespace MobileGL::MG_Impl::GLImpl { NamedBufferData, NamedBufferSubData, CopyNamedBufferSubData, + ClearBufferData, + ClearBufferSubData, ClearNamedBufferData, ClearNamedBufferSubData, MapBufferRange, @@ -65,6 +68,10 @@ namespace MobileGL::MG_Impl::GLImpl { return "NamedBufferSubData"; case BufferOp::CopyNamedBufferSubData: return "CopyNamedBufferSubData"; + case BufferOp::ClearBufferData: + return "ClearBufferData"; + case BufferOp::ClearBufferSubData: + return "ClearBufferSubData"; case BufferOp::ClearNamedBufferData: return "ClearNamedBufferData"; case BufferOp::ClearNamedBufferSubData: @@ -143,16 +150,6 @@ namespace MobileGL::MG_Impl::GLImpl { return 0; } - // The pattern is replicated verbatim, which is only the whole story while the client - // layout already matches the internal format - the case every entry point in practice - // uses, and the only one the conversion machinery here can express. Say so rather than - // quietly writing a differently-sized pattern. - const SizeT sourceSize = MG_Util::GetInputBytesPerPixel(inputFormat, pixelType); - if (sourceSize != elementSize) { - MGLOG_W_ONCE("%s: clear pattern is %zu bytes but internalformat 0x%X stores %zu; " - "converting between them is not implemented", - GetBufferOpName(op), sourceSize, internalformat, elementSize); - } return elementSize; } @@ -194,27 +191,59 @@ namespace MobileGL::MG_Impl::GLImpl { return true; } - void ClearNamedBufferRange_State(GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, - GLenum format, GLenum type, const void* data, BufferOp op) { + Bool BuildClearPattern(GLenum internalformat, GLenum format, GLenum type, const void* data, + SizeT patternSize, BufferOp op, Vector& pattern) { + const TextureInternalFormat internal = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat); + const TextureInputFormat inputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format); + const TexturePixelDataType inputType = MG_Util::ConvertGLEnumToTexturePixelDataType(type); + + Vector zeroInput; + const void* inputPixel = data; + if (inputPixel == nullptr) { + const SizeT inputSize = MG_Util::GetInputBytesPerPixel(inputFormat, inputType); + if (inputSize == 0) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", GetBufferOpName(op), + "format and type do not describe a source pixel.")); + return false; + } + zeroInput.resize(inputSize); + inputPixel = zeroInput.data(); + } + + if (!MG_Util::PixelStoreProcessor::ConvertOnePixelToInternal( + internal, inputFormat, inputType, inputPixel, pattern)) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique( + "MG_Impl/GLImpl", GetBufferOpName(op), + std::format("Cannot convert one ({}, {}) pixel into internalformat 0x{:X}.", + MG_Util::ConvertGLEnumToString(format), MG_Util::ConvertGLEnumToString(type), + internalformat))); + return false; + } + + if (data == nullptr) { + // GL defines a null clear value as all zero bits in the destination store, while + // retaining the format/type validation above. + pattern.assign(patternSize, 0); + } + return true; + } + + void ClearBufferRange_State(const SharedPtr& bufferObject, + GLenum internalformat, GLintptr offset, GLsizeiptr size, + GLenum format, GLenum type, const void* data, BufferOp op) { const SizeT patternSize = GetClearPatternSize(internalformat, format, type, op); if (patternSize == 0) return; - - auto bufferObject = GetNamedBufferObject(buffer, op); - if (!bufferObject) return; if (!ValidateBufferClearRange(bufferObject, offset, size, patternSize, op)) return; if (size == 0) return; - Vector clearData(static_cast(size)); - if (data) { - const auto* pattern = static_cast(data); - for (SizeT at = 0; at < clearData.size(); at += patternSize) { - Memcpy(clearData.data() + at, pattern, patternSize); - } - } else { - Memset(clearData.data(), 0, clearData.size()); - } - - bufferObject->UploadSubData({clearData.data(), clearData.size()}, static_cast(offset)); + Vector pattern; + if (!BuildClearPattern(internalformat, format, type, data, patternSize, op, pattern)) return; + bufferObject->FillSubData({pattern.data(), pattern.size()}, static_cast(offset), + static_cast(size)); } auto& GetBufferBindingSlot(BufferTarget target) { @@ -1197,17 +1226,34 @@ namespace MobileGL::MG_Impl::GLImpl { static_cast(writeOffset), static_cast(size)); } + void ClearBufferData_State(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) { + auto bufferObject = GetBoundBufferObject(target, BufferOp::ClearBufferData); + if (!bufferObject) return; + ClearBufferRange_State(bufferObject, internalformat, 0, static_cast(bufferObject->GetSize()), format, + type, data, BufferOp::ClearBufferData); + } + + void ClearBufferSubData_State(GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, + GLenum format, GLenum type, const void* data) { + auto bufferObject = GetBoundBufferObject(target, BufferOp::ClearBufferSubData); + if (!bufferObject) return; + ClearBufferRange_State(bufferObject, internalformat, offset, size, format, type, data, + BufferOp::ClearBufferSubData); + } + void ClearNamedBufferData_State(GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void* data) { auto bufferObject = GetNamedBufferObject(buffer, BufferOp::ClearNamedBufferData); if (!bufferObject) return; - ClearNamedBufferRange_State(buffer, internalformat, 0, static_cast(bufferObject->GetSize()), format, - type, data, BufferOp::ClearNamedBufferData); + ClearBufferRange_State(bufferObject, internalformat, 0, static_cast(bufferObject->GetSize()), format, + type, data, BufferOp::ClearNamedBufferData); } void ClearNamedBufferSubData_State(GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) { - ClearNamedBufferRange_State(buffer, internalformat, offset, size, format, type, data, - BufferOp::ClearNamedBufferSubData); + auto bufferObject = GetNamedBufferObject(buffer, BufferOp::ClearNamedBufferSubData); + if (!bufferObject) return; + ClearBufferRange_State(bufferObject, internalformat, offset, size, format, type, data, + BufferOp::ClearNamedBufferSubData); } void* MapNamedBuffer_State(GLuint buffer, GLenum access) { @@ -1662,6 +1708,15 @@ namespace MobileGL::MG_Impl::GLImpl { CopyNamedBufferSubData_State(readBuffer, writeBuffer, readOffset, writeOffset, size); } + void ClearBufferData(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) { + ClearBufferData_State(target, internalformat, format, type, data); + } + + void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, + GLenum type, const void* data) { + ClearBufferSubData_State(target, internalformat, offset, size, format, type, data); + } + void ClearNamedBufferData(GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void* data) { ClearNamedBufferData_State(buffer, internalformat, format, type, data); } diff --git a/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.h b/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.h index 3a24dfc2..95b8db44 100644 --- a/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.h +++ b/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.h @@ -27,6 +27,9 @@ namespace MobileGL::MG_Impl::GLImpl { void NamedBufferSubData(GLuint buffer, GLintptr offset, GLsizeiptr size, const void* data); void CopyNamedBufferSubData(GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); + void ClearBufferData(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data); + void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, + GLenum type, const void* data); void ClearNamedBufferData(GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void* data); void ClearNamedBufferSubData(GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data); diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index 7a15d937..1be2e4b2 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -985,8 +985,8 @@ DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstancedBaseVertexBaseInstance, GLen DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveAtomicCounterBufferiv, GLuint program, GLuint bufferIndex, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetActiveAtomicCounterBufferiv, program, bufferIndex, pname, params) DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedbackInstanced, GLenum mode, GLuint id, GLsizei instancecount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedbackInstanced, mode, id, instancecount) DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedbackStreamInstanced, GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedbackStreamInstanced, mode, id, stream, instancecount) -DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferData, GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferData, target, internalformat, format, type, data) -DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferSubData, GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferSubData, target, internalformat, offset, size, format, type, data) +DECLARE_GL_FUNCTION_HEAD(void, ClearBufferData, GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearBufferData, target, internalformat, format, type, data) +DECLARE_GL_FUNCTION_HEAD(void, ClearBufferSubData, GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearBufferSubData, target, internalformat, offset, size, format, type, data) DECLARE_GL_FUNCTION_STUB_HEAD(void, GetInternalformati64v, GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint64* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetInternalformati64v, target, internalformat, pname, count, params) DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateTexSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateTexSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth) DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateTexImage, GLuint texture, GLint level) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateTexImage, texture, level) diff --git a/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp b/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp index 6e6aa649..b98176f6 100644 --- a/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp +++ b/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp @@ -250,6 +250,34 @@ namespace MobileGL::MG_State::GLState { NotifyContentWrite(atOffset, data.size); } + void BufferObject::FillSubData(DataPtr pattern, SizeT atOffset, SizeT size) { + MOBILEGL_ASSERT(pattern.data != nullptr && pattern.size > 0, + "FillSubData requires a non-empty pattern."); + MOBILEGL_ASSERT(size % pattern.size == 0, + "FillSubData size (%zu) must be a multiple of pattern size (%zu).", size, pattern.size); + MOBILEGL_ASSERT(atOffset <= m_size && size <= m_size - atOffset, + "FillSubData out of bounds: atOffset (%zu) + size (%zu) > m_size (%zu)", atOffset, size, + m_size); + MOBILEGL_ASSERT(!m_isMapped || (m_mappingAccess & BufferMappingAccessBit::Persistent), + "Cannot fill data while buffer is non-persistently mapped."); + if (size == 0) return; + + // A clear is ordered after all earlier GPU writes. Partial clears additionally need the + // retained shadow bytes; whole-store clears need the same synchronization before writing + // an adopted persistent mapping that the GPU may still be accessing. + SyncGpuWrites(); + + Uint8* dst = m_resource.Bytes() + atOffset; + if (pattern.size == 1) { + Memset(dst, *static_cast(pattern.data), size); + } else { + for (SizeT at = 0; at < size; at += pattern.size) { + Memcpy(dst + at, pattern.data, pattern.size); + } + } + NotifyContentWrite(atOffset, 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, diff --git a/MobileGL/MG_State/GLState/BufferState/BufferObject.h b/MobileGL/MG_State/GLState/BufferState/BufferObject.h index 425dd80f..cebf4daf 100644 --- a/MobileGL/MG_State/GLState/BufferState/BufferObject.h +++ b/MobileGL/MG_State/GLState/BufferState/BufferObject.h @@ -132,6 +132,9 @@ namespace MobileGL { void UploadData(DataPtr data, SizeT atOffset); void UploadSubData(DataPtr data, SizeT atOffset); + // Repeats one already-converted element through [atOffset, atOffset + size) and + // publishes the range as one content mutation. + void FillSubData(DataPtr pattern, SizeT atOffset, SizeT size); // 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. diff --git a/MobileGL/MG_Test/Buffer/BufferTest.cpp b/MobileGL/MG_Test/Buffer/BufferTest.cpp index a5312565..c4f05d56 100644 --- a/MobileGL/MG_Test/Buffer/BufferTest.cpp +++ b/MobileGL/MG_Test/Buffer/BufferTest.cpp @@ -16,6 +16,7 @@ #include #include +#include #include using namespace MobileGL; @@ -599,6 +600,117 @@ TEST_F(BufferTest, ClearNamedBufferSubDataRepeatsPattern) { EXPECT_EQ(actual, (Vector{0, pattern, pattern, pattern, 0})); EXPECT_EQ(MobileGL::MG_Impl::GLImpl::GetError(), GL_NO_ERROR); } +TEST_F(BufferTest, ClearBufferSubDataInitializesIrisStaticSsboRange) { + GLuint buffer = 0; + MobileGL::MG_Impl::GLImpl::GenBuffers(1, &buffer); + MobileGL::MG_Impl::GLImpl::BindBuffer(GL_SHADER_STORAGE_BUFFER, buffer); + + Vector initial(32, 0x7F); + MobileGL::MG_Impl::GLImpl::BufferData( + GL_SHADER_STORAGE_BUFFER, initial.size(), initial.data(), GL_STATIC_DRAW); + const GLbyte zero = 0; + const auto clear = reinterpret_cast( + MobileGL::MG_Impl::GetProcAddress("glClearBufferSubData")); + ASSERT_NE(clear, nullptr); + clear(GL_SHADER_STORAGE_BUFFER, GL_R8, 4, 24, GL_RED, GL_BYTE, &zero); + + Vector actual(initial.size()); + auto bufferObject = MobileGL::MG_State::pGLContext->GetBufferObject(buffer); + ASSERT_NE(bufferObject, nullptr); + Memcpy(actual.data(), bufferObject->AcquireMemory(false, true, false), actual.size()); + EXPECT_EQ(actual, (Vector{0x7F, 0x7F, 0x7F, 0x7F, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0x7F, 0x7F, 0x7F, 0x7F})); + EXPECT_EQ(MobileGL::MG_Impl::GLImpl::GetError(), GL_NO_ERROR); + + MobileGL::MG_Impl::GLImpl::BindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + MobileGL::MG_Impl::GLImpl::DeleteBuffers(1, &buffer); + DrainPendingGlErrors(); +} + +TEST_F(BufferTest, ClearBufferSubDataInitializesCompleteIrisStaticSsbo) { + constexpr SizeT irisStaticSsboSize = 5'000'192; + GLuint buffer = 0; + MobileGL::MG_Impl::GLImpl::GenBuffers(1, &buffer); + MobileGL::MG_Impl::GLImpl::BindBuffer(GL_SHADER_STORAGE_BUFFER, buffer); + + Vector initial(irisStaticSsboSize, 0x7F); + MobileGL::MG_Impl::GLImpl::BufferData( + GL_SHADER_STORAGE_BUFFER, initial.size(), initial.data(), GL_STATIC_DRAW); + const GLbyte zero = 0; + MobileGL::MG_Impl::GLImpl::ClearBufferSubData( + GL_SHADER_STORAGE_BUFFER, GL_R8, 0, irisStaticSsboSize, GL_RED, GL_BYTE, &zero); + + Vector actual(irisStaticSsboSize); + auto bufferObject = MobileGL::MG_State::pGLContext->GetBufferObject(buffer); + ASSERT_NE(bufferObject, nullptr); + Memcpy(actual.data(), bufferObject->AcquireMemory(false, true, false), actual.size()); + EXPECT_EQ(actual, Vector(irisStaticSsboSize, 0)); + EXPECT_EQ(MobileGL::MG_Impl::GLImpl::GetError(), GL_NO_ERROR); + + MobileGL::MG_Impl::GLImpl::BindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + MobileGL::MG_Impl::GLImpl::DeleteBuffers(1, &buffer); + DrainPendingGlErrors(); +} + +TEST_F(BufferTest, ClearBufferDataConvertsOneClientPixelBeforeRepeatingIt) { + GLuint buffer = 0; + MobileGL::MG_Impl::GLImpl::GenBuffers(1, &buffer); + MobileGL::MG_Impl::GLImpl::BindBuffer(GL_ARRAY_BUFFER, buffer); + + Vector initial(4, 0u); + MobileGL::MG_Impl::GLImpl::BufferData(GL_ARRAY_BUFFER, initial.size() * sizeof(Uint32), initial.data(), + GL_STATIC_DRAW); + const Uint8 value = 0xAB; + MobileGL::MG_Impl::GLImpl::ClearBufferData( + GL_ARRAY_BUFFER, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, &value); + + Vector actual(initial.size()); + auto bufferObject = MobileGL::MG_State::pGLContext->GetBufferObject(buffer); + ASSERT_NE(bufferObject, nullptr); + Memcpy(actual.data(), bufferObject->AcquireMemory(false, true, false), actual.size() * sizeof(Uint32)); + EXPECT_EQ(actual, Vector(initial.size(), value)); + EXPECT_EQ(MobileGL::MG_Impl::GLImpl::GetError(), GL_NO_ERROR); + + MobileGL::MG_Impl::GLImpl::BindBuffer(GL_ARRAY_BUFFER, 0); + MobileGL::MG_Impl::GLImpl::DeleteBuffers(1, &buffer); + DrainPendingGlErrors(); +} + +TEST_F(BufferTest, ClearBufferSubDataRejectsUnboundTarget) { + MobileGL::MG_Impl::GLImpl::BindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + const GLbyte zero = 0; + MobileGL::MG_Impl::GLImpl::ClearBufferSubData( + GL_SHADER_STORAGE_BUFFER, GL_R8, 0, 1, GL_RED, GL_BYTE, &zero); + ExpectSingleGlError(GL_INVALID_OPERATION); +} + +TEST_F(BufferTest, ClearBufferDataRejectsInvalidPixelFormatTypePairs) { + GLuint buffer = 0; + MobileGL::MG_Impl::GLImpl::GenBuffers(1, &buffer); + MobileGL::MG_Impl::GLImpl::BindBuffer(GL_ARRAY_BUFFER, buffer); + + const Vector initial{0x7F, 0x7F}; + MobileGL::MG_Impl::GLImpl::BufferData(GL_ARRAY_BUFFER, initial.size(), initial.data(), GL_STATIC_DRAW); + const Uint16 packed = 0; + MobileGL::MG_Impl::GLImpl::ClearBufferData( + GL_ARRAY_BUFFER, GL_R16, GL_RED, GL_UNSIGNED_SHORT_5_6_5, &packed); + ExpectSingleGlError(GL_INVALID_VALUE); + MobileGL::MG_Impl::GLImpl::ClearBufferData( + GL_ARRAY_BUFFER, GL_R16, GL_RED, GL_UNSIGNED_SHORT_5_6_5, nullptr); + ExpectSingleGlError(GL_INVALID_VALUE); + + Vector actual(initial.size()); + auto bufferObject = MobileGL::MG_State::pGLContext->GetBufferObject(buffer); + ASSERT_NE(bufferObject, nullptr); + Memcpy(actual.data(), bufferObject->AcquireMemory(false, true, false), actual.size()); + EXPECT_EQ(actual, initial); + + MobileGL::MG_Impl::GLImpl::BindBuffer(GL_ARRAY_BUFFER, 0); + MobileGL::MG_Impl::GLImpl::DeleteBuffers(1, &buffer); + DrainPendingGlErrors(); +} // GL 4.6 core 6.5: glBufferSubData fails only when the written range OVERLAPS the mapped range. diff --git a/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp b/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp index 066a0822..c52449fa 100644 --- a/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp +++ b/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp @@ -423,6 +423,26 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { InternalPackedLayout internalPacked; }; + Bool IsValidUnpackPixelPair(TextureInputFormat format, TexturePixelDataType type) { + UnpackChannelMapping mapping{}; + if (!GetUnpackChannelMapping(format, mapping)) return false; + + PackedTypeLayout packed{}; + if (GetPackedTypeLayout(type, packed)) { + return packed.fieldCount == mapping.channelCount; + } + + switch (type) { + case TexturePixelDataType::UnsignedInt5999Rev: + case TexturePixelDataType::UnsignedInt101111Rev: + return !mapping.isInteger && mapping.channelCount == 3; + default: { + ShadowComponent component{}; + return GetDirectShadowComponentForType(type, mapping.isInteger, component); + } + } + } + // Returns true when the (format, type) -> internal-format upload needs a per-texel conversion; // returns false both for layouts that already match the shadow bytes (memcpy fast path) and for // combinations the converter does not support (legacy copy behavior). @@ -964,6 +984,32 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { return outputPixels; } + Bool ConvertOnePixelToInternal(TextureInternalFormat targetInternalFormat, + TextureInputFormat textureInputFormat, + TexturePixelDataType inputDataType, + const void* inputPixel, + Vector& outputPixel) { + outputPixel.clear(); + if (inputPixel == nullptr || !IsValidUnpackPixelPair(textureInputFormat, inputDataType)) return false; + + PixelStoreParameters params{}; + params.Alignment = 1; + SizeT convertedSize = 0; + void* converted = ProcessTexturePixelsDataUnpack( + inputPixel, params, targetInternalFormat, textureInputFormat, inputDataType, {1, 1, 1}, false, + convertedSize); + const SizeT expectedSize = MG_Util::GetSizedInternalFormatSizeInBytes(targetInternalFormat); + if (converted == nullptr || convertedSize != expectedSize || expectedSize == 0) { + if (converted != nullptr) free(converted); + return false; + } + + outputPixel.resize(convertedSize); + Memcpy(outputPixel.data(), converted, convertedSize); + free(converted); + return true; + } + void* ProcessTexturePixelsDataPack(const void* inputPixels, const PixelStoreParameters& params, TextureInternalFormat srcInternalFormat, TexturePixelDataType srcDataType, TextureInputFormat dstInputFormat, TexturePixelDataType dstDataType, diff --git a/MobileGL/MG_Util/Texture/PixelStoreProcessor.h b/MobileGL/MG_Util/Texture/PixelStoreProcessor.h index 7d3d683d..91169739 100644 --- a/MobileGL/MG_Util/Texture/PixelStoreProcessor.h +++ b/MobileGL/MG_Util/Texture/PixelStoreProcessor.h @@ -21,6 +21,12 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { TextureInternalFormat srcInternalFormat, TexturePixelDataType srcDataType, TextureInputFormat dstInputFormat, TexturePixelDataType dstDataType, IntVec3 dimension, Bool isBitmap, SizeT& outSize); + Bool ConvertOnePixelToInternal(TextureInternalFormat targetInternalFormat, + TextureInputFormat textureInputFormat, + TexturePixelDataType inputDataType, + const void* inputPixel, + Vector& outputPixel); + void ProcessColorSwizzle(void* data, SizeT pixelCount, const Vector& swizzle); // True when a packed internal format's 32-bit storage word IS the client (format, type) word,