mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 13:48:30 +09:00
[Fix, Test] (DirectVulkan, ShaderTranspiler, TraceReplay): derive NumSubgroups behind opt-in quirk
This commit is contained in:
@@ -435,6 +435,9 @@ jobs:
|
|||||||
if [ "${{ matrix.case.coherent_as_flush || false }}" = "true" ]; then
|
if [ "${{ matrix.case.coherent_as_flush || false }}" = "true" ]; then
|
||||||
extra_retrace_args+=(--coherent-as-flush)
|
extra_retrace_args+=(--coherent-as-flush)
|
||||||
fi
|
fi
|
||||||
|
if [ "${{ matrix.case.num_subgroups_quirk || false }}" = "true" ]; then
|
||||||
|
extra_retrace_args+=(--num-subgroups-quirk)
|
||||||
|
fi
|
||||||
|
|
||||||
run_retrace() {
|
run_retrace() {
|
||||||
timeout "$(( ${{ matrix.case.timeout_seconds }} + 300 ))" sh android-plugin/trace-replay-ci.sh \
|
timeout "$(( ${{ matrix.case.timeout_seconds }} + 300 ))" sh android-plugin/trace-replay-ci.sh \
|
||||||
|
|||||||
@@ -284,6 +284,7 @@ set(SOURCE_FILES
|
|||||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/SplitArrayVertexInputsPass.cpp
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/SplitArrayVertexInputsPass.cpp
|
||||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp
|
||||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ZeroBaseVertexPass.cpp
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ZeroBaseVertexPass.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeriveNumSubgroupsPass.cpp
|
||||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/NormalizeRectCoordinatesPass.cpp
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/NormalizeRectCoordinatesPass.cpp
|
||||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/Lower1DArrayImagesPass.cpp
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/Lower1DArrayImagesPass.cpp
|
||||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/BakeImageFormatsPass.cpp
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/BakeImageFormatsPass.cpp
|
||||||
|
|||||||
@@ -80,6 +80,11 @@ namespace MobileGL::MG_Config {
|
|||||||
#endif
|
#endif
|
||||||
// MOBILEGL_DISABLE_SUBGROUP: force-disable Vulkan shader subgroup support.
|
// MOBILEGL_DISABLE_SUBGROUP: force-disable Vulkan shader subgroup support.
|
||||||
Bool DisableSubgroup = false;
|
Bool DisableSubgroup = false;
|
||||||
|
// MOBILEGL_NUM_SUBGROUPS_QUIRK: derive compute gl_NumSubgroups from the local
|
||||||
|
// workgroup dimensions and gl_SubgroupSize instead of reading Vulkan's
|
||||||
|
// NumSubgroups builtin. Off by default; enable only for drivers whose builtin
|
||||||
|
// disagrees with the SubgroupId topology emitted by the same dispatch.
|
||||||
|
Bool NumSubgroupsQuirk = false;
|
||||||
// MOBILEGL_ADVERTISE_FP64: add GL_ARB_gpu_shader_fp64 to the advertised extension
|
// MOBILEGL_ADVERTISE_FP64: add GL_ARB_gpu_shader_fp64 to the advertised extension
|
||||||
// string. `double` in a shader always WORKS - it is narrowed to 32 bits before any
|
// string. `double` in a shader always WORKS - it is narrowed to 32 bits before any
|
||||||
// module reaches a backend (ShaderTranspiler::DemoteFloat64Pass) - but the extension
|
// module reaches a backend (ShaderTranspiler::DemoteFloat64Pass) - but the extension
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ namespace MobileGL::MG_ConfigLoader {
|
|||||||
QueryEnvVariable("MOBILEGL_TRACE_ANGLE_VARIANT", features.TraceAngleVariant, "");
|
QueryEnvVariable("MOBILEGL_TRACE_ANGLE_VARIANT", features.TraceAngleVariant, "");
|
||||||
#endif
|
#endif
|
||||||
features.DisableSubgroup = QueryEnvFlag("MOBILEGL_DISABLE_SUBGROUP");
|
features.DisableSubgroup = QueryEnvFlag("MOBILEGL_DISABLE_SUBGROUP");
|
||||||
|
features.NumSubgroupsQuirk = QueryEnvFlag("MOBILEGL_NUM_SUBGROUPS_QUIRK");
|
||||||
features.AdvertiseFp64 = QueryEnvFlag("MOBILEGL_ADVERTISE_FP64");
|
features.AdvertiseFp64 = QueryEnvFlag("MOBILEGL_ADVERTISE_FP64");
|
||||||
features.MagmaR11G11B10FFallback = QueryEnvFlag("MOBILEGL_MAGMA_R11G11B10F_FALLBACK");
|
features.MagmaR11G11B10FFallback = QueryEnvFlag("MOBILEGL_MAGMA_R11G11B10F_FALLBACK");
|
||||||
features.MagmaFramesInFlight = QueryEnvUint32("MOBILEGL_MAGMA_FRAMESINFLIGHT", 3, 1, 64);
|
features.MagmaFramesInFlight = QueryEnvUint32("MOBILEGL_MAGMA_FRAMESINFLIGHT", 3, 1, 64);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include "ProgramFactory.h"
|
#include "ProgramFactory.h"
|
||||||
|
|
||||||
|
#include "Config.h"
|
||||||
#include "MG_Backend/DirectVulkan/DirectVulkanResourceState.h"
|
#include "MG_Backend/DirectVulkan/DirectVulkanResourceState.h"
|
||||||
#include "MG_Util/ShaderTranspiler/ShaderCompiler.h"
|
#include "MG_Util/ShaderTranspiler/ShaderCompiler.h"
|
||||||
#include "MG_Util/ShaderTranspiler/SpvcSession.h"
|
#include "MG_Util/ShaderTranspiler/SpvcSession.h"
|
||||||
@@ -3163,6 +3164,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NumSubgroups is defined by the local workgroup dimensions and SubgroupSize. Derive
|
||||||
|
// it in SPIR-V instead of trusting a driver builtin that can disagree with the
|
||||||
|
// SubgroupId topology produced by the same compute dispatch (Adreno reports 1 while
|
||||||
|
// emitting IDs 0..7 for a 512-invocation, 64-wide workgroup).
|
||||||
|
if (MG_Config::Features.NumSubgroupsQuirk && shaders[i] &&
|
||||||
|
shaders[i]->GetShaderStage() == ShaderStage::Compute) {
|
||||||
|
Vector<Uint> derivedNumSubgroupsSpirv;
|
||||||
|
if (MG_Util::ShaderTranspiler::ShaderCompiler::DeriveNumSubgroupsForVulkan(
|
||||||
|
moduleSpirvs[i], derivedNumSubgroupsSpirv, enableSpirvValidation)) {
|
||||||
|
moduleSpirvs[i] = std::move(derivedNumSubgroupsSpirv);
|
||||||
|
} else {
|
||||||
|
MGLOG_E("ProgramFactory: failed to derive gl_NumSubgroups for program %u; "
|
||||||
|
"compute shaders may observe a driver-inconsistent subgroup count",
|
||||||
|
program.GetExternalIndex());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Vulkan's SPIR-V environment has no rectangle image dimension, so a
|
// Vulkan's SPIR-V environment has no rectangle image dimension, so a
|
||||||
// GL_TEXTURE_RECTANGLE lookup has to become the 2D one the texture is really
|
// GL_TEXTURE_RECTANGLE lookup has to become the 2D one the texture is really
|
||||||
// stored as - which addresses [0,1] where the application addressed texels.
|
// stored as - which addresses [0,1] where the application addressed texels.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.14)
|
|||||||
add_executable(
|
add_executable(
|
||||||
SpirvPassTest
|
SpirvPassTest
|
||||||
SpirvPassTest.cpp
|
SpirvPassTest.cpp
|
||||||
|
DeriveNumSubgroupsTest.cpp
|
||||||
DemoteFloat64Test.cpp
|
DemoteFloat64Test.cpp
|
||||||
FlattenXfbInterfaceBlocksTest.cpp
|
FlattenXfbInterfaceBlocksTest.cpp
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
// MobileGL - MobileGL/MG_Test/ShaderTranspiler/DeriveNumSubgroupsTest.cpp
|
||||||
|
// Copyright (c) 2026 MobileGL-Dev
|
||||||
|
// Licensed under the GNU Lesser General Public License v3.0:
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||||
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
// End of Source File Header
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#define SPV_ENABLE_UTILITY_CODE
|
||||||
|
#include "glslang/SPIRV/spirv.hpp11"
|
||||||
|
#undef SPV_ENABLE_UTILITY_CODE
|
||||||
|
|
||||||
|
#include "Includes.h"
|
||||||
|
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
|
||||||
|
#include <MG_Util/ShaderTranspiler/Types.h>
|
||||||
|
|
||||||
|
#include <spirv-tools/libspirv.hpp>
|
||||||
|
|
||||||
|
using namespace MobileGL;
|
||||||
|
using MobileGL::MG_Util::ShaderTranspiler::ShaderCompiler;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr SizeT kSpirvHeaderWordCount = 5u;
|
||||||
|
|
||||||
|
template <typename Visitor>
|
||||||
|
void ForEachInstruction(const Vector<Uint32>& spirv, Visitor&& visit) {
|
||||||
|
for (SizeT offset = kSpirvHeaderWordCount; offset < spirv.size();) {
|
||||||
|
const Uint32 wordCount = spirv[offset] >> 16u;
|
||||||
|
if (wordCount == 0u || offset + wordCount > spirv.size()) break;
|
||||||
|
visit(static_cast<spv::Op>(spirv[offset] & 0xffffu), &spirv[offset], wordCount);
|
||||||
|
offset += wordCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<Uint32> CompileCompute(const String& source) {
|
||||||
|
using namespace MobileGL::MG_Util::ShaderTranspiler;
|
||||||
|
ShaderAttrib shaderAttrib{.shaderType = GL_COMPUTE_SHADER, .sourceStr = source};
|
||||||
|
auto shaderResult = ShaderCompiler::CompileShader(shaderAttrib);
|
||||||
|
EXPECT_TRUE(shaderResult) << (shaderResult ? String{} : shaderResult.error().log);
|
||||||
|
if (!shaderResult) return {};
|
||||||
|
|
||||||
|
ProgramAttrib programAttrib{.shaders = {shaderResult.value()}};
|
||||||
|
auto programResult = ShaderCompiler::LinkProgram(programAttrib);
|
||||||
|
EXPECT_TRUE(programResult) << (programResult ? String{} : programResult.error().log);
|
||||||
|
if (!programResult) return {};
|
||||||
|
|
||||||
|
ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_COMPUTE_SHADER}, .program = *programResult.value()};
|
||||||
|
auto binaryResult = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib);
|
||||||
|
EXPECT_TRUE(binaryResult) << (binaryResult ? String{} : binaryResult.error().log);
|
||||||
|
if (!binaryResult || binaryResult->empty()) return {};
|
||||||
|
return binaryResult->front();
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 FindBuiltinTarget(const Vector<Uint32>& spirv, spv::BuiltIn builtin) {
|
||||||
|
Uint32 target = 0u;
|
||||||
|
ForEachInstruction(spirv, [&](spv::Op opcode, const Uint32* words, Uint32 wordCount) {
|
||||||
|
if (opcode == spv::Op::OpDecorate && wordCount >= 4u &&
|
||||||
|
static_cast<spv::Decoration>(words[2]) == spv::Decoration::BuiltIn &&
|
||||||
|
static_cast<spv::BuiltIn>(words[3]) == builtin) {
|
||||||
|
target = words[1];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 CountLoadsFrom(const Vector<Uint32>& spirv, Uint32 pointerId) {
|
||||||
|
Uint32 count = 0u;
|
||||||
|
ForEachInstruction(spirv, [&](spv::Op opcode, const Uint32* words, Uint32 wordCount) {
|
||||||
|
if (opcode == spv::Op::OpLoad && wordCount >= 4u && words[3] == pointerId) ++count;
|
||||||
|
});
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 CountOpcode(const Vector<Uint32>& spirv, spv::Op wanted) {
|
||||||
|
Uint32 count = 0u;
|
||||||
|
ForEachInstruction(spirv, [&](spv::Op opcode, const Uint32*, Uint32) {
|
||||||
|
if (opcode == wanted) ++count;
|
||||||
|
});
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Validates(const Vector<Uint32>& spirv) {
|
||||||
|
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
|
||||||
|
return tools.Validate(spirv);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const char* kNumSubgroupsOnlySource = R"(#version 450 core
|
||||||
|
#extension GL_KHR_shader_subgroup_basic : require
|
||||||
|
layout(local_size_x = 32, local_size_y = 16, local_size_z = 1) in;
|
||||||
|
layout(std430, binding = 0) buffer Output { uint value; } outputData;
|
||||||
|
void main() {
|
||||||
|
if (gl_LocalInvocationIndex == 0u)
|
||||||
|
outputData.value = gl_NumSubgroups;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
constexpr const char* kNoNumSubgroupsSource = R"(#version 450 core
|
||||||
|
layout(local_size_x = 32, local_size_y = 16, local_size_z = 1) in;
|
||||||
|
layout(std430, binding = 0) buffer Output { uint value; } outputData;
|
||||||
|
void main() {
|
||||||
|
if (gl_LocalInvocationIndex == 0u)
|
||||||
|
outputData.value = gl_WorkGroupSize.x;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(DeriveNumSubgroupsPass, ReplacesBuiltinLoadAndSynthesizesSubgroupSize) {
|
||||||
|
const Vector<Uint32> input = CompileCompute(kNumSubgroupsOnlySource);
|
||||||
|
ASSERT_FALSE(input.empty());
|
||||||
|
const Uint32 inputNumSubgroups = FindBuiltinTarget(input, spv::BuiltIn::NumSubgroups);
|
||||||
|
ASSERT_NE(inputNumSubgroups, 0u);
|
||||||
|
EXPECT_EQ(CountLoadsFrom(input, inputNumSubgroups), 1u);
|
||||||
|
EXPECT_EQ(FindBuiltinTarget(input, spv::BuiltIn::SubgroupSize), 0u);
|
||||||
|
|
||||||
|
Vector<Uint32> output;
|
||||||
|
ASSERT_TRUE(ShaderCompiler::DeriveNumSubgroupsForVulkan(input, output, true));
|
||||||
|
ASSERT_TRUE(Validates(output));
|
||||||
|
|
||||||
|
const Uint32 outputNumSubgroups = FindBuiltinTarget(output, spv::BuiltIn::NumSubgroups);
|
||||||
|
const Uint32 outputSubgroupSize = FindBuiltinTarget(output, spv::BuiltIn::SubgroupSize);
|
||||||
|
ASSERT_NE(outputNumSubgroups, 0u);
|
||||||
|
ASSERT_NE(outputSubgroupSize, 0u);
|
||||||
|
EXPECT_EQ(CountLoadsFrom(output, outputNumSubgroups), 0u);
|
||||||
|
EXPECT_EQ(CountLoadsFrom(output, outputSubgroupSize), 1u);
|
||||||
|
EXPECT_EQ(CountOpcode(output, spv::Op::OpCompositeExtract), 3u);
|
||||||
|
EXPECT_EQ(CountOpcode(output, spv::Op::OpIMul), 2u);
|
||||||
|
EXPECT_EQ(CountOpcode(output, spv::Op::OpUDiv), 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DeriveNumSubgroupsPass, IsIdempotent) {
|
||||||
|
Vector<Uint32> once;
|
||||||
|
ASSERT_TRUE(ShaderCompiler::DeriveNumSubgroupsForVulkan(CompileCompute(kNumSubgroupsOnlySource), once, true));
|
||||||
|
Vector<Uint32> twice;
|
||||||
|
ASSERT_TRUE(ShaderCompiler::DeriveNumSubgroupsForVulkan(once, twice, true));
|
||||||
|
EXPECT_EQ(twice, once);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DeriveNumSubgroupsPass, LeavesUnrelatedComputeShaderUntouched) {
|
||||||
|
const Vector<Uint32> input = CompileCompute(kNoNumSubgroupsSource);
|
||||||
|
ASSERT_FALSE(input.empty());
|
||||||
|
Vector<Uint32> output;
|
||||||
|
ASSERT_TRUE(ShaderCompiler::DeriveNumSubgroupsForVulkan(input, output, true));
|
||||||
|
EXPECT_EQ(output, input);
|
||||||
|
EXPECT_EQ(FindBuiltinTarget(output, spv::BuiltIn::SubgroupSize), 0u);
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "SpirvPasses/SplitArrayVertexInputsPass.h"
|
#include "SpirvPasses/SplitArrayVertexInputsPass.h"
|
||||||
#include "SpirvPasses/RebaseInstanceIndexPass.h"
|
#include "SpirvPasses/RebaseInstanceIndexPass.h"
|
||||||
#include "SpirvPasses/ZeroBaseVertexPass.h"
|
#include "SpirvPasses/ZeroBaseVertexPass.h"
|
||||||
|
#include "SpirvPasses/DeriveNumSubgroupsPass.h"
|
||||||
#include "SpirvPasses/NormalizeRectCoordinatesPass.h"
|
#include "SpirvPasses/NormalizeRectCoordinatesPass.h"
|
||||||
#include "SpirvPasses/Lower1DArrayImagesPass.h"
|
#include "SpirvPasses/Lower1DArrayImagesPass.h"
|
||||||
#include "SpirvPasses/BakeImageFormatsPass.h"
|
#include "SpirvPasses/BakeImageFormatsPass.h"
|
||||||
@@ -883,6 +884,17 @@ namespace MobileGL {
|
|||||||
return RunOptimizerChecked("ZeroBaseVertexForVulkan", optimizer, inputBinary, outputBinary, true, enableSpirvValidation);
|
return RunOptimizerChecked("ZeroBaseVertexForVulkan", optimizer, inputBinary, outputBinary, true, enableSpirvValidation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ShaderCompiler::DeriveNumSubgroupsForVulkan(const Vector<Uint32>& inputBinary,
|
||||||
|
Vector<uint32_t>& outputBinary,
|
||||||
|
const bool enableSpirvValidation) {
|
||||||
|
using namespace spvtools;
|
||||||
|
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||||
|
optimizer.RegisterPass(DeriveNumSubgroupsPass::CreateDeriveNumSubgroupsPass());
|
||||||
|
|
||||||
|
return RunOptimizerChecked("DeriveNumSubgroupsForVulkan", optimizer, inputBinary,
|
||||||
|
outputBinary, true, enableSpirvValidation);
|
||||||
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::DecoratePositionInvariantForVulkan(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::DecoratePositionInvariantForVulkan(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary, const bool enableSpirvValidation) {
|
Vector<uint32_t>& outputBinary, const bool enableSpirvValidation) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
|
|||||||
@@ -145,6 +145,13 @@ namespace MobileGL {
|
|||||||
static bool ZeroBaseVertexForVulkan(const Vector<Uint32>& inputBinary,
|
static bool ZeroBaseVertexForVulkan(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary,
|
Vector<uint32_t>& outputBinary,
|
||||||
bool enableSpirvValidation = false);
|
bool enableSpirvValidation = false);
|
||||||
|
// Replaces compute gl_NumSubgroups loads with the value derived from the local
|
||||||
|
// workgroup dimensions and gl_SubgroupSize. DirectVulkan only; this avoids a
|
||||||
|
// driver builtin that can disagree with the subgroup IDs the same dispatch emits.
|
||||||
|
// See DeriveNumSubgroupsPass.
|
||||||
|
static bool DeriveNumSubgroupsForVulkan(const Vector<Uint32>& inputBinary,
|
||||||
|
Vector<uint32_t>& outputBinary,
|
||||||
|
bool enableSpirvValidation = false);
|
||||||
// Re-declares 64-bit float vertex inputs as their 32-bit unsigned word pair
|
// Re-declares 64-bit float vertex inputs as their 32-bit unsigned word pair
|
||||||
// (double -> uvec2, dvec2 -> uvec4) and bitcasts them back to double at entry, so no
|
// (double -> uvec2, dvec2 -> uvec4) and bitcasts them back to double at entry, so no
|
||||||
// VK_FORMAT_R64*_SFLOAT is needed - lavapipe advertises none of them for vertex
|
// VK_FORMAT_R64*_SFLOAT is needed - lavapipe advertises none of them for vertex
|
||||||
|
|||||||
@@ -0,0 +1,251 @@
|
|||||||
|
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeriveNumSubgroupsPass.cpp
|
||||||
|
// Copyright (c) 2026 MobileGL-Dev
|
||||||
|
// Licensed under the GNU Lesser General Public License v3.0:
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||||
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
// End of Source File Header
|
||||||
|
|
||||||
|
#include "DeriveNumSubgroupsPass.h"
|
||||||
|
|
||||||
|
#include "spirv.hpp"
|
||||||
|
#include "source/opt/constants.h"
|
||||||
|
#include "source/opt/def_use_manager.h"
|
||||||
|
#include "source/opt/instruction.h"
|
||||||
|
#include "source/opt/ir_context.h"
|
||||||
|
#include "source/opt/module.h"
|
||||||
|
#include "source/util/make_unique.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace MobileGL {
|
||||||
|
namespace MG_Util {
|
||||||
|
namespace ShaderTranspiler {
|
||||||
|
namespace {
|
||||||
|
using spvtools::opt::Instruction;
|
||||||
|
using spvtools::opt::IRContext;
|
||||||
|
using spvtools::opt::Operand;
|
||||||
|
|
||||||
|
Instruction* FindBuiltinDefinition(IRContext* context, spv::BuiltIn builtin) {
|
||||||
|
auto* defUseMgr = context->get_def_use_mgr();
|
||||||
|
for (auto& annotation : context->annotations()) {
|
||||||
|
if (annotation.opcode() != spv::Op::OpDecorate || annotation.NumInOperands() < 3) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (static_cast<spv::Decoration>(annotation.GetSingleWordInOperand(1)) !=
|
||||||
|
spv::Decoration::BuiltIn) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (static_cast<spv::BuiltIn>(annotation.GetSingleWordInOperand(2)) != builtin) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return defUseMgr->GetDef(annotation.GetSingleWordInOperand(0));
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsInputPointerTo(IRContext* context, const Instruction* variable, uint32_t pointeeTypeId) {
|
||||||
|
if (variable == nullptr || variable->opcode() != spv::Op::OpVariable ||
|
||||||
|
variable->NumInOperands() < 1 ||
|
||||||
|
static_cast<spv::StorageClass>(variable->GetSingleWordInOperand(0)) !=
|
||||||
|
spv::StorageClass::Input) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const Instruction* pointerType = context->get_def_use_mgr()->GetDef(variable->type_id());
|
||||||
|
return pointerType != nullptr && pointerType->opcode() == spv::Op::OpTypePointer &&
|
||||||
|
pointerType->NumInOperands() >= 2 &&
|
||||||
|
static_cast<spv::StorageClass>(pointerType->GetSingleWordInOperand(0)) ==
|
||||||
|
spv::StorageClass::Input &&
|
||||||
|
pointerType->GetSingleWordInOperand(1) == pointeeTypeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsUnsignedInt32(IRContext* context, uint32_t typeId) {
|
||||||
|
const Instruction* type = context->get_def_use_mgr()->GetDef(typeId);
|
||||||
|
return type != nullptr && type->opcode() == spv::Op::OpTypeInt &&
|
||||||
|
type->NumInOperands() >= 2 && type->GetSingleWordInOperand(0) == 32u &&
|
||||||
|
type->GetSingleWordInOperand(1) == 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t SynthesizeSubgroupSizeVariable(IRContext* context, uint32_t pointerTypeId) {
|
||||||
|
const uint32_t variableId = context->TakeNextId();
|
||||||
|
context->AddGlobalValue(spvtools::MakeUnique<Instruction>(
|
||||||
|
context, spv::Op::OpVariable, pointerTypeId, variableId,
|
||||||
|
std::initializer_list<Operand>{
|
||||||
|
{SPV_OPERAND_TYPE_STORAGE_CLASS,
|
||||||
|
{static_cast<uint32_t>(spv::StorageClass::Input)}}}));
|
||||||
|
context->AddAnnotationInst(spvtools::MakeUnique<Instruction>(
|
||||||
|
context, spv::Op::OpDecorate, 0, 0,
|
||||||
|
std::initializer_list<Operand>{
|
||||||
|
{SPV_OPERAND_TYPE_ID, {variableId}},
|
||||||
|
{SPV_OPERAND_TYPE_DECORATION,
|
||||||
|
{static_cast<uint32_t>(spv::Decoration::BuiltIn)}},
|
||||||
|
{SPV_OPERAND_TYPE_LITERAL_INTEGER,
|
||||||
|
{static_cast<uint32_t>(spv::BuiltIn::SubgroupSize)}}}));
|
||||||
|
|
||||||
|
for (Instruction& entryPoint : context->module()->entry_points()) {
|
||||||
|
entryPoint.AddOperand({SPV_OPERAND_TYPE_ID, {variableId}});
|
||||||
|
}
|
||||||
|
return variableId;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
spvtools::opt::Pass::Status DeriveNumSubgroupsPass::Process() {
|
||||||
|
auto* irContext = context();
|
||||||
|
auto* defUseMgr = irContext->get_def_use_mgr();
|
||||||
|
|
||||||
|
Instruction* numSubgroupsVar = FindBuiltinDefinition(irContext, spv::BuiltIn::NumSubgroups);
|
||||||
|
if (numSubgroupsVar == nullptr) {
|
||||||
|
return Status::SuccessWithoutChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Instruction*> numSubgroupsLoads;
|
||||||
|
bool sawUnexpectedUser = false;
|
||||||
|
const uint32_t numSubgroupsVarId = numSubgroupsVar->result_id();
|
||||||
|
defUseMgr->ForEachUser(numSubgroupsVar, [&](Instruction* user) {
|
||||||
|
switch (user->opcode()) {
|
||||||
|
case spv::Op::OpLoad:
|
||||||
|
if (user->NumInOperands() >= 1 &&
|
||||||
|
user->GetSingleWordInOperand(0) == numSubgroupsVarId) {
|
||||||
|
numSubgroupsLoads.push_back(user);
|
||||||
|
} else {
|
||||||
|
sawUnexpectedUser = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case spv::Op::OpDecorate:
|
||||||
|
case spv::Op::OpDecorateId:
|
||||||
|
case spv::Op::OpDecorateString:
|
||||||
|
case spv::Op::OpName:
|
||||||
|
case spv::Op::OpEntryPoint:
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
sawUnexpectedUser = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (sawUnexpectedUser) {
|
||||||
|
return Status::Failure;
|
||||||
|
}
|
||||||
|
if (numSubgroupsLoads.empty()) {
|
||||||
|
return Status::SuccessWithoutChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t valueTypeId = numSubgroupsLoads.front()->type_id();
|
||||||
|
if (!IsUnsignedInt32(irContext, valueTypeId) ||
|
||||||
|
!IsInputPointerTo(irContext, numSubgroupsVar, valueTypeId)) {
|
||||||
|
return Status::Failure;
|
||||||
|
}
|
||||||
|
for (const Instruction* load : numSubgroupsLoads) {
|
||||||
|
if (load->type_id() != valueTypeId) {
|
||||||
|
return Status::Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Instruction* workgroupSize = FindBuiltinDefinition(irContext, spv::BuiltIn::WorkgroupSize);
|
||||||
|
if (workgroupSize == nullptr ||
|
||||||
|
(workgroupSize->opcode() != spv::Op::OpConstantComposite &&
|
||||||
|
workgroupSize->opcode() != spv::Op::OpSpecConstantComposite)) {
|
||||||
|
return Status::Failure;
|
||||||
|
}
|
||||||
|
const Instruction* workgroupSizeType = defUseMgr->GetDef(workgroupSize->type_id());
|
||||||
|
if (workgroupSizeType == nullptr || workgroupSizeType->opcode() != spv::Op::OpTypeVector ||
|
||||||
|
workgroupSizeType->NumInOperands() < 2 ||
|
||||||
|
workgroupSizeType->GetSingleWordInOperand(0) != valueTypeId ||
|
||||||
|
workgroupSizeType->GetSingleWordInOperand(1) != 3u) {
|
||||||
|
return Status::Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instruction* subgroupSizeVar = FindBuiltinDefinition(irContext, spv::BuiltIn::SubgroupSize);
|
||||||
|
if (subgroupSizeVar != nullptr &&
|
||||||
|
!IsInputPointerTo(irContext, subgroupSizeVar, valueTypeId)) {
|
||||||
|
return Status::Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* constantMgr = irContext->get_constant_mgr();
|
||||||
|
auto* typeMgr = irContext->get_type_mgr();
|
||||||
|
const auto* valueType = typeMgr->GetType(valueTypeId);
|
||||||
|
if (valueType == nullptr) {
|
||||||
|
return Status::Failure;
|
||||||
|
}
|
||||||
|
const auto* one = constantMgr->GetConstant(valueType, {1u});
|
||||||
|
const Instruction* oneInst =
|
||||||
|
one != nullptr ? constantMgr->GetDefiningInstruction(one, valueTypeId) : nullptr;
|
||||||
|
if (oneInst == nullptr) {
|
||||||
|
return Status::Failure;
|
||||||
|
}
|
||||||
|
const uint32_t oneId = oneInst->result_id();
|
||||||
|
|
||||||
|
const uint32_t subgroupSizeVarId = subgroupSizeVar != nullptr
|
||||||
|
? subgroupSizeVar->result_id()
|
||||||
|
: SynthesizeSubgroupSizeVariable(irContext, numSubgroupsVar->type_id());
|
||||||
|
const uint32_t workgroupSizeId = workgroupSize->result_id();
|
||||||
|
|
||||||
|
// The pipeline never enables ALLOW_VARYING_SUBGROUP_SIZE, so Vulkan's fixed
|
||||||
|
// subgroup partition is exactly ceil(local invocation count / SubgroupSize).
|
||||||
|
// `(count - 1) / size + 1` avoids an addition overflow at count + size - 1.
|
||||||
|
for (Instruction* load : numSubgroupsLoads) {
|
||||||
|
const uint32_t localSizeXId = irContext->TakeNextId();
|
||||||
|
const uint32_t localSizeYId = irContext->TakeNextId();
|
||||||
|
const uint32_t localSizeZId = irContext->TakeNextId();
|
||||||
|
const uint32_t localSizeXYId = irContext->TakeNextId();
|
||||||
|
const uint32_t invocationCountId = irContext->TakeNextId();
|
||||||
|
const uint32_t adjustedCountId = irContext->TakeNextId();
|
||||||
|
const uint32_t subgroupSizeId = irContext->TakeNextId();
|
||||||
|
const uint32_t quotientId = irContext->TakeNextId();
|
||||||
|
|
||||||
|
load->InsertBefore(spvtools::MakeUnique<Instruction>(
|
||||||
|
irContext, spv::Op::OpCompositeExtract, valueTypeId, localSizeXId,
|
||||||
|
std::initializer_list<Operand>{
|
||||||
|
{SPV_OPERAND_TYPE_ID, {workgroupSizeId}},
|
||||||
|
{SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}}}));
|
||||||
|
load->InsertBefore(spvtools::MakeUnique<Instruction>(
|
||||||
|
irContext, spv::Op::OpCompositeExtract, valueTypeId, localSizeYId,
|
||||||
|
std::initializer_list<Operand>{
|
||||||
|
{SPV_OPERAND_TYPE_ID, {workgroupSizeId}},
|
||||||
|
{SPV_OPERAND_TYPE_LITERAL_INTEGER, {1u}}}));
|
||||||
|
load->InsertBefore(spvtools::MakeUnique<Instruction>(
|
||||||
|
irContext, spv::Op::OpCompositeExtract, valueTypeId, localSizeZId,
|
||||||
|
std::initializer_list<Operand>{
|
||||||
|
{SPV_OPERAND_TYPE_ID, {workgroupSizeId}},
|
||||||
|
{SPV_OPERAND_TYPE_LITERAL_INTEGER, {2u}}}));
|
||||||
|
load->InsertBefore(spvtools::MakeUnique<Instruction>(
|
||||||
|
irContext, spv::Op::OpIMul, valueTypeId, localSizeXYId,
|
||||||
|
std::initializer_list<Operand>{
|
||||||
|
{SPV_OPERAND_TYPE_ID, {localSizeXId}},
|
||||||
|
{SPV_OPERAND_TYPE_ID, {localSizeYId}}}));
|
||||||
|
load->InsertBefore(spvtools::MakeUnique<Instruction>(
|
||||||
|
irContext, spv::Op::OpIMul, valueTypeId, invocationCountId,
|
||||||
|
std::initializer_list<Operand>{
|
||||||
|
{SPV_OPERAND_TYPE_ID, {localSizeXYId}},
|
||||||
|
{SPV_OPERAND_TYPE_ID, {localSizeZId}}}));
|
||||||
|
load->InsertBefore(spvtools::MakeUnique<Instruction>(
|
||||||
|
irContext, spv::Op::OpISub, valueTypeId, adjustedCountId,
|
||||||
|
std::initializer_list<Operand>{
|
||||||
|
{SPV_OPERAND_TYPE_ID, {invocationCountId}},
|
||||||
|
{SPV_OPERAND_TYPE_ID, {oneId}}}));
|
||||||
|
load->InsertBefore(spvtools::MakeUnique<Instruction>(
|
||||||
|
irContext, spv::Op::OpLoad, valueTypeId, subgroupSizeId,
|
||||||
|
std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {subgroupSizeVarId}}}));
|
||||||
|
load->InsertBefore(spvtools::MakeUnique<Instruction>(
|
||||||
|
irContext, spv::Op::OpUDiv, valueTypeId, quotientId,
|
||||||
|
std::initializer_list<Operand>{
|
||||||
|
{SPV_OPERAND_TYPE_ID, {adjustedCountId}},
|
||||||
|
{SPV_OPERAND_TYPE_ID, {subgroupSizeId}}}));
|
||||||
|
|
||||||
|
// Preserve the original result id so every downstream use automatically sees
|
||||||
|
// the derived value instead of the driver's NumSubgroups builtin.
|
||||||
|
load->SetOpcode(spv::Op::OpIAdd);
|
||||||
|
load->SetInOperands(Instruction::OperandList{
|
||||||
|
{SPV_OPERAND_TYPE_ID, {quotientId}},
|
||||||
|
{SPV_OPERAND_TYPE_ID, {oneId}}});
|
||||||
|
}
|
||||||
|
|
||||||
|
irContext->InvalidateAnalysesExceptFor(IRContext::kAnalysisNone);
|
||||||
|
return Status::SuccessWithChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
spvtools::Optimizer::PassToken DeriveNumSubgroupsPass::CreateDeriveNumSubgroupsPass() {
|
||||||
|
return spvtools::Optimizer::PassToken(MakeUnique<DeriveNumSubgroupsPass>());
|
||||||
|
}
|
||||||
|
} // namespace ShaderTranspiler
|
||||||
|
} // namespace MG_Util
|
||||||
|
} // namespace MobileGL
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeriveNumSubgroupsPass.h
|
||||||
|
// Copyright (c) 2026 MobileGL-Dev
|
||||||
|
// Licensed under the GNU Lesser General Public License v3.0:
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||||
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
// End of Source File Header
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "source/opt/pass.h"
|
||||||
|
#include "spirv-tools/optimizer.hpp"
|
||||||
|
|
||||||
|
#include <Includes.h>
|
||||||
|
|
||||||
|
namespace MobileGL {
|
||||||
|
namespace MG_Util {
|
||||||
|
namespace ShaderTranspiler {
|
||||||
|
// Replaces compute-stage NumSubgroups builtin loads with
|
||||||
|
// ceil(WorkgroupSize.x * WorkgroupSize.y * WorkgroupSize.z / SubgroupSize).
|
||||||
|
//
|
||||||
|
// That is the value Vulkan defines for NumSubgroups when the pipeline does not
|
||||||
|
// enable varying subgroup sizes, which MobileGL never does. Deriving it avoids
|
||||||
|
// drivers that expose the real SubgroupId topology but return an inconsistent
|
||||||
|
// NumSubgroups value. This is a DirectVulkan semantic repair, not a source-shader
|
||||||
|
// rewrite; the application's subgroup arithmetic and shared-memory logic remain
|
||||||
|
// unchanged.
|
||||||
|
class DeriveNumSubgroupsPass : public spvtools::opt::Pass {
|
||||||
|
public:
|
||||||
|
const char* name() const override { return "derive-num-subgroups"; }
|
||||||
|
Status Process() override;
|
||||||
|
|
||||||
|
static spvtools::Optimizer::PassToken CreateDeriveNumSubgroupsPass();
|
||||||
|
};
|
||||||
|
} // namespace ShaderTranspiler
|
||||||
|
} // namespace MG_Util
|
||||||
|
} // namespace MobileGL
|
||||||
@@ -164,6 +164,11 @@ bool LoadMobileGL(const Request& request, std::string& error) {
|
|||||||
} else {
|
} else {
|
||||||
unsetenv("MOBILEGL_COHERENT_AS_FLUSH");
|
unsetenv("MOBILEGL_COHERENT_AS_FLUSH");
|
||||||
}
|
}
|
||||||
|
if (request.numSubgroupsQuirk) {
|
||||||
|
setenv("MOBILEGL_NUM_SUBGROUPS_QUIRK", "1", 1);
|
||||||
|
} else {
|
||||||
|
unsetenv("MOBILEGL_NUM_SUBGROUPS_QUIRK");
|
||||||
|
}
|
||||||
if (request.fboAttachmentDumps.empty()) {
|
if (request.fboAttachmentDumps.empty()) {
|
||||||
unsetenv("MOBILEGL_TRACE_DUMP_FBO_ATTACHMENTS");
|
unsetenv("MOBILEGL_TRACE_DUMP_FBO_ATTACHMENTS");
|
||||||
} else {
|
} else {
|
||||||
@@ -818,6 +823,7 @@ bool WriteResultJson(const Request& request, const Result& result) {
|
|||||||
<< (request.avoidAngleLlvmpipeSamplerMipmapMinFilter ? "true" : "false") << ",\n";
|
<< (request.avoidAngleLlvmpipeSamplerMipmapMinFilter ? "true" : "false") << ",\n";
|
||||||
file << " \"avoidAngleLlvmpipeExplicitLodBias\": "
|
file << " \"avoidAngleLlvmpipeExplicitLodBias\": "
|
||||||
<< (request.avoidAngleLlvmpipeExplicitLodBias ? "true" : "false") << ",\n";
|
<< (request.avoidAngleLlvmpipeExplicitLodBias ? "true" : "false") << ",\n";
|
||||||
|
file << " \"numSubgroupsQuirk\": " << (request.numSubgroupsQuirk ? "true" : "false") << ",\n";
|
||||||
file << " \"holdMs\": " << request.holdMs << ",\n";
|
file << " \"holdMs\": " << request.holdMs << ",\n";
|
||||||
file << " \"mismatchPixels\": " << result.mismatchPixels << "\n";
|
file << " \"mismatchPixels\": " << result.mismatchPixels << "\n";
|
||||||
file << "}\n";
|
file << "}\n";
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ struct Request {
|
|||||||
bool avoidAngleLlvmpipeSamplerMipmapMinFilter = false;
|
bool avoidAngleLlvmpipeSamplerMipmapMinFilter = false;
|
||||||
bool avoidAngleLlvmpipeExplicitLodBias = false;
|
bool avoidAngleLlvmpipeExplicitLodBias = false;
|
||||||
bool coherentAsFlush = false;
|
bool coherentAsFlush = false;
|
||||||
|
bool numSubgroupsQuirk = false;
|
||||||
int holdMs = 0;
|
int holdMs = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ Java_top_mobilegl_plugin_trace_TraceReplayActivity_nativeRunTraceReplay(JNIEnv*
|
|||||||
jboolean avoidAngleLlvmpipeSamplerMipmapMinFilter,
|
jboolean avoidAngleLlvmpipeSamplerMipmapMinFilter,
|
||||||
jboolean avoidAngleLlvmpipeExplicitLodBias,
|
jboolean avoidAngleLlvmpipeExplicitLodBias,
|
||||||
jboolean coherentAsFlush,
|
jboolean coherentAsFlush,
|
||||||
|
jboolean numSubgroupsQuirk,
|
||||||
jstring texture2dDumps) {
|
jstring texture2dDumps) {
|
||||||
mobilegl_trace::Request request;
|
mobilegl_trace::Request request;
|
||||||
request.tracePath = ToString(env, tracePath);
|
request.tracePath = ToString(env, tracePath);
|
||||||
@@ -150,6 +151,7 @@ Java_top_mobilegl_plugin_trace_TraceReplayActivity_nativeRunTraceReplay(JNIEnv*
|
|||||||
avoidAngleLlvmpipeSamplerMipmapMinFilter == JNI_TRUE;
|
avoidAngleLlvmpipeSamplerMipmapMinFilter == JNI_TRUE;
|
||||||
request.avoidAngleLlvmpipeExplicitLodBias = avoidAngleLlvmpipeExplicitLodBias == JNI_TRUE;
|
request.avoidAngleLlvmpipeExplicitLodBias = avoidAngleLlvmpipeExplicitLodBias == JNI_TRUE;
|
||||||
request.coherentAsFlush = coherentAsFlush == JNI_TRUE;
|
request.coherentAsFlush = coherentAsFlush == JNI_TRUE;
|
||||||
|
request.numSubgroupsQuirk = numSubgroupsQuirk == JNI_TRUE;
|
||||||
|
|
||||||
ScopedTraceReplayState replayState;
|
ScopedTraceReplayState replayState;
|
||||||
mobilegl_trace_set_requested_size(request.width, request.height);
|
mobilegl_trace_set_requested_size(request.width, request.height);
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ public final class TraceReplayActivity extends Activity {
|
|||||||
request.avoidAngleLlvmpipeSamplerMipmapMinFilter,
|
request.avoidAngleLlvmpipeSamplerMipmapMinFilter,
|
||||||
request.avoidAngleLlvmpipeExplicitLodBias,
|
request.avoidAngleLlvmpipeExplicitLodBias,
|
||||||
request.coherentAsFlush,
|
request.coherentAsFlush,
|
||||||
|
request.numSubgroupsQuirk,
|
||||||
request.texture2dDumps
|
request.texture2dDumps
|
||||||
);
|
);
|
||||||
Log.i(TAG, result.toString());
|
Log.i(TAG, result.toString());
|
||||||
@@ -149,6 +150,7 @@ public final class TraceReplayActivity extends Activity {
|
|||||||
boolean avoidAngleLlvmpipeSamplerMipmapMinFilter,
|
boolean avoidAngleLlvmpipeSamplerMipmapMinFilter,
|
||||||
boolean avoidAngleLlvmpipeExplicitLodBias,
|
boolean avoidAngleLlvmpipeExplicitLodBias,
|
||||||
boolean coherentAsFlush,
|
boolean coherentAsFlush,
|
||||||
|
boolean numSubgroupsQuirk,
|
||||||
String texture2dDumps
|
String texture2dDumps
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -174,6 +176,7 @@ public final class TraceReplayActivity extends Activity {
|
|||||||
final boolean avoidAngleLlvmpipeSamplerMipmapMinFilter;
|
final boolean avoidAngleLlvmpipeSamplerMipmapMinFilter;
|
||||||
final boolean avoidAngleLlvmpipeExplicitLodBias;
|
final boolean avoidAngleLlvmpipeExplicitLodBias;
|
||||||
final boolean coherentAsFlush;
|
final boolean coherentAsFlush;
|
||||||
|
final boolean numSubgroupsQuirk;
|
||||||
final String texture2dDumps;
|
final String texture2dDumps;
|
||||||
|
|
||||||
private TraceReplayRequest(
|
private TraceReplayRequest(
|
||||||
@@ -198,6 +201,7 @@ public final class TraceReplayActivity extends Activity {
|
|||||||
boolean avoidAngleLlvmpipeSamplerMipmapMinFilter,
|
boolean avoidAngleLlvmpipeSamplerMipmapMinFilter,
|
||||||
boolean avoidAngleLlvmpipeExplicitLodBias,
|
boolean avoidAngleLlvmpipeExplicitLodBias,
|
||||||
boolean coherentAsFlush,
|
boolean coherentAsFlush,
|
||||||
|
boolean numSubgroupsQuirk,
|
||||||
String texture2dDumps
|
String texture2dDumps
|
||||||
) {
|
) {
|
||||||
this.tracePath = tracePath;
|
this.tracePath = tracePath;
|
||||||
@@ -221,6 +225,7 @@ public final class TraceReplayActivity extends Activity {
|
|||||||
this.avoidAngleLlvmpipeSamplerMipmapMinFilter = avoidAngleLlvmpipeSamplerMipmapMinFilter;
|
this.avoidAngleLlvmpipeSamplerMipmapMinFilter = avoidAngleLlvmpipeSamplerMipmapMinFilter;
|
||||||
this.avoidAngleLlvmpipeExplicitLodBias = avoidAngleLlvmpipeExplicitLodBias;
|
this.avoidAngleLlvmpipeExplicitLodBias = avoidAngleLlvmpipeExplicitLodBias;
|
||||||
this.coherentAsFlush = coherentAsFlush;
|
this.coherentAsFlush = coherentAsFlush;
|
||||||
|
this.numSubgroupsQuirk = numSubgroupsQuirk;
|
||||||
this.texture2dDumps = texture2dDumps;
|
this.texture2dDumps = texture2dDumps;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,6 +254,7 @@ public final class TraceReplayActivity extends Activity {
|
|||||||
intent.getBooleanExtra("avoid_angle_llvmpipe_sampler_mipmap_min_filter", false),
|
intent.getBooleanExtra("avoid_angle_llvmpipe_sampler_mipmap_min_filter", false),
|
||||||
intent.getBooleanExtra("avoid_angle_llvmpipe_explicit_lod_bias", false),
|
intent.getBooleanExtra("avoid_angle_llvmpipe_explicit_lod_bias", false),
|
||||||
intent.getBooleanExtra("coherent_as_flush", false),
|
intent.getBooleanExtra("coherent_as_flush", false),
|
||||||
|
intent.getBooleanExtra("num_subgroups_quirk", false),
|
||||||
readString(intent, "texture_2d_dumps", "")
|
readString(intent, "texture_2d_dumps", "")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ Usage:
|
|||||||
[--avoid-angle-llvmpipe-sampler-mipmap-min-filter] \
|
[--avoid-angle-llvmpipe-sampler-mipmap-min-filter] \
|
||||||
[--avoid-angle-llvmpipe-explicit-lod-bias] \
|
[--avoid-angle-llvmpipe-explicit-lod-bias] \
|
||||||
[--coherent-as-flush] \
|
[--coherent-as-flush] \
|
||||||
|
[--num-subgroups-quirk] \
|
||||||
[--dump-texture-2d CALL,TEXTURE,LEVEL,DIR] \
|
[--dump-texture-2d CALL,TEXTURE,LEVEL,DIR] \
|
||||||
--timeout-seconds N
|
--timeout-seconds N
|
||||||
|
|
||||||
@@ -47,6 +48,8 @@ sample with an explicit LOD that ANGLE llvmpipe cannot take a LOD bias on
|
|||||||
(MOBILEGL_AVOID_EXPLICIT_LOD_BIAS=1).
|
(MOBILEGL_AVOID_EXPLICIT_LOD_BIAS=1).
|
||||||
Pass --coherent-as-flush for traces whose engine writes persistent
|
Pass --coherent-as-flush for traces whose engine writes persistent
|
||||||
GL_MAP_FLUSH_EXPLICIT_BIT maps it never flushes (MOBILEGL_COHERENT_AS_FLUSH=1).
|
GL_MAP_FLUSH_EXPLICIT_BIT maps it never flushes (MOBILEGL_COHERENT_AS_FLUSH=1).
|
||||||
|
Pass --num-subgroups-quirk to derive compute gl_NumSubgroups instead of reading
|
||||||
|
the Vulkan builtin (MOBILEGL_NUM_SUBGROUPS_QUIRK=1).
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +108,7 @@ use_pbuffer=0
|
|||||||
avoid_angle_llvmpipe_sampler_mipmap_min_filter=0
|
avoid_angle_llvmpipe_sampler_mipmap_min_filter=0
|
||||||
avoid_angle_llvmpipe_explicit_lod_bias=0
|
avoid_angle_llvmpipe_explicit_lod_bias=0
|
||||||
coherent_as_flush=0
|
coherent_as_flush=0
|
||||||
|
num_subgroups_quirk=0
|
||||||
texture_2d_dumps=""
|
texture_2d_dumps=""
|
||||||
timeout_seconds=""
|
timeout_seconds=""
|
||||||
|
|
||||||
@@ -146,6 +150,7 @@ while [ "$#" -gt 0 ]; do
|
|||||||
shift 1
|
shift 1
|
||||||
;;
|
;;
|
||||||
--coherent-as-flush) coherent_as_flush=1; shift 1 ;;
|
--coherent-as-flush) coherent_as_flush=1; shift 1 ;;
|
||||||
|
--num-subgroups-quirk) num_subgroups_quirk=1; shift 1 ;;
|
||||||
--dump-texture-2d) texture_2d_dumps="$(next_arg "$@")"; shift 2 ;;
|
--dump-texture-2d) texture_2d_dumps="$(next_arg "$@")"; shift 2 ;;
|
||||||
--timeout-seconds) timeout_seconds="$(next_arg "$@")"; shift 2 ;;
|
--timeout-seconds) timeout_seconds="$(next_arg "$@")"; shift 2 ;;
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
@@ -266,7 +271,7 @@ copy_app_artifact() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
copy_texture_2d_dumps() {
|
copy_texture_2d_dumps() {
|
||||||
[ -n "${texture_2d_dumps}" ] || return
|
[ -n "${texture_2d_dumps}" ] || return 0
|
||||||
saved_ifs="${IFS}"
|
saved_ifs="${IFS}"
|
||||||
IFS=';'
|
IFS=';'
|
||||||
set -- ${texture_2d_dumps}
|
set -- ${texture_2d_dumps}
|
||||||
@@ -363,6 +368,9 @@ run_retrace() {
|
|||||||
if [ "${coherent_as_flush}" -eq 1 ]; then
|
if [ "${coherent_as_flush}" -eq 1 ]; then
|
||||||
set -- "$@" --ez coherent_as_flush true
|
set -- "$@" --ez coherent_as_flush true
|
||||||
fi
|
fi
|
||||||
|
if [ "${num_subgroups_quirk}" -eq 1 ]; then
|
||||||
|
set -- "$@" --ez num_subgroups_quirk true
|
||||||
|
fi
|
||||||
if [ -n "${texture_2d_dumps}" ]; then
|
if [ -n "${texture_2d_dumps}" ]; then
|
||||||
set -- "$@" --es texture_2d_dumps "${texture_2d_dumps}"
|
set -- "$@" --es texture_2d_dumps "${texture_2d_dumps}"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -284,7 +284,8 @@
|
|||||||
"golden": "minecraft-1.21.4-fabric-iris-iterationrp-in-world.0000202020.png",
|
"golden": "minecraft-1.21.4-fabric-iris-iterationrp-in-world.0000202020.png",
|
||||||
"target_call": 202020,
|
"target_call": 202020,
|
||||||
"timeout_seconds": 1800,
|
"timeout_seconds": 1800,
|
||||||
"ssim_threshold": 0.98
|
"ssim_threshold": 0.98,
|
||||||
|
"num_subgroups_quirk": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "minecraft-1.21.4-fabric-iris-bsl-esc-menu-854",
|
"name": "minecraft-1.21.4-fabric-iris-bsl-esc-menu-854",
|
||||||
|
|||||||
Reference in New Issue
Block a user