mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-13 06:38:31 +09:00
First wave of the advertised-extension CTS campaign (targeted caselist: the
glcts groups of every extension both backends advertise, 4867 cases across the
KHR-GL41..46 namespaces). All frontend, shared by both backends:
- Non-square float matrix uniforms actually upload: glUniformMatrix{2x3,3x2,
2x4,4x2,3x4,4x3}fv and the six glProgramUniformMatrix* twins were
validate-only no-ops; they now write column-at-a-time at the global UBO's
16-byte std140 column stride, honouring transpose. glUniformMatrix2fv had
the sibling bug - mat2 written as 4 contiguous floats put column 1 at byte
8 instead of 16. The readback path only ever un-padded mat3, so
glGetUniformfv is fixed for mat2, mat3x2 (previously mis-gathered) and
every non-square shape, with the bounds check widened to the padded span.
- glBindBufferRange validates offset/size at last: size <= 0, offset < 0,
SSBO and UBO offset alignment, transform-feedback offset AND size
multiples of 4 - all before any state write (a negative offset used to
reach Range1D unchecked). glBindBuffersRange inherits per element, with
the ARB_multi_bind up-front [first, first+count) checks added to the
BindBuffersBase/Range and BindSamplers prologues.
- BufferSubData's second, wrong mapped-overlap test deleted (it rejected
every write at or after a mapped range's start, mapped or not); the state
layer's assert relaxed to the same half-open intersection the frontend
checks. BufferStorage error precedence fixed: no-bound-buffer now beats
bad-size/flags.
- glSamplerParameteri accepts the full GL_NEVER..GL_ALWAYS compare-func
range (NEVER/LESS/EQUAL were rejected by a wrong lower bound).
glBindSampler's unit gate uses GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS instead
of the frontend array capacity, shared with glBindSamplers by construction.
- Getters: GL_MAX_SHADER_STORAGE_BLOCK_SIZE in glGetIntegerv; atomic-counter
buffer limits; all 11 per-unit GL_TEXTURE_BINDING_* plus GL_SAMPLER_BINDING
in glGetIntegeri_v; GL_VERTEX_ATTRIB_BINDING/_RELATIVE_OFFSET across the
vertex-attrib query family; glGetFloati_v/glGetDoublei_v implemented (were
stubs); KHR_debug limits raised to spec floors.
- glCreateShader records INVALID_ENUM for an unknown type (it previously
handed out a usable name with no error at all); glCreateShaderProgramv
validates count up front. glDispatchCompute/Indirect validate work-group
counts, offset alignment and indirect-buffer presence.
- glVertexAttribIFormat & friends take a positive integer-type whitelist -
GL_FLOAT/GL_HALF_FLOAT/GL_DOUBLE/GL_FIXED no longer slip through as
integer attributes.
Gate (headless Mesa, default config = async on): 570/570 unit at default and
with the kill switch; ext caselist Espryt 76.29% -> 77.87% (+81 fixed, 6
crashes -> 0, the whole list now runs in one glcts process), Magma 75.94% ->
77.58% (+80 fixed, 0 newly broken); KHR-GL33 full mustpass lost nothing
(9884/9886, the 2 known Mesa-drift failures); retrace smoke clean (the
bsl-GLES miss is the documented golden drift, bit-identical on the pristine
baseline). The 4 DirectGLES direct_state_access.renderbuffers_storage* cases
that turned red are a PRE-EXISTING GL_FRAMEBUFFER_SRGB cross-test leak,
A/B-proven on an unpatched 2e6fc1ff build - wave 1 removed the two accidental
maskers (a crash partition and a failing case whose error path reset the
state). Fixing the leak itself is queued.
268 lines
12 KiB
C++
268 lines
12 KiB
C++
// MobileGL - MobileGL/MG_Impl/GLImpl/VertexArray/Validators.cpp
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
|
|
#include "Validators.h"
|
|
#include <MG_Backend/BackendObjects.h>
|
|
#include <MG_State/GLState/Core.h>
|
|
#include <MG_State/GLState/ErrorState/Error.h>
|
|
#include <MG_Util/Converters/MGToGL/DataTypeConverter.h>
|
|
#include <MG_Util/Converters/MGToStr/DataTypeConverter.h>
|
|
|
|
namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
|
Uint GetMaxVertexAttribs() {
|
|
constexpr Uint capacity = static_cast<Uint>(MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS);
|
|
if (!MG_Backend::pActiveBackendObject) return capacity;
|
|
|
|
const Int backendLimit = MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxVertexAttribs;
|
|
if (backendLimit <= 0) return 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 isValid = MG_State::pGLContext->ValidateVertexArrayName(index);
|
|
if (!isValid) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ValidateVertexArrayName",
|
|
std::format("Vertex array name {} is not valid.", index)));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Bool ValidateVertexArrayObject(Uint index) {
|
|
if (!MG_State::pGLContext->ValidateVertexArrayObject(index)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ValidateVertexArrayObject",
|
|
std::format("Vertex array object {} does not exist.", index)));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Bool ValidateVertexAttributeIndex(Uint index) {
|
|
const Uint maxVertexAttribs = GetMaxVertexAttribs();
|
|
if (index >= maxVertexAttribs) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", "ValidateVertexAttributeIndex",
|
|
std::format("Attribute index {} exceeds maximum of {}.", index, maxVertexAttribs - 1)));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Bool ValidateVertexAttribPointerParams(Uint index, SizeT size, DataType type, Int stride) {
|
|
if (size < 1 || size > 4) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", "ValidateVertexAttribPointerParams",
|
|
std::format("Invalid size {} for attribute {}. Must be 1-4.", size, index)));
|
|
return false;
|
|
}
|
|
|
|
if (type == DataType::Unknown) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", "ValidateVertexAttribPointerParams",
|
|
std::format("Invalid type {} for attribute {}.", MG_Util::ConvertDataTypeToString(type), index)));
|
|
return false;
|
|
}
|
|
|
|
if (stride < 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", "ValidateVertexAttribPointerParams",
|
|
std::format("Negative stride {} is not allowed for attribute {}.", stride, index)));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, GLenum glType, DataType type, Bool normalized,
|
|
Int stride, Bool integerPath) {
|
|
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) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", fn,
|
|
std::format("Invalid type for attribute {}.", index)));
|
|
return false;
|
|
}
|
|
|
|
const bool isPacked = (type == DataType::Int2101010Rev || type == DataType::Uint2101010Rev);
|
|
// The packed 2_10_10_10 types are float-normalizing only; glVertexAttribIPointer never accepts them.
|
|
if (integerPath && isPacked) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", fn,
|
|
std::format("glVertexAttribIPointer does not accept packed 2_10_10_10 types (attribute {}).",
|
|
index)));
|
|
return false;
|
|
}
|
|
|
|
// The integer path takes exactly the six signed/unsigned integer types (GL 4.6
|
|
// core 10.3.2): BYTE, UNSIGNED_BYTE, SHORT, UNSIGNED_SHORT, INT, UNSIGNED_INT.
|
|
// A blacklist could not express that: GL_FLOAT, GL_HALF_FLOAT,
|
|
// GL_DOUBLE and GL_FIXED all convert to a perfectly valid DataType, so they slipped
|
|
// through and were recorded as integer attributes.
|
|
if (integerPath) {
|
|
switch (type) {
|
|
case DataType::Int8:
|
|
case DataType::Uint8:
|
|
case DataType::Int16:
|
|
case DataType::Uint16:
|
|
case DataType::Int32:
|
|
case DataType::Uint32:
|
|
break;
|
|
default:
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", fn,
|
|
std::format("Type is not an integer vertex attribute type (attribute {}).", index)));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (sizeRaw == static_cast<GLint>(GL_BGRA)) {
|
|
// GL_BGRA is a float-path-only size: it needs GL_UNSIGNED_BYTE or a 2_10_10_10 type and
|
|
// normalized == GL_TRUE. On the integer path it is simply an out-of-range size.
|
|
if (integerPath) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", fn,
|
|
std::format("GL_BGRA size is not valid for glVertexAttribIPointer (attribute {}).", index)));
|
|
return false;
|
|
}
|
|
if (!(type == DataType::Uint8 || isPacked)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", fn,
|
|
std::format("GL_BGRA size requires GL_UNSIGNED_BYTE or a 2_10_10_10 type (attribute {}).",
|
|
index)));
|
|
return false;
|
|
}
|
|
if (!normalized) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", fn,
|
|
std::format("GL_BGRA size requires normalized == GL_TRUE (attribute {}).", index)));
|
|
return false;
|
|
}
|
|
} else {
|
|
// Size-range (INVALID_VALUE) takes precedence over the packed-size-must-be-4 rule.
|
|
if (sizeRaw < 1 || sizeRaw > 4) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", fn,
|
|
std::format("Invalid size {} for attribute {}. Must be 1-4 or GL_BGRA.", sizeRaw, index)));
|
|
return false;
|
|
}
|
|
if (isPacked && sizeRaw != 4) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl", fn,
|
|
std::format("A 2_10_10_10 type requires size 4 or GL_BGRA (attribute {}).", index)));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (stride < 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", fn,
|
|
std::format("Negative stride {} for attribute {}.", stride, index)));
|
|
return false;
|
|
}
|
|
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
|