mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[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:
@@ -3344,6 +3344,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
effectiveSpirv = &noperspectiveSpirv;
|
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::SpvcSession spvcSession(*effectiveSpirv,
|
||||||
MG_Util::ShaderTranspiler::SessionUsageBit::Transpile);
|
MG_Util::ShaderTranspiler::SessionUsageBit::Transpile);
|
||||||
|
|
||||||
|
|||||||
@@ -270,18 +270,21 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
namespace TextureImpl {
|
namespace TextureImpl {
|
||||||
inline Bool IsSupportedTextureTarget(TextureTarget target) {
|
inline Bool IsSupportedTextureTarget(TextureTarget target) {
|
||||||
// Rectangle textures need non-normalized sampling ES cannot express; everything else is
|
// Every desktop-only target is stored on an ES one; see MapToBackendTextureTarget.
|
||||||
// either native or emulated (1D -> 2D with height 1, 1D array -> 2D array, see
|
(void)target;
|
||||||
// MapToBackendTextureTarget). SPIRV-Cross already emits the matching ESSL samplers and
|
return true;
|
||||||
// coordinate padding for 1D/1D-array shaders.
|
|
||||||
return target != TextureTarget::TextureRectangle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ES has no 1D targets: 1D textures are stored as 2D (height 1) and 1D arrays as 2D arrays
|
// ES has none of the desktop-only targets: 1D textures are stored as 2D (height 1), 1D
|
||||||
// (height 1, layers in depth). Must match SPIRV-Cross's ES 1D-as-2D shader emulation.
|
// 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) {
|
inline TextureTarget MapToBackendTextureTarget(TextureTarget target) {
|
||||||
switch (target) {
|
switch (target) {
|
||||||
case TextureTarget::Texture1D:
|
case TextureTarget::Texture1D:
|
||||||
|
case TextureTarget::TextureRectangle:
|
||||||
return TextureTarget::Texture2D;
|
return TextureTarget::Texture2D;
|
||||||
case TextureTarget::Texture1DArray:
|
case TextureTarget::Texture1DArray:
|
||||||
return TextureTarget::Texture2DArray;
|
return TextureTarget::Texture2DArray;
|
||||||
@@ -297,6 +300,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
inline GLenum ConvertTextureUploadTargetToBackendGLEnum(TextureUploadTarget uploadTarget) {
|
inline GLenum ConvertTextureUploadTargetToBackendGLEnum(TextureUploadTarget uploadTarget) {
|
||||||
switch (uploadTarget) {
|
switch (uploadTarget) {
|
||||||
case TextureUploadTarget::Texture1D:
|
case TextureUploadTarget::Texture1D:
|
||||||
|
case TextureUploadTarget::TextureRectangle:
|
||||||
return GL_TEXTURE_2D;
|
return GL_TEXTURE_2D;
|
||||||
case TextureUploadTarget::Texture1DArray:
|
case TextureUploadTarget::Texture1DArray:
|
||||||
return GL_TEXTURE_2D_ARRAY;
|
return GL_TEXTURE_2D_ARRAY;
|
||||||
|
|||||||
@@ -363,6 +363,86 @@ namespace MobileGL {
|
|||||||
return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options);
|
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,
|
bool ShaderCompiler::RebaseInstanceIndexForVulkan(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
|
|||||||
@@ -43,6 +43,16 @@ namespace MobileGL {
|
|||||||
// devices lacking GL_NV_shader_noperspective_interpolation. See EmulateNoPerspectivePass.
|
// devices lacking GL_NV_shader_noperspective_interpolation. See EmulateNoPerspectivePass.
|
||||||
static bool EmulateNoPerspectiveForEssl(const Vector<Uint32>& inputBinary,
|
static bool EmulateNoPerspectiveForEssl(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary);
|
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
|
// Rebases loads of the InstanceIndex builtin to (InstanceIndex - BaseInstance) so
|
||||||
// shaders see GL's zero-based gl_InstanceID. Vertex shaders only; DirectVulkan
|
// shaders see GL's zero-based gl_InstanceID. Vertex shaders only; DirectVulkan
|
||||||
// backend only (glslang's relaxed mode aliases gl_InstanceID to gl_InstanceIndex,
|
// backend only (glslang's relaxed mode aliases gl_InstanceID to gl_InstanceIndex,
|
||||||
|
|||||||
Reference in New Issue
Block a user