mirror of
https://github.com/rive-app/rive-cpp.git
synced 2026-01-18 21:21:17 +01:00
fix(Vulkan): Work around a driver issue causing graphical corruption, other minor changes (#11204) b8b0d3e01c
This change primarily works around a driver issue that was causing visual corruption on some newer Adreno-based devices. There are other minor changes as well (displaying the driver version from the bootstrapping code, setting a minimum requirement of Vulkan 1.1 in the renderer) Co-authored-by: Josh Jersild <joshua@rive.app>
This commit is contained in:
@@ -1 +1 @@
|
||||
f38d54d8e466ed5cb2ba1246bc79510ceaa5e6cf
|
||||
b8b0d3e01cb5b85000a1b2f19ab73c3c836154d9
|
||||
|
||||
@@ -71,12 +71,10 @@ public:
|
||||
void hotloadShaders(rive::Span<const uint32_t> spirvData);
|
||||
|
||||
private:
|
||||
RenderContextVulkanImpl(rcp<VulkanContext>,
|
||||
const VkPhysicalDeviceProperties&,
|
||||
const ContextOptions&);
|
||||
RenderContextVulkanImpl(rcp<VulkanContext>, const ContextOptions&);
|
||||
|
||||
// Called outside the constructor so we can use virtual methods.
|
||||
void initGPUObjects(ShaderCompilationMode, uint32_t vendorID);
|
||||
void initGPUObjects(ShaderCompilationMode);
|
||||
|
||||
void prepareToFlush(uint64_t nextFrameNumber,
|
||||
uint64_t safeFrameNumber) override;
|
||||
@@ -232,6 +230,7 @@ private:
|
||||
std::unique_ptr<TessellatePipeline> m_tessellatePipeline;
|
||||
rcp<vkutil::Buffer> m_tessSpanIndexBuffer;
|
||||
rcp<vkutil::Texture2D> m_tessTexture;
|
||||
rcp<vkutil::Texture2D> m_tesselationSyncIssueWorkaroundTexture;
|
||||
rcp<vkutil::Framebuffer> m_tessTextureFramebuffer;
|
||||
|
||||
// Renders feathers to the atlas.
|
||||
|
||||
@@ -31,7 +31,7 @@ struct VulkanFeatures
|
||||
bool fragmentShaderPixelInterlock = false;
|
||||
|
||||
// Indicates a nonconformant driver, like MoltenVK.
|
||||
bool VK_KHR_portability_subset;
|
||||
bool VK_KHR_portability_subset = false;
|
||||
};
|
||||
|
||||
// Wraps a VkDevice, function dispatch table, and VMA library instance.
|
||||
|
||||
@@ -47,10 +47,10 @@ public:
|
||||
#ifndef NDEBUG
|
||||
.desiredValidationType =
|
||||
options.enableVulkanCoreValidationLayers
|
||||
? VulkanValidationType::Core
|
||||
? VulkanValidationType::core
|
||||
: (options.enableVulkanSynchronizationValidationLayers
|
||||
? VulkanValidationType::Synchronization
|
||||
: VulkanValidationType::None),
|
||||
? VulkanValidationType::synchronization
|
||||
: VulkanValidationType::none),
|
||||
.wantDebugCallbacks = !options.disableDebugCallbacks,
|
||||
#endif
|
||||
});
|
||||
|
||||
@@ -73,6 +73,9 @@ private:
|
||||
std::string deviceName;
|
||||
VkPhysicalDeviceType deviceType;
|
||||
uint32_t deviceAPIVersion;
|
||||
uint32_t driverVersionMajor;
|
||||
uint32_t driverVersionMinor;
|
||||
uint32_t driverVersionPatch;
|
||||
};
|
||||
|
||||
static FindDeviceResult findCompatiblePhysicalDevice(
|
||||
|
||||
@@ -7,19 +7,19 @@ namespace rive_vkb
|
||||
{
|
||||
enum class VulkanValidationType
|
||||
{
|
||||
None,
|
||||
Core,
|
||||
Synchronization,
|
||||
none,
|
||||
core,
|
||||
synchronization,
|
||||
};
|
||||
|
||||
#ifndef NDEBUG
|
||||
constexpr bool RIVE_DEFAULT_VULKAN_DEBUG_PREFERENCE = true;
|
||||
constexpr VulkanValidationType RIVE_DEFAULT_VULKAN_VALIDATION_TYPE =
|
||||
VulkanValidationType::Core;
|
||||
VulkanValidationType::core;
|
||||
#else
|
||||
constexpr bool RIVE_DEFAULT_VULKAN_DEBUG_PREFERENCE = false;
|
||||
constexpr VulkanValidationType RIVE_DEFAULT_VULKAN_VALIDATION_TYPE =
|
||||
VulkanValidationType::None;
|
||||
VulkanValidationType::none;
|
||||
#endif
|
||||
|
||||
class VulkanLibrary;
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include "rive/renderer/vulkan/vkutil.hpp"
|
||||
#include "rive_vk_bootstrap/vulkan_device.hpp"
|
||||
#include "rive_vk_bootstrap/vulkan_instance.hpp"
|
||||
#include "shaders/constants.glsl"
|
||||
#include "logging.hpp"
|
||||
#include "vulkan_library.hpp"
|
||||
|
||||
@@ -30,6 +32,36 @@ static const char* physicalDeviceTypeName(VkPhysicalDeviceType type)
|
||||
}
|
||||
}
|
||||
|
||||
void unpackDriverVersion(const VkPhysicalDeviceProperties& props,
|
||||
uint32_t* majorOut,
|
||||
uint32_t* minorOut,
|
||||
uint32_t* patchOut)
|
||||
{
|
||||
if (props.vendorID == VULKAN_VENDOR_NVIDIA)
|
||||
{
|
||||
// NVidia uses 10|8|8|6 encoding for driver version. We'll ignore the
|
||||
// fourth version section.
|
||||
*majorOut = props.driverVersion >> 22;
|
||||
*minorOut = (props.driverVersion >> 14) & 0xff;
|
||||
*patchOut = (props.driverVersion >> 6) & 0xff;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
else if (props.vendorID == VULKAN_VENDOR_INTEL)
|
||||
{
|
||||
*majorOut = props.driverVersion >> 14;
|
||||
*minorOut = props.driverVersion & 0x3fff;
|
||||
*patchOut = 0;
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
// Everything else seems to use the standard Vulkan encoding.
|
||||
*majorOut = VK_API_VERSION_MAJOR(props.driverVersion);
|
||||
*minorOut = VK_API_VERSION_MINOR(props.driverVersion);
|
||||
*patchOut = VK_API_VERSION_PATCH(props.driverVersion);
|
||||
}
|
||||
}
|
||||
|
||||
VulkanDevice::VulkanDevice(VulkanInstance& instance, const Options& opts)
|
||||
{
|
||||
assert(!opts.headless ||
|
||||
@@ -220,12 +252,15 @@ VulkanDevice::VulkanDevice(VulkanInstance& instance, const Options& opts)
|
||||
LOAD_MEMBER_INSTANCE_FUNC(vkGetPhysicalDeviceSurfaceCapabilitiesKHR,
|
||||
instance);
|
||||
|
||||
printf("==== Vulkan %i.%i.%i GPU (%s): %s [ ",
|
||||
printf("==== Vulkan %i.%i.%i GPU (%s): %s (driver %i.%i.%i) [ ",
|
||||
VK_API_VERSION_MAJOR(m_riveVulkanFeatures.apiVersion),
|
||||
VK_API_VERSION_MINOR(m_riveVulkanFeatures.apiVersion),
|
||||
VK_API_VERSION_PATCH(m_riveVulkanFeatures.apiVersion),
|
||||
physicalDeviceTypeName(findResult.deviceType),
|
||||
findResult.deviceName.c_str());
|
||||
findResult.deviceName.c_str(),
|
||||
findResult.driverVersionMajor,
|
||||
findResult.driverVersionMinor,
|
||||
findResult.driverVersionPatch);
|
||||
struct CommaSeparator
|
||||
{
|
||||
const char* m_separator = "";
|
||||
@@ -382,11 +417,16 @@ VulkanDevice::FindDeviceResult VulkanDevice::findCompatiblePhysicalDevice(
|
||||
|
||||
if (strstr(props.deviceName, nameFilter) != nullptr)
|
||||
{
|
||||
uint32_t major, minor, patch;
|
||||
unpackDriverVersion(props, &major, &minor, &patch);
|
||||
matchResult = {
|
||||
.physicalDevice = device,
|
||||
.deviceName = props.deviceName,
|
||||
.deviceType = props.deviceType,
|
||||
.deviceAPIVersion = props.apiVersion,
|
||||
.driverVersionMajor = major,
|
||||
.driverVersionMinor = minor,
|
||||
.driverVersionPatch = patch,
|
||||
};
|
||||
matchedDeviceNames.push_back(std::string{props.deviceName});
|
||||
}
|
||||
@@ -444,11 +484,17 @@ VulkanDevice::FindDeviceResult VulkanDevice::findCompatiblePhysicalDevice(
|
||||
if (!onlyAcceptDesiredType ||
|
||||
props.deviceType == desiredDeviceType)
|
||||
{
|
||||
uint32_t major, minor, patch;
|
||||
unpackDriverVersion(props, &major, &minor, &patch);
|
||||
|
||||
return {
|
||||
.physicalDevice = device,
|
||||
.deviceName = props.deviceName,
|
||||
.deviceType = props.deviceType,
|
||||
.deviceAPIVersion = props.apiVersion,
|
||||
.driverVersionMajor = major,
|
||||
.driverVersionMinor = minor,
|
||||
.driverVersionPatch = patch,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,23 +168,26 @@ VulkanInstance::VulkanInstance(const Options& opts)
|
||||
#endif
|
||||
|
||||
bool useFallbackDebugCallbacks = false;
|
||||
if (validationType != VulkanValidationType::None)
|
||||
if (validationType != VulkanValidationType::none)
|
||||
{
|
||||
if (!add_layer_if_supported("VK_LAYER_KHRONOS_validation"))
|
||||
{
|
||||
LOG_ERROR_LINE("WARNING: Validation layers are not supported. "
|
||||
"Creating context without validation layers.\n");
|
||||
validationType = VulkanValidationType::None;
|
||||
validationType = VulkanValidationType::none;
|
||||
}
|
||||
else
|
||||
else if (validationType != VulkanValidationType::core)
|
||||
{
|
||||
// If we're doing any other type of validation, add the settings
|
||||
// extension. (RenderDoc does not work when this extension is,
|
||||
// enabled, which is why we don't always enable it)
|
||||
extensions.push_back(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
if (enableDebugCallbacks)
|
||||
{
|
||||
if (validationType != VulkanValidationType::None)
|
||||
if (validationType != VulkanValidationType::none)
|
||||
{
|
||||
// If we are enabling validation layers, VK_EXT_debug_utils willl
|
||||
// come along with that, even though it currently isn't in the
|
||||
@@ -258,7 +261,7 @@ VulkanInstance::VulkanInstance(const Options& opts)
|
||||
.pSettings = settingsForSyncValidate,
|
||||
};
|
||||
|
||||
if (validationType == VulkanValidationType::Synchronization)
|
||||
if (validationType == VulkanValidationType::synchronization)
|
||||
{
|
||||
validationLayerSettingsForSyncValidation.pNext = createInfo.pNext;
|
||||
createInfo.pNext = &validationLayerSettingsForSyncValidation;
|
||||
|
||||
@@ -50,7 +50,6 @@ static VkFilter vk_filter(rive::ImageFilter option)
|
||||
|
||||
PipelineManagerVulkan::PipelineManagerVulkan(rcp<VulkanContext> vk,
|
||||
ShaderCompilationMode mode,
|
||||
uint32_t vendorID,
|
||||
VkImageView nullTextureView) :
|
||||
Super(mode),
|
||||
m_vk(std::move(vk)),
|
||||
@@ -58,8 +57,7 @@ PipelineManagerVulkan::PipelineManagerVulkan(rcp<VulkanContext> vk,
|
||||
VK_FORMAT_R32_SFLOAT,
|
||||
VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT)
|
||||
? VK_FORMAT_R32_SFLOAT
|
||||
: VK_FORMAT_R16_SFLOAT),
|
||||
m_vendorID(vendorID)
|
||||
: VK_FORMAT_R16_SFLOAT)
|
||||
{
|
||||
// Create the immutable samplers.
|
||||
VkSamplerCreateInfo linearSamplerCreateInfo = {
|
||||
|
||||
@@ -17,7 +17,6 @@ class PipelineManagerVulkan : public AsyncPipelineManager<DrawPipelineVulkan>
|
||||
public:
|
||||
PipelineManagerVulkan(rcp<VulkanContext>,
|
||||
ShaderCompilationMode,
|
||||
uint32_t vendorID,
|
||||
VkImageView nullTextureView);
|
||||
~PipelineManagerVulkan();
|
||||
|
||||
@@ -31,7 +30,11 @@ public:
|
||||
InterlockMode,
|
||||
DrawPipelineLayoutVulkan::Options);
|
||||
|
||||
uint32_t vendorID() const { return m_vendorID; }
|
||||
uint32_t vendorID() const
|
||||
{
|
||||
return m_vk->physicalDeviceProperties().vendorID;
|
||||
}
|
||||
|
||||
VkFormat atlasFormat() const { return m_atlasFormat; }
|
||||
|
||||
VulkanContext* vulkanContext() const { return m_vk.get(); }
|
||||
@@ -110,7 +113,6 @@ private:
|
||||
|
||||
rcp<VulkanContext> m_vk;
|
||||
VkFormat m_atlasFormat;
|
||||
uint32_t m_vendorID;
|
||||
|
||||
// Samplers.
|
||||
VkSampler m_linearSampler;
|
||||
|
||||
@@ -674,7 +674,6 @@ private:
|
||||
|
||||
RenderContextVulkanImpl::RenderContextVulkanImpl(
|
||||
rcp<VulkanContext> vk,
|
||||
const VkPhysicalDeviceProperties& physicalDeviceProps,
|
||||
const ContextOptions& contextOptions) :
|
||||
m_vk(std::move(vk)),
|
||||
m_flushUniformBufferPool(m_vk, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT),
|
||||
@@ -688,6 +687,8 @@ RenderContextVulkanImpl::RenderContextVulkanImpl(
|
||||
m_triangleBufferPool(m_vk, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT),
|
||||
m_descriptorSetPoolPool(make_rcp<DescriptorSetPoolPool>(m_vk))
|
||||
{
|
||||
const auto& physicalDeviceProps = m_vk->physicalDeviceProperties();
|
||||
|
||||
m_platformFeatures.supportsRasterOrderingMode =
|
||||
!contextOptions.forceAtomicMode &&
|
||||
m_vk->features.rasterizationOrderColorAttachmentAccess;
|
||||
@@ -787,8 +788,7 @@ RenderContextVulkanImpl::RenderContextVulkanImpl(
|
||||
}
|
||||
|
||||
void RenderContextVulkanImpl::initGPUObjects(
|
||||
ShaderCompilationMode shaderCompilationMode,
|
||||
uint32_t vendorID)
|
||||
ShaderCompilationMode shaderCompilationMode)
|
||||
{
|
||||
// Bound when there is not an image paint.
|
||||
constexpr static uint8_t black[] = {0, 0, 0, 1};
|
||||
@@ -800,10 +800,28 @@ void RenderContextVulkanImpl::initGPUObjects(
|
||||
"null image texture");
|
||||
m_nullImageTexture->scheduleUpload(black, sizeof(black));
|
||||
|
||||
if (strstr(m_vk->physicalDeviceProperties().deviceName, "Adreno (TM) 8") !=
|
||||
nullptr)
|
||||
{
|
||||
// The Adreno 8s (at least on the Galaxy S25) have a strange
|
||||
// synchronization issue around our tesselation texture, where the
|
||||
// barriers appear to not work properly (leading to tesselation texture
|
||||
// corruption, even across frames).
|
||||
// We can do a blit to a 1x1 texture, however, which seems to make the
|
||||
// synchronization play nice.
|
||||
m_tesselationSyncIssueWorkaroundTexture = m_vk->makeTexture2D(
|
||||
{
|
||||
.format = VK_FORMAT_R8G8B8A8_UINT,
|
||||
.extent = {1, 1},
|
||||
.usage = VK_IMAGE_USAGE_SAMPLED_BIT |
|
||||
VK_IMAGE_USAGE_TRANSFER_DST_BIT,
|
||||
},
|
||||
"tesselation sync bug workaround texture");
|
||||
}
|
||||
|
||||
m_pipelineManager = std::make_unique<PipelineManagerVulkan>(
|
||||
m_vk,
|
||||
shaderCompilationMode,
|
||||
vendorID,
|
||||
m_nullImageTexture->vkImageView());
|
||||
|
||||
// The pipelines reference our vulkan objects. Delete them first.
|
||||
@@ -955,12 +973,21 @@ void RenderContextVulkanImpl::resizeTessellationTexture(uint32_t width,
|
||||
width = std::max(width, 1u);
|
||||
height = std::max(height, 1u);
|
||||
|
||||
VkImageUsageFlags usage =
|
||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
|
||||
// If we are doing the Adreno synchronization workaround we also need the
|
||||
// TRANSFER_SRC bit
|
||||
if (m_tesselationSyncIssueWorkaroundTexture != nullptr)
|
||||
{
|
||||
usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
}
|
||||
|
||||
m_tessTexture = m_vk->makeTexture2D(
|
||||
{
|
||||
.format = VK_FORMAT_R32G32B32A32_UINT,
|
||||
.extent = {width, height},
|
||||
.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
|
||||
VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
.usage = usage,
|
||||
},
|
||||
"tesselation texture");
|
||||
|
||||
@@ -1746,6 +1773,54 @@ void RenderContextVulkanImpl::flush(const FlushDescriptor& desc)
|
||||
// VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL.
|
||||
m_tessTexture->lastAccess().layout =
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
|
||||
if (m_tesselationSyncIssueWorkaroundTexture != nullptr)
|
||||
{
|
||||
// On the Adreno 8xx series drivers we've encountered, there is some
|
||||
// sort of synchronization issue with the tesselation texture that
|
||||
// causes the barriers to not work, and it ends up being corrupted.
|
||||
// However, if we first just blit it to an offscreen texture (just a
|
||||
// 1x1 texture), the render corruption goes away.
|
||||
m_tessTexture->barrier(
|
||||
commandBuffer,
|
||||
{
|
||||
.pipelineStages = VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
.accessMask = VK_ACCESS_TRANSFER_READ_BIT,
|
||||
.layout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
});
|
||||
|
||||
// We need this transition but really only one time (if we haven't
|
||||
// done so already)
|
||||
if (m_tesselationSyncIssueWorkaroundTexture->lastAccess().layout !=
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||||
{
|
||||
m_tesselationSyncIssueWorkaroundTexture->barrier(
|
||||
commandBuffer,
|
||||
{
|
||||
.pipelineStages = VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
.accessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
},
|
||||
vkutil::ImageAccessAction::invalidateContents);
|
||||
}
|
||||
|
||||
m_vk->blitSubRect(
|
||||
commandBuffer,
|
||||
m_tessTexture->vkImage(),
|
||||
VK_IMAGE_LAYOUT_GENERAL,
|
||||
m_tesselationSyncIssueWorkaroundTexture->vkImage(),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
IAABB::MakeWH(
|
||||
m_tesselationSyncIssueWorkaroundTexture->width(),
|
||||
m_tesselationSyncIssueWorkaroundTexture->height()));
|
||||
|
||||
// NOTE: Technically there should be a barrier after this blit to
|
||||
// prevent a write-after-write hazard. However, we don't use this
|
||||
// texture at all and thus don't care if we overwrite it, so there
|
||||
// is intentionally no barrier here...but you will get a failure on
|
||||
// this texture if you enable synchronization validation on a device
|
||||
// with the workaround enabled.
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the tessellation texture has finished rendering before the path
|
||||
@@ -2909,22 +2984,28 @@ std::unique_ptr<RenderContext> RenderContextVulkanImpl::MakeContext(
|
||||
device,
|
||||
features,
|
||||
pfnvkGetInstanceProcAddr);
|
||||
VkPhysicalDeviceProperties physicalDeviceProps;
|
||||
vk->GetPhysicalDeviceProperties(vk->physicalDevice, &physicalDeviceProps);
|
||||
|
||||
if (vk->physicalDeviceProperties().apiVersion < VK_API_VERSION_1_1)
|
||||
{
|
||||
fprintf(
|
||||
stderr,
|
||||
"ERROR: Rive Vulkan renderer requires a driver that supports at least Vulkan 1.1.\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<RenderContextVulkanImpl> impl(
|
||||
new RenderContextVulkanImpl(std::move(vk),
|
||||
physicalDeviceProps,
|
||||
contextOptions));
|
||||
new RenderContextVulkanImpl(std::move(vk), contextOptions));
|
||||
|
||||
if (contextOptions.forceAtomicMode &&
|
||||
!impl->platformFeatures().supportsClockwiseAtomicMode)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"ERROR: Requested \"atomic\" mode but Vulkan does not support "
|
||||
"fragmentStoresAndAtomics on this platform.\n");
|
||||
fprintf(
|
||||
stderr,
|
||||
"ERROR: Requested \"atomic\" mode but Vulkan does not support fragmentStoresAndAtomics on this platform.\n");
|
||||
return nullptr;
|
||||
}
|
||||
impl->initGPUObjects(contextOptions.shaderCompilationMode,
|
||||
physicalDeviceProps.vendorID);
|
||||
|
||||
impl->initGPUObjects(contextOptions.shaderCompilationMode);
|
||||
return std::make_unique<RenderContext>(std::move(impl));
|
||||
}
|
||||
} // namespace rive::gpu
|
||||
|
||||
@@ -65,10 +65,10 @@ public:
|
||||
#ifndef NDEBUG
|
||||
.desiredValidationType =
|
||||
m_backendParams.disableValidationLayers
|
||||
? VulkanValidationType::None
|
||||
? VulkanValidationType::none
|
||||
: (m_backendParams.wantVulkanSynchronizationValidation
|
||||
? VulkanValidationType::Synchronization
|
||||
: VulkanValidationType::Core),
|
||||
? VulkanValidationType::synchronization
|
||||
: VulkanValidationType::core),
|
||||
.wantDebugCallbacks = !m_backendParams.disableValidationLayers,
|
||||
#endif
|
||||
});
|
||||
|
||||
@@ -38,10 +38,10 @@ public:
|
||||
#ifndef NDEBUG
|
||||
.desiredValidationType =
|
||||
m_backendParams.disableValidationLayers
|
||||
? VulkanValidationType::None
|
||||
? VulkanValidationType::none
|
||||
: (m_backendParams.wantVulkanSynchronizationValidation
|
||||
? VulkanValidationType::Synchronization
|
||||
: VulkanValidationType::Core),
|
||||
? VulkanValidationType::synchronization
|
||||
: VulkanValidationType::core),
|
||||
.wantDebugCallbacks = !m_backendParams.disableDebugCallbacks,
|
||||
#endif
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user