mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Perf, Test] (ShaderTranspiler, DirectGLES): answer both pass-gate probes from one SPIR-V parse
This commit is contained in:
@@ -4909,9 +4909,32 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// fails to compile and every draw made with it silently renders nothing.
|
||||
// Gated on the module actually declaring the output, so no other stage pays an
|
||||
// optimizer round trip for it.
|
||||
// One parse of the module answers every armed pass gate below. The per-gate
|
||||
// Declares* probes each cost a BuildModule per stage, and on a driver where both
|
||||
// gates are armed (Mali: no GL_OES_viewport_array AND integer multisample
|
||||
// squeezed to 1) the doubled parse made compile-heavy workloads ~10% slower.
|
||||
// Probing the pre-lowering module is sound for both gates: demoting
|
||||
// gl_ViewportIndex neither adds nor removes multisampled image types.
|
||||
// Recomputed here rather than calling GL_Getter's GetAdvertisedMaxSamples():
|
||||
// this is backend code and must not reach into the GL frontend. 4 is that
|
||||
// translation unit's kFrontendMaxSamples, which is the source of truth -
|
||||
// keep the two in step.
|
||||
constexpr Int kFrontendMaxSamples = 4;
|
||||
const Int advertisedMaxSamples =
|
||||
std::max(g_GLESCapabilities.MaxSamples, kFrontendMaxSamples);
|
||||
const Bool viewportLoweringArmed = !g_GLESCapabilities.SupportsViewportArray;
|
||||
const Bool sampleClampArmed =
|
||||
g_GLESCapabilities.MaxColorTextureSamples < advertisedMaxSamples ||
|
||||
g_GLESCapabilities.MaxIntegerSamples < advertisedMaxSamples ||
|
||||
g_GLESCapabilities.MaxDepthTextureSamples < advertisedMaxSamples;
|
||||
MG_Util::ShaderTranspiler::ShaderCompiler::SpirvGateFeatures spirvGates;
|
||||
if (viewportLoweringArmed || sampleClampArmed) {
|
||||
spirvGates = MG_Util::ShaderTranspiler::ShaderCompiler::ProbeSpirvGateFeatures(
|
||||
*effectiveSpirv);
|
||||
}
|
||||
|
||||
Vector<unsigned int> loweredViewportSpirv;
|
||||
if (!g_GLESCapabilities.SupportsViewportArray &&
|
||||
MG_Util::ShaderTranspiler::ShaderCompiler::DeclaresViewportIndexBuiltin(*effectiveSpirv) &&
|
||||
if (viewportLoweringArmed && spirvGates.WritesViewportIndexOutput &&
|
||||
MG_Util::ShaderTranspiler::ShaderCompiler::LowerViewportIndexForEssl(
|
||||
*effectiveSpirv, loweredViewportSpirv, enableSpirvValidation) &&
|
||||
!loweredViewportSpirv.empty()) {
|
||||
@@ -4937,28 +4960,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// optimizer round trip for it. DirectVulkan is deliberately not given this: it
|
||||
// allocates the sample count it was asked for, so its modules are already right.
|
||||
Vector<unsigned int> clampedSampleSpirv;
|
||||
{
|
||||
// Recomputed here rather than calling GL_Getter's GetAdvertisedMaxSamples():
|
||||
// this is backend code and must not reach into the GL frontend. 4 is that
|
||||
// translation unit's kFrontendMaxSamples, which is the source of truth -
|
||||
// keep the two in step.
|
||||
constexpr Int kFrontendMaxSamples = 4;
|
||||
const Int advertisedMaxSamples =
|
||||
std::max(g_GLESCapabilities.MaxSamples, kFrontendMaxSamples);
|
||||
if ((g_GLESCapabilities.MaxColorTextureSamples < advertisedMaxSamples ||
|
||||
g_GLESCapabilities.MaxIntegerSamples < advertisedMaxSamples ||
|
||||
g_GLESCapabilities.MaxDepthTextureSamples < advertisedMaxSamples) &&
|
||||
MG_Util::ShaderTranspiler::ShaderCompiler::DeclaresMultisampledImage(
|
||||
*effectiveSpirv) &&
|
||||
MG_Util::ShaderTranspiler::ShaderCompiler::ClampMultisampleFetchesForEssl(
|
||||
*effectiveSpirv, clampedSampleSpirv,
|
||||
g_GLESCapabilities.MaxColorTextureSamples,
|
||||
g_GLESCapabilities.MaxIntegerSamples,
|
||||
g_GLESCapabilities.MaxDepthTextureSamples, advertisedMaxSamples,
|
||||
enableSpirvValidation) &&
|
||||
!clampedSampleSpirv.empty()) {
|
||||
effectiveSpirv = &clampedSampleSpirv;
|
||||
}
|
||||
if (sampleClampArmed && spirvGates.DeclaresMultisampledImage &&
|
||||
MG_Util::ShaderTranspiler::ShaderCompiler::ClampMultisampleFetchesForEssl(
|
||||
*effectiveSpirv, clampedSampleSpirv,
|
||||
g_GLESCapabilities.MaxColorTextureSamples,
|
||||
g_GLESCapabilities.MaxIntegerSamples,
|
||||
g_GLESCapabilities.MaxDepthTextureSamples, advertisedMaxSamples,
|
||||
enableSpirvValidation) &&
|
||||
!clampedSampleSpirv.empty()) {
|
||||
effectiveSpirv = &clampedSampleSpirv;
|
||||
}
|
||||
|
||||
// GLSL ES has no ARRAY vertex inputs, and SPIRV-Cross refuses the whole module
|
||||
|
||||
@@ -247,6 +247,26 @@ TEST_F(ClampMultisampleFetchTest, TheProbeAnswersOnlyForAMultisampledImage) {
|
||||
EXPECT_FALSE(ShaderCompiler::DeclaresMultisampledImage({}));
|
||||
}
|
||||
|
||||
// The combined probe answers both gate questions from one parse; it must agree with the
|
||||
// per-gate probes on the same modules and stay quiet for an empty stage.
|
||||
TEST_F(ClampMultisampleFetchTest, TheCombinedProbeAgreesWithThePerGateOnes) {
|
||||
const Vector<Uint32> integerMs = CompileFragment(kIntegerMultisampleFetch);
|
||||
ASSERT_FALSE(integerMs.empty());
|
||||
const auto msFeatures = ShaderCompiler::ProbeSpirvGateFeatures(integerMs);
|
||||
EXPECT_TRUE(msFeatures.DeclaresMultisampledImage);
|
||||
EXPECT_FALSE(msFeatures.WritesViewportIndexOutput);
|
||||
|
||||
const Vector<Uint32> plain = CompileFragment(kNoMultisampleFetch);
|
||||
ASSERT_FALSE(plain.empty());
|
||||
const auto plainFeatures = ShaderCompiler::ProbeSpirvGateFeatures(plain);
|
||||
EXPECT_FALSE(plainFeatures.DeclaresMultisampledImage);
|
||||
EXPECT_FALSE(plainFeatures.WritesViewportIndexOutput);
|
||||
|
||||
const auto emptyFeatures = ShaderCompiler::ProbeSpirvGateFeatures({});
|
||||
EXPECT_FALSE(emptyFeatures.DeclaresMultisampledImage);
|
||||
EXPECT_FALSE(emptyFeatures.WritesViewportIndexOutput);
|
||||
}
|
||||
|
||||
// The overwhelming majority of modules. Behind the probe they never reach the pass at all, but the
|
||||
// pass has to be inert for them on its own, or a future caller that forgets the gate silently
|
||||
// re-serialises every shader in the program.
|
||||
|
||||
@@ -672,6 +672,28 @@ namespace MobileGL {
|
||||
return ClampMultisampleFetchPass::DeclaresMultisampledImage(binary);
|
||||
}
|
||||
|
||||
ShaderCompiler::SpirvGateFeatures ShaderCompiler::ProbeSpirvGateFeatures(
|
||||
const Vector<Uint32>& binary) {
|
||||
SpirvGateFeatures features;
|
||||
if (binary.empty()) {
|
||||
return features;
|
||||
}
|
||||
std::unique_ptr<spvtools::opt::IRContext> context = spvtools::BuildModule(
|
||||
SPV_ENV_VULKAN_1_1,
|
||||
[](spv_message_level_t, const char*, const spv_position_t&, const char*) {},
|
||||
binary.data(), binary.size());
|
||||
if (!context) {
|
||||
// Unparseable here means unusable downstream too; let the ordinary transpile
|
||||
// path produce the error rather than inventing a verdict from it.
|
||||
return features;
|
||||
}
|
||||
features.WritesViewportIndexOutput =
|
||||
LowerViewportIndexPass::DeclaresViewportIndexBuiltin(context.get());
|
||||
features.DeclaresMultisampledImage =
|
||||
ClampMultisampleFetchPass::DeclaresMultisampledImage(context.get());
|
||||
return features;
|
||||
}
|
||||
|
||||
bool ShaderCompiler::SplitArrayVertexInputsForEssl(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary,
|
||||
const bool enableSpirvValidation) {
|
||||
|
||||
@@ -65,6 +65,16 @@ namespace MobileGL {
|
||||
// above has anything to do. The gate that keeps every other stage off an
|
||||
// optimizer round trip it does not need.
|
||||
static bool DeclaresMultisampledImage(const Vector<Uint32>& binary);
|
||||
// Both gate questions above answered from ONE parse. Every armed gate costs a
|
||||
// BuildModule per shader stage, and on a driver where both are armed (Mali: no
|
||||
// GL_OES_viewport_array AND integer multisample squeezed to 1) the separate
|
||||
// probes made compile-heavy workloads measurably slower - ReservedNames-class
|
||||
// CTS cases paid ~10%. Callers with more than one armed gate use this instead.
|
||||
struct SpirvGateFeatures {
|
||||
Bool WritesViewportIndexOutput = false;
|
||||
Bool DeclaresMultisampledImage = false;
|
||||
};
|
||||
static SpirvGateFeatures ProbeSpirvGateFeatures(const Vector<Uint32>& binary);
|
||||
// Replaces an ARRAY vertex input with one input per element at consecutive
|
||||
// locations, seeding a Private copy of the array so indexed reads still work.
|
||||
// GLSL ES has no array vertex inputs and SPIRV-Cross refuses the whole module
|
||||
|
||||
@@ -198,6 +198,10 @@ namespace MobileGL {
|
||||
// path produce the error rather than inventing a verdict from it.
|
||||
return false;
|
||||
}
|
||||
return DeclaresMultisampledImage(context.get());
|
||||
}
|
||||
|
||||
bool ClampMultisampleFetchPass::DeclaresMultisampledImage(IRContext* context) {
|
||||
for (const Instruction& type : context->module()->types_values()) {
|
||||
if (IsMultisampledImageType(&type)) {
|
||||
return true;
|
||||
|
||||
@@ -74,6 +74,10 @@ namespace MobileGL {
|
||||
// that read a multisample texture directly.
|
||||
static bool DeclaresMultisampledImage(const Vector<Uint32>& binary);
|
||||
|
||||
// Same question answered from an already-built module, so one parse can feed
|
||||
// several gates (ShaderCompiler::ProbeSpirvGateFeatures).
|
||||
static bool DeclaresMultisampledImage(spvtools::opt::IRContext* context);
|
||||
|
||||
static spvtools::Optimizer::PassToken CreateClampMultisampleFetchPass(
|
||||
Int32 maxColorSamples, Int32 maxIntegerSamples, Int32 maxDepthSamples,
|
||||
Int32 advertisedMaxSamples);
|
||||
|
||||
@@ -138,9 +138,13 @@ namespace MobileGL {
|
||||
// path produce the error rather than inventing a verdict from it.
|
||||
return false;
|
||||
}
|
||||
return DeclaresViewportIndexBuiltin(context.get());
|
||||
}
|
||||
|
||||
bool LowerViewportIndexPass::DeclaresViewportIndexBuiltin(IRContext* context) {
|
||||
for (const Instruction& annotation : context->annotations()) {
|
||||
if (IsViewportIndexBuiltinDecoration(annotation) &&
|
||||
GetDecoratedViewportIndexOutput(context.get(), annotation) != nullptr) {
|
||||
GetDecoratedViewportIndexOutput(context, annotation) != nullptr) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,10 @@ namespace MobileGL {
|
||||
// but the handful that route viewports from the shader.
|
||||
static bool DeclaresViewportIndexBuiltin(const Vector<Uint32>& binary);
|
||||
|
||||
// Same question answered from an already-built module, so one parse can feed
|
||||
// several gates (ShaderCompiler::ProbeSpirvGateFeatures).
|
||||
static bool DeclaresViewportIndexBuiltin(spvtools::opt::IRContext* context);
|
||||
|
||||
static spvtools::Optimizer::PassToken CreateLowerViewportIndexPass();
|
||||
};
|
||||
} // namespace ShaderTranspiler
|
||||
|
||||
Reference in New Issue
Block a user