mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (ShaderTranspiler): preserve block identity when deduplicating image types
This commit is contained in:
@@ -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();
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user