Compare commits

...
9 Commits
Author SHA1 Message Date
Claude 7463236b34 [Fix] (MG_Impl): report a buffer texture as the wrong object, not the wrong token
glGetTextureParameter* resolve the texture by name and then hand the work to the
target-based getter, which validates the target it was given. For a buffer texture that
is GL_TEXTURE_BUFFER, and the target form correctly calls that an unaccepted token -
INVALID_ENUM.

By name there is no token to blame. The application named an object that carries none of
the sampler or level state the query reports, which is INVALID_OPERATION (GL 4.6 core
8.11). The four by-name getters check the resolved object before delegating, so the error
describes what the caller actually got wrong.

Fixes direct_state_access.textures_parameter_errors on both backends, taking the group to
74.93% on Espryt and 73.32% on Magma.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnW6wQYv2Pvrfisqak8UJu
2026-08-05 02:36:31 +00:00
Claude 658e08c918 [Docs] (tools/cts): add the desktop Linux CTS skill
The Android and Windows paths each have a skill; the desktop Linux one had only
a runner script and a README section, so it was the least discoverable of the
three despite being the one to reach for while iterating - it needs no device
and no GPU, and a single test group takes seconds rather than hours.

Records what the other two skills cannot: that the toolchain has to be GCC 13+
or Clang 20+ (Clang 18 reports __cpp_concepts as 201907L, which switches
libstdc++'s <expected> off and breaks the shader transpiler), that
EGL_PLATFORM=surfaceless is mandatory for DirectGLES and why the symptom points
at the wrong call, and which of this environment's results are MobileGL's own
versus artefacts of software rendering.

Also states the rule the other skills only imply: report Espryt and Magma
separately. They fail different cases, and one combined number hides which
backend a change moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnW6wQYv2Pvrfisqak8UJu
2026-08-05 02:31:46 +00:00
Claude e543d9f7da [Docs] (tools/cts): refresh the DSA reference table for the fixes in this branch
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnW6wQYv2Pvrfisqak8UJu
2026-08-05 02:29:42 +00:00
Claude 31d7d97e86 [Fix] (MG_Impl): apply the buffer texture's own format and range rules
glTextureBuffer and glTextureBufferRange took any internal format the texture enum
converter recognised. A buffer texture accepts a much shorter list than a sampled or a
renderable texture does (GL 4.6 core table 8.16), and it cannot be inferred from either,
so a format like GL_RGB8 was accepted and produced a texture nothing could read.

Two error codes were wrong as well. A texture whose effective target is not
GL_TEXTURE_BUFFER is the wrong object rather than the wrong token, so it is
INVALID_OPERATION. And the range form never checked its range against the buffer it was
attaching, so a size past the end of the buffer was accepted and left the texture
addressing memory the buffer does not own.

Fixes direct_state_access.textures_buffer_errors and textures_buffer_range_errors on both
backends.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnW6wQYv2Pvrfisqak8UJu
2026-08-05 02:29:25 +00:00
Claude cf89f394c5 [Fix] (MG_Util): ask the ES driver for the texture buffer offset alignment
The DirectGLES capability probe queried GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT with a bare
glGetIntegerv while every other query in the same function goes through glesFuncs. A bare
call resolves to MobileGL's own exported entry point, which answers that pname out of the
capability table this code is in the middle of filling in, so the value read back was the
default it started from and the driver's real alignment never arrived.

The backend therefore advertised an alignment of 1. An application that trusts that -
which is the only thing it can do - passes glTextureBufferRange an offset the ES driver
cannot honour, and the driver produces a texture that reads as zeros with no error
anywhere. The alignment llvmpipe actually wants is 16.

Takes direct_state_access.textures_buffer_* from 3 to 30 of 30 on DirectGLES, and the
whole DSA group from 66.85% to 74.12%. DirectVulkan was unaffected: its alignment comes
from a Vulkan device limit and was already right.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnW6wQYv2Pvrfisqak8UJu
2026-08-05 02:26:50 +00:00
Claude 2412807afd [Docs] (tools/cts): document the desktop Linux CTS path and the DSA baseline
run_cts_local.py and the mobilegl-desktop VK-GL-CTS target were both in the tree
with nothing describing how to reach them, so the only documented ways to run the
suite needed either an Android device or a Windows box with a GPU. The desktop
Linux path needs neither: lavapipe gives DirectVulkan a headless surface and
Mesa's surfaceless EGL gives DirectGLES a context, so a single test group can be
measured in seconds while working on it.

Records the two things that cost time to find. EGL_PLATFORM=surfaceless is
mandatory for DirectGLES - without a /dev/dri node Mesa fails eglInitialize on
the default display, and MobileGL surfaces that as EGL_BAD_ALLOC from
eglCreatePbufferSurface, which points at the wrong call entirely. And
DirectVulkan's default-framebuffer readback returns zeros here exactly as it does
on Adreno, so that defect is MobileGL's and reproducible without a phone.

The direct_state_access reference table is the measured baseline for the fixes in
this branch, so a later change has something to be compared against.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnW6wQYv2Pvrfisqak8UJu
2026-08-05 02:22:09 +00:00
Claude 9ed287dbc7 [Fix] (MG_Impl): bound a colour attachment and a vertex binding range by the limit
GL_COLOR_ATTACHMENTn is a token for every n up to 31, but only the first
GL_MAX_COLOR_ATTACHMENTS of them name an attachment point of a framebuffer object. The
enum conversion accepted the whole token range, so attaching a renderbuffer or a texture
to a colour attachment past the limit silently succeeded instead of reporting
INVALID_OPERATION, and the attachment landed in a slot nothing else would ever look at.

glBindVertexBuffers and glVertexArrayVertexBuffers take a range of binding points rather
than one index. A range running past the last binding point is INVALID_OPERATION, which
the per-binding validation could not report: it saw one index at a time and reported the
INVALID_VALUE that a single out-of-range index earns. The range is checked up front now,
before any binding point is touched, so a rejected call also leaves none of them changed.

Takes direct_state_access.vertex_arrays_* to 18 of 19 and fixes
direct_state_access.framebuffers_renderbuffer_attachment_errors on both backends.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnW6wQYv2Pvrfisqak8UJu
2026-08-05 02:21:13 +00:00
Claude 836306feee [Feat] (MG_Impl, MG_State): implement the direct state access transform feedback API
glCreateTransformFeedbacks, glTransformFeedbackBufferBase, glTransformFeedbackBufferRange
and the three glGetTransformFeedback* queries were all stubs, so a transform feedback
object could only be configured and inspected by binding it first - the exact thing
direct state access exists to avoid. The queries were the worse half: they returned
nothing and raised no error, so an application could not tell that it had learned
nothing.

glCreateTransformFeedbacks creates the objects outright. glGenTransformFeedbacks only
reserves names, and a reserved name becomes an object when it is first bound
(GL 4.6 core 13.2.1); the DSA form has no bind step to create them from.

The queries and the buffer bindings read and write a named object's state. That state
lives in two places: the context keeps one live copy of the capture bindings and the
active/paused flags for whichever object is bound, and every other object's copy sits in
its saved state until a bind swaps it in. The by-name accessors added to the context
resolve that, so a query for the bound object reads the live copy rather than a stale
save.

GL_TRANSFORM_FEEDBACK_BUFFER_START and _SIZE are answered as zero unless the binding was
made by the range form, matching what the buffer object binding points already do.

Takes direct_state_access.xfb_* from 0 to 4 of 5 on both backends; xfb_functional still
fails on the capture itself, which is a separate defect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnW6wQYv2Pvrfisqak8UJu
2026-08-05 02:16:48 +00:00
Claude 126f617428 [Fix] (MG_Impl, MG_State): give the vertex buffer binding points a real state view
The binding-point half of ARB_vertex_attrib_binding was implemented, but nothing
outside it could see the result. glGetIntegerv answered GL_MAX_VERTEX_ATTRIB_BINDINGS,
GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET and GL_MAX_VERTEX_ATTRIB_STRIDE with a hardcoded
0 and a comment saying the entry points were stubs, which they no longer are. An
application that sizes its loops off those limits therefore saw none, and every
"bindingindex must be less than MAX_VERTEX_ATTRIB_BINDINGS" check silently accepted
everything because the limit it validated against was not the one it reported.

The indexed getters answer GL_VERTEX_BINDING_{BUFFER,DIVISOR,OFFSET,STRIDE} from the
bound vertex array now, and the non-indexed getter reports them as indexed-only rather
than returning a fabricated 0.

glVertexAttribPointer is defined in terms of the binding model: it also points the
attribute at its own binding point and gives that point the buffer, the pointer as the
offset and the effective (never zero) stride. MobileGL resolved the pointer form
straight into the flat attribute view and left the binding point untouched, so
GL_VERTEX_BINDING_OFFSET read back 0 for every attribute set up the classic way. The
flat view keeps the raw stride, because GL_VERTEX_ATTRIB_ARRAY_STRIDE reports that
argument verbatim, so the binding point is recorded alongside it rather than resolved
from it. glVertexAttribDivisor likewise now moves the binding point's divisor.

The by-name entry points reject vertex array 0. MobileGL keeps a real object at index 0
for the compatibility paths, so the name validation used to let the default vertex array
through a direct-state-access call that has no such thing.

glVertexAttribFormat and friends validated with the pointer-only subset, which reports
GL_BGRA as an out-of-range size instead of applying the BGRA rules, and never saw
relativeoffset at all. They share the full format validation now, which also grew the
GL_UNSIGNED_INT_10F_11F_11F_REV rules - that type has no DataType of its own, so it has
to be recognised before the conversion turns it into Unknown and reports the wrong error.

glVertexAttribLFormat and glVertexArrayAttribLFormat were stubs. They validate their
arguments now and then report that 64-bit vertex attributes are unsupported, which is
honest; silently accepting a format that can never be used is not.

Takes direct_state_access.vertex_arrays_* from 12 to 17 of 19 on both backends.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnW6wQYv2Pvrfisqak8UJu
2026-08-05 02:16:32 +00:00
22 changed files with 885 additions and 36 deletions
@@ -877,6 +877,172 @@ namespace MobileGL::MG_Impl::GLImpl {
Memcpy(ids, names.data(), static_cast<SizeT>(n) * sizeof(GLuint)); 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) { void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids) {
if (n < 0) { if (n < 0) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
@@ -16,7 +16,13 @@ namespace MobileGL::MG_Impl::GLImpl {
void PauseTransformFeedback(void); void PauseTransformFeedback(void);
void ResumeTransformFeedback(void); void ResumeTransformFeedback(void);
void GenTransformFeedbacks(GLsizei n, GLuint* ids); void GenTransformFeedbacks(GLsizei n, GLuint* ids);
void CreateTransformFeedbacks(GLsizei n, GLuint* ids);
void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids); void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids);
void TransformFeedbackBufferBase(GLuint xfb, GLuint index, GLuint buffer);
void TransformFeedbackBufferRange(GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size);
void GetTransformFeedbackiv(GLuint xfb, GLenum pname, GLint* param);
void GetTransformFeedbacki_v(GLuint xfb, GLenum pname, GLuint index, GLint* param);
void GetTransformFeedbacki64_v(GLuint xfb, GLenum pname, GLuint index, GLint64* param);
void BindTransformFeedback(GLenum target, GLuint id); void BindTransformFeedback(GLenum target, GLuint id);
GLboolean IsTransformFeedback(GLuint id); GLboolean IsTransformFeedback(GLuint id);
void DrawTransformFeedback(GLenum mode, GLuint id); void DrawTransformFeedback(GLenum mode, GLuint id);
@@ -996,7 +996,7 @@ DECLARE_GL_FUNCTION_HEAD(void, MultiDrawElementsIndirect, GLenum mode, GLenum ty
DECLARE_GL_FUNCTION_HEAD(GLint, GetProgramResourceLocationIndex, GLuint program, GLenum programInterface, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetProgramResourceLocationIndex, program, programInterface, name) DECLARE_GL_FUNCTION_HEAD(GLint, GetProgramResourceLocationIndex, GLuint program, GLenum programInterface, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetProgramResourceLocationIndex, program, programInterface, name)
DECLARE_GL_FUNCTION_HEAD(void, ShaderStorageBlockBinding, GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ShaderStorageBlockBinding, program, storageBlockIndex, storageBlockBinding) DECLARE_GL_FUNCTION_HEAD(void, ShaderStorageBlockBinding, GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ShaderStorageBlockBinding, program, storageBlockIndex, storageBlockBinding)
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureView, GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureView, texture, target, origtexture, internalformat, minlevel, numlevels, minlayer, numlayers) DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureView, GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureView, texture, target, origtexture, internalformat, minlevel, numlevels, minlayer, numlayers)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribLFormat, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribLFormat, attribindex, size, type, relativeoffset) DECLARE_GL_FUNCTION_HEAD(void, VertexAttribLFormat, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribLFormat, attribindex, size, type, relativeoffset)
DECLARE_GL_FUNCTION_HEAD(void, BufferStorage, GLenum target, GLsizeiptr size, const void* data, GLbitfield flags) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BufferStorage, target, size, data, flags) DECLARE_GL_FUNCTION_HEAD(void, BufferStorage, GLenum target, GLsizeiptr size, const void* data, GLbitfield flags) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BufferStorage, target, size, data, flags)
DECLARE_GL_FUNCTION_HEAD(void, ClearTexImage, GLuint texture, GLint level, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearTexImage, texture, level, format, type, data) DECLARE_GL_FUNCTION_HEAD(void, ClearTexImage, GLuint texture, GLint level, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearTexImage, texture, level, format, type, data)
DECLARE_GL_FUNCTION_HEAD(void, ClearTexSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearTexSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, data) DECLARE_GL_FUNCTION_HEAD(void, ClearTexSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearTexSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, data)
@@ -1007,12 +1007,12 @@ DECLARE_GL_FUNCTION_HEAD(void, BindSamplers, GLuint first, GLsizei count, const
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindImageTextures, GLuint first, GLsizei count, const GLuint* textures) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindImageTextures, first, count, textures) DECLARE_GL_FUNCTION_STUB_HEAD(void, BindImageTextures, GLuint first, GLsizei count, const GLuint* textures) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindImageTextures, first, count, textures)
DECLARE_GL_FUNCTION_HEAD(void, BindVertexBuffers, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets, const GLsizei* strides) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindVertexBuffers, first, count, buffers, offsets, strides) DECLARE_GL_FUNCTION_HEAD(void, BindVertexBuffers, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets, const GLsizei* strides) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindVertexBuffers, first, count, buffers, offsets, strides)
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClipControl, GLenum origin, GLenum depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClipControl, origin, depth) DECLARE_GL_FUNCTION_STUB_HEAD(void, ClipControl, GLenum origin, GLenum depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClipControl, origin, depth)
DECLARE_GL_FUNCTION_STUB_HEAD(void, CreateTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CreateTransformFeedbacks, n, ids) DECLARE_GL_FUNCTION_HEAD(void, CreateTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateTransformFeedbacks, n, ids)
DECLARE_GL_FUNCTION_STUB_HEAD(void, TransformFeedbackBufferBase, GLuint xfb, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TransformFeedbackBufferBase, xfb, index, buffer) DECLARE_GL_FUNCTION_HEAD(void, TransformFeedbackBufferBase, GLuint xfb, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TransformFeedbackBufferBase, xfb, index, buffer)
DECLARE_GL_FUNCTION_STUB_HEAD(void, TransformFeedbackBufferRange, GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TransformFeedbackBufferRange, xfb, index, buffer, offset, size) DECLARE_GL_FUNCTION_HEAD(void, TransformFeedbackBufferRange, GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TransformFeedbackBufferRange, xfb, index, buffer, offset, size)
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbackiv, GLuint xfb, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbackiv, xfb, pname, param) DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbackiv, GLuint xfb, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbackiv, xfb, pname, param)
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbacki_v, GLuint xfb, GLenum pname, GLuint index, GLint* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbacki_v, xfb, pname, index, param) DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbacki_v, GLuint xfb, GLenum pname, GLuint index, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbacki_v, xfb, pname, index, param)
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbacki64_v, GLuint xfb, GLenum pname, GLuint index, GLint64* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbacki64_v, xfb, pname, index, param) DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbacki64_v, GLuint xfb, GLenum pname, GLuint index, GLint64* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbacki64_v, xfb, pname, index, param)
DECLARE_GL_FUNCTION_HEAD(void, CreateBuffers, GLsizei n, GLuint* buffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateBuffers, n, buffers) DECLARE_GL_FUNCTION_HEAD(void, CreateBuffers, GLsizei n, GLuint* buffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateBuffers, n, buffers)
DECLARE_GL_FUNCTION_HEAD(void, NamedBufferStorage, GLuint buffer, GLsizeiptr size, const void* data, GLbitfield flags) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedBufferStorage, buffer, size, data, flags) DECLARE_GL_FUNCTION_HEAD(void, NamedBufferStorage, GLuint buffer, GLsizeiptr size, const void* data, GLbitfield flags) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedBufferStorage, buffer, size, data, flags)
DECLARE_GL_FUNCTION_HEAD(void, NamedBufferData, GLuint buffer, GLsizeiptr size, const void* data, GLenum usage) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedBufferData, buffer, size, data, usage) DECLARE_GL_FUNCTION_HEAD(void, NamedBufferData, GLuint buffer, GLsizeiptr size, const void* data, GLenum usage) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedBufferData, buffer, size, data, usage)
@@ -1090,7 +1090,7 @@ DECLARE_GL_FUNCTION_HEAD(void, VertexArrayVertexBuffers, GLuint vaobj, GLuint fi
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribBinding, GLuint vaobj, GLuint attribindex, GLuint bindingindex) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribBinding, vaobj, attribindex, bindingindex) DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribBinding, GLuint vaobj, GLuint attribindex, GLuint bindingindex) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribBinding, vaobj, attribindex, bindingindex)
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribFormat, vaobj, attribindex, size, type, normalized, relativeoffset) DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribFormat, vaobj, attribindex, size, type, normalized, relativeoffset)
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribIFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribIFormat, vaobj, attribindex, size, type, relativeoffset) DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribIFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribIFormat, vaobj, attribindex, size, type, relativeoffset)
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexArrayAttribLFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexArrayAttribLFormat, vaobj, attribindex, size, type, relativeoffset) DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribLFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribLFormat, vaobj, attribindex, size, type, relativeoffset)
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayBindingDivisor, GLuint vaobj, GLuint bindingindex, GLuint divisor) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayBindingDivisor, vaobj, bindingindex, divisor) DECLARE_GL_FUNCTION_HEAD(void, VertexArrayBindingDivisor, GLuint vaobj, GLuint bindingindex, GLuint divisor) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayBindingDivisor, vaobj, bindingindex, divisor)
DECLARE_GL_FUNCTION_HEAD(void, GetVertexArrayiv, GLuint vaobj, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexArrayiv, vaobj, pname, param) DECLARE_GL_FUNCTION_HEAD(void, GetVertexArrayiv, GLuint vaobj, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexArrayiv, vaobj, pname, param)
DECLARE_GL_FUNCTION_HEAD(void, GetVertexArrayIndexediv, GLuint vaobj, GLuint index, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexArrayIndexediv, vaobj, index, pname, param) DECLARE_GL_FUNCTION_HEAD(void, GetVertexArrayIndexediv, GLuint vaobj, GLuint index, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexArrayIndexediv, vaobj, index, pname, param)
@@ -1143,6 +1143,7 @@ namespace MobileGL::MG_Impl::GLImpl {
const FramebufferAttachmentType attachmentType = MG_Util::ConvertGLEnumToFramebufferAttachmentType(attachment); const FramebufferAttachmentType attachmentType = MG_Util::ConvertGLEnumToFramebufferAttachmentType(attachment);
if (!FramebufferImpl::ValidateFramebufferAttachmentType(attachmentType)) return; if (!FramebufferImpl::ValidateFramebufferAttachmentType(attachmentType)) return;
if (!FramebufferImpl::ValidateColorAttachmentInRange(attachmentType, functionName)) return;
if (!TextureImpl::ValidateTextureName(texture, true)) return; if (!TextureImpl::ValidateTextureName(texture, true)) return;
if (texture == 0) { if (texture == 0) {
@@ -1239,6 +1240,7 @@ namespace MobileGL::MG_Impl::GLImpl {
FramebufferTarget framebufferTarget = MG_Util::ConvertGLEnumToFramebufferTarget(target); FramebufferTarget framebufferTarget = MG_Util::ConvertGLEnumToFramebufferTarget(target);
RenderbufferTarget rbTarget = MG_Util::ConvertGLEnumToRenderbufferTarget(renderbuffertarget); RenderbufferTarget rbTarget = MG_Util::ConvertGLEnumToRenderbufferTarget(renderbuffertarget);
if (!FramebufferImpl::ValidateFramebufferAttachmentType(attachmentType)) return; if (!FramebufferImpl::ValidateFramebufferAttachmentType(attachmentType)) return;
if (!FramebufferImpl::ValidateColorAttachmentInRange(attachmentType, "FramebufferRenderbuffer_State")) return;
if (!FramebufferImpl::ValidateFramebufferTarget(framebufferTarget)) return; if (!FramebufferImpl::ValidateFramebufferTarget(framebufferTarget)) return;
if (!FramebufferImpl::ValidateRenderbufferTarget(rbTarget)) return; if (!FramebufferImpl::ValidateRenderbufferTarget(rbTarget)) return;
auto& bindingSlot = MG_State::pGLContext->GetFramebufferBindingSlot(framebufferTarget); auto& bindingSlot = MG_State::pGLContext->GetFramebufferBindingSlot(framebufferTarget);
@@ -1283,6 +1285,8 @@ namespace MobileGL::MG_Impl::GLImpl {
FramebufferAttachmentType attachmentType = MG_Util::ConvertGLEnumToFramebufferAttachmentType(attachment); FramebufferAttachmentType attachmentType = MG_Util::ConvertGLEnumToFramebufferAttachmentType(attachment);
RenderbufferTarget rbTarget = MG_Util::ConvertGLEnumToRenderbufferTarget(renderbuffertarget); RenderbufferTarget rbTarget = MG_Util::ConvertGLEnumToRenderbufferTarget(renderbuffertarget);
if (!FramebufferImpl::ValidateFramebufferAttachmentType(attachmentType)) return; if (!FramebufferImpl::ValidateFramebufferAttachmentType(attachmentType)) return;
if (!FramebufferImpl::ValidateColorAttachmentInRange(attachmentType, "NamedFramebufferRenderbuffer_State"))
return;
if (!FramebufferImpl::ValidateRenderbufferTarget(rbTarget)) return; if (!FramebufferImpl::ValidateRenderbufferTarget(rbTarget)) return;
if (renderbuffer == 0) { if (renderbuffer == 0) {
@@ -7,6 +7,7 @@
// End of Source File Header // End of Source File Header
#include "Validators.h" #include "Validators.h"
#include <MG_Backend/BackendObjects.h>
#include <MG_State/GLState/Core.h> #include <MG_State/GLState/Core.h>
#include <MG_State/GLState/ErrorState/Error.h> #include <MG_State/GLState/ErrorState/Error.h>
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h> #include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
@@ -60,6 +61,26 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl {
return true; return true;
} }
Bool ValidateColorAttachmentInRange(FramebufferAttachmentType attachment, const char* caller) {
const auto first = static_cast<SizeT>(FramebufferAttachmentType::Color0);
const auto index = static_cast<SizeT>(attachment);
if (index < first) return true;
const auto colorIndex = index - first;
const auto limit = static_cast<SizeT>(
MG_Backend::pActiveBackendObject ? MG_Backend::pActiveBackendObject->GetDynamicParameters()
.MaxColorAttachments
: static_cast<Int>(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS));
if (colorIndex >= limit) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>(
"MG_Impl/GLImpl/FramebufferImpl", caller,
std::format("Colour attachment {} is beyond GL_MAX_COLOR_ATTACHMENTS ({}).", colorIndex, limit)));
return false;
}
return true;
}
Bool ValidateRenderbufferTarget(RenderbufferTarget target) { Bool ValidateRenderbufferTarget(RenderbufferTarget target) {
if (target == RenderbufferTarget::Unknown) { if (target == RenderbufferTarget::Unknown) {
using namespace MG_Util; using namespace MG_Util;
@@ -14,6 +14,10 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl {
Bool ValidateFramebufferTarget(FramebufferTarget target); Bool ValidateFramebufferTarget(FramebufferTarget target);
Bool ValidateFramebufferName(Uint index, Bool allowZero = true); Bool ValidateFramebufferName(Uint index, Bool allowZero = true);
Bool ValidateFramebufferAttachmentType(FramebufferAttachmentType attachment); Bool ValidateFramebufferAttachmentType(FramebufferAttachmentType attachment);
// GL_COLOR_ATTACHMENTn is a token per n up to 31, but only the first GL_MAX_COLOR_ATTACHMENTS of
// them name an attachment point of a framebuffer object; the rest are INVALID_OPERATION for the
// attaching entry points (GL 4.6 core 9.2.7). Non-colour attachments pass through unchanged.
Bool ValidateColorAttachmentInRange(FramebufferAttachmentType attachment, const char* caller);
Bool ValidateRenderbufferTarget(RenderbufferTarget target); Bool ValidateRenderbufferTarget(RenderbufferTarget target);
Bool ValidateRenderbufferName(Uint index, Bool allowZero = true); Bool ValidateRenderbufferName(Uint index, Bool allowZero = true);
} // namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl } // namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl
+43 -7
View File
@@ -671,6 +671,40 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
switch (target) { switch (target) {
// The vertex buffer binding points of the vertex array object that is bound. Indexed by
// binding point, not by attribute (GL 4.6 core 10.3.1).
case GL_VERTEX_BINDING_BUFFER:
case GL_VERTEX_BINDING_DIVISOR:
case GL_VERTEX_BINDING_OFFSET:
case GL_VERTEX_BINDING_STRIDE: {
if (index >= VertexArrayImpl::GetMaxVertexAttribBindings()) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"Vertex buffer binding index is out of range."));
return;
}
const auto& vao = MG_State::pGLContext->GetBoundVertexArray();
if (!vao) {
*data = 0;
return;
}
const auto& binding = vao->GetBindingPoint(index);
switch (target) {
case GL_VERTEX_BINDING_BUFFER:
*data = binding.Buffer ? static_cast<GLint>(binding.Buffer->GetExternalIndex()) : 0;
return;
case GL_VERTEX_BINDING_DIVISOR:
*data = static_cast<GLint>(binding.Divisor);
return;
case GL_VERTEX_BINDING_OFFSET:
*data = static_cast<GLint>(binding.Offset);
return;
default:
*data = static_cast<GLint>(binding.Stride);
return;
}
}
case GL_IMAGE_BINDING_NAME: case GL_IMAGE_BINDING_NAME:
case GL_IMAGE_BINDING_LEVEL: case GL_IMAGE_BINDING_LEVEL:
case GL_IMAGE_BINDING_LAYERED: case GL_IMAGE_BINDING_LAYERED:
@@ -1738,20 +1772,22 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = vao ? static_cast<GLint>(vao->GetExternalIndex()) : 0; *params = vao ? static_cast<GLint>(vao->GetExternalIndex()) : 0;
return; return;
} }
// The vertex buffer binding points are per-binding-index state, so the non-indexed getter
// has nothing to answer with (GL 4.6 core table 23.4).
case GL_VERTEX_BINDING_BUFFER:
case GL_VERTEX_BINDING_DIVISOR: case GL_VERTEX_BINDING_DIVISOR:
*params = 0; // vertex-binding entrypoints are stubbed
return;
case GL_VERTEX_BINDING_OFFSET: case GL_VERTEX_BINDING_OFFSET:
*params = 0; // vertex-binding entrypoints are stubbed
return;
case GL_VERTEX_BINDING_STRIDE: case GL_VERTEX_BINDING_STRIDE:
*params = 0; // vertex-binding entrypoints are stubbed RecordIndexedOnlyGetterError(__func__, pname);
return; return;
case GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET: case GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET:
*params = 0; // vertex-binding entrypoints are stubbed *params = static_cast<GLint>(VertexArrayImpl::GetMaxVertexAttribRelativeOffset());
return; return;
case GL_MAX_VERTEX_ATTRIB_BINDINGS: case GL_MAX_VERTEX_ATTRIB_BINDINGS:
*params = 0; // vertex-binding entrypoints are stubbed *params = static_cast<GLint>(VertexArrayImpl::GetMaxVertexAttribBindings());
return;
case GL_MAX_VERTEX_ATTRIB_STRIDE:
*params = static_cast<GLint>(VertexArrayImpl::GetMaxVertexAttribStride());
return; return;
case GL_VIEWPORT: { case GL_VIEWPORT: {
const auto& vp = MG_State::pGLContext->GetViewport(); const auto& vp = MG_State::pGLContext->GetViewport();
+84 -1
View File
@@ -2039,11 +2039,63 @@ namespace MobileGL::MG_Impl::GLImpl {
// resolved - by binding for the target forms, by name for the DSA ones. `size` is // resolved - by binding for the target forms, by name for the DSA ones. `size` is
// kWholeBuffer for the non-Range entry points, which attach the buffer as it grows rather // kWholeBuffer for the non-Range entry points, which attach the buffer as it grows rather
// than freezing the size it happens to have now. // than freezing the size it happens to have now.
// The sized internal formats a buffer texture accepts (GL 4.6 core table 8.16). This is a much
// shorter list than the renderable or texturable formats, so it cannot be inferred from either.
static Bool IsBufferTextureInternalFormat(GLenum internalformat) {
switch (internalformat) {
case GL_R8:
case GL_R16:
case GL_R16F:
case GL_R32F:
case GL_R8I:
case GL_R16I:
case GL_R32I:
case GL_R8UI:
case GL_R16UI:
case GL_R32UI:
case GL_RG8:
case GL_RG16:
case GL_RG16F:
case GL_RG32F:
case GL_RG8I:
case GL_RG16I:
case GL_RG32I:
case GL_RG8UI:
case GL_RG16UI:
case GL_RG32UI:
case GL_RGB32F:
case GL_RGB32I:
case GL_RGB32UI:
case GL_RGBA8:
case GL_RGBA16:
case GL_RGBA16F:
case GL_RGBA32F:
case GL_RGBA8I:
case GL_RGBA16I:
case GL_RGBA32I:
case GL_RGBA8UI:
case GL_RGBA16UI:
case GL_RGBA32UI:
return true;
default:
return false;
}
}
static void AttachBufferToTexture(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject, static void AttachBufferToTexture(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
GLenum internalformat, GLuint buffer, GLintptr offset, SizeT size, GLenum internalformat, GLuint buffer, GLintptr offset, SizeT size,
const char* caller) { const char* caller) {
using MG_State::GLState::TextureObjectBuffer; using MG_State::GLState::TextureObjectBuffer;
TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat); TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
if (!IsBufferTextureInternalFormat(internalformat)) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>(
"MG_Impl/GLImpl", caller,
std::format("internalformat 0x{:X} is not one of the sized formats a buffer texture accepts.",
internalformat)));
return;
}
if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return; if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return;
auto& bufferObject = MG_State::pGLContext->GetBufferObject(buffer); auto& bufferObject = MG_State::pGLContext->GetBufferObject(buffer);
@@ -2056,8 +2108,10 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
if (!TextureImpl::ValidateTextureObject(textureObject)) return; if (!TextureImpl::ValidateTextureObject(textureObject)) return;
if (textureObject->GetStorageType() != TextureStorageType::Buffer) { if (textureObject->GetStorageType() != TextureStorageType::Buffer) {
// A texture whose target is something else is a wrong object, not a wrong token
// (GL 4.6 core 8.9).
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum, ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
"The effective target of `texture` is not `GL_TEXTURE_BUFFER`.")); "The effective target of `texture` is not `GL_TEXTURE_BUFFER`."));
return; return;
@@ -2086,6 +2140,15 @@ namespace MobileGL::MG_Impl::GLImpl {
"offset is not a multiple of GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT.")); "offset is not a multiple of GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT."));
return; return;
} }
// The range has to lie inside the buffer that is being attached. Detaching (buffer
// zero) carries no range to check.
if (bufferObject && static_cast<SizeT>(offset) + size > bufferObject->GetSize()) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
"offset + size is greater than the buffer object's GL_BUFFER_SIZE."));
return;
}
} }
auto* texBufferObject = static_cast<TextureObjectBuffer*>(textureObject.get()); auto* texBufferObject = static_cast<TextureObjectBuffer*>(textureObject.get());
@@ -3730,23 +3793,43 @@ namespace MobileGL::MG_Impl::GLImpl {
GetTextureImage(texture, level, format, type, bufSize, pixels); GetTextureImage(texture, level, format, type, bufSize, pixels);
} }
// A buffer texture carries none of the sampler or level state these queries report. Reached by
// name there is no target token to blame, so the wrong object is INVALID_OPERATION rather than
// the INVALID_ENUM the target forms report for an unaccepted target (GL 4.6 core 8.11).
static Bool ValidateNamedTextureHasParameters(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
const char* caller) {
if (!textureObject) return false;
if (textureObject->GetStorageType() == TextureStorageType::Buffer) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
"The effective target of `texture` has no texture parameters."));
return false;
}
return true;
}
void GetTextureParameteriv(GLuint texture, GLenum pname, GLint* params) { void GetTextureParameteriv(GLuint texture, GLenum pname, GLint* params) {
auto textureObject = GetTextureObjectByName(texture, __func__); auto textureObject = GetTextureObjectByName(texture, __func__);
if (!ValidateNamedTextureHasParameters(textureObject, __func__)) return;
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameteriv_State(target, pname, params); }); WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameteriv_State(target, pname, params); });
} }
void GetTextureParameterfv(GLuint texture, GLenum pname, GLfloat* params) { void GetTextureParameterfv(GLuint texture, GLenum pname, GLfloat* params) {
auto textureObject = GetTextureObjectByName(texture, __func__); auto textureObject = GetTextureObjectByName(texture, __func__);
if (!ValidateNamedTextureHasParameters(textureObject, __func__)) return;
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameterfv_State(target, pname, params); }); WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameterfv_State(target, pname, params); });
} }
void GetTextureParameterIiv(GLuint texture, GLenum pname, GLint* params) { void GetTextureParameterIiv(GLuint texture, GLenum pname, GLint* params) {
auto textureObject = GetTextureObjectByName(texture, __func__); auto textureObject = GetTextureObjectByName(texture, __func__);
if (!ValidateNamedTextureHasParameters(textureObject, __func__)) return;
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameterIiv_State(target, pname, params); }); WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameterIiv_State(target, pname, params); });
} }
void GetTextureParameterIuiv(GLuint texture, GLenum pname, GLuint* params) { void GetTextureParameterIuiv(GLuint texture, GLenum pname, GLuint* params) {
auto textureObject = GetTextureObjectByName(texture, __func__); auto textureObject = GetTextureObjectByName(texture, __func__);
if (!ValidateNamedTextureHasParameters(textureObject, __func__)) return;
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameterIuiv_State(target, pname, params); }); WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameterIuiv_State(target, pname, params); });
} }
@@ -105,12 +105,45 @@ namespace MobileGL::MG_Impl::GLImpl {
return pname == GL_CURRENT_VERTEX_ATTRIB; return pname == GL_CURRENT_VERTEX_ATTRIB;
} }
// The stride a pointer-style call gives its binding point: the argument when it is non-zero,
// otherwise the tightly packed element size (GL 4.6 core 10.3.2). A packed 2_10_10_10 or
// 10F_11F_11F attribute is one 32-bit word regardless of its component count.
static int EffectiveVertexStride(GLsizei stride, GLint size, GLenum type) {
if (stride != 0) return static_cast<int>(stride);
switch (type) {
case GL_INT_2_10_10_10_REV:
case GL_UNSIGNED_INT_2_10_10_10_REV:
case GL_UNSIGNED_INT_10F_11F_11F_REV:
return 4;
default:
break;
}
return static_cast<int>(size * MG_Util::GetGLTypeSize(type));
}
// glBindVertexBuffers / glVertexArrayVertexBuffers take a range of binding points, and a
// range that runs past the last one is INVALID_OPERATION rather than the INVALID_VALUE a
// single out-of-range index gets (GL 4.6 core 10.3.1).
static bool ValidateVertexBindingRange(GLuint first, GLsizei count, const char* funcName) {
if (count < 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName, "count must be non-negative."));
return false;
}
if (static_cast<Uint64>(first) + static_cast<Uint64>(count) >
VertexArrayImpl::GetMaxVertexAttribBindings()) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName,
"first + count exceeds GL_MAX_VERTEX_ATTRIB_BINDINGS."));
return false;
}
return true;
}
static bool ValidateVertexBindingIndex(GLuint bindingindex, const char* funcName) { static bool ValidateVertexBindingIndex(GLuint bindingindex, const char* funcName) {
// Bound by the same dynamic limit as attribute indices: the default attribute -> binding if (bindingindex >= VertexArrayImpl::GetMaxVertexAttribBindings()) {
// mapping is the identity, so a binding point the backend cannot address as an attribute
// would resolve into an attribute the backend must then reject on every draw. Real drivers
// likewise report MAX_VERTEX_ATTRIB_BINDINGS == MAX_VERTEX_ATTRIBS.
if (bindingindex >= VertexArrayImpl::GetMaxVertexAttribs()) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue, ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName,
@@ -155,6 +188,17 @@ namespace MobileGL::MG_Impl::GLImpl {
SharedPtr<MG_State::GLState::VertexArrayObject> GetNamedVertexArrayObject_State(GLuint vaobj, SharedPtr<MG_State::GLState::VertexArrayObject> GetNamedVertexArrayObject_State(GLuint vaobj,
const char* caller) { const char* caller) {
// Name zero is not a vertex array object in a core profile: it names the default vertex
// array, which the by-name (direct state access) entry points never accept. MobileGL keeps a
// real object at index 0 for the compatibility paths, so the generic name validation below
// would otherwise let it through (GL 4.6 core 10.3.1).
if (vaobj == 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
"Vertex array name 0 is not a vertex array object."));
return nullptr;
}
if (!VertexArrayImpl::ValidateVertexArrayName(vaobj)) return nullptr; if (!VertexArrayImpl::ValidateVertexArrayName(vaobj)) return nullptr;
if (!VertexArrayImpl::ValidateVertexArrayObject(vaobj)) return nullptr; if (!VertexArrayImpl::ValidateVertexArrayObject(vaobj)) return nullptr;
return MG_State::pGLContext->GetVertexArrayObject(vaobj); return MG_State::pGLContext->GetVertexArrayObject(vaobj);
@@ -210,7 +254,7 @@ namespace MobileGL::MG_Impl::GLImpl {
DataType dataType = MG_Util::ConvertGLEnumToDataType(type); DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
// Integer path: never normalized, never BGRA/packed (the validator rejects those). // Integer path: never normalized, never BGRA/packed (the validator rejects those).
if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, dataType, false, stride, true)) return; if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, type, dataType, false, stride, true)) return;
auto& vao = MG_State::pGLContext->GetBoundVertexArray(); auto& vao = MG_State::pGLContext->GetBoundVertexArray();
if (!vao) { if (!vao) {
@@ -227,6 +271,7 @@ namespace MobileGL::MG_Impl::GLImpl {
vao->SetAttributeFormat(index, size, dataType, false, stride, offset, true, false); vao->SetAttributeFormat(index, size, dataType, false, stride, offset, true, false);
vao->BindAttributeBuffer(index, vbo); vao->BindAttributeBuffer(index, vbo);
vao->MirrorPointerIntoBinding(index, vbo, offset, EffectiveVertexStride(stride, size, type));
} }
void VertexAttribPointer_State(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, void VertexAttribPointer_State(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride,
@@ -234,7 +279,7 @@ namespace MobileGL::MG_Impl::GLImpl {
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return; if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
DataType dataType = MG_Util::ConvertGLEnumToDataType(type); DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, dataType, normalized == GL_TRUE, stride, false)) if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, type, dataType, normalized == GL_TRUE, stride, false))
return; return;
auto& vao = MG_State::pGLContext->GetBoundVertexArray(); auto& vao = MG_State::pGLContext->GetBoundVertexArray();
@@ -256,6 +301,7 @@ namespace MobileGL::MG_Impl::GLImpl {
const int effectiveSize = isBgra ? 4 : size; const int effectiveSize = isBgra ? 4 : size;
vao->SetAttributeFormat(index, effectiveSize, dataType, normalized, stride, offset, false, isBgra); vao->SetAttributeFormat(index, effectiveSize, dataType, normalized, stride, offset, false, isBgra);
vao->BindAttributeBuffer(index, vbo); vao->BindAttributeBuffer(index, vbo);
vao->MirrorPointerIntoBinding(index, vbo, offset, EffectiveVertexStride(stride, effectiveSize, type));
} }
void BindVertexArray_State(GLuint array) { void BindVertexArray_State(GLuint array) {
@@ -359,6 +405,13 @@ namespace MobileGL::MG_Impl::GLImpl {
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "offset and stride must be non-negative.")); MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "offset and stride must be non-negative."));
return; return;
} }
if (static_cast<Uint>(stride) > VertexArrayImpl::GetMaxVertexAttribStride()) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
"stride exceeds GL_MAX_VERTEX_ATTRIB_STRIDE."));
return;
}
auto bufferObject = GetVertexArrayBufferObject_State(buffer, caller); auto bufferObject = GetVertexArrayBufferObject_State(buffer, caller);
if (buffer != 0 && !bufferObject) return; if (buffer != 0 && !bufferObject) return;
@@ -376,6 +429,7 @@ namespace MobileGL::MG_Impl::GLImpl {
const GLintptr* offsets, const GLsizei* strides) { const GLintptr* offsets, const GLsizei* strides) {
auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayVertexBuffers_State"); auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayVertexBuffers_State");
if (!vao) return; if (!vao) return;
if (!ValidateVertexBindingRange(first, count, "VertexArrayVertexBuffers_State")) return;
for (GLsizei i = 0; i < count; ++i) { for (GLsizei i = 0; i < count; ++i) {
if (!buffers) { if (!buffers) {
VertexBufferBinding_State(vao, first + i, 0, 0, 16, "VertexArrayVertexBuffers_State"); VertexBufferBinding_State(vao, first + i, 0, 0, 16, "VertexArrayVertexBuffers_State");
@@ -389,12 +443,36 @@ namespace MobileGL::MG_Impl::GLImpl {
static void VertexAttribFormatSeparate_State(const SharedPtr<MG_State::GLState::VertexArrayObject>& vao, static void VertexAttribFormatSeparate_State(const SharedPtr<MG_State::GLState::VertexArrayObject>& vao,
GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint attribindex, GLint size, GLenum type, GLboolean normalized,
GLuint relativeoffset, Bool isInteger, const char* caller) { GLuint relativeoffset, Bool isInteger, const char* caller) {
static_cast<void>(caller);
if (!VertexArrayImpl::ValidateVertexAttributeIndex(attribindex)) return; if (!VertexArrayImpl::ValidateVertexAttributeIndex(attribindex)) return;
DataType dataType = MG_Util::ConvertGLEnumToDataType(type); DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
if (!VertexArrayImpl::ValidateVertexAttribPointerParams(attribindex, size, dataType, 0)) return; // The separate-format entry points take the same size/type rules as the pointer ones,
// GL_BGRA included, so they need the full format validation rather than the pointer-only
// subset - that one reports GL_BGRA as an out-of-range size.
if (!VertexArrayImpl::ValidateVertexAttribFormat(attribindex, size, type, dataType, normalized == GL_TRUE, 0,
isInteger))
return;
if (!VertexArrayImpl::ValidateVertexAttribRelativeOffset(relativeoffset)) return;
vao->SetAttributeFormatSeparate(attribindex, size, dataType, normalized, isInteger, relativeoffset); const Bool isBgra = (size == static_cast<GLint>(GL_BGRA));
vao->SetAttributeFormatSeparate(attribindex, isBgra ? 4 : size, dataType, normalized, isInteger,
relativeoffset, isBgra);
}
// The long (64-bit) attribute format. MobileGL has no 64-bit vertex attributes, so nothing is
// recorded; what the entry point owes the application is the parameter validation, which is
// observable through glGetError regardless of whether the format could be used in a draw.
static void VertexAttribLFormatSeparate_State(GLuint attribindex, GLint size, GLenum type,
GLuint relativeoffset) {
if (!VertexArrayImpl::ValidateVertexAttributeIndex(attribindex)) return;
if (!VertexArrayImpl::ValidateVertexAttribLFormat(attribindex, size, type)) return;
if (!VertexArrayImpl::ValidateVertexAttribRelativeOffset(relativeoffset)) return;
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "VertexAttribLFormat",
"64-bit vertex attributes are not supported."));
} }
void VertexArrayAttribFormat_State(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, void VertexArrayAttribFormat_State(GLuint vaobj, GLuint attribindex, GLint size, GLenum type,
@@ -1153,6 +1231,7 @@ namespace MobileGL::MG_Impl::GLImpl {
const GLsizei* strides) { const GLsizei* strides) {
auto vao = GetBoundVertexArrayOrError("BindVertexBuffers"); auto vao = GetBoundVertexArrayOrError("BindVertexBuffers");
if (!vao) return; if (!vao) return;
if (!ValidateVertexBindingRange(first, count, "BindVertexBuffers")) return;
for (GLsizei i = 0; i < count; ++i) { for (GLsizei i = 0; i < count; ++i) {
if (!buffers) { if (!buffers) {
VertexBufferBinding_State(vao, first + i, 0, 0, 16, "BindVertexBuffers"); VertexBufferBinding_State(vao, first + i, 0, 0, 16, "BindVertexBuffers");
@@ -1177,6 +1256,18 @@ namespace MobileGL::MG_Impl::GLImpl {
"VertexAttribIFormat"); "VertexAttribIFormat");
} }
void VertexAttribLFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) {
auto vao = GetBoundVertexArrayOrError("VertexAttribLFormat");
if (!vao) return;
VertexAttribLFormatSeparate_State(attribindex, size, type, relativeoffset);
}
void VertexArrayAttribLFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) {
auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayAttribLFormat");
if (!vao) return;
VertexAttribLFormatSeparate_State(attribindex, size, type, relativeoffset);
}
void VertexAttribBinding(GLuint attribindex, GLuint bindingindex) { void VertexAttribBinding(GLuint attribindex, GLuint bindingindex) {
auto vao = GetBoundVertexArrayOrError("VertexAttribBinding"); auto vao = GetBoundVertexArrayOrError("VertexAttribBinding");
if (!vao) return; if (!vao) return;
@@ -98,6 +98,7 @@ namespace MobileGL::MG_Impl::GLImpl {
void VertexArrayAttribFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, void VertexArrayAttribFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized,
GLuint relativeoffset); GLuint relativeoffset);
void VertexArrayAttribIFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); void VertexArrayAttribIFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset);
void VertexArrayAttribLFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset);
void VertexArrayAttribBinding(GLuint vaobj, GLuint attribindex, GLuint bindingindex); void VertexArrayAttribBinding(GLuint vaobj, GLuint attribindex, GLuint bindingindex);
void VertexArrayBindingDivisor(GLuint vaobj, GLuint bindingindex, GLuint divisor); void VertexArrayBindingDivisor(GLuint vaobj, GLuint bindingindex, GLuint divisor);
void VertexArrayVertexBuffers(GLuint vaobj, GLuint first, GLsizei count, const GLuint* buffers, void VertexArrayVertexBuffers(GLuint vaobj, GLuint first, GLsizei count, const GLuint* buffers,
@@ -107,6 +108,7 @@ namespace MobileGL::MG_Impl::GLImpl {
const GLsizei* strides); const GLsizei* strides);
void VertexAttribFormat(GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); void VertexAttribFormat(GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset);
void VertexAttribIFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); void VertexAttribIFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset);
void VertexAttribLFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset);
void VertexAttribBinding(GLuint attribindex, GLuint bindingindex); void VertexAttribBinding(GLuint attribindex, GLuint bindingindex);
void VertexBindingDivisor(GLuint bindingindex, GLuint divisor); void VertexBindingDivisor(GLuint bindingindex, GLuint divisor);
void VertexAttribDivisor(GLuint index, GLuint divisor); void VertexAttribDivisor(GLuint index, GLuint divisor);
@@ -23,6 +23,18 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
return std::min(static_cast<Uint>(backendLimit), capacity); return std::min(static_cast<Uint>(backendLimit), capacity);
} }
Uint GetMaxVertexAttribBindings() {
return GetMaxVertexAttribs();
}
Uint GetMaxVertexAttribRelativeOffset() {
return 2047;
}
Uint GetMaxVertexAttribStride() {
return 2048;
}
Bool ValidateVertexArrayName(Uint index) { Bool ValidateVertexArrayName(Uint index) {
Bool isValid = MG_State::pGLContext->ValidateVertexArrayName(index); Bool isValid = MG_State::pGLContext->ValidateVertexArrayName(index);
if (!isValid) { if (!isValid) {
@@ -90,9 +102,31 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
return true; return true;
} }
Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, DataType type, Bool normalized, Int stride, Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, GLenum glType, DataType type, Bool normalized,
Bool integerPath) { Int stride, Bool integerPath) {
constexpr const char* fn = "ValidateVertexAttribFormat"; constexpr const char* fn = "ValidateVertexAttribFormat";
// GL_UNSIGNED_INT_10F_11F_11F_REV is a three-component float-path-only packing that has no
// DataType of its own, so it has to be recognised by name before the conversion below turns
// it into Unknown and reports the wrong error (GL 4.6 core 10.3.2).
if (glType == GL_UNSIGNED_INT_10F_11F_11F_REV) {
if (integerPath) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>(
"MG_Impl/GLImpl", fn,
std::format("GL_UNSIGNED_INT_10F_11F_11F_REV is not an integer-path type (attribute {}).",
index)));
return false;
}
if (sizeRaw != 3) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>(
"MG_Impl/GLImpl", fn,
std::format("GL_UNSIGNED_INT_10F_11F_11F_REV requires size 3 (attribute {}).", index)));
return false;
}
}
if (type == DataType::Unknown) { if (type == DataType::Unknown) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum, ErrorCode::InvalidEnum,
@@ -170,4 +204,40 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
} }
return true; return true;
} }
Bool ValidateVertexAttribLFormat(Uint index, GLint size, GLenum type) {
constexpr const char* fn = "ValidateVertexAttribLFormat";
if (size < 1 || size > 4) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>(
"MG_Impl/GLImpl", fn,
std::format("Invalid size {} for attribute {}. Must be 1-4.", size, index)));
return false;
}
// GL 4.6 core 10.3.2: the long form takes GL_DOUBLE and nothing else.
if (type != GL_DOUBLE) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>(
"MG_Impl/GLImpl", fn,
std::format("Type 0x{:X} is not GL_DOUBLE (attribute {}).", type, index)));
return false;
}
return true;
}
Bool ValidateVertexAttribRelativeOffset(Uint relativeOffset) {
const Uint limit = GetMaxVertexAttribRelativeOffset();
if (relativeOffset > limit) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>(
"MG_Impl/GLImpl", "ValidateVertexAttribRelativeOffset",
std::format("relativeoffset {} exceeds GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET ({}).", relativeOffset,
limit)));
return false;
}
return true;
}
} // namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl } // namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl
@@ -15,6 +15,20 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
// capacity). Falls back to the capacity when no backend is active (unit tests). // capacity). Falls back to the capacity when no backend is active (unit tests).
Uint GetMaxVertexAttribs(); Uint GetMaxVertexAttribs();
// GL_MAX_VERTEX_ATTRIB_BINDINGS. The default attribute -> binding mapping is the identity, so a
// binding point that cannot also be an attribute index would resolve into an attribute the
// backend has to reject on every draw; real drivers report the two limits equal as well.
Uint GetMaxVertexAttribBindings();
// GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET. The relative offset is folded into the resolved
// attribute offset in the frontend and never reaches a backend limit, so this is the value the
// spec requires an implementation to support at minimum (GL 4.6 core table 23.63).
Uint GetMaxVertexAttribRelativeOffset();
// GL_MAX_VERTEX_ATTRIB_STRIDE. Like the relative offset above, the stride never reaches a
// backend limit of its own, so this is the spec minimum (GL 4.6 core table 23.63).
Uint GetMaxVertexAttribStride();
Bool ValidateVertexArrayName(Uint index); Bool ValidateVertexArrayName(Uint index);
Bool ValidateVertexArrayObject(Uint index); Bool ValidateVertexArrayObject(Uint index);
Bool ValidateVertexAttributeIndex(Uint index); Bool ValidateVertexAttributeIndex(Uint index);
@@ -22,6 +36,13 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
// Full glVertexAttribPointer / glVertexAttribIPointer format validation, including the packed // Full glVertexAttribPointer / glVertexAttribIPointer format validation, including the packed
// 2_10_10_10 types and GL_BGRA size. sizeRaw is the untranslated GL size (possibly GL_BGRA); // 2_10_10_10 types and GL_BGRA size. sizeRaw is the untranslated GL size (possibly GL_BGRA);
// integerPath selects the glVertexAttribIPointer rules. // integerPath selects the glVertexAttribIPointer rules.
Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, DataType type, Bool normalized, Int stride, Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, GLenum glType, DataType type, Bool normalized,
Bool integerPath); Int stride, Bool integerPath);
// glVertexAttribLFormat / glVertexArrayAttribLFormat: the only accepted type is GL_DOUBLE and
// the size range is 1-4 (GL_BGRA is a float-path size). Separate from the function above
// because the long path shares none of its type or size rules.
Bool ValidateVertexAttribLFormat(Uint index, GLint size, GLenum type);
// Shared by every *Format entry point: INVALID_VALUE once relativeoffset leaves the range the
// implementation advertises.
Bool ValidateVertexAttribRelativeOffset(Uint relativeOffset);
} // namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl } // namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl
@@ -31,6 +31,9 @@ namespace MobileGL::MG_State::GLState {
BindingSlot<BufferObject>& GetBindingSlot(BufferTarget target); BindingSlot<BufferObject>& GetBindingSlot(BufferTarget target);
// For glBindBufferBase / glBindBufferRange // For glBindBufferBase / glBindBufferRange
BindingSlotRange1D<BufferObject>& GetBindingPoint(BufferTarget target, Uint index); BindingSlotRange1D<BufferObject>& GetBindingPoint(BufferTarget target, Uint index);
const BindingSlotRange1D<BufferObject>& GetBindingPoint(BufferTarget target, Uint index) const {
return const_cast<BufferState*>(this)->GetBindingPoint(target, index);
}
constexpr SizeT GetBindingPointCount(const BufferTarget target) const { constexpr SizeT GetBindingPointCount(const BufferTarget target) const {
auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target); auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target);
auto index = std::distance(BufferBindPointTargets.begin(), it); auto index = std::distance(BufferBindPointTargets.begin(), it);
+58
View File
@@ -842,6 +842,64 @@ namespace MobileGL::MG_State {
const auto it = m_transformFeedbackObjects.find(index); const auto it = m_transformFeedbackObjects.find(index);
return it != m_transformFeedbackObjects.end() && it->second.hasCompletedSpan; return it != m_transformFeedbackObjects.end() && it->second.hasCompletedSpan;
} }
void GLContext::CreateTransformFeedbackObject(Uint index) {
// glCreateTransformFeedbacks has no bind step to infer existence from, so the name it
// hands out is already the name of an object (GL 4.6 core 13.2.1).
m_transformFeedbackObjects[index] = {};
m_transformFeedbackObjects[index].everBound = true;
}
Bool GLContext::IsNamedTransformFeedbackActive(Uint index) const {
if (index == m_boundTransformFeedback) return m_transformFeedbackActive;
const auto it = m_transformFeedbackObjects.find(index);
return it != m_transformFeedbackObjects.end() && it->second.active;
}
Bool GLContext::IsNamedTransformFeedbackPaused(Uint index) const {
if (index == m_boundTransformFeedback) return m_transformFeedbackPaused;
const auto it = m_transformFeedbackObjects.find(index);
return it != m_transformFeedbackObjects.end() && it->second.paused;
}
NamedTransformFeedbackBinding GLContext::GetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex) const {
NamedTransformFeedbackBinding result;
if (bufferIndex >= MAX_TRANSFORM_FEEDBACK_BUFFERS) return result;
// The bound object's capture bindings live in the context's own binding points, not in
// the saved copy - that one is only written when the object is swapped out.
if (index == m_boundTransformFeedback) {
const auto& point = m_bufferState.GetBindingPoint(BufferTarget::TransformFeedback, bufferIndex);
result.Buffer = point.GetBoundObject();
result.Range = point.GetRange();
result.HasExplicitRange = point.HasExplicitRange();
return result;
}
const auto it = m_transformFeedbackObjects.find(index);
if (it == m_transformFeedbackObjects.end()) return result;
const auto& saved = it->second.bindings[bufferIndex];
result.Buffer = saved.buffer;
result.Range = saved.range;
result.HasExplicitRange = saved.hasExplicitRange;
return result;
}
void GLContext::SetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex,
const SharedPtr<BufferObject>& buffer, Range1D range,
Bool hasExplicitRange) {
if (bufferIndex >= MAX_TRANSFORM_FEEDBACK_BUFFERS) return;
if (index == m_boundTransformFeedback) {
auto& point = m_bufferState.GetBindingPoint(BufferTarget::TransformFeedback, bufferIndex);
point.Bind(buffer);
if (buffer && hasExplicitRange) {
point.SetRange(range, true);
} else {
point.ClearRange();
}
return;
}
auto& object = m_transformFeedbackObjects[index];
object.bindings[bufferIndex] = {buffer, range, hasExplicitRange};
}
} // namespace GLState } // namespace GLState
// Leak-at-exit storage; see GlobalObjects.cpp. // Leak-at-exit storage; see GlobalObjects.cpp.
+19
View File
@@ -45,6 +45,14 @@ namespace MobileGL {
// translates the result into its own API call. // translates the result into its own API call.
VertexAttribTypeInfo ClassifyVertexAttribType(GLenum glType); VertexAttribTypeInfo ClassifyVertexAttribType(GLenum glType);
// One indexed capture binding of a transform feedback object, as the by-name queries
// report it. An empty Buffer means the binding point is unbound.
struct NamedTransformFeedbackBinding {
SharedPtr<BufferObject> Buffer;
Range1D Range{};
Bool HasExplicitRange = false;
};
class GLContext { class GLContext {
public: public:
GLContext() = default; GLContext() = default;
@@ -299,6 +307,17 @@ namespace MobileGL {
// cannot express: an empty completed span is legal and draws nothing. // cannot express: an empty completed span is legal and draws nothing.
Bool HasTransformFeedbackCompletedSpan(Uint index) const; Bool HasTransformFeedbackCompletedSpan(Uint index) const;
// The by-name (direct state access) view. A named object that happens to be the
// bound one is answered from the live copy, since that is where its state actually
// is until a bind swaps it out.
void CreateTransformFeedbackObject(Uint index);
Bool IsNamedTransformFeedbackActive(Uint index) const;
Bool IsNamedTransformFeedbackPaused(Uint index) const;
NamedTransformFeedbackBinding GetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex) const;
void SetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex,
const SharedPtr<BufferObject>& buffer, Range1D range,
Bool hasExplicitRange);
// Framebuffer // Framebuffer
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers); void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
const SharedPtr<FramebufferObject>& GetFramebufferObject(Uint index); const SharedPtr<FramebufferObject>& GetFramebufferObject(Uint index);
@@ -77,6 +77,26 @@ namespace MobileGL::MG_State::GLState {
BumpAttributeFormatVersion(index); BumpAttributeFormatVersion(index);
} }
void VertexArrayObject::MirrorPointerIntoBinding(Uint index, const SharedPtr<BufferObject>& buffer, SizeT offset,
int effectiveStride) {
if (index >= MAX_VERTEX_ATTRIBS || index >= MAX_VERTEX_ATTRIB_BINDINGS) return;
// glVertexAttribPointer is defined in terms of the binding model (GL 4.6 core 10.3.2): it
// also sets binding point `index` to the buffer, the pointer as the offset, and the
// *effective* stride, and points the attribute at that binding point with relative offset 0.
// The flat attribute view keeps the raw stride, because VERTEX_ATTRIB_ARRAY_STRIDE reports
// that argument verbatim, so the binding point is recorded alongside the resolved attribute
// rather than being resolved into it.
m_attributeBindingIndex[index] = index;
m_attributeRelativeOffset[index] = 0;
auto& binding = m_bindingPoints[index];
binding.Buffer = buffer;
binding.Offset = offset;
binding.Stride = effectiveStride;
binding.Divisor = m_attributes[index].Divisor;
}
void VertexArrayObject::BindAttributeBuffer(Uint index, const SharedPtr<BufferObject>& buffer) { void VertexArrayObject::BindAttributeBuffer(Uint index, const SharedPtr<BufferObject>& buffer) {
if (index >= MAX_VERTEX_ATTRIBS) return; if (index >= MAX_VERTEX_ATTRIBS) return;
@@ -111,6 +131,11 @@ namespace MobileGL::MG_State::GLState {
void VertexArrayObject::SetAttributeDivisor(Uint index, Uint divisor) { void VertexArrayObject::SetAttributeDivisor(Uint index, Uint divisor) {
if (index >= MAX_VERTEX_ATTRIBS) return; if (index >= MAX_VERTEX_ATTRIBS) return;
// glVertexAttribDivisor is VertexBindingDivisor on the attribute's own binding point
// (GL 4.6 core 10.3.2), so the binding-point view has to follow the resolved attribute.
if (index < MAX_VERTEX_ATTRIB_BINDINGS && m_attributeBindingIndex[index] == index) {
m_bindingPoints[index].Divisor = divisor;
}
if (m_attributes[index].Divisor == divisor) return; if (m_attributes[index].Divisor == divisor) return;
m_attributes[index].Divisor = divisor; m_attributes[index].Divisor = divisor;
BumpAttributeFormatVersion(index); BumpAttributeFormatVersion(index);
@@ -187,18 +212,18 @@ namespace MobileGL::MG_State::GLState {
} }
void VertexArrayObject::SetAttributeFormatSeparate(Uint attribIndex, int size, DataType type, Bool normalized, void VertexArrayObject::SetAttributeFormatSeparate(Uint attribIndex, int size, DataType type, Bool normalized,
Bool isInteger, Uint relativeOffset) { Bool isInteger, Uint relativeOffset, Bool isBgra) {
if (attribIndex >= MAX_VERTEX_ATTRIBS) return; if (attribIndex >= MAX_VERTEX_ATTRIBS) return;
if (size < 1 || size > 4) return; if (size < 1 || size > 4) return;
auto& attr = m_attributes[attribIndex]; auto& attr = m_attributes[attribIndex];
if (attr.Size != size || attr.Type != type || attr.Normalized != normalized || attr.IsInteger != isInteger || if (attr.Size != size || attr.Type != type || attr.Normalized != normalized || attr.IsInteger != isInteger ||
attr.IsBgra || m_attributeRelativeOffset[attribIndex] != relativeOffset) { attr.IsBgra != isBgra || m_attributeRelativeOffset[attribIndex] != relativeOffset) {
attr.Size = size; attr.Size = size;
attr.Type = type; attr.Type = type;
attr.Normalized = normalized; attr.Normalized = normalized;
attr.IsInteger = isInteger; attr.IsInteger = isInteger;
attr.IsBgra = false; // the binding-format path (glVertexAttribFormat) does not carry BGRA attr.IsBgra = isBgra;
m_attributeRelativeOffset[attribIndex] = relativeOffset; m_attributeRelativeOffset[attribIndex] = relativeOffset;
BumpAttributeFormatVersion(attribIndex); BumpAttributeFormatVersion(attribIndex);
} }
@@ -64,6 +64,12 @@ namespace MobileGL {
void BindAttributeBuffer(Uint index, const SharedPtr<BufferObject>& buffer); void BindAttributeBuffer(Uint index, const SharedPtr<BufferObject>& buffer);
// Record what the pointer-style API implies for the binding-point view: attribute
// `index` bound to binding point `index` with relative offset 0, and that binding
// point carrying the buffer, the pointer offset and the effective stride.
void MirrorPointerIntoBinding(Uint index, const SharedPtr<BufferObject>& buffer, SizeT offset,
int effectiveStride);
BindingSlot<BufferObject>& GetIndexBufferBindingSlot(); BindingSlot<BufferObject>& GetIndexBufferBindingSlot();
const BindingSlot<BufferObject>& GetIndexBufferBindingSlot() const; const BindingSlot<BufferObject>& GetIndexBufferBindingSlot() const;
@@ -82,7 +88,7 @@ namespace MobileGL {
void SetBindingDivisor(Uint bindingIndex, Uint divisor); void SetBindingDivisor(Uint bindingIndex, Uint divisor);
void SetAttributeBinding(Uint attribIndex, Uint bindingIndex); void SetAttributeBinding(Uint attribIndex, Uint bindingIndex);
void SetAttributeFormatSeparate(Uint attribIndex, int size, DataType type, Bool normalized, void SetAttributeFormatSeparate(Uint attribIndex, int size, DataType type, Bool normalized,
Bool isInteger, Uint relativeOffset); Bool isInteger, Uint relativeOffset, Bool isBgra = false);
// The binding-point view the attributes were resolved from. Kept queryable // The binding-point view the attributes were resolved from. Kept queryable
// because glGetVertexArrayIndexed[64]iv reports it verbatim, and the resolved // because glGetVertexArrayIndexed[64]iv reports it verbatim, and the resolved
@@ -1056,9 +1056,17 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.MaxComputeWorkGroupInvocations = maxComputeWorkGroupInvocations; caps.MaxComputeWorkGroupInvocations = maxComputeWorkGroupInvocations;
caps.MaxShaderStorageBufferBindings = maxShaderStorageBufferBindings; caps.MaxShaderStorageBufferBindings = maxShaderStorageBufferBindings;
caps.MaxTextureBufferSize = maxTextureBufferSize; caps.MaxTextureBufferSize = maxTextureBufferSize;
// Through glesFuncs, like every other capability query here: a bare glGetIntegerv resolves
// to MobileGL's own exported entry point, which answers this pname from the very
// capability table being filled in - so the driver's real alignment never arrived and the
// backend reported an unconstrained offset it cannot honour.
GLint textureBufferOffsetAlignment = 1; GLint textureBufferOffsetAlignment = 1;
glGetIntegerv(GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT, &textureBufferOffsetAlignment); glesFuncs.glGetIntegerv(GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT, &textureBufferOffsetAlignment);
while (glGetError() != GL_NO_ERROR) {} // Core in ES 3.2 and in EXT_texture_buffer; an older context rejects the pname and leaves
// the default in place.
if (glesFuncs.glGetError) {
while (glesFuncs.glGetError() != GL_NO_ERROR) {}
}
caps.TextureBufferOffsetAlignment = std::max(1, textureBufferOffsetAlignment); caps.TextureBufferOffsetAlignment = std::max(1, textureBufferOffsetAlignment);
caps.MaxUniformBufferBindings = maxUniformBufferBindings; caps.MaxUniformBufferBindings = maxUniformBufferBindings;
caps.MaxUniformBlockSize = maxUniformBlockSize; caps.MaxUniformBlockSize = maxUniformBlockSize;
+63 -1
View File
@@ -1,12 +1,16 @@
# Running the OpenGL CTS against MobileGL # Running the OpenGL CTS against MobileGL
This directory contains two supported paths: This directory contains three supported paths:
- Android arm64 / MobileGL EGL: the KHR-GL33 workflow documented below and in - Android arm64 / MobileGL EGL: the KHR-GL33 workflow documented below and in
`skills/gl-cts-on-mobilegl/SKILL.md`. `skills/gl-cts-on-mobilegl/SKILL.md`.
- Windows x64 / MobileGL WGL: the GL30-GL46 pipeline in - Windows x64 / MobileGL WGL: the GL30-GL46 pipeline in
`scripts/wgl_glcts_pipeline.py`, documented by `scripts/wgl_glcts_pipeline.py`, documented by
`skills/wgl-gl-cts-on-mobilegl/SKILL.md`. `skills/wgl-gl-cts-on-mobilegl/SKILL.md`.
- Desktop Linux x64 / MobileGL EGL: `scripts/run_cts_local.py` against the
`mobilegl-desktop` VK-GL-CTS target, documented in "Desktop Linux workflow"
below and in `skills/linux-gl-cts-on-mobilegl/SKILL.md`. This is the path that
needs no device and no GPU.
Windows prerequisites are Git, Python 3.9+, CMake, Visual Studio 2022's Desktop Windows prerequisites are Git, Python 3.9+, CMake, Visual Studio 2022's Desktop
C++ workload, and a Vulkan SDK visible to CMake. DirectVulkan also needs a C++ workload, and a Vulkan SDK visible to CMake. DirectVulkan also needs a
@@ -25,6 +29,64 @@ resumes individual suites after crashes/timeouts, and writes Markdown plus JSON
reports below the printed `runs/<first-16-of-run-fingerprint>` directory. Its reports below the printed `runs/<first-16-of-run-fingerprint>` directory. Its
manifest records provenance and the runner settings used to validate a resume. manifest records provenance and the runner settings used to validate a resume.
## Desktop Linux workflow
The `mobilegl-desktop` target builds `glcts` as an ordinary host executable that
reaches OpenGL only through `libMobileGL.so`. Both backends run headless with no
GPU at all, which makes this the cheapest way to measure a single test group
while working on it.
Apt packages: `mesa-vulkan-drivers` (lavapipe, for DirectVulkan),
`libegl1-mesa-dev` and `libgles2-mesa-dev` (the system EGL/ES that DirectGLES
drives), `libvulkan-dev`, `ninja-build`.
```sh
cmake -S . -B build-linux -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DMOBILEGL_BUILD_TEST=OFF -DMOBILEGL_BUILD_BENCHMARK=OFF
cmake --build build-linux --parallel "$(nproc)"
python tools/cts/scripts/sync_to_cts.py "$CTS"
git -C "$CTS" apply <path-to>/tools/cts/patches/0001-fbo-color-texture-attachment.patch
cmake -S "$CTS" -B "$CTS/build-cts" -G Ninja -DDEQP_TARGET=mobilegl-desktop \
-DCMAKE_BUILD_TYPE=Release
ninja -C "$CTS/build-cts" glcts
python tools/cts/scripts/run_cts_local.py --backend DirectGLES \
--glcts "$CTS/build-cts/external/openglcts/modules/glcts" \
--lib build-linux/libMobileGL.so \
--caselist cases.txt --outdir runs/gles --env EGL_PLATFORM=surfaceless
python tools/cts/scripts/qpa_report.py runs/gles --label DirectGLES
```
`EGL_PLATFORM=surfaceless` is not optional for DirectGLES: without a `/dev/dri`
node Mesa's EGL fails `eglInitialize` on the default display, and MobileGL
reports that as `EGL_BAD_ALLOC` out of `eglCreatePbufferSurface`. DirectVulkan
needs nothing extra - lavapipe exposes `VK_EXT_headless_surface`, which is what
the desktop platform port's pbuffer path requires.
Two known differences from a real GPU, both MobileGL's rather than the harness's:
DirectVulkan reads back zeros from the **default** framebuffer (a user FBO,
renderbuffer- or texture-attached, is correct on both backends), and lavapipe
supports renderbuffer formats that Adreno reports as unsupported, so the Android
runs see `NotSupported` where these do not.
### Reference results: KHR-GL45.direct_state_access
`opengl-cts-4.6.8.1`, the 371 `direct_state_access` cases of the `gl45-main`
mustpass list, lavapipe / Mesa 25.2.8, `--deqp-surface-type=fbo`.
| backend | conformance | strict Pass | Fail | InternalError | Crash |
| --- | ---: | ---: | ---: | ---: | ---: |
| DirectGLES | **74.93%** | 67.39% | 85 | 8 | 0 |
| DirectVulkan | **73.32%** | 73.05% | 88 | 8 | 3 |
`textures_storage_multisample_*` is 54 of what remains on either backend and
needs a feature rather than a fix: a multisample texture has to be attachable to
a framebuffer and then sampleable through `sampler2DMS`, and the array half of
the group additionally needs layered attachments, which the framebuffer
attachment model does not represent yet. `program_pipelines_*` needs
ARB_separate_shader_objects, which is stubbed throughout.
## Android KHR-GL33 workflow ## Android KHR-GL33 workflow
Goal: measure how much of the OpenGL 3.3 core-profile conformance suite MobileGL Goal: measure how much of the OpenGL 3.3 core-profile conformance suite MobileGL
+1
View File
@@ -17,3 +17,4 @@ Each skill is a self-contained package, matching the layout used by
| --- | --- | | --- | --- |
| [gl-cts-on-mobilegl](gl-cts-on-mobilegl/SKILL.md) | Build VK-GL-CTS `glcts` as a standalone Android arm64 binary against MobileGL's own EGL, run KHR-GL33, and report a per-backend OpenGL 3.3 core conformance rate. | | [gl-cts-on-mobilegl](gl-cts-on-mobilegl/SKILL.md) | Build VK-GL-CTS `glcts` as a standalone Android arm64 binary against MobileGL's own EGL, run KHR-GL33, and report a per-backend OpenGL 3.3 core conformance rate. |
| [wgl-gl-cts-on-mobilegl](wgl-gl-cts-on-mobilegl/SKILL.md) | Build MobileGL's Windows x64 WGL drop-in, run GL30-GL46 core CTS against DirectGLES and DirectVulkan, resume safely, and emit validated reports. | | [wgl-gl-cts-on-mobilegl](wgl-gl-cts-on-mobilegl/SKILL.md) | Build MobileGL's Windows x64 WGL drop-in, run GL30-GL46 core CTS against DirectGLES and DirectVulkan, resume safely, and emit validated reports. |
| [linux-gl-cts-on-mobilegl](linux-gl-cts-on-mobilegl/SKILL.md) | Build `glcts` as a desktop Linux host binary against MobileGL, run any CTS group headlessly with no GPU, and report Espryt and Magma separately. The path to reach for while iterating on a fix. |
@@ -0,0 +1,159 @@
---
name: linux-gl-cts-on-mobilegl
description: Run the Khronos OpenGL CTS (VK-GL-CTS glcts) against MobileGL on desktop Linux with no GPU and no device, and report a per-backend conformance rate for Espryt (DirectGLES) and Magma (DirectVulkan). Use when measuring or iterating on the conformance of one test group, when a fix needs a before/after number, or when neither an Android device nor a Windows GPU box is available.
---
# OpenGL CTS on MobileGL (desktop Linux)
## Overview
`glcts` is built as an ordinary x86-64 host executable that reaches OpenGL only
through `libMobileGL.so`, using the `mobilegl-desktop` VK-GL-CTS target and the
`tcu::Platform` port in `tools/cts/platform/`. Nothing links `libGL` or `libEGL`,
so a result is unambiguously MobileGL's.
Both backends run headless on software rendering, so this needs no GPU at all:
- **Espryt** (`DirectGLES`) drives Mesa's OpenGL ES through the system EGL.
- **Magma** (`DirectVulkan`) runs on lavapipe, whose `VK_EXT_headless_surface`
is what the desktop platform port's pbuffer path requires.
Always report the two backends **separately**. They are different
implementations of the same front end, they fail different cases, and a single
combined number hides which one a change moved.
## Prerequisites
```sh
sudo apt-get install -y ninja-build cmake libvulkan-dev \
libegl1-mesa-dev libgles2-mesa-dev mesa-vulkan-drivers
```
`mesa-vulkan-drivers` is what installs lavapipe; without it DirectVulkan has no
ICD and `eglInitialize` fails inside the backend. A C++23 toolchain is required
(GCC 13+, or Clang 20+ — Clang 18 defines `__cpp_concepts` as 201907L, which
switches libstdc++'s `<expected>` off and the build fails in
`MG_Util/ShaderTranspiler/Types.h`).
```sh
export MG=<path-to-MobileGL-worktree>
export CTS=<path-to-VK-GL-CTS-checkout>
```
## Step 1 — build libMobileGL.so
```sh
git -C "$MG" submodule update --init --recursive
python3 "$MG/3rdparty/glslang/update_glslang_sources.py" # SPIRV-Tools; ENABLE_OPT is forced on
cmake -S "$MG" -B "$MG/build-linux" -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DMOBILEGL_BUILD_TEST=OFF -DMOBILEGL_BUILD_BENCHMARK=OFF \
-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
cmake --build "$MG/build-linux" --parallel "$(nproc)"
```
Add `-DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=FALSE` when iterating. The
Release configuration turns LTO on, which makes every relink cost minutes for no
behavioural difference; a conformance number is identical either way.
## Step 2 — get VK-GL-CTS and build glcts
Use a release tag so the mustpass list, and therefore the reported rate, is
citable.
```sh
git -C "$CTS" checkout opengl-cts-4.6.8.1
python3 "$CTS/external/fetch_sources.py"
python3 "$MG/tools/cts/scripts/sync_to_cts.py" "$CTS"
git -C "$CTS" apply "$MG/tools/cts/patches/0001-fbo-color-texture-attachment.patch"
cmake -S "$CTS" -B "$CTS/build-cts" -G Ninja -DDEQP_TARGET=mobilegl-desktop \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
ninja -C "$CTS/build-cts" glcts
```
Confirm the configure output says `*** Using MobileGL desktop target`. Budget a
couple of hours for the `glcts` link on a small machine; it is a one-time cost
that ccache makes cheap afterwards.
If `fetch_sources.py` dies with `HTTP Error 403` it is an egress policy blocking
the GitHub archive downloads (zlib, libpng), not a broken checkout: `git clone`
still works, so clone the package at the tag the script pins into
`external/<pkg>/src` by hand — for libpng also copy
`scripts/pnglibconf.h.prebuilt` to `src/pnglibconf.h`, which is what the
script's post-extract step does.
## Step 3 — run, once per backend
```sh
cd "$MG"
for BACKEND in DirectGLES DirectVulkan; do
python3 tools/cts/scripts/run_cts_local.py --backend "$BACKEND" \
--glcts "$CTS/build-cts/external/openglcts/modules/glcts" \
--lib build-linux/libMobileGL.so \
--caselist cases.txt --outdir "runs/${BACKEND}" \
--env EGL_PLATFORM=surfaceless
python3 tools/cts/scripts/qpa_report.py "runs/${BACKEND}" --label "$BACKEND"
done
```
`cases.txt` is any subset of a mustpass list. For one group, filter the list
rather than running the whole suite:
```sh
grep direct_state_access \
"$CTS"/external/openglcts/data/gl_cts/data/mustpass/gl/khronos_mustpass/main/gl45-main.txt \
> cases.txt
```
`run_cts_local.py` re-invokes `glcts` with only the cases that have no result
yet, so a crash costs one case rather than the run, and it records the case that
was open when the process died as `Crash`. `qpa_report.py` scores `Pass`,
`NotSupported` and the warning statuses as non-failures, the way Khronos scores
a submission, and reports the strict `Pass`-only rate alongside.
## Required flags, and why
| Flag | Why it is not optional |
| --- | --- |
| `--env EGL_PLATFORM=surfaceless` | For DirectGLES. With no `/dev/dri` node Mesa's EGL fails `eglInitialize` on the default display, and MobileGL surfaces that as `EGL_BAD_ALLOC` out of `eglCreatePbufferSurface` — which points at the wrong call entirely. Harmless for DirectVulkan, so pass it to both. |
| `--deqp-surface-type=fbo` (the runner's default) | DirectVulkan reads back zeros from the **default** framebuffer. dEQP verifies nearly everything through `glReadPixels`, so rendering to the surface scores Magma near zero for a reason unrelated to conformance. Use it for both backends so the two numbers stay comparable. |
| `--deqp-terminate-on-device-lost=disable` (supplied by the runner) | Its default calls `glGetGraphicsResetStatus()` after every case. That is GL 4.5 / `KHR_robustness`, absent from what MobileGL exports, so the pointer is null and the process segfaults on the first case. |
## What this environment does and does not tell you
Reproducible here, and MobileGL's own rather than a driver quirk:
- DirectVulkan's default-framebuffer readback returns zeros; a user FBO,
renderbuffer- or texture-attached, is correct on both backends. This is the
same defect the Android runs work around, so it can be debugged without a
phone.
Different from a real GPU, so do not read conformance into it:
- lavapipe supports renderbuffer formats Adreno reports as unsupported, so the
Android runs see `NotSupported` where these do not, and vice versa.
- Backend limits differ. `GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT` is 16 on llvmpipe
and on lavapipe; a device that reports 1 will not exercise the same paths.
- Everything is a software rasterizer, so a case that fails only under real
timing or real tiling will not fail here.
## Reference results: KHR-GL45.direct_state_access
`opengl-cts-4.6.8.1`, the 371 `direct_state_access` cases of the `gl45-main`
mustpass list, Mesa 25.2.8, `--deqp-surface-type=fbo`.
| backend | renderer | conformance | strict Pass | Fail | InternalError | Crash |
| --- | --- | ---: | ---: | ---: | ---: | ---: |
| DirectGLES | Espryt | **74.93%** | 67.39% | 85 | 8 | 0 |
| DirectVulkan | Magma | **73.32%** | 73.05% | 88 | 8 | 3 |
## Contents
platform/tcuMobileGLPlatform.{cpp,hpp} dEQP tcu::Platform for MobileGL
targets/mobilegl-desktop.cmake VK-GL-CTS target (-DDEQP_TARGET=mobilegl-desktop)
scripts/sync_to_cts.py inject the port into a CTS checkout
scripts/run_cts_local.py crash-resuming local-host runner
scripts/qpa_report.py .qpa -> conformance rate
@@ -0,0 +1,4 @@
interface:
display_name: "OpenGL CTS on MobileGL (desktop Linux)"
short_description: "Run VK-GL-CTS glcts against MobileGL headlessly and report Espryt and Magma separately"
default_prompt: "Use $linux-gl-cts-on-mobilegl to run the selected OpenGL CTS group against MobileGL on this Linux host and report the conformance rate for DirectGLES (Espryt) and DirectVulkan (Magma) separately."