mirror of https://github.com/mosra/magnum.git
9 changed files with 841 additions and 5 deletions
@ -0,0 +1,156 @@
|
||||
/*
|
||||
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 "ShaderSet.h" |
||||
|
||||
#include <Corrade/Containers/GrowableArray.h> |
||||
#include <Corrade/Containers/String.h> |
||||
#include <Corrade/Containers/StringView.h> |
||||
#include <Corrade/Utility/Algorithms.h> |
||||
|
||||
#include "Magnum/Vk/Shader.h" |
||||
|
||||
namespace Magnum { namespace Vk { |
||||
|
||||
struct ShaderSet::State { |
||||
Containers::Array<Shader> ownedShaders; |
||||
Containers::Array<Containers::String> entrypointNames; |
||||
Containers::Array<char> specializationData; |
||||
Containers::Array<VkSpecializationMapEntry> specializations; |
||||
}; |
||||
|
||||
ShaderSet::ShaderSet(): _stages{}, _specializations{}, _stageCount{} {} |
||||
|
||||
ShaderSet::ShaderSet(ShaderSet&& other) noexcept: _stageCount{other._stageCount}, _state{std::move(other._state)} { |
||||
/* C++, WHY THE FUCK can't you copy C arrays, why do I have to do that for
|
||||
you?! */ |
||||
Utility::copy(other._stages, _stages); |
||||
Utility::copy(other._specializations, _specializations); |
||||
/* The easiest is to just make the original stage list empty and leave
|
||||
whatever dangling internal pointers are there. Otherwise we'd need to |
||||
clear even the entrypoint field in case the name is owned, which would |
||||
make the whole list invalid and thus pointless. */ |
||||
other._stageCount = 0; |
||||
} |
||||
|
||||
ShaderSet::~ShaderSet() = default; |
||||
|
||||
ShaderSet& ShaderSet::operator=(ShaderSet&& other) noexcept { |
||||
using std::swap; |
||||
swap(other._stages, _stages); |
||||
swap(other._specializations, _specializations); |
||||
swap(other._stageCount, _stageCount); |
||||
swap(other._state, _state); |
||||
return *this; |
||||
} |
||||
|
||||
ShaderSet& ShaderSet::addShader(const ShaderStage stage, const VkShaderModule shader, const Containers::StringView entrypoint, const Containers::ArrayView<const ShaderSpecialization> specializations) { |
||||
CORRADE_ASSERT(_stageCount < Containers::arraySize(_stages), |
||||
"Vk::ShaderSet::addShader(): too many stages, expected at most" << Containers::arraySize(_stages), *this); |
||||
|
||||
_stages[_stageCount].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; |
||||
_stages[_stageCount].stage = VkShaderStageFlagBits(stage); |
||||
_stages[_stageCount].module = shader; |
||||
|
||||
/* Not using String::nullTerminatedGlobalView() unconditionally because
|
||||
this way we can avoid allocating the state struct altogether */ |
||||
if(entrypoint.flags() >= (Containers::StringViewFlag::Global|Containers::StringViewFlag::NullTerminated)) { |
||||
_stages[_stageCount].pName = entrypoint.data(); |
||||
} else { |
||||
if(!_state) _state.emplace(); |
||||
/* Ensure the data are never SSO'd and so when the array reallocates we
|
||||
don't need to rewire existing name pointers */ |
||||
_stages[_stageCount].pName = arrayAppend(_state->entrypointNames, Containers::InPlaceInit, Containers::AllocatedInit, entrypoint).data(); |
||||
} |
||||
|
||||
/* Specialization, also only if there are any to avoid allocating the state
|
||||
struct when not neccessary */ |
||||
if(!specializations.empty()) { |
||||
if(!_state) _state.emplace(); |
||||
|
||||
/* Remember the original base data pointers so we can reroute the
|
||||
structures after a potential reallocation */ |
||||
const char* const previousBaseDataPointer = _state->specializationData.data(); |
||||
const VkSpecializationMapEntry* const previousBasePointer = _state->specializations.data(); |
||||
|
||||
/* The data is (currently) always four bytes, so we don't need to do
|
||||
any extra work to calculate the total data size over all |
||||
specializations */ |
||||
const Containers::ArrayView<char> newSpecializationData = arrayAppend(_state->specializationData, Containers::NoInit, specializations.size()*4); |
||||
const Containers::ArrayView<VkSpecializationMapEntry> newSpecializations = arrayAppend(_state->specializations, Containers::NoInit, specializations.size()); |
||||
|
||||
/* Reroute the existing structures for possible reallocations */ |
||||
for(std::size_t i = 0; i != _stageCount; ++i) { |
||||
if(!_specializations[i].dataSize) continue; |
||||
|
||||
CORRADE_INTERNAL_ASSERT(_specializations[i].pData >= previousBaseDataPointer && _specializations[i].pData < previousBaseDataPointer + _state->specializationData.size() - specializations.size()*4); |
||||
_specializations[i].pData = _state->specializationData.data() + (static_cast<const char*>(_specializations[i].pData) - previousBaseDataPointer); |
||||
|
||||
CORRADE_INTERNAL_ASSERT(_specializations[i].pMapEntries >= previousBasePointer && _specializations[i].pMapEntries < previousBasePointer + _state->specializationData.size() - specializations.size()); |
||||
_specializations[i].pMapEntries = _state->specializations.data() + (_specializations[i].pMapEntries - previousBasePointer); |
||||
} |
||||
|
||||
/* Add new specializations */ |
||||
const auto newSpecializationDataInteger = Containers::arrayCast<UnsignedInt>(newSpecializationData); |
||||
for(std::size_t i = 0; i != specializations.size(); ++i) { |
||||
newSpecializations[i].constantID = specializations[i].id(); |
||||
newSpecializations[i].offset = i*4; |
||||
newSpecializations[i].size = 4; |
||||
newSpecializationDataInteger[i] = specializations[i].data(); |
||||
} |
||||
|
||||
_specializations[_stageCount].mapEntryCount = newSpecializations.size(); |
||||
_specializations[_stageCount].pMapEntries = newSpecializations; |
||||
_specializations[_stageCount].dataSize = newSpecializationData.size(); |
||||
_specializations[_stageCount].pData = newSpecializationData; |
||||
_stages[_stageCount].pSpecializationInfo = _specializations + _stageCount; |
||||
} |
||||
|
||||
++_stageCount; |
||||
return *this; |
||||
} |
||||
|
||||
ShaderSet& ShaderSet::addShader(const ShaderStage stage, const VkShaderModule shader, const Containers::StringView entrypoint, const std::initializer_list<ShaderSpecialization> specializations) { |
||||
return addShader(stage, shader, entrypoint, Containers::arrayView(specializations)); |
||||
} |
||||
|
||||
ShaderSet& ShaderSet::addShader(const ShaderStage stage, Shader&& shader, const Containers::StringView entrypoint, const Containers::ArrayView<const ShaderSpecialization> specializations) { |
||||
if(!_state) _state.emplace(); |
||||
return addShader(stage, arrayAppend(_state->ownedShaders, std::move(shader)), entrypoint, specializations); |
||||
} |
||||
|
||||
ShaderSet& ShaderSet::addShader(const ShaderStage stage, Shader&& shader, const Containers::StringView entrypoint, const std::initializer_list<ShaderSpecialization> specializations) { |
||||
return addShader(stage, std::move(shader), entrypoint, Containers::arrayView(specializations)); |
||||
} |
||||
|
||||
Containers::ArrayView<VkPipelineShaderStageCreateInfo> ShaderSet::stages() { |
||||
return {_stages, _stageCount}; |
||||
} |
||||
|
||||
Containers::ArrayView<const VkPipelineShaderStageCreateInfo> ShaderSet::stages() const { |
||||
return {_stages, _stageCount}; |
||||
} |
||||
|
||||
}} |
||||
@ -0,0 +1,252 @@
|
||||
#ifndef Magnum_Vk_ShaderSet_h |
||||
#define Magnum_Vk_ShaderSet_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::ShaderSet, @ref Magnum::Vk::ShaderSpecialization |
||||
* @m_since_latest |
||||
*/ |
||||
|
||||
#include <Corrade/Containers/Pointer.h> |
||||
|
||||
#include "Magnum/Tags.h" |
||||
#include "Magnum/Vk/Vk.h" |
||||
#include "Magnum/Vk/Vulkan.h" |
||||
#include "Magnum/Vk/visibility.h" |
||||
|
||||
namespace Magnum { namespace Vk { |
||||
|
||||
/**
|
||||
@brief Shader specialization |
||||
@m_since_latest |
||||
|
||||
Used by @ref ShaderSet for specifying shader specialization constants. See its |
||||
documentation for more information. |
||||
*/ |
||||
class ShaderSpecialization { |
||||
public: |
||||
/**
|
||||
* @brief Construct an integer specialization constant |
||||
* @param id Specialization constant ID |
||||
* @param value Specialized value |
||||
*/ |
||||
/*implicit*/ ShaderSpecialization(UnsignedInt id, Int value): _id{id}, _data{reinterpret_cast<const UnsignedInt&>(value)} {} |
||||
|
||||
/**
|
||||
* @brief Construct a float specialization constant |
||||
* @param id Specialization constant ID |
||||
* @param value Specialized value |
||||
*/ |
||||
/*implicit*/ ShaderSpecialization(UnsignedInt id, Float value): _id{id}, _data{reinterpret_cast<const UnsignedInt&>(value)} {} |
||||
|
||||
/**
|
||||
* @brief Construct a boolean specialization constant |
||||
* @param id Specialization constant ID |
||||
* @param value Specialized value |
||||
*/ |
||||
/*implicit*/ ShaderSpecialization(UnsignedInt id, bool value): _id{id}, _data{value} {} |
||||
|
||||
/** @brief Specialization constant ID */ |
||||
UnsignedInt id() const { return _id; } |
||||
|
||||
/**
|
||||
* @brief Specialization value data |
||||
* |
||||
* The contents can be an integer, a float or a boolean extended to |
||||
* four bytes based on what constructor got used. |
||||
*/ |
||||
UnsignedInt data() const { return _data; } |
||||
|
||||
private: |
||||
UnsignedInt _id; |
||||
|
||||
/* It would be great if this was explicitly said in either the SPIR-V
|
||||
or Vulkan spec, but AFAICT, specialization is only possible for |
||||
booleans (which have to be four bytes), ints and floats, not |
||||
composite types (and at least in the GL_KHR_vulkan_glsl spec these |
||||
are enumerated as the only allowed types). Looking at the SPIR-V |
||||
spec, OpSpecConstant gets turned into OpConstant and that can only |
||||
be an int or float as well. |
||||
|
||||
In conclusion, I don't see why there has to be the size specified |
||||
if it's required to be always 4 bytes. Maybe future-proofing blah |
||||
blah, which may as well never happen. Here I'm making my life |
||||
simpler by explicitly supporting only the three allowed types, |
||||
putting them all into an int. */ |
||||
UnsignedInt _data; |
||||
}; |
||||
|
||||
/**
|
||||
@brief Shader set |
||||
@m_since_latest |
||||
|
||||
A collection of @ref Shader instances together with populated |
||||
@type_vk_keyword{PipelineShaderStageCreateInfo} structures for use in a |
||||
pipeline. |
||||
|
||||
@section Vk-ShaderSet-usage Usage |
||||
|
||||
Based on whether the shader set is for a rasterization, compute or ray tracing |
||||
pipeline, you'll call @ref addShader() with all stages that the pipeline needs. |
||||
At the very least you need to specify what stage is the shader for and the |
||||
entrypoint name --- usually it'd be @cpp main() @ce, but there can be also |
||||
SPIR-V shader modules with multiple entry points, which is why this parameter |
||||
is needed. |
||||
|
||||
@snippet MagnumVk.cpp ShaderSet-usage |
||||
|
||||
<b></b> |
||||
|
||||
@m_class{m-note m-success} |
||||
|
||||
@par |
||||
The above code uses the @link Containers::Literals::operator""_s() @endlink |
||||
literal, which lets the library know that given string is global and |
||||
null-terminated. Such strings then don't need to be copied internally to |
||||
keep them in scope until they're consumed by Vulkan APIs. |
||||
|
||||
@subsection Vk-ShaderSet-usage-specializations Specialization constants |
||||
|
||||
If the shader module exposes specialization constants, those can be specialized |
||||
via an additional parameter, taking a list of @ref ShaderSpecialization |
||||
instances. The constant can be an integer, float or a boolean; constant IDs not |
||||
present in the SPIR-V module are ignored. |
||||
|
||||
@snippet MagnumVk.cpp ShaderSet-usage-specializations |
||||
|
||||
@subsection Vk-ShaderSet-usage-ownership-transfer Shader ownership transfer |
||||
|
||||
To create a self-contained shader set it's possible to move the @ref Shader |
||||
instances into the class using the @ref addShader(ShaderStage, Shader&&, Containers::StringView, Containers::ArrayView<const ShaderSpecialization>) |
||||
overload. If you have a multi-entrypoint shader, move only the last specified |
||||
stage, for example: |
||||
|
||||
@snippet MagnumVk.cpp ShaderSet-usage-ownership-transfer |
||||
*/ |
||||
class MAGNUM_VK_EXPORT ShaderSet { |
||||
public: |
||||
/**
|
||||
* @brief Constructor |
||||
* |
||||
* Creates an empty shader set. At least one shader has to be present, |
||||
* call @ref addShader() to add it. |
||||
*/ |
||||
explicit ShaderSet(); |
||||
|
||||
/** @brief Copying is not allowed */ |
||||
ShaderSet(const ShaderSet&) = delete; |
||||
|
||||
/** @brief Move constructor */ |
||||
ShaderSet(ShaderSet&& other) noexcept; |
||||
|
||||
/**
|
||||
* @brief Destructor |
||||
* |
||||
* If any shaders were added using @ref addShader(ShaderStage, Shader&&, Containers::StringView, Containers::ArrayView<const ShaderSpecialization>), |
||||
* their owned instances are destructed at this point. |
||||
*/ |
||||
~ShaderSet(); |
||||
|
||||
/** @brief Copying is not allowed */ |
||||
ShaderSet& operator=(const ShaderSet&) = delete; |
||||
|
||||
/** @brief Move assignment */ |
||||
ShaderSet& operator=(ShaderSet&& other) noexcept; |
||||
|
||||
/**
|
||||
* @brief Add a shader |
||||
* @param stage Shader stage |
||||
* @param shader A @ref Shader or a raw Vulkan shader handle |
||||
* @param entrypoint Entrypoint name |
||||
* @param specializations Specialization constant values |
||||
* @return Reference to self (for method chaining) |
||||
* |
||||
* The function makes a copy of @p entrypoint if it's not global or |
||||
* null-terminated, use the @link Containers::Literals::operator""_s() @endlink |
||||
* literal to prevent that where possible. |
||||
* |
||||
* The populated @type_vk{VkPipelineShaderStageCreateInfo} is |
||||
* subsequently available through @ref stages() for direct editing. The |
||||
* following fields are pre-filled in addition to `sType`, everything |
||||
* else is zero-filled: |
||||
* |
||||
* - `stage` |
||||
* - `module` to @p shader |
||||
* - `pName` to @p entrypoint |
||||
* - `pSpecializationInfo`, if @p specializations are non-empty |
||||
* - @cpp pSpecializationInfo->mapEntryCount @ce, |
||||
* @cpp pSpecializationInfo->pMapEntries @ce, |
||||
* @cpp pSpecializationInfo->pMapEntries[i].constantID @ce, |
||||
* @cpp pSpecializationInfo->pMapEntries[i].offset @ce, |
||||
* @cpp pSpecializationInfo->pMapEntries[i].size @ce, |
||||
* @cpp pSpecializationInfo->dataSize @ce and |
||||
* @cpp pSpecializationInfo->pData @ce to processed and linearized |
||||
* contents of @p specializations |
||||
*/ |
||||
ShaderSet& addShader(ShaderStage stage, VkShaderModule shader, Containers::StringView entrypoint, Containers::ArrayView<const ShaderSpecialization> specializations); |
||||
|
||||
/** @overload */ |
||||
/* Having a default here to avoid having to include ArrayView or have
|
||||
two addShader() implementations, one with and one without */ |
||||
ShaderSet& addShader(ShaderStage stage, VkShaderModule shader, Containers::StringView entrypoint, std::initializer_list<ShaderSpecialization> specializations = {}); |
||||
|
||||
/**
|
||||
* @brief Add a shader and take over its ownership |
||||
* @return Reference to self (for method chaining) |
||||
* |
||||
* Compared to @ref addShader(ShaderStage, VkShaderModule, Containers::StringView, Containers::ArrayView<const ShaderSpecialization>) |
||||
* the @p shader instance ownership is transferred to the class and |
||||
* thus doesn't have to be managed separately. |
||||
*/ |
||||
ShaderSet& addShader(ShaderStage stage, Shader&& shader, Containers::StringView entrypoint, Containers::ArrayView<const ShaderSpecialization> specializations); |
||||
|
||||
/** @overload */ |
||||
/* Having a default here to avoid having to include ArrayView or have
|
||||
two addShader() implementations, one with and one without */ |
||||
ShaderSet& addShader(ShaderStage stage, Shader&& shader, Containers::StringView entrypoint, std::initializer_list<ShaderSpecialization> specializations = {}); |
||||
|
||||
/**
|
||||
* @brief Shader stages |
||||
* |
||||
* Exposes all data added with @ref addShader() calls. If |
||||
* @ref addShader() was not called yet, the returned view is empty. |
||||
*/ |
||||
Containers::ArrayView<VkPipelineShaderStageCreateInfo> stages(); |
||||
/** @overload */ |
||||
Containers::ArrayView<const VkPipelineShaderStageCreateInfo> stages() const; |
||||
|
||||
private: |
||||
VkPipelineShaderStageCreateInfo _stages[6]; |
||||
VkSpecializationInfo _specializations[6]; |
||||
std::size_t _stageCount; |
||||
|
||||
struct State; |
||||
Containers::Pointer<State> _state; |
||||
}; |
||||
|
||||
}} |
||||
|
||||
#endif |
||||
@ -0,0 +1,318 @@
|
||||
/*
|
||||
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/ArrayView.h> |
||||
#include <Corrade/Containers/StringView.h> |
||||
#include <Corrade/TestSuite/Tester.h> |
||||
#include <Corrade/Utility/DebugStl.h> |
||||
|
||||
#include "Magnum/Vk/Device.h" |
||||
#include "Magnum/Vk/Shader.h" |
||||
#include "Magnum/Vk/ShaderSet.h" |
||||
|
||||
namespace Magnum { namespace Vk { namespace Test { namespace { |
||||
|
||||
struct ShaderSetTest: TestSuite::Tester { |
||||
explicit ShaderSetTest(); |
||||
|
||||
void specializationConstructInt(); |
||||
void specializationConstructFloat(); |
||||
void specializationConstructBool(); |
||||
|
||||
void construct(); |
||||
void constructCopy(); |
||||
void constructMove(); |
||||
|
||||
void addShader(); |
||||
void addShaderEntrypointCopy(); |
||||
void addShaderEntrypointCopyReallocation(); |
||||
void addShaderSpecializations(); |
||||
void addShaderSpecializationsReallocation(); |
||||
void addShaderOwnershipTransfer(); |
||||
void addShaderTooManyStages(); |
||||
}; |
||||
|
||||
ShaderSetTest::ShaderSetTest() { |
||||
addTests({&ShaderSetTest::specializationConstructInt, |
||||
&ShaderSetTest::specializationConstructFloat, |
||||
&ShaderSetTest::specializationConstructBool, |
||||
|
||||
&ShaderSetTest::construct, |
||||
&ShaderSetTest::constructCopy, |
||||
&ShaderSetTest::constructMove, |
||||
|
||||
&ShaderSetTest::addShader, |
||||
&ShaderSetTest::addShaderEntrypointCopy, |
||||
&ShaderSetTest::addShaderEntrypointCopyReallocation, |
||||
&ShaderSetTest::addShaderSpecializations, |
||||
&ShaderSetTest::addShaderSpecializationsReallocation, |
||||
&ShaderSetTest::addShaderOwnershipTransfer, |
||||
&ShaderSetTest::addShaderTooManyStages}); |
||||
} |
||||
|
||||
using namespace Containers::Literals; |
||||
|
||||
void ShaderSetTest::specializationConstructInt() { |
||||
ShaderSpecialization spec{42, 133785}; |
||||
CORRADE_COMPARE(spec.id(), 42); |
||||
CORRADE_COMPARE(spec.data(), 133785); |
||||
} |
||||
|
||||
void ShaderSetTest::specializationConstructFloat() { |
||||
ShaderSpecialization spec{42, 4.32f}; |
||||
CORRADE_COMPARE(spec.id(), 42); |
||||
UnsignedInt data = spec.data(); |
||||
CORRADE_COMPARE(reinterpret_cast<Float&>(data), 4.32f); |
||||
} |
||||
|
||||
void ShaderSetTest::specializationConstructBool() { |
||||
ShaderSpecialization spec{42, true}; |
||||
CORRADE_COMPARE(spec.id(), 42); |
||||
CORRADE_COMPARE(spec.data(), 1); |
||||
} |
||||
|
||||
void ShaderSetTest::construct() { |
||||
ShaderSet set; |
||||
CORRADE_VERIFY(set.stages().empty()); |
||||
|
||||
/* The actually meaningful test done in addShader() and friends */ |
||||
} |
||||
|
||||
void ShaderSetTest::constructCopy() { |
||||
CORRADE_VERIFY(!std::is_copy_constructible<ShaderSet>{}); |
||||
CORRADE_VERIFY(!std::is_copy_assignable<ShaderSet>{}); |
||||
} |
||||
|
||||
void ShaderSetTest::constructMove() { |
||||
ShaderSet c; |
||||
|
||||
{ |
||||
ShaderSet a; |
||||
a.addShader(ShaderStage::Geometry, reinterpret_cast<VkShaderModule>(0xdeadbeef), "main!"_s.except(1), { |
||||
{42, 1.15f} |
||||
}); |
||||
CORRADE_COMPARE(a.stages().size(), 1); |
||||
CORRADE_COMPARE(a.stages()[0].pName, "main"_s); |
||||
CORRADE_VERIFY(a.stages()[0].pSpecializationInfo); |
||||
CORRADE_COMPARE(a.stages()[0].pSpecializationInfo->mapEntryCount, 1); |
||||
CORRADE_VERIFY(a.stages()[0].pSpecializationInfo->pMapEntries); |
||||
CORRADE_COMPARE(a.stages()[0].pSpecializationInfo->pMapEntries[0].constantID, 42); |
||||
CORRADE_VERIFY(a.stages()[0].pSpecializationInfo->pData); |
||||
CORRADE_COMPARE(*reinterpret_cast<const Float*>(a.stages()[0].pSpecializationInfo->pData), 1.15f); |
||||
|
||||
ShaderSet b = std::move(a); |
||||
CORRADE_VERIFY(a.stages().empty()); |
||||
CORRADE_COMPARE(b.stages().size(), 1); |
||||
CORRADE_COMPARE(b.stages()[0].pName, "main"_s); |
||||
CORRADE_VERIFY(b.stages()[0].pSpecializationInfo); |
||||
CORRADE_COMPARE(b.stages()[0].pSpecializationInfo->mapEntryCount, 1); |
||||
CORRADE_VERIFY(b.stages()[0].pSpecializationInfo->pMapEntries); |
||||
CORRADE_COMPARE(b.stages()[0].pSpecializationInfo->pMapEntries[0].constantID, 42); |
||||
CORRADE_VERIFY(b.stages()[0].pSpecializationInfo->pData); |
||||
CORRADE_COMPARE(*reinterpret_cast<const Float*>(b.stages()[0].pSpecializationInfo->pData), 1.15f); |
||||
|
||||
c = std::move(b); |
||||
CORRADE_VERIFY(b.stages().empty()); |
||||
} |
||||
|
||||
/* Doing this in outer scope to verify that the internal state pointer got
|
||||
properly transferred as well and we're not referencing destroyed data */ |
||||
CORRADE_COMPARE(c.stages().size(), 1); |
||||
CORRADE_COMPARE(c.stages()[0].pName, "main"_s); |
||||
CORRADE_VERIFY(c.stages()[0].pSpecializationInfo); |
||||
CORRADE_COMPARE(c.stages()[0].pSpecializationInfo->mapEntryCount, 1); |
||||
CORRADE_VERIFY(c.stages()[0].pSpecializationInfo->pMapEntries); |
||||
CORRADE_COMPARE(c.stages()[0].pSpecializationInfo->pMapEntries[0].constantID, 42); |
||||
CORRADE_VERIFY(c.stages()[0].pSpecializationInfo->pData); |
||||
CORRADE_COMPARE(*reinterpret_cast<const Float*>(c.stages()[0].pSpecializationInfo->pData), 1.15f); |
||||
} |
||||
|
||||
void ShaderSetTest::addShader() { |
||||
ShaderSet set; |
||||
Containers::StringView entrypoint = "enterHere"_s; |
||||
set.addShader(ShaderStage::Geometry, reinterpret_cast<VkShaderModule>(0xdeadbeef), entrypoint); |
||||
CORRADE_COMPARE(set.stages().size(), 1); |
||||
CORRADE_COMPARE(set.stages()[0].stage, VK_SHADER_STAGE_GEOMETRY_BIT); |
||||
CORRADE_COMPARE(set.stages()[0].module, reinterpret_cast<VkShaderModule>(0xdeadbeef)); |
||||
/* The name should not be copied if it's null-terminated and global */ |
||||
CORRADE_COMPARE(set.stages()[0].pName, entrypoint.data()); |
||||
CORRADE_VERIFY(!set.stages()[0].pSpecializationInfo); |
||||
} |
||||
|
||||
void ShaderSetTest::addShaderEntrypointCopy() { |
||||
ShaderSet set; |
||||
Containers::StringView entrypoint = "enterHere!"_s; |
||||
set.addShader(ShaderStage{}, {}, entrypoint.except(1)); |
||||
CORRADE_COMPARE(set.stages().size(), 1); |
||||
CORRADE_VERIFY(set.stages()[0].pName != entrypoint.data()); |
||||
CORRADE_COMPARE(set.stages()[0].pName, "enterHere"_s); |
||||
} |
||||
|
||||
void ShaderSetTest::addShaderEntrypointCopyReallocation() { |
||||
ShaderSet set; |
||||
Containers::StringView entrypoint = "enterHere!"_s; |
||||
set.addShader(ShaderStage{}, {}, entrypoint.except(1)); |
||||
CORRADE_COMPARE(set.stages().size(), 1); |
||||
CORRADE_VERIFY(set.stages()[0].pName != entrypoint.data()); |
||||
CORRADE_COMPARE(set.stages()[0].pName, "enterHere"_s); |
||||
|
||||
/* After adding more stages, the original name pointers should be preserved
|
||||
-- no SSO strings getting reallocated but instead all copies allocated */ |
||||
const char* prev = set.stages()[0].pName; |
||||
set.addShader(ShaderStage{}, {}, "huajajajaja"_s.prefix(5)) |
||||
.addShader(ShaderStage{}, {}, "ablablablab"_s.prefix(5)); |
||||
CORRADE_COMPARE(set.stages().size(), 3); |
||||
CORRADE_COMPARE(set.stages()[0].pName, prev); |
||||
CORRADE_COMPARE(set.stages()[0].pName, "enterHere"_s); |
||||
CORRADE_COMPARE(set.stages()[1].pName, "huaja"_s); |
||||
CORRADE_COMPARE(set.stages()[2].pName, "ablab"_s); |
||||
} |
||||
|
||||
void ShaderSetTest::addShaderSpecializations() { |
||||
ShaderSet set; |
||||
set.addShader(ShaderStage{}, {}, "main"_s, { |
||||
{42, 1.15f}, |
||||
{1, true}, |
||||
{13, -227} |
||||
}); |
||||
CORRADE_COMPARE(set.stages().size(), 1); |
||||
CORRADE_COMPARE(set.stages()[0].pName, "main"_s); |
||||
CORRADE_VERIFY(set.stages()[0].pSpecializationInfo); |
||||
CORRADE_COMPARE(set.stages()[0].pSpecializationInfo->mapEntryCount, 3); |
||||
CORRADE_VERIFY(set.stages()[0].pSpecializationInfo->pMapEntries); |
||||
CORRADE_COMPARE(set.stages()[0].pSpecializationInfo->pMapEntries[0].constantID, 42); |
||||
CORRADE_COMPARE(set.stages()[0].pSpecializationInfo->pMapEntries[0].offset, 0); |
||||
CORRADE_COMPARE(set.stages()[0].pSpecializationInfo->pMapEntries[0].size, 4); |
||||
CORRADE_COMPARE(set.stages()[0].pSpecializationInfo->pMapEntries[1].constantID, 1); |
||||
CORRADE_COMPARE(set.stages()[0].pSpecializationInfo->pMapEntries[1].offset, 4); |
||||
CORRADE_COMPARE(set.stages()[0].pSpecializationInfo->pMapEntries[1].size, 4); |
||||
CORRADE_COMPARE(set.stages()[0].pSpecializationInfo->pMapEntries[2].constantID, 13); |
||||
CORRADE_COMPARE(set.stages()[0].pSpecializationInfo->pMapEntries[2].offset, 8); |
||||
CORRADE_COMPARE(set.stages()[0].pSpecializationInfo->pMapEntries[2].size, 4); |
||||
CORRADE_COMPARE(set.stages()[0].pSpecializationInfo->dataSize, 4*3); |
||||
CORRADE_VERIFY(set.stages()[0].pSpecializationInfo->pData); |
||||
CORRADE_COMPARE(*reinterpret_cast<const Float*>(set.stages()[0].pSpecializationInfo->pData), 1.15f); |
||||
CORRADE_COMPARE(*(reinterpret_cast<const UnsignedInt*>(set.stages()[0].pSpecializationInfo->pData) + 1), 1); |
||||
CORRADE_COMPARE(*(reinterpret_cast<const Int*>(set.stages()[0].pSpecializationInfo->pData) + 2), -227); |
||||
} |
||||
|
||||
void ShaderSetTest::addShaderSpecializationsReallocation() { |
||||
ShaderSet set; |
||||
set.addShader(ShaderStage{}, {}, "main"_s, { |
||||
{42, 1.15f} |
||||
}); |
||||
CORRADE_COMPARE(set.stages().size(), 1); |
||||
CORRADE_COMPARE(set.stages()[0].pName, "main"_s); |
||||
CORRADE_VERIFY(set.stages()[0].pSpecializationInfo); |
||||
CORRADE_COMPARE(set.stages()[0].pSpecializationInfo->mapEntryCount, 1); |
||||
CORRADE_VERIFY(set.stages()[0].pSpecializationInfo->pMapEntries); |
||||
CORRADE_COMPARE(set.stages()[0].pSpecializationInfo->pMapEntries[0].constantID, 42); |
||||
CORRADE_VERIFY(set.stages()[0].pSpecializationInfo->pData); |
||||
CORRADE_COMPARE(*reinterpret_cast<const Float*>(set.stages()[0].pSpecializationInfo->pData), 1.15f); |
||||
|
||||
const void* prevData = set.stages()[0].pSpecializationInfo->pData; |
||||
const VkSpecializationMapEntry* prev = set.stages()[0].pSpecializationInfo->pMapEntries; |
||||
|
||||
set.addShader(ShaderStage{}, {}, "well"_s, { |
||||
{1, true}, |
||||
{13, -227} |
||||
}); |
||||
CORRADE_COMPARE(set.stages().size(), 2); |
||||
|
||||
/* Don't fail in this case -- the allocator is expected to be smarter than
|
||||
this test */ |
||||
if(set.stages()[0].pSpecializationInfo->pData == prevData) |
||||
Warning{} << "No data reallocation happened."; |
||||
if(set.stages()[0].pSpecializationInfo->pMapEntries == prev) |
||||
Warning{} << "No entry map reallocation happened."; |
||||
|
||||
/* Same as above, everything should be kept */ |
||||
CORRADE_COMPARE(set.stages()[0].pName, "main"_s); |
||||
CORRADE_VERIFY(set.stages()[0].pSpecializationInfo); |
||||
CORRADE_COMPARE(set.stages()[0].pSpecializationInfo->mapEntryCount, 1); |
||||
CORRADE_VERIFY(set.stages()[0].pSpecializationInfo->pMapEntries); |
||||
CORRADE_COMPARE(set.stages()[0].pSpecializationInfo->pMapEntries[0].constantID, 42); |
||||
CORRADE_VERIFY(set.stages()[0].pSpecializationInfo->pData); |
||||
CORRADE_COMPARE(*reinterpret_cast<const Float*>(set.stages()[0].pSpecializationInfo->pData), 1.15f); |
||||
|
||||
/* New entries */ |
||||
CORRADE_COMPARE(set.stages()[1].pName, "well"_s); |
||||
CORRADE_VERIFY(set.stages()[1].pSpecializationInfo); |
||||
CORRADE_COMPARE(set.stages()[1].pSpecializationInfo->mapEntryCount, 2); |
||||
CORRADE_VERIFY(set.stages()[1].pSpecializationInfo->pMapEntries); |
||||
CORRADE_COMPARE(set.stages()[1].pSpecializationInfo->pMapEntries[0].constantID, 1); |
||||
CORRADE_COMPARE(set.stages()[1].pSpecializationInfo->pMapEntries[1].constantID, 13); |
||||
CORRADE_VERIFY(set.stages()[1].pSpecializationInfo->pData); |
||||
CORRADE_COMPARE(*reinterpret_cast<const UnsignedInt*>(set.stages()[1].pSpecializationInfo->pData), 1); |
||||
CORRADE_COMPARE(*(reinterpret_cast<const Int*>(set.stages()[1].pSpecializationInfo->pData) + 1), -227); |
||||
} |
||||
|
||||
void ShaderSetTest::addShaderOwnershipTransfer() { |
||||
Device device{NoCreate}; |
||||
auto shader = Shader::wrap(device, reinterpret_cast<VkShaderModule>(0xdeadbeef)); |
||||
|
||||
ShaderSet set; |
||||
set.addShader(ShaderStage::RayAnyHit, std::move(shader), "main"_s, { |
||||
{13, 1227} |
||||
}); |
||||
|
||||
CORRADE_COMPARE(set.stages()[0].stage, VK_SHADER_STAGE_ANY_HIT_BIT_KHR); |
||||
CORRADE_COMPARE(set.stages()[0].pName, "main"_s); |
||||
CORRADE_COMPARE(set.stages()[0].module, reinterpret_cast<VkShaderModule>(0xdeadbeef)); |
||||
CORRADE_VERIFY(set.stages()[0].pSpecializationInfo); |
||||
CORRADE_COMPARE(set.stages()[0].pSpecializationInfo->mapEntryCount, 1); |
||||
CORRADE_VERIFY(set.stages()[0].pSpecializationInfo->pMapEntries); |
||||
CORRADE_COMPARE(set.stages()[0].pSpecializationInfo->pMapEntries[0].constantID, 13); |
||||
CORRADE_VERIFY(set.stages()[0].pSpecializationInfo->pData); |
||||
CORRADE_COMPARE(*reinterpret_cast<const UnsignedInt*>(set.stages()[0].pSpecializationInfo->pData), 1227); |
||||
|
||||
/* The shader should be moved away */ |
||||
CORRADE_VERIFY(!shader.handle()); |
||||
} |
||||
|
||||
void ShaderSetTest::addShaderTooManyStages() { |
||||
#ifdef CORRADE_NO_ASSERT |
||||
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); |
||||
#endif |
||||
|
||||
ShaderSet set; |
||||
|
||||
set.addShader({}, {}, {}) |
||||
.addShader({}, {}, {}) |
||||
.addShader({}, {}, {}) |
||||
.addShader({}, {}, {}) |
||||
.addShader({}, {}, {}) |
||||
.addShader({}, {}, {}); |
||||
|
||||
std::ostringstream out; |
||||
Error redirectError{&out}; |
||||
set.addShader({}, {}, {}); |
||||
CORRADE_COMPARE(out.str(), "Vk::ShaderSet::addShader(): too many stages, expected at most 6\n"); |
||||
} |
||||
|
||||
}}}} |
||||
|
||||
CORRADE_TEST_MAIN(Magnum::Vk::Test::ShaderSetTest) |
||||
Loading…
Reference in new issue