[Fix] (ShaderTranspiler): preserve block identity when deduplicating image types

This commit is contained in:
2026-10-05 01:27:50 -04:00
parent c464947bdd
commit c5d25868cf
5 changed files with 125 additions and 4 deletions
+1
View File
@@ -372,6 +372,7 @@ set(SOURCE_FILES
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FixIterationRPSubgroupScratchPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EmulateSubgroupsPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/NormalizeRectCoordinatesPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeduplicateImageTypesPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/Lower1DArrayImagesPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/Lower1DSampledImagesPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/BakeImageFormatsPass.cpp
@@ -124,12 +124,16 @@ layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(rgba16, binding = 0) uniform image2D floatImage;
layout(r32ui, binding = 1) uniform uimage2D atomicImage;
layout(rgba32f, binding = 2) uniform image2D otherFloatImages[2];
layout(std430, binding = 3) buffer Output0 { uint value0; };
layout(std430, binding = 4) buffer Output1 { uint value1; };
void main() {
ivec2 coordinate = ivec2(gl_GlobalInvocationID.xy);
imageStore(floatImage, coordinate, imageLoad(floatImage, coordinate));
imageStore(otherFloatImages[1], coordinate, imageLoad(otherFloatImages[0], coordinate));
imageAtomicAdd(atomicImage, coordinate, 1u);
value0 = 11u;
value1 = 22u;
}
)";
@@ -180,6 +184,8 @@ void main() {
EXPECT_NE(outputText.find("Binding 0"), String::npos) << outputText;
EXPECT_NE(outputText.find("Binding 1"), String::npos) << outputText;
EXPECT_NE(outputText.find("Binding 2"), String::npos) << outputText;
EXPECT_NE(outputText.find("\"Output0\""), String::npos) << outputText;
EXPECT_NE(outputText.find("\"Output1\""), String::npos) << outputText;
EXPECT_TRUE(tools.Validate(outputBinary));
Vector<Uint32> secondOutputBinary;
@@ -195,12 +201,28 @@ TEST_F(ProgramUtilTest, LowerRectImagesMergesWithExisting2DImageTypes) {
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpName %Output0 "Output0"
OpName %Output1 "Output1"
OpDecorate %Output0 Block
OpDecorate %Output1 Block
OpMemberDecorate %Output0 0 Offset 0
OpMemberDecorate %Output1 0 Offset 0
OpDecorate %buffer0 DescriptorSet 0
OpDecorate %buffer0 Binding 2
OpDecorate %buffer1 DescriptorSet 0
OpDecorate %buffer1 Binding 3
OpDecorate %rect DescriptorSet 0
OpDecorate %rect Binding 0
OpDecorate %flat DescriptorSet 0
OpDecorate %flat Binding 1
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%Output0 = OpTypeStruct %uint
%Output1 = OpTypeStruct %uint
%outputPtr0 = OpTypePointer StorageBuffer %Output0
%outputPtr1 = OpTypePointer StorageBuffer %Output1
%buffer0 = OpVariable %outputPtr0 StorageBuffer
%buffer1 = OpVariable %outputPtr1 StorageBuffer
%rectType = OpTypeImage %uint Rect 0 0 0 2 R32ui
%flatType = OpTypeImage %uint 2D 0 0 0 2 R32ui
%rectPtr = OpTypePointer UniformConstant %rectType
@@ -228,6 +250,8 @@ TEST_F(ProgramUtilTest, LowerRectImagesMergesWithExisting2DImageTypes) {
const auto firstImage = outputText.find("OpTypeImage");
ASSERT_NE(firstImage, String::npos);
EXPECT_EQ(outputText.find("OpTypeImage", firstImage + 1), String::npos);
EXPECT_NE(outputText.find("\"Output0\""), String::npos) << outputText;
EXPECT_NE(outputText.find("\"Output1\""), String::npos) << outputText;
}
TEST_F(ProgramUtilTest, UnformattedFloatStorageImagesKeepFloatAtomicImageTypesTyped) {
@@ -38,6 +38,7 @@
#include "SpirvPasses/FixIterationRPBarrierPass.h"
#include "SpirvPasses/FixIterationRPSubgroupScratchPass.h"
#include "SpirvPasses/NormalizeRectCoordinatesPass.h"
#include "SpirvPasses/DeduplicateImageTypesPass.h"
#include "SpirvPasses/Lower1DArrayImagesPass.h"
#include "SpirvPasses/Lower1DSampledImagesPass.h"
#include "SpirvPasses/BakeImageFormatsPass.h"
@@ -1814,7 +1815,7 @@ namespace MobileGL {
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
optimizer.RegisterPass(NormalizeRectCoordinatesPass::CreateNormalizeRectCoordinatesPass());
// Rect and 2D images of the same format become the same type after lowering.
optimizer.RegisterPass(CreateRemoveDuplicatesPass());
optimizer.RegisterPass(DeduplicateImageTypesPass::Create());
return RunOptimizerChecked("LowerRectImages", optimizer, inputBinary, outputBinary, true, enableSpirvValidation);
}
@@ -2106,10 +2107,9 @@ namespace MobileGL {
outputBinary.insert(outputBinary.begin() + static_cast<std::ptrdiff_t>(capabilityInsertOffset),
addedCapabilities.begin(), addedCapabilities.end());
// Erasing the format can make distinct image types identical. Use the same
// cleanup as the image-widening/lowering paths to merge their dependent types
// and update every use before validating the rewritten module.
// image-only cleanup as rectangle lowering; named blocks retain their identity.
spvtools::Optimizer optimizer(SPV_ENV_VULKAN_1_1);
optimizer.RegisterPass(spvtools::CreateRemoveDuplicatesPass());
optimizer.RegisterPass(DeduplicateImageTypesPass::Create());
Vector<Uint32> rewrittenBinary = std::move(outputBinary);
return RunOptimizerChecked("UseUnformattedFloatStorageImagesForVulkan", optimizer,
rewrittenBinary, outputBinary, true, enableSpirvValidation);
@@ -0,0 +1,75 @@
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeduplicateImageTypesPass.cpp
// Copyright (c) 2025-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 "DeduplicateImageTypesPass.h"
#include "source/opt/decoration_manager.h"
#include "source/opt/ir_context.h"
#include "source/util/make_unique.h"
#include <map>
#include <unordered_set>
#include <vector>
namespace MobileGL::MG_Util::ShaderTranspiler {
spvtools::opt::Pass::Status DeduplicateImageTypesPass::Process() {
using spvtools::opt::Instruction;
std::unordered_set<uint32_t> imageTypes;
std::map<std::vector<uint32_t>, std::vector<Instruction*>> canonical;
std::vector<Instruction*> duplicates;
for (auto& type : context()->types_values()) {
bool imageRelated = false;
switch (type.opcode()) {
case spv::Op::OpTypeImage:
imageRelated = true;
break;
case spv::Op::OpTypeSampledImage:
case spv::Op::OpTypeArray:
case spv::Op::OpTypeRuntimeArray:
case spv::Op::OpTypePointer:
case spv::Op::OpTypeFunction:
type.ForEachInId([&](const uint32_t* id) {
imageRelated |= imageTypes.count(*id) != 0;
});
break;
default:
break;
}
if (!imageRelated) continue;
imageTypes.insert(type.result_id());
// Compare operand IDs, not structural equivalence. Distinct struct IDs
// must never collapse just because their member layouts happen to match.
std::vector<uint32_t> key{static_cast<uint32_t>(type.opcode())};
for (uint32_t i = 0; i < type.NumInOperands(); ++i) {
const auto& words = type.GetInOperand(i).words;
key.insert(key.end(), words.begin(), words.end());
}
auto& candidates = canonical[key];
Instruction* match = nullptr;
for (auto* candidate : candidates) {
if (context()->get_decoration_mgr()->HaveTheSameDecorations(
type.result_id(), candidate->result_id())) {
match = candidate;
break;
}
}
if (!match) {
candidates.push_back(&type);
continue;
}
context()->KillNamesAndDecorates(type.result_id());
context()->ReplaceAllUsesWith(type.result_id(), match->result_id());
duplicates.push_back(&type);
}
for (auto* type : duplicates) context()->KillInst(type);
return duplicates.empty() ? Status::SuccessWithoutChange : Status::SuccessWithChange;
}
spvtools::Optimizer::PassToken DeduplicateImageTypesPass::Create() {
return spvtools::Optimizer::PassToken(spvtools::MakeUnique<DeduplicateImageTypesPass>());
}
}
@@ -0,0 +1,21 @@
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeduplicateImageTypesPass.h
// Copyright (c) 2025-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 "spirv-tools/optimizer.hpp"
#include "source/opt/pass.h"
namespace MobileGL::MG_Util::ShaderTranspiler {
// Preserve named structs: GL reflection and cross-stage interfaces use their identity.
class DeduplicateImageTypesPass final : public spvtools::opt::Pass {
public:
const char* name() const override { return "mobilegl-deduplicate-image-types"; }
Status Process() override;
static spvtools::Optimizer::PassToken Create();
};
}