mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +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.
|
// 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
|
// Gated on the module actually declaring the output, so no other stage pays an
|
||||||
// optimizer round trip for it.
|
// 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;
|
Vector<unsigned int> loweredViewportSpirv;
|
||||||
if (!g_GLESCapabilities.SupportsViewportArray &&
|
if (viewportLoweringArmed && spirvGates.WritesViewportIndexOutput &&
|
||||||
MG_Util::ShaderTranspiler::ShaderCompiler::DeclaresViewportIndexBuiltin(*effectiveSpirv) &&
|
|
||||||
MG_Util::ShaderTranspiler::ShaderCompiler::LowerViewportIndexForEssl(
|
MG_Util::ShaderTranspiler::ShaderCompiler::LowerViewportIndexForEssl(
|
||||||
*effectiveSpirv, loweredViewportSpirv, enableSpirvValidation) &&
|
*effectiveSpirv, loweredViewportSpirv, enableSpirvValidation) &&
|
||||||
!loweredViewportSpirv.empty()) {
|
!loweredViewportSpirv.empty()) {
|
||||||
@@ -4937,28 +4960,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// optimizer round trip for it. DirectVulkan is deliberately not given this: it
|
// 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.
|
// allocates the sample count it was asked for, so its modules are already right.
|
||||||
Vector<unsigned int> clampedSampleSpirv;
|
Vector<unsigned int> clampedSampleSpirv;
|
||||||
{
|
if (sampleClampArmed && spirvGates.DeclaresMultisampledImage &&
|
||||||
// Recomputed here rather than calling GL_Getter's GetAdvertisedMaxSamples():
|
MG_Util::ShaderTranspiler::ShaderCompiler::ClampMultisampleFetchesForEssl(
|
||||||
// this is backend code and must not reach into the GL frontend. 4 is that
|
*effectiveSpirv, clampedSampleSpirv,
|
||||||
// translation unit's kFrontendMaxSamples, which is the source of truth -
|
g_GLESCapabilities.MaxColorTextureSamples,
|
||||||
// keep the two in step.
|
g_GLESCapabilities.MaxIntegerSamples,
|
||||||
constexpr Int kFrontendMaxSamples = 4;
|
g_GLESCapabilities.MaxDepthTextureSamples, advertisedMaxSamples,
|
||||||
const Int advertisedMaxSamples =
|
enableSpirvValidation) &&
|
||||||
std::max(g_GLESCapabilities.MaxSamples, kFrontendMaxSamples);
|
!clampedSampleSpirv.empty()) {
|
||||||
if ((g_GLESCapabilities.MaxColorTextureSamples < advertisedMaxSamples ||
|
effectiveSpirv = &clampedSampleSpirv;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GLSL ES has no ARRAY vertex inputs, and SPIRV-Cross refuses the whole module
|
// 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({}));
|
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
|
// 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
|
// 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.
|
// re-serialises every shader in the program.
|
||||||
|
|||||||
@@ -672,6 +672,28 @@ namespace MobileGL {
|
|||||||
return ClampMultisampleFetchPass::DeclaresMultisampledImage(binary);
|
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,
|
bool ShaderCompiler::SplitArrayVertexInputsForEssl(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary,
|
Vector<uint32_t>& outputBinary,
|
||||||
const bool enableSpirvValidation) {
|
const bool enableSpirvValidation) {
|
||||||
|
|||||||
@@ -65,6 +65,16 @@ namespace MobileGL {
|
|||||||
// above has anything to do. The gate that keeps every other stage off an
|
// above has anything to do. The gate that keeps every other stage off an
|
||||||
// optimizer round trip it does not need.
|
// optimizer round trip it does not need.
|
||||||
static bool DeclaresMultisampledImage(const Vector<Uint32>& binary);
|
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
|
// 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.
|
// 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
|
// 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.
|
// path produce the error rather than inventing a verdict from it.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return DeclaresMultisampledImage(context.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClampMultisampleFetchPass::DeclaresMultisampledImage(IRContext* context) {
|
||||||
for (const Instruction& type : context->module()->types_values()) {
|
for (const Instruction& type : context->module()->types_values()) {
|
||||||
if (IsMultisampledImageType(&type)) {
|
if (IsMultisampledImageType(&type)) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -74,6 +74,10 @@ namespace MobileGL {
|
|||||||
// that read a multisample texture directly.
|
// that read a multisample texture directly.
|
||||||
static bool DeclaresMultisampledImage(const Vector<Uint32>& binary);
|
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(
|
static spvtools::Optimizer::PassToken CreateClampMultisampleFetchPass(
|
||||||
Int32 maxColorSamples, Int32 maxIntegerSamples, Int32 maxDepthSamples,
|
Int32 maxColorSamples, Int32 maxIntegerSamples, Int32 maxDepthSamples,
|
||||||
Int32 advertisedMaxSamples);
|
Int32 advertisedMaxSamples);
|
||||||
|
|||||||
@@ -138,9 +138,13 @@ namespace MobileGL {
|
|||||||
// path produce the error rather than inventing a verdict from it.
|
// path produce the error rather than inventing a verdict from it.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return DeclaresViewportIndexBuiltin(context.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LowerViewportIndexPass::DeclaresViewportIndexBuiltin(IRContext* context) {
|
||||||
for (const Instruction& annotation : context->annotations()) {
|
for (const Instruction& annotation : context->annotations()) {
|
||||||
if (IsViewportIndexBuiltinDecoration(annotation) &&
|
if (IsViewportIndexBuiltinDecoration(annotation) &&
|
||||||
GetDecoratedViewportIndexOutput(context.get(), annotation) != nullptr) {
|
GetDecoratedViewportIndexOutput(context, annotation) != nullptr) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ namespace MobileGL {
|
|||||||
// but the handful that route viewports from the shader.
|
// but the handful that route viewports from the shader.
|
||||||
static bool DeclaresViewportIndexBuiltin(const Vector<Uint32>& binary);
|
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();
|
static spvtools::Optimizer::PassToken CreateLowerViewportIndexPass();
|
||||||
};
|
};
|
||||||
} // namespace ShaderTranspiler
|
} // namespace ShaderTranspiler
|
||||||
|
|||||||
Reference in New Issue
Block a user