mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-10 21:28:32 +09:00
[Feat] (ShaderTranspiler, DirectGLES): support noperspective conformantly instead of stripping it - let the qualifier reach glslang as the core SPIR-V NoPerspective decoration (native on DirectVulkan; SPIRV-Cross emits ESSL noperspective + GL_NV_shader_noperspective_interpolation on DirectGLES), and for GLES devices lacking that extension add StripNoPerspectivePass to drop the decoration and fall back to smooth; the old naked substring erase discarded the interpolation shader packs need and mangled identifiers containing the word
This commit is contained in:
@@ -192,6 +192,7 @@ set(SOURCE_FILES
|
|||||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerDrawParametersPass.cpp
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerDrawParametersPass.cpp
|
||||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp
|
||||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripUboMemberRelaxedPrecisionPass.cpp
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripUboMemberRelaxedPrecisionPass.cpp
|
||||||
|
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripNoPerspectivePass.cpp
|
||||||
|
|
||||||
MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp
|
MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp
|
||||||
MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp
|
MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp
|
||||||
|
|||||||
@@ -2790,6 +2790,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
effectiveSpirv = &uboPrecisionSpirv;
|
effectiveSpirv = &uboPrecisionSpirv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// noperspective is core desktop GLSL and reaches here as the SPIR-V NoPerspective
|
||||||
|
// decoration. SPIRV-Cross renders it as ESSL `noperspective` + `#extension
|
||||||
|
// GL_NV_shader_noperspective_interpolation : require`; on a driver without that
|
||||||
|
// extension the require fails, so strip the decoration first and let the varying
|
||||||
|
// fall back to smooth interpolation. Devices that have the extension keep the
|
||||||
|
// decoration and get true screen-linear interpolation.
|
||||||
|
Vector<unsigned int> noperspectiveSpirv;
|
||||||
|
if (!g_GLESCapabilities.SupportsNoperspectiveInterpolation &&
|
||||||
|
MG_Util::ShaderTranspiler::ShaderCompiler::StripNoPerspectiveForEssl(
|
||||||
|
*effectiveSpirv, noperspectiveSpirv) &&
|
||||||
|
!noperspectiveSpirv.empty()) {
|
||||||
|
effectiveSpirv = &noperspectiveSpirv;
|
||||||
|
}
|
||||||
|
|
||||||
MG_Util::ShaderTranspiler::SpvcSession spvcSession(*effectiveSpirv,
|
MG_Util::ShaderTranspiler::SpvcSession spvcSession(*effectiveSpirv,
|
||||||
MG_Util::ShaderTranspiler::SessionUsageBit::Transpile);
|
MG_Util::ShaderTranspiler::SessionUsageBit::Transpile);
|
||||||
|
|
||||||
|
|||||||
@@ -1089,6 +1089,174 @@ TEST_F(ProgramUtilTest, CompileFragmentShaderWithDiscard) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// noperspective is core desktop GLSL (1.30+) and maps to the SPIR-V NoPerspective decoration. It must
|
||||||
|
// reach glslang (not be stripped as text) so the SPIR-V carries the decoration; SPIRV-Cross then emits
|
||||||
|
// ESSL `noperspective` + the GL_NV_shader_noperspective_interpolation extension. Shader packs
|
||||||
|
// (Iris/Complementary) depend on it, and KHR-GL33.glsl_noperspective fails if the result matches
|
||||||
|
// smooth. This is the DirectGLES path with the NV extension available (SPIRV-Cross's default).
|
||||||
|
TEST_F(ProgramUtilTest, NoperspectiveInterpolationSurvivesToEssl) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
|
String fs = R"(#version 330 core
|
||||||
|
noperspective in vec4 vColor;
|
||||||
|
out vec4 fragColor;
|
||||||
|
void main() { fragColor = vColor; }
|
||||||
|
)";
|
||||||
|
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = fs};
|
||||||
|
auto res = ShaderCompiler::CompileShader(attrib);
|
||||||
|
if (!res) FAIL() << "compile errc: " << res.error().errc << "\nlog: " << res.error().log;
|
||||||
|
|
||||||
|
ProgramAttrib programAttrib{.shaders = {res.value()}};
|
||||||
|
auto program_res = ShaderCompiler::LinkProgram(programAttrib);
|
||||||
|
if (!program_res) FAIL() << "link errc: " << program_res.error().errc << "\nlog: " << program_res.error().log;
|
||||||
|
|
||||||
|
ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_FRAGMENT_SHADER}, .program = *program_res.value()};
|
||||||
|
auto bin_res = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib);
|
||||||
|
if (!bin_res) FAIL() << "spirv errc: " << bin_res.error().errc << "\nlog: " << bin_res.error().log;
|
||||||
|
ASSERT_EQ(bin_res.value().size(), 1u);
|
||||||
|
|
||||||
|
SpvcSession session(bin_res.value()[0], SessionUsageBit::Transpile);
|
||||||
|
auto essl = ShaderCompiler::DecompileShader(session);
|
||||||
|
if (!essl) FAIL() << "decompile errc: " << essl.error().errc << "\nlog: " << essl.error().log;
|
||||||
|
|
||||||
|
EXPECT_NE(essl.value().find("noperspective"), String::npos)
|
||||||
|
<< "noperspective was lost before it reached SPIR-V:\n" << essl.value();
|
||||||
|
EXPECT_NE(essl.value().find("GL_NV_shader_noperspective_interpolation"), String::npos)
|
||||||
|
<< "SPIRV-Cross must require the NV extension for ES noperspective:\n" << essl.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The old handling was a naked substring erase of "noperspective", so any identifier that merely
|
||||||
|
// contained those characters (a uniform named noperspectiveBlend, say) got mangled. Removing the
|
||||||
|
// strip fixes it - glslang, which is identifier-aware, is the only thing that should see the keyword.
|
||||||
|
TEST_F(ProgramUtilTest, PreprocessDoesNotCorruptIdentifiersContainingNoperspective) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
|
String source = R"(#version 330 core
|
||||||
|
uniform float noperspectiveBlend;
|
||||||
|
out vec4 fragColor;
|
||||||
|
void main() { fragColor = vec4(noperspectiveBlend); }
|
||||||
|
)";
|
||||||
|
PreprocessShaderSource(ShaderStage::Fragment, source);
|
||||||
|
EXPECT_NE(source.find("noperspectiveBlend"), String::npos)
|
||||||
|
<< "identifier was corrupted by substring stripping:\n" << source;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The DirectGLES fallback for devices without GL_NV_shader_noperspective_interpolation: stripping the
|
||||||
|
// NoPerspective decoration makes SPIRV-Cross emit a plain smooth varying with no `#extension … :
|
||||||
|
// require`, so the shader still compiles (rendering as smooth) instead of being rejected by the driver.
|
||||||
|
TEST_F(ProgramUtilTest, StripNoPerspectiveFallbackProducesPlainEssl) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
|
String fs = R"(#version 330 core
|
||||||
|
noperspective in vec4 vColor;
|
||||||
|
out vec4 fragColor;
|
||||||
|
void main() { fragColor = vColor; }
|
||||||
|
)";
|
||||||
|
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = fs};
|
||||||
|
auto res = ShaderCompiler::CompileShader(attrib);
|
||||||
|
if (!res) FAIL() << "compile errc: " << res.error().errc << "\nlog: " << res.error().log;
|
||||||
|
|
||||||
|
ProgramAttrib programAttrib{.shaders = {res.value()}};
|
||||||
|
auto program_res = ShaderCompiler::LinkProgram(programAttrib);
|
||||||
|
if (!program_res) FAIL() << "link errc: " << program_res.error().errc << "\nlog: " << program_res.error().log;
|
||||||
|
|
||||||
|
ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_FRAGMENT_SHADER}, .program = *program_res.value()};
|
||||||
|
auto bin_res = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib);
|
||||||
|
if (!bin_res) FAIL() << "spirv errc: " << bin_res.error().errc << "\nlog: " << bin_res.error().log;
|
||||||
|
ASSERT_EQ(bin_res.value().size(), 1u);
|
||||||
|
|
||||||
|
// Precondition: with the decoration present the default decompile requires the NV extension.
|
||||||
|
{
|
||||||
|
SpvcSession session(bin_res.value()[0], SessionUsageBit::Transpile);
|
||||||
|
auto essl = ShaderCompiler::DecompileShader(session);
|
||||||
|
if (!essl) FAIL() << "decompile errc: " << essl.error().errc;
|
||||||
|
ASSERT_NE(essl.value().find("noperspective"), String::npos) << essl.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The fallback strips the decoration -> plain smooth ESSL, no extension require.
|
||||||
|
Vector<Uint32> stripped;
|
||||||
|
ASSERT_TRUE(ShaderCompiler::StripNoPerspectiveForEssl(bin_res.value()[0], stripped));
|
||||||
|
ASSERT_FALSE(stripped.empty());
|
||||||
|
|
||||||
|
SpvcSession session(stripped, SessionUsageBit::Transpile);
|
||||||
|
auto essl = ShaderCompiler::DecompileShader(session);
|
||||||
|
if (!essl) FAIL() << "decompile errc: " << essl.error().errc << "\nlog: " << essl.error().log;
|
||||||
|
EXPECT_EQ(essl.value().find("noperspective"), String::npos)
|
||||||
|
<< "the decoration should be gone:\n" << essl.value();
|
||||||
|
EXPECT_EQ(essl.value().find("GL_NV_shader_noperspective_interpolation"), String::npos)
|
||||||
|
<< "no extension require without the decoration:\n" << essl.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directly exercises BOTH decoration forms StripNoPerspectivePass handles: a plain-variable
|
||||||
|
// OpDecorate NoPerspective (in-operand 1) and an interface-block-member OpMemberDecorate NoPerspective
|
||||||
|
// (in-operand 2). The ESSL round-trip tests above use only a scalar input, so they never reach the
|
||||||
|
// member-decorate branch, which a block varying like `in Block { noperspective vec4 c; }` (common in
|
||||||
|
// shader packs) produces. Unrelated decorations (Flat, Location) must survive untouched.
|
||||||
|
TEST_F(ProgramUtilTest, StripNoPerspectivePassRemovesBothDecorateForms) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
|
const String spirvText = R"(
|
||||||
|
OpCapability Shader
|
||||||
|
OpMemoryModel Logical GLSL450
|
||||||
|
OpEntryPoint Fragment %main "main" %plainVar %blockVar %flatVar
|
||||||
|
OpExecutionMode %main OriginUpperLeft
|
||||||
|
OpName %main "main"
|
||||||
|
OpDecorate %plainVar Location 0
|
||||||
|
OpDecorate %plainVar NoPerspective
|
||||||
|
OpMemberDecorate %Block 0 NoPerspective
|
||||||
|
OpDecorate %blockVar Location 1
|
||||||
|
OpDecorate %flatVar Location 2
|
||||||
|
OpDecorate %flatVar Flat
|
||||||
|
%void = OpTypeVoid
|
||||||
|
%mainFn = OpTypeFunction %void
|
||||||
|
%float = OpTypeFloat 32
|
||||||
|
%v4float = OpTypeVector %float 4
|
||||||
|
%int = OpTypeInt 32 1
|
||||||
|
%inV4Ptr = OpTypePointer Input %v4float
|
||||||
|
%plainVar = OpVariable %inV4Ptr Input
|
||||||
|
%Block = OpTypeStruct %v4float
|
||||||
|
%inBlockPtr = OpTypePointer Input %Block
|
||||||
|
%blockVar = OpVariable %inBlockPtr Input
|
||||||
|
%inIntPtr = OpTypePointer Input %int
|
||||||
|
%flatVar = OpVariable %inIntPtr Input
|
||||||
|
%main = OpFunction %void None %mainFn
|
||||||
|
%mainBody = OpLabel
|
||||||
|
OpReturn
|
||||||
|
OpFunctionEnd
|
||||||
|
)";
|
||||||
|
|
||||||
|
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
|
||||||
|
Vector<uint32_t> inputBinary;
|
||||||
|
ASSERT_TRUE(tools.Assemble(spirvText, &inputBinary));
|
||||||
|
|
||||||
|
const auto countNoPerspective = [](const String& text) {
|
||||||
|
SizeT count = 0, offset = 0;
|
||||||
|
while ((offset = text.find("NoPerspective", offset)) != String::npos) {
|
||||||
|
++count;
|
||||||
|
offset += std::strlen("NoPerspective");
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
};
|
||||||
|
|
||||||
|
String inputText;
|
||||||
|
ASSERT_TRUE(tools.Disassemble(inputBinary, &inputText));
|
||||||
|
ASSERT_EQ(countNoPerspective(inputText), 2u)
|
||||||
|
<< "fixture must carry both a plain and a member NoPerspective:\n" << inputText;
|
||||||
|
|
||||||
|
Vector<uint32_t> outputBinary;
|
||||||
|
ASSERT_TRUE(ShaderCompiler::StripNoPerspectiveForEssl(inputBinary, outputBinary));
|
||||||
|
ASSERT_FALSE(outputBinary.empty());
|
||||||
|
|
||||||
|
String outputText;
|
||||||
|
ASSERT_TRUE(tools.Disassemble(outputBinary, &outputText));
|
||||||
|
EXPECT_EQ(countNoPerspective(outputText), 0u)
|
||||||
|
<< "both NoPerspective decorations (OpDecorate and OpMemberDecorate) must be stripped:\n" << outputText;
|
||||||
|
EXPECT_NE(outputText.find("Flat"), String::npos)
|
||||||
|
<< "the unrelated Flat decoration must survive:\n" << outputText;
|
||||||
|
EXPECT_NE(outputText.find("Location"), String::npos)
|
||||||
|
<< "Location decorations must survive:\n" << outputText;
|
||||||
|
}
|
||||||
|
|
||||||
const char* vs_location = R"(#version 460
|
const char* vs_location = R"(#version 460
|
||||||
|
|
||||||
in vec4 Position;
|
in vec4 Position;
|
||||||
|
|||||||
@@ -811,6 +811,9 @@ namespace MobileGL::MG_Util::BackendLoader {
|
|||||||
if (std::strcmp(extension, "GL_EXT_blend_func_extended") == 0) {
|
if (std::strcmp(extension, "GL_EXT_blend_func_extended") == 0) {
|
||||||
caps.SupportsDualSourceBlend = true;
|
caps.SupportsDualSourceBlend = true;
|
||||||
}
|
}
|
||||||
|
if (std::strcmp(extension, "GL_NV_shader_noperspective_interpolation") == 0) {
|
||||||
|
caps.SupportsNoperspectiveInterpolation = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1050,6 +1050,11 @@ namespace MobileGL {
|
|||||||
// factors and layout(index = 1) fragment outputs. GLES core has no dual-source blending,
|
// factors and layout(index = 1) fragment outputs. GLES core has no dual-source blending,
|
||||||
// so without this a draw using a SRC1 factor cannot proceed.
|
// so without this a draw using a SRC1 factor cannot proceed.
|
||||||
Bool SupportsDualSourceBlend = false;
|
Bool SupportsDualSourceBlend = false;
|
||||||
|
// GL_NV_shader_noperspective_interpolation is present: the driver accepts the
|
||||||
|
// `noperspective` interpolation qualifier in ESSL. GLES core has none, so without this
|
||||||
|
// SPIRV-Cross's `#extension ... : require` would fail to compile and MobileGL falls back
|
||||||
|
// to stripping the NoPerspective decoration (smooth interpolation) via StripNoPerspectivePass.
|
||||||
|
Bool SupportsNoperspectiveInterpolation = false;
|
||||||
// GL_RENDERER contains "ANGLE".
|
// GL_RENDERER contains "ANGLE".
|
||||||
Bool IsAngleRenderer = false;
|
Bool IsAngleRenderer = false;
|
||||||
// GL_RENDERER contains both "ANGLE" and "llvmpipe".
|
// GL_RENDERER contains both "ANGLE" and "llvmpipe".
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "SpirvPasses/LowerDrawParametersPass.h"
|
#include "SpirvPasses/LowerDrawParametersPass.h"
|
||||||
#include "SpirvPasses/RebaseInstanceIndexPass.h"
|
#include "SpirvPasses/RebaseInstanceIndexPass.h"
|
||||||
#include "SpirvPasses/StripUboMemberRelaxedPrecisionPass.h"
|
#include "SpirvPasses/StripUboMemberRelaxedPrecisionPass.h"
|
||||||
|
#include "SpirvPasses/StripNoPerspectivePass.h"
|
||||||
#include "spirv-tools/libspirv.h"
|
#include "spirv-tools/libspirv.h"
|
||||||
#include "spirv-tools/optimizer.hpp"
|
#include "spirv-tools/optimizer.hpp"
|
||||||
|
|
||||||
@@ -334,6 +335,18 @@ namespace MobileGL {
|
|||||||
return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options);
|
return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ShaderCompiler::StripNoPerspectiveForEssl(const Vector<Uint32>& inputBinary,
|
||||||
|
Vector<uint32_t>& outputBinary) {
|
||||||
|
using namespace spvtools;
|
||||||
|
OptimizerOptions options;
|
||||||
|
options.set_run_validator(false);
|
||||||
|
|
||||||
|
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||||
|
optimizer.RegisterPass(StripNoPerspectivePass::CreateStripNoPerspectivePass());
|
||||||
|
|
||||||
|
return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options);
|
||||||
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::RebaseInstanceIndexForVulkan(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::RebaseInstanceIndexForVulkan(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ namespace MobileGL {
|
|||||||
// Only for the DirectGLES transpile path.
|
// Only for the DirectGLES transpile path.
|
||||||
static bool StripUboMemberRelaxedPrecisionForEssl(const Vector<Uint32>& inputBinary,
|
static bool StripUboMemberRelaxedPrecisionForEssl(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary);
|
Vector<uint32_t>& outputBinary);
|
||||||
|
// Removes NoPerspective decorations so SPIRV-Cross emits plain (smooth) ESSL varyings.
|
||||||
|
// DirectGLES fallback only, for devices lacking GL_NV_shader_noperspective_interpolation
|
||||||
|
// (SPIRV-Cross would otherwise require that extension and the driver would reject it).
|
||||||
|
static bool StripNoPerspectiveForEssl(const Vector<Uint32>& inputBinary,
|
||||||
|
Vector<uint32_t>& outputBinary);
|
||||||
// Rebases loads of the InstanceIndex builtin to (InstanceIndex - BaseInstance) so
|
// Rebases loads of the InstanceIndex builtin to (InstanceIndex - BaseInstance) so
|
||||||
// shaders see GL's zero-based gl_InstanceID. Vertex shaders only; DirectVulkan
|
// shaders see GL's zero-based gl_InstanceID. Vertex shaders only; DirectVulkan
|
||||||
// backend only (glslang's relaxed mode aliases gl_InstanceID to gl_InstanceIndex,
|
// backend only (glslang's relaxed mode aliases gl_InstanceID to gl_InstanceIndex,
|
||||||
|
|||||||
@@ -1251,15 +1251,13 @@ namespace MobileGL {
|
|||||||
|
|
||||||
NormalizeLineDirectives(source);
|
NormalizeLineDirectives(source);
|
||||||
|
|
||||||
// remove "noperspective"
|
// noperspective is intentionally NOT touched here. It is core in desktop GLSL (1.30+)
|
||||||
const char* str_np = "noperspective";
|
// and maps to the core SPIR-V NoPerspective decoration, which DirectVulkan renders
|
||||||
const SizeT len_np = strlen(str_np);
|
// natively and SPIRV-Cross turns into ESSL `noperspective` + the
|
||||||
SizeT noperspectivePos = source.find(str_np);
|
// GL_NV_shader_noperspective_interpolation extension. The old naked substring erase
|
||||||
while (noperspectivePos != String::npos) {
|
// both discarded that interpolation (shader packs need it) and corrupted any
|
||||||
// + length of "\n"
|
// identifier that merely contained the word. The GLES fallback for devices without
|
||||||
source = source.replace(noperspectivePos, len_np, "");
|
// the extension lives in the backend, where device capabilities are known.
|
||||||
noperspectivePos = source.find(str_np);
|
|
||||||
}
|
|
||||||
|
|
||||||
FilterUnsupportedGpuShaderInt64(source);
|
FilterUnsupportedGpuShaderInt64(source);
|
||||||
CoerceUniformBlockPackingToStd140(source);
|
CoerceUniformBlockPackingToStd140(source);
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripNoPerspectivePass.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 "StripNoPerspectivePass.h"
|
||||||
|
|
||||||
|
#include "spirv.hpp"
|
||||||
|
#include "source/opt/instruction.h"
|
||||||
|
#include "source/opt/ir_context.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;
|
||||||
|
|
||||||
|
// OpDecorate <target-id> <decoration> [literals...]
|
||||||
|
// OpMemberDecorate <struct-id> <member> <decoration> [literals...]
|
||||||
|
constexpr uint32_t kDecorateDecorationOperand = 1;
|
||||||
|
constexpr uint32_t kMemberDecorateDecorationOperand = 2;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
spvtools::opt::Pass::Status StripNoPerspectivePass::Process() {
|
||||||
|
auto* irContext = context();
|
||||||
|
|
||||||
|
// Collect first: KillInst mutates the annotation list being walked.
|
||||||
|
std::vector<Instruction*> toKill;
|
||||||
|
for (Instruction& annotation : irContext->annotations()) {
|
||||||
|
uint32_t decorationOperand = 0;
|
||||||
|
if (annotation.opcode() == spv::Op::OpDecorate) {
|
||||||
|
decorationOperand = kDecorateDecorationOperand;
|
||||||
|
} else if (annotation.opcode() == spv::Op::OpMemberDecorate) {
|
||||||
|
decorationOperand = kMemberDecorateDecorationOperand;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (annotation.NumInOperands() <= decorationOperand) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (static_cast<spv::Decoration>(annotation.GetSingleWordInOperand(decorationOperand)) ==
|
||||||
|
spv::Decoration::NoPerspective) {
|
||||||
|
toKill.push_back(&annotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toKill.empty()) {
|
||||||
|
return Status::SuccessWithoutChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Instruction* inst : toKill) {
|
||||||
|
irContext->KillInst(inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
irContext->InvalidateAnalysesExceptFor(IRContext::kAnalysisNone);
|
||||||
|
return Status::SuccessWithChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
spvtools::Optimizer::PassToken StripNoPerspectivePass::CreateStripNoPerspectivePass() {
|
||||||
|
return spvtools::Optimizer::PassToken(MakeUnique<StripNoPerspectivePass>());
|
||||||
|
}
|
||||||
|
} // namespace ShaderTranspiler
|
||||||
|
} // namespace MG_Util
|
||||||
|
} // namespace MobileGL
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripNoPerspectivePass.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 {
|
||||||
|
// Removes the NoPerspective decoration from every interface variable and block member.
|
||||||
|
// DirectGLES fallback only, for devices that lack GL_NV_shader_noperspective_interpolation:
|
||||||
|
// SPIRV-Cross renders a NoPerspective-decorated varying as ESSL `noperspective` plus
|
||||||
|
// `#extension GL_NV_shader_noperspective_interpolation : require`, which such a driver
|
||||||
|
// rejects. Dropping the decoration falls the varying back to smooth (perspective-correct)
|
||||||
|
// interpolation - the same visible result the old text-level strip produced, but without
|
||||||
|
// corrupting identifiers and without touching DirectVulkan, where NoPerspective is native.
|
||||||
|
// (The exact screen-linear emulation via gl_Position.w / gl_FragCoord.w is a later step.)
|
||||||
|
class StripNoPerspectivePass : public spvtools::opt::Pass {
|
||||||
|
public:
|
||||||
|
const char* name() const override { return "strip-noperspective"; }
|
||||||
|
Status Process() override;
|
||||||
|
|
||||||
|
static spvtools::Optimizer::PassToken CreateStripNoPerspectivePass();
|
||||||
|
};
|
||||||
|
} // namespace ShaderTranspiler
|
||||||
|
} // namespace MG_Util
|
||||||
|
} // namespace MobileGL
|
||||||
Reference in New Issue
Block a user