mirror of https://github.com/mosra/magnum.git
135 changed files with 9178 additions and 793 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,61 @@
|
||||
/*
|
||||
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 "Stage.h" |
||||
|
||||
#include <Corrade/Utility/Debug.h> |
||||
|
||||
namespace Magnum { namespace ShaderTools { |
||||
|
||||
Debug& operator<<(Debug& debug, const Stage value) { |
||||
debug << "ShaderTools::Stage" << Debug::nospace; |
||||
|
||||
switch(value) { |
||||
/* LCOV_EXCL_START */ |
||||
#define _c(v) case Stage::v: return debug << "::" #v; |
||||
_c(Unspecified) |
||||
_c(Vertex) |
||||
_c(Fragment) |
||||
_c(Geometry) |
||||
_c(TessellationControl) |
||||
_c(TessellationEvaluation) |
||||
_c(Compute) |
||||
_c(RayGeneration) |
||||
_c(RayAnyHit) |
||||
_c(RayClosestHit) |
||||
_c(RayMiss) |
||||
_c(RayIntersection) |
||||
_c(RayCallable) |
||||
_c(MeshTask) |
||||
_c(Mesh) |
||||
_c(Kernel) |
||||
#undef _c |
||||
/* LCOV_EXCL_STOP */ |
||||
} |
||||
|
||||
return debug << "(" << Debug::nospace << reinterpret_cast<void*>(UnsignedByte(value)) << Debug::nospace << ")"; |
||||
} |
||||
|
||||
}} |
||||
@ -0,0 +1,97 @@
|
||||
#ifndef Magnum_ShaderTools_Stage_h |
||||
#define Magnum_ShaderTools_Stage_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. |
||||
*/ |
||||
|
||||
/** @file
|
||||
* @brief Enum @ref Magnum::ShaderTools::Stage |
||||
* @m_since_latest |
||||
*/ |
||||
|
||||
#include "Magnum/Magnum.h" |
||||
#include "Magnum/ShaderTools/visibility.h" |
||||
|
||||
namespace Magnum { namespace ShaderTools { |
||||
|
||||
/**
|
||||
@brief Shader stage |
||||
@m_since_latest |
||||
|
||||
@see @ref AbstractConverter |
||||
*/ |
||||
enum class Stage: UnsignedInt { |
||||
/**
|
||||
* Unspecified stage. When used in the |
||||
* @ref AbstractConverter::validateFile(), |
||||
* @ref AbstractConverter::convertFileToFile() "convertFileToFile()", |
||||
* @ref AbstractConverter::convertFileToData() "convertFileToData()", |
||||
* @ref AbstractConverter::linkFilesToFile() "linkFilesToFile()" or |
||||
* @ref AbstractConverter::linkFilesToData() "linkFilesToData()" APIs, |
||||
* particular plugins may attempt to detect the stage from filename, the |
||||
* shader stage might also be encoded directly in certain |
||||
* @ref Format "Format"s. Leaving the stage unspecified might limit |
||||
* validation and conversion capabilities, see documentation of a |
||||
* particular converter for concrete behavior. |
||||
* |
||||
* This value is guaranteed to be @cpp 0 @ce, which means you're encouraged |
||||
* to simply use @cpp {} @ce in function calls and elsewhere. |
||||
*/ |
||||
Unspecified = 0, |
||||
|
||||
Vertex, /**< Vertex stage */ |
||||
Fragment, /**< Fragment stage */ |
||||
Geometry, /**< Geometry stage */ |
||||
TessellationControl, /**< Tessellation control stage */ |
||||
TessellationEvaluation, /**< Tessellation evaluation stage */ |
||||
Compute, /**< Compute stage */ |
||||
|
||||
RayGeneration, /**< Ray generation stage */ |
||||
RayAnyHit, /**< Ray any hit stage */ |
||||
RayClosestHit, /**< Ray closest hit stage */ |
||||
RayMiss, /**< Ray miss stage */ |
||||
RayIntersection, /**< Ray intersection stage */ |
||||
RayCallable, /**< Ray callable stage */ |
||||
|
||||
MeshTask, /**< Mesh task stage */ |
||||
Mesh, /**< Mesh stage */ |
||||
|
||||
/**
|
||||
* Compute kernel. |
||||
* |
||||
* Compared to @ref Stage::Compute, which is used by graphics APIs such as |
||||
* Vulkan or OpenGL, this one is for use by OpenCL. |
||||
*/ |
||||
Kernel |
||||
}; |
||||
|
||||
/**
|
||||
@debugoperatorenum{Stage} |
||||
@m_since_latest |
||||
*/ |
||||
MAGNUM_SHADERTOOLS_EXPORT Debug& operator<<(Debug& debug, Stage value); |
||||
|
||||
}} |
||||
|
||||
#endif |
||||
@ -0,0 +1,265 @@
|
||||
/*
|
||||
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", {}}, |
||||
/* GCC 4.8 needs the ArrayView conversion explicit */ |
||||
{"just the header", Containers::arrayView(JustHeader)}, |
||||
{"invalid magic", Containers::arrayView(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 |
||||
@ -0,0 +1,53 @@
|
||||
/*
|
||||
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 <sstream> |
||||
#include <Corrade/TestSuite/Tester.h> |
||||
#include <Corrade/Utility/DebugStl.h> |
||||
|
||||
#include "Magnum/ShaderTools/Stage.h" |
||||
|
||||
namespace Magnum { namespace ShaderTools { namespace Test { namespace { |
||||
|
||||
struct StageTest: TestSuite::Tester { |
||||
explicit StageTest(); |
||||
|
||||
void debug(); |
||||
}; |
||||
|
||||
StageTest::StageTest() { |
||||
addTests({&StageTest::debug}); |
||||
} |
||||
|
||||
void StageTest::debug() { |
||||
std::ostringstream out; |
||||
|
||||
Debug{&out} << Stage::RayMiss << Stage(0xf0); |
||||
CORRADE_COMPARE(out.str(), "ShaderTools::Stage::RayMiss ShaderTools::Stage(0xf0)\n"); |
||||
} |
||||
|
||||
}}}} |
||||
|
||||
CORRADE_TEST_MAIN(Magnum::ShaderTools::Test::StageTest) |
||||
@ -0,0 +1,116 @@
|
||||
#ifndef Magnum_Vk_Implementation_spirvPatching_h |
||||
#define Magnum_Vk_Implementation_spirvPatching_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/ArrayTuple.h> |
||||
|
||||
#include "Magnum/Math/Functions.h" |
||||
|
||||
/* All code in this file should be self-contained, with no link-time dependency
|
||||
on ShaderTools */ |
||||
#include "Magnum/ShaderTools/Implementation/spirv.h" |
||||
|
||||
namespace Magnum { namespace Vk { namespace Implementation { namespace { |
||||
|
||||
/* This kinda throws const-correctness through the window -- the the SPIR-V
|
||||
utils work on const data, but this function actually does mutate them */ |
||||
bool spirvPatchSwiftShaderConflictingMultiEntrypointLocations(Containers::ArrayView<const UnsignedInt> data) { |
||||
/* Find vertex/fragment entrypoints and count how many is there in total */ |
||||
UnsignedInt entrypointCount = 0; |
||||
Containers::Optional<ShaderTools::Implementation::SpirvEntrypoint> vertexEntryPoint, fragmentEntryPoint; |
||||
while(Containers::Optional<ShaderTools::Implementation::SpirvEntrypoint> entrypoint = ShaderTools::Implementation::spirvNextEntrypoint(data)) { |
||||
++entrypointCount; |
||||
if(entrypoint->executionModel == SpvExecutionModelVertex) |
||||
vertexEntryPoint = *entrypoint; |
||||
else if(entrypoint->executionModel == SpvExecutionModelFragment) |
||||
fragmentEntryPoint = *entrypoint; |
||||
} |
||||
|
||||
/* If there aren't both entrypoints, this bug doesn't affect the shader. If
|
||||
there are more, we won't attempt anything -- right now SwiftShader |
||||
doesn't support geom/tess shaders, so the only possibility is that the |
||||
module is a library of multiple different vertex / fragment |
||||
implementations and that's too frightening as any patching would most |
||||
likely break things *really bad*. */ |
||||
if(entrypointCount > 2 || !vertexEntryPoint || !fragmentEntryPoint) |
||||
return false; |
||||
|
||||
/* Get locations and storage classes for all entrypoint interfaces */ |
||||
Containers::ArrayView<ShaderTools::Implementation::SpirvEntrypointInterface> vertexInterface, fragmentInterface; |
||||
Containers::ArrayTuple interfaceData{ |
||||
{Containers::ValueInit, vertexEntryPoint->interfaces.size(), vertexInterface}, |
||||
{Containers::ValueInit, fragmentEntryPoint->interfaces.size(), fragmentInterface} |
||||
}; |
||||
spirvEntrypointInterface(data, *vertexEntryPoint, vertexInterface); |
||||
spirvEntrypointInterface(data, *fragmentEntryPoint, fragmentInterface); |
||||
|
||||
/* Calculate the max location so we know what to change to */ |
||||
UnsignedInt maxLocation = 0; |
||||
for(const ShaderTools::Implementation::SpirvEntrypointInterface& i: vertexInterface) |
||||
if(i.location) maxLocation = Math::max(maxLocation, *i.location); |
||||
for(const ShaderTools::Implementation::SpirvEntrypointInterface& i: fragmentInterface) |
||||
if(i.location) maxLocation = Math::max(maxLocation, *i.location); |
||||
|
||||
/* For all vertex outputs check if there are fragment outputs with the same
|
||||
locations */ |
||||
for(const ShaderTools::Implementation::SpirvEntrypointInterface& vertexOutput: vertexInterface) { |
||||
/* Ignore what's not an output or what doesn't have a location (for
|
||||
example a builtin) */ |
||||
if(!vertexOutput.storageClass || *vertexOutput.storageClass != SpvStorageClassOutput || !vertexOutput.location) |
||||
continue; |
||||
|
||||
for(const ShaderTools::Implementation::SpirvEntrypointInterface& fragmentOutput: fragmentInterface) { |
||||
/* Ignore what's not an output or what doesn't have a location (for
|
||||
example a builtin) */ |
||||
if(!fragmentOutput.storageClass || *fragmentOutput.storageClass != SpvStorageClassOutput || !fragmentOutput.location) continue; |
||||
|
||||
/* The same location used, we need to remap. Use the next highest
|
||||
unused location and change also the corresponding fragment |
||||
input, if there's any. */ |
||||
if(*vertexOutput.location == *fragmentOutput.location) { |
||||
const UnsignedInt newLocation = ++maxLocation; |
||||
for(const ShaderTools::Implementation::SpirvEntrypointInterface& fragmentInput: fragmentInterface) { |
||||
if(*fragmentInput.storageClass != SpvStorageClassInput) |
||||
continue; |
||||
if(*fragmentInput.location == *vertexOutput.location) { |
||||
/** @todo ugly! */ |
||||
*const_cast<UnsignedInt*>(fragmentInput.location) = newLocation; |
||||
break; |
||||
} |
||||
} |
||||
/** @todo ugly! */ |
||||
*const_cast<UnsignedInt*>(vertexOutput.location) = newLocation; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
}}}} |
||||
|
||||
#endif |
||||
@ -0,0 +1,250 @@
|
||||
/*
|
||||
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 "Mesh.h" |
||||
#include "CommandBuffer.h" |
||||
|
||||
#include <Corrade/Containers/Array.h> |
||||
|
||||
#include "Magnum/Mesh.h" |
||||
#include "Magnum/Vk/Buffer.h" |
||||
#include "Magnum/Vk/Device.h" |
||||
#include "Magnum/Vk/MeshLayout.h" |
||||
#include "Magnum/Vk/RasterizationPipelineCreateInfo.h" |
||||
#include "Magnum/Vk/Implementation/DeviceState.h" |
||||
|
||||
namespace Magnum { namespace Vk { |
||||
|
||||
namespace { |
||||
|
||||
constexpr MeshIndexType IndexTypeMapping[]{ |
||||
MeshIndexType::UnsignedByte, |
||||
MeshIndexType::UnsignedShort, |
||||
MeshIndexType::UnsignedInt |
||||
}; |
||||
|
||||
} |
||||
|
||||
MeshIndexType meshIndexType(const Magnum::MeshIndexType type) { |
||||
CORRADE_ASSERT(UnsignedInt(type) - 1 < Containers::arraySize(IndexTypeMapping), |
||||
"Vk::meshIndexType(): invalid type" << type, {}); |
||||
return IndexTypeMapping[UnsignedInt(type) - 1]; |
||||
} |
||||
|
||||
Debug& operator<<(Debug& debug, const MeshIndexType value) { |
||||
debug << "Vk::MeshIndexType" << Debug::nospace; |
||||
|
||||
switch(value) { |
||||
/* LCOV_EXCL_START */ |
||||
#define _c(value) case Vk::MeshIndexType::value: return debug << "::" << Debug::nospace << #value; |
||||
_c(UnsignedByte) |
||||
_c(UnsignedShort) |
||||
_c(UnsignedInt) |
||||
#undef _c |
||||
/* LCOV_EXCL_STOP */ |
||||
} |
||||
|
||||
/* Vulkan docs have the values in decimal, so not converting to hex */ |
||||
return debug << "(" << Debug::nospace << Int(value) << Debug::nospace << ")"; |
||||
} |
||||
|
||||
struct Mesh::State { |
||||
Containers::ArrayTuple vertexBufferData; |
||||
Containers::ArrayView<VkBuffer> vertexBuffers; |
||||
Containers::ArrayView<UnsignedLong> vertexBufferOffsets; |
||||
Containers::ArrayView<UnsignedLong> vertexBufferStrides; |
||||
Containers::ArrayView<Buffer> ownedVertexBuffers; |
||||
|
||||
VkBuffer indexBuffer{}; |
||||
Buffer ownedIndexBuffer{NoCreate}; |
||||
UnsignedLong indexBufferOffset{}; |
||||
MeshIndexType indexType{}; |
||||
}; |
||||
|
||||
Mesh::Mesh(const MeshLayout& layout): Mesh{MeshLayout{layout.vkPipelineVertexInputStateCreateInfo(), layout.vkPipelineInputAssemblyStateCreateInfo()}} {} |
||||
|
||||
Mesh::Mesh(MeshLayout&& layout): _layout{std::move(layout)} { |
||||
/* Since we know the count of buffer bindings, we can directly allocate
|
||||
all needed memory upfront */ |
||||
if(const UnsignedInt count = _layout.vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount) { |
||||
_state.emplace(); |
||||
_state->vertexBufferData = Containers::ArrayTuple{ |
||||
{Containers::ValueInit, count, _state->vertexBuffers}, |
||||
{Containers::ValueInit, count, _state->vertexBufferOffsets}, |
||||
{Containers::ValueInit, count, _state->vertexBufferStrides}, |
||||
{Containers::NoInit, count, _state->ownedVertexBuffers} |
||||
}; |
||||
|
||||
/** @tod use DirectInit once ArrayTuple can do that */ |
||||
for(Buffer& b: _state->ownedVertexBuffers) new(&b) Buffer{NoCreate}; |
||||
} |
||||
} |
||||
|
||||
/* We don't have any internal self-pointers so a default is fine, and
|
||||
MeshLayout has its own constructor to handle those (if any) */ |
||||
Mesh::Mesh(Mesh&&) noexcept = default; |
||||
|
||||
Mesh::~Mesh() = default; |
||||
|
||||
Mesh& Mesh::operator=(Mesh&&) noexcept = default; |
||||
|
||||
UnsignedInt Mesh::count() const { |
||||
/* The inverse value is used to detect & assert on forgotten setCount()
|
||||
in CommandBuffer::draw(), return 0 in that case as well */ |
||||
return _count == ~UnsignedInt{} ? 0 : _count; |
||||
} |
||||
|
||||
std::size_t Mesh::addVertexBufferInternal(UnsignedInt binding, VkBuffer buffer, UnsignedLong offset) { |
||||
/* Find this binding in the layout */ |
||||
for(std::size_t i = 0, max = _layout.vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount; i != max; ++i) { |
||||
if(_layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[i].binding == binding) { |
||||
_state->vertexBuffers[i] = buffer; |
||||
_state->vertexBufferOffsets[i] = offset; |
||||
/* Save the stride as well in case a dynamic state would need it */ |
||||
_state->vertexBufferStrides[i] = _layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[i].stride; |
||||
return i; |
||||
} |
||||
} |
||||
|
||||
CORRADE_ASSERT_UNREACHABLE("Vk::Mesh::addVertexBuffer(): binding" << binding << "not present among" << _layout.vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount << "bindings in the layout", ~std::size_t{}); |
||||
} |
||||
|
||||
Mesh& Mesh::addVertexBuffer(const UnsignedInt binding, const VkBuffer buffer, const UnsignedLong offset) { |
||||
addVertexBufferInternal(binding, buffer, offset); |
||||
return *this; |
||||
} |
||||
|
||||
Mesh& Mesh::addVertexBuffer(const UnsignedInt binding, Buffer&& buffer, const UnsignedLong offset) { |
||||
const std::size_t index = addVertexBufferInternal(binding, buffer, offset); |
||||
#ifdef CORRADE_GRACEFUL_ASSERT |
||||
if(index == ~std::size_t{}) return *this; |
||||
#endif |
||||
_state->ownedVertexBuffers[index] = std::move(buffer); |
||||
return *this; |
||||
} |
||||
|
||||
Mesh& Mesh::setIndexBuffer(const VkBuffer buffer, const UnsignedLong offset, const MeshIndexType indexType) { |
||||
/* If the mesh has no vertex buffer bindings, the state isn't populated
|
||||
in the constructor. Do it here. */ |
||||
if(!_state) _state.emplace(); |
||||
|
||||
_state->indexBuffer = buffer; |
||||
_state->indexBufferOffset = offset; |
||||
_state->indexType = indexType; |
||||
return *this; |
||||
} |
||||
|
||||
Mesh& Mesh::setIndexBuffer(const VkBuffer buffer, const UnsignedLong offset, const Magnum::MeshIndexType indexType) { |
||||
return setIndexBuffer(buffer, offset, meshIndexType(indexType)); |
||||
} |
||||
|
||||
Mesh& Mesh::setIndexBuffer(Buffer&& buffer, const UnsignedLong offset, const MeshIndexType indexType) { |
||||
setIndexBuffer(buffer, offset, indexType); |
||||
_state->ownedIndexBuffer = std::move(buffer); |
||||
return *this; |
||||
} |
||||
|
||||
Mesh& Mesh::setIndexBuffer(Buffer&& buffer, const UnsignedLong offset, const Magnum::MeshIndexType indexType) { |
||||
return setIndexBuffer(std::move(buffer), offset, meshIndexType(indexType)); |
||||
} |
||||
|
||||
Containers::ArrayView<const VkBuffer> Mesh::vertexBuffers() { |
||||
return _state ? _state->vertexBuffers : nullptr; |
||||
} |
||||
|
||||
Containers::ArrayView<const UnsignedLong> Mesh::vertexBufferOffsets() const { |
||||
return _state ? _state->vertexBufferOffsets : nullptr; |
||||
} |
||||
|
||||
Containers::ArrayView<const UnsignedLong> Mesh::vertexBufferStrides() const { |
||||
return _state ? _state->vertexBufferStrides : nullptr; |
||||
} |
||||
|
||||
bool Mesh::isIndexed() const { return _state && _state->indexBuffer; } |
||||
|
||||
VkBuffer Mesh::indexBuffer() { |
||||
CORRADE_ASSERT(isIndexed(), "Vk::Mesh::indexBuffer(): the mesh is not indexed", {}); |
||||
return _state->indexBuffer; |
||||
} |
||||
|
||||
UnsignedLong Mesh::indexBufferOffset() const { |
||||
CORRADE_ASSERT(isIndexed(), "Vk::Mesh::indexBufferOffset(): the mesh is not indexed", {}); |
||||
return _state->indexBufferOffset; |
||||
} |
||||
|
||||
MeshIndexType Mesh::indexType() const { |
||||
CORRADE_ASSERT(isIndexed(), "Vk::Mesh::indexType(): the mesh is not indexed", {}); |
||||
return _state->indexType; |
||||
} |
||||
|
||||
CommandBuffer& CommandBuffer::draw(Mesh& mesh) { |
||||
CORRADE_ASSERT(mesh.isCountSet(), |
||||
"Vk::CommandBuffer::draw(): Mesh::setCount() was never called, probably a mistake?", *this); |
||||
|
||||
if(!mesh.count() || !mesh.instanceCount()) return *this; |
||||
|
||||
if(_dynamicRasterizationStates & DynamicRasterizationState::MeshPrimitive) |
||||
(**_device).CmdSetPrimitiveTopologyEXT(_handle, mesh.layout().vkPipelineInputAssemblyStateCreateInfo().topology); |
||||
|
||||
const VkVertexInputBindingDescription* bindings = mesh.layout().vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions; |
||||
for(std::size_t i = 0, max = mesh.layout().vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount; i != max; ++i) { |
||||
/** @todo don't call this for each binding, detect ranges */ |
||||
_device->state().cmdBindVertexBuffersImplementation(*this, |
||||
bindings[i].binding, 1, |
||||
mesh.vertexBuffers() + i, |
||||
mesh.vertexBufferOffsets() + i, |
||||
_dynamicRasterizationStates & DynamicRasterizationState::VertexInputBindingStride ? |
||||
mesh.vertexBufferStrides() + i : nullptr |
||||
); |
||||
} |
||||
|
||||
if(mesh.isIndexed()) { |
||||
(**_device).CmdBindIndexBuffer(_handle, mesh.indexBuffer(), mesh.indexBufferOffset(), VkIndexType(mesh.indexType())); |
||||
(**_device).CmdDrawIndexed(_handle, mesh.count(), mesh.instanceCount(), mesh.indexOffset(), mesh.vertexOffset(), mesh.instanceOffset()); |
||||
} else { |
||||
(**_device).CmdDraw(_handle, mesh.count(), mesh.instanceCount(), mesh.vertexOffset(), mesh.instanceOffset()); |
||||
} |
||||
|
||||
return *this; |
||||
} |
||||
|
||||
void CommandBuffer::bindVertexBuffersImplementationDefault(CommandBuffer& self, const UnsignedInt firstBinding, const UnsignedInt bindingCount, const VkBuffer* const buffers, const UnsignedLong* const offsets, const UnsignedLong* const strides) { |
||||
CORRADE_ASSERT(!strides, |
||||
"Vk::CommandBuffer::draw(): dynamic strides supplied for an implementation without extended dynamic state", |
||||
/* Calling this even in case the assert blows up to avoid validation
|
||||
layer errors about unbound attributes when CORRADE_GRACEFUL_ASSERT |
||||
is enabled */ |
||||
(**self._device).CmdBindVertexBuffers(self, firstBinding, bindingCount, buffers, offsets)); |
||||
#ifdef CORRADE_NO_ASSERT |
||||
static_cast<void>(strides); |
||||
#endif |
||||
(**self._device).CmdBindVertexBuffers(self, firstBinding, bindingCount, buffers, offsets); |
||||
} |
||||
|
||||
void CommandBuffer::bindVertexBuffersImplementationEXT(CommandBuffer& self, const UnsignedInt firstBinding, const UnsignedInt bindingCount, const VkBuffer* const buffers, const UnsignedLong* const offsets, const UnsignedLong* const strides) { |
||||
return (**self._device).CmdBindVertexBuffers2EXT(self, firstBinding, bindingCount, buffers, offsets, nullptr, strides); |
||||
} |
||||
|
||||
}} |
||||
@ -0,0 +1,413 @@
|
||||
#ifndef Magnum_Vk_Mesh_h |
||||
#define Magnum_Vk_Mesh_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. |
||||
*/ |
||||
|
||||
/** @file
|
||||
* @brief Class @ref Magnum::Vk::Mesh, enum @ref Magnum::Vk::MeshIndexType, function @ref Magnum::Vk::meshIndexType() |
||||
* @m_since_latest |
||||
*/ |
||||
|
||||
#include <Corrade/Containers/Pointer.h> |
||||
|
||||
#include "Magnum/Vk/MeshLayout.h" |
||||
|
||||
namespace Magnum { namespace Vk { |
||||
|
||||
/**
|
||||
@brief Mesh index type |
||||
@m_since_latest |
||||
|
||||
Wraps a @type_vk_keyword{IndexType}. |
||||
*/ |
||||
enum class MeshIndexType: Int { |
||||
/**
|
||||
* @ref Magnum::UnsignedByte "UnsignedByte". |
||||
* |
||||
* @m_class{m-note m-success} |
||||
* |
||||
* @par |
||||
* Discouraged on contemporary GPU architectures, prefer to use 16-bit |
||||
* indices instead. |
||||
* |
||||
* @requires_vk_feature @ref DeviceFeature::IndexTypeUnsignedByte |
||||
*/ |
||||
UnsignedByte = VK_INDEX_TYPE_UINT8_EXT, |
||||
|
||||
/** @ref Magnum::UnsignedShort "UnsignedShort" */ |
||||
UnsignedShort = VK_INDEX_TYPE_UINT16, |
||||
|
||||
/**
|
||||
* @ref Magnum::UnsignedInt "UnsignedInt" |
||||
* @see @ref DeviceFeature::FullDrawIndexUnsignedInt |
||||
*/ |
||||
UnsignedInt = VK_INDEX_TYPE_UINT32 |
||||
}; |
||||
|
||||
/**
|
||||
@debugoperatorenum{MeshIndexType} |
||||
@m_since_latest |
||||
*/ |
||||
MAGNUM_VK_EXPORT Debug& operator<<(Debug& debug, MeshIndexType value); |
||||
|
||||
/**
|
||||
@brief Convert a generic index type to Vulkan index type |
||||
@m_since_latest |
||||
|
||||
@see @ref meshPrimitive(), @ref vertexFormat() |
||||
*/ |
||||
MAGNUM_VK_EXPORT MeshIndexType meshIndexType(Magnum::MeshIndexType type); |
||||
|
||||
/**
|
||||
@brief Mesh |
||||
@m_since_latest |
||||
|
||||
Connects @ref MeshLayout with concrete vertex/index @ref Buffer instances and |
||||
manages related information such as vertex or instance count. |
||||
|
||||
@section Vk-Mesh-populating Populating a mesh |
||||
|
||||
Continuing from the @ref Vk-MeshLayout-usage "mesh layout setup", the @ref Mesh |
||||
gets concrete buffers bound using @ref addVertexBuffer() and vertex count |
||||
specified with @ref setCount(): |
||||
|
||||
@snippet MagnumVk.cpp Mesh-populating |
||||
|
||||
For an indexed mesh, the index buffer can be specified via @ref setIndexBuffer(). |
||||
With an index buffer present, @ref setCount() then describes count of indices, |
||||
instead of vertices: |
||||
|
||||
@snippet MagnumVk.cpp Mesh-populating-indexed |
||||
|
||||
@subsection Vk-Mesh-populating-owned Transferring buffer and layout ownership |
||||
|
||||
To simplify resource management, it's possible to have the @ref Buffer |
||||
instances owned by the @ref Mesh, as well as the @ref MeshLayout, either using |
||||
@ref std::move() or by directly passing a r-value. If a single buffer is used |
||||
for multiple bindings (for example as both a vertex and an index buffer), |
||||
perform the move last: |
||||
|
||||
@snippet MagnumVk.cpp Mesh-populating-owned |
||||
|
||||
@section Vk-Mesh-drawing Drawing a mesh |
||||
|
||||
Assuming a rasterization pipeline with the same @ref MeshLayout was bound, a |
||||
mesh can be then drawn using @ref CommandBuffer::draw(). The function takes |
||||
care of binding all buffers and executing an appropriate draw command: |
||||
|
||||
@snippet MagnumVk.cpp Mesh-drawing |
||||
|
||||
@subsection Vk-Mesh-drawing-dynamic Dynamic pipeline state |
||||
|
||||
Both the @ref MeshPrimitive set in @ref MeshLayout constructor and binding |
||||
stride set in @ref MeshLayout::addBinding() can be set as dynamic in the |
||||
pipeline using @ref DynamicRasterizationState::MeshPrimitive and |
||||
@relativeref{DynamicRasterizationState,VertexInputBindingStride}, assuming |
||||
@ref DeviceFeature::ExtendedDynamicState is supported and enabled. The |
||||
@ref CommandBuffer::draw() function then checks what dynamic state is enabled |
||||
in the currently bound pipeline and implicitly sets all dynamic states. |
||||
|
||||
Taking this to the extreme, with these two dynamic states and a dedicated |
||||
binding for each attribute you can make the pipeline accept basically any mesh |
||||
as long as just the attribute locations and types are the same --- offsets and |
||||
strided of particular attributes are then fully dynamic. |
||||
|
||||
@snippet MagnumVk.cpp Mesh-drawing-dynamic |
||||
|
||||
<b></b> |
||||
|
||||
@m_class{m-note m-success} |
||||
|
||||
@par |
||||
The performance aspect of this approach is a whole different topic, of |
||||
course. It isn't expected to be as fast as if the vertex and primitive |
||||
layout was fixed, but it *may* be faster than having to create and bind a |
||||
whole new pipeline several times over when drawing a large set of |
||||
layout-incompatible meshes. |
||||
*/ |
||||
class MAGNUM_VK_EXPORT Mesh { |
||||
public: |
||||
/**
|
||||
* @brief Construct with a reference to external @ref MeshLayout |
||||
* |
||||
* Assumes @p layout stays in scope for the whole lifetime of the |
||||
* @ref Mesh instance. |
||||
*/ |
||||
explicit Mesh(const MeshLayout& layout); |
||||
|
||||
/** @brief Construct with taking over @ref MeshLayout ownership */ |
||||
explicit Mesh(MeshLayout&& layout); |
||||
|
||||
/** @brief Copying is not allowed */ |
||||
Mesh(const Mesh&) = delete; |
||||
|
||||
/** @brief Move constructor */ |
||||
Mesh(Mesh&&) noexcept; |
||||
|
||||
/**
|
||||
* @brief Destructor |
||||
* |
||||
* If any buffers were added using @ref addVertexBuffer(UnsignedInt, Buffer&&, UnsignedLong) |
||||
* or @ref setIndexBuffer(Buffer&&, UnsignedLong, MeshIndexType), their |
||||
* owned instanced are destructed at this point. |
||||
*/ |
||||
~Mesh(); |
||||
|
||||
/** @brief Copying is not allowed */ |
||||
Mesh& operator=(const Mesh&) = delete; |
||||
|
||||
/** @brief Move assignment */ |
||||
Mesh& operator=(Mesh&&) noexcept; |
||||
|
||||
/** @brief Vertex/index count */ |
||||
UnsignedInt count() const; |
||||
|
||||
/**
|
||||
* @brief Set vertex/index count |
||||
* @return Reference to self (for method chaining) |
||||
* |
||||
* If the mesh is indexed, the value is treated as index count, |
||||
* otherwise the value is vertex count. If set to @cpp 0 @ce, no draw |
||||
* commands are issued when calling @ref CommandBuffer::draw(). |
||||
* |
||||
* @attention To prevent nothing being rendered by accident, this |
||||
* function has to be always called, even to just set the count to |
||||
* @cpp 0 @ce. |
||||
* |
||||
* @see @ref isIndexed(), @ref setInstanceCount(), |
||||
* @ref setVertexOffset(), @ref setIndexOffset() |
||||
*/ |
||||
Mesh& setCount(UnsignedInt count) { |
||||
_count = count; |
||||
return *this; |
||||
} |
||||
|
||||
/** @brief Vertex offset */ |
||||
UnsignedInt vertexOffset() const { return _vertexOffset; } |
||||
|
||||
/**
|
||||
* @brief Set vertex offset |
||||
* @return Reference to self (for method chaining) |
||||
* |
||||
* For non-indexed meshes specifies the first vertex that will be |
||||
* drawn, for indexed meshes specifies the offset added to each index. |
||||
* Default is @cpp 0 @ce. |
||||
* @see @ref isIndexed(), @ref setIndexOffset(), @ref setCount() |
||||
*/ |
||||
Mesh& setVertexOffset(UnsignedInt offset) { |
||||
_vertexOffset = offset; |
||||
return *this; |
||||
} |
||||
|
||||
/** @brief Index offset */ |
||||
UnsignedInt indexOffset() const { return _indexOffset; } |
||||
|
||||
/**
|
||||
* @brief Set index offset |
||||
* @return Reference to self (for method chaining) |
||||
* |
||||
* Expects that the mesh is indexed. Specifies the first index that |
||||
* will be drawn. Default is @cpp 0 @ce. |
||||
* @see @ref isIndexed(), @ref setVertexOffset(), @ref setCount() |
||||
*/ |
||||
Mesh& setIndexOffset(UnsignedInt offset) { |
||||
_indexOffset = offset; |
||||
return *this; |
||||
} |
||||
|
||||
/** @brief Instance count */ |
||||
UnsignedInt instanceCount() const { return _instanceCount; } |
||||
|
||||
/**
|
||||
* @brief Set instance count |
||||
* @return Reference to self (for method chaining) |
||||
* |
||||
* If set to @cpp 0 @ce, no draw commands are issued when calling |
||||
* @ref CommandBuffer::draw(). Default is @cpp 1 @ce. |
||||
* @see @ref setCount(), @ref setInstanceOffset() |
||||
*/ |
||||
Mesh& setInstanceCount(UnsignedInt count) { |
||||
_instanceCount = count; |
||||
return *this; |
||||
} |
||||
|
||||
/** @brief Instance offset */ |
||||
UnsignedInt instanceOffset() const { return _instanceOffset; } |
||||
|
||||
/**
|
||||
* @brief Set instance offset |
||||
* @return Reference to self (for method chaining) |
||||
* |
||||
* Specifies the first instance that will be drawn. Default is |
||||
* @cpp 0 @ce. |
||||
* @see @ref setInstanceCount() |
||||
*/ |
||||
Mesh& setInstanceOffset(UnsignedInt offset) { |
||||
_instanceOffset = offset; |
||||
return *this; |
||||
} |
||||
|
||||
/**
|
||||
* @brief Add a vertex buffer |
||||
* @param binding Binding corresponding to a particular |
||||
* @ref MeshLayout::addBinding() call |
||||
* @param buffer A @ref Buffer instance or a raw Vulkan buffer |
||||
* handle |
||||
* @param offset Offset into the buffer, in bytes |
||||
* @return Reference to self (for method chaining) |
||||
* |
||||
* @see @ref setCount(), @ref setVertexOffset() |
||||
*/ |
||||
Mesh& addVertexBuffer(UnsignedInt binding, VkBuffer buffer, UnsignedLong offset); |
||||
|
||||
/**
|
||||
* @brief Add a vertex buffer and take over its ownership |
||||
* @return Reference to self (for method chaining) |
||||
* |
||||
* Compared to @ref addVertexBuffer(UnsignedInt, VkBuffer, UnsignedLong) |
||||
* the @p buffer instance ownership is transferred to the class and |
||||
* thus doesn't have to be managed separately. |
||||
*/ |
||||
Mesh& addVertexBuffer(UnsignedInt binding, Buffer&& buffer, UnsignedLong offset); |
||||
|
||||
/**
|
||||
* @brief Set an index buffer |
||||
* @param buffer A @ref Buffer instance or a raw Vulkan buffer |
||||
* handle |
||||
* @param offset Offset into the buffer, in bytes |
||||
* @param indexType Index type |
||||
* @return Reference to self (for method chaining) |
||||
* |
||||
* @see @ref setCount(), @ref setIndexOffset() |
||||
*/ |
||||
Mesh& setIndexBuffer(VkBuffer buffer, UnsignedLong offset, MeshIndexType indexType); |
||||
/** @overload */ |
||||
Mesh& setIndexBuffer(VkBuffer buffer, UnsignedLong offset, Magnum::MeshIndexType indexType); |
||||
|
||||
/**
|
||||
* @brief Set an index buffer and take over its ownership |
||||
* @return Reference to self (for method chaining) |
||||
* |
||||
* Compared to @ref setIndexBuffer(VkBuffer, UnsignedLong, MeshIndexType) |
||||
* the @p buffer instance ownership is transferred to the class and |
||||
* thus doesn't have to be managed separately. |
||||
*/ |
||||
Mesh& setIndexBuffer(Buffer&& buffer, UnsignedLong offset, MeshIndexType indexType); |
||||
/** @overload */ |
||||
Mesh& setIndexBuffer(Buffer&& buffer, UnsignedLong offset, Magnum::MeshIndexType indexType); |
||||
|
||||
/** @brief Layout of this mesh */ |
||||
const MeshLayout& layout() const { return _layout; } |
||||
|
||||
/**
|
||||
* @brief Vertex buffers |
||||
* |
||||
* Has the same length as the vertex buffer binding array in |
||||
* @ref layout(), the buffers correspond to binding IDs at the same |
||||
* index. |
||||
*/ |
||||
Containers::ArrayView<const VkBuffer> vertexBuffers(); |
||||
|
||||
/**
|
||||
* @brief Vertex buffer offsets |
||||
* |
||||
* Has the same length as the vertex buffer binding array in |
||||
* @ref layout(), offsets correspond to @ref vertexBuffers() at the |
||||
* same index. |
||||
*/ |
||||
Containers::ArrayView<const UnsignedLong> vertexBufferOffsets() const; |
||||
|
||||
/**
|
||||
* @brief Vertex buffer strides |
||||
* |
||||
* Has the same length as the vertex buffer binding array in |
||||
* @ref layout(). The strides are the same as strides in the layout |
||||
* at the same index, but here in a form that's usable by |
||||
* @fn_vk{CmdBindVertexBuffers2} if |
||||
* @ref DynamicRasterizationState::VertexInputBindingStride is enabled. |
||||
*/ |
||||
Containers::ArrayView<const UnsignedLong> vertexBufferStrides() const; |
||||
|
||||
/**
|
||||
* @brief Whether the mesh is indexed |
||||
* |
||||
* The mesh is considered indexed if @ref setIndexBuffer() was called. |
||||
*/ |
||||
bool isIndexed() const; |
||||
|
||||
/**
|
||||
* @brief Index buffer |
||||
* |
||||
* Expects that the mesh is indexed. |
||||
* @see @ref isIndexed() |
||||
*/ |
||||
VkBuffer indexBuffer(); |
||||
|
||||
/**
|
||||
* @brief Index buffer offset |
||||
* |
||||
* Expects that the mesh is indexed. |
||||
* @see @ref isIndexed() |
||||
*/ |
||||
UnsignedLong indexBufferOffset() const; |
||||
|
||||
/**
|
||||
* @brief Index type |
||||
* |
||||
* Expects that the mesh is indexed. |
||||
* @see @ref isIndexed() |
||||
*/ |
||||
MeshIndexType indexType() const; |
||||
|
||||
#ifdef DOXYGEN_GENERATING_OUTPUT |
||||
private: |
||||
#endif |
||||
#ifndef CORRADE_NO_ASSERT |
||||
/* Used by CommandBuffer::draw() for a sanity assert */ |
||||
bool isCountSet() const { return _count != ~UnsignedInt{}; } |
||||
#endif |
||||
|
||||
private: |
||||
/* This is all here and not in the State struct in order to avoid
|
||||
unnecessary allocations for buffer-less meshes -- like with GL, we |
||||
want `draw(Mesh{MeshLayout{MeshPrimitive::Triangle}}.setCount(3))` |
||||
to be performant enough to not need to invent any alternatives. The |
||||
MeshLayout class does a similar thing. */ |
||||
UnsignedInt _count = ~UnsignedInt{}, |
||||
_vertexOffset = 0, |
||||
_indexOffset = 0, |
||||
_instanceCount = 1, |
||||
_instanceOffset = 0; |
||||
MeshLayout _layout; |
||||
|
||||
MAGNUM_VK_LOCAL std::size_t addVertexBufferInternal(UnsignedInt binding, VkBuffer buffer, UnsignedLong offset); |
||||
|
||||
struct State; |
||||
Containers::Pointer<State> _state; |
||||
}; |
||||
|
||||
}} |
||||
|
||||
#endif |
||||
@ -0,0 +1,278 @@
|
||||
/*
|
||||
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 <sstream> |
||||
#include <Corrade/TestSuite/Tester.h> |
||||
#include <Corrade/TestSuite/Compare/Container.h> |
||||
#include <Corrade/Utility/DebugStl.h> |
||||
|
||||
#include "Magnum/Mesh.h" |
||||
#include "Magnum/Vk/Buffer.h" |
||||
#include "Magnum/Vk/Device.h" |
||||
#include "Magnum/Vk/Mesh.h" |
||||
|
||||
namespace Magnum { namespace Vk { namespace Test { namespace { |
||||
|
||||
struct MeshTest: TestSuite::Tester { |
||||
explicit MeshTest(); |
||||
|
||||
void mapIndexType(); |
||||
void mapIndexTypeInvalid(); |
||||
|
||||
void construct(); |
||||
void countsOffsets(); |
||||
|
||||
void addVertexBuffer(); |
||||
void addVertexBufferOwned(); |
||||
void addVertexBufferNoSuchBinding(); |
||||
|
||||
template<class T> void setIndexBuffer(); |
||||
template<class T> void setIndexBufferOwned(); |
||||
|
||||
void indexPropertiesNotIndexed(); |
||||
|
||||
void debugIndexType(); |
||||
}; |
||||
|
||||
MeshTest::MeshTest() { |
||||
addTests({&MeshTest::mapIndexType, |
||||
&MeshTest::mapIndexTypeInvalid, |
||||
|
||||
&MeshTest::construct, |
||||
&MeshTest::countsOffsets, |
||||
|
||||
&MeshTest::addVertexBuffer, |
||||
&MeshTest::addVertexBufferOwned, |
||||
&MeshTest::addVertexBufferNoSuchBinding, |
||||
|
||||
&MeshTest::setIndexBuffer<MeshIndexType>, |
||||
&MeshTest::setIndexBuffer<Magnum::MeshIndexType>, |
||||
&MeshTest::setIndexBufferOwned<MeshIndexType>, |
||||
&MeshTest::setIndexBufferOwned<Magnum::MeshIndexType>, |
||||
|
||||
&MeshTest::indexPropertiesNotIndexed, |
||||
|
||||
&MeshTest::debugIndexType}); |
||||
} |
||||
|
||||
template<class> struct IndexTypeTraits; |
||||
template<> struct IndexTypeTraits<MeshIndexType> { |
||||
static const char* name() { return "MeshIndexType"; } |
||||
}; |
||||
template<> struct IndexTypeTraits<Magnum::MeshIndexType> { |
||||
static const char* name() { return "Magnum::MeshIndexType"; } |
||||
}; |
||||
|
||||
void MeshTest::mapIndexType() { |
||||
CORRADE_COMPARE(meshIndexType(Magnum::MeshIndexType::UnsignedByte), MeshIndexType::UnsignedByte); |
||||
CORRADE_COMPARE(meshIndexType(Magnum::MeshIndexType::UnsignedShort), MeshIndexType::UnsignedShort); |
||||
CORRADE_COMPARE(meshIndexType(Magnum::MeshIndexType::UnsignedInt), MeshIndexType::UnsignedInt); |
||||
} |
||||
|
||||
void MeshTest::mapIndexTypeInvalid() { |
||||
#ifdef CORRADE_NO_ASSERT |
||||
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); |
||||
#endif |
||||
|
||||
std::ostringstream out; |
||||
Error redirectError{&out}; |
||||
meshIndexType(Magnum::MeshIndexType(0x0)); |
||||
meshIndexType(Magnum::MeshIndexType(0x12)); |
||||
CORRADE_COMPARE(out.str(), |
||||
"Vk::meshIndexType(): invalid type MeshIndexType(0x0)\n" |
||||
"Vk::meshIndexType(): invalid type MeshIndexType(0x12)\n"); |
||||
} |
||||
|
||||
void MeshTest::construct() { |
||||
MeshLayout layout{MeshPrimitive::Triangles}; |
||||
layout.vkPipelineVertexInputStateCreateInfo().sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2; |
||||
layout.vkPipelineInputAssemblyStateCreateInfo().sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2; |
||||
Mesh mesh{layout}; |
||||
/* These should be copies of the original layout */ |
||||
CORRADE_COMPARE(mesh.layout().vkPipelineVertexInputStateCreateInfo().sType, VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2); |
||||
CORRADE_COMPARE(mesh.layout().vkPipelineInputAssemblyStateCreateInfo().sType, VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2); |
||||
CORRADE_COMPARE(mesh.count(), 0); |
||||
CORRADE_COMPARE(mesh.vertexOffset(), 0); |
||||
CORRADE_COMPARE(mesh.indexOffset(), 0); |
||||
CORRADE_COMPARE(mesh.instanceCount(), 1); |
||||
CORRADE_COMPARE(mesh.instanceOffset(), 0); |
||||
CORRADE_VERIFY(mesh.vertexBuffers().empty()); |
||||
CORRADE_VERIFY(mesh.vertexBufferOffsets().empty()); |
||||
CORRADE_VERIFY(mesh.vertexBufferStrides().empty()); |
||||
CORRADE_VERIFY(!mesh.isIndexed()); |
||||
} |
||||
|
||||
void MeshTest::countsOffsets() { |
||||
Mesh mesh{MeshLayout{MeshPrimitive::Triangles}}; |
||||
mesh.setCount(15) |
||||
.setVertexOffset(3) |
||||
.setIndexOffset(5) |
||||
.setInstanceCount(7) |
||||
.setInstanceOffset(9); |
||||
CORRADE_COMPARE(mesh.count(), 15); |
||||
CORRADE_COMPARE(mesh.vertexOffset(), 3); |
||||
CORRADE_COMPARE(mesh.indexOffset(), 5); |
||||
CORRADE_COMPARE(mesh.instanceCount(), 7); |
||||
CORRADE_COMPARE(mesh.instanceOffset(), 9); |
||||
} |
||||
|
||||
void MeshTest::addVertexBuffer() { |
||||
Mesh mesh{MeshLayout{MeshPrimitive::TriangleFan} |
||||
.addBinding(1, 2) |
||||
.addInstancedBinding(5, 3) |
||||
}; |
||||
CORRADE_COMPARE_AS(mesh.vertexBuffers(), Containers::arrayView({ |
||||
VkBuffer{}, VkBuffer{} |
||||
}), TestSuite::Compare::Container); |
||||
CORRADE_COMPARE_AS(mesh.vertexBufferOffsets(), Containers::arrayView<UnsignedLong>({ |
||||
0, 0 |
||||
}), TestSuite::Compare::Container); |
||||
CORRADE_COMPARE_AS(mesh.vertexBufferStrides(), Containers::arrayView<UnsignedLong>({ |
||||
0, 0 |
||||
}), TestSuite::Compare::Container); |
||||
|
||||
mesh.addVertexBuffer(5, reinterpret_cast<VkBuffer>(0xdead), 15); |
||||
CORRADE_COMPARE_AS(mesh.vertexBuffers(), Containers::arrayView({ |
||||
VkBuffer{}, reinterpret_cast<VkBuffer>(0xdead) |
||||
}), TestSuite::Compare::Container); |
||||
CORRADE_COMPARE_AS(mesh.vertexBufferOffsets(), Containers::arrayView<UnsignedLong>({ |
||||
0, 15 |
||||
}), TestSuite::Compare::Container); |
||||
CORRADE_COMPARE_AS(mesh.vertexBufferStrides(), Containers::arrayView<UnsignedLong>({ |
||||
0, 3 |
||||
}), TestSuite::Compare::Container); |
||||
|
||||
mesh.addVertexBuffer(1, reinterpret_cast<VkBuffer>(0xbeef), 37); |
||||
CORRADE_COMPARE_AS(mesh.vertexBuffers(), Containers::arrayView({ |
||||
reinterpret_cast<VkBuffer>(0xbeef), reinterpret_cast<VkBuffer>(0xdead) |
||||
}), TestSuite::Compare::Container); |
||||
CORRADE_COMPARE_AS(mesh.vertexBufferOffsets(), Containers::arrayView<UnsignedLong>({ |
||||
37, 15 |
||||
}), TestSuite::Compare::Container); |
||||
CORRADE_COMPARE_AS(mesh.vertexBufferStrides(), Containers::arrayView<UnsignedLong>({ |
||||
2, 3 |
||||
}), TestSuite::Compare::Container); |
||||
} |
||||
|
||||
void MeshTest::addVertexBufferOwned() { |
||||
Mesh mesh{MeshLayout{MeshPrimitive::TriangleFan} |
||||
.addBinding(1, 2) |
||||
.addInstancedBinding(5, 3) |
||||
}; |
||||
|
||||
Device device{NoCreate}; |
||||
Buffer a = Buffer::wrap(device, reinterpret_cast<VkBuffer>(0xdead)); |
||||
Buffer b = Buffer::wrap(device, reinterpret_cast<VkBuffer>(0xbeef)); |
||||
mesh.addVertexBuffer(5, std::move(a), 15) |
||||
.addVertexBuffer(1, std::move(b), 37); |
||||
CORRADE_VERIFY(!a.handle()); |
||||
CORRADE_VERIFY(!b.handle()); |
||||
|
||||
CORRADE_COMPARE_AS(mesh.vertexBuffers(), Containers::arrayView({ |
||||
reinterpret_cast<VkBuffer>(0xbeef), reinterpret_cast<VkBuffer>(0xdead) |
||||
}), TestSuite::Compare::Container); |
||||
CORRADE_COMPARE_AS(mesh.vertexBufferOffsets(), Containers::arrayView<UnsignedLong>({ |
||||
37, 15 |
||||
}), TestSuite::Compare::Container); |
||||
CORRADE_COMPARE_AS(mesh.vertexBufferStrides(), Containers::arrayView<UnsignedLong>({ |
||||
2, 3 |
||||
}), TestSuite::Compare::Container); |
||||
} |
||||
|
||||
void MeshTest::addVertexBufferNoSuchBinding() { |
||||
#ifdef CORRADE_NO_ASSERT |
||||
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); |
||||
#endif |
||||
|
||||
Mesh noBindings{MeshLayout{MeshPrimitive::Triangles}}; |
||||
Mesh differentBindings{MeshLayout{MeshPrimitive::Lines} |
||||
.addBinding(1, 2) |
||||
.addInstancedBinding(5, 3)}; |
||||
|
||||
std::ostringstream out; |
||||
Error redirectError{&out}; |
||||
noBindings.addVertexBuffer(2, VkBuffer{}, 0); |
||||
differentBindings.addVertexBuffer(3, Buffer{NoCreate}, 5); |
||||
CORRADE_COMPARE(out.str(), |
||||
"Vk::Mesh::addVertexBuffer(): binding 2 not present among 0 bindings in the layout\n" |
||||
"Vk::Mesh::addVertexBuffer(): binding 3 not present among 2 bindings in the layout\n"); |
||||
} |
||||
|
||||
template<class T> void MeshTest::setIndexBuffer() { |
||||
setTestCaseTemplateName(IndexTypeTraits<T>::name()); |
||||
|
||||
Mesh mesh{MeshLayout{MeshPrimitive::Triangles}}; |
||||
CORRADE_VERIFY(!mesh.isIndexed()); |
||||
|
||||
mesh.setIndexBuffer(reinterpret_cast<VkBuffer>(0xdead), 15, T::UnsignedByte); |
||||
CORRADE_VERIFY(mesh.isIndexed()); |
||||
CORRADE_COMPARE(mesh.indexBuffer(), reinterpret_cast<VkBuffer>(0xdead)); |
||||
CORRADE_COMPARE(mesh.indexBufferOffset(), 15); |
||||
CORRADE_COMPARE(mesh.indexType(), MeshIndexType::UnsignedByte); |
||||
} |
||||
|
||||
template<class T> void MeshTest::setIndexBufferOwned() { |
||||
setTestCaseTemplateName(IndexTypeTraits<T>::name()); |
||||
|
||||
Device device{NoCreate}; |
||||
Buffer a = Buffer::wrap(device, reinterpret_cast<VkBuffer>(0xdead)); |
||||
|
||||
Mesh mesh{MeshLayout{MeshPrimitive::Triangles}}; |
||||
mesh.setIndexBuffer(std::move(a), 15, T::UnsignedByte); |
||||
CORRADE_VERIFY(!a.handle()); |
||||
CORRADE_VERIFY(mesh.isIndexed()); |
||||
CORRADE_COMPARE(mesh.indexBuffer(), reinterpret_cast<VkBuffer>(0xdead)); |
||||
CORRADE_COMPARE(mesh.indexBufferOffset(), 15); |
||||
CORRADE_COMPARE(mesh.indexType(), MeshIndexType::UnsignedByte); |
||||
} |
||||
|
||||
void MeshTest::indexPropertiesNotIndexed() { |
||||
#ifdef CORRADE_NO_ASSERT |
||||
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); |
||||
#endif |
||||
|
||||
Mesh mesh{MeshLayout{MeshPrimitive::Triangles}}; |
||||
CORRADE_VERIFY(!mesh.isIndexed()); |
||||
|
||||
std::ostringstream out; |
||||
Error redirectError{&out}; |
||||
mesh.indexBuffer(); |
||||
mesh.indexBufferOffset(); |
||||
mesh.indexType(); |
||||
CORRADE_COMPARE(out.str(), |
||||
"Vk::Mesh::indexBuffer(): the mesh is not indexed\n" |
||||
"Vk::Mesh::indexBufferOffset(): the mesh is not indexed\n" |
||||
"Vk::Mesh::indexType(): the mesh is not indexed\n"); |
||||
} |
||||
|
||||
void MeshTest::debugIndexType() { |
||||
std::ostringstream out; |
||||
Debug{&out} << MeshIndexType::UnsignedShort << MeshIndexType(-10007655); |
||||
CORRADE_COMPARE(out.str(), "Vk::MeshIndexType::UnsignedShort Vk::MeshIndexType(-10007655)\n"); |
||||
} |
||||
|
||||
}}}} |
||||
|
||||
CORRADE_TEST_MAIN(Magnum::Vk::Test::MeshTest) |
||||
@ -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,35 @@
|
||||
OpCapability Shader |
||||
OpMemoryModel Logical GLSL450 |
||||
OpEntryPoint Vertex %ver "ver" %position %gl_Position |
||||
OpEntryPoint Fragment %fra "fra" %fragmentColor |
||||
OpExecutionMode %fra OriginUpperLeft |
||||
OpDecorate %position Location 0 |
||||
OpDecorate %gl_Position BuiltIn Position |
||||
OpDecorate %fragmentColor 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 |
||||
%_ptr_Output_v4float = OpTypePointer Output %v4float |
||||
%gl_Position = OpVariable %_ptr_Output_v4float Output |
||||
%fragmentColor = OpVariable %_ptr_Output_v4float Output |
||||
|
||||
%9 = OpConstant %float 0 |
||||
%11 = OpConstant %float 1 |
||||
%12 = OpConstantComposite %v4float %11 %9 %9 %11 |
||||
|
||||
%ver = OpFunction %void None %10 |
||||
%ver_ = OpLabel |
||||
%16 = OpLoad %v4float %position |
||||
OpStore %gl_Position %16 |
||||
OpReturn |
||||
OpFunctionEnd |
||||
|
||||
%fra = OpFunction %void None %10 |
||||
%fra_ = OpLabel |
||||
OpStore %fragmentColor %12 |
||||
OpReturn |
||||
OpFunctionEnd |
||||
Binary file not shown.
Binary file not shown.
@ -0,0 +1,30 @@
|
||||
OpCapability Shader |
||||
OpMemoryModel Logical GLSL450 |
||||
OpEntryPoint Vertex %ver "ver" %gl_Position |
||||
OpEntryPoint Fragment %fra "fra" %fragmentColor |
||||
OpExecutionMode %fra OriginUpperLeft |
||||
OpDecorate %gl_Position BuiltIn Position |
||||
OpDecorate %fragmentColor Location 0 |
||||
|
||||
%void = OpTypeVoid |
||||
%10 = OpTypeFunction %void |
||||
%float = OpTypeFloat 32 |
||||
%v4float = OpTypeVector %float 4 |
||||
%_ptr_Output_v4float = OpTypePointer Output %v4float |
||||
%gl_Position = OpVariable %_ptr_Output_v4float Output |
||||
%fragmentColor = OpVariable %_ptr_Output_v4float Output |
||||
|
||||
%9 = OpConstant %float 0 |
||||
%11 = OpConstantComposite %v4float %9 %9 %9 %9 |
||||
|
||||
%ver = OpFunction %void None %10 |
||||
%ver_ = OpLabel |
||||
OpStore %gl_Position %11 |
||||
OpReturn |
||||
OpFunctionEnd |
||||
|
||||
%fra = OpFunction %void None %10 |
||||
%fra_ = OpLabel |
||||
OpStore %fragmentColor %11 |
||||
OpReturn |
||||
OpFunctionEnd |
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,40 @@
|
||||
OpCapability Shader |
||||
OpMemoryModel Logical GLSL450 |
||||
OpEntryPoint Vertex %ver "ver" %position %color %gl_Position %interpolatedColorOut |
||||
OpEntryPoint Fragment %fra "fra" %interpolatedColorIn %fragmentColor |
||||
OpExecutionMode %fra OriginUpperLeft |
||||
|
||||
OpDecorate %gl_Position BuiltIn Position |
||||
OpDecorate %position Location 0 |
||||
OpDecorate %color Location 1 |
||||
OpDecorate %interpolatedColorIn Location 0 |
||||
OpDecorate %interpolatedColorOut Location 0 |
||||
OpDecorate %fragmentColor 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 |
||||
%_ptr_Output_v4float = OpTypePointer Output %v4float |
||||
%gl_Position = OpVariable %_ptr_Output_v4float Output |
||||
%interpolatedColorOut = OpVariable %_ptr_Output_v4float Output |
||||
%interpolatedColorIn = OpVariable %_ptr_Input_v4float Input |
||||
%fragmentColor = OpVariable %_ptr_Output_v4float Output |
||||
|
||||
%ver = OpFunction %void None %10 |
||||
%ver_ = OpLabel |
||||
%16 = OpLoad %v4float %position |
||||
%17 = OpLoad %v4float %color |
||||
OpStore %gl_Position %16 |
||||
OpStore %interpolatedColorOut %17 |
||||
OpReturn |
||||
OpFunctionEnd |
||||
|
||||
%fra = OpFunction %void None %10 |
||||
%fra_ = OpLabel |
||||
%19 = OpLoad %v4float %interpolatedColorIn |
||||
OpStore %fragmentColor %19 |
||||
OpReturn |
||||
OpFunctionEnd |
||||
Binary file not shown.
@ -0,0 +1,878 @@
|
||||
/*
|
||||
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 <sstream> |
||||
#include <Corrade/Containers/Array.h> |
||||
#include <Corrade/Containers/StringView.h> |
||||
#include <Corrade/PluginManager/Manager.h> |
||||
#include <Corrade/Utility/Algorithms.h> |
||||
#include <Corrade/Utility/DebugStl.h> |
||||
#include <Corrade/Utility/Directory.h> |
||||
|
||||
#include "Magnum/ImageView.h" |
||||
#include "Magnum/PixelFormat.h" |
||||
#include "Magnum/DebugTools/CompareImage.h" |
||||
#include "Magnum/Math/Color.h" |
||||
#include "Magnum/Math/Range.h" |
||||
#include "Magnum/Trade/AbstractImporter.h" |
||||
#include "Magnum/Vk/BufferCreateInfo.h" |
||||
#include "Magnum/Vk/CommandBuffer.h" |
||||
#include "Magnum/Vk/CommandPoolCreateInfo.h" |
||||
#include "Magnum/Vk/DeviceCreateInfo.h" |
||||
#include "Magnum/Vk/DeviceFeatures.h" |
||||
#include "Magnum/Vk/DeviceProperties.h" |
||||
#include "Magnum/Vk/ExtensionProperties.h" |
||||
#include "Magnum/Vk/Extensions.h" |
||||
#include "Magnum/Vk/Fence.h" |
||||
#include "Magnum/Vk/FramebufferCreateInfo.h" |
||||
#include "Magnum/Vk/ImageCreateInfo.h" |
||||
#include "Magnum/Vk/ImageViewCreateInfo.h" |
||||
#include "Magnum/Vk/Mesh.h" |
||||
#include "Magnum/Vk/PipelineLayoutCreateInfo.h" |
||||
#include "Magnum/Vk/PixelFormat.h" |
||||
#include "Magnum/Vk/RasterizationPipelineCreateInfo.h" |
||||
#include "Magnum/Vk/RenderPassCreateInfo.h" |
||||
#include "Magnum/Vk/ShaderCreateInfo.h" |
||||
#include "Magnum/Vk/ShaderSet.h" |
||||
#include "Magnum/Vk/VertexFormat.h" |
||||
#include "Magnum/Vk/VulkanTester.h" |
||||
|
||||
#include "configure.h" |
||||
|
||||
namespace Magnum { namespace Vk { namespace Test { namespace { |
||||
|
||||
struct MeshVkTest: VulkanTester { |
||||
explicit MeshVkTest(); |
||||
|
||||
void setup(Device& device); |
||||
void setup() { setup(device()); } |
||||
void setupRobustness2(); |
||||
void setupExtendedDynamicState(); |
||||
void teardown(); |
||||
|
||||
void cmdDraw(); |
||||
void cmdDrawIndexed(); |
||||
void cmdDrawTwoAttributes(); |
||||
void cmdDrawTwoAttributesTwoBindings(); |
||||
void cmdDrawNullBindingRobustness2(); |
||||
void cmdDrawZeroCount(); |
||||
void cmdDrawNoCountSet(); |
||||
|
||||
void cmdDrawDynamicPrimitive(); |
||||
void cmdDrawDynamicStride(); |
||||
void cmdDrawDynamicStrideInsufficientImplementation(); |
||||
|
||||
Queue _queue{NoCreate}; |
||||
Device _deviceRobustness2{NoCreate}, _deviceExtendedDynamicState{NoCreate}; |
||||
CommandPool _pool{NoCreate}; |
||||
Image _color{NoCreate}; |
||||
RenderPass _renderPass{NoCreate}; |
||||
ImageView _colorView{NoCreate}; |
||||
Framebuffer _framebuffer{NoCreate}; |
||||
PipelineLayout _pipelineLayout{NoCreate}; |
||||
Buffer _pixels{NoCreate}; |
||||
|
||||
private: |
||||
PluginManager::Manager<Trade::AbstractImporter> _manager{"nonexistent"}; |
||||
}; |
||||
|
||||
using namespace Containers::Literals; |
||||
using namespace Math::Literals; |
||||
|
||||
const struct { |
||||
const char* name; |
||||
UnsignedInt count; |
||||
UnsignedInt instanceCount; |
||||
} CmdDrawZeroCountData[] { |
||||
{"zero elements", 0, 1}, |
||||
{"zero instances", 4, 0} |
||||
}; |
||||
|
||||
MeshVkTest::MeshVkTest() { |
||||
addTests({&MeshVkTest::cmdDraw, |
||||
&MeshVkTest::cmdDrawIndexed, |
||||
&MeshVkTest::cmdDrawTwoAttributes, |
||||
&MeshVkTest::cmdDrawTwoAttributesTwoBindings}, |
||||
&MeshVkTest::setup, |
||||
&MeshVkTest::teardown); |
||||
|
||||
addTests({&MeshVkTest::cmdDrawNullBindingRobustness2}, |
||||
&MeshVkTest::setupRobustness2, |
||||
&MeshVkTest::teardown); |
||||
|
||||
addInstancedTests({&MeshVkTest::cmdDrawZeroCount}, |
||||
Containers::arraySize(CmdDrawZeroCountData), |
||||
&MeshVkTest::setup, |
||||
&MeshVkTest::teardown); |
||||
|
||||
addTests({&MeshVkTest::cmdDrawNoCountSet}, |
||||
&MeshVkTest::setup, |
||||
&MeshVkTest::teardown); |
||||
|
||||
addTests({&MeshVkTest::cmdDrawDynamicPrimitive, |
||||
&MeshVkTest::cmdDrawDynamicStride}, |
||||
&MeshVkTest::setupExtendedDynamicState, |
||||
&MeshVkTest::teardown); |
||||
|
||||
addTests({&MeshVkTest::cmdDrawDynamicStrideInsufficientImplementation}, |
||||
&MeshVkTest::setup, |
||||
&MeshVkTest::teardown); |
||||
|
||||
/* Load the plugins directly from the build tree. Otherwise they're either
|
||||
static and already loaded or not present in the build tree */ |
||||
#ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME |
||||
CORRADE_INTERNAL_ASSERT_OUTPUT(_manager.load(ANYIMAGEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); |
||||
#endif |
||||
#ifdef TGAIMPORTER_PLUGIN_FILENAME |
||||
CORRADE_INTERNAL_ASSERT_OUTPUT(_manager.load(TGAIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); |
||||
#endif |
||||
} |
||||
|
||||
void MeshVkTest::setup(Device& device) { |
||||
_pool = CommandPool{device, CommandPoolCreateInfo{ |
||||
device.properties().pickQueueFamily(QueueFlag::Graphics)}}; |
||||
_color = Image{device, ImageCreateInfo2D{ |
||||
ImageUsage::ColorAttachment|ImageUsage::TransferSource, |
||||
PixelFormat::RGBA8Srgb, {32, 32}, 1 |
||||
}, Vk::MemoryFlag::DeviceLocal}; |
||||
_renderPass = RenderPass{device, RenderPassCreateInfo{} |
||||
.setAttachments({AttachmentDescription{ |
||||
_color.format(), |
||||
AttachmentLoadOperation::Clear, |
||||
AttachmentStoreOperation::Store, |
||||
ImageLayout::Undefined, |
||||
ImageLayout::TransferSource |
||||
}}) |
||||
.addSubpass(SubpassDescription{}.setColorAttachments({ |
||||
AttachmentReference{0, ImageLayout::ColorAttachment} |
||||
})) |
||||
/* So the color data are visible for the transfer */ |
||||
.setDependencies({SubpassDependency{ |
||||
0, SubpassDependency::External, |
||||
PipelineStage::ColorAttachmentOutput, |
||||
PipelineStage::Transfer, |
||||
Access::ColorAttachmentWrite, |
||||
Access::TransferRead |
||||
}}) |
||||
}; |
||||
_colorView = ImageView{device, ImageViewCreateInfo2D{_color}}; |
||||
_framebuffer = Framebuffer{device, FramebufferCreateInfo{_renderPass, { |
||||
_colorView |
||||
}, {32, 32}}}; |
||||
_pipelineLayout = PipelineLayout{device, PipelineLayoutCreateInfo{}}; |
||||
_pixels = Buffer{device, BufferCreateInfo{ |
||||
BufferUsage::TransferDestination, 32*32*4 |
||||
}, Vk::MemoryFlag::HostVisible}; |
||||
} |
||||
|
||||
void MeshVkTest::setupRobustness2() { |
||||
DeviceProperties properties = pickDevice(instance()); |
||||
/* If the extension / feature isn't supported, do nothing */ |
||||
if(!properties.enumerateExtensionProperties().isSupported<Extensions::EXT::robustness2>() || |
||||
!(properties.features() & DeviceFeature::NullDescriptor)) |
||||
return; |
||||
|
||||
/* Create the device only if not already, to avoid spamming the output */ |
||||
if(!_deviceRobustness2.handle()) _deviceRobustness2.create(instance(), DeviceCreateInfo{std::move(properties)} |
||||
.addQueues(QueueFlag::Graphics, {0.0f}, {_queue}) |
||||
.addEnabledExtensions<Extensions::EXT::robustness2>() |
||||
.setEnabledFeatures(DeviceFeature::NullDescriptor) |
||||
); |
||||
|
||||
setup(_deviceRobustness2); |
||||
} |
||||
|
||||
void MeshVkTest::setupExtendedDynamicState() { |
||||
DeviceProperties properties = pickDevice(instance()); |
||||
/* If the extension / feature isn't supported, do nothing */ |
||||
if(!properties.enumerateExtensionProperties().isSupported<Extensions::EXT::extended_dynamic_state>() || |
||||
!(properties.features() & DeviceFeature::ExtendedDynamicState)) |
||||
return; |
||||
|
||||
/* Create the device only if not already, to avoid spamming the output */ |
||||
if(!_deviceExtendedDynamicState.handle()) _deviceExtendedDynamicState.create(instance(), DeviceCreateInfo{std::move(properties)} |
||||
.addQueues(QueueFlag::Graphics, {0.0f}, {_queue}) |
||||
.addEnabledExtensions<Extensions::EXT::extended_dynamic_state>() |
||||
.setEnabledFeatures(DeviceFeature::ExtendedDynamicState) |
||||
); |
||||
|
||||
setup(_deviceExtendedDynamicState); |
||||
} |
||||
|
||||
void MeshVkTest::teardown() { |
||||
_pool = CommandPool{NoCreate}; |
||||
_renderPass = RenderPass{NoCreate}; |
||||
_color = Image{NoCreate}; |
||||
_colorView = ImageView{NoCreate}; |
||||
_framebuffer = Framebuffer{NoCreate}; |
||||
_pipelineLayout = PipelineLayout{NoCreate}; |
||||
_pixels = Buffer{NoCreate}; |
||||
} |
||||
|
||||
const struct Quad { |
||||
Vector3 position; |
||||
Vector3 color; |
||||
} QuadData[] { |
||||
{{-0.5f, -0.5f, 0.0f}, 0xff0000_srgbf}, |
||||
{{ 0.5f, -0.5f, 0.0f}, 0x00ff00_srgbf}, |
||||
{{-0.5f, 0.5f, 0.0f}, 0x0000ff_srgbf}, |
||||
{{ 0.5f, 0.5f, 0.0f}, 0xffffff_srgbf} |
||||
}; |
||||
|
||||
constexpr UnsignedShort QuadIndexData[] { |
||||
0, 1, 2, 2, 1, 3 |
||||
}; |
||||
|
||||
void MeshVkTest::cmdDraw() { |
||||
/* This is the most simple binding (no offsets, single attribute, single
|
||||
buffer) to test the basic workflow. The cmdDrawIndexed() test and others |
||||
pile on the complexity, but when everything goes wrong it's good to have |
||||
a simple test case. */ |
||||
|
||||
Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip} |
||||
.addBinding(0, sizeof(Vector3)) |
||||
.addAttribute(0, 0, VertexFormat::Vector3, 0) |
||||
}; |
||||
{ |
||||
Buffer buffer{device(), BufferCreateInfo{ |
||||
BufferUsage::VertexBuffer, |
||||
sizeof(Vector3)*4 |
||||
}, MemoryFlag::HostVisible}; |
||||
/** @todo ffs fucking casts!!! */ |
||||
Utility::copy( |
||||
Containers::stridedArrayView(QuadData).slice(&Quad::position), |
||||
Containers::arrayCast<Vector3>(Containers::arrayView(buffer.dedicatedMemory().map()))); |
||||
mesh.addVertexBuffer(0, std::move(buffer), 0) |
||||
.setCount(4); |
||||
} |
||||
|
||||
Shader shader{device(), ShaderCreateInfo{ |
||||
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.spv")) |
||||
}}; |
||||
|
||||
ShaderSet shaderSet; |
||||
shaderSet |
||||
.addShader(ShaderStage::Vertex, shader, "ver"_s) |
||||
.addShader(ShaderStage::Fragment, shader, "fra"_s); |
||||
|
||||
Pipeline pipeline{device(), RasterizationPipelineCreateInfo{ |
||||
shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1} |
||||
.setViewport({{}, Vector2{_framebuffer.size().xy()}}) |
||||
}; |
||||
|
||||
CommandBuffer cmd = _pool.allocate(); |
||||
cmd.begin() |
||||
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} |
||||
.clearColor(0, 0x1f1f1f_srgbf) |
||||
) |
||||
.bindPipeline(pipeline) |
||||
.draw(mesh) |
||||
.endRenderPass() |
||||
.copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, { |
||||
Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}} |
||||
}}) |
||||
.pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, { |
||||
{Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels} |
||||
}) |
||||
.end(); |
||||
|
||||
queue().submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait(); |
||||
|
||||
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || |
||||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) |
||||
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); |
||||
|
||||
CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm, |
||||
_framebuffer.size().xy(), |
||||
_pixels.dedicatedMemory().mapRead()}), |
||||
Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.tga"), |
||||
DebugTools::CompareImageToFile{_manager}); |
||||
} |
||||
|
||||
void MeshVkTest::cmdDrawIndexed() { |
||||
Mesh mesh{MeshLayout{MeshPrimitive::Triangles} |
||||
.addBinding(0, sizeof(Vector3)) |
||||
.addAttribute(0, 0, VertexFormat::Vector3, 0) |
||||
}; |
||||
{ |
||||
Buffer buffer{device(), BufferCreateInfo{ |
||||
BufferUsage::VertexBuffer|BufferUsage::IndexBuffer, |
||||
/* Artifical offset at the beginning to test that the offset is
|
||||
used correctly in both cases */ |
||||
32 + 12*4 + sizeof(QuadIndexData) |
||||
}, MemoryFlag::HostVisible}; |
||||
Containers::Array<char, MemoryMapDeleter> data = buffer.dedicatedMemory().map(); |
||||
/** @todo ffs fucking casts!!! */ |
||||
Utility::copy(Containers::stridedArrayView(QuadData).slice(&Quad::position), |
||||
Containers::arrayCast<Vector3>(data.slice(32, 32 + 12*4))); |
||||
Utility::copy(Containers::arrayCast<const char>(QuadIndexData), |
||||
Containers::stridedArrayView(data).suffix(32 + 12*4)); |
||||
mesh.addVertexBuffer(0, buffer, 32) |
||||
.setIndexBuffer(std::move(buffer), 32 + 12*4, MeshIndexType::UnsignedShort) |
||||
.setCount(6); |
||||
} |
||||
|
||||
Shader shader{device(), ShaderCreateInfo{ |
||||
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.spv")) |
||||
}}; |
||||
|
||||
ShaderSet shaderSet; |
||||
shaderSet |
||||
.addShader(ShaderStage::Vertex, shader, "ver"_s) |
||||
.addShader(ShaderStage::Fragment, shader, "fra"_s); |
||||
|
||||
Pipeline pipeline{device(), RasterizationPipelineCreateInfo{ |
||||
shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1} |
||||
.setViewport({{}, Vector2{_framebuffer.size().xy()}}) |
||||
}; |
||||
|
||||
CommandBuffer cmd = _pool.allocate(); |
||||
cmd.begin() |
||||
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} |
||||
.clearColor(0, 0x1f1f1f_srgbf) |
||||
) |
||||
.bindPipeline(pipeline) |
||||
.draw(mesh) |
||||
.endRenderPass() |
||||
.copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, { |
||||
Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}} |
||||
}}) |
||||
.pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, { |
||||
{Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels} |
||||
}) |
||||
.end(); |
||||
|
||||
queue().submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait(); |
||||
|
||||
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || |
||||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) |
||||
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); |
||||
|
||||
CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm, |
||||
_framebuffer.size().xy(), |
||||
_pixels.dedicatedMemory().mapRead()}), |
||||
Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.tga"), |
||||
DebugTools::CompareImageToFile{_manager}); |
||||
} |
||||
|
||||
void MeshVkTest::cmdDrawTwoAttributes() { |
||||
Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip} |
||||
.addBinding(0, sizeof(Quad)) |
||||
.addAttribute(0, 0, VertexFormat::Vector3, offsetof(Quad, position)) |
||||
.addAttribute(1, 0, VertexFormat::Vector3, offsetof(Quad, color)) |
||||
}; |
||||
{ |
||||
Buffer buffer{device(), BufferCreateInfo{ |
||||
BufferUsage::VertexBuffer, |
||||
sizeof(QuadData) |
||||
}, MemoryFlag::HostVisible}; |
||||
/** @todo ffs fucking casts!!! */ |
||||
Utility::copy(Containers::arrayCast<const char>(QuadData), |
||||
Containers::stridedArrayView(buffer.dedicatedMemory().map())); |
||||
mesh.addVertexBuffer(0, std::move(buffer), 0) |
||||
.setCount(4); |
||||
} |
||||
|
||||
Shader shader{device(), ShaderCreateInfo{ |
||||
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/vertexcolor.spv")) |
||||
}}; |
||||
|
||||
ShaderSet shaderSet; |
||||
shaderSet |
||||
.addShader(ShaderStage::Vertex, shader, "ver"_s) |
||||
.addShader(ShaderStage::Fragment, shader, "fra"_s); |
||||
|
||||
Pipeline pipeline{device(), RasterizationPipelineCreateInfo{ |
||||
shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1} |
||||
.setViewport({{}, Vector2{_framebuffer.size().xy()}}) |
||||
}; |
||||
|
||||
CommandBuffer cmd = _pool.allocate(); |
||||
cmd.begin() |
||||
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} |
||||
.clearColor(0, 0x1f1f1f_srgbf) |
||||
) |
||||
.bindPipeline(pipeline) |
||||
.draw(mesh) |
||||
.endRenderPass() |
||||
.copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, { |
||||
Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}} |
||||
}}) |
||||
.pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, { |
||||
{Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels} |
||||
}) |
||||
.end(); |
||||
|
||||
queue().submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait(); |
||||
|
||||
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || |
||||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) |
||||
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); |
||||
|
||||
CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm, |
||||
_framebuffer.size().xy(), |
||||
_pixels.dedicatedMemory().mapRead()}), |
||||
Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/vertexcolor.tga"), |
||||
/* ARM Mali (Android) has some minor off-by-one differences */ |
||||
(DebugTools::CompareImageToFile{_manager, 0.5f, 0.012f})); |
||||
} |
||||
|
||||
void MeshVkTest::cmdDrawTwoAttributesTwoBindings() { |
||||
Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip} |
||||
.addBinding(0, sizeof(Vector3)) |
||||
.addBinding(1, sizeof(Vector3)) |
||||
.addAttribute(0, 0, VertexFormat::Vector3, 0) |
||||
.addAttribute(1, 1, VertexFormat::Vector3, 0) |
||||
}; |
||||
{ |
||||
Buffer positions{device(), BufferCreateInfo{ |
||||
BufferUsage::VertexBuffer, |
||||
sizeof(Vector3)*4 |
||||
}, MemoryFlag::HostVisible}; |
||||
Buffer colors{device(), BufferCreateInfo{ |
||||
BufferUsage::VertexBuffer, |
||||
sizeof(Vector3)*4 |
||||
}, MemoryFlag::HostVisible}; |
||||
/** @todo ffs fucking casts!!! */ |
||||
Utility::copy(Containers::stridedArrayView(QuadData).slice(&Quad::position), |
||||
Containers::arrayCast<Vector3>(Containers::arrayView(positions.dedicatedMemory().map()))); |
||||
Utility::copy(Containers::stridedArrayView(QuadData).slice(&Quad::color), |
||||
Containers::arrayCast<Vector3>(Containers::arrayView(colors.dedicatedMemory().map()))); |
||||
mesh.addVertexBuffer(0, std::move(positions), 0) |
||||
.addVertexBuffer(1, std::move(colors), 0) |
||||
.setCount(4); |
||||
} |
||||
|
||||
Shader shader{device(), ShaderCreateInfo{ |
||||
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/vertexcolor.spv")) |
||||
}}; |
||||
|
||||
ShaderSet shaderSet; |
||||
shaderSet |
||||
.addShader(ShaderStage::Vertex, shader, "ver"_s) |
||||
.addShader(ShaderStage::Fragment, shader, "fra"_s); |
||||
|
||||
Pipeline pipeline{device(), RasterizationPipelineCreateInfo{ |
||||
shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1} |
||||
.setViewport({{}, Vector2{_framebuffer.size().xy()}}) |
||||
}; |
||||
|
||||
CommandBuffer cmd = _pool.allocate(); |
||||
cmd.begin() |
||||
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} |
||||
.clearColor(0, 0x1f1f1f_srgbf) |
||||
) |
||||
.bindPipeline(pipeline) |
||||
.draw(mesh) |
||||
.endRenderPass() |
||||
.copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, { |
||||
Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}} |
||||
}}) |
||||
.pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, { |
||||
{Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels} |
||||
}) |
||||
.end(); |
||||
|
||||
queue().submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait(); |
||||
|
||||
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || |
||||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) |
||||
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); |
||||
|
||||
CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm, |
||||
_framebuffer.size().xy(), |
||||
_pixels.dedicatedMemory().mapRead()}), |
||||
Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/vertexcolor.tga"), |
||||
/* ARM Mali (Android) has some minor off-by-one differences */ |
||||
(DebugTools::CompareImageToFile{_manager, 0.5f, 0.012f})); |
||||
} |
||||
|
||||
void MeshVkTest::cmdDrawNullBindingRobustness2() { |
||||
if(!(_deviceRobustness2.enabledFeatures() & DeviceFeature::NullDescriptor)) |
||||
CORRADE_SKIP("DeviceFeature::NullDescriptor not supported, can't test."); |
||||
|
||||
Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip} |
||||
.addBinding(0, sizeof(Vector3)) |
||||
.addBinding(1, sizeof(Vector3)) |
||||
.addAttribute(0, 0, VertexFormat::Vector3, 0) |
||||
.addAttribute(1, 1, VertexFormat::Vector3, 0) |
||||
}; |
||||
{ |
||||
Buffer positions{_deviceRobustness2, BufferCreateInfo{ |
||||
BufferUsage::VertexBuffer, |
||||
sizeof(Vector3)*4 |
||||
}, MemoryFlag::HostVisible}; |
||||
/** @todo ffs fucking casts!!! */ |
||||
Utility::copy(Containers::stridedArrayView(QuadData).slice(&Quad::position), |
||||
Containers::arrayCast<Vector3>(Containers::arrayView(positions.dedicatedMemory().map()))); |
||||
mesh.addVertexBuffer(0, std::move(positions), 0) |
||||
.setCount(4); |
||||
} |
||||
|
||||
Shader shader{_deviceRobustness2, ShaderCreateInfo{ |
||||
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/vertexcolor.spv")) |
||||
}}; |
||||
|
||||
ShaderSet shaderSet; |
||||
shaderSet |
||||
.addShader(ShaderStage::Vertex, shader, "ver"_s) |
||||
.addShader(ShaderStage::Fragment, shader, "fra"_s); |
||||
|
||||
Pipeline pipeline{_deviceRobustness2, RasterizationPipelineCreateInfo{ |
||||
shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1} |
||||
.setViewport({{}, Vector2{_framebuffer.size().xy()}}) |
||||
}; |
||||
|
||||
CommandBuffer cmd = _pool.allocate(); |
||||
cmd.begin() |
||||
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} |
||||
.clearColor(0, 0x1f1f1f_srgbf) |
||||
) |
||||
.bindPipeline(pipeline) |
||||
.draw(mesh) |
||||
.endRenderPass() |
||||
.copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, { |
||||
Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}} |
||||
}}) |
||||
.pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, { |
||||
{Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels} |
||||
}) |
||||
.end(); |
||||
|
||||
_queue.submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait(); |
||||
|
||||
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || |
||||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) |
||||
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); |
||||
|
||||
CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm, |
||||
_framebuffer.size().xy(), |
||||
_pixels.dedicatedMemory().mapRead()}), |
||||
Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/nullcolor.tga"), |
||||
/* ARM Mali (Android) has some minor off-by-one differences */ |
||||
(DebugTools::CompareImageToFile{_manager})); |
||||
} |
||||
|
||||
void MeshVkTest::cmdDrawZeroCount() { |
||||
auto&& data = CmdDrawZeroCountData[testCaseInstanceId()]; |
||||
setTestCaseDescription(data.name); |
||||
|
||||
Mesh mesh{MeshLayout{MeshPrimitive::Triangles} |
||||
.addBinding(0, sizeof(Vector3)) |
||||
.addAttribute(0, 0, VertexFormat::Vector3, 0) |
||||
}; |
||||
/* Deliberately not setting up any buffer -- the draw() should be a no-op
|
||||
and thus no draw validation (and error messages) should happen */ |
||||
mesh.setCount(data.count) |
||||
.setInstanceCount(data.instanceCount); |
||||
|
||||
Shader shader{device(), ShaderCreateInfo{ |
||||
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.spv")) |
||||
}}; |
||||
|
||||
ShaderSet shaderSet; |
||||
shaderSet |
||||
.addShader(ShaderStage::Vertex, shader, "ver"_s) |
||||
.addShader(ShaderStage::Fragment, shader, "fra"_s); |
||||
|
||||
Pipeline pipeline{device(), RasterizationPipelineCreateInfo{ |
||||
shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1} |
||||
.setViewport({{}, Vector2{_framebuffer.size().xy()}}) |
||||
}; |
||||
|
||||
CommandBuffer cmd = _pool.allocate(); |
||||
cmd.begin() |
||||
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} |
||||
.clearColor(0, 0x1f1f1f_srgbf) |
||||
) |
||||
.bindPipeline(pipeline) |
||||
.draw(mesh) |
||||
.endRenderPass() |
||||
.copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, { |
||||
Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}} |
||||
}}) |
||||
.pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, { |
||||
{Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels} |
||||
}) |
||||
.end(); |
||||
|
||||
queue().submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait(); |
||||
|
||||
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || |
||||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) |
||||
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); |
||||
|
||||
CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm, |
||||
_framebuffer.size().xy(), |
||||
_pixels.dedicatedMemory().mapRead()}), |
||||
Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/noop.tga"), |
||||
DebugTools::CompareImageToFile{_manager}); |
||||
} |
||||
|
||||
void MeshVkTest::cmdDrawNoCountSet() { |
||||
#ifdef CORRADE_NO_ASSERT |
||||
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); |
||||
#endif |
||||
|
||||
Mesh mesh{MeshLayout{MeshPrimitive::Triangles}}; |
||||
|
||||
Shader shader{device(), ShaderCreateInfo{ |
||||
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/noop.spv")) |
||||
}}; |
||||
|
||||
ShaderSet shaderSet; |
||||
shaderSet |
||||
.addShader(ShaderStage::Vertex, shader, "ver"_s) |
||||
.addShader(ShaderStage::Fragment, shader, "fra"_s); |
||||
|
||||
Pipeline pipeline{device(), RasterizationPipelineCreateInfo{ |
||||
shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1} |
||||
.setViewport({{}, Vector2{_framebuffer.size().xy()}}) |
||||
}; |
||||
|
||||
CommandBuffer cmd = _pool.allocate(); |
||||
cmd.begin() |
||||
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} |
||||
.clearColor(0, 0x1f1f1f_srgbf) |
||||
) |
||||
.bindPipeline(pipeline); |
||||
|
||||
std::ostringstream out; |
||||
Error redirectError{&out}; |
||||
cmd.draw(mesh); |
||||
CORRADE_COMPARE(out.str(), "Vk::CommandBuffer::draw(): Mesh::setCount() was never called, probably a mistake?\n"); |
||||
} |
||||
|
||||
void MeshVkTest::cmdDrawDynamicPrimitive() { |
||||
if(!(_deviceExtendedDynamicState.enabledFeatures() & DeviceFeature::ExtendedDynamicState)) |
||||
CORRADE_SKIP("DeviceFeature::ExtendedDynamicState not supported, can't test."); |
||||
|
||||
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || |
||||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) |
||||
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); |
||||
|
||||
Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip} |
||||
.addBinding(0, sizeof(Vector3)) |
||||
.addAttribute(0, 0, VertexFormat::Vector3, 0) |
||||
}; |
||||
{ |
||||
Buffer buffer{_deviceExtendedDynamicState, BufferCreateInfo{ |
||||
BufferUsage::VertexBuffer, |
||||
sizeof(Vector3)*4 |
||||
}, MemoryFlag::HostVisible}; |
||||
/** @todo ffs fucking casts!!! */ |
||||
Utility::copy( |
||||
Containers::stridedArrayView(QuadData).slice(&Quad::position), |
||||
Containers::arrayCast<Vector3>(Containers::arrayView(buffer.dedicatedMemory().map()))); |
||||
mesh.addVertexBuffer(0, std::move(buffer), 0) |
||||
.setCount(4); |
||||
} |
||||
|
||||
Shader shader{_deviceExtendedDynamicState, ShaderCreateInfo{ |
||||
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.spv")) |
||||
}}; |
||||
|
||||
ShaderSet shaderSet; |
||||
shaderSet |
||||
.addShader(ShaderStage::Vertex, shader, "ver"_s) |
||||
.addShader(ShaderStage::Fragment, shader, "fra"_s); |
||||
|
||||
/* Create the pipeline with Triangles while the mesh is TriangleStrip */ |
||||
MeshLayout pipelineLayout{MeshPrimitive::Triangles}; |
||||
pipelineLayout |
||||
.addBinding(0, sizeof(Vector3)) |
||||
.addAttribute(0, 0, VertexFormat::Vector3, 0); |
||||
Pipeline pipeline{_deviceExtendedDynamicState, RasterizationPipelineCreateInfo{ |
||||
shaderSet, pipelineLayout, _pipelineLayout, _renderPass, 0, 1} |
||||
.setViewport({{}, Vector2{_framebuffer.size().xy()}}) |
||||
.setDynamicStates(DynamicRasterizationState::MeshPrimitive) |
||||
}; |
||||
|
||||
CommandBuffer cmd = _pool.allocate(); |
||||
cmd.begin() |
||||
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} |
||||
.clearColor(0, 0x1f1f1f_srgbf) |
||||
) |
||||
.bindPipeline(pipeline) |
||||
.draw(mesh) |
||||
.endRenderPass() |
||||
.copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, { |
||||
Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}} |
||||
}}) |
||||
.pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, { |
||||
{Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels} |
||||
}) |
||||
.end(); |
||||
|
||||
_queue.submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait(); |
||||
|
||||
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || |
||||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) |
||||
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); |
||||
|
||||
CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm, |
||||
_framebuffer.size().xy(), |
||||
_pixels.dedicatedMemory().mapRead()}), |
||||
Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.tga"), |
||||
DebugTools::CompareImageToFile{_manager}); |
||||
} |
||||
|
||||
void MeshVkTest::cmdDrawDynamicStride() { |
||||
if(!(_deviceExtendedDynamicState.enabledFeatures() & DeviceFeature::ExtendedDynamicState)) |
||||
CORRADE_SKIP("DeviceFeature::ExtendedDynamicState not supported, can't test."); |
||||
|
||||
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || |
||||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) |
||||
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); |
||||
|
||||
Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip} |
||||
.addBinding(0, sizeof(Vector3)) |
||||
.addAttribute(0, 0, VertexFormat::Vector3, 0) |
||||
}; |
||||
{ |
||||
Buffer buffer{_deviceExtendedDynamicState, BufferCreateInfo{ |
||||
BufferUsage::VertexBuffer, |
||||
sizeof(Vector3)*4 |
||||
}, MemoryFlag::HostVisible}; |
||||
/** @todo ffs fucking casts!!! */ |
||||
Utility::copy( |
||||
Containers::stridedArrayView(QuadData).slice(&Quad::position), |
||||
Containers::arrayCast<Vector3>(Containers::arrayView(buffer.dedicatedMemory().map()))); |
||||
mesh.addVertexBuffer(0, std::move(buffer), 0) |
||||
.setCount(4); |
||||
} |
||||
|
||||
Shader shader{_deviceExtendedDynamicState, ShaderCreateInfo{ |
||||
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.spv")) |
||||
}}; |
||||
|
||||
ShaderSet shaderSet; |
||||
shaderSet |
||||
.addShader(ShaderStage::Vertex, shader, "ver"_s) |
||||
.addShader(ShaderStage::Fragment, shader, "fra"_s); |
||||
|
||||
/* Create the pipeline with a 1 kB stride, while the actual stride is
|
||||
different */ |
||||
MeshLayout pipelineLayout{MeshPrimitive::TriangleStrip}; |
||||
pipelineLayout |
||||
.addBinding(0, 1024) |
||||
.addAttribute(0, 0, VertexFormat::Vector3, 0); |
||||
Pipeline pipeline{_deviceExtendedDynamicState, RasterizationPipelineCreateInfo{ |
||||
shaderSet, pipelineLayout, _pipelineLayout, _renderPass, 0, 1} |
||||
.setViewport({{}, Vector2{_framebuffer.size().xy()}}) |
||||
.setDynamicStates(DynamicRasterizationState::VertexInputBindingStride) |
||||
}; |
||||
|
||||
CommandBuffer cmd = _pool.allocate(); |
||||
cmd.begin() |
||||
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} |
||||
.clearColor(0, 0x1f1f1f_srgbf) |
||||
) |
||||
.bindPipeline(pipeline) |
||||
.draw(mesh) |
||||
.endRenderPass() |
||||
.copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, { |
||||
Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}} |
||||
}}) |
||||
.pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, { |
||||
{Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels} |
||||
}) |
||||
.end(); |
||||
|
||||
_queue.submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait(); |
||||
|
||||
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || |
||||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) |
||||
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); |
||||
|
||||
CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm, |
||||
_framebuffer.size().xy(), |
||||
_pixels.dedicatedMemory().mapRead()}), |
||||
Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.tga"), |
||||
DebugTools::CompareImageToFile{_manager}); |
||||
} |
||||
|
||||
void MeshVkTest::cmdDrawDynamicStrideInsufficientImplementation() { |
||||
#ifdef CORRADE_NO_ASSERT |
||||
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); |
||||
#endif |
||||
|
||||
if(device().isExtensionEnabled<Extensions::EXT::extended_dynamic_state>()) |
||||
CORRADE_SKIP("VK_EXT_extended_dynamic_state enabled, can't test."); |
||||
|
||||
Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip} |
||||
.addBinding(0, sizeof(Vector3)) |
||||
.addAttribute(0, 0, VertexFormat::Vector3, 0) |
||||
}; |
||||
{ |
||||
Buffer buffer{device(), BufferCreateInfo{ |
||||
BufferUsage::VertexBuffer, |
||||
sizeof(Vector3)*4 |
||||
}, MemoryFlag::HostVisible}; |
||||
/** @todo ffs fucking casts!!! */ |
||||
Utility::copy( |
||||
Containers::stridedArrayView(QuadData).slice(&Quad::position), |
||||
Containers::arrayCast<Vector3>(Containers::arrayView(buffer.dedicatedMemory().map()))); |
||||
mesh.addVertexBuffer(0, std::move(buffer), 0) |
||||
.setCount(4); |
||||
} |
||||
|
||||
Shader shader{device(), ShaderCreateInfo{ |
||||
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.spv")) |
||||
}}; |
||||
|
||||
ShaderSet shaderSet; |
||||
shaderSet |
||||
.addShader(ShaderStage::Vertex, shader, "ver"_s) |
||||
.addShader(ShaderStage::Fragment, shader, "fra"_s); |
||||
|
||||
/* Create a pipeline without any dynamic state and then wrap it with fake
|
||||
enabled vertex input binding stride -- doing so directly would trigger |
||||
validation layer failures (using dynamic state from a non-enabled ext), |
||||
which we don't want */ |
||||
Pipeline pipeline{device(), RasterizationPipelineCreateInfo{ |
||||
shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1} |
||||
.setViewport({{}, Vector2{_framebuffer.size().xy()}}) |
||||
}; |
||||
Pipeline fakeDynamicStatePipeline = Pipeline::wrap(device(), |
||||
PipelineBindPoint::Rasterization, pipeline, |
||||
DynamicRasterizationState::VertexInputBindingStride); |
||||
|
||||
CommandBuffer cmd = _pool.allocate(); |
||||
cmd.begin() |
||||
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer} |
||||
.clearColor(0, 0x1f1f1f_srgbf) |
||||
) |
||||
.bindPipeline(fakeDynamicStatePipeline); |
||||
|
||||
std::ostringstream out; |
||||
Error redirectError{&out}; |
||||
cmd.draw(mesh); |
||||
CORRADE_COMPARE(out.str(), "Vk::CommandBuffer::draw(): dynamic strides supplied for an implementation without extended dynamic state\n"); |
||||
} |
||||
|
||||
}}}} |
||||
|
||||
CORRADE_TEST_MAIN(Magnum::Vk::Test::MeshVkTest) |
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue