mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 22:28:32 +09:00
First wave of the advertised-extension CTS campaign (targeted caselist: the
glcts groups of every extension both backends advertise, 4867 cases across the
KHR-GL41..46 namespaces). All frontend, shared by both backends:
- Non-square float matrix uniforms actually upload: glUniformMatrix{2x3,3x2,
2x4,4x2,3x4,4x3}fv and the six glProgramUniformMatrix* twins were
validate-only no-ops; they now write column-at-a-time at the global UBO's
16-byte std140 column stride, honouring transpose. glUniformMatrix2fv had
the sibling bug - mat2 written as 4 contiguous floats put column 1 at byte
8 instead of 16. The readback path only ever un-padded mat3, so
glGetUniformfv is fixed for mat2, mat3x2 (previously mis-gathered) and
every non-square shape, with the bounds check widened to the padded span.
- glBindBufferRange validates offset/size at last: size <= 0, offset < 0,
SSBO and UBO offset alignment, transform-feedback offset AND size
multiples of 4 - all before any state write (a negative offset used to
reach Range1D unchecked). glBindBuffersRange inherits per element, with
the ARB_multi_bind up-front [first, first+count) checks added to the
BindBuffersBase/Range and BindSamplers prologues.
- BufferSubData's second, wrong mapped-overlap test deleted (it rejected
every write at or after a mapped range's start, mapped or not); the state
layer's assert relaxed to the same half-open intersection the frontend
checks. BufferStorage error precedence fixed: no-bound-buffer now beats
bad-size/flags.
- glSamplerParameteri accepts the full GL_NEVER..GL_ALWAYS compare-func
range (NEVER/LESS/EQUAL were rejected by a wrong lower bound).
glBindSampler's unit gate uses GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS instead
of the frontend array capacity, shared with glBindSamplers by construction.
- Getters: GL_MAX_SHADER_STORAGE_BLOCK_SIZE in glGetIntegerv; atomic-counter
buffer limits; all 11 per-unit GL_TEXTURE_BINDING_* plus GL_SAMPLER_BINDING
in glGetIntegeri_v; GL_VERTEX_ATTRIB_BINDING/_RELATIVE_OFFSET across the
vertex-attrib query family; glGetFloati_v/glGetDoublei_v implemented (were
stubs); KHR_debug limits raised to spec floors.
- glCreateShader records INVALID_ENUM for an unknown type (it previously
handed out a usable name with no error at all); glCreateShaderProgramv
validates count up front. glDispatchCompute/Indirect validate work-group
counts, offset alignment and indirect-buffer presence.
- glVertexAttribIFormat & friends take a positive integer-type whitelist -
GL_FLOAT/GL_HALF_FLOAT/GL_DOUBLE/GL_FIXED no longer slip through as
integer attributes.
Gate (headless Mesa, default config = async on): 570/570 unit at default and
with the kill switch; ext caselist Espryt 76.29% -> 77.87% (+81 fixed, 6
crashes -> 0, the whole list now runs in one glcts process), Magma 75.94% ->
77.58% (+80 fixed, 0 newly broken); KHR-GL33 full mustpass lost nothing
(9884/9886, the 2 known Mesa-drift failures); retrace smoke clean (the
bsl-GLES miss is the documented golden drift, bit-identical on the pristine
baseline). The 4 DirectGLES direct_state_access.renderbuffers_storage* cases
that turned red are a PRE-EXISTING GL_FRAMEBUFFER_SRGB cross-test leak,
A/B-proven on an unpatched 2e6fc1ff build - wave 1 removed the two accidental
maskers (a crash partition and a failing case whose error path reset the
state). Fixing the leak itself is queued.
1209 lines
59 KiB
C++
1209 lines
59 KiB
C++
// 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 <Config.h>
|
|
#include <MG_State/GLState/Core.h>
|
|
#include <MG_State/EGLState/Core.h>
|
|
#include <MG_Backend/BackendObjects.h>
|
|
#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<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "There is no current program object."));
|
|
return false;
|
|
}
|
|
|
|
if (!currentProgram->GetLinkStatus()) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("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<GenericErrorInfo>("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<Uint64>(count);
|
|
case GL_LINES: return static_cast<Uint64>(count / 2);
|
|
case GL_LINE_STRIP: return count >= 2 ? static_cast<Uint64>(count - 1) : 0;
|
|
case GL_LINE_LOOP: return count >= 2 ? static_cast<Uint64>(count) : 0;
|
|
case GL_TRIANGLES: return static_cast<Uint64>(count / 3);
|
|
case GL_TRIANGLE_STRIP:
|
|
case GL_TRIANGLE_FAN: return count >= 3 ? static_cast<Uint64>(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<Uint32>(i));
|
|
if (stride == 0) continue;
|
|
const auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback,
|
|
static_cast<Uint>(i));
|
|
const Range1D range = point.GetRange();
|
|
const Uint64 bytes = range.end > range.start ? static_cast<Uint64>(range.end - range.start) : 0;
|
|
capacityVertices = std::min<Uint64>(capacityVertices, bytes / stride);
|
|
}
|
|
if (capacityVertices != ~0ull) {
|
|
const Uint64 usedVertices = MG_State::pGLContext->GetTransformFeedbackCapturedVertices();
|
|
const Uint64 remainingVertices = capacityVertices > usedVertices ? capacityVertices - usedVertices : 0;
|
|
primitives = std::min<Uint64>(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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>(
|
|
"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<GenericErrorInfo>(
|
|
"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<uintptr_t>(indirect);
|
|
if (offset % 4 != 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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<GLuint>(std::max(maxGroups, 0))) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"value must be in [1, GL_MAX_PATCH_VERTICES]."));
|
|
return;
|
|
}
|
|
MG_State::pGLContext->SetPatchVertices(static_cast<Uint>(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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>(
|
|
"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<Uint32>(i)) == 0) continue;
|
|
const auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback,
|
|
static_cast<Uint>(i));
|
|
if (point.GetBoundObject() == nullptr) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"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<MG_State::GLState::ProgramObject>& 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<Uint64> 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<Uint32>(bufferIndex));
|
|
if (stride == 0) continue;
|
|
const auto& bindingPoint =
|
|
MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback,
|
|
static_cast<Uint>(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<Uint64>(range.end - range.start) : 0;
|
|
const Uint64 capturedTriangles = std::min<Uint64>(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<Uint8> scratch(stride);
|
|
for (const Uint64 triangle : swapTriangles) {
|
|
if (triangle >= capturedTriangles) break;
|
|
const SizeT v0Offset = static_cast<SizeT>(range.start) + static_cast<SizeT>(triangle * 3) * stride;
|
|
const SizeT v1Offset = v0Offset + stride;
|
|
const SizeT v2Offset = v1Offset + stride;
|
|
Memcpy(scratch.data(), mapped + v2Offset, stride);
|
|
buffer->WritebackFromBackend({const_cast<Uint8*>(mapped) + v1Offset, stride}, v2Offset);
|
|
buffer->WritebackFromBackend({const_cast<Uint8*>(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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "n must be non-negative."));
|
|
return;
|
|
}
|
|
if (n == 0 || ids == nullptr) return;
|
|
Vector<Uint> names;
|
|
MG_State::pGLContext->GenTransformFeedbackNames(static_cast<Uint>(n), names);
|
|
Memcpy(ids, names.data(), static_cast<SizeT>(n) * sizeof(GLuint));
|
|
}
|
|
|
|
void CreateTransformFeedbacks(GLsizei n, GLuint* ids) {
|
|
if (n < 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "n must be non-negative."));
|
|
return;
|
|
}
|
|
if (n == 0 || ids == nullptr) return;
|
|
Vector<Uint> names;
|
|
MG_State::pGLContext->GenTransformFeedbackNames(static_cast<Uint>(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<SizeT>(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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
|
"The transform feedback object is capturing."));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
SharedPtr<MG_State::GLState::BufferObject> ResolveTransformFeedbackBuffer(GLuint buffer,
|
|
const char* functionName) {
|
|
if (buffer == 0) return nullptr;
|
|
if (!MG_State::pGLContext->ValidateBufferName(buffer)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
std::to_string(buffer) + " is not a buffer object."));
|
|
return;
|
|
}
|
|
auto bufferObject = ResolveTransformFeedbackBuffer(buffer, __func__);
|
|
const Range1D range{static_cast<SizeT>(offset), static_cast<SizeT>(offset) + static_cast<SizeT>(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<GenericErrorInfo>("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<GenericErrorInfo>("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<GLint>(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<GenericErrorInfo>("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<GLint64>(binding.Range.start)
|
|
: static_cast<GLint64>(binding.Range.end - binding.Range.start);
|
|
}
|
|
|
|
void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids) {
|
|
if (n < 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "instancecount must be non-negative."));
|
|
return;
|
|
}
|
|
if (!MG_State::pGLContext->ValidateTransformFeedbackName(id)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("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<GenericErrorInfo>("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<GenericErrorInfo>("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<GLsizei>(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
|