Files
filament/filament/backend/src/vulkan/VulkanDriver.cpp
T

2074 lines
81 KiB
C++

/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "VulkanDriver.h"
#include "CommandStreamDispatcher.h"
#include "SystraceProfile.h"
#include "VulkanAsyncHandles.h"
#include "VulkanBufferCache.h"
#include "VulkanBufferProxy.h"
#include "VulkanCommands.h"
#include "VulkanDriverFactory.h"
#include "VulkanHandles.h"
#include "VulkanMemory.h"
#include "VulkanTexture.h"
#include "vulkan/VulkanSamplerCache.h"
#include "vulkan/memory/ResourceManager.h"
#include "vulkan/memory/ResourcePointer.h"
#include "vulkan/utils/Conversion.h"
#include "vulkan/utils/Definitions.h"
#include <backend/DriverEnums.h>
#include <backend/platforms/VulkanPlatform.h>
#include <utils/CString.h>
#include <utils/Panic.h>
#ifndef NDEBUG
#include <set> // For VulkanDriver::debugCommandBegin
#endif
using namespace bluevk;
#if defined(__clang__)
// Vulkan functions often immediately dereference pointers, so it's fine to pass in a pointer
// to a stack-allocated variable.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreturn-stack-address"
#pragma clang diagnostic ignored "-Wunused-parameter"
#endif
namespace filament::backend {
namespace {
VmaAllocator createAllocator(VkInstance instance, VkPhysicalDevice physicalDevice,
VkDevice device) {
VmaAllocator allocator;
VmaVulkanFunctions const funcs {
#if VMA_DYNAMIC_VULKAN_FUNCTIONS
.vkGetInstanceProcAddr = vkGetInstanceProcAddr,
.vkGetDeviceProcAddr = vkGetDeviceProcAddr,
#else
.vkGetPhysicalDeviceProperties = vkGetPhysicalDeviceProperties,
.vkGetPhysicalDeviceMemoryProperties = vkGetPhysicalDeviceMemoryProperties,
.vkAllocateMemory = vkAllocateMemory,
.vkFreeMemory = vkFreeMemory,
.vkMapMemory = vkMapMemory,
.vkUnmapMemory = vkUnmapMemory,
.vkFlushMappedMemoryRanges = vkFlushMappedMemoryRanges,
.vkInvalidateMappedMemoryRanges = vkInvalidateMappedMemoryRanges,
.vkBindBufferMemory = vkBindBufferMemory,
.vkBindImageMemory = vkBindImageMemory,
.vkGetBufferMemoryRequirements = vkGetBufferMemoryRequirements,
.vkGetImageMemoryRequirements = vkGetImageMemoryRequirements,
.vkCreateBuffer = vkCreateBuffer,
.vkDestroyBuffer = vkDestroyBuffer,
.vkCreateImage = vkCreateImage,
.vkDestroyImage = vkDestroyImage,
.vkCmdCopyBuffer = vkCmdCopyBuffer,
.vkGetBufferMemoryRequirements2KHR = vkGetBufferMemoryRequirements2KHR,
.vkGetImageMemoryRequirements2KHR = vkGetImageMemoryRequirements2KHR
#endif
};
VmaAllocatorCreateInfo const allocatorInfo{
// Disable the internal VMA synchronization because the backend is singled threaded.
// Improve CPU performance when using VMA functions. The backend will guarantee that all
// access to VMA is done in a thread safe way.
.flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT,
.physicalDevice = physicalDevice,
.device = device,
.pVulkanFunctions = &funcs,
.instance = instance,
};
vmaCreateAllocator(&allocatorInfo, &allocator);
return allocator;
}
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
VKAPI_ATTR VkBool32 VKAPI_CALL debugReportCallback(VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location,
int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData) {
if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
FVK_LOGE << "VULKAN ERROR: (" << pLayerPrefix << ") " << pMessage;
} else {
// TODO: emit best practices warnings about aggressive pipeline barriers.
if (strstr(pMessage, "ALL_GRAPHICS_BIT") || strstr(pMessage, "ALL_COMMANDS_BIT")) {
return VK_FALSE;
}
FVK_LOGW << "VULKAN WARNING: (" << pLayerPrefix << ") " << pMessage;
}
FVK_LOGE;
return VK_FALSE;
}
#endif // FVK_ENABLED(FVK_DEBUG_VALIDATION)
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
VkDebugUtilsMessageTypeFlagsEXT types, const VkDebugUtilsMessengerCallbackDataEXT* cbdata,
void* pUserData) {
if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
FVK_LOGE << "VULKAN ERROR: (" << cbdata->pMessageIdName << ") " << cbdata->pMessage;
} else {
// TODO: emit best practices warnings about aggressive pipeline barriers.
if (strstr(cbdata->pMessage, "ALL_GRAPHICS_BIT")
|| strstr(cbdata->pMessage, "ALL_COMMANDS_BIT")) {
return VK_FALSE;
}
FVK_LOGW << "VULKAN WARNING: (" << cbdata->pMessageIdName << ") " << cbdata->pMessage;
}
FVK_LOGE << "";
return VK_FALSE;
}
#endif // FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
}// anonymous namespace
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
using DebugUtils = VulkanDriver::DebugUtils;
DebugUtils* DebugUtils::mSingleton = nullptr;
DebugUtils::DebugUtils(VkInstance instance, VkDevice device, VulkanContext const& context)
: mInstance(instance),
mDevice(device),
mEnabled(context.isDebugUtilsSupported()) {
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
// Also initialize debug utils messenger here
if (mEnabled) {
VkDebugUtilsMessengerCreateInfoEXT const createInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
.pNext = nullptr,
.flags = 0,
.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
| VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT,
.pfnUserCallback = debugUtilsCallback,
};
VkResult result = vkCreateDebugUtilsMessengerEXT(instance, &createInfo,
VKALLOC, &mDebugMessenger);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "Unable to create Vulkan debug messenger. error="
<< static_cast<int32_t>(result);
}
#endif // FVK_ENABLED(FVK_DEBUG_VALIDATION)
}
DebugUtils* DebugUtils::get() {
assert_invariant(DebugUtils::mSingleton);
return DebugUtils::mSingleton;
}
DebugUtils::~DebugUtils() {
if (mDebugMessenger) {
vkDestroyDebugUtilsMessengerEXT(mInstance, mDebugMessenger, VKALLOC);
}
}
void DebugUtils::setName(VkObjectType type, uint64_t handle, char const* name) {
auto impl = DebugUtils::get();
if (!impl->mEnabled) {
return;
}
VkDebugUtilsObjectNameInfoEXT const info = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
.pNext = nullptr,
.objectType = type,
.objectHandle = handle,
.pObjectName = name,
};
vkSetDebugUtilsObjectNameEXT(impl->mDevice, &info);
}
#endif // FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
Dispatcher VulkanDriver::getDispatcher() const noexcept {
return ConcreteDispatcher<VulkanDriver>::make();
}
VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
Platform::DriverConfig const& driverConfig)
: mPlatform(platform),
mResourceManager(driverConfig.handleArenaSize, driverConfig.disableHandleUseAfterFreeCheck,
driverConfig.disableHeapHandleTags),
mAllocator(createAllocator(mPlatform->getInstance(), mPlatform->getPhysicalDevice(),
mPlatform->getDevice())),
mContext(context),
mCommands(mPlatform->getDevice(), mPlatform->getGraphicsQueue(),
mPlatform->getGraphicsQueueFamilyIndex(), mPlatform->getProtectedGraphicsQueue(),
mPlatform->getProtectedGraphicsQueueFamilyIndex(), mContext),
mPipelineLayoutCache(mPlatform->getDevice()),
mPipelineCache(mPlatform->getDevice()),
mStagePool(mAllocator, &mResourceManager, &mCommands, &mContext.getPhysicalDeviceLimits()),
mBufferCache(mContext, mResourceManager, mAllocator),
mFramebufferCache(mPlatform->getDevice()),
mYcbcrConversionCache(mPlatform->getDevice()),
mSamplerCache(mPlatform->getDevice()),
mBlitter(mPlatform->getPhysicalDevice(), &mCommands),
mReadPixels(mPlatform->getDevice()),
mDescriptorSetLayoutCache(mPlatform->getDevice(), &mResourceManager),
mDescriptorSetCache(mPlatform->getDevice(), &mResourceManager),
mQueryManager(mPlatform->getDevice()),
mExternalImageManager(platform, &mSamplerCache, &mYcbcrConversionCache, &mDescriptorSetCache,
&mDescriptorSetLayoutCache),
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
mStereoscopicType(driverConfig.stereoscopicType) {
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
DebugUtils::mSingleton =
new DebugUtils(mPlatform->getInstance(), mPlatform->getDevice(), mContext);
#endif
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
UTILS_UNUSED const PFN_vkCreateDebugReportCallbackEXT createDebugReportCallback
= vkCreateDebugReportCallbackEXT;
if (!mContext.isDebugUtilsSupported() && createDebugReportCallback) {
VkDebugReportCallbackCreateInfoEXT const cbinfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT,
.flags = VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_ERROR_BIT_EXT,
.pfnCallback = debugReportCallback,
};
VkResult result = createDebugReportCallback(mPlatform->getInstance(), &cbinfo, VKALLOC,
&mDebugCallback);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "Unable to create Vulkan debug callback."
<< " error=" << static_cast<int32_t>(result);
}
#endif
}
VulkanDriver::~VulkanDriver() noexcept = default;
UTILS_NOINLINE
Driver* VulkanDriver::create(VulkanPlatform* platform, VulkanContext& context,
Platform::DriverConfig const& driverConfig) {
#if 0
// this is useful for development, but too verbose even for debug builds
// For reference on a 64-bits machine in Release mode:
// HwStream : 24 few
// VulkanFence : 32 few
// VulkanProgram : 32 moderate
// VulkanIndexBuffer : 64 moderate
// VulkanBufferObject : 64 many
// -- less than or equal 64 bytes
// VulkanRenderPrimitive : 72 many
// VulkanVertexBufferInfo : 96 moderate
// VulkanSwapChain : 104 few
// VulkanTimerQuery : 160 few
// -- less than or equal 160 bytes
// VulkanVertexBuffer : 192 moderate
// VulkanTexture : 224 moderate
// VulkanRenderTarget : 312 few
// -- less than or equal to 312 bytes
FVK_LOGD << "VulkanSwapChain: " << sizeof(VulkanSwapChain);
FVK_LOGD << "VulkanBufferObject: " << sizeof(VulkanBufferObject);
FVK_LOGD << "VulkanVertexBuffer: " << sizeof(VulkanVertexBuffer);
FVK_LOGD << "VulkanVertexBufferInfo: " << sizeof(VulkanVertexBufferInfo);
FVK_LOGD << "VulkanIndexBuffer: " << sizeof(VulkanIndexBuffer);
FVK_LOGD << "VulkanRenderPrimitive: " << sizeof(VulkanRenderPrimitive);
FVK_LOGD << "VulkanTexture: " << sizeof(VulkanTexture);
FVK_LOGD << "VulkanTimerQuery: " << sizeof(VulkanTimerQuery);
FVK_LOGD << "HwStream: " << sizeof(HwStream);
FVK_LOGD << "VulkanRenderTarget: " << sizeof(VulkanRenderTarget);
FVK_LOGD << "VulkanFence: " << sizeof(VulkanFence);
FVK_LOGD << "VulkanProgram: " << sizeof(VulkanProgram);
#endif
assert_invariant(platform);
size_t defaultSize = FVK_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
Platform::DriverConfig validConfig {driverConfig};
validConfig.handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize);
return new VulkanDriver(platform, context, validConfig);
}
ShaderModel VulkanDriver::getShaderModel() const noexcept {
#if defined(__ANDROID__) || defined(FILAMENT_IOS)
return ShaderModel::MOBILE;
#else
return ShaderModel::DESKTOP;
#endif
}
ShaderLanguage VulkanDriver::getShaderLanguage() const noexcept {
return ShaderLanguage::SPIRV;
}
void VulkanDriver::terminate() {
// Flush and wait here to make sure all queued commands are executed and resources that are tied
// to those commands are no longer referenced.
finish(0);
mCurrentSwapChain = {};
mDefaultRenderTarget = {};
mPipelineState = {};
mQueryManager.terminate();
mBlitter.terminate();
mReadPixels.terminate();
// Allow the stage pool to clean up.
mStagePool.gc();
mCommands.terminate();
// Must come before samplerCache, ycbcrConversionCache, descriptorSetCache,
// descriptorSetLayoutCache
mExternalImageManager.terminate();
mPipelineCache.terminate();
mFramebufferCache.terminate();
mSamplerCache.terminate();
mDescriptorSetLayoutCache.terminate();
mDescriptorSetCache.terminate();
mPipelineLayoutCache.terminate();
// Before terminating ResourceManager, we must make sure all of the resource_ptrs have been unset.
mResourceManager.terminate();
// Must come after `mResourceManager`.
// Before terminating the memory pool, we must make sure all the VulkanBufferMemory are yielded
// back to the pool.
mBufferCache.terminate();
// Before terminating stagePool, we need all resources to have been
// reclaimed, as they perform cleanup within the stage pool.
mStagePool.terminate();
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
mResourceManager.print();
#endif
vmaDestroyAllocator(mAllocator);
if (mDebugCallback) {
vkDestroyDebugReportCallbackEXT(mPlatform->getInstance(), mDebugCallback, VKALLOC);
}
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
assert_invariant(DebugUtils::mSingleton);
delete DebugUtils::mSingleton;
#endif
mPlatform->terminate();
}
void VulkanDriver::tick(int) {
mCommands.updateFences();
}
// Garbage collection should not occur too frequently, only about once per frame. Internally, the
// eviction time of various resources is often measured in terms of an approximate frame number
// rather than the wall clock, because we must wait 3 frames after a DriverAPI-level resource has
// been destroyed for safe destruction, due to outstanding command buffers and triple buffering.
void VulkanDriver::collectGarbage() {
FVK_SYSTRACE_SCOPE();
// Command buffers need to be submitted and completed before other resources can be gc'd.
mCommands.gc();
mDescriptorSetCache.gc();
mStagePool.gc();
mBufferCache.gc();
mFramebufferCache.gc();
mPipelineCache.gc();
mResourceManager.gc();
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
mResourceManager.print();
#endif
}
void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
int64_t refreshIntervalNs, uint32_t frameId) {
FVK_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME);
// Check if any command have finished and reset all its used resources. The resources
// wont be destroyed but their reference count will decreased if the command is already
// completed.
//
// This will let us check if any VulkanBuffer is currently in flight or not.
mCommands.gc();
if (mAppState.hasExternalSamplers()) {
mExternalImageManager.onBeginFrame();
}
}
void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, CallbackHandler* handler,
FrameScheduledCallback&& callback, uint64_t flags) {
}
void VulkanDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
}
void VulkanDriver::setPresentationTime(int64_t monotonic_clock_ns) {
}
void VulkanDriver::endFrame(uint32_t frameId) {
FVK_PROFILE_MARKER(PROFILE_NAME_ENDFRAME);
endCommandRecording();
collectGarbage();
}
void VulkanDriver::updateDescriptorSetBuffer(
backend::DescriptorSetHandle dsh,
backend::descriptor_binding_t binding,
backend::BufferObjectHandle boh,
uint32_t offset,
uint32_t size) {
FVK_SYSTRACE_SCOPE();
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
auto buffer = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
mDescriptorSetCache.updateBuffer(set, binding, buffer, offset, size);
}
void VulkanDriver::updateDescriptorSetTexture(
backend::DescriptorSetHandle dsh,
backend::descriptor_binding_t binding,
backend::TextureHandle th,
SamplerParams params) {
FVK_SYSTRACE_SCOPE();
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
if (mExternalImageManager.isExternallySampledTexture(texture)) {
mExternalImageManager.bindExternallySampledTexture(set, binding, texture, params);
mAppState.hasBoundExternalImages = true;
} else {
VulkanSamplerCache::Params cacheParams = {
.sampler = params,
};
VkSampler const vksampler = mSamplerCache.getSampler(cacheParams);
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler);
mExternalImageManager.clearTextureBinding(set, binding);
}
}
void VulkanDriver::flush(int) {
FVK_SYSTRACE_SCOPE();
endCommandRecording();
}
void VulkanDriver::finish(int dummy) {
FVK_SYSTRACE_SCOPE();
endCommandRecording();
mCommands.wait();
mReadPixels.runUntilComplete();
}
void VulkanDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh,
PrimitiveType pt) {
FVK_SYSTRACE_SCOPE();
auto vb = resource_ptr<VulkanVertexBuffer>::cast(&mResourceManager, vbh);
auto ib = resource_ptr<VulkanIndexBuffer>::cast(&mResourceManager, ibh);
auto ptr = resource_ptr<VulkanRenderPrimitive>::make(&mResourceManager, rph, pt, vb, ib);
ptr.inc();
}
void VulkanDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
if (!rph) {
return;
}
FVK_SYSTRACE_SCOPE();
auto ptr = resource_ptr<VulkanRenderPrimitive>::cast(&mResourceManager, rph);
ptr.dec();
}
void VulkanDriver::createVertexBufferInfoR(Handle<HwVertexBufferInfo> vbih, uint8_t bufferCount,
uint8_t attributeCount, AttributeArray attributes) {
FVK_SYSTRACE_SCOPE();
auto vbi = resource_ptr<VulkanVertexBufferInfo>::make(&mResourceManager, vbih, bufferCount,
attributeCount, attributes);
vbi.inc();
}
void VulkanDriver::destroyVertexBufferInfo(Handle<HwVertexBufferInfo> vbih) {
if (!vbih) {
return;
}
FVK_SYSTRACE_SCOPE();
auto vbi = resource_ptr<VulkanVertexBufferInfo>::cast(&mResourceManager, vbih);
vbi.dec();
}
void VulkanDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh, uint32_t vertexCount,
Handle<HwVertexBufferInfo> vbih) {
FVK_SYSTRACE_SCOPE();
auto vbi = resource_ptr<VulkanVertexBufferInfo>::cast(&mResourceManager, vbih);
auto vb = resource_ptr<VulkanVertexBuffer>::make(&mResourceManager, vbh, mContext, mStagePool,
vertexCount, vbi);
vb.inc();
}
void VulkanDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vbh) {
if (!vbh) {
return;
}
FVK_SYSTRACE_SCOPE();
auto vb = resource_ptr<VulkanVertexBuffer>::cast(&mResourceManager, vbh);
vb.dec();
}
void VulkanDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elementType,
uint32_t indexCount, BufferUsage usage) {
FVK_SYSTRACE_SCOPE();
auto elementSize = (uint8_t) getElementTypeSize(elementType);
auto ib = resource_ptr<VulkanIndexBuffer>::make(&mResourceManager, ibh, mAllocator, mStagePool,
mBufferCache, elementSize, indexCount);
ib.inc();
}
void VulkanDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
if (!ibh) {
return;
}
FVK_SYSTRACE_SCOPE();
auto ib = resource_ptr<VulkanIndexBuffer>::cast(&mResourceManager, ibh);
ib.dec();
}
void VulkanDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage) {
FVK_SYSTRACE_SCOPE();
auto bo = resource_ptr<VulkanBufferObject>::make(&mResourceManager, boh, mAllocator, mStagePool,
mBufferCache, byteCount, bindingType);
bo.inc();
}
void VulkanDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
if (!boh) {
return;
}
FVK_SYSTRACE_SCOPE();
auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
bo.dec();
}
void VulkanDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage usage) {
FVK_SYSTRACE_SCOPE();
auto texture = resource_ptr<VulkanTexture>::make(&mResourceManager, th, mPlatform->getDevice(),
mPlatform->getPhysicalDevice(), mContext, mAllocator, &mResourceManager, &mCommands,
target, levels, format, samples, w, h, depth, usage, mStagePool);
// Do transition to default layout.
VulkanCommandBuffer& commandsBuf = mCommands.get();
auto const& primaryViewRange = texture->getPrimaryViewRange();
auto const defaultLayout = texture->getDefaultLayout();
texture->transitionLayout(&commandsBuf, primaryViewRange, defaultLayout);
texture.inc();
}
void VulkanDriver::createTextureViewR(Handle<HwTexture> th, Handle<HwTexture> srch,
uint8_t baseLevel, uint8_t levelCount) {
auto src = resource_ptr<VulkanTexture>::cast(&mResourceManager, srch);
auto texture = resource_ptr<VulkanTexture>::make(&mResourceManager, th, mPlatform->getDevice(),
mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, src, baseLevel,
levelCount);
texture.inc();
}
void VulkanDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwTexture> srch,
backend::TextureSwizzle r, backend::TextureSwizzle g, backend::TextureSwizzle b,
backend::TextureSwizzle a) {
TextureSwizzle const swizzleArray[] = {r, g, b, a};
VkComponentMapping const swizzle = fvkutils::getSwizzleMap(swizzleArray);
auto src = resource_ptr<VulkanTexture>::cast(&mResourceManager, srch);
auto texture = resource_ptr<VulkanTexture>::make(&mResourceManager, th, mPlatform->getDevice(),
mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, src, swizzle);
texture.inc();
}
void VulkanDriver::createTextureExternalImage2R(Handle<HwTexture> th, backend::SamplerType target,
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
Platform::ExternalImageHandleRef externalImage) {
FVK_SYSTRACE_SCOPE();
auto metadata = mPlatform->extractExternalImageMetadata(externalImage);
assert_invariant(width == metadata.width);
assert_invariant(height == metadata.height);
// We do not check the format since AHB could return both a known format and an external format.
// In which case, we choose one or the other, but this choice is not known to the client.
// Therefore the following lines are commented out.
// assert_invariant(format == metadata.filamentFormat);
// assert_invariant(fvkutils::getVkFormat(format) == metadata.format);
auto imgData = mPlatform->createVkImageFromExternal(externalImage);
assert_invariant(imgData.internal.valid() || imgData.external.valid());
VkFormat vkformat = metadata.format;
VkImage vkimage = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
if (imgData.internal.valid()) {
metadata.externalFormat = 0;
vkimage = imgData.internal.image;
memory = imgData.internal.memory;
} else { // imgData.external.valid()
vkformat = VK_FORMAT_UNDEFINED;
vkimage = imgData.external.image;
memory = imgData.external.memory;
}
VkSamplerYcbcrConversion const conversion =
mExternalImageManager.getVkSamplerYcbcrConversion(metadata);
auto texture = resource_ptr<VulkanTexture>::make(&mResourceManager, th, mContext,
mPlatform->getDevice(), mAllocator, &mResourceManager, &mCommands, vkimage, memory,
vkformat, conversion, metadata.samples, metadata.width, metadata.height,
metadata.layers, usage, mStagePool);
auto& commands = mCommands.get();
// Unlike uploaded textures or swapchains, we need to explicit transition this
// texture into the read layout.
texture->transitionLayout(&commands, texture->getPrimaryViewRange(), VulkanLayout::FRAG_READ);
if (imgData.external.valid()) {
mExternalImageManager.addExternallySampledTexture(texture, externalImage);
}
texture.inc();
}
void VulkanDriver::createTextureExternalImageR(Handle<HwTexture> th, backend::SamplerType target,
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
void* externalImage) {
FVK_SYSTRACE_SCOPE();
assert_invariant(false && "Not supported in Vulkan backend");
// not supported in this backend
}
void VulkanDriver::createTextureExternalImagePlaneR(Handle<HwTexture> th,
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
void* image, uint32_t plane) {
assert_invariant(false && "Not supported in Vulkan backend");
}
void VulkanDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
SamplerType target, uint8_t levels,
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage usage) {
// not supported in this backend
assert_invariant(false && "Not supported in Vulkan backend");
}
void VulkanDriver::destroyTexture(Handle<HwTexture> th) {
if (!th) {
return;
}
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
texture.dec();
mExternalImageManager.removeExternallySampledTexture(texture);
}
void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
FVK_SYSTRACE_SCOPE();
auto vprogram = resource_ptr<VulkanProgram>::make(&mResourceManager, ph, mPlatform->getDevice(),
program);
vprogram.inc();
}
void VulkanDriver::destroyProgram(Handle<HwProgram> ph) {
if (!ph) {
return;
}
auto vprogram = resource_ptr<VulkanProgram>::cast(&mResourceManager, ph);
vprogram.dec();
}
void VulkanDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {
assert_invariant(!mDefaultRenderTarget);
auto renderTarget = resource_ptr<VulkanRenderTarget>::make(&mResourceManager, rth);
mDefaultRenderTarget = renderTarget;
}
void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
TargetBufferFlags targets, uint32_t width, uint32_t height, uint8_t samples,
uint8_t layerCount, MRT color, TargetBufferInfo depth, TargetBufferInfo stencil) {
FVK_SYSTRACE_SCOPE();
UTILS_UNUSED_IN_RELEASE math::vec2<uint32_t> tmin = {std::numeric_limits<uint32_t>::max()};
UTILS_UNUSED_IN_RELEASE math::vec2<uint32_t> tmax = {0};
UTILS_UNUSED_IN_RELEASE size_t attachmentCount = 0;
VulkanAttachment colorTargets[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {};
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
if (color[i].handle) {
colorTargets[i] = {
.texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, color[i].handle),
.level = color[i].level,
.layerCount = layerCount,
.layer = (uint8_t) color[i].layer,
};
UTILS_UNUSED_IN_RELEASE VkExtent2D extent = colorTargets[i].getExtent2D();
tmin = { std::min(tmin.x, extent.width), std::min(tmin.y, extent.height) };
tmax = { std::max(tmax.x, extent.width), std::max(tmax.y, extent.height) };
attachmentCount++;
}
}
VulkanAttachment depthStencil[2] = {};
if (depth.handle) {
depthStencil[0] = {
.texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, depth.handle),
.level = depth.level,
.layerCount = layerCount,
.layer = (uint8_t) depth.layer,
};
UTILS_UNUSED_IN_RELEASE VkExtent2D extent = depthStencil[0].getExtent2D();
tmin = { std::min(tmin.x, extent.width), std::min(tmin.y, extent.height) };
tmax = { std::max(tmax.x, extent.width), std::max(tmax.y, extent.height) };
attachmentCount++;
}
if (stencil.handle) {
depthStencil[1] = {
.texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, stencil.handle),
.level = stencil.level,
.layerCount = layerCount,
.layer = (uint8_t) stencil.layer,
};
UTILS_UNUSED_IN_RELEASE VkExtent2D extent = depthStencil[1].getExtent2D();
tmin = { std::min(tmin.x, extent.width), std::min(tmin.y, extent.height) };
tmax = { std::max(tmax.x, extent.width), std::max(tmax.y, extent.height) };
attachmentCount++;
}
// All attachments must have the same dimensions, which must be greater than or equal to the
// render target dimensions.
assert_invariant(attachmentCount > 0);
assert_invariant(tmin == tmax);
assert_invariant(tmin.x >= width && tmin.y >= height);
auto rt = resource_ptr<VulkanRenderTarget>::make(&mResourceManager, rth, mPlatform->getDevice(),
mPlatform->getPhysicalDevice(), mContext, &mResourceManager, mAllocator, &mCommands,
width, height, samples, colorTargets, depthStencil, mStagePool, layerCount);
rt.inc();
}
void VulkanDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
if (!rth) {
return;
}
auto rt = resource_ptr<VulkanRenderTarget>::cast(&mResourceManager, rth);
if (UTILS_UNLIKELY(rt == mDefaultRenderTarget)) {
mDefaultRenderTarget = {};
} else {
rt.dec();
}
}
void VulkanDriver::createFenceR(Handle<HwFence> fh, int) {
VulkanCommandBuffer* cmdbuf;
if (mCurrentRenderPass.commandBuffer) {
cmdbuf = mCurrentRenderPass.commandBuffer;
} else {
cmdbuf = &mCommands.get();
}
// Note at this point, the fence has already been constructed via createFenceS, so we just tag
// it with appropriate VulkanCmdFence, which is associated with the current, recording command
// buffer.
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
fence->fence = cmdbuf->getFenceStatus();
}
void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
FVK_SYSTRACE_SCOPE();
// Running gc() to guard against an edge case where the old swapchains need to have been
// destroyed before the new swapchain can be created. Otherwise, we would fail
// vkCreateSwapchainKHR with VK_ERROR_NATIVE_WINDOW_IN_USE_KHR.
mResourceManager.gc();
if ((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0 && !isSRGBSwapChainSupported()) {
FVK_LOGW << "sRGB swapchain requested, but Platform does not support it";
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
}
if (flags & backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
if (!isProtectedContentSupported()) {
FVK_LOGW << "protected swapchain requested, but Platform does not support it";
}
}
auto swapChain = resource_ptr<VulkanSwapChain>::make(&mResourceManager, sch, mPlatform,
mContext, &mResourceManager, mAllocator, &mCommands, mStagePool, nativeWindow, flags);
swapChain.inc();
}
void VulkanDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t width,
uint32_t height, uint64_t flags) {
if ((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0 && !isSRGBSwapChainSupported()) {
FVK_LOGW << "sRGB swapchain requested, but Platform does not support it";
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
}
assert_invariant(width > 0 && height > 0 && "Vulkan requires non-zero swap chain dimensions.");
auto swapChain = resource_ptr<VulkanSwapChain>::make(&mResourceManager, sch, mPlatform,
mContext, &mResourceManager, mAllocator, &mCommands, mStagePool, nullptr, flags,
VkExtent2D{width, height});
swapChain.inc();
}
void VulkanDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
// nothing to do, timer query was constructed in createTimerQueryS
}
void VulkanDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh,
backend::DescriptorSetLayout&& info) {
FVK_SYSTRACE_SCOPE();
auto layout = mDescriptorSetLayoutCache.createLayout(dslh, std::move(info));
layout.inc();
}
void VulkanDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
Handle<HwDescriptorSetLayout> dslh) {
FVK_SYSTRACE_SCOPE();
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout =
fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, dslh);
auto set = mDescriptorSetCache.createSet(dsh, layout);
set.inc();
if (layout->hasExternalSamplers()) {
mAppState.hasExternalSamplerLayouts = true;
}
}
Handle<HwVertexBufferInfo> VulkanDriver::createVertexBufferInfoS() noexcept {
return mResourceManager.allocHandle<VulkanVertexBufferInfo>();
}
Handle<HwVertexBuffer> VulkanDriver::createVertexBufferS() noexcept {
return mResourceManager.allocHandle<VulkanVertexBuffer>();
}
Handle<HwIndexBuffer> VulkanDriver::createIndexBufferS() noexcept {
return mResourceManager.allocHandle<VulkanIndexBuffer>();
}
Handle<HwBufferObject> VulkanDriver::createBufferObjectS() noexcept {
return mResourceManager.allocHandle<VulkanBufferObject>();
}
Handle<HwTexture> VulkanDriver::createTextureS() noexcept {
return mResourceManager.allocHandle<VulkanTexture>();
}
Handle<HwTexture> VulkanDriver::createTextureViewS() noexcept {
return mResourceManager.allocHandle<VulkanTexture>();
}
Handle<HwTexture> VulkanDriver::createTextureViewSwizzleS() noexcept {
return mResourceManager.allocHandle<VulkanTexture>();
}
Handle<HwTexture> VulkanDriver::createTextureExternalImage2S() noexcept {
return mResourceManager.allocHandle<VulkanTexture>();
}
Handle<HwTexture> VulkanDriver::createTextureExternalImageS() noexcept {
return mResourceManager.allocHandle<VulkanTexture>();
}
Handle<HwTexture> VulkanDriver::createTextureExternalImagePlaneS() noexcept {
return mResourceManager.allocHandle<VulkanTexture>();
}
Handle<HwTexture> VulkanDriver::importTextureS() noexcept {
return mResourceManager.allocHandle<VulkanTexture>();
}
Handle<HwRenderPrimitive> VulkanDriver::createRenderPrimitiveS() noexcept {
return mResourceManager.allocHandle<VulkanRenderPrimitive>();
}
Handle<HwProgram> VulkanDriver::createProgramS() noexcept {
return mResourceManager.allocHandle<VulkanProgram>();
}
Handle<HwRenderTarget> VulkanDriver::createDefaultRenderTargetS() noexcept {
return mResourceManager.allocHandle<VulkanRenderTarget>();
}
Handle<HwRenderTarget> VulkanDriver::createRenderTargetS() noexcept {
return mResourceManager.allocHandle<VulkanRenderTarget>();
}
Handle<HwFence> VulkanDriver::createFenceS() noexcept {
auto handle = mResourceManager.allocHandle<VulkanFence>();
auto fence = resource_ptr<VulkanFence>::make(&mResourceManager, handle);
fence.inc();
return handle;
}
Handle<HwSwapChain> VulkanDriver::createSwapChainS() noexcept {
return mResourceManager.allocHandle<VulkanSwapChain>();
}
Handle<HwSwapChain> VulkanDriver::createSwapChainHeadlessS() noexcept {
return mResourceManager.allocHandle<VulkanSwapChain>();
}
Handle<HwTimerQuery> VulkanDriver::createTimerQueryS() noexcept {
// The handle must be constructed here, as a synchronous call to getTimerQueryValue might happen
// before createTimerQueryR is executed.
auto query = mQueryManager.getNextQuery(&mResourceManager);
query.inc();
return Handle<VulkanTimerQuery>(query.id());
}
Handle<HwDescriptorSetLayout> VulkanDriver::createDescriptorSetLayoutS() noexcept {
return mResourceManager.allocHandle<VulkanDescriptorSetLayout>();
}
Handle<HwDescriptorSet> VulkanDriver::createDescriptorSetS() noexcept {
return mResourceManager.allocHandle<VulkanDescriptorSet>();
}
void VulkanDriver::destroySwapChain(Handle<HwSwapChain> sch) {
if (!sch) {
return;
}
auto swapChain = resource_ptr<VulkanSwapChain>::cast(&mResourceManager, sch);
if (mCurrentSwapChain == swapChain) {
mCurrentSwapChain = {};
}
swapChain.dec();
}
void VulkanDriver::destroyStream(Handle<HwStream> sh) {
}
void VulkanDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
if (!tqh) {
return;
}
auto vtq = resource_ptr<VulkanTimerQuery>::cast(&mResourceManager, tqh);
mQueryManager.clearQuery(vtq);
vtq.dec();
}
void VulkanDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> dslh) {
auto layout = resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, dslh);
layout.dec();
}
void VulkanDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
set.dec();
if (mAppState.hasExternalSamplers() && set->getExternalSamplerVkSet() != VK_NULL_HANDLE) {
mExternalImageManager.removeDescriptorSet(set);
}
}
Handle<HwStream> VulkanDriver::createStreamNative(void* nativeStream) {
return {};
}
Handle<HwStream> VulkanDriver::createStreamAcquired() {
return {};
}
void VulkanDriver::setAcquiredImage(Handle<HwStream> sh, void* image, const math::mat3f& transform,
CallbackHandler* handler, StreamCallback cb, void* userData) {
}
void VulkanDriver::setStreamDimensions(Handle<HwStream> sh, uint32_t width, uint32_t height) {
}
int64_t VulkanDriver::getStreamTimestamp(Handle<HwStream> sh) {
return 0;
}
void VulkanDriver::updateStreams(CommandStream* driver) {
}
void VulkanDriver::destroyFence(Handle<HwFence> fh) {
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
fence.dec();
}
FenceStatus VulkanDriver::getFenceStatus(Handle<HwFence> fh) {
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
auto& cmdfence = fence->fence;
if (!cmdfence) {
// If wait is called before a fence actually exists, we return timeout. This matches the
// current behavior in OpenGLDriver, but we should eventually reconsider a different error
// code.
return FenceStatus::TIMEOUT_EXPIRED;
}
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted".
// When this fence gets submitted, its status changes to VK_NOT_READY.
if (cmdfence->getStatus() == VK_SUCCESS) {
return FenceStatus::CONDITION_SATISFIED;
}
// Two other states are possible:
// - VK_INCOMPLETE: the corresponding buffer has not yet been submitted.
// - VK_NOT_READY: the buffer has been submitted but not yet signaled.
// In either case, we return TIMEOUT_EXPIRED to indicate the fence has not been signaled.
return FenceStatus::TIMEOUT_EXPIRED;
}
// We create all textures using VK_IMAGE_TILING_OPTIMAL, so our definition of "supported" is that
// the GPU supports the given texture format with non-zero optimal tiling features.
bool VulkanDriver::isTextureFormatSupported(TextureFormat format) {
VkFormat vkformat = fvkutils::getVkFormat(format);
if (vkformat == VK_FORMAT_UNDEFINED) {
return false;
}
VkFormatProperties info;
vkGetPhysicalDeviceFormatProperties(mPlatform->getPhysicalDevice(), vkformat, &info);
return info.optimalTilingFeatures != 0;
}
bool VulkanDriver::isTextureSwizzleSupported() {
return true;
}
bool VulkanDriver::isTextureFormatMipmappable(TextureFormat format) {
switch (format) {
case TextureFormat::DEPTH16:
case TextureFormat::DEPTH24:
case TextureFormat::DEPTH32F:
case TextureFormat::DEPTH24_STENCIL8:
case TextureFormat::DEPTH32F_STENCIL8:
return false;
default:
return isRenderTargetFormatSupported(format);
}
}
bool VulkanDriver::isRenderTargetFormatSupported(TextureFormat format) {
VkFormat vkformat = fvkutils::getVkFormat(format);
if (vkformat == VK_FORMAT_UNDEFINED) {
return false;
}
VkFormatProperties info;
vkGetPhysicalDeviceFormatProperties(mPlatform->getPhysicalDevice(), vkformat, &info);
return (info.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0;
}
bool VulkanDriver::isFrameBufferFetchSupported() {
// TODO: we must fix this before landing descriptor set change. Otherwise, the scuba tests will fail.
//return true;
return false;
}
bool VulkanDriver::isFrameBufferFetchMultiSampleSupported() {
return false;
}
bool VulkanDriver::isFrameTimeSupported() {
return true;
}
bool VulkanDriver::isAutoDepthResolveSupported() {
// TODO: this could be supported with vk 1.2 or VK_KHR_depth_stencil_resolve
return false;
}
bool VulkanDriver::isSRGBSwapChainSupported() {
return mIsSRGBSwapChainSupported;
}
bool VulkanDriver::isProtectedContentSupported() {
return mContext.isProtectedMemorySupported();
}
bool VulkanDriver::isStereoSupported() {
switch (mStereoscopicType) {
case backend::StereoscopicType::INSTANCED:
return mContext.isClipDistanceSupported();
case backend::StereoscopicType::MULTIVIEW:
return mContext.isMultiviewEnabled();
case backend::StereoscopicType::NONE:
return false;
}
}
bool VulkanDriver::isParallelShaderCompileSupported() {
return false;
}
bool VulkanDriver::isDepthStencilResolveSupported() {
// TODO: apparently it could be supported in core 1.2 and/or with VK_KHR_depth_stencil_resolve
return false;
}
bool VulkanDriver::isDepthStencilBlitSupported(TextureFormat format) {
auto const& formats = mContext.getBlittableDepthStencilFormats();
return std::find(formats.begin(), formats.end(), fvkutils::getVkFormat(format)) !=
formats.end();
}
bool VulkanDriver::isProtectedTexturesSupported() { return isProtectedContentSupported(); }
bool VulkanDriver::isDepthClampSupported() {
return mContext.isDepthClampSupported();
}
bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) {
switch (workaround) {
case Workaround::SPLIT_EASU: {
auto const vendorId = mContext.getPhysicalDeviceVendorId();
// early exit condition is flattened in EASU code
return vendorId == 0x5143; // Qualcomm
}
case Workaround::ALLOW_READ_ONLY_ANCILLARY_FEEDBACK_LOOP:
// Supporting depth attachment as both sampler and attachment is only possible if we set
// the depth attachment as read-only (e.g. during SSAO pass), however note that the
// store-ops for attachments wrt VkRenderPass only has VK_ATTACHMENT_STORE_OP_DONT_CARE
// and VK_ATTACHMENT_STORE_OP_STORE for versions below 1.3. Only at 1.3 and above do we
// have a true read-only choice VK_ATTACHMENT_STORE_OP_NONE. That means for < 1.3, we
// will trigger a validation sync error if we use the depth attachment also as a
// sampler. See full error below:
//
// SYNC-HAZARD-WRITE-AFTER-READ(ERROR / SPEC): msgNum: 929810911 - Validation Error:
// [ SYNC-HAZARD-WRITE-AFTER-READ ] Object 0: handle = 0x6160000c3680,
// type = VK_OBJECT_TYPE_RENDER_PASS; | MessageID = 0x376bc9df | vkCmdEndRenderPass:
// Hazard WRITE_AFTER_READ in subpass 0 for attachment 1 depth aspect during store with
// storeOp VK_ATTACHMENT_STORE_OP_STORE. Access info (usage:
// SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE, prior_usage:
// SYNC_FRAGMENT_SHADER_SHADER_STORAGE_READ, read_barriers: VK_PIPELINE_STAGE_2_NONE,
// command: vkCmdDrawIndexed, seq_no: 177, reset_no: 1)
//
// Therefore we apply the existing workaround of an extra blit until a better
// resolution.
return false;
case Workaround::ADRENO_UNIFORM_ARRAY_CRASH:
return false;
case Workaround::DISABLE_BLIT_INTO_TEXTURE_ARRAY:
return false;
default:
return false;
}
return false;
}
FeatureLevel VulkanDriver::getFeatureLevel() {
VkPhysicalDeviceLimits const& limits = mContext.getPhysicalDeviceLimits();
// If cubemap arrays are not supported, then this is an FL1 device.
if (!mContext.isImageCubeArraySupported()) {
return FeatureLevel::FEATURE_LEVEL_1;
}
// If the max sampler counts do not meet FL2 standards, then this is an FL1 device.
const auto& fl2 = FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_2];
if (limits.maxPerStageDescriptorSamplers < fl2.MAX_VERTEX_SAMPLER_COUNT ||
limits.maxPerStageDescriptorSamplers < fl2.MAX_FRAGMENT_SAMPLER_COUNT) {
return FeatureLevel::FEATURE_LEVEL_1;
}
// If the max sampler counts do not meet FL3 standards, then this is an FL2 device.
const auto& fl3 = FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_3];
if (limits.maxPerStageDescriptorSamplers < fl3.MAX_VERTEX_SAMPLER_COUNT||
limits.maxPerStageDescriptorSamplers < fl3.MAX_FRAGMENT_SAMPLER_COUNT) {
return FeatureLevel::FEATURE_LEVEL_2;
}
return FeatureLevel::FEATURE_LEVEL_3;
}
math::float2 VulkanDriver::getClipSpaceParams() {
// virtual and physical z-coordinate of clip-space is in [-w, 0]
// Note: this is actually never used (see: surface_main.vs), but it's a backend API, so we
// implement it properly.
return math::float2{ 1.0f, 0.0f };
}
uint8_t VulkanDriver::getMaxDrawBuffers() {
return mContext.getPhysicalDeviceLimits().maxColorAttachments;
}
size_t VulkanDriver::getMaxUniformBufferSize() {
return mContext.getPhysicalDeviceLimits().maxUniformBufferRange;
}
size_t VulkanDriver::getMaxTextureSize(SamplerType type) {
switch (type) {
case SamplerType::SAMPLER_2D:
return mContext.getPhysicalDeviceLimits().maxImageDimension2D;
case SamplerType::SAMPLER_3D:
return mContext.getPhysicalDeviceLimits().maxImageDimension3D;
case SamplerType::SAMPLER_CUBEMAP:
return mContext.getPhysicalDeviceLimits().maxImageDimensionCube;
default:
return mContext.getPhysicalDeviceLimits().maxImageDimension1D;
}
}
size_t VulkanDriver::getMaxArrayTextureLayers() {
return mContext.getPhysicalDeviceLimits().maxImageArrayLayers;
}
void VulkanDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t index,
Handle<HwBufferObject> boh) {
auto vb = resource_ptr<VulkanVertexBuffer>::cast(&mResourceManager, vbh);
auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
assert_invariant(bo->bindingType == BufferObjectBinding::VERTEX);
vb->setBuffer(bo, index);
}
void VulkanDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& p,
uint32_t byteOffset) {
VulkanCommandBuffer& commands = mCommands.get();
auto ib = resource_ptr<VulkanIndexBuffer>::cast(&mResourceManager, ibh);
commands.acquire(ib);
ib->buffer.loadFromCpu(commands, p.buffer, byteOffset, p.size);
scheduleDestroy(std::move(p));
}
void VulkanDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh,
BufferObjectStreamDescriptor&& streams) {
// Noop
}
void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& bd,
uint32_t byteOffset) {
VulkanCommandBuffer& commands = mCommands.get();
auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
commands.acquire(bo);
bo->buffer.loadFromCpu(commands, bd.buffer, byteOffset, bd.size);
scheduleDestroy(std::move(bd));
}
void VulkanDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> boh,
BufferDescriptor&& bd, uint32_t byteOffset) {
VulkanCommandBuffer& commands = mCommands.get();
auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
commands.acquire(bo);
// TODO: implement unsynchronized version
bo->buffer.loadFromCpu(commands, bd.buffer, byteOffset, bd.size);
scheduleDestroy(std::move(bd));
}
void VulkanDriver::resetBufferObject(Handle<HwBufferObject> boh) {
// TODO: implement resetBufferObject(). This is equivalent to calling
// destroyBufferObject() followed by createBufferObject() keeping the same handle.
// It is actually okay to keep a no-op implementation, the intention here is to "orphan" the
// buffer (and possibly return it to a pool) and allocate a new one (or get it from a pool),
// so that no further synchronization with the GPU is needed.
// This is only useful if updateBufferObjectUnsynchronized() is implemented unsynchronizedly.
}
void VulkanDriver::update3DImage(Handle<HwTexture> th, uint32_t level, uint32_t xoffset,
uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth,
PixelBufferDescriptor&& data) {
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
texture->updateImage(data, width, height, depth, xoffset, yoffset, zoffset, level);
scheduleDestroy(std::move(data));
}
void VulkanDriver::setupExternalImage2(Platform::ExternalImageHandleRef image) {
}
void VulkanDriver::setupExternalImage(void* image) {
}
TimerQueryResult VulkanDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
auto vtq = resource_ptr<VulkanTimerQuery>::cast(&mResourceManager, tqh);
if (!vtq->isCompleted()) {
return TimerQueryResult::NOT_READY;
}
auto results = mQueryManager.getResult(vtq);
if (results.beginAvailable == 0 || results.endAvailable == 0) {
return TimerQueryResult::NOT_READY;
}
uint64_t const begin = results.beginTime;
uint64_t const end = results.endTime;
if (begin >= end) {
// TODO: queries might have ran on different command buffers.
FVK_LOGW << "Timestamps are not monotonically increasing. ";
*elapsedTime = 0;
return TimerQueryResult::ERROR;
}
// NOTE: MoltenVK currently writes system time so the following delta will always be zero.
// However there are plans for implementing this properly. See the following GitHub ticket.
// https://github.com/KhronosGroup/MoltenVK/issues/773
float const period = mContext.getPhysicalDeviceLimits().timestampPeriod;
*elapsedTime = uint64_t(float(end - begin) * period);
return TimerQueryResult::AVAILABLE;
}
void VulkanDriver::setExternalStream(Handle<HwTexture> th, Handle<HwStream> sh) {
}
void VulkanDriver::generateMipmaps(Handle<HwTexture> th) {
auto t = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
assert_invariant(t);
int32_t layerCount = int32_t(t->depth);
if (t->target == SamplerType::SAMPLER_CUBEMAP_ARRAY ||
t->target == SamplerType::SAMPLER_CUBEMAP) {
layerCount *= 6;
}
assert_invariant(layerCount < 1 << (sizeof(VulkanAttachment::layerCount) * 8));
// FIXME: the loop below can perform many layout transitions and back. We should be
// able to optimize that.
uint8_t level = 0;
int32_t srcw = int32_t(t->width);
int32_t srch = int32_t(t->height);
do {
int32_t const dstw = std::max(srcw >> 1, 1);
int32_t const dsth = std::max(srch >> 1, 1);
const VkOffset3D srcOffsets[2] = {{ 0, 0, 0 }, { srcw, srch, 1 }};
const VkOffset3D dstOffsets[2] = {{ 0, 0, 0 }, { dstw, dsth, 1 }};
// TODO: there should be a way to do this using layerCount in vkBlitImage
// TODO: vkBlitImage should be able to handle 3D textures too
for (uint8_t layer = 0; layer < layerCount; layer++) {
VulkanAttachment dst {
.level = uint8_t(level + 1),
.layer = layer,
};
dst.texture = t;
VulkanAttachment src {
.level = uint8_t(level),
.layer = layer,
};
src.texture = t;
mBlitter.blit(VK_FILTER_LINEAR, dst, dstOffsets, src, srcOffsets);
}
srcw = dstw;
srch = dsth;
} while ((srcw > 1 || srch > 1) && ++level < t->levels - 1);
}
void VulkanDriver::compilePrograms(CompilerPriorityQueue priority,
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
if (callback) {
scheduleCallback(handler, user, callback);
}
}
void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassParams& params) {
FVK_SYSTRACE_SCOPE();
auto rt = resource_ptr<VulkanRenderTarget>::cast(&mResourceManager, rth);
VkExtent2D const extent = rt->getExtent();
assert_invariant(rt == mDefaultRenderTarget || extent.width > 0 && extent.height > 0);
VulkanCommandBuffer* commandBuffer = rt->isProtected() ?
&mCommands.getProtected() : &mCommands.get();
// Filament has the expectation that the contents of the swap chain are not preserved on the
// first render pass. Note however that its contents are often preserved on subsequent render
// passes, due to multiple views.
TargetBufferFlags discardStart = params.flags.discardStart;
if (rt->isSwapChain()) {
fvkmemory::resource_ptr<VulkanSwapChain> sc = mCurrentSwapChain;
assert_invariant(sc);
if (sc->isFirstRenderPass()) {
discardStart |= TargetBufferFlags::COLOR;
sc->markFirstRenderPass();
}
}
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
if (rt->hasDepth()) {
auto depth = rt->getDepth();
depth.texture->print();
}
#endif
// We need to determine whether the same depth texture is both sampled and set as an attachment.
// If that's the case, we need to change the layout of the texture to DEPTH_SAMPLER, which is a
// more general layout. Otherwise, we prefer the DEPTH_ATTACHMENT layout, which is optimal for
// the non-sampling case.
VkCommandBuffer const cmdbuffer = commandBuffer->buffer();
// Scissor is reset with each render pass
// This also takes care of VUID-vkCmdDrawIndexed-None-07832.
VkRect2D const scissor{ .offset = { 0, 0 }, .extent = extent };
vkCmdSetScissor(cmdbuffer, 0, 1, &scissor);
VulkanLayout currentDepthLayout = VulkanLayout::UNDEFINED;
TargetBufferFlags clearVal = params.flags.clear;
TargetBufferFlags discardEndVal = params.flags.discardEnd;
if (rt->hasDepth()) {
if (params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) {
discardEndVal &= ~TargetBufferFlags::DEPTH;
clearVal &= ~TargetBufferFlags::DEPTH;
}
currentDepthLayout = VulkanLayout::DEPTH_ATTACHMENT;
}
// Create the VkRenderPass or fetch it from cache.
VulkanFboCache::RenderPassKey rpkey = rt->getRenderPassKey();
rpkey.clear = clearVal;
rpkey.discardStart = discardStart;
rpkey.discardEnd = discardEndVal;
rpkey.initialDepthLayout = currentDepthLayout;
rpkey.subpassMask = uint8_t(params.subpassMask);
VkRenderPass renderPass = mFramebufferCache.getRenderPass(rpkey);
mPipelineCache.bindRenderPass(renderPass, 0);
// Create the VkFramebuffer or fetch it from cache.
VulkanFboCache::FboKey fbkey = rt->getFboKey();
fbkey.renderPass = renderPass;
fbkey.layers = 1;
rt->emitBarriersBeginRenderPass(*commandBuffer);
VkFramebuffer vkfb = mFramebufferCache.getFramebuffer(fbkey);
// Assign a label to the framebuffer for debugging purposes.
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS | FVK_DEBUG_DEBUG_UTILS)
auto const topMarker = mCommands.getTopGroupMarker();
if (!topMarker.empty()) {
DebugUtils::setName(VK_OBJECT_TYPE_FRAMEBUFFER, reinterpret_cast<uint64_t>(vkfb),
topMarker.c_str());
}
#endif
// The current command buffer now has references to the render target and its attachments.
commandBuffer->acquire(rt);
// Populate the structures required for vkCmdBeginRenderPass.
VkRenderPassBeginInfo renderPassInfo {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.renderPass = renderPass,
.framebuffer = vkfb,
// The renderArea field constrains the LoadOp, but scissoring does not.
// Therefore, we do not set the scissor rect here, we only need it in draw().
.renderArea = { .offset = {}, .extent = extent }
};
rt->transformClientRectToPlatform(&renderPassInfo.renderArea);
VkClearValue clearValues[
MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT +
1] = {};
if (clearVal != TargetBufferFlags::NONE) {
// NOTE: clearValues must be populated in the same order as the attachments array in
// VulkanFboCache::getFramebuffer. Values must be provided regardless of whether Vulkan is
// actually clearing that particular target.
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
if (fbkey.color[i]) {
VkClearValue &clearValue = clearValues[renderPassInfo.clearValueCount++];
clearValue.color.float32[0] = params.clearColor.r;
clearValue.color.float32[1] = params.clearColor.g;
clearValue.color.float32[2] = params.clearColor.b;
clearValue.color.float32[3] = params.clearColor.a;
}
}
// Resolve attachments are not cleared but still have entries in the list, so skip over them.
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
if (rpkey.needsResolveMask & (1u << i)) {
renderPassInfo.clearValueCount++;
}
}
if (fbkey.depth) {
VkClearValue &clearValue = clearValues[renderPassInfo.clearValueCount++];
clearValue.depthStencil = {(float) params.clearDepth, 0};
}
renderPassInfo.pClearValues = &clearValues[0];
}
vkCmdBeginRenderPass(cmdbuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
VkViewport viewport = {
.x = (float) params.viewport.left,
.y = (float) params.viewport.bottom,
.width = (float) params.viewport.width,
.height = (float) params.viewport.height,
.minDepth = params.depthRange.near,
.maxDepth = params.depthRange.far
};
rt->transformViewportToPlatform(&viewport);
vkCmdSetViewport(cmdbuffer, 0, 1, &viewport);
mCurrentRenderPass = {
.commandBuffer = commandBuffer,
.renderTarget = rt,
.renderPass = renderPassInfo.renderPass,
.params = params,
.currentSubpass = 0,
};
}
void VulkanDriver::endRenderPass(int) {
FVK_SYSTRACE_SCOPE();
VkCommandBuffer cmdbuffer = mCurrentRenderPass.commandBuffer->buffer();
vkCmdEndRenderPass(cmdbuffer);
auto rt = mCurrentRenderPass.renderTarget;
assert_invariant(rt);
// Since we might soon be sampling from the render target that we just wrote to, we need a
// pipeline barrier between framebuffer writes and shader reads.
rt->emitBarriersEndRenderPass(*mCurrentRenderPass.commandBuffer);
mCurrentRenderPass.renderTarget = {};
mCurrentRenderPass.renderPass = VK_NULL_HANDLE;
mCurrentRenderPass.commandBuffer = nullptr;
}
void VulkanDriver::nextSubpass(int) {
FILAMENT_CHECK_PRECONDITION(mCurrentRenderPass.currentSubpass == 0)
<< "Only two subpasses are currently supported.";
auto renderTarget = mCurrentRenderPass.renderTarget;
assert_invariant(renderTarget);
assert_invariant(mCurrentRenderPass.params.subpassMask);
vkCmdNextSubpass(mCurrentRenderPass.commandBuffer->buffer(),
VK_SUBPASS_CONTENTS_INLINE);
mPipelineCache.bindRenderPass(mCurrentRenderPass.renderPass,
++mCurrentRenderPass.currentSubpass);
if (mCurrentRenderPass.params.subpassMask & 0x1) {
VulkanAttachment& subpassInput = renderTarget->getColor0();
mDescriptorSetCache.updateInputAttachment({}, subpassInput);
}
}
void VulkanDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> readSch) {
FVK_SYSTRACE_SCOPE();
ASSERT_PRECONDITION_NON_FATAL(drawSch == readSch,
"Vulkan driver does not support distinct draw/read swap chains.");
resource_ptr<VulkanSwapChain> swapChain =
resource_ptr<VulkanSwapChain>::cast(&mResourceManager, drawSch);
mCurrentSwapChain = swapChain;
bool resized = false;
swapChain->acquire(resized);
if (resized) {
mFramebufferCache.resetFramebuffers();
}
if (UTILS_LIKELY(mDefaultRenderTarget)) {
mDefaultRenderTarget->bindToSwapChain(swapChain);
}
}
void VulkanDriver::commit(Handle<HwSwapChain> sch) {
FVK_SYSTRACE_SCOPE();
auto swapChain = resource_ptr<VulkanSwapChain>::cast(&mResourceManager, sch);
// Present the backbuffer after the most recent command buffer submission has finished.
swapChain->present();
}
void VulkanDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
backend::PushConstantVariant value) {
assert_invariant(mPipelineState.program && "Expect a program when writing to push constants");
assert_invariant(mCurrentRenderPass.commandBuffer && "Should be called within a renderpass");
mPipelineState.program->writePushConstant(mCurrentRenderPass.commandBuffer->buffer(),
mPipelineState.pipelineLayout, stage, index, value);
}
void VulkanDriver::insertEventMarker(char const* string) {
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
mCommands.insertEventMarker(string, strlen(string));
#endif
}
void VulkanDriver::pushGroupMarker(char const* string) {
// Turns out all the markers are 0-terminated, so we can just pass it without len.
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
mCommands.pushGroupMarker(string);
#endif
FVK_SYSTRACE_CONTEXT();
FVK_SYSTRACE_START(string);
}
void VulkanDriver::popGroupMarker(int) {
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
mCommands.popGroupMarker();
#endif
FVK_SYSTRACE_CONTEXT();
FVK_SYSTRACE_END();
}
void VulkanDriver::startCapture(int) {}
void VulkanDriver::stopCapture(int) {}
void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y, uint32_t width,
uint32_t height, PixelBufferDescriptor&& pbd) {
auto srcTarget = resource_ptr<VulkanRenderTarget>::cast(&mResourceManager, src);
endCommandRecording();
mReadPixels.run(
srcTarget, x, y, width, height, mPlatform->getGraphicsQueueFamilyIndex(),
std::move(pbd),
[&context = mContext](uint32_t types, VkFlags reqs) {
return context.selectMemoryType(types, reqs);
},
[this](PixelBufferDescriptor&& pbd) {
scheduleDestroy(std::move(pbd));
});
}
void VulkanDriver::readBufferSubData(backend::BufferObjectHandle boh,
uint32_t offset, uint32_t size, backend::BufferDescriptor&& p) {
// TODO: implement readBufferSubData
scheduleDestroy(std::move(p));
}
void VulkanDriver::resolve(
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer,
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer) {
FVK_SYSTRACE_SCOPE();
FILAMENT_CHECK_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE)
<< "resolve() cannot be invoked inside a render pass.";
auto srcTexture = resource_ptr<VulkanTexture>::cast(&mResourceManager, src);
auto dstTexture = resource_ptr<VulkanTexture>::cast(&mResourceManager, dst);
assert_invariant(srcTexture);
assert_invariant(dstTexture);
FILAMENT_CHECK_PRECONDITION(
dstTexture->width == srcTexture->width && dstTexture->height == srcTexture->height)
<< "invalid resolve: src and dst sizes don't match";
FILAMENT_CHECK_PRECONDITION(srcTexture->samples > 1 && dstTexture->samples == 1)
<< "invalid resolve: src.samples=" << +srcTexture->samples
<< ", dst.samples=" << +dstTexture->samples;
FILAMENT_CHECK_PRECONDITION(srcTexture->format == dstTexture->format)
<< "src and dst texture format don't match";
FILAMENT_CHECK_PRECONDITION(!isDepthFormat(srcTexture->format))
<< "can't resolve depth formats";
FILAMENT_CHECK_PRECONDITION(!isStencilFormat(srcTexture->format))
<< "can't resolve stencil formats";
FILAMENT_CHECK_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST))
<< "texture doesn't have BLIT_DST";
FILAMENT_CHECK_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC))
<< "texture doesn't have BLIT_SRC";
mBlitter.resolve(
{ .texture = dstTexture, .level = dstLevel, .layer = dstLayer },
{ .texture = srcTexture, .level = srcLevel, .layer = srcLayer });
}
void VulkanDriver::blit(
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer, math::uint2 dstOrigin,
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer, math::uint2 srcOrigin,
math::uint2 size) {
FVK_SYSTRACE_SCOPE();
FILAMENT_CHECK_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE)
<< "blit() cannot be invoked inside a render pass.";
auto srcTexture = resource_ptr<VulkanTexture>::cast(&mResourceManager, src);
auto dstTexture = resource_ptr<VulkanTexture>::cast(&mResourceManager, dst);
FILAMENT_CHECK_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST))
<< "texture doesn't have BLIT_DST";
FILAMENT_CHECK_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC))
<< "texture doesn't have BLIT_SRC";
FILAMENT_CHECK_PRECONDITION(srcTexture->format == dstTexture->format)
<< "src and dst texture format don't match";
// The Y inversion below makes it so that Vk matches GL and Metal.
auto const srcLeft = int32_t(srcOrigin.x);
auto const dstLeft = int32_t(dstOrigin.x);
auto const srcTop = int32_t(srcTexture->height - (srcOrigin.y + size.y));
auto const dstTop = int32_t(dstTexture->height - (dstOrigin.y + size.y));
auto const srcRight = int32_t(srcOrigin.x + size.x);
auto const dstRight = int32_t(dstOrigin.x + size.x);
auto const srcBottom = int32_t(srcTop + size.y);
auto const dstBottom = int32_t(dstTop + size.y);
VkOffset3D const srcOffsets[2] = { { srcLeft, srcTop, 0 }, { srcRight, srcBottom, 1 }};
VkOffset3D const dstOffsets[2] = { { dstLeft, dstTop, 0 }, { dstRight, dstBottom, 1 }};
// no scaling guaranteed
mBlitter.blit(VK_FILTER_NEAREST,
{ .texture = dstTexture, .level = dstLevel, .layer = dstLayer }, dstOffsets,
{ .texture = srcTexture, .level = srcLevel, .layer = srcLayer }, srcOffsets);
}
void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
Handle<HwRenderTarget> dst, Viewport dstRect,
Handle<HwRenderTarget> src, Viewport srcRect,
SamplerMagFilter filter) {
FVK_SYSTRACE_SCOPE();
// Note: blitDEPRECATED is only used for Renderer::copyFrame()
FILAMENT_CHECK_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE)
<< "blitDEPRECATED() cannot be invoked inside a render pass.";
FILAMENT_CHECK_PRECONDITION(buffers == TargetBufferFlags::COLOR0)
<< "blitDEPRECATED only supports COLOR0";
FILAMENT_CHECK_PRECONDITION(
srcRect.left >= 0 && srcRect.bottom >= 0 && dstRect.left >= 0 && dstRect.bottom >= 0)
<< "Source and destination rects must be positive.";
auto dstTarget = resource_ptr<VulkanRenderTarget>::cast(&mResourceManager, dst);
auto srcTarget = resource_ptr<VulkanRenderTarget>::cast(&mResourceManager, src);
VkFilter const vkfilter = (filter == SamplerMagFilter::NEAREST) ?
VK_FILTER_NEAREST : VK_FILTER_LINEAR;
// The Y inversion below makes it so that Vk matches GL and Metal.
VkExtent2D const srcExtent = srcTarget->getExtent();
VkExtent2D const dstExtent = dstTarget->getExtent();
auto const dstLeft = int32_t(dstRect.left);
auto const srcLeft = int32_t(srcRect.left);
auto const dstTop = int32_t(dstExtent.height - (dstRect.bottom + dstRect.height));
auto const srcTop = int32_t(srcExtent.height - (srcRect.bottom + srcRect.height));
auto const dstRight = int32_t(dstRect.left + dstRect.width);
auto const srcRight = int32_t(srcRect.left + srcRect.width);
auto const dstBottom = int32_t(dstTop + dstRect.height);
auto const srcBottom = int32_t(srcTop + srcRect.height);
VkOffset3D const srcOffsets[2] = { { srcLeft, srcTop, 0 }, { srcRight, srcBottom, 1 }};
VkOffset3D const dstOffsets[2] = { { dstLeft, dstTop, 0 }, { dstRight, dstBottom, 1 }};
mBlitter.blit(vkfilter,
dstTarget->getColor0(), dstOffsets,
srcTarget->getColor0(), srcOffsets);
}
void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
// This resets all of the pipeline states; the most relevant (needing reset) is .bindInDraw.
mPipelineState = {};
auto& setLayouts = pipelineState.pipelineLayout.setLayout;
DescriptorSetLayoutHandleList layoutHandles;
uint8_t layoutCount = 0;
std::transform(setLayouts.begin(), setLayouts.end(), layoutHandles.begin(),
[&](auto const& handle) -> resource_ptr<VulkanDescriptorSetLayout> {
if (!handle) {
return {};
}
layoutCount++;
return resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, handle);
});
constexpr uint8_t descriptorSetMaskTable[4] = {0x1, 0x3, 0x7, 0xF};
fvkutils::DescriptorSetMask const descriptorSetMask =
fvkutils::DescriptorSetMask(descriptorSetMaskTable[layoutCount]);
if (mAppState.hasExternalSamplers()) {
auto const haveExternalSamplers = [&](auto layoutHandle) {
if (!layoutHandle) {
return false;
}
return layoutHandle->hasExternalSamplers();
};
if (std::any_of(layoutHandles.begin(), layoutHandles.end(), haveExternalSamplers)) {
BindInDrawBundle bundle = {
.pipelineState = pipelineState,
.dsLayoutHandles = layoutHandles,
.descriptorSetMask = descriptorSetMask,
};
mPipelineState.bindInDraw = { true, bundle };
return;
}
}
// The normal, non-external sampler path
using VkDescriptorSetLayoutArray = VulkanPipelineLayoutCache::DescriptorSetLayoutArray;
VkDescriptorSetLayoutArray vkLayouts;
std::transform(layoutHandles.begin(), layoutHandles.end(), vkLayouts.begin(),
[](auto const& layout) -> VkDescriptorSetLayout {
if (!layout) {
return VK_NULL_HANDLE;
}
return layout->getVkLayout();
});
auto program = resource_ptr<VulkanProgram>::cast(&mResourceManager, pipelineState.program);
auto pipelineLayout = mPipelineLayoutCache.getLayout(vkLayouts, program);
bindPipelineImpl(pipelineState, pipelineLayout, descriptorSetMask);
}
void VulkanDriver::bindPipelineImpl(PipelineState const& pipelineState,
VkPipelineLayout pipelineLayout, fvkutils::DescriptorSetMask descriptorSetMask) {
FVK_SYSTRACE_SCOPE();
auto commands = mCurrentRenderPass.commandBuffer;
auto vbi = resource_ptr<VulkanVertexBufferInfo>::cast(&mResourceManager,
pipelineState.vertexBufferInfo);
RasterState const& rasterState = pipelineState.rasterState;
PolygonOffset const& depthOffset = pipelineState.polygonOffset;
auto program = resource_ptr<VulkanProgram>::cast(&mResourceManager, pipelineState.program);
commands->acquire(program);
// Update the VK raster state.
auto rt = mCurrentRenderPass.renderTarget;
VulkanPipelineCache::RasterState const vulkanRasterState{
.cullMode = fvkutils::getCullMode(rasterState.culling),
.frontFace = fvkutils::getFrontFace(rasterState.inverseFrontFaces),
.depthBiasEnable = (depthOffset.constant || depthOffset.slope) ? true : false,
.blendEnable = rasterState.hasBlending(),
.depthWriteEnable = rasterState.depthWrite,
.alphaToCoverageEnable = rasterState.alphaToCoverage,
.srcColorBlendFactor = fvkutils::getBlendFactor(rasterState.blendFunctionSrcRGB),
.dstColorBlendFactor = fvkutils::getBlendFactor(rasterState.blendFunctionDstRGB),
.srcAlphaBlendFactor = fvkutils::getBlendFactor(rasterState.blendFunctionSrcAlpha),
.dstAlphaBlendFactor = fvkutils::getBlendFactor(rasterState.blendFunctionDstAlpha),
.colorWriteMask = (VkColorComponentFlags) (rasterState.colorWrite ? 0xf : 0x0),
.rasterizationSamples = rt->getSamples(),
.depthClamp = rasterState.depthClamp,
.colorTargetCount = rt->getColorTargetCount(mCurrentRenderPass),
.colorBlendOp = rasterState.blendEquationRGB,
.alphaBlendOp = rasterState.blendEquationAlpha,
.depthCompareOp = rasterState.depthFunc,
.depthBiasConstantFactor = depthOffset.constant,
.depthBiasSlopeFactor = depthOffset.slope,
};
// unfortunately in Vulkan the topology is per pipeline
VkPrimitiveTopology const topology =
fvkutils::getPrimitiveTopology(pipelineState.primitiveType);
// Declare fixed-size arrays that get passed to the pipeCache and to vkCmdBindVertexBuffers.
VkVertexInputAttributeDescription const* attribDesc = vbi->getAttribDescriptions();
VkVertexInputBindingDescription const* bufferDesc = vbi->getBufferDescriptions();
// Push state changes to the VulkanPipelineCache instance. This is fast and does not make VK calls.
mPipelineCache.bindProgram(program);
mPipelineCache.bindRasterState(vulkanRasterState);
mPipelineCache.bindPrimitiveTopology(topology);
mPipelineCache.bindVertexArray(attribDesc, bufferDesc, vbi->getAttributeCount());
// Note that we cannot reinit mPipeline because the .bindInDraw metadata that needs to carry
// over even on bind.
mPipelineState.program = program;
mPipelineState.pipelineLayout = pipelineLayout;
mPipelineState.descriptorSetMask = descriptorSetMask;
mPipelineCache.bindLayout(pipelineLayout);
mPipelineCache.bindPipeline(mCurrentRenderPass.commandBuffer);
}
void VulkanDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
FVK_SYSTRACE_SCOPE();
VulkanCommandBuffer* commands = mCurrentRenderPass.commandBuffer;
VkCommandBuffer cmdbuffer = commands->buffer();
auto prim = resource_ptr<VulkanRenderPrimitive>::cast(&mResourceManager, rph);
commands->acquire(prim);
// This *must* match the VulkanVertexBufferInfo that was bound in bindPipeline(). But we want
// to allow to call this before bindPipeline(), so the validation can only happen in draw()
auto vbi = prim->vertexBuffer->vbi;
uint32_t const bufferCount = vbi->getAttributeCount();
VkDeviceSize const* offsets = vbi->getOffsets();
VkBuffer const* buffers = prim->vertexBuffer->getVkBuffers();
// Next bind the vertex buffers and index buffer. One potential performance improvement is to
// avoid rebinding these if they are already bound, but since we do not (yet) support subranges
// it would be rare for a client to make consecutive draw calls with the same render primitive.
vkCmdBindVertexBuffers(cmdbuffer, 0, bufferCount, buffers, offsets);
vkCmdBindIndexBuffer(cmdbuffer, prim->indexBuffer->buffer.getVkBuffer(), 0,
prim->indexBuffer->indexType);
}
void VulkanDriver::bindDescriptorSet(
backend::DescriptorSetHandle dsh,
backend::descriptor_set_t setIndex,
backend::DescriptorSetOffsetArray&& offsets) {
if (dsh) {
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
mDescriptorSetCache.bind(setIndex, set, std::move(offsets));
} else {
mDescriptorSetCache.unbind(setIndex);
}
}
void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
FVK_SYSTRACE_SCOPE();
VkCommandBuffer cmdbuffer = mCurrentRenderPass.commandBuffer->buffer();
auto const& [doBindInDraw, bundle] = mPipelineState.bindInDraw;
fvkutils::DescriptorSetMask setsWithExternalSamplers = {};
if (doBindInDraw) {
auto& layoutHandles = bundle.dsLayoutHandles;
setsWithExternalSamplers = mExternalImageManager.prepareBindSets(layoutHandles,
mDescriptorSetCache.getBoundSets());
VulkanDescriptorSetLayout::DescriptorSetLayoutArray vklayouts;
for (size_t i = 0; i < layoutHandles.size(); i++) {
if (!layoutHandles[i]) {
vklayouts[i] = VK_NULL_HANDLE;
continue;
}
if (setsWithExternalSamplers[i]) {
vklayouts[i] = layoutHandles[i]->getExternalSamplerVkLayout();
} else {
vklayouts[i] = layoutHandles[i]->getVkLayout();
}
}
auto program =
resource_ptr<VulkanProgram>::cast(&mResourceManager, bundle.pipelineState.program);
VkPipelineLayout const pipelineLayout = mPipelineLayoutCache.getLayout(vklayouts, program);
if (pipelineLayout != mPipelineState.pipelineLayout) {
bindPipelineImpl(bundle.pipelineState, pipelineLayout, bundle.descriptorSetMask);
}
mPipelineState.bindInDraw.first = false;
}
mDescriptorSetCache.commit(mCurrentRenderPass.commandBuffer, mPipelineState.pipelineLayout,
setsWithExternalSamplers, mPipelineState.descriptorSetMask);
// Finally, make the actual draw call. TODO: support subranges
uint32_t const firstIndex = indexOffset;
constexpr int32_t vertexOffset = 0;
constexpr uint32_t firstInstId = 0;
vkCmdDrawIndexed(cmdbuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstId);
}
void VulkanDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph,
uint32_t const indexOffset, uint32_t const indexCount, uint32_t const instanceCount) {
auto rp = resource_ptr<VulkanRenderPrimitive>::cast(&mResourceManager, rph);
state.primitiveType = rp->type;
state.vertexBufferInfo = Handle<HwVertexBufferInfo>(rp->vertexBuffer->vbi.id());
bindPipeline(state);
bindRenderPrimitive(rph);
draw2(indexOffset, indexCount, instanceCount);
}
void VulkanDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
// FIXME: implement me
}
void VulkanDriver::scissor(Viewport scissorBox) {
VkCommandBuffer cmdbuffer = mCurrentRenderPass.commandBuffer->buffer();
// TODO: it's a common case that scissor() is called with (0, 0, maxint, maxint)
// we should maybe have a fast path for this and avoid vkCmdSetScissor() if possible
// Set scissoring.
// clamp left-bottom to 0,0 and avoid overflows
constexpr int32_t maxvali = std::numeric_limits<int32_t>::max();
constexpr uint32_t maxvalu = std::numeric_limits<int32_t>::max();
int32_t l = scissorBox.left;
int32_t b = scissorBox.bottom;
uint32_t w = std::min(maxvalu, scissorBox.width);
uint32_t h = std::min(maxvalu, scissorBox.height);
int32_t r = (l > int32_t(maxvalu - w)) ? maxvali : l + int32_t(w);
int32_t t = (b > int32_t(maxvalu - h)) ? maxvali : b + int32_t(h);
l = std::max(0, l);
b = std::max(0, b);
assert_invariant(r >= l && t >= b);
VkRect2D scissor{
.offset = { l, b },
.extent = { uint32_t(r - l), uint32_t(t - b) }
};
auto rt = mCurrentRenderPass.renderTarget;
rt->transformClientRectToPlatform(&scissor);
vkCmdSetScissor(cmdbuffer, 0, 1, &scissor);
}
void VulkanDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
auto vtq = resource_ptr<VulkanTimerQuery>::cast(&mResourceManager, tqh);
mQueryManager.beginQuery(&(mCommands.get()), vtq);
}
void VulkanDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
auto vtq = resource_ptr<VulkanTimerQuery>::cast(&mResourceManager, tqh);
mQueryManager.endQuery(&(mCommands.get()), vtq);
}
void VulkanDriver::debugCommandBegin(CommandStream* cmds, bool synchronous, const char* methodName) noexcept {
DriverBase::debugCommandBegin(cmds, synchronous, methodName);
#ifndef NDEBUG
static const std::set<std::string_view> OUTSIDE_COMMANDS = {
"loadUniformBuffer",
"updateBufferObject",
"updateIndexBuffer",
"update3DImage",
};
static const std::string_view BEGIN_COMMAND = "beginRenderPass";
static const std::string_view END_COMMAND = "endRenderPass";
static bool inRenderPass = false; // for debug only
const std::string_view command{ methodName };
if (command == BEGIN_COMMAND) {
assert_invariant(!inRenderPass);
inRenderPass = true;
} else if (command == END_COMMAND) {
assert_invariant(inRenderPass);
inRenderPass = false;
} else if (inRenderPass && OUTSIDE_COMMANDS.find(command) != OUTSIDE_COMMANDS.end()) {
FVK_LOGE << command.data() << " issued inside a render pass.";
}
#endif
}
void VulkanDriver::resetState(int) {
}
void VulkanDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) {
mResourceManager.associateHandle(handleId, std::move(tag));
}
void VulkanDriver::endCommandRecording() {
mCommands.flush();
mPipelineCache.resetBoundPipeline();
}
// explicit instantiation of the Dispatcher
template class ConcreteDispatcher<VulkanDriver>;
} // namespace filament::backend
#if defined(__clang__)
#pragma clang diagnostic pop
#endif