mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +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.
163 lines
8.1 KiB
C++
163 lines
8.1 KiB
C++
// MobileGL - MobileGL/MG_Impl/GLImpl/Buffer/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/GLToStr/GLEnumConverter.h>
|
|
#include <MG_Util/Converters/MGToGL/BufferEnumConverter.h>
|
|
#include <MG_Util/Converters/MGToStr/BufferEnumConverter.h>
|
|
|
|
namespace MobileGL::MG_Impl::GLImpl::BufferImpl {
|
|
Bool ValidateBufferTarget(BufferTarget target) {
|
|
if (target == BufferTarget::Unknown) {
|
|
using namespace MG_Util;
|
|
String bufferTargetStr = ConvertBufferTargetToString(target);
|
|
String glTargetStr = ConvertGLEnumToString(ConvertBufferTargetToGLEnum(target));
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum, MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl/BufferImpl", "ValidateBufferTarget",
|
|
std::format("Target {} ({}) is not valid.", bufferTargetStr, glTargetStr)));
|
|
return false;
|
|
}
|
|
|
|
if (target == BufferTarget::Index && MG_State::pGLContext->GetBoundVertexArray() == nullptr) {
|
|
MG_State::pGLContext->RecordError(ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl",
|
|
"ValidateBufferTarget",
|
|
"No vertex array object is bound."));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Bool ValidateBufferBindingPointTarget(BufferTarget target) {
|
|
if (target != BufferTarget::Uniform && target != BufferTarget::AtomicCounter &&
|
|
target != BufferTarget::TransformFeedback && target != BufferTarget::ShaderStorage) {
|
|
using namespace MG_Util;
|
|
String bufferTargetStr = ConvertBufferTargetToString(target);
|
|
String glTargetStr = ConvertGLEnumToString(ConvertBufferTargetToGLEnum(target));
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum, MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl/BufferImpl", "ValidateBufferTarget",
|
|
std::format("Target {} ({}) is not valid.", bufferTargetStr, glTargetStr)));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
namespace {
|
|
// The GL-visible number of indexed binding points for `target`.
|
|
SizeT GetBufferBindingPointLimit(BufferTarget target) {
|
|
SizeT pointCount = MG_State::pGLContext->GetBufferBindingPointCount(target);
|
|
if (target == BufferTarget::ShaderStorage && MG_Backend::pActiveBackendObject) {
|
|
const Int backendCount =
|
|
MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxShaderStorageBufferBindings;
|
|
pointCount = std::min(pointCount, static_cast<SizeT>(std::max(backendCount, 0)));
|
|
}
|
|
if (target == BufferTarget::TransformFeedback) {
|
|
// GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS bounds the indexed capture
|
|
// binding points in GL 3.3 (no ARB_transform_feedback3).
|
|
pointCount = std::min<SizeT>(pointCount, 4);
|
|
}
|
|
return pointCount;
|
|
}
|
|
} // namespace
|
|
|
|
Bool ValidateBufferBindingPointRange(BufferTarget target, Uint first, GLsizei count, const char* funcName) {
|
|
if (count < 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl", funcName,
|
|
"count must be non-negative."));
|
|
return false;
|
|
}
|
|
const SizeT pointCount = GetBufferBindingPointLimit(target);
|
|
if (static_cast<Uint64>(first) + static_cast<Uint64>(count) > static_cast<Uint64>(pointCount)) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl/BufferImpl", funcName,
|
|
std::format("first + count ({} + {}) exceeds the {} indexed binding points of target {}.", first,
|
|
count, pointCount, MG_Util::ConvertBufferTargetToString(target))));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Bool ValidateBufferBindingPointIndex(BufferTarget target, Uint index) {
|
|
const SizeT pointCount = GetBufferBindingPointLimit(target);
|
|
|
|
if (index < pointCount) {
|
|
return true;
|
|
}
|
|
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl", "ValidateBufferBindingPointIndex",
|
|
std::format("Binding point index {} is out of range for target {}.", index,
|
|
MG_Util::ConvertBufferTargetToString(target))));
|
|
return false;
|
|
}
|
|
|
|
Bool ValidateBufferName(Uint index, Bool allowZero) {
|
|
if (index == 0) {
|
|
if (allowZero) return true;
|
|
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl", "ValidateBufferName",
|
|
"Buffer name 0 is not valid."));
|
|
return false;
|
|
}
|
|
Bool isValid = MG_State::pGLContext->ValidateBufferName(index);
|
|
if (isValid) return true;
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidOperation,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl", "ValidateBufferName",
|
|
std::format("Buffer name {} is not valid.", index)));
|
|
return false;
|
|
}
|
|
|
|
Bool ValidateBufferUsage(BufferUsage usage) {
|
|
if (usage != BufferUsage::Unknown) {
|
|
return true;
|
|
}
|
|
using namespace MG_Util;
|
|
String bufferUsageStr = ConvertBufferUsageToString(usage);
|
|
String glUsageStr = ConvertGLEnumToString(ConvertBufferUsageToGLEnum(usage));
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>(
|
|
"MG_Impl/GLImpl/BufferImpl", "ValidateBufferUsage",
|
|
std::format("Usage {} ({}) is not one of the allowable values.", bufferUsageStr, glUsageStr)));
|
|
return false;
|
|
}
|
|
|
|
Bool ValidateBufferMappingAccess(Flags<BufferMappingAccessBit> accessBits) {
|
|
// An empty mask is a legal value for a bitfield - it just fails the rule that a mapping
|
|
// must ask for read or write access, which is INVALID_OPERATION and belongs to the callers
|
|
// (both of them check it immediately after this). Rejecting it here as INVALID_ENUM
|
|
// reported the wrong error and hid theirs.
|
|
const auto validBits = BufferMappingAccessBit::Read | BufferMappingAccessBit::Write |
|
|
BufferMappingAccessBit::InvalidateRange | BufferMappingAccessBit::InvalidateBuffer |
|
|
BufferMappingAccessBit::FlushExplicit | BufferMappingAccessBit::Unsynchronized |
|
|
BufferMappingAccessBit::Persistent | BufferMappingAccessBit::Coherent;
|
|
|
|
if ((accessBits & validBits) != accessBits) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl/BufferImpl", "ValidateBufferMappingAccess",
|
|
"Access bits cannot contain invalid flags."));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
} // namespace MobileGL::MG_Impl::GLImpl::BufferImpl
|