// MobileGL - MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp // Copyright (c) 2025-2026 MobileGL-Dev // Licensed under the GNU Lesser General Public License v3.0: // https://www.gnu.org/licenses/gpl-3.0.txt // https://www.gnu.org/licenses/lgpl-3.0.txt // SPDX-License-Identifier: LGPL-3.0-only // End of Source File Header #include "GL_Drawing.h" #include #include #include #include #include "../Getter/GL_Getter.h" namespace MobileGL::MG_Impl::GLImpl { static Bool ValidateCurrentProgramForExecution(const char* functionName) { const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw(); if (!currentProgram) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", functionName, "There is no current program object.")); return false; } if (!currentProgram->GetLinkStatus()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", functionName, "The current program object is not linked.")); return false; } return true; } static Bool ValidateCurrentProgramForCompute(const char* functionName) { if (!ValidateCurrentProgramForExecution(functionName)) return false; const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw(); if (currentProgram->GetShaderIndexByStage(ShaderStage::Compute) < 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", functionName, "The current program object has no compute shader stage.")); return false; } return true; } // Primitives a draw of `count` vertices in `mode` assembles (0 for // incomplete primitives). Used for the CPU-side transform feedback // primitive accounting. static Uint64 CountPrimitivesForDraw(GLenum mode, GLsizei count) { if (count <= 0) return 0; switch (mode) { case GL_POINTS: return static_cast(count); case GL_LINES: return static_cast(count / 2); case GL_LINE_STRIP: return count >= 2 ? static_cast(count - 1) : 0; case GL_LINE_LOOP: return count >= 2 ? static_cast(count) : 0; case GL_TRIANGLES: return static_cast(count / 3); case GL_TRIANGLE_STRIP: case GL_TRIANGLE_FAN: return count >= 3 ? static_cast(count - 2) : 0; default: return 0; } } // Accumulate the transform feedback primitive counter for a captured draw. // Draws without a geometry stage write exactly the primitives they assemble, // clamped by the capture buffers' remaining capacity (a full buffer stops // recording whole primitives, which is what PRIMITIVES_WRITTEN reports). // Geometry amplification is not modelled here. static void AccountTransformFeedbackPrimitives(GLenum mode, GLsizei count) { if (!MG_State::pGLContext->IsTransformFeedbackActive()) return; // A paused span captures nothing, so a draw made while paused contributes to // PRIMITIVES_GENERATED but not to TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN. if (MG_State::pGLContext->IsTransformFeedbackPaused()) { MG_State::pGLContext->AddTransformFeedbackPausedPrimitives(CountPrimitivesForDraw(mode, count)); return; } Uint64 primitives = CountPrimitivesForDraw(mode, count); if (primitives == 0) return; MG_State::pGLContext->AddTransformFeedbackInputPrimitives(primitives); Uint64 verticesPerPrimitive = 1; switch (mode) { case GL_LINES: case GL_LINE_STRIP: case GL_LINE_LOOP: verticesPerPrimitive = 2; break; case GL_TRIANGLES: case GL_TRIANGLE_STRIP: case GL_TRIANGLE_FAN: verticesPerPrimitive = 3; break; default: break; } const auto& program = MG_State::pGLContext->GetTransformFeedbackProgram(); if (program != nullptr) { // Capacity in captured vertices = the tightest bound buffer. Uint64 capacityVertices = ~0ull; for (SizeT i = 0; i < program->GetTransformFeedbackBufferCount(); ++i) { const Uint32 stride = program->GetTransformFeedbackStride(static_cast(i)); if (stride == 0) continue; const auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback, static_cast(i)); const Range1D range = point.GetRange(); const Uint64 bytes = range.end > range.start ? static_cast(range.end - range.start) : 0; capacityVertices = std::min(capacityVertices, bytes / stride); } if (capacityVertices != ~0ull) { const Uint64 usedVertices = MG_State::pGLContext->GetTransformFeedbackCapturedVertices(); const Uint64 remainingVertices = capacityVertices > usedVertices ? capacityVertices - usedVertices : 0; primitives = std::min(primitives, remainingVertices / verticesPerPrimitive); } } MG_State::pGLContext->AddTransformFeedbackPrimitives(primitives); MG_State::pGLContext->AddTransformFeedbackCapturedVertices(primitives * verticesPerPrimitive); } // Every primitive mode a draw command accepts (GL 4.6 core table 10.1, plus // GL_PATCHES for the tessellation pipeline). Anything else is GL_INVALID_ENUM. static Bool IsAcceptedPrimitiveMode(GLenum mode) { switch (mode) { case GL_POINTS: case GL_LINES: case GL_LINE_LOOP: case GL_LINE_STRIP: case GL_LINES_ADJACENCY: case GL_LINE_STRIP_ADJACENCY: case GL_TRIANGLES: case GL_TRIANGLE_STRIP: case GL_TRIANGLE_FAN: case GL_TRIANGLES_ADJACENCY: case GL_TRIANGLE_STRIP_ADJACENCY: case GL_PATCHES: return true; default: return false; } } static Bool ValidatePrimitiveModeForBackend(const char* functionName, GLenum mode) { if (!IsAcceptedPrimitiveMode(mode)) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", functionName, "mode is not an accepted primitive type.")); return false; } const auto& activeBackendObject = MG_Backend::pActiveBackendObject; if (!activeBackendObject) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", functionName, "No active backend object.")); return false; } const auto& vao = MG_State::pGLContext->GetBoundVertexArray(); if (vao && vao->GetExternalIndex() == 0 && !MG_State::IsRelaxedSemanticsActive()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", functionName, "Default vertex array object cannot be used for drawing in core profile.")); return false; } // A geometry stage only accepts the primitive types that decompose into its declared // input primitive (GL 4.6 core 11.3.1); anything else is INVALID_OPERATION. GL_PATCHES // is the tessellation pipeline's input and reaches the geometry stage already // converted, so it is not constrained here. const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw(); const GLenum gsInput = currentProgram ? currentProgram->GetGeometryInputType() : GL_NONE; if (gsInput != GL_NONE && mode != GL_PATCHES) { Bool compatible = false; switch (gsInput) { case GL_POINTS: compatible = mode == GL_POINTS; break; case GL_LINES: compatible = mode == GL_LINES || mode == GL_LINE_STRIP || mode == GL_LINE_LOOP; break; case GL_LINES_ADJACENCY: compatible = mode == GL_LINES_ADJACENCY || mode == GL_LINE_STRIP_ADJACENCY; break; case GL_TRIANGLES: compatible = mode == GL_TRIANGLES || mode == GL_TRIANGLE_STRIP || mode == GL_TRIANGLE_FAN; break; case GL_TRIANGLES_ADJACENCY: compatible = mode == GL_TRIANGLES_ADJACENCY || mode == GL_TRIANGLE_STRIP_ADJACENCY; break; default: break; } if (!compatible) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique( "MG_Impl/GLImpl", functionName, "Primitive mode is incompatible with the geometry shader's input primitive type.")); return false; } } // While transform feedback is active the draw's primitive type must match // the feedback primitive mode (GL 3.3 core 13.2.2). With a geometry shader // the constraint moves to the shader's output primitive type instead, so // the draw mode itself is unconstrained here. A paused span is exempt: it // captures nothing, so there is nothing for the mode to be incompatible with // (GL 4.6 core 13.2.3). if (MG_State::pGLContext->IsTransformFeedbackActive() && !MG_State::pGLContext->IsTransformFeedbackPaused() && !(MG_State::pGLContext->GetTransformFeedbackProgram() && MG_State::pGLContext->GetTransformFeedbackProgram()->GetShaderIndexByStage(ShaderStage::Geometry) >= 0)) { const GLenum feedbackMode = MG_State::pGLContext->GetTransformFeedbackPrimitiveMode(); Bool compatible = false; switch (feedbackMode) { case GL_POINTS: compatible = mode == GL_POINTS; break; case GL_LINES: compatible = mode == GL_LINES || mode == GL_LINE_STRIP || mode == GL_LINE_LOOP; break; case GL_TRIANGLES: compatible = mode == GL_TRIANGLES || mode == GL_TRIANGLE_STRIP || mode == GL_TRIANGLE_FAN; break; default: break; } if (!compatible) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique( "MG_Impl/GLImpl", functionName, "Primitive mode is incompatible with the active transform feedback primitive mode.")); return false; } } return true; } // Byte size of the command structures the indirect draws read (GL 4.6 core 10.3.10). constexpr SizeT kDrawArraysIndirectCommandBytes = 4 * sizeof(Uint32); constexpr SizeT kDrawElementsIndirectCommandBytes = 5 * sizeof(Uint32); // Shared preconditions of every *Indirect draw: `indirect` is a byte offset into the // buffer bound to GL_DRAW_INDIRECT_BUFFER, must be 4-byte aligned, and the whole // command has to lie inside that buffer. static Bool ValidateIndirectDrawSource(const char* functionName, const void* indirect, SizeT commandBytes) { const auto offset = reinterpret_cast(indirect); if (offset % 4 != 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", functionName, "indirect offset must be a multiple of 4.")); return false; } const auto& buffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); if (!buffer) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", functionName, "No buffer is bound to GL_DRAW_INDIRECT_BUFFER.")); return false; } if (offset + commandBytes > buffer->GetSize()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", functionName, "The indirect command extends past the end of the bound " "GL_DRAW_INDIRECT_BUFFER.")); return false; } return true; } // Index type accepted by the DrawElements family (GL 4.6 core 10.3.9). static Bool ValidateDrawElementsIndexType(const char* functionName, GLenum type) { switch (type) { case GL_UNSIGNED_BYTE: case GL_UNSIGNED_SHORT: case GL_UNSIGNED_INT: return true; default: MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", functionName, "type is not an accepted index type.")); return false; } } void Clear_Backend(GLbitfield mask) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_Backend::gBackendFunctionsTable.GL.Clear(mask); } void DrawElements_Backend(GLenum mode, GLsizei count, GLenum type, const void* indices) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_Backend::gBackendFunctionsTable.GL.DrawElements(mode, count, type, indices); } void MultiDrawElements_Backend(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices, GLsizei drawcount) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_Backend::gBackendFunctionsTable.GL.MultiDrawElements(mode, count, type, indices, drawcount); } void MultiDrawElementsBaseVertex_Backend(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices, GLsizei drawcount, const GLint* basevertex) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_Backend::gBackendFunctionsTable.GL.MultiDrawElementsBaseVertex(mode, count, type, indices, drawcount, basevertex); } void DrawArrays_Backend(GLenum mode, GLint first, GLsizei count) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif 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 ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_Backend::gBackendFunctionsTable.GL.DrawElementsBaseVertex(mode, count, type, indices, basevertex); } void MultiDrawElementsIndirect_Backend(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_Backend::gBackendFunctionsTable.GL.MultiDrawElementsIndirect(mode, type, indirect, drawcount, stride); } void MultiDrawArraysIndirect_Backend(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_Backend::gBackendFunctionsTable.GL.MultiDrawArraysIndirect(mode, indirect, drawcount, stride); } void MultiDrawElementsIndirectCount_Backend(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_Backend::gBackendFunctionsTable.GL.MultiDrawElementsIndirectCount(mode, type, indirect, drawcount, maxdrawcount, stride); } void MultiDrawArraysIndirectCount_Backend(GLenum mode, const void* indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_Backend::gBackendFunctionsTable.GL.MultiDrawArraysIndirectCount(mode, indirect, drawcount, maxdrawcount, stride); } void DrawRangeElementsBaseVertex_Backend(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices, GLint basevertex) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_Backend::gBackendFunctionsTable.GL.DrawRangeElementsBaseVertex(mode, start, end, count, type, indices, basevertex); } void DrawRangeElements_Backend(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_Backend::gBackendFunctionsTable.GL.DrawRangeElements(mode, start, end, count, type, indices); } void DrawElementsInstancedBaseVertexBaseInstance_Backend(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_Backend::gBackendFunctionsTable.GL.DrawElementsInstancedBaseVertexBaseInstance( mode, count, type, indices, instancecount, basevertex, baseinstance); } void DrawElementsInstancedBaseVertex_Backend(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLint basevertex) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_Backend::gBackendFunctionsTable.GL.DrawElementsInstancedBaseVertex(mode, count, type, indices, instancecount, basevertex); } void DrawElementsInstancedBaseInstance_Backend(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLuint baseinstance) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_Backend::gBackendFunctionsTable.GL.DrawElementsInstancedBaseInstance(mode, count, type, indices, instancecount, baseinstance); } void DrawElementsInstanced_Backend(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_Backend::gBackendFunctionsTable.GL.DrawElementsInstanced(mode, count, type, indices, instancecount); } void DrawElementsIndirect_Backend(GLenum mode, GLenum type, const void* indirect) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_Backend::gBackendFunctionsTable.GL.DrawElementsIndirect(mode, type, indirect); } void DrawArraysInstancedBaseInstance_Backend(GLenum mode, GLint first, GLsizei count, GLsizei instancecount, GLuint baseinstance) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_Backend::gBackendFunctionsTable.GL.DrawArraysInstancedBaseInstance(mode, first, count, instancecount, baseinstance); } void DrawArraysInstanced_Backend(GLenum mode, GLint first, GLsizei count, GLsizei instancecount) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_Backend::gBackendFunctionsTable.GL.DrawArraysInstanced(mode, first, count, instancecount); } void DrawArraysIndirect_Backend(GLenum mode, const void* indirect) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif MG_Backend::gBackendFunctionsTable.GL.DrawArraysIndirect(mode, indirect); } /* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */ void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ) { auto dispatchCompute = MG_Backend::gBackendFunctionsTable.GL.DispatchCompute; if (!dispatchCompute) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, "Backend does not support compute dispatch.")); return; } if (!ValidateCurrentProgramForCompute(__func__)) return; // GL 4.6 core 19: each num_groups_* must be within GL_MAX_COMPUTE_WORK_GROUP_COUNT // for its dimension. GetIntegeri_v already floors that at the spec minimum. const GLuint numGroups[3] = {numGroupsX, numGroupsY, numGroupsZ}; for (GLuint dimension = 0; dimension < 3; ++dimension) { GLint maxGroups = 0; GetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, dimension, &maxGroups); if (numGroups[dimension] > static_cast(std::max(maxGroups, 0))) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", __func__, "num_groups exceeds GL_MAX_COMPUTE_WORK_GROUP_COUNT for dimension " + std::to_string(dimension) + ".")); return; } } dispatchCompute(numGroupsX, numGroupsY, numGroupsZ); } void DispatchComputeIndirect(GLintptr indirect) { auto dispatchComputeIndirect = MG_Backend::gBackendFunctionsTable.GL.DispatchComputeIndirect; if (!dispatchComputeIndirect) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, "Backend does not support indirect compute dispatch.")); return; } if (!ValidateCurrentProgramForCompute(__func__)) return; // GL 4.6 core 19: `indirect` is a byte offset into GL_DISPATCH_INDIRECT_BUFFER - // negative or misaligned is INVALID_VALUE, nothing bound is INVALID_OPERATION. if (indirect < 0 || (indirect % 4) != 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", __func__, "indirect must be non-negative and a multiple of 4.")); return; } const auto& indirectBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DispatchIndirect).GetBoundObject(); if (!indirectBuffer) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, "No buffer is bound to GL_DISPATCH_INDIRECT_BUFFER.")); return; } dispatchComputeIndirect(indirect); } void PatchParameteri(GLenum pname, GLint value) { if (pname != GL_PATCH_VERTICES) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", __func__, "pname must be GL_PATCH_VERTICES.")); return; } GLint maxPatchVertices = 32; GetIntegerv(GL_MAX_PATCH_VERTICES, &maxPatchVertices); if (value <= 0 || value > maxPatchVertices) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", __func__, "value must be in [1, GL_MAX_PATCH_VERTICES].")); return; } MG_State::pGLContext->SetPatchVertices(static_cast(value)); if (const auto patchParameteri = MG_Backend::gBackendFunctionsTable.GL.PatchParameteri) { patchParameteri(pname, value); } } void MemoryBarrier(GLbitfield barriers) { auto memoryBarrier = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrier; if (!memoryBarrier) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, "Backend does not support memory barriers.")); return; } memoryBarrier(barriers); } void MemoryBarrierByRegion(GLbitfield barriers) { auto memoryBarrierByRegion = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrierByRegion; if (!memoryBarrierByRegion) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, "Backend does not support regional memory barriers.")); return; } memoryBarrierByRegion(barriers); } void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) { if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; MultiDrawElementsIndirect_Backend(mode, type, indirect, drawcount, stride); } void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) { if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; MultiDrawArraysIndirect_Backend(mode, indirect, drawcount, stride); } void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride) { auto multiDrawElementsIndirectCount = MG_Backend::gBackendFunctionsTable.GL.MultiDrawElementsIndirectCount; if (!multiDrawElementsIndirectCount) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, "Backend does not support indirect-parameter indexed draws.")); return; } MultiDrawElementsIndirectCount_Backend(mode, type, indirect, drawcount, maxdrawcount, stride); } void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride) { auto multiDrawArraysIndirectCount = MG_Backend::gBackendFunctionsTable.GL.MultiDrawArraysIndirectCount; if (!multiDrawArraysIndirectCount) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, "Backend does not support indirect-parameter array draws.")); return; } MultiDrawArraysIndirectCount_Backend(mode, indirect, drawcount, maxdrawcount, stride); } void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices, GLint basevertex) { if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; DrawRangeElementsBaseVertex_Backend(mode, start, end, count, type, indices, basevertex); } void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices) { if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; DrawRangeElements_Backend(mode, start, end, count, type, indices); } void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance) { if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; DrawElementsInstancedBaseVertexBaseInstance_Backend(mode, count, type, indices, instancecount, basevertex, baseinstance); } void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLint basevertex) { if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; DrawElementsInstancedBaseVertex_Backend(mode, count, type, indices, instancecount, basevertex); } void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLuint baseinstance) { if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; DrawElementsInstancedBaseInstance_Backend(mode, count, type, indices, instancecount, baseinstance); } void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount) { if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; DrawElementsInstanced_Backend(mode, count, type, indices, instancecount); } void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) { if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; if (!ValidateDrawElementsIndexType(__func__, type)) return; if (!ValidateIndirectDrawSource(__func__, indirect, kDrawElementsIndirectCommandBytes)) return; DrawElementsIndirect_Backend(mode, type, indirect); } void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount, GLuint baseinstance) { if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; DrawArraysInstancedBaseInstance_Backend(mode, first, count, instancecount, baseinstance); } void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount) { if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; DrawArraysInstanced_Backend(mode, first, count, instancecount); } void DrawArraysIndirect(GLenum mode, const void* indirect) { if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; if (!ValidateIndirectDrawSource(__func__, indirect, kDrawArraysIndirectCommandBytes)) return; DrawArraysIndirect_Backend(mode, indirect); } void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex) { if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; AccountTransformFeedbackPrimitives(mode, count); DrawElementsBaseVertex_Backend(mode, count, type, indices, basevertex); } void DrawArrays(GLenum mode, GLint first, GLsizei count) { if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; AccountTransformFeedbackPrimitives(mode, count); 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; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; MultiDrawElements_Backend(mode, count, type, indices, drawcount); } void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices, GLsizei drawcount, const GLint* basevertex) { if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; MultiDrawElementsBaseVertex_Backend(mode, count, type, indices, drawcount, basevertex); } void Clear(GLbitfield mask) { Clear_Backend(mask); } void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) { if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; AccountTransformFeedbackPrimitives(mode, count); DrawElements_Backend(mode, count, type, indices); } void BeginTransformFeedback(GLenum primitiveMode) { if (primitiveMode != GL_POINTS && primitiveMode != GL_LINES && primitiveMode != GL_TRIANGLES) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", __func__, "primitiveMode must be GL_POINTS, GL_LINES or GL_TRIANGLES.")); return; } if (MG_State::pGLContext->IsTransformFeedbackActive()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, "Transform feedback is already active.")); return; } const auto& program = MG_State::pGLContext->GetProgramForDraw(); if (!program || !program->GetLinkStatus() || program->GetTransformFeedbackVaryingCount() == 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique( "MG_Impl/GLImpl", __func__, "No program with transform feedback varyings is active.")); return; } // Every capture buffer slot the program's mode uses must have a buffer bound. A slot // of stride 0 - two consecutive gl_NextBuffer entries - captures nothing and so needs // no binding. const SizeT usedBufferCount = program->GetTransformFeedbackBufferCount(); for (SizeT i = 0; i < usedBufferCount; ++i) { if (program->GetTransformFeedbackStride(static_cast(i)) == 0) continue; const auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback, static_cast(i)); if (point.GetBoundObject() == nullptr) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique( "MG_Impl/GLImpl", __func__, "Transform feedback buffer binding point " + std::to_string(i) + " has no buffer bound.")); return; } } MG_State::pGLContext->BeginTransformFeedback(primitiveMode, program); if (const auto beginXfb = MG_Backend::gBackendFunctionsTable.GL.BeginTransformFeedback) { beginXfb(primitiveMode); } } // Vulkan transform feedback captures triangle strips in plain (i, i+1, i+2) // vertex order, but GL decomposes odd strip triangles as (i+1, i, i+2) // (GL 4.6 table 10.1). With the geometry stage's statically-known strip // lengths the captured records are reordered in place: swap the first two // vertex records of every odd triangle within each emitted strip. static void FixupGsStripCaptureOrder(const SharedPtr& program, Uint64 inputPrimitives) { // Only Vulkan-order captures need this. A backend that runs the capture on its // own GL/ES driver (it owns the span, hence the EndTransformFeedback entry) has // already produced GL's vertex order, and reordering it again would corrupt it. if (MG_Backend::gBackendFunctionsTable.GL.EndTransformFeedback != nullptr) { return; } if (program == nullptr || !program->HasGsTriangleStripCaptureFixup() || inputPrimitives == 0) { return; } const auto& stripTriangles = program->GetGsStripTriangles(); // Global triangle indices whose leading vertex pair must swap. Vector swapTriangles; Uint64 triangleBase = 0; for (Uint64 input = 0; input < inputPrimitives; ++input) { for (const Uint32 stripLength : stripTriangles) { for (Uint32 t = 1; t < stripLength; t += 2) { swapTriangles.push_back(triangleBase + t); } triangleBase += stripLength; } } if (swapTriangles.empty()) { return; } for (SizeT bufferIndex = 0; bufferIndex < program->GetTransformFeedbackBufferCount(); ++bufferIndex) { const Uint32 stride = program->GetTransformFeedbackStride(static_cast(bufferIndex)); if (stride == 0) continue; const auto& bindingPoint = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback, static_cast(bufferIndex)); const auto& buffer = bindingPoint.GetBoundObject(); if (buffer == nullptr) continue; const Range1D range = bindingPoint.GetRange(); const Uint8* mapped = buffer->MappedData(); if (mapped == nullptr) continue; // The geometry stage amplifies, so the CPU vertex counter does not bound // the capture; the binding range's whole-triangle capacity does. const Uint64 rangeBytes = range.end > range.start ? static_cast(range.end - range.start) : 0; const Uint64 capturedTriangles = std::min(triangleBase, (rangeBytes / stride) / 3); // Observed Vulkan capture order for odd strip triangles is (i, i+2, i+1) // (winding preserved by swapping the trailing pair); GL wants // (i+1, i, i+2), which is one rotation away: (a,b,c) -> (c,a,b). Vector scratch(stride); for (const Uint64 triangle : swapTriangles) { if (triangle >= capturedTriangles) break; const SizeT v0Offset = static_cast(range.start) + static_cast(triangle * 3) * stride; const SizeT v1Offset = v0Offset + stride; const SizeT v2Offset = v1Offset + stride; Memcpy(scratch.data(), mapped + v2Offset, stride); buffer->WritebackFromBackend({const_cast(mapped) + v1Offset, stride}, v2Offset); buffer->WritebackFromBackend({const_cast(mapped) + v0Offset, stride}, v1Offset); buffer->WritebackFromBackend({scratch.data(), stride}, v0Offset); } } } void EndTransformFeedback(void) { if (!MG_State::pGLContext->IsTransformFeedbackActive()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, "Transform feedback is not active.")); return; } const auto capturedProgram = MG_State::pGLContext->GetTransformFeedbackProgram(); const Uint64 inputPrimitives = MG_State::pGLContext->GetTransformFeedbackInputPrimitives(); // Closed while the capture state is still active: a backend that captures // through its own driver reads the capture program and buffer bindings here. if (const auto endXfb = MG_Backend::gBackendFunctionsTable.GL.EndTransformFeedback) { endXfb(); } MG_State::pGLContext->EndTransformFeedback(); // Captured results must be visible to MapBuffer/GetBufferSubData after // End; the capture targets are host-coherent GPU memory, so completing // the GPU work is all that is required. auto& backendGL = MG_Backend::gBackendFunctionsTable.GL; if (backendGL.FenceSync && backendGL.ClientWaitSync) { if (auto sync = backendGL.FenceSync()) { backendGL.ClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, ~0ull); if (backendGL.DeleteSync) { backendGL.DeleteSync(sync); } } } FixupGsStripCaptureOrder(capturedProgram, inputPrimitives); } void PauseTransformFeedback(void) { if (!MG_State::pGLContext->IsTransformFeedbackActive() || MG_State::pGLContext->IsTransformFeedbackPaused()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, "Transform feedback is not active, or is already paused.")); return; } MG_State::pGLContext->SetTransformFeedbackPaused(true); if (const auto pauseXfb = MG_Backend::gBackendFunctionsTable.GL.PauseTransformFeedback) { pauseXfb(); } } void ResumeTransformFeedback(void) { if (!MG_State::pGLContext->IsTransformFeedbackActive() || !MG_State::pGLContext->IsTransformFeedbackPaused()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, "Transform feedback is not paused.")); return; } MG_State::pGLContext->SetTransformFeedbackPaused(false); if (const auto resumeXfb = MG_Backend::gBackendFunctionsTable.GL.ResumeTransformFeedback) { resumeXfb(); } } void GenTransformFeedbacks(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); 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( ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", __func__, "n must be non-negative.")); return; } if (ids == nullptr) return; for (GLsizei i = 0; i < n; ++i) { const GLuint id = ids[i]; // Unknown names and 0 are silently ignored; an object whose capture span is // still open is not (GL 4.6 core 13.2.1). if (id == 0 || !MG_State::pGLContext->ValidateTransformFeedbackName(id)) continue; if (id == MG_State::pGLContext->GetBoundTransformFeedbackName() && MG_State::pGLContext->IsTransformFeedbackActive()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, "Cannot delete a transform feedback object whose capture is active.")); continue; } if (const auto deleteXfb = MG_Backend::gBackendFunctionsTable.GL.DeleteTransformFeedback) { deleteXfb(id); } MG_State::pGLContext->MarkTransformFeedbackObjectForDeletion(id); } } void BindTransformFeedback(GLenum target, GLuint id) { if (target != GL_TRANSFORM_FEEDBACK) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", __func__, "target must be GL_TRANSFORM_FEEDBACK.")); return; } // A running capture pins its object; only a paused one may be swapped out. if (MG_State::pGLContext->IsTransformFeedbackActive() && !MG_State::pGLContext->IsTransformFeedbackPaused()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, "Transform feedback is active and not paused.")); return; } if (!MG_State::pGLContext->ValidateTransformFeedbackName(id)) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, std::to_string(id) + " is not a transform feedback object name.")); return; } MG_State::pGLContext->BindTransformFeedbackObject(id); if (const auto bindXfb = MG_Backend::gBackendFunctionsTable.GL.BindTransformFeedback) { bindXfb(id); } } GLboolean IsTransformFeedback(GLuint id) { // Name 0 is the default object, and a name glGenTransformFeedbacks handed out only // becomes the name of an object once it has been bound. return MG_State::pGLContext->IsTransformFeedbackObject(id) ? GL_TRUE : GL_FALSE; } // glDrawTransformFeedback[Stream][Instanced]: replays the vertices the named object // captured in its last completed span, as if by glDrawArraysInstanced with that count // (GL 4.6 core 10.3.7). static void DrawTransformFeedbackImpl(const char* functionName, GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) { if (!ValidateCurrentProgramForExecution(functionName)) return; if (!ValidatePrimitiveModeForBackend(functionName, mode)) return; if (instancecount < 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", functionName, "instancecount must be non-negative.")); return; } if (!MG_State::pGLContext->ValidateTransformFeedbackName(id)) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", functionName, std::to_string(id) + " is not a transform feedback object name.")); return; } // GL_MAX_VERTEX_STREAMS is 1, so stream 0 is the only one that exists. if (stream != 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", functionName, "stream must be less than GL_MAX_VERTEX_STREAMS.")); return; } // Drawing from an object whose capture is currently open is legal and deliberate: // it is how a transform feedback result is fed straight back into the next span // (ARB_transform_feedback2 lists no such restriction). if (!MG_State::pGLContext->HasTransformFeedbackCompletedSpan(id)) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", functionName, "glEndTransformFeedback has never been called for this object.")); return; } const Uint64 vertices = MG_State::pGLContext->GetTransformFeedbackRecordedVertices(id); if (vertices == 0) return; const auto count = static_cast(vertices); AccountTransformFeedbackPrimitives(mode, count); if (instancecount == 1) { DrawArrays_Backend(mode, 0, count); } else { DrawArraysInstanced_Backend(mode, 0, count, instancecount); } } void DrawTransformFeedback(GLenum mode, GLuint id) { DrawTransformFeedbackImpl(__func__, mode, id, 0, 1); } void DrawTransformFeedbackInstanced(GLenum mode, GLuint id, GLsizei instancecount) { DrawTransformFeedbackImpl(__func__, mode, id, 0, instancecount); } void DrawTransformFeedbackStream(GLenum mode, GLuint id, GLuint stream) { DrawTransformFeedbackImpl(__func__, mode, id, stream, 1); } void DrawTransformFeedbackStreamInstanced(GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) { DrawTransformFeedbackImpl(__func__, mode, id, stream, instancecount); } } // namespace MobileGL::MG_Impl::GLImpl