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