mirror of https://github.com/mosra/magnum.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
878 lines
34 KiB
878 lines
34 KiB
/* |
|
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, |
|
/* Artificial 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)
|
|
|