Browse Source

Vk: add driver workaround scaffolding and the first one for SwiftShader.

Yay? It's funny, what produces correct result causes validation errors.
I suppose this will be very similar for all other workarounds.
pull/494/head
Vladimír Vondruš 5 years ago
parent
commit
79c3519045
  1. 6
      doc/opengl-workarounds.dox
  2. 58
      doc/vulkan-workarounds.dox
  3. 6
      doc/vulkan.dox
  4. 4
      src/Magnum/GL/Implementation/driverSpecific.cpp
  5. 2
      src/Magnum/Vk/CMakeLists.txt
  6. 3
      src/Magnum/Vk/CommandBuffer.h
  7. 54
      src/Magnum/Vk/Device.cpp
  8. 14
      src/Magnum/Vk/Device.h
  9. 65
      src/Magnum/Vk/Image.cpp
  10. 4
      src/Magnum/Vk/Implementation/Arguments.cpp
  11. 23
      src/Magnum/Vk/Implementation/DeviceState.cpp
  12. 4
      src/Magnum/Vk/Implementation/DeviceState.h
  13. 114
      src/Magnum/Vk/Implementation/DriverWorkaround.cpp
  14. 43
      src/Magnum/Vk/Implementation/DriverWorkaround.h
  15. 5
      src/Magnum/Vk/Instance.cpp
  16. 13
      src/Magnum/Vk/Instance.h
  17. 73
      src/Magnum/Vk/Test/DeviceVkTest.cpp
  18. 91
      src/Magnum/Vk/Test/ImageVkTest.cpp

6
doc/opengl-workarounds.dox

@ -46,8 +46,8 @@ Using driver workarounds:
...
@endcode
These identifiers correspond to the strings in the below listing. For debugging
and diagnostic purpose it's possible to disable particular workarounds by
These identifiers correspond to the strings in the listing below. For debugging
and diagnostic purposes it's possible to disable particular workarounds by
passing their identifier string to the `--magnum-disable-workarounds`
command-line option. See @ref GL-Context-command-line for more information.
@ -55,7 +55,7 @@ command-line option. See @ref GL-Context-command-line for more information.
@snippet src/Magnum/GL/Implementation/driverSpecific.cpp workarounds
Chromium has a similar list: https://cs.chromium.org/chromium/src/gpu/config/gpu_driver_bug_list.json
Chromium has a similar list: https://cs.chromium.org/chromium/src/gpu/config/gpu_driver_bug_list.json . See also @ref vulkan-workarounds "Vulkan driver workarounds".
*/
}

58
doc/vulkan-workarounds.dox

@ -0,0 +1,58 @@
/*
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.
*/
namespace Magnum {
/** @page vulkan-workarounds Driver workarounds
@brief List of Vulkan driver workarounds used by Magnum
@m_since_latest
@m_footernavigation
Driver workarounds used by a particular app are listed in the engine startup
log such as here:
@code{.shell-session}
Device: SwiftShader Device (LLVM 10.0.0)
Device version: Vulkan 1.1
Enabled device extensions:
VK_KHR_create_renderpass2
...
Using driver workarounds:
swiftshader-image-copy-extent-instead-of-layers
...
@endcode
These identifiers correspond to the strings in the listing below. For debugging
and diagnostic purposes it's possible to disable particular workarounds by
passing their identifier string to the `--magnum-disable-workarounds`
command-line option. See @ref Vk-Instance-command-line for more information.
@m_class{m-console-wrap}
@snippet src/Magnum/Vk/Implementation/DriverWorkaround.cpp workarounds
@see @ref opengl-workarounds
*/
}

6
doc/vulkan.dox

@ -43,6 +43,12 @@ following table.
- @subpage vulkan-support
While not as bad as with OpenGL, neither the Vulkan driver world is ideal. In
order to work on majority of platforms, the engine has to work around some
driver bugs. An exhaustive list is here:
- @subpage vulkan-workarounds
@section vulkan-required-extensions Version and extension requirements
The engine can work on unextended Vulkan 1.0, but some specific functionality

4
src/Magnum/GL/Implementation/driverSpecific.cpp

