[Merge] (DirectGLES, GLState, ShaderTranspiler): land GL43 wave5 with its two new passes inside the L2 boundary

This commit is contained in:
2026-08-21 00:43:39 -04:00
49 changed files with 4535 additions and 233 deletions
+37 -7
View File
@@ -199,11 +199,37 @@ namespace MobileGL::MG_Impl::GLImpl {
return false;
}
const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw();
// GL 4.6 core 10.1: the tessellation pipeline's only input primitive is GL_PATCHES, and
// GL_PATCHES has no meaning without it. Both directions are INVALID_OPERATION, and
// neither was implemented - which is two of the four sites
// KHR-GL43.transform_feedback.api_errors_test checks with one shared message string.
// The EVALUATION stage is what decides: a control stage cannot run without one, and a
// program carrying only an evaluation stage still tessellates, through GL's
// fixed-function pass-through control stage (11.2.2).
const Bool tessellationActive =
currentProgram && currentProgram->GetShaderIndexByStage(ShaderStage::TessEval) >= 0;
if (tessellationActive && mode != GL_PATCHES) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>(
"MG_Impl/GLImpl", functionName,
"A program with a tessellation evaluation shader can only be drawn with GL_PATCHES."));
return false;
}
if (!tessellationActive && mode == GL_PATCHES) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
"GL_PATCHES requires an active tessellation evaluation shader."));
return false;
}
// A geometry stage only accepts the primitive types that decompose into its declared
// input primitive (GL 4.6 core 11.3.1); anything else is INVALID_OPERATION. GL_PATCHES
// is the tessellation pipeline's input and reaches the geometry stage already
// converted, so it is not constrained here.
const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw();
const GLenum gsInput = currentProgram ? currentProgram->GetGeometryInputType() : GL_NONE;
if (gsInput != GL_NONE && mode != GL_PATCHES) {
Bool compatible = false;
@@ -239,13 +265,17 @@ namespace MobileGL::MG_Impl::GLImpl {
// While transform feedback is active the draw's primitive type must match
// the feedback primitive mode (GL 3.3 core 13.2.2). With a geometry shader
// the constraint moves to the shader's output primitive type instead, so
// the draw mode itself is unconstrained here. A paused span is exempt: it
// captures nothing, so there is nothing for the mode to be incompatible with
// (GL 4.6 core 13.2.3).
// the draw mode itself is unconstrained here - and a TESSELLATION EVALUATION
// stage relocates it exactly the same way (GL 4.6 core 13.2.2 names both):
// what is captured is the tessellator's output primitive, and the draw mode
// can only ever be GL_PATCHES. A paused span is exempt: it captures nothing,
// so there is nothing for the mode to be incompatible with (GL 4.6 core 13.2.3).
const auto& feedbackProgram = MG_State::pGLContext->GetTransformFeedbackProgram();
const Bool feedbackModeIsProgramDriven =
feedbackProgram && (feedbackProgram->GetShaderIndexByStage(ShaderStage::Geometry) >= 0 ||
feedbackProgram->GetShaderIndexByStage(ShaderStage::TessEval) >= 0);
if (MG_State::pGLContext->IsTransformFeedbackActive() &&
!MG_State::pGLContext->IsTransformFeedbackPaused() &&
!(MG_State::pGLContext->GetTransformFeedbackProgram() &&
MG_State::pGLContext->GetTransformFeedbackProgram()->GetShaderIndexByStage(ShaderStage::Geometry) >= 0)) {
!MG_State::pGLContext->IsTransformFeedbackPaused() && !feedbackModeIsProgramDriven) {
const GLenum feedbackMode = MG_State::pGLContext->GetTransformFeedbackPrimitiveMode();
Bool compatible = false;
switch (feedbackMode) {
@@ -528,6 +528,10 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_UNIFORM_ARRAY_STRIDE:
case GL_UNIFORM_MATRIX_STRIDE:
case GL_UNIFORM_IS_ROW_MAJOR:
// GL 4.2 / ARB_shader_atomic_counters adds this one to the accepted set. Leaving it
// out did not merely lose the answer: the leftover GL_INVALID_ENUM is what made
// KHR-GL43.shader_atomic_counters.basic-program-query force a FAIL.
case GL_UNIFORM_ATOMIC_COUNTER_BUFFER_INDEX:
break;
default:
MG_State::pGLContext->RecordError(
@@ -583,6 +587,11 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_UNIFORM_IS_ROW_MAJOR:
params[i] = programObject->GetActiveUniformIsRowMajor(idx);
break;
case GL_UNIFORM_ATOMIC_COUNTER_BUFFER_INDEX:
// Index into the GL_ACTIVE_ATOMIC_COUNTER_BUFFERS list, -1 for every uniform
// that is not an atomic counter (GL 4.6 core table 7.6).
params[i] = programObject->GetActiveUniformAtomicCounterBufferIndex(idx);
break;
default:
break;
}
+31 -16
View File
@@ -3750,19 +3750,34 @@ namespace MobileGL::MG_Impl::GLImpl {
return GetCopyImageLevelSize(endpoint.Texture, uploadTarget, level);
}
// How far the region's z axis may reach. It does not mean the same thing on every target
// GL 4.6 core 18.3.2 accepts: on a CUBE MAP it selects among the six faces, which this
// frontend keeps as six separate one-slice upload targets - so the level's own extent
// says 1 and the real bound is 6. A cube-map ARRAY is one upload target whose depth
// already counts layer-faces, and a 1D array carries its layers on y (which is where GL
// puts them for this entry point too), so both are answered by the level extent.
Int GetCopyImageEndpointLayerCount(const MG_Backend::CopyImageEndpoint& endpoint,
const IntVec3& levelSize) {
if (!endpoint.IsRenderbuffer() && endpoint.Texture &&
endpoint.Texture->GetTarget() == TextureTarget::TextureCubeMap) {
return 6;
// The per-axis extent of one endpoint's image AS THIS ENTRY POINT ADDRESSES IT, which is
// not always the level extent this frontend stores.
//
// GL 4.6 core 18.3.2 treats EVERY array texture as a stack of slices addressed by z, and
// gives a 1D array an image height of 1. This frontend stores a 1D array the way
// glTexImage2D(GL_TEXTURE_1D_ARRAY, w, layers) writes it instead - layers on y - so the
// two views have to be told apart here. Measuring y against the LAYER count is what let
// srcY = 14 on a 16-wide, 16-layer 1D array come back GL_NO_ERROR
// (KHR-GL43.copy_image.exceeding_boundaries, the src_test_case y variants); the CTS is
// unambiguous about the convention, forcing height = 1 for 1D and 1D_ARRAY and listing
// 1D_ARRAY as multilayer.
//
// A CUBE MAP is the other target whose z bound is not the level extent: this frontend
// keeps its six faces as six separate one-slice upload targets, so the level says 1 and
// the real bound is 6. A cube-map ARRAY is one upload target whose depth already counts
// layer-faces, and every remaining target is answered by the level extent verbatim.
IntVec3 GetCopyImageEndpointRegionBounds(const MG_Backend::CopyImageEndpoint& endpoint,
const IntVec3& levelSize) {
const TextureTarget target = (!endpoint.IsRenderbuffer() && endpoint.Texture)
? endpoint.Texture->GetTarget()
: TextureTarget::Unknown;
if (target == TextureTarget::TextureCubeMap) {
return {levelSize.x(), levelSize.y(), 6};
}
return std::max(levelSize.z(), 1);
if (target == TextureTarget::Texture1DArray) {
return {levelSize.x(), 1, std::max(levelSize.y(), 1)};
}
return {levelSize.x(), levelSize.y(), std::max(levelSize.z(), 1)};
}
// GL 4.6 core 18.3.2 requires INVALID_VALUE when the region exceeds either image's
@@ -3780,9 +3795,9 @@ namespace MobileGL::MG_Impl::GLImpl {
// reject a copy GL allows. Every caller has already established that the level
// exists and that the image is complete, so this is a belt-and-braces guard.
if (levelSize.x() <= 0 || levelSize.y() <= 0) return true;
const Int layers = GetCopyImageEndpointLayerCount(endpoint, levelSize);
if (x >= 0 && y >= 0 && z >= 0 && static_cast<Int64>(x) + width <= levelSize.x() &&
static_cast<Int64>(y) + height <= levelSize.y() && static_cast<Int64>(z) + depth <= layers) {
const IntVec3 bounds = GetCopyImageEndpointRegionBounds(endpoint, levelSize);
if (x >= 0 && y >= 0 && z >= 0 && static_cast<Int64>(x) + width <= bounds.x() &&
static_cast<Int64>(y) + height <= bounds.y() && static_cast<Int64>(z) + depth <= bounds.z()) {
return true;
}
MG_State::pGLContext->RecordError(
@@ -3791,7 +3806,7 @@ namespace MobileGL::MG_Impl::GLImpl {
"MG_Impl/GLImpl", "ValidateCopyImageSubData_State",
std::format("The {} region [{}, {}, {}] + [{} x {} x {}] does not fit inside the {} x {} x {} "
"image.",
endpointName, x, y, z, width, height, depth, levelSize.x(), levelSize.y(), layers)));
endpointName, x, y, z, width, height, depth, bounds.x(), bounds.y(), bounds.z())));
return false;
}
} // namespace
@@ -514,15 +514,17 @@ namespace MobileGL::MG_Impl::GLImpl {
// recorded DataType is always Float64 - what IsLong adds is that this is the *unconverted* form,
// as opposed to VertexAttribFormat(GL_DOUBLE), which asks for a float conversion.
//
// Whether the backend can FEED it is detected, not assumed: DirectVulkan needs shaderFloat64,
// and DirectGLES can never have it at all. What that costs is the ARRAY, not the call: GL 4.6
// core 10.3.2 defines no error for a well-formed glVertexAttribLFormat, and a GL 4.3 context
// has 64-bit attributes in core, so declining the call would be non-conformant and would make
// the four pure state queries (VERTEX_ATTRIB_ARRAY_SIZE / _TYPE / _LONG / _RELATIVE_OFFSET)
// unanswerable (KHR-GL43.vertex_attrib_binding.basic-state1/3). The format is therefore
// RECORDED here and the enabled array is dropped at draw instead - loudly, once, naming the
// reason. The matching startup POST row is in MG_Util/SelfTest/DriverPost.cpp; the draw-side
// drop is DirectGLES/Managers.cpp and, on DirectVulkan, VertexInputStateFactory's Float64 case.
// Whether the backend can FEED it at full precision is detected, not assumed: DirectVulkan
// needs shaderFloat64, and DirectGLES can never have it at all. What that costs is PRECISION,
// not the call and no longer the array: GL 4.6 core 10.3.2 defines no error for a well-formed
// glVertexAttribLFormat, and a GL 4.3 context has 64-bit attributes in core, so declining the
// call would be non-conformant and would make the four pure state queries
// (VERTEX_ATTRIB_ARRAY_SIZE / _TYPE / _LONG / _RELATIVE_OFFSET) unanswerable
// (KHR-GL43.vertex_attrib_binding.basic-state1/3). The format is therefore RECORDED here and
// the array is NARROWED to float32 at draw, matching the fp64 demotion every shader already
// gets (DemoteFloat64Pass) - loudly, once, naming the cost. The matching startup POST row is in
// MG_Util/SelfTest/DriverPost.cpp; the draw-side narrowing is DirectGLES/Managers.cpp and, on
// DirectVulkan, VertexInputStateFactory's Float64 case.
static void VertexAttribLFormatSeparate_State(const SharedPtr<MG_State::GLState::VertexArrayObject>& vao,
GLuint attribindex, GLint size, GLenum type,
GLuint relativeoffset) {
@@ -534,9 +536,9 @@ namespace MobileGL::MG_Impl::GLImpl {
!MG_Backend::pActiveBackendObject->GetDynamicParameters().SupportsFloat64VertexAttributes) {
MGLOG_W_ONCE("VertexAttribLFormat: attribute %u asked for a 64-bit (GL_DOUBLE) format, but this "
"backend has no double-precision vertex attribute support - the format is recorded "
"and queryable, but the array will be DROPPED at draw and the attribute will read "
"its generic current value; see the \"64-bit vertex attributes\" / \"shaderFloat64\" "
"POST row for what that costs",
"and queryable, and the array is FETCHED AT FLOAT32 PRECISION at draw (the same "
"narrowing the shader's dvec inputs already get); see the \"64-bit vertex "
"attributes\" / \"shaderFloat64\" POST row for what that costs",
attribindex);
}