From dac02ca044859f75edbc762aba09450935fdb94a Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Wed, 5 Aug 2026 02:16:48 +0000 Subject: [PATCH] [Feat] (MG_Impl, MG_State): implement the direct state access transform feedback API glCreateTransformFeedbacks, glTransformFeedbackBufferBase, glTransformFeedbackBufferRange and the three glGetTransformFeedback* queries were all stubs, so a transform feedback object could only be configured and inspected by binding it first - the exact thing direct state access exists to avoid. The queries were the worse half: they returned nothing and raised no error, so an application could not tell that it had learned nothing. glCreateTransformFeedbacks creates the objects outright. glGenTransformFeedbacks only reserves names, and a reserved name becomes an object when it is first bound (GL 4.6 core 13.2.1); the DSA form has no bind step to create them from. The queries and the buffer bindings read and write a named object's state. That state lives in two places: the context keeps one live copy of the capture bindings and the active/paused flags for whichever object is bound, and every other object's copy sits in its saved state until a bind swaps it in. The by-name accessors added to the context resolve that, so a query for the bound object reads the live copy rather than a stale save. GL_TRANSFORM_FEEDBACK_BUFFER_START and _SIZE are answered as zero unless the binding was made by the range form, matching what the buffer object binding points already do. Takes direct_state_access.xfb_* from 0 to 4 of 5 on both backends; xfb_functional still fails on the capture itself, which is a separate defect. --- .../MG_Impl/GLImpl/Drawing/GL_Drawing.cpp | 166 ++++++++++++++++++ MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h | 6 + .../MG_Impl/GLImpl/Exporting/Definitions.cpp | 12 +- .../GLState/BufferState/BufferState.h | 3 + MobileGL/MG_State/GLState/Core.cpp | 58 ++++++ MobileGL/MG_State/GLState/Core.h | 19 ++ 6 files changed, 258 insertions(+), 6 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index e34c8156..f64d9a92 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -877,6 +877,172 @@ namespace MobileGL::MG_Impl::GLImpl { Memcpy(ids, names.data(), static_cast(n) * sizeof(GLuint)); } + void CreateTransformFeedbacks(GLsizei n, GLuint* ids) { + if (n < 0) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, "n must be non-negative.")); + return; + } + if (n == 0 || ids == nullptr) return; + Vector names; + MG_State::pGLContext->GenTransformFeedbackNames(static_cast(n), names); + // Unlike glGenTransformFeedbacks, the names are objects immediately: there is no bind step + // to create them from (GL 4.6 core 13.2.1). + for (const Uint name : names) { + MG_State::pGLContext->CreateTransformFeedbackObject(name); + } + Memcpy(ids, names.data(), static_cast(n) * sizeof(GLuint)); + } + + namespace { + // Shared front half of the by-name transform feedback entry points: the object has to exist + // (INVALID_OPERATION otherwise) before anything else about the call is looked at. + Bool ValidateNamedTransformFeedback(GLuint xfb, const char* functionName) { + if (!MG_State::pGLContext->IsTransformFeedbackObject(xfb)) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", functionName, + std::to_string(xfb) + " is not a transform feedback object.")); + return false; + } + return true; + } + + Bool ValidateTransformFeedbackBufferIndex(GLuint index, const char* functionName) { + if (index >= MG_State::GLState::GLContext::MAX_TRANSFORM_FEEDBACK_BUFFERS) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", functionName, + "index exceeds GL_MAX_TRANSFORM_FEEDBACK_BUFFERS.")); + return false; + } + return true; + } + + // A capture binding may not be changed while the object is capturing (GL 4.6 core 13.2.2). + Bool ValidateNamedTransformFeedbackNotActive(GLuint xfb, const char* functionName) { + if (MG_State::pGLContext->IsNamedTransformFeedbackActive(xfb)) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", functionName, + "The transform feedback object is capturing.")); + return false; + } + return true; + } + + SharedPtr ResolveTransformFeedbackBuffer(GLuint buffer, + const char* functionName) { + if (buffer == 0) return nullptr; + if (!MG_State::pGLContext->ValidateBufferName(buffer)) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", functionName, + std::to_string(buffer) + " is not a buffer object.")); + return nullptr; + } + return MG_State::pGLContext->GetBufferObject(buffer); + } + } // namespace + + void TransformFeedbackBufferBase(GLuint xfb, GLuint index, GLuint buffer) { + if (!ValidateNamedTransformFeedback(xfb, __func__)) return; + if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return; + if (!ValidateNamedTransformFeedbackNotActive(xfb, __func__)) return; + if (buffer != 0 && !MG_State::pGLContext->ValidateBufferName(buffer)) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", __func__, + std::to_string(buffer) + " is not a buffer object.")); + return; + } + MG_State::pGLContext->SetNamedTransformFeedbackBinding(xfb, index, + ResolveTransformFeedbackBuffer(buffer, __func__), {}, + false); + } + + void TransformFeedbackBufferRange(GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) { + if (!ValidateNamedTransformFeedback(xfb, __func__)) return; + if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return; + if (!ValidateNamedTransformFeedbackNotActive(xfb, __func__)) return; + if (offset < 0 || size <= 0 || (offset % 4) != 0 || (size % 4) != 0) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, + "offset and size must be non-negative multiples of 4.")); + return; + } + if (buffer != 0 && !MG_State::pGLContext->ValidateBufferName(buffer)) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", __func__, + std::to_string(buffer) + " is not a buffer object.")); + return; + } + auto bufferObject = ResolveTransformFeedbackBuffer(buffer, __func__); + const Range1D range{static_cast(offset), static_cast(offset) + static_cast(size)}; + MG_State::pGLContext->SetNamedTransformFeedbackBinding(xfb, index, bufferObject, range, + bufferObject != nullptr); + } + + void GetTransformFeedbackiv(GLuint xfb, GLenum pname, GLint* param) { + if (!ValidateNamedTransformFeedback(xfb, __func__)) return; + if (!param) return; + switch (pname) { + case GL_TRANSFORM_FEEDBACK_ACTIVE: + *param = MG_State::pGLContext->IsNamedTransformFeedbackActive(xfb) ? GL_TRUE : GL_FALSE; + return; + case GL_TRANSFORM_FEEDBACK_PAUSED: + *param = MG_State::pGLContext->IsNamedTransformFeedbackPaused(xfb) ? GL_TRUE : GL_FALSE; + return; + default: + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", __func__, + "pname must be GL_TRANSFORM_FEEDBACK_ACTIVE or _PAUSED.")); + return; + } + } + + void GetTransformFeedbacki_v(GLuint xfb, GLenum pname, GLuint index, GLint* param) { + if (!ValidateNamedTransformFeedback(xfb, __func__)) return; + if (pname != GL_TRANSFORM_FEEDBACK_BUFFER_BINDING) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", __func__, + "pname must be GL_TRANSFORM_FEEDBACK_BUFFER_BINDING.")); + return; + } + if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return; + if (!param) return; + const auto binding = MG_State::pGLContext->GetNamedTransformFeedbackBinding(xfb, index); + *param = binding.Buffer ? static_cast(binding.Buffer->GetExternalIndex()) : 0; + } + + void GetTransformFeedbacki64_v(GLuint xfb, GLenum pname, GLuint index, GLint64* param) { + if (!ValidateNamedTransformFeedback(xfb, __func__)) return; + if (pname != GL_TRANSFORM_FEEDBACK_BUFFER_START && pname != GL_TRANSFORM_FEEDBACK_BUFFER_SIZE) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", __func__, + "pname must be GL_TRANSFORM_FEEDBACK_BUFFER_START or _SIZE.")); + return; + } + if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return; + if (!param) return; + const auto binding = MG_State::pGLContext->GetNamedTransformFeedbackBinding(xfb, index); + // glTransformFeedbackBufferBase leaves both at zero; only the range form sets them + // (GL 4.6 core table 23.48). + if (!binding.Buffer || !binding.HasExplicitRange) { + *param = 0; + return; + } + *param = (pname == GL_TRANSFORM_FEEDBACK_BUFFER_START) + ? static_cast(binding.Range.start) + : static_cast(binding.Range.end - binding.Range.start); + } + void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids) { if (n < 0) { MG_State::pGLContext->RecordError( diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h index 94fe61cb..e9228610 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h @@ -16,7 +16,13 @@ namespace MobileGL::MG_Impl::GLImpl { void PauseTransformFeedback(void); void ResumeTransformFeedback(void); void GenTransformFeedbacks(GLsizei n, GLuint* ids); + void CreateTransformFeedbacks(GLsizei n, GLuint* ids); void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids); + void TransformFeedbackBufferBase(GLuint xfb, GLuint index, GLuint buffer); + void TransformFeedbackBufferRange(GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); + void GetTransformFeedbackiv(GLuint xfb, GLenum pname, GLint* param); + void GetTransformFeedbacki_v(GLuint xfb, GLenum pname, GLuint index, GLint* param); + void GetTransformFeedbacki64_v(GLuint xfb, GLenum pname, GLuint index, GLint64* param); void BindTransformFeedback(GLenum target, GLuint id); GLboolean IsTransformFeedback(GLuint id); void DrawTransformFeedback(GLenum mode, GLuint id); diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index cc66abbb..5a688d77 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -1007,12 +1007,12 @@ DECLARE_GL_FUNCTION_HEAD(void, BindSamplers, GLuint first, GLsizei count, const DECLARE_GL_FUNCTION_STUB_HEAD(void, BindImageTextures, GLuint first, GLsizei count, const GLuint* textures) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindImageTextures, first, count, textures) DECLARE_GL_FUNCTION_HEAD(void, BindVertexBuffers, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets, const GLsizei* strides) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindVertexBuffers, first, count, buffers, offsets, strides) DECLARE_GL_FUNCTION_STUB_HEAD(void, ClipControl, GLenum origin, GLenum depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClipControl, origin, depth) -DECLARE_GL_FUNCTION_STUB_HEAD(void, CreateTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CreateTransformFeedbacks, n, ids) -DECLARE_GL_FUNCTION_STUB_HEAD(void, TransformFeedbackBufferBase, GLuint xfb, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TransformFeedbackBufferBase, xfb, index, buffer) -DECLARE_GL_FUNCTION_STUB_HEAD(void, TransformFeedbackBufferRange, GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TransformFeedbackBufferRange, xfb, index, buffer, offset, size) -DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbackiv, GLuint xfb, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbackiv, xfb, pname, param) -DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbacki_v, GLuint xfb, GLenum pname, GLuint index, GLint* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbacki_v, xfb, pname, index, param) -DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbacki64_v, GLuint xfb, GLenum pname, GLuint index, GLint64* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbacki64_v, xfb, pname, index, param) +DECLARE_GL_FUNCTION_HEAD(void, CreateTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateTransformFeedbacks, n, ids) +DECLARE_GL_FUNCTION_HEAD(void, TransformFeedbackBufferBase, GLuint xfb, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TransformFeedbackBufferBase, xfb, index, buffer) +DECLARE_GL_FUNCTION_HEAD(void, TransformFeedbackBufferRange, GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TransformFeedbackBufferRange, xfb, index, buffer, offset, size) +DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbackiv, GLuint xfb, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbackiv, xfb, pname, param) +DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbacki_v, GLuint xfb, GLenum pname, GLuint index, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbacki_v, xfb, pname, index, param) +DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbacki64_v, GLuint xfb, GLenum pname, GLuint index, GLint64* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbacki64_v, xfb, pname, index, param) DECLARE_GL_FUNCTION_HEAD(void, CreateBuffers, GLsizei n, GLuint* buffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateBuffers, n, buffers) DECLARE_GL_FUNCTION_HEAD(void, NamedBufferStorage, GLuint buffer, GLsizeiptr size, const void* data, GLbitfield flags) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedBufferStorage, buffer, size, data, flags) DECLARE_GL_FUNCTION_HEAD(void, NamedBufferData, GLuint buffer, GLsizeiptr size, const void* data, GLenum usage) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedBufferData, buffer, size, data, usage) diff --git a/MobileGL/MG_State/GLState/BufferState/BufferState.h b/MobileGL/MG_State/GLState/BufferState/BufferState.h index 0fa94d68..efff69da 100644 --- a/MobileGL/MG_State/GLState/BufferState/BufferState.h +++ b/MobileGL/MG_State/GLState/BufferState/BufferState.h @@ -31,6 +31,9 @@ namespace MobileGL::MG_State::GLState { BindingSlot& GetBindingSlot(BufferTarget target); // For glBindBufferBase / glBindBufferRange BindingSlotRange1D& GetBindingPoint(BufferTarget target, Uint index); + const BindingSlotRange1D& GetBindingPoint(BufferTarget target, Uint index) const { + return const_cast(this)->GetBindingPoint(target, index); + } constexpr SizeT GetBindingPointCount(const BufferTarget target) const { auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target); auto index = std::distance(BufferBindPointTargets.begin(), it); diff --git a/MobileGL/MG_State/GLState/Core.cpp b/MobileGL/MG_State/GLState/Core.cpp index 4894d792..041a29fc 100644 --- a/MobileGL/MG_State/GLState/Core.cpp +++ b/MobileGL/MG_State/GLState/Core.cpp @@ -842,6 +842,64 @@ namespace MobileGL::MG_State { const auto it = m_transformFeedbackObjects.find(index); return it != m_transformFeedbackObjects.end() && it->second.hasCompletedSpan; } + + void GLContext::CreateTransformFeedbackObject(Uint index) { + // glCreateTransformFeedbacks has no bind step to infer existence from, so the name it + // hands out is already the name of an object (GL 4.6 core 13.2.1). + m_transformFeedbackObjects[index] = {}; + m_transformFeedbackObjects[index].everBound = true; + } + + Bool GLContext::IsNamedTransformFeedbackActive(Uint index) const { + if (index == m_boundTransformFeedback) return m_transformFeedbackActive; + const auto it = m_transformFeedbackObjects.find(index); + return it != m_transformFeedbackObjects.end() && it->second.active; + } + + Bool GLContext::IsNamedTransformFeedbackPaused(Uint index) const { + if (index == m_boundTransformFeedback) return m_transformFeedbackPaused; + const auto it = m_transformFeedbackObjects.find(index); + return it != m_transformFeedbackObjects.end() && it->second.paused; + } + + NamedTransformFeedbackBinding GLContext::GetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex) const { + NamedTransformFeedbackBinding result; + if (bufferIndex >= MAX_TRANSFORM_FEEDBACK_BUFFERS) return result; + // The bound object's capture bindings live in the context's own binding points, not in + // the saved copy - that one is only written when the object is swapped out. + if (index == m_boundTransformFeedback) { + const auto& point = m_bufferState.GetBindingPoint(BufferTarget::TransformFeedback, bufferIndex); + result.Buffer = point.GetBoundObject(); + result.Range = point.GetRange(); + result.HasExplicitRange = point.HasExplicitRange(); + return result; + } + const auto it = m_transformFeedbackObjects.find(index); + if (it == m_transformFeedbackObjects.end()) return result; + const auto& saved = it->second.bindings[bufferIndex]; + result.Buffer = saved.buffer; + result.Range = saved.range; + result.HasExplicitRange = saved.hasExplicitRange; + return result; + } + + void GLContext::SetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex, + const SharedPtr& buffer, Range1D range, + Bool hasExplicitRange) { + if (bufferIndex >= MAX_TRANSFORM_FEEDBACK_BUFFERS) return; + if (index == m_boundTransformFeedback) { + auto& point = m_bufferState.GetBindingPoint(BufferTarget::TransformFeedback, bufferIndex); + point.Bind(buffer); + if (buffer && hasExplicitRange) { + point.SetRange(range, true); + } else { + point.ClearRange(); + } + return; + } + auto& object = m_transformFeedbackObjects[index]; + object.bindings[bufferIndex] = {buffer, range, hasExplicitRange}; + } } // namespace GLState // Leak-at-exit storage; see GlobalObjects.cpp. diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index a84d7ec2..69d042f6 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -45,6 +45,14 @@ namespace MobileGL { // translates the result into its own API call. VertexAttribTypeInfo ClassifyVertexAttribType(GLenum glType); + // One indexed capture binding of a transform feedback object, as the by-name queries + // report it. An empty Buffer means the binding point is unbound. + struct NamedTransformFeedbackBinding { + SharedPtr Buffer; + Range1D Range{}; + Bool HasExplicitRange = false; + }; + class GLContext { public: GLContext() = default; @@ -299,6 +307,17 @@ namespace MobileGL { // cannot express: an empty completed span is legal and draws nothing. Bool HasTransformFeedbackCompletedSpan(Uint index) const; + // The by-name (direct state access) view. A named object that happens to be the + // bound one is answered from the live copy, since that is where its state actually + // is until a bind swaps it out. + void CreateTransformFeedbackObject(Uint index); + Bool IsNamedTransformFeedbackActive(Uint index) const; + Bool IsNamedTransformFeedbackPaused(Uint index) const; + NamedTransformFeedbackBinding GetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex) const; + void SetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex, + const SharedPtr& buffer, Range1D range, + Bool hasExplicitRange); + // Framebuffer void GenFramebufferNames(Uint number, Vector& framebuffers); const SharedPtr& GetFramebufferObject(Uint index);