@ -482,8 +482,10 @@ auto Context::detectedDriver() -> DetectedDrivers {
void Context::disableDriverWorkaround(const std::string& workaround) {
/* Ignore unknown workarounds */
/** @todo this will probably cause false positives when both GL and Vulkan
is used together? */
if(std::find(std::begin(KnownWorkarounds), std::end(KnownWorkarounds), workaround) == std::end(KnownWorkarounds)) {
Warning() << "Unknown workaround" << workaround;
Warning() << "GL: unknown workaround" << workaround;
return;
}
_driverWorkarounds.emplace_back(workaround, true);

2
src/Magnum/Vk/CMakeLists.txt

@ -41,6 +41,7 @@ set(MagnumVk_SRCS
Implementation/Arguments.cpp
Implementation/DeviceState.cpp
Implementation/DriverWorkaround.cpp
Implementation/InstanceState.cpp)
set(MagnumVk_GracefulAssert_SRCS
@ -106,6 +107,7 @@ set(MagnumVk_PRIVATE_HEADERS
Implementation/Arguments.h
Implementation/DeviceFeatures.h
Implementation/DeviceState.h
Implementation/DriverWorkaround.h
Implementation/InstanceState.h
Implementation/compressedPixelFormatMapping.hpp

3
src/Magnum/Vk/CommandBuffer.h

@ -726,12 +726,15 @@ class MAGNUM_VK_EXPORT CommandBuffer {
MAGNUM_VK_LOCAL static void copyBufferImplementationKHR(CommandBuffer& self, const CopyBufferInfo& info);
MAGNUM_VK_LOCAL static void copyImageImplementationDefault(CommandBuffer& self, const CopyImageInfo& info);
MAGNUM_VK_LOCAL static void copyImageImplementationSwiftShader(CommandBuffer& self, const CopyImageInfo& info);
MAGNUM_VK_LOCAL static void copyImageImplementationKHR(CommandBuffer& self, const CopyImageInfo& info);
MAGNUM_VK_LOCAL static void copyBufferToImageImplementationDefault(CommandBuffer& self, const CopyBufferToImageInfo& info);
MAGNUM_VK_LOCAL static void copyBufferToImageImplementationSwiftShader(CommandBuffer& self, const CopyBufferToImageInfo& info);
MAGNUM_VK_LOCAL static void copyBufferToImageImplementationKHR(CommandBuffer& self, const CopyBufferToImageInfo& info);
MAGNUM_VK_LOCAL static void copyImageToBufferImplementationDefault(CommandBuffer& self, const CopyImageToBufferInfo& info);
MAGNUM_VK_LOCAL static void copyImageToBufferImplementationSwiftShader(CommandBuffer& self, const CopyImageToBufferInfo& info);
MAGNUM_VK_LOCAL static void copyImageToBufferImplementationKHR(CommandBuffer& self, const CopyImageToBufferInfo& info);
/* Can't be a reference because of the NoCreate constructor */

54
src/Magnum/Vk/Device.cpp

@ -50,6 +50,7 @@
#include "Magnum/Vk/Implementation/InstanceState.h"
#include "Magnum/Vk/Implementation/DeviceFeatures.h"
#include "Magnum/Vk/Implementation/DeviceState.h"
#include "Magnum/Vk/Implementation/DriverWorkaround.h"
#include "Magnum/Vk/Implementation/structureHelpers.h"
#include "MagnumExternal/Vulkan/flextVkGlobal.h"
@ -103,6 +104,9 @@ struct DeviceCreateInfo::State {
Containers::String disabledExtensionsStorage;
Containers::Array<Containers::StringView> disabledExtensions;
/* .second = true means the workaround is disabled; the views always point
to the internal KnownWorkarounds array */
Containers::Array<std::pair<Containers::StringView, bool>> encounteredWorkarounds;
Containers::Array<VkDeviceQueueCreateInfo> queues;
Containers::StaticArray<32, Float> queuePriorities;
Containers::StaticArray<32, Queue*> queueOutput;
@ -131,6 +135,18 @@ DeviceCreateInfo::DeviceCreateInfo(DeviceProperties& deviceProperties, const Ext
version via --magnum-vulkan-version, which will be later used to cap available features. */
_state->version = Version(Math::min(UnsignedInt(deviceProperties._instance->version()), UnsignedInt(deviceProperties.version())));
/* If there are any disabled workarounds, save them until initialize() uses
them on device creation. The disableWorkaround() function saves the
internal string view instead of the one passed from the command line so
we don't need to bother with String allocations. */
Containers::StringView disabledWorkarounds = args.value<Containers::StringView>("disable-workarounds");
if(!disabledWorkarounds.isEmpty()) {
Containers::Array<Containers::StringView> split = disabledWorkarounds.splitWithoutEmptyParts();
arrayReserve(_state->encounteredWorkarounds, split.size());
for(Containers::StringView workaround: split)
Implementation::disableWorkaround(_state->encounteredWorkarounds, workaround);
}
/* If there are any disabled extensions, sort them and save for later --
we'll use them to filter the ones added by the app */
Containers::String disabledExtensions = args.value<Containers::String>("disable-extensions");
@ -622,7 +638,11 @@ void Device::wrap(Instance& instance, const VkPhysicalDevice physicalDevice, con
_flags = flags;
_properties.emplace(DeviceProperties::wrap(instance, physicalDevice));
initializeExtensions(enabledExtensions);
initialize(instance, version, enabledFeatures);
/* Because we have no control over extensions / features, no workarounds
are used here -- better to just do nothing than just a partial attempt */
Containers::Array<std::pair<Containers::StringView, bool>> encounteredWorkarounds = Implementation::disableAllWorkarounds();
initialize(instance, version, encounteredWorkarounds, enabledFeatures);
}
void Device::wrap(Instance& instance, const VkPhysicalDevice physicalDevice, const VkDevice handle, const Version version, const std::initializer_list<Containers::StringView> enabledExtensions, const DeviceFeatures& enabledFeatures, const HandleFlags flags) {
@ -720,8 +740,34 @@ Result Device::tryCreateInternal(Instance& instance, const DeviceCreateInfo& inf
return Result(result);
}
/* Make a copy of the workarounds list coming from DeviceCreateInfo as
initialize() may modify it */
/** @todo switch to Containers::Pair once it exists and use Utility::copy()
(std::pair isn't trivially copyable, ffs) */
Containers::Array<std::pair<Containers::StringView, bool>> encounteredWorkarounds{info._state->encounteredWorkarounds.size()};
for(std::size_t i = 0; i != encounteredWorkarounds.size(); ++i)
encounteredWorkarounds[i] = info._state->encounteredWorkarounds[i];
/* Initialize the enabled extension list and feature-, extension-,
workaround-dependent function pointers */
initializeExtensions<const char*>({info->ppEnabledExtensionNames, info->enabledExtensionCount});
initialize(instance, version, info._state->enabledFeatures | info._state->implicitFeatures);
initialize(instance, version, encounteredWorkarounds, info._state->enabledFeatures | info._state->implicitFeatures);
/* Print a list of used workarounds */
if(!info._state->quietLog) {
bool workaroundHeaderPrinted = false;
for(const auto& workaround: encounteredWorkarounds) {
/* Skip disabled workarounds */
if(workaround.second) continue;
if(!workaroundHeaderPrinted) {
workaroundHeaderPrinted = true;
Debug{} << "Using device driver workarounds:";
}
Debug{} << " " << workaround.first;
}
}
#ifndef CORRADE_NO_ASSERT
/* This is a dumb O(n^2) search but in an assert that's completely fine */
@ -789,14 +835,14 @@ template<class T> void Device::initializeExtensions(const Containers::ArrayView<
}
}
void Device::initialize(Instance& instance, const Version version, const DeviceFeatures& enabledFeatures) {
void Device::initialize(Instance& instance, const Version version, Containers::Array<std::pair<Containers::StringView, bool>>& encounteredWorkarounds, const DeviceFeatures& enabledFeatures) {
/* Init version, features, function pointers */
_version = version;
_enabledFeatures = enabledFeatures;
flextVkInitDevice(_handle, &_functionPointers, instance->GetDeviceProcAddr);
/* Set up extension-dependent functionality */
_state.emplace(*this);
_state.emplace(*this, encounteredWorkarounds);
}
bool Device::isExtensionEnabled(const Extension& extension) const {

14
src/Magnum/Vk/Device.h

@ -151,6 +151,9 @@ The @ref Device inherits a subset of the
the following. If the @ref Instance didn't get `argc` / `argv` passed, only the
environment variables are used.
- `--magnum-disable-workarounds LIST` --- Vulkan driver workarounds to
disable (see @ref vulkan-workarounds for detailed info) (environment:
`MAGNUM_DISABLE_WORKAROUNDS`)
- `--magnum-disable-extensions LIST` --- Vulkan instance or device extensions
to disable, meaning @ref DeviceCreateInfo::addEnabledExtensions() will skip
them (environment: `MAGNUM_DISABLE_EXTENSIONS`)
@ -241,6 +244,15 @@ class MAGNUM_VK_EXPORT Device {
* @p enabledExtensions / @p enabledFeatures is empty, the device will
* behave as if no extensions / no features were enabled.
*
* @m_class{m-note m-danger}
*
* @par
* Due to the extension / feature list being outside of library
* control here, driver bug workarounds are not detected and
* enabled when using this function. Depending on bug severity,
* that may lead to crashes and unexpected behavior that wouldn't
* otherwise happen with a @ref Device created the usual way.
*
* Note that this function retrieves all device-specific Vulkan
* function pointers, which is a relatively costly operation. It's thus
* not recommended to call this function repeatedly for creating
@ -480,7 +492,7 @@ class MAGNUM_VK_EXPORT Device {
Result tryCreateInternal(Instance& isntance, const DeviceCreateInfo&, DeviceProperties&&);
template<class T> MAGNUM_VK_LOCAL void initializeExtensions(Containers::ArrayView<const T> enabledExtensions);
MAGNUM_VK_LOCAL void initialize(Instance& instance, Version version, const DeviceFeatures& enabledFeatures);
MAGNUM_VK_LOCAL void initialize(Instance& instance, Version version, Containers::Array<std::pair<Containers::StringView, bool>>& encounteredWorkarounds, const DeviceFeatures& enabledFeatures);
MAGNUM_VK_LOCAL static void getQueueImplementationDefault(Device& self, const VkDeviceQueueInfo2& info, VkQueue& queue);
MAGNUM_VK_LOCAL static void getQueueImplementation11(Device& self, const VkDeviceQueueInfo2& info, VkQueue& queue);

65
src/Magnum/Vk/Image.cpp

@ -634,12 +634,53 @@ CommandBuffer& CommandBuffer::copyImage(const CopyImageInfo& info) {
return *this;
}
namespace {
/* See the "swiftshader-image-copy-extent-instead-of-layers" workaround for
more info */
void fixupImageCopySwiftShader(VkImageSubresourceLayers& subresource, VkOffset3D& offset, VkExtent3D& extent) {
/* Not a layered image, nothing to do */
if(subresource.baseArrayLayer == 0 && subresource.layerCount == 1) return;
/* When copying 2D array to 3D, depth is already at the value we want it to
be */
CORRADE_INTERNAL_ASSERT(offset.z == 0 && (extent.depth == 1 || extent.depth == subresource.layerCount));
/* Put the layer info into the third extent dimension instead of the layer
fields, as those seem to be interpreted in a wrong way. However those
still need to be set to values that make sense in total, otherwise nasty
crashes happen.
Fortunately this works for 1D array images as well, and we don't need to
do extra voodoo to detect if the image is 1D to use y / width instead of
z / depth. */
offset.z = subresource.baseArrayLayer;
extent.depth = subresource.layerCount;
subresource.baseArrayLayer = 0;
subresource.layerCount = 1;
}
}
void CommandBuffer::copyImageImplementationDefault(CommandBuffer& self, const CopyImageInfo& info) {
CORRADE_ASSERT(!info->pNext,
"Vk::CommandBuffer::copyImage(): disallowing extraction of CopyImageInfo with non-empty pNext to prevent information loss", );
return (**self._device).CmdCopyImage(self, info->srcImage, info->srcImageLayout, info->dstImage, info->dstImageLayout, info->regionCount, info.vkImageCopies());
}
void CommandBuffer::copyImageImplementationSwiftShader(CommandBuffer& self, const CopyImageInfo& info) {
CORRADE_ASSERT(!info->pNext,
"Vk::CommandBuffer::copyImage(): disallowing extraction of CopyImageInfo with non-empty pNext to prevent information loss", );
Containers::Array<VkImageCopy> copies = info.vkImageCopies();
for(VkImageCopy& copy: copies) {
fixupImageCopySwiftShader(copy.srcSubresource, copy.srcOffset, copy.extent);
fixupImageCopySwiftShader(copy.dstSubresource, copy.dstOffset, copy.extent);
}
return (**self._device).CmdCopyImage(self, info->srcImage, info->srcImageLayout, info->dstImage, info->dstImageLayout, info->regionCount, copies);
}
void CommandBuffer::copyImageImplementationKHR(CommandBuffer& self, const CopyImageInfo& info) {
return (**self._device).CmdCopyImage2KHR(self, info);
}
@ -655,6 +696,18 @@ void CommandBuffer::copyBufferToImageImplementationDefault(CommandBuffer& self,
return (**self._device).CmdCopyBufferToImage(self, info->srcBuffer, info->dstImage, info->dstImageLayout, info->regionCount, info.vkBufferImageCopies());
}
void CommandBuffer::copyBufferToImageImplementationSwiftShader(CommandBuffer& self, const CopyBufferToImageInfo& info) {
CORRADE_ASSERT(!info->pNext,
"Vk::CommandBuffer::copyBufferToImage(): disallowing extraction of CopyBufferToImageInfo with non-empty pNext to prevent information loss", );
Containers::Array<VkBufferImageCopy> copies = info.vkBufferImageCopies();
for(VkBufferImageCopy& copy: copies) {
fixupImageCopySwiftShader(copy.imageSubresource, copy.imageOffset, copy.imageExtent);
}
return (**self._device).CmdCopyBufferToImage(self, info->srcBuffer, info->dstImage, info->dstImageLayout, info->regionCount, copies);
}
void CommandBuffer::copyBufferToImageImplementationKHR(CommandBuffer& self, const CopyBufferToImageInfo& info) {
return (**self._device).CmdCopyBufferToImage2KHR(self, info);
}
@ -670,6 +723,18 @@ void CommandBuffer::copyImageToBufferImplementationDefault(CommandBuffer& self,
return (**self._device).CmdCopyImageToBuffer(self, info->srcImage, info->srcImageLayout, info->dstBuffer, info->regionCount, info.vkBufferImageCopies());
}
void CommandBuffer::copyImageToBufferImplementationSwiftShader(CommandBuffer& self, const CopyImageToBufferInfo& info) {
CORRADE_ASSERT(!info->pNext,
"Vk::CommandBuffer::copyImageToBuffer(): disallowing extraction of CopyImageToBufferInfo with non-empty pNext to prevent information loss", );
Containers::Array<VkBufferImageCopy> copies = info.vkBufferImageCopies();
for(VkBufferImageCopy& copy: copies) {
fixupImageCopySwiftShader(copy.imageSubresource, copy.imageOffset, copy.imageExtent);
}
return (**self._device).CmdCopyImageToBuffer(self, info->srcImage, info->srcImageLayout, info->dstBuffer, info->regionCount, copies);
}
void CommandBuffer::copyImageToBufferImplementationKHR(CommandBuffer& self, const CopyImageToBufferInfo& info) {
return (**self._device).CmdCopyImageToBuffer2KHR(self, info);
}

4
src/Magnum/Vk/Implementation/Arguments.cpp

@ -31,7 +31,8 @@ namespace Magnum { namespace Vk { namespace Implementation {
Utility::Arguments arguments() {
Utility::Arguments args{"magnum"};
args.addOption("disable-layers").setHelp("disable-layers", "Vulkan layers to disable", "LIST")
args.addOption("disable-workarounds").setHelp("disable-workarounds", "Vulkan driver workarounds to disable\n (see https://doc.magnum.graphics/magnum/vulkan-workarounds.html for detailed info)", "LIST")
.addOption("disable-layers").setHelp("disable-layers", "Vulkan layers to disable", "LIST")
.addOption("disable-extensions").setHelp("disable-extensions", "Vulkan instance or device extensions to disable", "LIST")
.addOption("enable-layers").setHelp("enable-layers", "Vulkan layers to enable in addition to the defaults and what the application requests", "LIST")
.addOption("enable-instance-extensions").setHelp("enable-instance-extensions", "Vulkan instance extensions to enable in addition to the defaults and what the application requests", "LIST")
@ -39,6 +40,7 @@ Utility::Arguments arguments() {
.addOption("vulkan-version").setHelp("vulkan-version", "force Vulkan version", "X.Y")
.addOption("log", "default").setHelp("log", "console logging", "default|quiet|verbose")
.addOption("device").setHelp("device", "device to use", "ID|integrated|discrete|virtual|cpu")
.setFromEnvironment("disable-workarounds")
.setFromEnvironment("disable-layers")
.setFromEnvironment("disable-extensions")
.setFromEnvironment("enable-layers")

23
src/Magnum/Vk/Implementation/DeviceState.cpp

@ -28,14 +28,18 @@
#include "Magnum/Vk/Buffer.h"
#include "Magnum/Vk/CommandBuffer.h"
#include "Magnum/Vk/Device.h"
#include "Magnum/Vk/DeviceProperties.h"
#include "Magnum/Vk/Extensions.h"
#include "Magnum/Vk/Image.h"
#include "Magnum/Vk/RenderPass.h"
#include "Magnum/Vk/Version.h"
#include "Magnum/Vk/Implementation/DriverWorkaround.h"
namespace Magnum { namespace Vk { namespace Implementation {
DeviceState::DeviceState(Device& device) {
using namespace Containers::Literals;
DeviceState::DeviceState(Device& device, Containers::Array<std::pair<Containers::StringView, bool>>& encounteredWorkarounds) {
if(device.isVersionSupported(Version::Vk11)) {
getDeviceQueueImplementation = &Device::getQueueImplementation11;
} else {
@ -88,9 +92,20 @@ DeviceState::DeviceState(Device& device) {
cmdCopyImageToBufferImplementation = &CommandBuffer::copyImageToBufferImplementationKHR;
} else {
cmdCopyBufferImplementation = &CommandBuffer::copyBufferImplementationDefault;
cmdCopyImageImplementation = &CommandBuffer::copyImageImplementationDefault;
cmdCopyBufferToImageImplementation = &CommandBuffer::copyBufferToImageImplementationDefault;
cmdCopyImageToBufferImplementation = &CommandBuffer::copyImageToBufferImplementationDefault;
/* SwiftShader doesn't implement KHR_copy_commands2 yet so we only need
to work around the classical code path. When it will, the image
array tests will blow up, notifying about this omission (though I
hope the bug gets fixed before KHR_copy_commands2 are implemented). */
if(device.properties().name().hasPrefix("SwiftShader"_s) && !Implementation::isDriverWorkaroundDisabled(encounteredWorkarounds, "swiftshader-image-copy-extent-instead-of-layers"_s)) {
cmdCopyImageImplementation = &CommandBuffer::copyImageImplementationSwiftShader;
cmdCopyBufferToImageImplementation = &CommandBuffer::copyBufferToImageImplementationSwiftShader;
cmdCopyImageToBufferImplementation = &CommandBuffer::copyImageToBufferImplementationSwiftShader;
} else {
cmdCopyImageImplementation = &CommandBuffer::copyImageImplementationDefault;
cmdCopyBufferToImageImplementation = &CommandBuffer::copyBufferToImageImplementationDefault;
cmdCopyImageToBufferImplementation = &CommandBuffer::copyImageToBufferImplementationDefault;
}
}
}

4
src/Magnum/Vk/Implementation/DeviceState.h

@ -25,6 +25,8 @@
DEALINGS IN THE SOFTWARE.
*/
#include <utility>
#include "Magnum/Vk/Vk.h"
#include "Magnum/Vk/Vulkan.h"
@ -37,7 +39,7 @@ class RenderPassCreateInfo;
namespace Implementation {
struct DeviceState {
explicit DeviceState(Device& device);
explicit DeviceState(Device& device, Containers::Array<std::pair<Containers::StringView, bool>>& encounteredWorkarounds);
void(*getDeviceQueueImplementation)(Device&, const VkDeviceQueueInfo2&, VkQueue&);

114
src/Magnum/Vk/Implementation/DriverWorkaround.cpp

@ -0,0 +1,114 @@
/*
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/GrowableArray.h>
#include <Corrade/Containers/StringView.h>
#include <Corrade/Utility/Debug.h>
#include "Magnum/Magnum.h"
#include "Magnum/Vk/Implementation/DriverWorkaround.h"
namespace Magnum { namespace Vk { namespace Implementation {
namespace {
using namespace Containers::Literals;
/* Search the code for the following strings to see where they are implemented */
constexpr Containers::StringView KnownWorkarounds[]{
/* [workarounds] */
/* For layered image copies, SwiftShader (5.0? the version reporting is messy)
expects the layer offsets/counts to be included as second/third dimension of
the image offset/extent instead. Actually, having the Vulkan API contain
just 3D offset and extent with no layer offset/count would make more sense
to me as well -- the last dimension can be either in the offset/extent or
layer offset/count, but never in both, so the extra fields feel redundant.
Or maybe it's reserving space for layered 3D images? */
"swiftshader-image-copy-extent-instead-of-layers"_s,
/* [workarounds] */
};
/* I could use std::find(), right? Well, it'd be a whole lot more typing and
an #include <algorithm> *and* #include <iterator> or whatever as well,
because apparently ONE CAN'T GET std::begin() / std::end() without including
tens thousands lines of irrelevant shit, FFS.
Also the comparison to array end to discover if it wasn't found is just a
useless verbose crap shit as well, so we'll do better here and return a null
view instead.
Moreover, based on the experience with GL, I don't expect there being too
many workarounds used heavily (10 at most, maybe?) so I won't bother with
some binary search, which needs extra testing effort. */
Containers::StringView findWorkaround(Containers::StringView workaround) {
for(Containers::StringView i: KnownWorkarounds)
if(workaround == i) return i;
return {};
}
}
void disableWorkaround(Containers::Array<std::pair<Containers::StringView, bool>>& encounteredWorkarounds, const Containers::StringView workaround) {
/* Find the workaround. Note that we'll add the found view to the array
and not the passed view, as the found view is guaranteed to stay in
scope */
Containers::StringView found = findWorkaround(workaround);
/* Ignore unknown workarounds */
/** @todo this will probably cause false positives when both GL and Vulkan
is used together? */
if(found.isEmpty()) {
Warning{} << "Vk: unknown workaround" << workaround;
return;
}
arrayAppend(encounteredWorkarounds, Containers::InPlaceInit, found, true);
}
Containers::Array<std::pair<Containers::StringView, bool>> disableAllWorkarounds() {
Containers::Array<std::pair<Containers::StringView, bool>> encounteredWorkarounds;
for(Containers::StringView i: KnownWorkarounds)
arrayAppend(encounteredWorkarounds, Containers::InPlaceInit, i, true);
return encounteredWorkarounds;
}
bool isDriverWorkaroundDisabled(Containers::Array<std::pair<Containers::StringView, bool>>& encounteredWorkarounds, const Containers::StringView workaround) {
/* Find the workaround. Note that we'll add the found view to the array
and not the passed view, as the found view is guaranteed to stay in
scope */
Containers::StringView found = findWorkaround(workaround);
CORRADE_INTERNAL_ASSERT(!found.isEmpty());
/* If the workaround was already asked for or disabled, return its state,
otherwise add it to the list as used one. Here we again cheat a bit and
compare just data pointers instead of the whole string as we store only
the views in the KnownWorkarounds list. */
for(const auto& i: encounteredWorkarounds)
if(i.first.data() == found.data()) return i.second;
arrayAppend(encounteredWorkarounds, Containers::InPlaceInit, found, false);
return false;
}
}}}

43
src/Magnum/Vk/Implementation/DriverWorkaround.h

@ -0,0 +1,43 @@
#ifndef Magnum_Vk_Implementation_DriverWorkaround_h
#define Magnum_Vk_Implementation_DriverWorkaround_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 "Magnum/Magnum.h"
namespace Magnum { namespace Vk { namespace Implementation {
void disableWorkaround(Containers::Array<std::pair<Containers::StringView, bool>>& encounteredWorkarounds, Containers::StringView workaround);
bool isDriverWorkaroundDisabled(Containers::Array<std::pair<Containers::StringView, bool>>& encounteredWorkarounds, Containers::StringView workaround);
/* Used by Device::wrap() -- because device extension setup is outside of our
control and the function doesn't print anything on the output, it's better
to just do nothing at all than silently enabling some subset */
Containers::Array<std::pair<Containers::StringView, bool>> disableAllWorkarounds();
}}}
#endif

5
src/Magnum/Vk/Instance.cpp

@ -91,6 +91,11 @@ InstanceCreateInfo::InstanceCreateInfo(const Int argc, const char** const argv,
_state->version = enumerateInstanceVersion();
_applicationInfo.apiVersion = UnsignedInt(_state ? _state->version : enumerateInstanceVersion());
/** @todo handle disabled workarounds once we need them on the instance
level as well -- also ensure that warnings about unknown workarounds are
printed just once (silence the output on one side), or not? since we're
printing two lists anyway */
/* If there are any disabled layers or extensions, sort them and save for
later -- we'll use them to filter the ones added by the app */
Containers::String disabledLayers = args.value<Containers::String>("disable-layers");

13
src/Magnum/Vk/Instance.h

@ -110,6 +110,7 @@ is reused by a subsequently created @ref Device as well.
@code{.sh}
<application> [--magnum-help]
[--magnum-disable-workarounds LIST]
[--magnum-disable-layers LIST]
[--magnum-disable-extensions LIST]
[--magnum-enable-layers LIST]
@ -124,6 +125,9 @@ Arguments:
- `...` --- main application arguments (see `-h` or `--help` for details)
- `--magnum-help` --- display this help message and exit
- `--magnum-disable-workarounds LIST` --- Vulkan driver workarounds to
disable (see @ref vulkan-workarounds for detailed info) (environment:
`MAGNUM_DISABLE_WORKAROUNDS`)
- `--magnum-disable-layers LIST` --- Vulkan layers to disable, meaning
@ref InstanceCreateInfo::addEnabledLayers() will skip them (environment:
`MAGNUM_DISABLE_LAYERS`)
@ -216,6 +220,15 @@ class MAGNUM_VK_EXPORT Instance {
* things. If @p enabledExtensions empty, the instance will behave as
* if no extensions were enabled.
*
* @m_class{m-note m-danger}
*
* @par
* Due to the extension and layer list being outside of library
* control here, driver bug workarounds are not detected and
* enabled when using this function. Depending on bug severity,
* that may lead to crashes and unexpected behavior that wouldn't
* otherwise happen with an @ref Instance created the usual way.
*
* Note that this function retrieves all instance-specific Vulkan
* function pointers, which is a relatively costly operation. It's thus
* not recommended to call this function repeatedly for creating

73
src/Magnum/Vk/Test/DeviceVkTest.cpp

@ -78,6 +78,7 @@ struct DeviceVkTest: VulkanTester {
void constructTransferDeviceProperties();
void constructExtensionsCommandLineDisable();
void constructExtensionsCommandLineEnable();
void constructWorkaroundsCommandLineDisable();
void constructMultipleQueues();
void constructRawQueue();
void constructFeatureNotSupported();
@ -104,8 +105,8 @@ struct {
bool driverVersionSupported, debugMarkerEnabled, maintenance1Enabled;
const char* log;
} ConstructCommandLineData[] {
/* Shouldn't print anything about version, enabled layers/exts if quier
output is enabled. */
/* Shouldn't print anything about device/version, enabled layers/exts if
quiet output is enabled. */
{"quiet", "quiet, enabled extensions",
Containers::array({"", "--magnum-log", "quiet"}),
Containers::array({"", "--magnum-log", "quiet",
@ -152,6 +153,29 @@ struct {
"Device version: Vulkan {}.{}{}\n"},
};
struct {
const char* name;
bool shouldPassAlways;
Containers::Array<const char*> args;
const char* log;
} ConstructWorkaroundsCommandLineData[] {
{"default", false, nullptr,
"Device: {}\n"
"Device version: Vulkan {}.{}{}\n"
"Using device driver workarounds:\n"
" swiftshader-image-copy-extent-instead-of-layers\n"},
/* Shouldn't print anything if quiet output is enabled */
{"quiet", true,
Containers::array({"",
"--magnum-log", "quiet"}),
""},
{"disabled workarounds", true,
Containers::array({"",
"--magnum-disable-workarounds", "swiftshader-image-copy-extent-instead-of-layers"}),
"Device: {}\n"
"Device version: Vulkan {}.{}{}\n"}
};
DeviceVkTest::DeviceVkTest(): VulkanTester{NoCreate} {
addTests({&DeviceVkTest::createInfoConstruct,
&DeviceVkTest::createInfoConstructNoImplicitExtensions,
@ -180,6 +204,9 @@ DeviceVkTest::DeviceVkTest(): VulkanTester{NoCreate} {
&DeviceVkTest::constructExtensionsCommandLineEnable},
Containers::arraySize(ConstructCommandLineData));
addInstancedTests({&DeviceVkTest::constructWorkaroundsCommandLineDisable},
Containers::arraySize(ConstructWorkaroundsCommandLineData));
addTests({&DeviceVkTest::constructMultipleQueues,
&DeviceVkTest::constructRawQueue,
&DeviceVkTest::constructFeatureNotSupported,
@ -750,7 +777,10 @@ void DeviceVkTest::constructExtensionsCommandLineDisable() {
UnsignedInt minor = versionMinor(deviceProperties.version());
UnsignedInt patch = versionPatch(deviceProperties.version());
/* SwiftShader reports just 1.1 with no patch version, special-case that */
CORRADE_COMPARE(out.str(), Utility::formatString(data.log, deviceProperties.name(), major, minor, patch ? Utility::formatString(".{}", patch) : ""));
std::string expected = Utility::formatString(data.log, deviceProperties.name(), major, minor, patch ? Utility::formatString(".{}", patch) : "");
/* The output might contain a device workaround list, cut that away.
That's tested thoroughly in constructWorkaroundsCommandLineDisable(). */
CORRADE_COMPARE(out.str().substr(0, expected.size()), expected);
/* Verify that the entrypoint is actually (not) loaded as expected, to
avoid all the above reporting being just smoke & mirrors */
@ -804,7 +834,10 @@ void DeviceVkTest::constructExtensionsCommandLineEnable() {
UnsignedInt minor = versionMinor(deviceProperties.version());
UnsignedInt patch = versionPatch(deviceProperties.version());
/* SwiftShader reports just 1.1 with no patch version, special-case that */
CORRADE_COMPARE(out.str(), Utility::formatString(data.log, deviceProperties.name(), major, minor, patch ? Utility::formatString(".{}", patch) : ""));
std::string expected = Utility::formatString(data.log, deviceProperties.name(), major, minor, patch ? Utility::formatString(".{}", patch) : "");
/* The output might contain a device workaround list, cut that away.
That's tested thoroughly in constructWorkaroundsCommandLineDisable(). */
CORRADE_COMPARE(out.str().substr(0, expected.size()), expected);
/* Verify that the entrypoint is actually (not) loaded as expected, to
avoid all the above reporting being just smoke & mirrors */
@ -812,6 +845,38 @@ void DeviceVkTest::constructExtensionsCommandLineEnable() {
CORRADE_COMPARE(!!device->TrimCommandPoolKHR, data.maintenance1Enabled);
}
void DeviceVkTest::constructWorkaroundsCommandLineDisable() {
auto&& data = ConstructWorkaroundsCommandLineData[testCaseInstanceId()];
setTestCaseDescription(data.name);
if(std::getenv("MAGNUM_VULKAN_VERSION"))
CORRADE_SKIP("Can't test with the MAGNUM_VULKAN_VERSION environment variable set");
/* Creating a dedicated instance so we can pass custom args */
Instance instance2{InstanceCreateInfo{Int(data.args.size()), data.args}};
DeviceProperties deviceProperties = pickDevice(instance2);
if(!deviceProperties.name().hasPrefix("SwiftShader"_s) && !data.shouldPassAlways)
CORRADE_SKIP("Workarounds only available on SwiftShader, can't test.");
std::ostringstream out;
Debug redirectOutput{&out};
Queue queue{NoCreate};
Device device{instance2, DeviceCreateInfo{deviceProperties, DeviceCreateInfo::Flag::NoImplicitExtensions}
.addQueues(0, {0.0f}, {queue})
};
CORRADE_VERIFY(device.handle());
/** @todo cleanup when Debug::toString() or some similar utility exists */
UnsignedInt major = versionMajor(deviceProperties.version());
UnsignedInt minor = versionMinor(deviceProperties.version());
UnsignedInt patch = versionPatch(deviceProperties.version());
/* SwiftShader reports just 1.1 with no patch version, special-case that */
CORRADE_COMPARE(out.str(), Utility::formatString(data.log, deviceProperties.name(), major, minor, patch ? Utility::formatString(".{}", patch) : ""));
}
void DeviceVkTest::constructMultipleQueues() {
/* Find a GPU that has at least two queue families and at least four
queues in one family */

91
src/Magnum/Vk/Test/ImageVkTest.cpp

@ -79,6 +79,7 @@ struct ImageVkTest: VulkanTester {
void cmdClearStencilImage();
void cmdCopyImage2D();
void cmdCopyImage2DArrayTo3D();
void cmdCopyImageDisallowedConversion();
void cmdCopyBufferImage1D();
@ -120,6 +121,7 @@ ImageVkTest::ImageVkTest() {
&ImageVkTest::cmdClearStencilImage,
&ImageVkTest::cmdCopyImage2D,
&ImageVkTest::cmdCopyImage2DArrayTo3D,
&ImageVkTest::cmdCopyImageDisallowedConversion,
&ImageVkTest::cmdCopyBufferImage1D,
@ -672,6 +674,95 @@ void ImageVkTest::cmdCopyImage2D() {
"----DdddDdddDdddDddd------------"_s);
}
void ImageVkTest::cmdCopyImage2DArrayTo3D() {
CommandPool pool{device(), CommandPoolCreateInfo{
device().properties().pickQueueFamily(QueueFlag::Graphics)}};
CommandBuffer cmd = pool.allocate();
/* Mostly just to test the assertions in the
swiftshader-image-copy-extent-instead-of-layers workaround, but also if
I actually understand the overcomplicated ambiguous parameters
correctly. Apparently array/3D images can't be linear on SwiftShader,
so going through a buffer instead. */
/* Source buffer */
Buffer a{device(), BufferCreateInfo{
BufferUsage::TransferSource, 8*4*2*4
}, MemoryFlag::HostVisible};
Utility::copy("________________________________"
"________________________________"
"____________AaaaAaaaAaaaAaaa____"
"____________BbbbBbbbBbbbBbbb____"
"____________CcccCcccCcccCccc____"
"____________DdddDdddDdddDddd____"
"________________________________"
"________________________________"_s, a.dedicatedMemory().map());
/* Source 2D array image, created from the buffer */
Image b{device(), ImageCreateInfo2DArray{
ImageUsage::TransferDestination|ImageUsage::TransferSource,
PixelFormat::RGBA8UI, {8, 2, 4}, 1
}, MemoryFlag::DeviceLocal};
/* Destination 3D image, copied to a destination buffer */
Image c{device(), ImageCreateInfo3D{
ImageUsage::TransferDestination|ImageUsage::TransferSource,
PixelFormat::RGBA8UI, {8, 4, 2}, 1
}, MemoryFlag::DeviceLocal};
/* Destination buffer */
Buffer d{device(), BufferCreateInfo{
BufferUsage::TransferDestination, 8*4*2*4
}, MemoryFlag::HostVisible};
cmd.begin()
.pipelineBarrier(PipelineStage::TopOfPipe, PipelineStage::Transfer, {
{Accesses{}, Access::TransferWrite,
ImageLayout::Undefined, ImageLayout::TransferDestination, b},
{Accesses{}, Access::TransferWrite,
ImageLayout::Undefined, ImageLayout::TransferDestination, c}
})
.copyBufferToImage({a, b, ImageLayout::TransferDestination, {
BufferImageCopy2DArray{0, ImageAspect::Color, 0, {{}, {8, 2, 4}}}
}})
.clearColorImage(c, ImageLayout::TransferDestination, Vector4ui{'-'})
.pipelineBarrier(PipelineStage::Transfer, PipelineStage::Transfer, {
{Access::TransferWrite, Access::TransferRead,
ImageLayout::TransferDestination, ImageLayout::TransferSource, b},
{Access::TransferWrite, Access::TransferWrite,
ImageLayout::TransferDestination, ImageLayout::TransferDestination, c}
})
.copyImage({b, ImageLayout::TransferSource, c, ImageLayout::TransferDestination, {
{ImageAspect::Color, 0, 1, 2, {3, 0, 0}, 0, 0, 1, {1, 1, 0}, {4, 2, 2}}
}})
.pipelineBarrier(PipelineStage::Transfer, PipelineStage::Transfer, {
{Access::TransferWrite, Access::TransferRead,
ImageLayout::TransferDestination, ImageLayout::TransferSource, c}
})
.copyImageToBuffer({c, ImageLayout::TransferSource, d, {
BufferImageCopy3D{0, ImageAspect::Color, 0, {{}, {8, 4, 2}}}
}})
.pipelineBarrier(PipelineStage::Transfer, PipelineStage::Host, {
{Access::TransferWrite, Access::HostRead, d}
})
.end();
queue().submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait();
CORRADE_COMPARE(arrayView(d.dedicatedMemory().mapRead()),
"--------------------------------"
"----AaaaAaaaAaaaAaaa------------"
"----BbbbBbbbBbbbBbbb------------"
"--------------------------------"
"--------------------------------"
"----CcccCcccCcccCccc------------"
"----DdddDdddDdddDddd------------"
"--------------------------------"_s);
}
void ImageVkTest::cmdCopyImageDisallowedConversion() {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");

Loading…
Cancel
Save