mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
[Fix] (Magma): lower pixel_center_integer gl_FragCoord so Vulkan validation stops refusing the program
This commit is contained in:
@@ -416,6 +416,7 @@ set(SOURCE_FILES
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/SplitArrayVertexInputsPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ZeroBaseVertexPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerPixelCenterIntegerPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeriveNumSubgroupsPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FixIterationRPBarrierPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FixIterationRPSubgroupScratchPass.cpp
|
||||
|
||||
@@ -3675,6 +3675,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
|
||||
// Vulkan forbids the PixelCenterInteger execution mode that
|
||||
// `layout(pixel_center_integer) in vec4 gl_FragCoord;` compiles to, so a module
|
||||
// carrying it would fail validation and every draw of the program be refused. This
|
||||
// runs before the FragCoordYFlip variant: the flip then rewrites the unshifted builtin
|
||||
// read and the -0.5 lands on the flipped value, as GL defines it.
|
||||
if (stages[i] == ShaderStage::Fragment) {
|
||||
Vector<Uint> centeredSpirv;
|
||||
if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerPixelCenterIntegerForVulkan(
|
||||
moduleSpirvs[i], centeredSpirv, enableSpirvValidation) &&
|
||||
!centeredSpirv.empty()) {
|
||||
moduleSpirvs[i] = Move(centeredSpirv);
|
||||
}
|
||||
}
|
||||
|
||||
if ((flags & ProgramFactory::CompileOptionBit::FragCoordYFlip) && stages[i] == ShaderStage::Fragment) {
|
||||
Vector<Uint> fragCoordSpirv;
|
||||
if (TransformSpirvForFragCoordYFlip(moduleSpirvs[i], fragCoordSpirv, m_defaultFramebufferHeight)) {
|
||||
|
||||
@@ -20,6 +20,7 @@ add_executable(
|
||||
GlslangCaptureTest.cpp
|
||||
DemotePointSizeTest.cpp
|
||||
FragCoordYFlipTest.cpp
|
||||
PixelCenterIntegerTest.cpp
|
||||
RelaxedOpaqueStructTest.cpp
|
||||
ShadingRateEsslTest.cpp
|
||||
)
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
// MobileGL - MobileGL/MG_Test/ShaderTranspiler/PixelCenterIntegerTest.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
|
||||
|
||||
// `layout(pixel_center_integer) in vec4 gl_FragCoord;` compiles to the PixelCenterInteger
|
||||
// execution mode, which the Vulkan environment forbids: DirectVulkan's validation refused every
|
||||
// such program (KHR-GL45.direct_state_access.textures_* draw through one). The lowered module
|
||||
// must validate, carry no such mode, and shift exactly the x/y reads of the builtin.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Includes.h"
|
||||
#include "Init.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 {
|
||||
Vector<Uint32> CompileFragment(const char* fragment) {
|
||||
using namespace MG_Util::ShaderTranspiler;
|
||||
constexpr const char* kVertex = R"(#version 330 core
|
||||
layout(location = 0) in vec2 position;
|
||||
void main() { gl_Position = vec4(position, 0.0, 1.0); }
|
||||
)";
|
||||
Vector<SharedPtr<glslang::TShader>> shaders;
|
||||
for (const auto& [stage, source] : Vector<Pair<GLenum, const char*>>{{GL_VERTEX_SHADER, kVertex},
|
||||
{GL_FRAGMENT_SHADER, fragment}}) {
|
||||
ShaderAttrib attrib{.shaderType = stage, .sourceStr = source};
|
||||
auto shader = ShaderCompiler::CompileShader(attrib);
|
||||
EXPECT_TRUE(shader) << (shader ? String{} : shader.error().log);
|
||||
if (!shader) return {};
|
||||
shaders.push_back(shader.value());
|
||||
}
|
||||
ProgramAttrib programAttrib{.shaders = shaders};
|
||||
auto program = ShaderCompiler::LinkProgram(programAttrib);
|
||||
EXPECT_TRUE(program) << (program ? String{} : program.error().log);
|
||||
if (!program) return {};
|
||||
ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_VERTEX_SHADER, GL_FRAGMENT_SHADER},
|
||||
.program = *program.value()};
|
||||
auto binary = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib);
|
||||
EXPECT_TRUE(binary) << (binary ? String{} : binary.error().log);
|
||||
if (!binary || binary->size() != 2) return {};
|
||||
Vector<Uint32> module = binary.value()[1];
|
||||
EXPECT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(module, module, true, true));
|
||||
return module;
|
||||
}
|
||||
|
||||
String Disassemble(const Vector<Uint32>& spirv) {
|
||||
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
|
||||
String text;
|
||||
EXPECT_TRUE(tools.Disassemble(spirv, &text));
|
||||
return text;
|
||||
}
|
||||
|
||||
String ValidationError(const Vector<Uint32>& spirv) {
|
||||
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
|
||||
String error;
|
||||
tools.SetMessageConsumer([&](spv_message_level_t, const char*, const spv_position_t&,
|
||||
const char* message) { error += message != nullptr ? message : ""; });
|
||||
return tools.Validate(spirv) ? String{} : (error.empty() ? String{"invalid"} : error);
|
||||
}
|
||||
|
||||
SizeT CountOccurrences(const String& text, const String& needle) {
|
||||
SizeT count = 0;
|
||||
for (SizeT at = text.find(needle); at != String::npos; at = text.find(needle, at + needle.size())) ++count;
|
||||
return count;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// The direct_state_access shape: the whole vector converted to integer texel coordinates.
|
||||
TEST(LowerPixelCenterInteger, ShiftsWholeVectorReadsAndDropsTheMode) {
|
||||
MobileGL::Initialize();
|
||||
const Vector<Uint32> input = CompileFragment(R"(#version 330 core
|
||||
layout(pixel_center_integer) in vec4 gl_FragCoord;
|
||||
uniform sampler2D data;
|
||||
out vec4 color;
|
||||
void main() { color = texelFetch(data, ivec2(gl_FragCoord.xy), 0); }
|
||||
)");
|
||||
ASSERT_FALSE(input.empty());
|
||||
ASSERT_NE(Disassemble(input).find("PixelCenterInteger"), String::npos);
|
||||
EXPECT_FALSE(ValidationError(input).empty()) << "the unlowered module is what Vulkan refuses";
|
||||
|
||||
Vector<Uint32> output;
|
||||
ASSERT_TRUE(ShaderCompiler::LowerPixelCenterIntegerForVulkan(input, output, true));
|
||||
EXPECT_EQ(ValidationError(output), String{});
|
||||
const String text = Disassemble(output);
|
||||
EXPECT_EQ(text.find("PixelCenterInteger"), String::npos) << text;
|
||||
EXPECT_EQ(CountOccurrences(text, "OpFSub"), 1u) << text;
|
||||
EXPECT_NE(text.find("OpConstantComposite %v4float %float_0_5 %float_0_5 %float_0 %float_0"), String::npos)
|
||||
<< text;
|
||||
}
|
||||
|
||||
// Single components arrive through an access chain: x and y move, z and w keep their values.
|
||||
TEST(LowerPixelCenterInteger, ShiftsOnlyTheXAndYComponents) {
|
||||
MobileGL::Initialize();
|
||||
const Vector<Uint32> input = CompileFragment(R"(#version 330 core
|
||||
layout(pixel_center_integer) in vec4 gl_FragCoord;
|
||||
out vec4 color;
|
||||
void main() { color = vec4(gl_FragCoord.x, gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w); }
|
||||
)");
|
||||
ASSERT_FALSE(input.empty());
|
||||
|
||||
Vector<Uint32> output;
|
||||
ASSERT_TRUE(ShaderCompiler::LowerPixelCenterIntegerForVulkan(input, output, true));
|
||||
EXPECT_EQ(ValidationError(output), String{});
|
||||
const String text = Disassemble(output);
|
||||
EXPECT_EQ(text.find("PixelCenterInteger"), String::npos) << text;
|
||||
EXPECT_EQ(CountOccurrences(text, "OpFSub"), 2u) << text;
|
||||
}
|
||||
|
||||
// A module without the mode is left alone.
|
||||
TEST(LowerPixelCenterInteger, LeavesDefaultCentredModulesUnchanged) {
|
||||
MobileGL::Initialize();
|
||||
const Vector<Uint32> input = CompileFragment(R"(#version 330 core
|
||||
out vec4 color;
|
||||
void main() { color = vec4(gl_FragCoord.xy, 0.0, 1.0); }
|
||||
)");
|
||||
ASSERT_FALSE(input.empty());
|
||||
|
||||
Vector<Uint32> output;
|
||||
ASSERT_TRUE(ShaderCompiler::LowerPixelCenterIntegerForVulkan(input, output, true));
|
||||
EXPECT_EQ(Disassemble(output), Disassemble(input));
|
||||
}
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "SpirvPasses/SplitArrayVertexInputsPass.h"
|
||||
#include "SpirvPasses/RebaseInstanceIndexPass.h"
|
||||
#include "SpirvPasses/ZeroBaseVertexPass.h"
|
||||
#include "SpirvPasses/LowerPixelCenterIntegerPass.h"
|
||||
#include "SpirvPasses/DeriveNumSubgroupsPass.h"
|
||||
#include "SpirvPasses/EmulateSubgroupsPass.h"
|
||||
#include "SpirvPasses/FixIterationRPBarrierPass.h"
|
||||
@@ -1985,6 +1986,17 @@ namespace MobileGL {
|
||||
return RunOptimizerChecked("ZeroBaseVertexForVulkan", optimizer, inputBinary, outputBinary, true, enableSpirvValidation);
|
||||
}
|
||||
|
||||
bool ShaderCompiler::LowerPixelCenterIntegerForVulkan(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary,
|
||||
const bool enableSpirvValidation) {
|
||||
using namespace spvtools;
|
||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||
optimizer.RegisterPass(LowerPixelCenterIntegerPass::CreateLowerPixelCenterIntegerPass());
|
||||
|
||||
return RunOptimizerChecked("LowerPixelCenterIntegerForVulkan", optimizer, inputBinary, outputBinary,
|
||||
true, enableSpirvValidation);
|
||||
}
|
||||
|
||||
bool ShaderCompiler::DeriveNumSubgroupsForVulkan(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary,
|
||||
const bool enableSpirvValidation) {
|
||||
|
||||
@@ -347,6 +347,12 @@ namespace MobileGL {
|
||||
static bool ZeroBaseVertexForVulkan(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary,
|
||||
bool enableSpirvValidation = false);
|
||||
// Drops `layout(pixel_center_integer)` from a fragment module, which the Vulkan
|
||||
// environment forbids, and shifts every gl_FragCoord.xy read by -0.5 to keep its
|
||||
// values. See LowerPixelCenterIntegerPass.
|
||||
static bool LowerPixelCenterIntegerForVulkan(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary,
|
||||
bool enableSpirvValidation = false);
|
||||
// Replaces compute gl_NumSubgroups loads with ceil(workgroup invocations /
|
||||
// gl_SubgroupSize). DirectVulkan only; this repairs drivers whose builtin
|
||||
// disagrees with the subgroup IDs the same dispatch emits (Adreno reports 1
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerPixelCenterIntegerPass.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 "LowerPixelCenterIntegerPass.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 <bit>
|
||||
#include <vector>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_Util {
|
||||
namespace ShaderTranspiler {
|
||||
namespace {
|
||||
using spvtools::opt::Instruction;
|
||||
using spvtools::opt::IRContext;
|
||||
|
||||
// OpExecutionMode <entry-point> <mode> [literals...]
|
||||
constexpr uint32_t kExecutionModeModeOperand = 1;
|
||||
|
||||
// Returns the Input OpVariable decorated BuiltIn FragCoord, or nullptr if none.
|
||||
Instruction* FindFragCoordVariable(IRContext* context) {
|
||||
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)) != spv::BuiltIn::FragCoord)
|
||||
continue;
|
||||
Instruction* variable = defUseMgr->GetDef(annotation.GetSingleWordInOperand(0));
|
||||
if (variable != nullptr && variable->opcode() == spv::Op::OpVariable &&
|
||||
static_cast<spv::StorageClass>(variable->GetSingleWordInOperand(0)) ==
|
||||
spv::StorageClass::Input) {
|
||||
return variable;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Bool IsDeclarationUse(const Instruction* user) {
|
||||
switch (user->opcode()) {
|
||||
case spv::Op::OpDecorate:
|
||||
case spv::Op::OpDecorateId:
|
||||
case spv::Op::OpDecorateString:
|
||||
case spv::Op::OpName:
|
||||
case spv::Op::OpEntryPoint:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// One gl_FragCoord read to shift: the load and the id of the constant to subtract.
|
||||
struct ShiftedLoad {
|
||||
Instruction* load = nullptr;
|
||||
uint32_t offsetId = 0;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
spvtools::opt::Pass::Status LowerPixelCenterIntegerPass::Process() {
|
||||
auto* irContext = context();
|
||||
auto* defUseMgr = irContext->get_def_use_mgr();
|
||||
auto* constantMgr = irContext->get_constant_mgr();
|
||||
auto* typeMgr = irContext->get_type_mgr();
|
||||
|
||||
std::vector<Instruction*> modes;
|
||||
for (Instruction& mode : irContext->module()->execution_modes()) {
|
||||
if (mode.opcode() == spv::Op::OpExecutionMode &&
|
||||
mode.NumInOperands() > kExecutionModeModeOperand &&
|
||||
static_cast<spv::ExecutionMode>(mode.GetSingleWordInOperand(kExecutionModeModeOperand)) ==
|
||||
spv::ExecutionMode::PixelCenterInteger) {
|
||||
modes.push_back(&mode);
|
||||
}
|
||||
}
|
||||
if (modes.empty()) return Status::SuccessWithoutChange;
|
||||
|
||||
// Gather every read and its offset constant before mutating anything, so a shape
|
||||
// the pass refuses leaves the module exactly as it was.
|
||||
std::vector<ShiftedLoad> loads;
|
||||
Bool refused = false;
|
||||
const auto scalarOffset = [&](uint32_t floatTypeId, float value) -> uint32_t {
|
||||
const auto* floatType = typeMgr->GetType(floatTypeId);
|
||||
if (floatType == nullptr || floatType->AsFloat() == nullptr) return 0;
|
||||
const auto* constant = constantMgr->GetConstant(floatType, {std::bit_cast<uint32_t>(value)});
|
||||
const Instruction* inst = constant ? constantMgr->GetDefiningInstruction(constant, floatTypeId) : nullptr;
|
||||
return inst ? inst->result_id() : 0;
|
||||
};
|
||||
|
||||
if (Instruction* fragCoord = FindFragCoordVariable(irContext)) {
|
||||
const uint32_t fragCoordId = fragCoord->result_id();
|
||||
defUseMgr->ForEachUser(fragCoord, [&](Instruction* user) {
|
||||
if (refused || IsDeclarationUse(user)) return;
|
||||
if (user->opcode() == spv::Op::OpLoad && user->GetSingleWordInOperand(0) == fragCoordId) {
|
||||
// The whole vec4: subtract (0.5, 0.5, 0, 0).
|
||||
const auto* vecType = typeMgr->GetType(user->type_id());
|
||||
const auto* vector = vecType ? vecType->AsVector() : nullptr;
|
||||
if (vector == nullptr || vector->element_count() != 4) {
|
||||
refused = true;
|
||||
return;
|
||||
}
|
||||
const uint32_t floatTypeId = typeMgr->GetId(vector->element_type());
|
||||
const uint32_t half = scalarOffset(floatTypeId, 0.5f);
|
||||
const uint32_t zero = scalarOffset(floatTypeId, 0.0f);
|
||||
const auto* composite =
|
||||
(half && zero) ? constantMgr->GetConstant(vecType, {half, half, zero, zero}) : nullptr;
|
||||
const Instruction* inst =
|
||||
composite ? constantMgr->GetDefiningInstruction(composite, user->type_id()) : nullptr;
|
||||
if (inst == nullptr) {
|
||||
refused = true;
|
||||
return;
|
||||
}
|
||||
loads.push_back({user, inst->result_id()});
|
||||
return;
|
||||
}
|
||||
if ((user->opcode() == spv::Op::OpAccessChain ||
|
||||
user->opcode() == spv::Op::OpInBoundsAccessChain) &&
|
||||
user->NumInOperands() == 2 && user->GetSingleWordInOperand(0) == fragCoordId) {
|
||||
const auto* index = constantMgr->FindDeclaredConstant(user->GetSingleWordInOperand(1));
|
||||
if (index == nullptr || index->AsScalarConstant() == nullptr) {
|
||||
refused = true;
|
||||
return;
|
||||
}
|
||||
const Uint64 component = index->GetZeroExtendedValue();
|
||||
defUseMgr->ForEachUser(user, [&](Instruction* chainUser) {
|
||||
if (refused || IsDeclarationUse(chainUser)) return;
|
||||
if (chainUser->opcode() != spv::Op::OpLoad) {
|
||||
refused = true;
|
||||
return;
|
||||
}
|
||||
// z and w keep their values; only the centre of x and y moves.
|
||||
if (component >= 2) return;
|
||||
const uint32_t half = scalarOffset(chainUser->type_id(), 0.5f);
|
||||
if (half == 0) {
|
||||
refused = true;
|
||||
return;
|
||||
}
|
||||
loads.push_back({chainUser, half});
|
||||
});
|
||||
return;
|
||||
}
|
||||
refused = true;
|
||||
});
|
||||
}
|
||||
if (refused) return Status::Failure;
|
||||
|
||||
for (Instruction* mode : modes) irContext->KillInst(mode);
|
||||
|
||||
// `%r = OpLoad %ty %ptr` becomes `%fresh = OpLoad %ty %ptr` followed by
|
||||
// `%r = OpFSub %ty %fresh %offset`: every existing use of %r picks the shifted
|
||||
// value up without being rewritten.
|
||||
for (const ShiftedLoad& shifted : loads) {
|
||||
const uint32_t freshId = TakeNextId();
|
||||
if (freshId == 0) return Status::Failure;
|
||||
const uint32_t resultId = shifted.load->result_id();
|
||||
const uint32_t typeId = shifted.load->type_id();
|
||||
// A load is never the last instruction of its block (a terminator follows),
|
||||
// so inserting before its successor places the subtraction right after it.
|
||||
Instruction* next = shifted.load->NextNode();
|
||||
if (next == nullptr) return Status::Failure;
|
||||
shifted.load->SetResultId(freshId);
|
||||
next->InsertBefore(MakeUnique<Instruction>(
|
||||
irContext, spv::Op::OpFSub, typeId, resultId,
|
||||
Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {freshId}},
|
||||
{SPV_OPERAND_TYPE_ID, {shifted.offsetId}}}));
|
||||
}
|
||||
|
||||
irContext->InvalidateAnalysesExceptFor(IRContext::kAnalysisNone);
|
||||
return Status::SuccessWithChange;
|
||||
}
|
||||
|
||||
spvtools::Optimizer::PassToken LowerPixelCenterIntegerPass::CreateLowerPixelCenterIntegerPass() {
|
||||
return spvtools::Optimizer::PassToken(MakeUnique<LowerPixelCenterIntegerPass>());
|
||||
}
|
||||
} // namespace ShaderTranspiler
|
||||
} // namespace MG_Util
|
||||
} // namespace MobileGL
|
||||
@@ -0,0 +1,38 @@
|
||||
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerPixelCenterIntegerPass.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 "source/opt/pass.h"
|
||||
#include "spirv-tools/optimizer.hpp"
|
||||
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_Util {
|
||||
namespace ShaderTranspiler {
|
||||
// Lowers `layout(pixel_center_integer) in vec4 gl_FragCoord;` for the Vulkan
|
||||
// environment, which forbids the PixelCenterInteger execution mode
|
||||
// (VUID-StandaloneSpirv-PixelCenterInteger-04654) - a module carrying it fails
|
||||
// validation and DirectVulkan refuses every draw of the program. The mode only moves
|
||||
// the pixel centre from (n + 0.5) to n, so the pass drops it and subtracts 0.5 from
|
||||
// x and y of every gl_FragCoord read: whole-vector loads get vec4(0.5, 0.5, 0, 0),
|
||||
// component loads through a constant-index access chain get 0.5 for x and y.
|
||||
//
|
||||
// Any other use of the builtin (a non-constant component index, the pointer handed
|
||||
// to a function) fails the pass rather than half-rewriting it; the caller then keeps
|
||||
// the original module, which is no worse than before.
|
||||
class LowerPixelCenterIntegerPass : public spvtools::opt::Pass {
|
||||
public:
|
||||
const char* name() const override { return "lower-pixel-center-integer"; }
|
||||
Status Process() override;
|
||||
|
||||
static spvtools::Optimizer::PassToken CreateLowerPixelCenterIntegerPass();
|
||||
};
|
||||
} // namespace ShaderTranspiler
|
||||
} // namespace MG_Util
|
||||
} // namespace MobileGL
|
||||
Reference in New Issue
Block a user