From 6de38c666c4dafa73cf11213cd74f943c8ac95b0 Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Sun, 2 Aug 2026 01:56:22 -0400 Subject: [PATCH] [Feat] (DirectGLES): support rectangle textures where the emulation is exact ES has no rectangle target and no rectangle sampler, so DirectGLES declared GL_TEXTURE_RECTANGLE unsupported outright: the texture was never synced or bound, and SPIRV-Cross refused the shader ("Rectangle textures are not supported on OpenGL ES") which left the whole program unlinkable. A rectangle texture is a single-level, clamped 2D texture whose only real difference is that its lookups take non-normalized coordinates. Where every use takes *integer* texel coordinates - texelFetch, textureSize - that difference does not exist at all, and the two are the same thing. So: - A new SPIR-V pass rewrites Dim::Rect image types to Dim::2D before transpiling, and restates the rectangle capabilities as Shader. It declines any module containing a normalized-coordinate lookup rather than emitting something subtly wrong; SPIRV-Cross then rejects that module exactly as before, so nothing that used to work changes and nothing new renders wrongly. - The target maps to GL_TEXTURE_2D for storage, uploads and binding, alongside the existing 1D and 1D-array emulation. Fixes KHR-GL31.texture_size_promotion.functional outright, which takes GL31 to 100% conformance. GL32/GL33 advance past their rectangle cases to a separate GL_RGB16 multisample issue. No regressions across texture_swizzle, shaders30, texture_lod_*, framebuffer_blit, packed_depth_stencil, transform_feedback, clip_distance or draw_buffers; DirectVulkan re-verified unaffected. --- MobileGL/MG_Backend/DirectGLES/Managers.cpp | 11 +++ MobileGL/MG_Backend/DirectGLES/Managers.h | 18 +++-- .../ShaderTranspiler/ShaderCompiler.cpp | 80 +++++++++++++++++++ .../MG_Util/ShaderTranspiler/ShaderCompiler.h | 10 +++ 4 files changed, 112 insertions(+), 7 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 7244d4c7..0e3efb6c 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -3344,6 +3344,17 @@ namespace MobileGL::MG_Backend::DirectGLES { effectiveSpirv = &noperspectiveSpirv; } + // ES has no rectangle sampler, and SPIRV-Cross refuses the whole module rather + // than approximating one. Where every use takes integer texel coordinates a + // rectangle image is indistinguishable from a 2D one, so rewrite the type and let + // it through; the pass declines anything it cannot convert exactly. + Vector rectLoweredSpirv; + if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImagesForEssl(*effectiveSpirv, + rectLoweredSpirv) && + !rectLoweredSpirv.empty()) { + effectiveSpirv = &rectLoweredSpirv; + } + MG_Util::ShaderTranspiler::SpvcSession spvcSession(*effectiveSpirv, MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 0c8101e2..a3278219 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -270,18 +270,21 @@ namespace MobileGL::MG_Backend::DirectGLES { namespace TextureImpl { inline Bool IsSupportedTextureTarget(TextureTarget target) { - // Rectangle textures need non-normalized sampling ES cannot express; everything else is - // either native or emulated (1D -> 2D with height 1, 1D array -> 2D array, see - // MapToBackendTextureTarget). SPIRV-Cross already emits the matching ESSL samplers and - // coordinate padding for 1D/1D-array shaders. - return target != TextureTarget::TextureRectangle; + // Every desktop-only target is stored on an ES one; see MapToBackendTextureTarget. + (void)target; + return true; } - // ES has no 1D targets: 1D textures are stored as 2D (height 1) and 1D arrays as 2D arrays - // (height 1, layers in depth). Must match SPIRV-Cross's ES 1D-as-2D shader emulation. + // ES has none of the desktop-only targets: 1D textures are stored as 2D (height 1), 1D + // arrays as 2D arrays (height 1, layers in depth), and rectangle textures as plain 2D - + // they are single-level and already clamp, so only the non-normalized coordinates differ. + // Must match the shader-side emulation: SPIRV-Cross handles 1D/1D-array itself, and + // ShaderCompiler::LowerRectImagesForEssl rewrites rectangle images (declining any module + // whose lookups are not integer-coordinate, which SPIRV-Cross then still rejects). inline TextureTarget MapToBackendTextureTarget(TextureTarget target) { switch (target) { case TextureTarget::Texture1D: + case TextureTarget::TextureRectangle: return TextureTarget::Texture2D; case TextureTarget::Texture1DArray: return TextureTarget::Texture2DArray; @@ -297,6 +300,7 @@ namespace MobileGL::MG_Backend::DirectGLES { inline GLenum ConvertTextureUploadTargetToBackendGLEnum(TextureUploadTarget uploadTarget) { switch (uploadTarget) { case TextureUploadTarget::Texture1D: + case TextureUploadTarget::TextureRectangle: return GL_TEXTURE_2D; case TextureUploadTarget::Texture1DArray: return GL_TEXTURE_2D_ARRAY; diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp index 9ceff58c..c2b1c510 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp @@ -363,6 +363,86 @@ namespace MobileGL { return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options); } + bool ShaderCompiler::LowerRectImagesForEssl(const Vector& inputBinary, + Vector& outputBinary) { + constexpr SizeT kSpirvHeaderWordCount = 5; + // OpTypeImage: [0] opcode/wordcount, [1] result id, [2] sampled type, [3] Dim, ... + constexpr SizeT kTypeImageDimWordIndex = 3; + constexpr SizeT kTypeImageMinWordCount = 9; + outputBinary.clear(); + if (inputBinary.size() < kSpirvHeaderWordCount || inputBinary[0] != spv::MagicNumber) { + return false; + } + + Vector rectDimWordOffsets; + Vector rectCapabilityWordOffsets; + Bool hasNormalizedCoordinateLookup = false; + for (SizeT offset = kSpirvHeaderWordCount; offset < inputBinary.size();) { + const Uint32 instructionWord = inputBinary[offset]; + const SizeT wordCount = instructionWord >> 16u; + const auto opcode = static_cast(instructionWord & 0xffffu); + if (wordCount == 0 || offset + wordCount > inputBinary.size()) { + return false; + } + + if (opcode == spv::Op::OpTypeImage && wordCount >= kTypeImageMinWordCount) { + if (static_cast(inputBinary[offset + kTypeImageDimWordIndex]) == spv::Dim::Rect) { + rectDimWordOffsets.push_back(offset + kTypeImageDimWordIndex); + } + } else if (opcode == spv::Op::OpCapability && wordCount >= 2) { + const auto capability = static_cast(inputBinary[offset + 1]); + if (capability == spv::Capability::SampledRect || + capability == spv::Capability::ImageRect) { + rectCapabilityWordOffsets.push_back(offset + 1); + } + } else { + switch (opcode) { + // Everything that takes normalized coordinates. Tracing each one back to + // its image type would let a module mix a normalized 2D lookup with a + // rectangle fetch, but the extra reach is not worth the risk of getting + // the trace wrong: decline the whole module instead. + case spv::Op::OpImageSampleImplicitLod: + case spv::Op::OpImageSampleExplicitLod: + case spv::Op::OpImageSampleDrefImplicitLod: + case spv::Op::OpImageSampleDrefExplicitLod: + case spv::Op::OpImageSampleProjImplicitLod: + case spv::Op::OpImageSampleProjExplicitLod: + case spv::Op::OpImageSampleProjDrefImplicitLod: + case spv::Op::OpImageSampleProjDrefExplicitLod: + case spv::Op::OpImageGather: + case spv::Op::OpImageDrefGather: + case spv::Op::OpImageSparseSampleImplicitLod: + case spv::Op::OpImageSparseSampleExplicitLod: + case spv::Op::OpImageSparseSampleDrefImplicitLod: + case spv::Op::OpImageSparseSampleDrefExplicitLod: + case spv::Op::OpImageSparseGather: + case spv::Op::OpImageSparseDrefGather: + hasNormalizedCoordinateLookup = true; + break; + default: + break; + } + } + offset += wordCount; + } + + if (rectDimWordOffsets.empty() || hasNormalizedCoordinateLookup) { + return false; + } + + outputBinary.assign(inputBinary.begin(), inputBinary.end()); + for (const SizeT dimWordOffset : rectDimWordOffsets) { + outputBinary[dimWordOffset] = static_cast(spv::Dim::Dim2D); + } + // The rectangle capabilities describe types that no longer exist. Shader is always + // declared by a graphics module, so restating it keeps the word count intact + // without leaving a capability SPIRV-Cross would key off. + for (const SizeT capabilityWordOffset : rectCapabilityWordOffsets) { + outputBinary[capabilityWordOffset] = static_cast(spv::Capability::Shader); + } + return true; + } + bool ShaderCompiler::RebaseInstanceIndexForVulkan(const Vector& inputBinary, Vector& outputBinary) { using namespace spvtools; diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h index 4f282fb8..db1fbcb2 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h @@ -43,6 +43,16 @@ namespace MobileGL { // devices lacking GL_NV_shader_noperspective_interpolation. See EmulateNoPerspectivePass. static bool EmulateNoPerspectiveForEssl(const Vector& inputBinary, Vector& outputBinary); + // Rewrites rectangle images (Dim::Rect) to plain 2D so SPIRV-Cross can emit ESSL + // for them at all - it refuses outright ("Rectangle textures are not supported on + // OpenGL ES"), which left the whole program unlinkable. Only valid while every use + // of the image takes integer texel coordinates (texelFetch / textureSize), where a + // rectangle target and a 2D target are indistinguishable; a normalized-coordinate + // lookup would also need its coordinates divided by the texture size, so the pass + // declines those modules instead of emitting something subtly wrong. Returns false + // when it changed nothing or cannot safely convert. DirectGLES only. + static bool LowerRectImagesForEssl(const Vector& inputBinary, + Vector& outputBinary); // Rebases loads of the InstanceIndex builtin to (InstanceIndex - BaseInstance) so // shaders see GL's zero-based gl_InstanceID. Vertex shaders only; DirectVulkan // backend only (glslang's relaxed mode aliases gl_InstanceID to gl_InstanceIndex,