mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-15 22:13:24 +09:00
[Fix] (Espryt): sample cube-array shadow textures at LOD 0 with texture() outside the fragment stage
This commit is contained in:
@@ -12943,6 +12943,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
source = ProcessOutColorLocations(source);
|
||||
source = ForceFlatIntegerVaryings(source, glShaderType);
|
||||
source = BroadcastLegacyFragColor(std::move(source), glShaderType, m_fragColorBroadcastCount);
|
||||
source = LowerZeroGradCubeArrayShadowLookups(source, glShaderType);
|
||||
source = EmulateTextureLodBias(source, ShouldAvoidExplicitLodBiasOnAngleLlvmpipe());
|
||||
if (ClampsUnsignedMultisampleFetchesAtRuntime()) {
|
||||
source = ClampUnsignedMultisampleFetches(source);
|
||||
|
||||
@@ -2057,6 +2057,56 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return result;
|
||||
}
|
||||
|
||||
String LowerZeroGradCubeArrayShadowLookups(const String& glslCode, GLenum shaderType) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (shaderType == GL_FRAGMENT_SHADER || glslCode.find("samplerCubeArrayShadow") == String::npos ||
|
||||
glslCode.find("textureGrad") == String::npos) {
|
||||
return glslCode;
|
||||
}
|
||||
// Every name declared with the type: uniforms, and the parameters of functions the
|
||||
// sampler is passed to.
|
||||
static const std::regex declRegex(R"(samplerCubeArrayShadow\s+([A-Za-z_][A-Za-z0-9_]*))");
|
||||
UnorderedMap<String, Bool> names;
|
||||
for (std::sregex_iterator it(glslCode.begin(), glslCode.end(), declRegex), end; it != end; ++it) {
|
||||
names.emplace((*it)[1].str(), true);
|
||||
}
|
||||
if (names.empty()) return glslCode;
|
||||
|
||||
static constexpr const char* kName = "textureGrad";
|
||||
static constexpr SizeT kNameLength = 11;
|
||||
String result = glslCode;
|
||||
Bool changed = false;
|
||||
// Right to left, so an earlier call's offsets stay valid.
|
||||
for (SizeT scan = result.rfind(kName); scan != String::npos;
|
||||
scan = scan == 0 ? String::npos : result.rfind(kName, scan - 1)) {
|
||||
if (scan > 0 && IsIdentifierChar(result[scan - 1])) continue;
|
||||
const SizeT openParen = result.find_first_not_of(" \t", scan + kNameLength);
|
||||
if (openParen == String::npos || result[openParen] != '(') continue;
|
||||
const Vector<SizeT> marks = SplitCallArguments(result, openParen);
|
||||
// sampler, P, compare, dPdx, dPdy
|
||||
if (marks.size() != 5) continue;
|
||||
SizeT nameStart = result.find_first_not_of(" \t", openParen + 1);
|
||||
SizeT nameEnd = nameStart;
|
||||
while (nameEnd < marks[0] && IsIdentifierChar(result[nameEnd])) ++nameEnd;
|
||||
if (names.find(result.substr(nameStart, nameEnd - nameStart)) == names.end()) continue;
|
||||
const auto argument = [&](SizeT index) {
|
||||
const SizeT start = marks[index - 1] + 1;
|
||||
String text = result.substr(start, marks[index] - start);
|
||||
text.erase(std::remove_if(text.begin(), text.end(),
|
||||
[](char c) { return c == ' ' || c == '\t'; }),
|
||||
text.end());
|
||||
return text;
|
||||
};
|
||||
if (argument(3) != "vec3(0.0)" || argument(4) != "vec3(0.0)") continue;
|
||||
result.erase(marks[2], marks[4] - marks[2]);
|
||||
result.replace(scan, kNameLength, "texture");
|
||||
changed = true;
|
||||
}
|
||||
return changed ? result : glslCode;
|
||||
}
|
||||
|
||||
String ClampUnsignedMultisampleFetches(const String& glslCode) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
|
||||
@@ -576,6 +576,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// Off by default and only ever set on ANGLE + llvmpipe, where injecting the uniform
|
||||
// into a constant LOD crashes the driver (MOBILEGL_ESPRYT_AVOID_EXPLICIT_LOD_BIAS).
|
||||
String EmulateTextureLodBias(const String& glslCode, Bool avoidExplicitLodBias = false);
|
||||
// A depth-compare lookup at an explicit LOD of 0 on a samplerCubeArrayShadow - what
|
||||
// texture() is outside the fragment stage - has no GLSL ES spelling: there is no
|
||||
// textureLod or textureGrad overload for that sampler. SPIRV-Cross prints it as
|
||||
// textureGrad(s, P, compare, vec3(0.0), vec3(0.0)) anyway; Adreno compiles that and
|
||||
// returns wrong comparisons (KHR-GL4x.texture_cube_map_array.sampling, DEPTH32F, vertex/
|
||||
// tessellation control/geometry/compute). Outside the fragment stage texture(s, P,
|
||||
// compare) samples level 0 exactly (GLSL ES 3.20 8.9), so those calls are rewritten to
|
||||
// it. The fragment stage is left alone: there texture() would pick its own level.
|
||||
String LowerZeroGradCubeArrayShadowLookups(const String& glslCode, GLenum shaderType);
|
||||
// Prefix of the per-sampler int uniform that carries the bound multisample texture's
|
||||
// last real sample index into the shader (see ClampUnsignedMultisampleFetches); the
|
||||
// suffix is the sampler's own name.
|
||||
|
||||
@@ -34,6 +34,7 @@ using MobileGL::MG_Backend::DirectGLES::PrgramImpl::IMAGE_SPLIT_READ_ALIAS_PREFI
|
||||
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::IMAGE_WRITE_ALIAS_PREFIX;
|
||||
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::IMAGE_WRITEONLY_ALIAS_PREFIX;
|
||||
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::ImageArrayUnitPlan;
|
||||
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::LowerZeroGradCubeArrayShadowLookups;
|
||||
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RedeclareClipCullDistancesInPerVertexBlock;
|
||||
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RedeclareClipCullDistanceInputsInPerVertexBlock;
|
||||
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RemapImageArrayElementUnits;
|
||||
@@ -1848,3 +1849,57 @@ void main() {
|
||||
}
|
||||
EXPECT_EQ(CountOf(out, "uniform highp int mg_msSampleMax_stencilTex;"), 1u) << out;
|
||||
}
|
||||
|
||||
// End to end: texture() on a samplerCubeArrayShadow outside the fragment stage is an explicit
|
||||
// LOD 0 lookup in SPIR-V, which SPIRV-Cross prints as a textureGrad overload GLSL ES does not
|
||||
// have (KHR-GL4x.texture_cube_map_array.sampling). The pass turns it back into texture(), which
|
||||
// is level 0 in that stage.
|
||||
TEST(LowerZeroGradCubeArrayShadowLookupsTest, RestoresTextureOutsideTheFragmentStage) {
|
||||
using namespace MG_Util::ShaderTranspiler;
|
||||
MobileGL::Initialize();
|
||||
const char* vertexSource = R"(#version 450 core
|
||||
uniform samplerCubeArrayShadow depthTex[2];
|
||||
uniform samplerCubeArrayShadow single;
|
||||
uniform vec4 coord;
|
||||
out float result;
|
||||
float lookup(samplerCubeArrayShadow s, vec4 p) { return texture(s, p, 0.5); }
|
||||
void main() {
|
||||
result = texture(depthTex[1], coord, 0.25) + texture(single, coord, 0.75) + lookup(single, coord);
|
||||
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
)";
|
||||
const char* fragmentSource = R"(#version 450 core
|
||||
in float result;
|
||||
out vec4 color;
|
||||
void main() { color = vec4(result); }
|
||||
)";
|
||||
Vector<SharedPtr<glslang::TShader>> shaders;
|
||||
const Vector<Pair<GLenum, const char*>> stages{{GL_VERTEX_SHADER, vertexSource},
|
||||
{GL_FRAGMENT_SHADER, fragmentSource}};
|
||||
for (const auto& [stage, text] : stages) {
|
||||
ShaderAttrib attrib{.shaderType = stage, .sourceStr = text};
|
||||
auto compiled = ShaderCompiler::CompileShader(attrib);
|
||||
ASSERT_TRUE(compiled) << compiled.error().log;
|
||||
shaders.push_back(compiled.value());
|
||||
}
|
||||
ProgramAttrib programAttrib{.shaders = shaders};
|
||||
auto program = ShaderCompiler::LinkProgram(programAttrib);
|
||||
ASSERT_TRUE(program) << program.error().log;
|
||||
ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_VERTEX_SHADER, GL_FRAGMENT_SHADER},
|
||||
.program = *program.value()};
|
||||
auto modules = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib);
|
||||
ASSERT_TRUE(modules) << modules.error().log;
|
||||
SpvcSession session(modules.value()[0], SessionUsageBit::Transpile);
|
||||
auto essl = ShaderCompiler::DecompileShader(session);
|
||||
ASSERT_TRUE(essl) << essl.error().log;
|
||||
ASSERT_EQ(CountOf(essl.value(), "vec3(0.0), vec3(0.0))"), 3u) << essl.value();
|
||||
|
||||
const String out = LowerZeroGradCubeArrayShadowLookups(essl.value(), GL_VERTEX_SHADER);
|
||||
EXPECT_FALSE(Contains(out, "textureGrad")) << out;
|
||||
EXPECT_EQ(CountOf(out, "texture("), 3u) << out;
|
||||
EXPECT_EQ(LowerZeroGradCubeArrayShadowLookups(essl.value(), GL_FRAGMENT_SHADER), essl.value());
|
||||
|
||||
const String other = "#version 320 es\nuniform highp samplerCube c;\n"
|
||||
"void main() { highp vec4 v = textureGrad(c, vec3(1.0), vec3(0.0), vec3(0.0)); }\n";
|
||||
EXPECT_EQ(LowerZeroGradCubeArrayShadowLookups(other, GL_VERTEX_SHADER), other);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user