mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (Espryt): bound usampler2DMS fetches by the bound texture's real sample count
This commit is contained in:
@@ -5757,6 +5757,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
}
|
||||
|
||||
// The last sample a usampler2DMS fetch may name, from the texture
|
||||
// actually bound: a stencil texture's real count is not an integer
|
||||
// colour texture's. Keyed like the bias above - a texture's storage or
|
||||
// a unit's binding moving is what invalidates this pass's memo.
|
||||
if (samplerBinding.msSampleMaxLocation >= 0) {
|
||||
const Int sampleMax =
|
||||
std::max(PipeTextureBackendSampleCountForHandle(view.Texture), 1) - 1;
|
||||
if (sampleMax != samplerBinding.lastAssignedMsSampleMax) {
|
||||
g_GLESFuncs.glUniform1i(samplerBinding.msSampleMaxLocation, sampleMax);
|
||||
samplerBinding.lastAssignedMsSampleMax = sampleMax;
|
||||
}
|
||||
}
|
||||
|
||||
const auto* viewRecord = PipeSamplerViewRecordForHandle(view.View);
|
||||
const Bool sampledIsTexture2D =
|
||||
viewRecord != nullptr &&
|
||||
|
||||
@@ -146,6 +146,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// kFrontendMaxSamples, which is the source of truth - keep the two in step.
|
||||
constexpr Int kFrontendMaxSamples = 4;
|
||||
|
||||
// Whether a usampler2DMS fetch's sample index is bounded at RUNTIME by the bound texture's
|
||||
// real sample count rather than at compile time by the integer ceiling: armed when that
|
||||
// ceiling is squeezed below what MobileGL advertises AND the depth ceiling - which the
|
||||
// stencil aspect of a multisample depth/stencil texture is allocated under, and which a
|
||||
// usampler reads too - exceeds it, so no one static bound is right for both. Read on both
|
||||
// sides of the translation-memo boundary (the SPIR-V pass defers, the ESSL pass takes
|
||||
// over); it is a function of three values the memo key already carries, so the two halves
|
||||
// cannot disagree about one program.
|
||||
static Bool ClampsUnsignedMultisampleFetchesAtRuntime() {
|
||||
const Int advertisedMaxSamples = std::max(g_GLESCapabilities.MaxSamples, kFrontendMaxSamples);
|
||||
return g_GLESCapabilities.MaxIntegerSamples < advertisedMaxSamples &&
|
||||
g_GLESCapabilities.MaxDepthTextureSamples > g_GLESCapabilities.MaxIntegerSamples;
|
||||
}
|
||||
|
||||
static Uint ResolveBackendEsslVersion() {
|
||||
const auto& version = g_GLESCapabilities.GLESVersion;
|
||||
if (version.Major > 3 || (version.Major == 3 && version.Minor >= 2)) {
|
||||
@@ -4502,6 +4516,21 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return MobileGL::TextureTarget::Unknown;
|
||||
}
|
||||
|
||||
Int PipeTextureBackendSampleCountForHandle(MG_Pipe::MGPipeHandle res) {
|
||||
const auto* record = PipeTextureRecordForHandle(res);
|
||||
if (record == nullptr) return 0;
|
||||
const MobileGL::TextureTarget target = PipeTextureTargetForHandle(res);
|
||||
if (!TextureImpl::IsMultisampleTextureTarget(target)) return 0;
|
||||
// The same three inputs the storage allocation clamps with (SyncToBackend's multisample
|
||||
// arm): the record's format and requested count, and the format's ES transfer format,
|
||||
// which only decides the integer-vs-colour fallback when the probe has no answer.
|
||||
const auto internalFormat = static_cast<TextureInternalFormat>(record->Desc.InternalFormat);
|
||||
GLenum glInternalFormat = 0, glFormat = 0, glType = 0;
|
||||
TextureImpl::GenerateTextureFormatInfo(internalFormat, &glInternalFormat, &glFormat, &glType, target);
|
||||
return ClampSamplesToBackendSupport(GetFormatCapabilityTargetIndex(target), internalFormat, glFormat,
|
||||
static_cast<Int>(record->Desc.Samples));
|
||||
}
|
||||
|
||||
const MG_Pipe::MGPipeSamplerCsoRecord* PipeSamplerCsoRecordForHandle(MG_Pipe::MGPipeHandle cso) {
|
||||
return PipeRecordAt(MG_Pipe::MGPipeApplier().SamplerCsos, cso);
|
||||
}
|
||||
@@ -11694,7 +11723,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
g_GLESCapabilities.MaxColorTextureSamples,
|
||||
g_GLESCapabilities.MaxIntegerSamples,
|
||||
g_GLESCapabilities.MaxDepthTextureSamples, advertisedMaxSamples,
|
||||
enableSpirvValidation) &&
|
||||
enableSpirvValidation, ClampsUnsignedMultisampleFetchesAtRuntime()) &&
|
||||
!clampedSampleSpirv.empty()) {
|
||||
effectiveSpirv = &clampedSampleSpirv;
|
||||
}
|
||||
@@ -12915,6 +12944,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
source = ForceFlatIntegerVaryings(source, glShaderType);
|
||||
source = BroadcastLegacyFragColor(std::move(source), glShaderType, m_fragColorBroadcastCount);
|
||||
source = EmulateTextureLodBias(source, ShouldAvoidExplicitLodBiasOnAngleLlvmpipe());
|
||||
if (ClampsUnsignedMultisampleFetchesAtRuntime()) {
|
||||
source = ClampUnsignedMultisampleFetches(source);
|
||||
}
|
||||
source = EmulateBaseInstanceInVertexShader(std::move(source), glShaderType);
|
||||
source = PromoteDrawParameterGlobalsToUniforms(std::move(source), glShaderType);
|
||||
// The two halves of the gl_ViewportIndex routing emulation, next to the draw-
|
||||
@@ -13440,6 +13472,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
binding.lodBiasLocation = g_GLESFuncs.glGetUniformLocation(
|
||||
m_backendProgramId, (String(LOD_BIAS_UNIFORM_PREFIX) + elementName).c_str());
|
||||
binding.lastAssignedLodBias = 0.0f;
|
||||
// Present only for the unsigned multisample samplers ClampUnsignedMultisampleFetches
|
||||
// rewrote, named the same way.
|
||||
if (uniformType == GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE ||
|
||||
uniformType == GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY) {
|
||||
binding.msSampleMaxLocation = g_GLESFuncs.glGetUniformLocation(
|
||||
m_backendProgramId, (String(MS_SAMPLE_MAX_UNIFORM_PREFIX) + elementName).c_str());
|
||||
}
|
||||
binding.lastAssignedMsSampleMax = 0;
|
||||
m_samplerUniformBindings.push_back(binding);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -644,6 +644,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// every caller treats as "decline", never as a default.
|
||||
MobileGL::TextureTarget PipeTextureTargetForHandle(MG_Pipe::MGPipeHandle res);
|
||||
|
||||
// The sample count the backend really allocates for the multisample texture this handle
|
||||
// names - the requested count clamped by ClampSamplesToBackendSupport, exactly as the
|
||||
// storage allocation clamps it - or 0 when the handle names no live multisample texture.
|
||||
// What a usampler2DMS fetch's runtime bound (ClampUnsignedMultisampleFetches) is set from.
|
||||
Int PipeTextureBackendSampleCountForHandle(MG_Pipe::MGPipeHandle res);
|
||||
|
||||
// The pending-upload entry the applier accumulated for this (uploadTarget, level) of this
|
||||
// texture record, or null (D-D5). SERVER-SIDE STATE, and that is the whole point: the
|
||||
// client clears its own dirty flags at EMISSION for the levels the applier accepted, while
|
||||
@@ -2774,6 +2780,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// so an unbiased shader issues no per-draw glUniform1f at all.
|
||||
Int lodBiasLocation = -1;
|
||||
Float lastAssignedLodBias = 0.0f;
|
||||
// Location of this sampler's runtime sample-index bound
|
||||
// (PrgramImpl::ClampUnsignedMultisampleFetches), -1 when the shader has none;
|
||||
// lastAssignedMsSampleMax mirrors the value the program holds (0 after link).
|
||||
Int msSampleMaxLocation = -1;
|
||||
Int lastAssignedMsSampleMax = 0;
|
||||
};
|
||||
|
||||
// Memo of the whole per-draw sampler-uniform pass (glUniform1i unit
|
||||
|
||||
@@ -2057,6 +2057,74 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return result;
|
||||
}
|
||||
|
||||
String ClampUnsignedMultisampleFetches(const String& glslCode) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (glslCode.find("usampler2DMS") == String::npos || glslCode.find("texelFetch") == String::npos) {
|
||||
return glslCode;
|
||||
}
|
||||
|
||||
// The non-arrayed unsigned multisample samplers - the ones ClampMultisampleFetchPass
|
||||
// deferred, and the only ones a lookup can name directly as its first argument.
|
||||
static const std::regex declRegex(
|
||||
R"(uniform\s+(?:(?:highp|mediump|lowp)\s+)?usampler2DMS(?:Array)?\s+([A-Za-z_][A-Za-z0-9_]*)\s*;)");
|
||||
UnorderedMap<String, String> clampNames; // sampler name -> sample-max uniform name
|
||||
Vector<std::pair<SizeT, String>> declEnds; // where each declaration ends, and its uniform
|
||||
for (std::sregex_iterator it(glslCode.begin(), glslCode.end(), declRegex), end; it != end; ++it) {
|
||||
const String name = (*it)[1].str();
|
||||
const String clampName = String(MS_SAMPLE_MAX_UNIFORM_PREFIX) + name;
|
||||
if (clampNames.emplace(name, clampName).second) {
|
||||
declEnds.emplace_back(static_cast<SizeT>(it->position(0) + it->length(0)), clampName);
|
||||
}
|
||||
}
|
||||
if (clampNames.empty()) {
|
||||
return glslCode;
|
||||
}
|
||||
|
||||
// Right-to-left, so an earlier call's offsets survive the insertions behind it.
|
||||
constexpr const char* kFetch = "texelFetch";
|
||||
const SizeT fetchLength = std::strlen(kFetch);
|
||||
String result = glslCode;
|
||||
Vector<String> usedClamps;
|
||||
for (SizeT scan = result.rfind(kFetch); scan != String::npos;
|
||||
scan = scan == 0 ? String::npos : result.rfind(kFetch, scan - 1)) {
|
||||
if (scan > 0 && IsIdentifierChar(result[scan - 1])) continue;
|
||||
const SizeT openParen = result.find_first_not_of(" \t", scan + fetchLength);
|
||||
if (openParen == String::npos || result[openParen] != '(') continue; // texelFetchOffset etc.
|
||||
const Vector<SizeT> marks = SplitCallArguments(result, openParen);
|
||||
if (marks.size() != 3) continue; // (sampler, coord, sample) is the multisample form
|
||||
const SizeT firstStart = result.find_first_not_of(" \t", openParen + 1);
|
||||
SizeT firstEnd = marks[0];
|
||||
while (firstEnd > firstStart && (result[firstEnd - 1] == ' ' || result[firstEnd - 1] == '\t')) {
|
||||
--firstEnd;
|
||||
}
|
||||
if (firstStart == String::npos || firstEnd <= firstStart) continue;
|
||||
const auto found = clampNames.find(result.substr(firstStart, firstEnd - firstStart));
|
||||
if (found == clampNames.end()) continue;
|
||||
const SizeT sampleStart = result.find_first_not_of(" \t\n", marks[1] + 1);
|
||||
if (sampleStart == String::npos || sampleStart >= marks[2]) continue;
|
||||
const SizeT sampleEnd = marks[2];
|
||||
result.insert(sampleEnd, String(", ") + found->second + ")");
|
||||
result.insert(sampleStart, "min(");
|
||||
if (std::find(usedClamps.begin(), usedClamps.end(), found->second) == usedClamps.end()) {
|
||||
usedClamps.push_back(found->second);
|
||||
}
|
||||
}
|
||||
if (usedClamps.empty()) {
|
||||
return glslCode;
|
||||
}
|
||||
|
||||
// Declared right after the sampler they bound, back to front so the recorded
|
||||
// offsets (taken from the unedited text) stay valid: every fetch sits after the
|
||||
// declarations in the emitted ESSL, so no insertion above has moved them.
|
||||
for (auto it = declEnds.rbegin(); it != declEnds.rend(); ++it) {
|
||||
if (std::find(usedClamps.begin(), usedClamps.end(), it->second) == usedClamps.end()) continue;
|
||||
result.insert(it->first, String("\nuniform highp int ") + it->second + ";");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace PrgramImpl
|
||||
|
||||
namespace Utils {
|
||||
|
||||
@@ -576,6 +576,21 @@ 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);
|
||||
// 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.
|
||||
constexpr const char* MS_SAMPLE_MAX_UNIFORM_PREFIX = "mg_msSampleMax_";
|
||||
// The runtime half of ClampMultisampleFetchPass's deferDirectUnsignedFetches. A
|
||||
// usampler2DMS[Array] reads integer colour textures, which the backend may allocate with
|
||||
// fewer samples than MobileGL advertises, AND the stencil aspect of multisample
|
||||
// depth/stencil textures, which it allocates under the (larger) depth ceiling - so no
|
||||
// single compile-time bound is right for both. Declares one
|
||||
// `uniform highp int mg_msSampleMax_<sampler>;` per non-arrayed unsigned multisample
|
||||
// sampler and wraps the sample argument of every texelFetch that names it in
|
||||
// min(sample, that uniform). Draws push the bound texture's real sample count - 1 into
|
||||
// it (0, the program's initial value, when nothing multisampled is bound). Returns the
|
||||
// source unchanged when there is nothing to rewrite.
|
||||
String ClampUnsignedMultisampleFetches(const String& glslCode);
|
||||
} // namespace PrgramImpl
|
||||
|
||||
namespace Utils {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
using namespace MobileGL;
|
||||
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::BakeImageFormatQualifiers;
|
||||
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::BuildPassthroughTessControlEssl;
|
||||
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::ClampUnsignedMultisampleFetches;
|
||||
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::ExtractPerVertexBlockMembers;
|
||||
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::ForceFlatIntegerVaryings;
|
||||
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::IMAGE_ARRAY_ELEMENT_PREFIX;
|
||||
@@ -1763,3 +1764,87 @@ void main() { gl_Position = gl_in[0].gl_Position; EmitVertex(); }
|
||||
EXPECT_EQ(RedeclareClipCullDistanceInputsInPerVertexBlock(readsPointSize, GL_VERTEX_SHADER, producer),
|
||||
readsPointSize);
|
||||
}
|
||||
|
||||
// The runtime half of the deferred usampler2DMS clamp: the sample argument of every fetch that
|
||||
// names an unsigned multisample uniform is bounded by that sampler's own uniform, declared right
|
||||
// after it. A float multisample sampler and a non-multisample one are none of its business.
|
||||
TEST(ClampUnsignedMultisampleFetchesTest, BoundsTheSampleOfEveryUnsignedMultisampleFetch) {
|
||||
const String essl = "#version 320 es\n"
|
||||
"precision highp float;\n"
|
||||
"layout(binding = 0) uniform highp usampler2DMS stencilTex;\n"
|
||||
"uniform highp sampler2DMS colorMs;\n"
|
||||
"uniform highp usampler2D plain;\n"
|
||||
"layout(location = 0) out highp vec4 color;\n"
|
||||
"void main() {\n"
|
||||
" uint a = texelFetch(stencilTex, ivec2(0), 3).x;\n"
|
||||
" uint b = texelFetch(stencilTex, ivec2(0), int(gl_FragCoord.x)).x;\n"
|
||||
" vec4 c = texelFetch(colorMs, ivec2(0), 3);\n"
|
||||
" uint d = texelFetch(plain, ivec2(0), 0).x;\n"
|
||||
" color = vec4(float(a), float(b), c.x, float(d));\n"
|
||||
"}\n";
|
||||
const String out = ClampUnsignedMultisampleFetches(essl);
|
||||
EXPECT_TRUE(Contains(out, "texelFetch(stencilTex, ivec2(0), min(3, mg_msSampleMax_stencilTex))")) << out;
|
||||
EXPECT_TRUE(Contains(out, "texelFetch(stencilTex, ivec2(0), min(int(gl_FragCoord.x), mg_msSampleMax_stencilTex))"))
|
||||
<< out;
|
||||
EXPECT_TRUE(Contains(out, "uniform highp usampler2DMS stencilTex;\nuniform highp int mg_msSampleMax_stencilTex;"))
|
||||
<< out;
|
||||
EXPECT_EQ(CountOf(out, "uniform highp int mg_msSampleMax_"), 1u) << out;
|
||||
EXPECT_EQ(CountOf(out, "min("), 2u) << out;
|
||||
|
||||
const String untouched = "#version 320 es\nuniform highp sampler2DMS colorMs;\n"
|
||||
"void main() { highp vec4 c = texelFetch(colorMs, ivec2(0), 3); }\n";
|
||||
EXPECT_EQ(ClampUnsignedMultisampleFetches(untouched), untouched);
|
||||
}
|
||||
|
||||
// End to end, the way the program build runs it: the SPIR-V pass defers the direct usampler2DMS
|
||||
// fetches of KHR-GL4x.texture_stencil8.multisample's checker, SPIRV-Cross prints them with their
|
||||
// literal samples intact, and the text pass bounds each one by the runtime uniform.
|
||||
TEST(ClampUnsignedMultisampleFetchesTest, BoundsTheStencilCheckerAfterTheDeferredSpirvClamp) {
|
||||
using namespace MG_Util::ShaderTranspiler;
|
||||
MobileGL::Initialize();
|
||||
const char* vertexSource = R"(#version 330 core
|
||||
void main() { gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }
|
||||
)";
|
||||
const char* fragmentSource = R"(#version 330 core
|
||||
uniform usampler2DMS stencilTex;
|
||||
out vec4 color;
|
||||
void main() {
|
||||
uint s0 = texelFetch(stencilTex, ivec2(0), 0).r;
|
||||
uint s1 = texelFetch(stencilTex, ivec2(0), 1).r;
|
||||
uint s2 = texelFetch(stencilTex, ivec2(0), 2).r;
|
||||
uint s3 = texelFetch(stencilTex, ivec2(0), 3).r;
|
||||
color = vec4(float(s0), float(s1), float(s2), float(s3)) / 255.0;
|
||||
}
|
||||
)";
|
||||
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;
|
||||
ASSERT_EQ(modules.value().size(), 2u);
|
||||
|
||||
Vector<uint32_t> deferred;
|
||||
ASSERT_TRUE(ShaderCompiler::ClampMultisampleFetchesForEssl(modules.value()[1], deferred, 4, 1, 4, 4, true,
|
||||
/*deferDirectUnsignedFetches=*/true));
|
||||
const Vector<uint32_t>& fragmentModule = deferred.empty() ? modules.value()[1] : deferred;
|
||||
SpvcSession session(fragmentModule, SessionUsageBit::Transpile);
|
||||
auto essl = ShaderCompiler::DecompileShader(session);
|
||||
ASSERT_TRUE(essl) << essl.error().log;
|
||||
|
||||
const String out = ClampUnsignedMultisampleFetches(essl.value());
|
||||
for (Int sample = 1; sample <= 3; ++sample) {
|
||||
EXPECT_TRUE(Contains(out, "min(" + std::to_string(sample) + ", mg_msSampleMax_stencilTex)")) << out;
|
||||
}
|
||||
EXPECT_EQ(CountOf(out, "uniform highp int mg_msSampleMax_stencilTex;"), 1u) << out;
|
||||
}
|
||||
|
||||
@@ -381,3 +381,67 @@ TEST_F(ClampMultisampleFetchTest, ClampsAColorSampleWithAMinimum) {
|
||||
ASSERT_EQ(constants.count(minimum->operandIds[1]), 1u) << dis;
|
||||
EXPECT_EQ(constants.at(minimum->operandIds[1]), 1u) << dis;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// KHR-GL4x.texture_stencil8.multisample in miniature: a four-sample STENCIL_INDEX8 texture
|
||||
// read through a usampler2DMS with literal sample indices. Beside it, the same sampler
|
||||
// reached through a function parameter and a signed integer sampler - neither of which the
|
||||
// runtime bound can name, so both must keep the static integer clamp.
|
||||
const char* const kStencilMultisampleFetch = R"(#version 410 core
|
||||
uniform usampler2DMS stencilTex;
|
||||
uniform isampler2DMS signedMs;
|
||||
out vec4 fragColor;
|
||||
uint fetchThrough(usampler2DMS s, int i) { return texelFetch(s, ivec2(0), i).r; }
|
||||
void main() {
|
||||
uint direct = texelFetch(stencilTex, ivec2(0), 3).r;
|
||||
uint viaParameter = fetchThrough(stencilTex, 3);
|
||||
int signedValue = texelFetch(signedMs, ivec2(0), 3).r;
|
||||
fragColor = vec4(float(direct), float(viaParameter), float(signedValue), 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
// How many Sample operands of the module's multisample fetches are the constant `value`.
|
||||
SizeT CountSampleOperandsEqualTo(const Vector<Uint32>& spirv, Uint32 value) {
|
||||
const std::map<Uint32, Uint32> constants = CollectScalarConstants(spirv);
|
||||
SizeT count = 0;
|
||||
for (const ImageFetch& fetch : CollectImageFetches(spirv)) {
|
||||
if ((fetch.mask & static_cast<Uint32>(spv::ImageOperandsMask::Sample)) == 0u) continue;
|
||||
const auto constant = constants.find(fetch.maskOperandIds.back());
|
||||
if (constant != constants.end() && constant->second == value) ++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Integer colour squeezed to one sample but depth/stencil at four (Adreno): a usampler2DMS fetch
|
||||
// straight from its uniform is handed to the runtime bound and keeps sample 3 - a stencil texture
|
||||
// really has it - while the fetch through a parameter and the signed one still read sample 0.
|
||||
TEST_F(ClampMultisampleFetchTest, DefersADirectUnsignedFetchToTheRuntimeBound) {
|
||||
const Vector<Uint32> input = CompileFragment(kStencilMultisampleFetch);
|
||||
ASSERT_FALSE(input.empty());
|
||||
|
||||
Vector<Uint32> output;
|
||||
ASSERT_TRUE(ShaderCompiler::ClampMultisampleFetchesForEssl(
|
||||
input, output, /*maxColorSamples=*/4, /*maxIntegerSamples=*/1, /*maxDepthSamples=*/4,
|
||||
kAdvertisedMaxSamples, true, /*deferDirectUnsignedFetches=*/true));
|
||||
ASSERT_FALSE(output.empty());
|
||||
const String dis = Disassemble(output);
|
||||
ASSERT_TRUE(Validates(output)) << dis;
|
||||
EXPECT_EQ(CountSampleOperandsEqualTo(output, 3u), 1u) << dis;
|
||||
EXPECT_EQ(CountSampleOperandsEqualTo(output, 0u), 2u) << dis;
|
||||
}
|
||||
|
||||
// Without the runtime bound nothing is deferred: the pre-existing static clamp, all three to 0.
|
||||
TEST_F(ClampMultisampleFetchTest, WithoutTheRuntimeBoundEveryIntegerFetchKeepsTheStaticClamp) {
|
||||
const Vector<Uint32> input = CompileFragment(kStencilMultisampleFetch);
|
||||
ASSERT_FALSE(input.empty());
|
||||
|
||||
Vector<Uint32> output;
|
||||
ASSERT_TRUE(ShaderCompiler::ClampMultisampleFetchesForEssl(
|
||||
input, output, /*maxColorSamples=*/4, /*maxIntegerSamples=*/1, /*maxDepthSamples=*/4,
|
||||
kAdvertisedMaxSamples, true));
|
||||
ASSERT_FALSE(output.empty());
|
||||
const String dis = Disassemble(output);
|
||||
EXPECT_EQ(CountSampleOperandsEqualTo(output, 3u), 0u) << dis;
|
||||
EXPECT_EQ(CountSampleOperandsEqualTo(output, 0u), 3u) << dis;
|
||||
}
|
||||
|
||||
@@ -1461,11 +1461,13 @@ namespace MobileGL {
|
||||
const Int32 maxIntegerSamples,
|
||||
const Int32 maxDepthSamples,
|
||||
const Int32 advertisedMaxSamples,
|
||||
const bool enableSpirvValidation) {
|
||||
const bool enableSpirvValidation,
|
||||
const bool deferDirectUnsignedFetches) {
|
||||
using namespace spvtools;
|
||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||
optimizer.RegisterPass(ClampMultisampleFetchPass::CreateClampMultisampleFetchPass(
|
||||
maxColorSamples, maxIntegerSamples, maxDepthSamples, advertisedMaxSamples));
|
||||
maxColorSamples, maxIntegerSamples, maxDepthSamples, advertisedMaxSamples,
|
||||
deferDirectUnsignedFetches));
|
||||
|
||||
return RunOptimizerChecked("ClampMultisampleFetchesForEssl", optimizer, inputBinary,
|
||||
outputBinary, true, enableSpirvValidation);
|
||||
|
||||
@@ -78,14 +78,16 @@ namespace MobileGL {
|
||||
// end of a one-sample allocation. Pass the backend-real per-category ceilings and
|
||||
// the advertised maximum (GL_Getter's GetAdvertisedMaxSamples); a category that
|
||||
// already reaches the advertised value is left alone. DirectGLES transpile path
|
||||
// only. See ClampMultisampleFetchPass.
|
||||
// only. See ClampMultisampleFetchPass, which also says what
|
||||
// deferDirectUnsignedFetches hands over to the caller's runtime clamp.
|
||||
static bool ClampMultisampleFetchesForEssl(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary,
|
||||
Int32 maxColorSamples,
|
||||
Int32 maxIntegerSamples,
|
||||
Int32 maxDepthSamples,
|
||||
Int32 advertisedMaxSamples,
|
||||
bool enableSpirvValidation = false);
|
||||
bool enableSpirvValidation = false,
|
||||
bool deferDirectUnsignedFetches = false);
|
||||
// Whether the module declares any multisampled image type, i.e. whether the pass
|
||||
// above has anything to do. The gate that keeps every other stage off an
|
||||
// optimizer round trip it does not need.
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "source/util/make_unique.h"
|
||||
#include "source/util/string_utils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
@@ -120,6 +121,39 @@ namespace MobileGL {
|
||||
return SampleCategory::Color;
|
||||
}
|
||||
|
||||
// An unsigned integer sampled type: the usampler* family, which is what reads the
|
||||
// stencil aspect of a multisample texture as well as integer colour.
|
||||
bool HasUnsignedIntegerSampledType(IRContext* context, const Instruction* imageType) {
|
||||
const Instruction* sampledType =
|
||||
context->get_def_use_mgr()->GetDef(imageType->GetSingleWordInOperand(kSampledTypeOperand));
|
||||
return sampledType != nullptr && sampledType->opcode() == spv::Op::OpTypeInt &&
|
||||
sampledType->NumInOperands() > 1 && sampledType->GetSingleWordInOperand(1) == 0u;
|
||||
}
|
||||
|
||||
// Whether the image an operation reads is loaded straight from a non-arrayed
|
||||
// UniformConstant global - through at most an OpImage of the loaded sampled image
|
||||
// - i.e. whether the ESSL will name that global's uniform as the lookup's first
|
||||
// argument.
|
||||
bool ReadsDirectlyFromGlobal(IRContext* context, uint32_t imageId) {
|
||||
auto* defUseMgr = context->get_def_use_mgr();
|
||||
const Instruction* object = defUseMgr->GetDef(imageId);
|
||||
if (object != nullptr && object->opcode() == spv::Op::OpImage) {
|
||||
object = defUseMgr->GetDef(object->GetSingleWordInOperand(0));
|
||||
}
|
||||
if (object == nullptr || object->opcode() != spv::Op::OpLoad) return false;
|
||||
const Instruction* variable = defUseMgr->GetDef(object->GetSingleWordInOperand(0));
|
||||
if (variable == nullptr || variable->opcode() != spv::Op::OpVariable ||
|
||||
variable->GetSingleWordInOperand(0) !=
|
||||
static_cast<uint32_t>(spv::StorageClass::UniformConstant)) {
|
||||
return false;
|
||||
}
|
||||
const Instruction* pointerType = defUseMgr->GetDef(variable->type_id());
|
||||
if (pointerType == nullptr || pointerType->opcode() != spv::Op::OpTypePointer) return false;
|
||||
const Instruction* pointee = defUseMgr->GetDef(pointerType->GetSingleWordInOperand(1));
|
||||
return pointee != nullptr && (pointee->opcode() == spv::Op::OpTypeImage ||
|
||||
pointee->opcode() == spv::Op::OpTypeSampledImage);
|
||||
}
|
||||
|
||||
// Where the Sample id sits among an image operation's in-operands, or false when
|
||||
// the operation carries no Sample at all.
|
||||
//
|
||||
@@ -261,6 +295,15 @@ namespace MobileGL {
|
||||
switch (CategoryOf(irContext, imageType)) {
|
||||
case SampleCategory::Integer:
|
||||
categoryMaxSamples = m_maxIntegerSamples;
|
||||
// Taken over at runtime by the bound texture's real count (see
|
||||
// the class comment); here only the larger static ceiling, so a
|
||||
// stencil texture's samples above the integer one stay readable.
|
||||
if (m_deferDirectUnsignedFetches &&
|
||||
HasUnsignedIntegerSampledType(irContext, imageType) &&
|
||||
ReadsDirectlyFromGlobal(irContext,
|
||||
instruction.GetSingleWordInOperand(kImageOperand))) {
|
||||
categoryMaxSamples = std::max(m_maxIntegerSamples, m_maxDepthSamples);
|
||||
}
|
||||
break;
|
||||
case SampleCategory::Depth:
|
||||
categoryMaxSamples = m_maxDepthSamples;
|
||||
@@ -369,9 +412,10 @@ namespace MobileGL {
|
||||
|
||||
spvtools::Optimizer::PassToken ClampMultisampleFetchPass::CreateClampMultisampleFetchPass(
|
||||
const Int32 maxColorSamples, const Int32 maxIntegerSamples, const Int32 maxDepthSamples,
|
||||
const Int32 advertisedMaxSamples) {
|
||||
const Int32 advertisedMaxSamples, const bool deferDirectUnsignedFetches) {
|
||||
return spvtools::Optimizer::PassToken(spvtools::MakeUnique<ClampMultisampleFetchPass>(
|
||||
maxColorSamples, maxIntegerSamples, maxDepthSamples, advertisedMaxSamples));
|
||||
maxColorSamples, maxIntegerSamples, maxDepthSamples, advertisedMaxSamples,
|
||||
deferDirectUnsignedFetches));
|
||||
}
|
||||
} // namespace ShaderTranspiler
|
||||
} // namespace MG_Util
|
||||
|
||||
@@ -51,6 +51,20 @@ namespace MobileGL {
|
||||
// image and everything else falls to the colour limit - which is the one a
|
||||
// mis-classified image would want anyway.
|
||||
//
|
||||
// An UNSIGNED integer image is the one category whose static ceiling can be wrong
|
||||
// in both directions: a usampler2DMS reads integer colour formats AND the stencil
|
||||
// aspect of a multisample STENCIL_INDEX8 / depth-stencil texture, and the backend
|
||||
// allocates the latter under the DEPTH ceiling (Adreno: 4 samples where integer
|
||||
// colour gets 1). Clamping such a fetch to the integer ceiling reads sample 0 for
|
||||
// every index of a four-sample stencil texture. With deferDirectUnsignedFetches the
|
||||
// caller takes those fetches over at runtime - a per-sampler uniform holding the
|
||||
// bound texture's real last sample index (DirectGLES ClampUnsignedMultisampleFetches)
|
||||
// - and this pass only bounds them by the larger of the two ceilings. "Direct" is a
|
||||
// fetch whose image is loaded straight from a non-arrayed global, which is exactly
|
||||
// what reaches the ESSL as `texelFetch(<uniform name>, ...)` for the text pass to
|
||||
// find; a sampler reached through a function parameter or an array keeps the
|
||||
// integer clamp.
|
||||
//
|
||||
// DirectGLES transpile path only. DirectVulkan allocates the sample count it was
|
||||
// asked for and must see the module unchanged.
|
||||
class ClampMultisampleFetchPass : public spvtools::opt::Pass {
|
||||
@@ -59,11 +73,13 @@ namespace MobileGL {
|
||||
// advertises (GL_Getter's GetAdvertisedMaxSamples). A category whose real ceiling
|
||||
// already reaches the advertised one is left completely alone.
|
||||
ClampMultisampleFetchPass(Int32 maxColorSamples, Int32 maxIntegerSamples,
|
||||
Int32 maxDepthSamples, Int32 advertisedMaxSamples)
|
||||
Int32 maxDepthSamples, Int32 advertisedMaxSamples,
|
||||
bool deferDirectUnsignedFetches = false)
|
||||
: m_maxColorSamples(maxColorSamples),
|
||||
m_maxIntegerSamples(maxIntegerSamples),
|
||||
m_maxDepthSamples(maxDepthSamples),
|
||||
m_advertisedMaxSamples(advertisedMaxSamples) {}
|
||||
m_advertisedMaxSamples(advertisedMaxSamples),
|
||||
m_deferDirectUnsignedFetches(deferDirectUnsignedFetches) {}
|
||||
|
||||
const char* name() const override { return "clamp-multisample-fetch"; }
|
||||
Status Process() override;
|
||||
@@ -80,13 +96,14 @@ namespace MobileGL {
|
||||
|
||||
static spvtools::Optimizer::PassToken CreateClampMultisampleFetchPass(
|
||||
Int32 maxColorSamples, Int32 maxIntegerSamples, Int32 maxDepthSamples,
|
||||
Int32 advertisedMaxSamples);
|
||||
Int32 advertisedMaxSamples, bool deferDirectUnsignedFetches = false);
|
||||
|
||||
private:
|
||||
Int32 m_maxColorSamples;
|
||||
Int32 m_maxIntegerSamples;
|
||||
Int32 m_maxDepthSamples;
|
||||
Int32 m_advertisedMaxSamples;
|
||||
bool m_deferDirectUnsignedFetches;
|
||||
};
|
||||
} // namespace ShaderTranspiler
|
||||
} // namespace MG_Util
|
||||
|
||||
Reference in New Issue
Block a user