[Fix] (ShaderTranspiler): enforce the layout(binding) range rule for samplers, images and uniform/atomic-counter blocks, not only for SSBOs

This commit is contained in:
2026-08-27 05:37:17 -04:00
parent 0e4302b399
commit be7bf21eb8
16 changed files with 568 additions and 23 deletions
@@ -40,6 +40,15 @@ namespace MobileGL::MG_Impl::GLImpl {
// below resolves it exactly once and hands it to both users.
static Bool ValidateResolvedProgramForDraw(const SharedPtr<MG_State::GLState::ProgramObject>& currentProgram,
const char* functionName) {
// "If there is no current program object or bound program pipeline object, the results of
// a draw are UNDEFINED" - and undefined is not an error (GL 4.6 core 7.3, ES 3.1 7.3).
// The draw is dropped, silently, which is one of the shapes "undefined" is allowed to
// take; recording INVALID_OPERATION here is not, and es31cSeparateShaderObjsTests'
// StateInteraction reads exactly that error back after useProgram(0) + bindProgramPipeline(0).
// A DISPATCH is the opposite rule ("INVALID_OPERATION if there is no active program for
// the compute shader stage"), which is why this lives on the draw path and not in the
// shared ValidateProgramForExecution below.
if (!currentProgram) return false;
if (!ValidateProgramForExecution(currentProgram, functionName)) return false;
// GL 4.6 core 7.4.1, the pipeline validation rule every vertex-transferring command
+22 -3
View File
@@ -768,7 +768,10 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = programObject->GetBinaryRetrievableHint() ? GL_TRUE : GL_FALSE;
break;
case GL_PROGRAM_SEPARABLE:
*params = programObject->GetSeparable() ? GL_TRUE : GL_FALSE;
// The LATCHED flag, not the live one: glProgramParameteri's write takes effect at the
// next link (GL 4.6 core 7.3), so a program told to be separable and then never
// linked still reports GL_FALSE.
*params = programObject->GetLinkedSeparable() ? GL_TRUE : GL_FALSE;
break;
// The geometry and tessellation link properties (GL 4.6 core table 23.35). Same shape as
@@ -951,6 +954,16 @@ namespace MobileGL::MG_Impl::GLImpl {
GLint GetUniformLocation_State(GLuint program, const GLchar* name) {
auto& programObject = TryToGetProgramObject(program);
if (!programObject) return -1;
// GL 4.6 core 7.6: "INVALID_OPERATION is generated if program has not been successfully
// linked". Answering -1 silently is not the same thing - the conformance suite reads the
// error, not the location.
if (!programObject->GetLinkStatus()) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"program " + std::to_string(program) + " is not linked."));
return -1;
}
auto loc = programObject->GetUniformLocation(name);
MGLOG_D("%s: loc %02d = %s", __func__, loc, name);
return loc;
@@ -1363,11 +1376,13 @@ namespace MobileGL::MG_Impl::GLImpl {
template <GLsizei ItemCount, typename T>
void ProgramUniformv_State(GLuint program, GLint location, GLsizei count, T* value) {
if (location == -1) return;
auto& programObject = TryToGetProgramObject(program);
if (!programObject) return;
// The link check comes BEFORE the location == -1 early-out, not after. GL 4.6 core 7.6
// makes an unlinked program INVALID_OPERATION regardless of the location, and -1 is
// exactly the location an application holds after glGetUniformLocation on such a program -
// so checking -1 first swallowed the very case the rule exists for.
if (!programObject->GetLinkStatus()) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
@@ -1375,6 +1390,10 @@ namespace MobileGL::MG_Impl::GLImpl {
"program " + std::to_string(program) + " is not linked."));
return;
}
// "If location is equal to -1, the data passed in will be silently ignored and the
// specified uniform variable will not be changed" - after the program itself has been
// found acceptable.
if (location == -1) return;
for (GLint offset = 0; offset < count; offset++) {
if (offset > 0 && !programObject->UniformLocationsAliasSameUniform(location, location + offset)) {
@@ -192,6 +192,15 @@ namespace MobileGL::MG_Impl::GLImpl {
std::format("Program {} has not been linked successfully.", program));
return;
}
// GL 4.6 core 7.4: "INVALID_OPERATION is generated if program was not linked with its
// PROGRAM_SEPARABLE status set". The LATCHED flag is the one that decides - a program
// whose live flag was cleared after a separable link is still a legal stage, and a
// program whose live flag was set after a non-separable link is not.
if (!programObject->GetLinkedSeparable()) {
RecordPipelineError(ErrorCode::InvalidOperation, __func__,
std::format("Program {} was not linked as a separable program.", program));
return;
}
}
const GLbitfield selected = stages == GL_ALL_SHADER_BITS ? kAllStageBits : stages;
+35 -1
View File
@@ -2649,6 +2649,22 @@ namespace MobileGL::MG_Impl::GLImpl {
}
}
// GL 4.6 core 8.9 / GL_EXT_texture_buffer: the two TARGET-taking forms (glTexBuffer,
// glTexBufferRange) accept exactly GL_TEXTURE_BUFFER, and anything else is GL_INVALID_ENUM.
// Checked up front rather than left to fall out of "the bound object is not a buffer texture"
// deeper in, because that path's error code depends on which entry point took it - the
// name-taking DSA forms owe GL_INVALID_OPERATION for the same shape - and because for some
// targets it did not reach that check at all. esextcTextureBufferErrors walks every other
// texture target through both entry points and reads the code back each time.
static Bool ValidateBufferTextureTarget(GLenum target, const char* caller) {
if (target == GL_TEXTURE_BUFFER) return true;
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
std::format("target 0x{:X} is not GL_TEXTURE_BUFFER.", target)));
return false;
}
static void AttachBufferToTexture(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
GLenum internalformat, GLuint buffer, GLintptr offset, SizeT size,
const char* caller) {
@@ -2731,9 +2747,21 @@ namespace MobileGL::MG_Impl::GLImpl {
TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
// ===================== Error Checking ==============================
if (!ValidateBufferTextureTarget(target, __func__)) return;
if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return;
if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return;
// TODO: make sure `internalformat` is in one of supported format for TexBuffer
// The sized-format table a buffer texture accepts (GL 4.6 core table 8.15). The DSA and
// range forms have always run this through AttachBufferToTexture; this one carried a TODO
// instead, so glTexBuffer(GL_TEXTURE_BUFFER, GL_DEPTH_COMPONENT32F, ...) succeeded.
if (!IsBufferTextureInternalFormat(internalformat)) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>(
"MG_Impl/GLImpl", __func__,
std::format("internalformat 0x{:X} is not one of the sized formats a buffer texture accepts.",
internalformat)));
return;
}
// GL 3.3 core 3.8.5: buffer zero detaches any buffer from the buffer texture - only a
// nonzero name that is not an existing buffer object is an error. This is reachable on
// the default buffer texture (bound whenever texture 0 is bound to GL_TEXTURE_BUFFER),
@@ -2757,6 +2785,8 @@ namespace MobileGL::MG_Impl::GLImpl {
// silent no-op; the slot is never empty now that every unit/target holds its default.
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
if (textureObject->GetStorageType() != TextureStorageType::Buffer) {
// Defensive: the target gate above already rejected every target but GL_TEXTURE_BUFFER,
// whose binding slot only ever holds buffer textures.
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
@@ -6593,6 +6623,10 @@ namespace MobileGL::MG_Impl::GLImpl {
}
void TexBufferRange(GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size) {
// The TARGET-taking form owes GL_INVALID_ENUM for a target that is not GL_TEXTURE_BUFFER,
// where the name-taking DSA forms below owe GL_INVALID_OPERATION for the corresponding
// "that texture is not a buffer texture". Same shared body, different gate.
if (!ValidateBufferTextureTarget(target, __func__)) return;
AttachBufferToTexture(GetBoundBufferTexture(target, __func__), internalformat, buffer, offset,
static_cast<SizeT>(size < 0 ? 0 : size), __func__);
}