mirror of https://github.com/mosra/magnum.git
Browse Source
Currently those will be needed mainly by the Vk library to patch around a SwiftShader bug. I'm not sure yet how the public API should look so it's all hidden in the Implementation namespace for now.pull/495/head
7 changed files with 488 additions and 2 deletions
@ -0,0 +1,183 @@
|
||||
#ifndef Magnum_ShaderTools_Implementation_spirv_h |
||||
#define Magnum_ShaderTools_Implementation_spirv_h |
||||
/*
|
||||
This file is part of Magnum. |
||||
|
||||
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, |
||||
2020, 2021 Vladimír Vondruš <mosra@centrum.cz> |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a |
||||
copy of this software and associated documentation files (the "Software"), |
||||
to deal in the Software without restriction, including without limitation |
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
||||
and/or sell copies of the Software, and to permit persons to whom the |
||||
Software is furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included |
||||
in all copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||
DEALINGS IN THE SOFTWARE. |
||||
*/ |
||||
|
||||
#include <Corrade/Containers/ArrayView.h> |
||||
#include <Corrade/Containers/Optional.h> |
||||
#include <Corrade/Containers/Reference.h> |
||||
#include <Corrade/Containers/StringView.h> |
||||
|
||||
#include "Magnum/Magnum.h" |
||||
#include "MagnumExternal/Vulkan/spirv.h" |
||||
|
||||
namespace Magnum { namespace ShaderTools { namespace Implementation { namespace { |
||||
|
||||
/* This is used by both magnum-shaderconverter and the Vk library for
|
||||
SwiftShader workarounds but we don't want the Vk library to depend on |
||||
ShaderTools, so the minimal needed subset is made header-only. |
||||
|
||||
Eventually this should be turned into a public API, but so far it's just a |
||||
bag of random functions with very specific usage patterns and isn't clear |
||||
yet how to expose a usable interface. Moreover, the SwiftShader patching |
||||
needs to mutate the original, which means the outputs are pointers to the |
||||
original data. */ |
||||
|
||||
/* If the code looks like a valid SPIR-V, returns everything after the
|
||||
header. If not, nullptr. */ |
||||
Containers::ArrayView<const UnsignedInt> spirvData(const void* code, UnsignedInt size) { |
||||
const UnsignedInt* const spirv = static_cast<const UnsignedInt*>(code); |
||||
/* Not >= 5*4 because just the header alone is useless also */ |
||||
return size % 4 == 0 && size > 5*4 && spirv[0] == SpvMagicNumber ? |
||||
Containers::ArrayView<const UnsignedInt>{spirv, size/4}.suffix(5) : nullptr; |
||||
} |
||||
|
||||
/* When an instruction is found, the `data` is advanced after it in order to
|
||||
allow calling this function in a loop. When not found, `data` is left |
||||
untouched. */ |
||||
Containers::ArrayView<const UnsignedInt> spirvFindInstruction(Containers::ArrayView<const UnsignedInt>& data, const SpvOp op) { |
||||
/* Copy the view and iterate that. If we find the instruction, update the
|
||||
passed `data` reference, if not, keep it as it was -- that way, if the |
||||
find fails, `data` won't become empty and can be used further */ |
||||
for(Containers::ArrayView<const UnsignedInt> dataIteration = data; !dataIteration.empty(); ) { |
||||
const UnsignedInt instructionSize = dataIteration[0] >> 16; |
||||
const UnsignedInt instructionOp = dataIteration[0] & 0xffff; |
||||
|
||||
/* Corrupted SPIR-V */ |
||||
/** @todo print a message here? */ |
||||
if(dataIteration.size() < instructionSize) { |
||||
data = dataIteration; |
||||
return nullptr; |
||||
} |
||||
|
||||
/* This is the instruction we're looking for, return it and update the
|
||||
view to point after it. */ |
||||
if(instructionOp == op) { |
||||
data = dataIteration.suffix(instructionSize); |
||||
return dataIteration.prefix(instructionSize); |
||||
} |
||||
|
||||
/* Otherwise advance the view for next round */ |
||||
dataIteration = dataIteration.suffix(instructionSize); |
||||
} |
||||
|
||||
/* Nothing found. Leave the input data as-is. */ |
||||
return nullptr; |
||||
} |
||||
|
||||
struct SpirvEntrypoint { |
||||
Containers::Reference<const SpvExecutionModel> executionModel; |
||||
Containers::StringView name; |
||||
Containers::ArrayView<const UnsignedInt> interfaces; |
||||
}; |
||||
|
||||
/* When an entrypoint is found, the `data` is advanced after the instruction in
|
||||
order to allow calling this function in a loop. When not found, `data` is |
||||
left untouched. Most of other SPIR-V code is meant to appear after the |
||||
entrypoints, so it's fine to feed the resulting `data` to |
||||
spirvEntrypointInterface() and others. */ |
||||
Containers::Optional<SpirvEntrypoint> spirvNextEntrypoint(Containers::ArrayView<const UnsignedInt>& data) { |
||||
while(const Containers::ArrayView<const UnsignedInt> entryPoint = spirvFindInstruction(data, SpvOpEntryPoint)) { |
||||
/* Expecting at least op, execution model, ID, name. If less, it's an
|
||||
invalid SPIR-V. */ |
||||
/** @todo print a message here? */ |
||||
if(entryPoint.size() < 4) return {}; |
||||
|
||||
/* Find where the name ends and interface IDs start. According to the
|
||||
spec, a string literal is null-terminated and all bytes after are |
||||
zeros as well, so it should be enough to check that the last byte is |
||||
zero. */ |
||||
Containers::ArrayView<const UnsignedInt> interfaces; |
||||
for(std::size_t i = 3; i != entryPoint.size(); ++i) { |
||||
if(entryPoint[i] >> 24 == 0) { |
||||
interfaces = entryPoint.suffix(i + 1); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
return SpirvEntrypoint{ |
||||
*reinterpret_cast<const SpvExecutionModel*>(entryPoint + 1), |
||||
reinterpret_cast<const char*>(entryPoint + 3), |
||||
interfaces |
||||
}; |
||||
} |
||||
|
||||
return {}; |
||||
} |
||||
|
||||
struct SpirvEntrypointInterface { |
||||
/* If null, the interface might be for example builtin */ |
||||
const UnsignedInt* location; |
||||
/* If null, the SPIR-V is probably invalid */ |
||||
const SpvStorageClass* storageClass; |
||||
}; |
||||
|
||||
/* Unlike above, `data` isn't modified by this function -- because the
|
||||
decoration and variable instructions are likely intermixed for different |
||||
entrypoint, it makes sense to restart the search from the beginning for each |
||||
entrypoint. |
||||
|
||||
The `out` array is expected to have the same size as entrypoint.interfaces |
||||
and be zero-initialized (so the not found data stay null). */ |
||||
void spirvEntrypointInterface(Containers::ArrayView<const UnsignedInt> data, const SpirvEntrypoint& entrypoint, Containers::ArrayView<SpirvEntrypointInterface> out) { |
||||
CORRADE_INTERNAL_ASSERT(out.size() == entrypoint.interfaces.size()); |
||||
|
||||
/* Find location decorations */ |
||||
while(const Containers::ArrayView<const UnsignedInt> decoration = spirvFindInstruction(data, SpvOpDecorate)) { |
||||
/* Expecting at least op, ID, SpvDecorationLocation, location. The
|
||||
instruction can be three words, so if we get less than 4 it's not an |
||||
error. */ |
||||
if(decoration.size() < 4 || decoration[2] != SpvDecorationLocation) |
||||
continue; |
||||
|
||||
for(std::size_t i = 0; i != entrypoint.interfaces.size(); ++i) { |
||||
if(decoration[1] == entrypoint.interfaces[i]) { |
||||
out[i].location = decoration + 3; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* Find storage classes. According to the spec, OpVariable is meant to
|
||||
appear after OpDecorate, so we don't need to restart from the |
||||
beginning. */ |
||||
while(const Containers::ArrayView<const UnsignedInt> variable = spirvFindInstruction(data, SpvOpVariable)) { |
||||
/* Expecting at least op, result, ID, SpvStorageClass. If less, it's an
|
||||
invalid SPIR-V. */ |
||||
/** @todo print a message here? */ |
||||
if(variable.size() < 4) return; |
||||
|
||||
for(std::size_t i = 0; i != entrypoint.interfaces.size(); ++i) { |
||||
if(variable[2] == entrypoint.interfaces[i]) { |
||||
out[i].storageClass = reinterpret_cast<const SpvStorageClass*>(variable + 3); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
}}}} |
||||
|
||||
#endif |
||||
@ -0,0 +1,264 @@
|
||||
/*
|
||||
This file is part of Magnum. |
||||
|
||||
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, |
||||
2020, 2021 Vladimír Vondruš <mosra@centrum.cz> |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a |
||||
copy of this software and associated documentation files (the "Software"), |
||||
to deal in the Software without restriction, including without limitation |
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
||||
and/or sell copies of the Software, and to permit persons to whom the |
||||
Software is furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included |
||||
in all copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||
DEALINGS IN THE SOFTWARE. |
||||
*/ |
||||
|
||||
#include <string> |
||||
#include <Corrade/Containers/Array.h> |
||||
#include <Corrade/TestSuite/Tester.h> |
||||
#include <Corrade/Utility/Directory.h> |
||||
|
||||
#include "Magnum/ShaderTools/Implementation/spirv.h" |
||||
#include "MagnumExternal/Vulkan/spirv.h" |
||||
|
||||
#include "configure.h" |
||||
|
||||
namespace Magnum { namespace ShaderTools { namespace Test { namespace { |
||||
|
||||
struct SpirvTest: TestSuite::Tester { |
||||
explicit SpirvTest(); |
||||
|
||||
void data(); |
||||
void dataInvalid(); |
||||
|
||||
void findInstruction(); |
||||
void findInstructionNotEnoughData(); |
||||
|
||||
void nextEntrypoint(); |
||||
void nextEntrypointInvalidInstruction(); |
||||
|
||||
void entrypointInterface(); |
||||
void entrypointInterfaceNothing(); |
||||
}; |
||||
|
||||
const UnsignedInt Data[] { |
||||
SpvMagicNumber, SpvVersion, 0, 66, 0, |
||||
0 /* first instruction */ |
||||
}; |
||||
|
||||
const UnsignedInt JustHeader[]{ |
||||
SpvMagicNumber, SpvVersion, 0, 66, 0 |
||||
}; |
||||
|
||||
const UnsignedInt InvalidMagic[]{ |
||||
SpvMagicNumber + 1, SpvVersion, 0, 66, 0, |
||||
0 /* first instruction */ |
||||
}; |
||||
|
||||
const struct { |
||||
const char* name; |
||||
Containers::ArrayView<const void> data; |
||||
} InvalidData[] { |
||||
{"empty", {}}, |
||||
{"just the header", JustHeader}, |
||||
{"invalid magic", InvalidMagic}, |
||||
{"size not divisible by four", Containers::arrayCast<const char>(Data).except(1)} |
||||
}; |
||||
|
||||
SpirvTest::SpirvTest() { |
||||
addTests({&SpirvTest::data}); |
||||
|
||||
addInstancedTests({&SpirvTest::dataInvalid}, |
||||
Containers::arraySize(InvalidData)); |
||||
|
||||
addTests({&SpirvTest::findInstruction, |
||||
&SpirvTest::findInstructionNotEnoughData, |
||||
|
||||
&SpirvTest::nextEntrypoint, |
||||
&SpirvTest::nextEntrypointInvalidInstruction, |
||||
|
||||
&SpirvTest::entrypointInterface, |
||||
&SpirvTest::entrypointInterfaceNothing}); |
||||
} |
||||
|
||||
void SpirvTest::data() { |
||||
CORRADE_COMPARE(Implementation::spirvData(Data, sizeof(Data)), Containers::arrayView(Data + 5, 1)); |
||||
} |
||||
|
||||
void SpirvTest::dataInvalid() { |
||||
auto&& data = InvalidData[testCaseInstanceId()]; |
||||
setTestCaseDescription(data.name); |
||||
CORRADE_VERIFY(!Implementation::spirvData(data.data, data.data.size())); |
||||
} |
||||
|
||||
UnsignedInt op(UnsignedInt length, SpvOp op) { |
||||
return length << 16 | op; |
||||
} |
||||
|
||||
void SpirvTest::findInstruction() { |
||||
const UnsignedInt data[] { |
||||
op(3, SpvOpMemoryModel), SpvAddressingModelLogical, SpvMemoryModelGLSL450, |
||||
op(4, SpvOpDecorate), 12, SpvDecorationLocation, 0, |
||||
op(1, SpvOpNop), |
||||
op(1, SpvOpNop), |
||||
op(4, SpvOpDecorate), 13, SpvDecorationLocation, 1, |
||||
}; |
||||
Containers::ArrayView<const UnsignedInt> view = data; |
||||
|
||||
Containers::ArrayView<const UnsignedInt> decorate1 = Implementation::spirvFindInstruction(view, SpvOpDecorate); |
||||
CORRADE_COMPARE(decorate1.size(), 4); |
||||
CORRADE_COMPARE(decorate1.data(), data + 3); |
||||
CORRADE_COMPARE(view.data(), data + 7); |
||||
|
||||
/* Verify a single-word instruction works too */ |
||||
Containers::ArrayView<const UnsignedInt> nop = Implementation::spirvFindInstruction(view, SpvOpNop); |
||||
CORRADE_COMPARE(nop.size(), 1); |
||||
CORRADE_COMPARE(nop.data(), data + 7); |
||||
CORRADE_COMPARE(view.data(), data + 8); |
||||
|
||||
Containers::ArrayView<const UnsignedInt> decorate2 = Implementation::spirvFindInstruction(view, SpvOpDecorate); |
||||
CORRADE_COMPARE(decorate2.size(), 4); |
||||
CORRADE_COMPARE(decorate2.data(), data + 9); |
||||
CORRADE_COMPARE(view.data(), data + 13); |
||||
|
||||
/* We're at the end, there's no more OpDecorate instructions to find */ |
||||
CORRADE_VERIFY(!Implementation::spirvFindInstruction(view, SpvOpDecorate)); |
||||
} |
||||
|
||||
void SpirvTest::findInstructionNotEnoughData() { |
||||
const UnsignedInt data[] { |
||||
op(3, SpvOpMemoryModel), SpvAddressingModelLogical, SpvMemoryModelGLSL450, |
||||
/* Should be 4 */ |
||||
op(5, SpvOpDecorate), 12, SpvDecorationLocation, 0 |
||||
}; |
||||
Containers::ArrayView<const UnsignedInt> view = data; |
||||
|
||||
CORRADE_VERIFY(!Implementation::spirvFindInstruction(view, SpvOpDecorate)); |
||||
/* View gets set to the first invalid instruction */ |
||||
CORRADE_COMPARE(view.data(), data + 3); |
||||
} |
||||
|
||||
void SpirvTest::nextEntrypoint() { |
||||
Containers::Array<char> data = Utility::Directory::read(Utility::Directory::join(SHADERTOOLS_TEST_DIR, "SpirvTestFiles/entrypoint-interface.spv")); |
||||
|
||||
/* The file is a full SPIR-V, strip the header first */ |
||||
Containers::ArrayView<const UnsignedInt> view = Implementation::spirvData(data, data.size()); |
||||
CORRADE_VERIFY(view); |
||||
|
||||
Containers::Optional<Implementation::SpirvEntrypoint> vert = Implementation::spirvNextEntrypoint(view); |
||||
CORRADE_VERIFY(vert); |
||||
/* Verify that long names get recognized properly */ |
||||
CORRADE_COMPARE(vert->name, "vertexLongEntrypointName"); |
||||
CORRADE_COMPARE(vert->executionModel, SpvExecutionModelVertex); |
||||
/* We don't care about the contents, those would change with each assembly
|
||||
anyway. Verified fully in entrypointInterface(). */ |
||||
CORRADE_COMPARE(vert->interfaces.size(), 4); |
||||
|
||||
Containers::Optional<Implementation::SpirvEntrypoint> frag = Implementation::spirvNextEntrypoint(view); |
||||
CORRADE_VERIFY(frag); |
||||
CORRADE_COMPARE(frag->name, "fra"); |
||||
CORRADE_COMPARE(frag->executionModel, SpvExecutionModelFragment); |
||||
CORRADE_COMPARE(frag->interfaces.size(), 3); |
||||
|
||||
/* Only two entrypoints in this file */ |
||||
CORRADE_VERIFY(!Implementation::spirvNextEntrypoint(view)); |
||||
} |
||||
|
||||
void SpirvTest::nextEntrypointInvalidInstruction() { |
||||
const UnsignedInt data[] { |
||||
op(3, SpvOpMemoryModel), SpvAddressingModelLogical, SpvMemoryModelGLSL450, |
||||
|
||||
/* Should be 4 (missing name) */ |
||||
op(3, SpvOpEntryPoint), SpvExecutionModelVertex, 1 |
||||
}; |
||||
Containers::ArrayView<const UnsignedInt> view = data; |
||||
|
||||
CORRADE_VERIFY(!Implementation::spirvNextEntrypoint(view)); |
||||
} |
||||
|
||||
void SpirvTest::entrypointInterface() { |
||||
Containers::Array<char> data = Utility::Directory::read(Utility::Directory::join(SHADERTOOLS_TEST_DIR, "SpirvTestFiles/entrypoint-interface.spv")); |
||||
|
||||
/* The file is a full SPIR-V, strip the header first */ |
||||
Containers::ArrayView<const UnsignedInt> view = Implementation::spirvData(data, data.size()); |
||||
CORRADE_VERIFY(view); |
||||
|
||||
Containers::Optional<Implementation::SpirvEntrypoint> vert = Implementation::spirvNextEntrypoint(view); |
||||
CORRADE_VERIFY(vert); |
||||
CORRADE_COMPARE(vert->interfaces.size(), 4); |
||||
|
||||
Implementation::SpirvEntrypointInterface vertInterface[4]{}; |
||||
Implementation::spirvEntrypointInterface(view, *vert, vertInterface); |
||||
CORRADE_VERIFY(vertInterface[0].location); /* position */ |
||||
CORRADE_VERIFY(vertInterface[0].storageClass); |
||||
CORRADE_COMPARE(*vertInterface[0].location, 0); |
||||
CORRADE_COMPARE(*vertInterface[0].storageClass, SpvStorageClassInput); |
||||
|
||||
CORRADE_VERIFY(vertInterface[1].location); /* color */ |
||||
CORRADE_VERIFY(vertInterface[1].storageClass); |
||||
CORRADE_COMPARE(*vertInterface[1].location, 1); |
||||
CORRADE_COMPARE(*vertInterface[1].storageClass, SpvStorageClassInput); |
||||
|
||||
/* Verify that absence of location is handled properly */ |
||||
CORRADE_VERIFY(!vertInterface[2].location); /* gl_Position */ |
||||
CORRADE_VERIFY(vertInterface[2].storageClass); |
||||
CORRADE_COMPARE(*vertInterface[2].storageClass, SpvStorageClassOutput); |
||||
|
||||
CORRADE_VERIFY(vertInterface[3].location); /* interpolatedColorOut */ |
||||
CORRADE_VERIFY(vertInterface[2].storageClass); |
||||
CORRADE_COMPARE(*vertInterface[3].location, 0); |
||||
CORRADE_COMPARE(*vertInterface[2].storageClass, SpvStorageClassOutput); |
||||
|
||||
Containers::Optional<Implementation::SpirvEntrypoint> frag = Implementation::spirvNextEntrypoint(view); |
||||
CORRADE_VERIFY(frag); |
||||
CORRADE_COMPARE(frag->interfaces.size(), 3); |
||||
|
||||
Implementation::SpirvEntrypointInterface fragInterface[3]{}; |
||||
Implementation::spirvEntrypointInterface(view, *frag, fragInterface); |
||||
CORRADE_VERIFY(fragInterface[0].location); /* interpolatedColorIn */ |
||||
CORRADE_VERIFY(fragInterface[0].storageClass); |
||||
CORRADE_COMPARE(*fragInterface[0].location, 0); |
||||
CORRADE_COMPARE(*fragInterface[0].storageClass, SpvStorageClassInput); |
||||
|
||||
CORRADE_VERIFY(fragInterface[1].location); /* fragmentColor */ |
||||
CORRADE_VERIFY(fragInterface[1].storageClass); |
||||
CORRADE_COMPARE(*fragInterface[1].location, 0); |
||||
CORRADE_COMPARE(*fragInterface[1].storageClass, SpvStorageClassOutput); |
||||
|
||||
/* Verify that absence of storageClass is handled properly */ |
||||
CORRADE_VERIFY(fragInterface[2].location); /* unknownFragmentInterface */ |
||||
CORRADE_VERIFY(!fragInterface[2].storageClass); |
||||
CORRADE_COMPARE(*fragInterface[2].location, 1); |
||||
} |
||||
|
||||
void SpirvTest::entrypointInterfaceNothing() { |
||||
const UnsignedInt data[] { |
||||
op(3, SpvOpMemoryModel), SpvAddressingModelLogical, SpvMemoryModelGLSL450, |
||||
|
||||
op(4, SpvOpEntryPoint), SpvExecutionModelGLCompute, 1, '\0' |
||||
}; |
||||
Containers::ArrayView<const UnsignedInt> view = data; |
||||
|
||||
Containers::Optional<Implementation::SpirvEntrypoint> comp = Implementation::spirvNextEntrypoint(view); |
||||
CORRADE_VERIFY(comp); |
||||
CORRADE_VERIFY(comp->interfaces.empty()); |
||||
|
||||
Implementation::spirvEntrypointInterface(view, *comp, {}); |
||||
|
||||
/* Well, it shouldn't crash */ |
||||
CORRADE_VERIFY(true); |
||||
} |
||||
|
||||
}}}} |
||||
|
||||
CORRADE_TEST_MAIN(Magnum::ShaderTools::Test::SpirvTest) |
||||
@ -0,0 +1,5 @@
|
||||
#!/bin/bash |
||||
|
||||
for i in $(ls *.spvasm); do |
||||
magnum-shaderconverter $i ${i%asm} |
||||
done |
||||
Binary file not shown.
@ -0,0 +1,23 @@
|
||||
OpCapability Shader |
||||
OpEntryPoint Vertex %ver "vertexLongEntrypointName" %position %color %gl_Position %interpolatedColorOut |
||||
OpEntryPoint Fragment %fra "fra" %interpolatedColorIn %fragmentColor %unknownFragmentInterface |
||||
OpExecutionMode %fra OriginUpperLeft |
||||
OpDecorate %gl_Position BuiltIn Position |
||||
OpDecorate %position Location 0 |
||||
OpDecorate %color Location 1 |
||||
OpDecorate %fragmentColor Location 0 |
||||
OpDecorate %unknownFragmentInterface Location 1 |
||||
OpDecorate %interpolatedColorIn Location 0 |
||||
OpDecorate %interpolatedColorOut Location 0 |
||||
%void = OpTypeVoid |
||||
%10 = OpTypeFunction %void |
||||
%float = OpTypeFloat 32 |
||||
%v4float = OpTypeVector %float 4 |
||||
%_ptr_Input_v4float = OpTypePointer Input %v4float |
||||
%position = OpVariable %_ptr_Input_v4float Input |
||||
%color = OpVariable %_ptr_Input_v4float Input |
||||
%interpolatedColorIn = OpVariable %_ptr_Input_v4float Input |
||||
%_ptr_Output_v4float = OpTypePointer Output %v4float |
||||
%gl_Position = OpVariable %_ptr_Output_v4float Output |
||||
%interpolatedColorOut = OpVariable %_ptr_Output_v4float Output |
||||
%fragmentColor = OpVariable %_ptr_Output_v4float Output |
||||
Loading…
Reference in new issue