mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
[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:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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__);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user