[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.
This commit is contained in:
BZLZHH
2026-08-02 01:56:22 -04:00
parent 9dff24f3e1
commit 6de38c666c
4 changed files with 112 additions and 7 deletions
@@ -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<unsigned int> rectLoweredSpirv;
if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImagesForEssl(*effectiveSpirv,
rectLoweredSpirv) &&
!rectLoweredSpirv.empty()) {
effectiveSpirv = &rectLoweredSpirv;
}
MG_Util::ShaderTranspiler::SpvcSession spvcSession(*effectiveSpirv,
MG_Util::ShaderTranspiler::SessionUsageBit::Transpile);
+11 -7
View File
@@ -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;
@@ -363,6 +363,86 @@ namespace MobileGL {
return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options);
}
bool ShaderCompiler::LowerRectImagesForEssl(const Vector<Uint32>& inputBinary,
Vector<uint32_t>& 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<SizeT> rectDimWordOffsets;
Vector<SizeT> 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<spv::Op>(instructionWord & 0xffffu);
if (wordCount == 0 || offset + wordCount > inputBinary.size()) {
return false;
}
if (opcode == spv::Op::OpTypeImage && wordCount >= kTypeImageMinWordCount) {
if (static_cast<spv::Dim>(inputBinary[offset + kTypeImageDimWordIndex]) == spv::Dim::Rect) {
rectDimWordOffsets.push_back(offset + kTypeImageDimWordIndex);
}
} else if (opcode == spv::Op::OpCapability && wordCount >= 2) {
const auto capability = static_cast<spv::Capability>(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<Uint32>(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<Uint32>(spv::Capability::Shader);
}
return true;
}
bool ShaderCompiler::RebaseInstanceIndexForVulkan(const Vector<Uint32>& inputBinary,
Vector<uint32_t>& outputBinary) {
using namespace spvtools;
@@ -43,6 +43,16 @@ namespace MobileGL {
// devices lacking GL_NV_shader_noperspective_interpolation. See EmulateNoPerspectivePass.
static bool EmulateNoPerspectiveForEssl(const Vector<Uint32>& inputBinary,
Vector<uint32_t>& 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<Uint32>& inputBinary,
Vector<uint32_t>& 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,