[Fix, Test] (ShaderTranspiler, DirectGLES): drop the inert readonly+writeonly pair a storage block cannot carry in ESSL

This commit is contained in:
2026-08-21 21:43:42 -04:00
parent f1b4a5e07f
commit 7b0f443d3a
4 changed files with 112 additions and 0 deletions
@@ -459,6 +459,43 @@ namespace MobileGL {
SPVC_CHK_RETURN
}
spvc_result SpvcSession::RelaxReadWriteExclusiveStorageBuffers() {
if (!(usage & SessionUsageBit::Transpile)) return SPVC_ERROR_INVALID_ARGUMENT;
SPVC_CHK_INIT
const spvc_reflected_resource* list = nullptr;
size_t count = 0;
SPVC_CHK_RESULT(spvc_resources_get_resource_list_for_type(
resources, SPVC_RESOURCE_TYPE_STORAGE_BUFFER, &list, &count));
for (size_t i = 0; i < count; ++i) {
const spvc_reflected_resource& resource = list[i];
// The variable itself, for a block the application qualified as a whole.
if (spvc_compiler_has_decoration(compiler, resource.id, SpvDecorationNonReadable) &&
spvc_compiler_has_decoration(compiler, resource.id, SpvDecorationNonWritable)) {
spvc_compiler_unset_decoration(compiler, resource.id, SpvDecorationNonReadable);
spvc_compiler_unset_decoration(compiler, resource.id, SpvDecorationNonWritable);
}
// ...and each member, which is where the qualifiers usually sit and where
// SPIRV-Cross reads them from before hoisting the ones every member shares.
const spvc_type blockType = spvc_compiler_get_type_handle(compiler, resource.base_type_id);
if (blockType == nullptr) continue;
const unsigned memberCount = spvc_type_get_num_member_types(blockType);
for (unsigned member = 0; member < memberCount; ++member) {
if (!spvc_compiler_has_member_decoration(compiler, resource.base_type_id, member,
SpvDecorationNonReadable) ||
!spvc_compiler_has_member_decoration(compiler, resource.base_type_id, member,
SpvDecorationNonWritable)) {
continue;
}
spvc_compiler_unset_member_decoration(compiler, resource.base_type_id, member,
SpvDecorationNonReadable);
spvc_compiler_unset_member_decoration(compiler, resource.base_type_id, member,
SpvDecorationNonWritable);
}
}
SPVC_CHK_RETURN
}
spvc_result SpvcSession::Compile(const char** result) {
if (!(usage & SessionUsageBit::Transpile)) return SPVC_ERROR_INVALID_ARGUMENT;
SPVC_CHK_INIT
@@ -139,6 +139,27 @@ namespace MobileGL {
// must keep reaching the driver (the frontend's own glBindFragDataLocationIndexed
// path already emits only non-zero indices for the same reason).
spvc_result DropDefaultFragmentOutputColorIndex();
// Drops `readonly` and `writeonly` from every shader storage block - and every
// block member - that carries BOTH of them.
//
// GL 4.6 core 4.10 lets a buffer variable be declared readonly AND writeonly at
// once: it then cannot be read or written at all, and the only thing left that
// it can be used for is `.length()`. The pair is therefore inert by
// construction - the frontend has already rejected any access to it - so
// dropping it cannot change what the shader does.
//
// Emitting it does change whether the shader EXISTS. SPIRV-Cross hoists the
// qualifiers every member shares onto the block, and Mesa's ES compiler rejects
// that spelling outright ("Interface block sets both readonly and writeonly",
// verified on Mesa 26.1.4 llvmpipe with no MobileGL in the process, against the
// exact source this transpiler emitted). The stage then never compiles, the
// program links without it, and every dispatch or draw is a silent no-op -
// which is how KHR-GL43.shader_storage_buffer_object.basic-readonly-writeonly
// read back 0 instead of the array length.
//
// A block carrying only ONE of the two is left exactly as it is: those really do
// constrain the accesses the shader makes, and the driver is entitled to know.
spvc_result RelaxReadWriteExclusiveStorageBuffers();
spvc_result Compile(const char** result);
const SpvcMetadata& GetMetadata() const;
const char* GetLastErrorString() const;