This fixes validation errors and makes a first pass at simplification. VulkanTexture now tracks image layout using RangeMap, which paves the way for further simplification.
1909 lines
76 KiB
C++
1909 lines
76 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 "vulkan/VulkanDriver.h"
|
|
|
|
#include "CommandStreamDispatcher.h"
|
|
#include "DataReshaper.h"
|
|
#include "VulkanBuffer.h"
|
|
#include "VulkanCommands.h"
|
|
#include "VulkanDriverFactory.h"
|
|
#include "VulkanHandles.h"
|
|
#include "VulkanMemory.h"
|
|
#include "VulkanPlatform.h"
|
|
|
|
#include <utils/CString.h>
|
|
#include <utils/FixedCapacityVector.h>
|
|
#include <utils/Panic.h>
|
|
#include <utils/trap.h>
|
|
|
|
#ifndef NDEBUG
|
|
#include <set>
|
|
#endif
|
|
|
|
using namespace bluevk;
|
|
|
|
using utils::FixedCapacityVector;
|
|
|
|
// 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"
|
|
|
|
#if VK_ENABLE_VALIDATION
|
|
|
|
namespace {
|
|
|
|
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) {
|
|
utils::slog.e << "VULKAN ERROR: (" << pLayerPrefix << ") " << pMessage << utils::io::endl;
|
|
utils::debug_trap();
|
|
} else {
|
|
utils::slog.w << "VULKAN WARNING: (" << pLayerPrefix << ") "
|
|
<< pMessage << utils::io::endl;
|
|
}
|
|
return VK_FALSE;
|
|
}
|
|
|
|
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) {
|
|
utils::slog.e << "VULKAN ERROR: (" << cbdata->pMessageIdName << ") "
|
|
<< cbdata->pMessage << utils::io::endl;
|
|
utils::debug_trap();
|
|
} 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;
|
|
}
|
|
utils::slog.w << "VULKAN WARNING: (" << cbdata->pMessageIdName << ") "
|
|
<< cbdata->pMessage << utils::io::endl;
|
|
}
|
|
return VK_FALSE;
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
namespace filament {
|
|
namespace backend {
|
|
|
|
Driver* VulkanDriverFactory::create(VulkanPlatform* const platform,
|
|
const char* const* ppRequiredExtensions, uint32_t requiredExtensionCount) noexcept {
|
|
return VulkanDriver::create(platform, ppRequiredExtensions, requiredExtensionCount);
|
|
}
|
|
|
|
VulkanDriver::VulkanDriver(VulkanPlatform* platform,
|
|
const char* const* ppRequiredExtensions, uint32_t requiredExtensionCount) noexcept :
|
|
DriverBase(new ConcreteDispatcher<VulkanDriver>()),
|
|
mHandleAllocator("Handles", FILAMENT_VULKAN_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U),
|
|
mContextManager(*platform),
|
|
mStagePool(mContext),
|
|
mFramebufferCache(mContext),
|
|
mSamplerCache(mContext),
|
|
mBlitter(mContext, mStagePool, mPipelineCache, mFramebufferCache, mSamplerCache) {
|
|
mContext.rasterState = mPipelineCache.getDefaultRasterState();
|
|
|
|
// Load Vulkan entry points.
|
|
ASSERT_POSTCONDITION(bluevk::initialize(), "BlueVK is unable to load entry points.");
|
|
|
|
// Determine if the VK_EXT_debug_utils instance extension is available.
|
|
mContext.debugUtilsSupported = false;
|
|
uint32_t availableExtsCount = 0;
|
|
vkEnumerateInstanceExtensionProperties(nullptr, &availableExtsCount, nullptr);
|
|
utils::FixedCapacityVector<VkExtensionProperties> availableExts(availableExtsCount);
|
|
vkEnumerateInstanceExtensionProperties(nullptr, &availableExtsCount, availableExts.data());
|
|
for (const auto& extProps : availableExts) {
|
|
if (!strcmp(extProps.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) {
|
|
mContext.debugUtilsSupported = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
VkInstanceCreateInfo instanceCreateInfo = {};
|
|
|
|
bool validationFeaturesSupported = false;
|
|
|
|
#if VK_ENABLE_VALIDATION
|
|
const utils::StaticString DESIRED_LAYERS[] = {
|
|
"VK_LAYER_KHRONOS_validation",
|
|
#if FILAMENT_VULKAN_DUMP_API
|
|
"VK_LAYER_LUNARG_api_dump",
|
|
#endif
|
|
#if defined(ENABLE_RENDERDOC)
|
|
"VK_LAYER_RENDERDOC_Capture",
|
|
#endif
|
|
};
|
|
|
|
constexpr size_t kMaxEnabledLayersCount = sizeof(DESIRED_LAYERS) / sizeof(DESIRED_LAYERS[0]);
|
|
|
|
uint32_t layerCount;
|
|
vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
|
|
FixedCapacityVector<VkLayerProperties> availableLayers(layerCount);
|
|
vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());
|
|
auto enabledLayers = FixedCapacityVector<const char*>::with_capacity(kMaxEnabledLayersCount);
|
|
for (const auto& desired : DESIRED_LAYERS) {
|
|
for (const VkLayerProperties& layer : availableLayers) {
|
|
const utils::CString availableLayer(layer.layerName);
|
|
if (availableLayer == desired) {
|
|
enabledLayers.push_back(desired.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!enabledLayers.empty()) {
|
|
instanceCreateInfo.enabledLayerCount = (uint32_t) enabledLayers.size();
|
|
instanceCreateInfo.ppEnabledLayerNames = enabledLayers.data();
|
|
|
|
// Check if VK_EXT_validation_features is supported.
|
|
uint32_t availableExtsCount = 0;
|
|
vkEnumerateInstanceExtensionProperties("VK_LAYER_KHRONOS_validation", &availableExtsCount, nullptr);
|
|
utils::FixedCapacityVector<VkExtensionProperties> availableExts(availableExtsCount);
|
|
vkEnumerateInstanceExtensionProperties("VK_LAYER_KHRONOS_validation", &availableExtsCount, availableExts.data());
|
|
for (const auto& extProps : availableExts) {
|
|
if (!strcmp(extProps.extensionName, VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME)) {
|
|
validationFeaturesSupported = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
#if defined(__ANDROID__)
|
|
utils::slog.d << "Validation layers are not available; did you set jniLibs in your "
|
|
<< "gradle file?" << utils::io::endl;
|
|
#else
|
|
utils::slog.d << "Validation layer not available; did you install the Vulkan SDK?\n"
|
|
<< "Please ensure that VK_LAYER_PATH is set correctly." << utils::io::endl;
|
|
#endif
|
|
}
|
|
#endif // VK_ENABLE_VALIDATION
|
|
|
|
// The Platform class can require 1 or 2 instance extensions, plus we'll request at most 5
|
|
// instance extensions here in the common code. So that's a max of 7.
|
|
static constexpr uint32_t MAX_INSTANCE_EXTENSION_COUNT = 7;
|
|
const char* ppEnabledExtensions[MAX_INSTANCE_EXTENSION_COUNT];
|
|
uint32_t enabledExtensionCount = 0;
|
|
|
|
// Request all cross-platform extensions.
|
|
ppEnabledExtensions[enabledExtensionCount++] = "VK_KHR_surface";
|
|
ppEnabledExtensions[enabledExtensionCount++] = "VK_KHR_get_physical_device_properties2";
|
|
#if VK_ENABLE_VALIDATION
|
|
#if defined(__ANDROID__)
|
|
ppEnabledExtensions[enabledExtensionCount++] = "VK_EXT_debug_report";
|
|
#endif
|
|
if (validationFeaturesSupported) {
|
|
ppEnabledExtensions[enabledExtensionCount++] = "VK_EXT_validation_features";
|
|
}
|
|
#endif
|
|
if (mContext.debugUtilsSupported) {
|
|
ppEnabledExtensions[enabledExtensionCount++] = "VK_EXT_debug_utils";
|
|
}
|
|
|
|
// Request platform-specific extensions.
|
|
for (uint32_t i = 0; i < requiredExtensionCount; ++i) {
|
|
assert_invariant(enabledExtensionCount < MAX_INSTANCE_EXTENSION_COUNT);
|
|
ppEnabledExtensions[enabledExtensionCount++] = ppRequiredExtensions[i];
|
|
}
|
|
|
|
// Create the Vulkan instance.
|
|
VkApplicationInfo appInfo = {};
|
|
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
|
appInfo.apiVersion = VK_MAKE_VERSION(VK_REQUIRED_VERSION_MAJOR, VK_REQUIRED_VERSION_MINOR, 0);
|
|
instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
|
instanceCreateInfo.pApplicationInfo = &appInfo;
|
|
instanceCreateInfo.enabledExtensionCount = enabledExtensionCount;
|
|
instanceCreateInfo.ppEnabledExtensionNames = ppEnabledExtensions;
|
|
|
|
VkValidationFeaturesEXT features = {};
|
|
VkValidationFeatureEnableEXT enables[] = {
|
|
VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT,
|
|
// TODO: Enable synchronization validation.
|
|
// VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT,
|
|
};
|
|
if (validationFeaturesSupported) {
|
|
features.sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT;
|
|
features.enabledValidationFeatureCount = sizeof(enables) / sizeof(enables[0]);
|
|
features.pEnabledValidationFeatures = enables;
|
|
instanceCreateInfo.pNext = &features;
|
|
}
|
|
|
|
VkResult result = vkCreateInstance(&instanceCreateInfo, VKALLOC, &mContext.instance);
|
|
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create Vulkan instance.");
|
|
bluevk::bindInstance(mContext.instance);
|
|
UTILS_UNUSED const PFN_vkCreateDebugReportCallbackEXT createDebugReportCallback =
|
|
vkCreateDebugReportCallbackEXT;
|
|
|
|
#if VK_ENABLE_VALIDATION
|
|
if (mContext.debugUtilsSupported) {
|
|
VkDebugUtilsMessengerCreateInfoEXT 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
|
|
};
|
|
result = vkCreateDebugUtilsMessengerEXT(mContext.instance, &createInfo, VKALLOC, &mDebugMessenger);
|
|
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create Vulkan debug messenger.");
|
|
} else if (createDebugReportCallback) {
|
|
const VkDebugReportCallbackCreateInfoEXT cbinfo = {
|
|
VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT,
|
|
nullptr,
|
|
VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_ERROR_BIT_EXT,
|
|
debugReportCallback,
|
|
nullptr
|
|
};
|
|
result = createDebugReportCallback(mContext.instance, &cbinfo, VKALLOC, &mDebugCallback);
|
|
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create Vulkan debug callback.");
|
|
}
|
|
#endif
|
|
|
|
// Initialize the following fields: physicalDevice, physicalDeviceProperties,
|
|
// physicalDeviceFeatures, graphicsQueueFamilyIndex.
|
|
mContext.selectPhysicalDevice();
|
|
|
|
// Initialize device, graphicsQueue, and command buffer manager.
|
|
mContext.createLogicalDevice();
|
|
|
|
mContext.createEmptyTexture(mStagePool);
|
|
|
|
mContext.commands->setObserver(&mPipelineCache);
|
|
mPipelineCache.setDevice(mContext.device, mContext.allocator);
|
|
mPipelineCache.setDummyTexture(mContext.emptyTexture->getPrimaryImageView());
|
|
|
|
// Choose a depth format that meets our requirements. Take care not to include stencil formats
|
|
// just yet, since that would require a corollary change to the "aspect" flags for the VkImage.
|
|
const VkFormat formats[] = { VK_FORMAT_D32_SFLOAT, VK_FORMAT_X8_D24_UNORM_PACK32 };
|
|
mContext.finalDepthFormat = mContext.findSupportedFormat(
|
|
utils::Slice<VkFormat>(formats, formats + 2),
|
|
VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT);
|
|
|
|
// For diagnostic purposes, print useful information about available depth formats.
|
|
// Note that Vulkan is more constrained than OpenGL ES 3.1 in this area.
|
|
#if VK_ENABLE_VALIDATION
|
|
const VkFormatFeatureFlags required = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT |
|
|
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
|
|
utils::slog.i << "Sampleable depth formats: ";
|
|
for (VkFormat format = (VkFormat) 1;;) {
|
|
VkFormatProperties props;
|
|
vkGetPhysicalDeviceFormatProperties(mContext.physicalDevice, format, &props);
|
|
if ((props.optimalTilingFeatures & required) == required) {
|
|
utils::slog.i << format << " ";
|
|
}
|
|
if (format == VK_FORMAT_ASTC_12x12_SRGB_BLOCK) {
|
|
utils::slog.i << utils::io::endl;
|
|
break;
|
|
}
|
|
format = (VkFormat) (1 + (int) format);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
VulkanDriver::~VulkanDriver() noexcept = default;
|
|
|
|
UTILS_NOINLINE
|
|
Driver* VulkanDriver::create(VulkanPlatform* const platform,
|
|
const char* const* ppEnabledExtensions, uint32_t enabledExtensionCount) noexcept {
|
|
assert_invariant(platform);
|
|
return new VulkanDriver(platform, ppEnabledExtensions, enabledExtensionCount);
|
|
}
|
|
|
|
ShaderModel VulkanDriver::getShaderModel() const noexcept {
|
|
#if defined(__ANDROID__) || defined(IOS)
|
|
return ShaderModel::GL_ES_30;
|
|
#else
|
|
return ShaderModel::GL_CORE_41;
|
|
#endif
|
|
}
|
|
|
|
void VulkanDriver::terminate() {
|
|
if (!mContext.instance) {
|
|
return;
|
|
}
|
|
|
|
delete mContext.commands;
|
|
delete mContext.emptyTexture;
|
|
|
|
mBlitter.shutdown();
|
|
|
|
// Allow the stage pool and disposer to clean up.
|
|
mStagePool.gc();
|
|
mDisposer.reset();
|
|
|
|
mStagePool.reset();
|
|
mPipelineCache.destroyCache();
|
|
mFramebufferCache.reset();
|
|
mSamplerCache.reset();
|
|
|
|
vmaDestroyPool(mContext.allocator, mContext.vmaPoolGPU);
|
|
vmaDestroyPool(mContext.allocator, mContext.vmaPoolCPU);
|
|
vmaDestroyAllocator(mContext.allocator);
|
|
|
|
vkDestroyQueryPool(mContext.device, mContext.timestamps.pool, VKALLOC);
|
|
vkDestroyCommandPool(mContext.device, mContext.commandPool, VKALLOC);
|
|
vkDestroyDevice(mContext.device, VKALLOC);
|
|
if (mDebugCallback) {
|
|
vkDestroyDebugReportCallbackEXT(mContext.instance, mDebugCallback, VKALLOC);
|
|
}
|
|
if (mDebugMessenger) {
|
|
vkDestroyDebugUtilsMessengerEXT(mContext.instance, mDebugMessenger, VKALLOC);
|
|
}
|
|
vkDestroyInstance(mContext.instance, VKALLOC);
|
|
mContext.device = nullptr;
|
|
mContext.instance = nullptr;
|
|
}
|
|
|
|
void VulkanDriver::tick(int) {
|
|
mContext.commands->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() {
|
|
mStagePool.gc();
|
|
mFramebufferCache.gc();
|
|
mDisposer.gc();
|
|
mContext.commands->gc();
|
|
}
|
|
|
|
void VulkanDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) {
|
|
// Do nothing.
|
|
}
|
|
|
|
void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
|
backend::FrameScheduledCallback callback, void* user) {
|
|
|
|
}
|
|
|
|
void VulkanDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
|
backend::FrameCompletedCallback callback, void* user) {
|
|
|
|
}
|
|
|
|
void VulkanDriver::setPresentationTime(int64_t monotonic_clock_ns) {
|
|
}
|
|
|
|
void VulkanDriver::endFrame(uint32_t frameId) {
|
|
if (mContext.commands->flush()) {
|
|
collectGarbage();
|
|
}
|
|
}
|
|
|
|
void VulkanDriver::flush(int) {
|
|
mContext.commands->flush();
|
|
}
|
|
|
|
void VulkanDriver::finish(int dummy) {
|
|
mContext.commands->flush();
|
|
}
|
|
|
|
void VulkanDriver::createSamplerGroupR(Handle<HwSamplerGroup> sbh, uint32_t count) {
|
|
construct<VulkanSamplerGroup>(sbh, count);
|
|
}
|
|
|
|
void VulkanDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph, int) {
|
|
construct<VulkanRenderPrimitive>(rph);
|
|
}
|
|
|
|
void VulkanDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
|
if (rph) {
|
|
destruct<VulkanRenderPrimitive>(rph);
|
|
}
|
|
}
|
|
|
|
void VulkanDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh, uint8_t bufferCount,
|
|
uint8_t attributeCount, uint32_t elementCount, AttributeArray attributes) {
|
|
auto vertexBuffer = construct<VulkanVertexBuffer>(vbh, mContext, mStagePool,
|
|
bufferCount, attributeCount, elementCount, attributes);
|
|
mDisposer.createDisposable(vertexBuffer, [this, vbh] () {
|
|
destruct<VulkanVertexBuffer>(vbh);
|
|
});
|
|
}
|
|
|
|
void VulkanDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vbh) {
|
|
if (vbh) {
|
|
auto vertexBuffer = handle_cast<VulkanVertexBuffer*>(vbh);
|
|
mDisposer.removeReference(vertexBuffer);
|
|
}
|
|
}
|
|
|
|
void VulkanDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh,
|
|
ElementType elementType, uint32_t indexCount, BufferUsage usage) {
|
|
auto elementSize = (uint8_t) getElementTypeSize(elementType);
|
|
auto indexBuffer = construct<VulkanIndexBuffer>(ibh, mContext, mStagePool,
|
|
elementSize, indexCount);
|
|
mDisposer.createDisposable(indexBuffer, [this, ibh] () {
|
|
destruct<VulkanIndexBuffer>(mContext, ibh);
|
|
});
|
|
}
|
|
|
|
void VulkanDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
|
|
if (ibh) {
|
|
auto indexBuffer = handle_cast<VulkanIndexBuffer*>(ibh);
|
|
mDisposer.removeReference(indexBuffer);
|
|
}
|
|
}
|
|
|
|
void VulkanDriver::createBufferObjectR(Handle<HwBufferObject> boh,
|
|
uint32_t byteCount, BufferObjectBinding bindingType, BufferUsage usage) {
|
|
auto bufferObject = construct<VulkanBufferObject>(boh, mContext, mStagePool, byteCount,
|
|
bindingType, usage);
|
|
mDisposer.createDisposable(bufferObject, [this, boh] () {
|
|
destruct<VulkanBufferObject>(mContext, boh);
|
|
});
|
|
}
|
|
|
|
void VulkanDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
|
|
if (boh) {
|
|
auto bufferObject = handle_cast<VulkanBufferObject*>(boh);
|
|
if (bufferObject->bindingType == BufferObjectBinding::UNIFORM) {
|
|
mPipelineCache.unbindUniformBuffer(bufferObject->buffer.getGpuBuffer());
|
|
// Decrement the refcount of the uniform buffer, but schedule it for destruction a few
|
|
// frames in the future. To be safe, we need to assume that the current command buffer is
|
|
// still using it somewhere.
|
|
mDisposer.acquire(bufferObject);
|
|
}
|
|
mDisposer.removeReference(bufferObject);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
auto vktexture = construct<VulkanTexture>(th, mContext, target, levels,
|
|
format, samples, w, h, depth, usage, mStagePool);
|
|
mDisposer.createDisposable(vktexture, [this, th] () {
|
|
destruct<VulkanTexture>(th);
|
|
});
|
|
}
|
|
|
|
void VulkanDriver::createTextureSwizzledR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
|
|
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
|
|
TextureUsage usage,
|
|
TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) {
|
|
TextureSwizzle swizzleArray[] = {r, g, b, a};
|
|
const VkComponentMapping swizzleMap = getSwizzleMap(swizzleArray);
|
|
auto vktexture = construct<VulkanTexture>(th, mContext, target, levels,
|
|
format, samples, w, h, depth, usage, mStagePool, swizzleMap);
|
|
mDisposer.createDisposable(vktexture, [this, th] () {
|
|
destruct<VulkanTexture>(th);
|
|
});
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
void VulkanDriver::destroyTexture(Handle<HwTexture> th) {
|
|
if (th) {
|
|
auto texture = handle_cast<VulkanTexture*>(th);
|
|
mPipelineCache.unbindImageView(texture->getPrimaryImageView());
|
|
mDisposer.removeReference(texture);
|
|
}
|
|
}
|
|
|
|
void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
|
auto vkprogram = construct<VulkanProgram>(ph, mContext, program);
|
|
mDisposer.createDisposable(vkprogram, [this, ph] () {
|
|
destruct<VulkanProgram>(ph);
|
|
});
|
|
}
|
|
|
|
void VulkanDriver::destroyProgram(Handle<HwProgram> ph) {
|
|
if (ph) {
|
|
mDisposer.removeReference(handle_cast<VulkanProgram*>(ph));
|
|
}
|
|
}
|
|
|
|
void VulkanDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {
|
|
auto renderTarget = construct<VulkanRenderTarget>(rth, mContext);
|
|
mDisposer.createDisposable(renderTarget, [this, rth] () {
|
|
destruct<VulkanRenderTarget>(rth);
|
|
});
|
|
}
|
|
|
|
void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
|
TargetBufferFlags targets, uint32_t width, uint32_t height, uint8_t samples,
|
|
backend::MRT color, TargetBufferInfo depth, TargetBufferInfo stencil) {
|
|
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 = handle_cast<VulkanTexture*>(color[i].handle);
|
|
}
|
|
colorTargets[i].level = color[i].level;
|
|
colorTargets[i].layer = color[i].layer;
|
|
}
|
|
|
|
VulkanAttachment depthStencil[2] = {};
|
|
TextureHandle handle = depth.handle;
|
|
depthStencil[0].texture = handle ? handle_cast<VulkanTexture*>(handle) : nullptr;
|
|
depthStencil[0].level = depth.level;
|
|
depthStencil[0].layer = depth.layer;
|
|
|
|
handle = stencil.handle;
|
|
depthStencil[1].texture = handle ? handle_cast<VulkanTexture*>(handle) : nullptr;
|
|
depthStencil[1].level = stencil.level;
|
|
depthStencil[1].layer = stencil.layer;
|
|
|
|
auto renderTarget = construct<VulkanRenderTarget>(rth, mContext,
|
|
width, height, samples, colorTargets, depthStencil, mStagePool);
|
|
mDisposer.createDisposable(renderTarget, [this, rth] () {
|
|
destruct<VulkanRenderTarget>(rth);
|
|
});
|
|
}
|
|
|
|
void VulkanDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
|
|
if (rth) {
|
|
mDisposer.removeReference(handle_cast<VulkanRenderTarget*>(rth));
|
|
}
|
|
}
|
|
|
|
void VulkanDriver::createFenceR(Handle<HwFence> fh, int) {
|
|
VulkanCommandBuffer const& commandBuffer = mContext.commands->get();
|
|
construct<VulkanFence>(fh, commandBuffer);
|
|
}
|
|
|
|
void VulkanDriver::createSyncR(Handle<HwSync> sh, int) {
|
|
VulkanCommandBuffer const& commandBuffer = mContext.commands->get();
|
|
construct<VulkanSync>(sh, commandBuffer);
|
|
}
|
|
|
|
void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
|
|
const VkInstance instance = mContext.instance;
|
|
auto vksurface = (VkSurfaceKHR) mContextManager.createVkSurfaceKHR(nativeWindow, instance,
|
|
flags);
|
|
construct<VulkanSwapChain>(sch, mContext, vksurface);
|
|
}
|
|
|
|
void VulkanDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch,
|
|
uint32_t width, uint32_t height, uint64_t flags) {
|
|
assert_invariant(width > 0 && height > 0 && "Vulkan requires non-zero swap chain dimensions.");
|
|
construct<VulkanSwapChain>(sch, mContext, width, height);
|
|
}
|
|
|
|
void VulkanDriver::createStreamFromTextureIdR(Handle<HwStream> sh, intptr_t externalTextureId,
|
|
uint32_t width, uint32_t height) {
|
|
}
|
|
|
|
void VulkanDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
|
|
// nothing to do, timer query was constructed in createTimerQueryS
|
|
}
|
|
|
|
Handle<HwVertexBuffer> VulkanDriver::createVertexBufferS() noexcept {
|
|
return allocHandle<VulkanVertexBuffer>();
|
|
}
|
|
|
|
Handle<HwIndexBuffer> VulkanDriver::createIndexBufferS() noexcept {
|
|
return allocHandle<VulkanIndexBuffer>();
|
|
}
|
|
|
|
Handle<HwBufferObject> VulkanDriver::createBufferObjectS() noexcept {
|
|
return allocHandle<VulkanBufferObject>();
|
|
}
|
|
|
|
Handle<HwTexture> VulkanDriver::createTextureS() noexcept {
|
|
return allocHandle<VulkanTexture>();
|
|
}
|
|
|
|
Handle<HwTexture> VulkanDriver::createTextureSwizzledS() noexcept {
|
|
return allocHandle<VulkanTexture>();
|
|
}
|
|
|
|
Handle<HwTexture> VulkanDriver::importTextureS() noexcept {
|
|
return allocHandle<VulkanTexture>();
|
|
}
|
|
|
|
Handle<HwSamplerGroup> VulkanDriver::createSamplerGroupS() noexcept {
|
|
return allocHandle<VulkanSamplerGroup>();
|
|
}
|
|
|
|
Handle<HwRenderPrimitive> VulkanDriver::createRenderPrimitiveS() noexcept {
|
|
return allocHandle<VulkanRenderPrimitive>();
|
|
}
|
|
|
|
Handle<HwProgram> VulkanDriver::createProgramS() noexcept {
|
|
return allocHandle<VulkanProgram>();
|
|
}
|
|
|
|
Handle<HwRenderTarget> VulkanDriver::createDefaultRenderTargetS() noexcept {
|
|
return allocHandle<VulkanRenderTarget>();
|
|
}
|
|
|
|
Handle<HwRenderTarget> VulkanDriver::createRenderTargetS() noexcept {
|
|
return allocHandle<VulkanRenderTarget>();
|
|
}
|
|
|
|
Handle<HwFence> VulkanDriver::createFenceS() noexcept {
|
|
return allocHandle<VulkanFence>();
|
|
}
|
|
|
|
Handle<HwSync> VulkanDriver::createSyncS() noexcept {
|
|
Handle<HwSync> sh = allocHandle<VulkanSync>();
|
|
construct<VulkanSync>(sh);
|
|
return sh;
|
|
}
|
|
|
|
Handle<HwSwapChain> VulkanDriver::createSwapChainS() noexcept {
|
|
return allocHandle<VulkanSwapChain>();
|
|
}
|
|
|
|
Handle<HwSwapChain> VulkanDriver::createSwapChainHeadlessS() noexcept {
|
|
return allocHandle<VulkanSwapChain>();
|
|
}
|
|
|
|
Handle<HwStream> VulkanDriver::createStreamFromTextureIdS() noexcept {
|
|
return {};
|
|
}
|
|
|
|
Handle<HwTimerQuery> VulkanDriver::createTimerQueryS() noexcept {
|
|
// The handle must be constructed here, as a synchronous call to getTimerQueryValue might happen
|
|
// before createTimerQueryR is executed.
|
|
Handle<HwTimerQuery> tqh = initHandle<VulkanTimerQuery>(mContext);
|
|
auto query = handle_cast<VulkanTimerQuery*>(tqh);
|
|
mDisposer.createDisposable(query, [this, tqh] () {
|
|
destruct<VulkanTimerQuery>(tqh);
|
|
});
|
|
return tqh;
|
|
}
|
|
|
|
void VulkanDriver::destroySamplerGroup(Handle<HwSamplerGroup> sbh) {
|
|
if (sbh) {
|
|
// Unlike most of the other "Hw" handles, the sampler buffer is an abstract concept and does
|
|
// not map to any Vulkan objects. To handle destruction, the only thing we need to do is
|
|
// ensure that the next draw call doesn't try to access a zombie sampler buffer. Therefore,
|
|
// simply replace all weak references with null.
|
|
auto* hwsb = handle_cast<VulkanSamplerGroup*>(sbh);
|
|
for (auto& binding : mSamplerBindings) {
|
|
if (binding == hwsb) {
|
|
binding = nullptr;
|
|
}
|
|
}
|
|
destruct<VulkanSamplerGroup>(sbh);
|
|
}
|
|
}
|
|
|
|
void VulkanDriver::destroySwapChain(Handle<HwSwapChain> sch) {
|
|
if (sch) {
|
|
VulkanSwapChain& surfaceContext = *handle_cast<VulkanSwapChain*>(sch);
|
|
surfaceContext.destroy();
|
|
|
|
vkDestroySurfaceKHR(mContext.instance, surfaceContext.surface, VKALLOC);
|
|
if (mContext.currentSurface == &surfaceContext) {
|
|
mContext.currentSurface = nullptr;
|
|
}
|
|
|
|
destruct<VulkanSwapChain>(sch);
|
|
}
|
|
}
|
|
|
|
void VulkanDriver::destroyStream(Handle<HwStream> sh) {
|
|
}
|
|
|
|
void VulkanDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
|
|
if (tqh) {
|
|
mDisposer.removeReference(handle_cast<VulkanTimerQuery*>(tqh));
|
|
}
|
|
}
|
|
|
|
void VulkanDriver::destroySync(Handle<HwSync> sh) {
|
|
destruct<VulkanSync>(sh);
|
|
}
|
|
|
|
|
|
Handle<HwStream> VulkanDriver::createStreamNative(void* nativeStream) {
|
|
return {};
|
|
}
|
|
|
|
Handle<HwStream> VulkanDriver::createStreamAcquired() {
|
|
return {};
|
|
}
|
|
|
|
void VulkanDriver::setAcquiredImage(Handle<HwStream> sh, void* image,
|
|
backend::CallbackHandler* handler, backend::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) {
|
|
destruct<VulkanFence>(fh);
|
|
}
|
|
|
|
FenceStatus VulkanDriver::wait(Handle<HwFence> fh, uint64_t timeout) {
|
|
auto& cmdfence = handle_cast<VulkanFence*>(fh)->fence;
|
|
|
|
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted".
|
|
// When this fence gets submitted, its status changes to VK_NOT_READY.
|
|
std::unique_lock<utils::Mutex> lock(cmdfence->mutex);
|
|
if (cmdfence->status.load() == VK_INCOMPLETE) {
|
|
// This will obviously timeout if Filament creates a fence and immediately waits on it
|
|
// without calling endFrame() or commit().
|
|
cmdfence->condition.wait(lock);
|
|
} else {
|
|
lock.unlock();
|
|
}
|
|
VkResult result = vkWaitForFences(mContext.device, 1, &cmdfence->fence, VK_TRUE, timeout);
|
|
return result == VK_SUCCESS ? FenceStatus::CONDITION_SATISFIED : 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) {
|
|
assert_invariant(mContext.physicalDevice);
|
|
VkFormat vkformat = getVkFormat(format);
|
|
// We automatically use an alternative format when the client requests DEPTH24.
|
|
if (format == TextureFormat::DEPTH24) {
|
|
vkformat = mContext.finalDepthFormat;
|
|
}
|
|
if (vkformat == VK_FORMAT_UNDEFINED) {
|
|
return false;
|
|
}
|
|
VkFormatProperties info;
|
|
vkGetPhysicalDeviceFormatProperties(mContext.physicalDevice, vkformat, &info);
|
|
return info.optimalTilingFeatures != 0;
|
|
}
|
|
|
|
bool VulkanDriver::isTextureSwizzleSupported() {
|
|
return true;
|
|
}
|
|
|
|
bool VulkanDriver::isTextureFormatMipmappable(backend::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) {
|
|
assert_invariant(mContext.physicalDevice);
|
|
VkFormat vkformat = getVkFormat(format);
|
|
// We automatically use an alternative format when the client requests DEPTH24.
|
|
if (format == TextureFormat::DEPTH24) {
|
|
vkformat = mContext.finalDepthFormat;
|
|
}
|
|
if (vkformat == VK_FORMAT_UNDEFINED) {
|
|
return false;
|
|
}
|
|
VkFormatProperties info;
|
|
vkGetPhysicalDeviceFormatProperties(mContext.physicalDevice, vkformat, &info);
|
|
return (info.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0;
|
|
}
|
|
|
|
bool VulkanDriver::isFrameBufferFetchSupported() {
|
|
return true;
|
|
}
|
|
|
|
bool VulkanDriver::isFrameBufferFetchMultiSampleSupported() {
|
|
return false;
|
|
}
|
|
|
|
bool VulkanDriver::isFrameTimeSupported() {
|
|
return true;
|
|
}
|
|
|
|
bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) {
|
|
VkPhysicalDeviceProperties const& deviceProperties = mContext.physicalDeviceProperties;
|
|
switch (workaround) {
|
|
case Workaround::SPLIT_EASU:
|
|
// early exit condition is flattened in EASU code
|
|
return deviceProperties.vendorID == 0x5143; // Qualcomm
|
|
}
|
|
return false;
|
|
}
|
|
|
|
math::float2 VulkanDriver::getClipSpaceParams() {
|
|
// virtual and physical z-coordinate of clip-space is in [-w, 0]
|
|
// Note: this is actually never used (see: 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 backend::MRT::MIN_SUPPORTED_RENDER_TARGET_COUNT; // TODO: query real value
|
|
}
|
|
|
|
void VulkanDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t index,
|
|
Handle<HwBufferObject> boh) {
|
|
auto& vb = *handle_cast<VulkanVertexBuffer*>(vbh);
|
|
auto& bo = *handle_cast<VulkanBufferObject*>(boh);
|
|
assert_invariant(bo.bindingType == BufferObjectBinding::VERTEX);
|
|
vb.buffers[index] = &bo.buffer;
|
|
}
|
|
|
|
void VulkanDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& p,
|
|
uint32_t byteOffset) {
|
|
auto ib = handle_cast<VulkanIndexBuffer*>(ibh);
|
|
ib->buffer.loadFromCpu(mContext, mStagePool, p.buffer, byteOffset, p.size);
|
|
mDisposer.acquire(ib);
|
|
scheduleDestroy(std::move(p));
|
|
}
|
|
|
|
void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& bd,
|
|
uint32_t byteOffset) {
|
|
auto bo = handle_cast<VulkanBufferObject*>(boh);
|
|
bo->buffer.loadFromCpu(mContext, mStagePool, bd.buffer, byteOffset, bd.size);
|
|
mDisposer.acquire(bo);
|
|
scheduleDestroy(std::move(bd));
|
|
}
|
|
|
|
void VulkanDriver::update2DImage(Handle<HwTexture> th,
|
|
uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
|
|
PixelBufferDescriptor&& data) {
|
|
assert_invariant(xoffset == 0 && yoffset == 0 && "Offsets not yet supported.");
|
|
handle_cast<VulkanTexture*>(th)->update2DImage(data, width, height, level);
|
|
scheduleDestroy(std::move(data));
|
|
}
|
|
|
|
void VulkanDriver::setMinMaxLevels(Handle<HwTexture> th, uint32_t minLevel, uint32_t maxLevel) {
|
|
handle_cast<VulkanTexture*>(th)->setPrimaryRange(minLevel, maxLevel);
|
|
}
|
|
|
|
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) {
|
|
assert_invariant(xoffset == 0 && yoffset == 0 && zoffset == 0 && "Offsets not yet supported.");
|
|
handle_cast<VulkanTexture*>(th)->update3DImage(data, width, height, depth, level);
|
|
scheduleDestroy(std::move(data));
|
|
}
|
|
|
|
void VulkanDriver::updateCubeImage(Handle<HwTexture> th, uint32_t level,
|
|
PixelBufferDescriptor&& data, FaceOffsets faceOffsets) {
|
|
handle_cast<VulkanTexture*>(th)->updateCubeImage(data, faceOffsets, level);
|
|
scheduleDestroy(std::move(data));
|
|
}
|
|
|
|
void VulkanDriver::setupExternalImage(void* image) {
|
|
}
|
|
|
|
void VulkanDriver::cancelExternalImage(void* image) {
|
|
}
|
|
|
|
bool VulkanDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
|
|
VulkanTimerQuery* vtq = handle_cast<VulkanTimerQuery*>(tqh);
|
|
|
|
// This is a synchronous call and might occur before beginTimerQuery has written anything into
|
|
// the command buffer, which is an error according to the validation layer that ships in the
|
|
// Android NDK. Even when AVAILABILITY_BIT is set, validation seems to require that the
|
|
// timestamp has at least been written into a processed command buffer.
|
|
VulkanCommandBuffer const* cmdbuf = vtq->cmdbuffer.load();
|
|
if (!cmdbuf || !cmdbuf->fence) {
|
|
return false;
|
|
}
|
|
VkResult status = cmdbuf->fence->status.load(std::memory_order_relaxed);
|
|
if (status != VK_SUCCESS) {
|
|
return false;
|
|
}
|
|
|
|
uint64_t results[4] = {};
|
|
size_t dataSize = sizeof(results);
|
|
VkDeviceSize stride = sizeof(uint64_t) * 2;
|
|
|
|
VkResult result = vkGetQueryPoolResults(mContext.device, mContext.timestamps.pool,
|
|
vtq->startingQueryIndex, 2, dataSize, (void*) results, stride,
|
|
VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WITH_AVAILABILITY_BIT);
|
|
|
|
uint64_t timestamp0 = results[0];
|
|
uint64_t available0 = results[1];
|
|
uint64_t timestamp1 = results[2];
|
|
uint64_t available1 = results[3];
|
|
|
|
if (result == VK_NOT_READY || available0 == 0 || available1 == 0) {
|
|
return false;
|
|
}
|
|
|
|
ASSERT_POSTCONDITION(result == VK_SUCCESS, "vkGetQueryPoolResults error.");
|
|
ASSERT_POSTCONDITION(timestamp1 >= timestamp0, "Timestamps are not monotonically increasing.");
|
|
|
|
// 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 period = mContext.physicalDeviceProperties.limits.timestampPeriod;
|
|
uint64_t delta = uint64_t(float(timestamp1 - timestamp0) * period);
|
|
*elapsedTime = delta;
|
|
return true;
|
|
}
|
|
|
|
SyncStatus VulkanDriver::getSyncStatus(Handle<HwSync> sh) {
|
|
VulkanSync* sync = handle_cast<VulkanSync*>(sh);
|
|
if (sync->fence == nullptr) {
|
|
return SyncStatus::NOT_SIGNALED;
|
|
}
|
|
VkResult status = sync->fence->status.load(std::memory_order_relaxed);
|
|
switch (status) {
|
|
case VK_SUCCESS: return SyncStatus::SIGNALED;
|
|
case VK_INCOMPLETE: return SyncStatus::NOT_SIGNALED;
|
|
case VK_NOT_READY: return SyncStatus::NOT_SIGNALED;
|
|
case VK_ERROR_DEVICE_LOST: return SyncStatus::ERROR;
|
|
default:
|
|
// NOTE: In theory, the fence status must be one of the above values.
|
|
return SyncStatus::ERROR;
|
|
}
|
|
}
|
|
|
|
void VulkanDriver::setExternalImage(Handle<HwTexture> th, void* image) {
|
|
}
|
|
|
|
void VulkanDriver::setExternalImagePlane(Handle<HwTexture> th, void* image, uint32_t plane) {
|
|
}
|
|
|
|
void VulkanDriver::setExternalStream(Handle<HwTexture> th, Handle<HwStream> sh) {
|
|
}
|
|
|
|
void VulkanDriver::generateMipmaps(Handle<HwTexture> th) { }
|
|
|
|
bool VulkanDriver::canGenerateMipmaps() {
|
|
return false;
|
|
}
|
|
|
|
void VulkanDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
|
|
SamplerGroup&& samplerGroup) {
|
|
auto* sb = handle_cast<VulkanSamplerGroup*>(sbh);
|
|
*sb->sb = samplerGroup;
|
|
}
|
|
|
|
void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassParams& params) {
|
|
mCurrentRenderTarget = handle_cast<VulkanRenderTarget*>(rth);
|
|
VulkanRenderTarget* const rt = mCurrentRenderTarget;
|
|
VulkanSwapChain* const sc = mContext.currentSurface;
|
|
|
|
const VkExtent2D extent = rt->getExtent(sc);
|
|
assert_invariant(extent.width > 0 && extent.height > 0);
|
|
|
|
// 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()) {
|
|
assert_invariant(sc);
|
|
VulkanSwapChain& surface = *sc;
|
|
if (surface.firstRenderPass) {
|
|
discardStart |= TargetBufferFlags::COLOR;
|
|
surface.firstRenderPass = false;
|
|
}
|
|
}
|
|
|
|
const VkCommandBuffer cmdbuffer = mContext.commands->get().cmdbuffer;
|
|
VulkanAttachment depth = rt->getDepth(sc);
|
|
VulkanTexture* depthFeedback = nullptr;
|
|
|
|
// If an uncleared depth buffer is attached but discarded at the end of the pass, then we should
|
|
// permit the shader to sample from it by transitioning the layout of all its subresources to a
|
|
// read-only layout. This is especially crucial for SSAO.
|
|
if (depth.texture && any(params.flags.discardEnd & TargetBufferFlags::DEPTH) &&
|
|
!any(params.flags.clear & TargetBufferFlags::DEPTH)) {
|
|
depthFeedback = depth.texture;
|
|
const VulkanLayoutTransition transition = {
|
|
.image = depth.image,
|
|
.oldLayout = depth.layout,
|
|
.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL,
|
|
.subresources = {
|
|
.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT,
|
|
.levelCount = depth.texture->levels,
|
|
.layerCount = depth.texture->depth,
|
|
},
|
|
.srcStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
|
.dstStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT
|
|
};
|
|
transitionImageLayout(cmdbuffer, transition);
|
|
depth.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
|
|
}
|
|
|
|
// Create the VkRenderPass or fetch it from cache.
|
|
VulkanFboCache::RenderPassKey rpkey = {
|
|
.depthLayout = depth.layout,
|
|
.depthFormat = depth.format,
|
|
.clear = params.flags.clear,
|
|
.discardStart = discardStart,
|
|
.discardEnd = params.flags.discardEnd,
|
|
.samples = rt->getSamples(),
|
|
.subpassMask = uint8_t(params.subpassMask)
|
|
};
|
|
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
|
rpkey.colorLayout[i] = rt->getColor(sc, i).layout;
|
|
rpkey.colorFormat[i] = rt->getColor(sc, i).format;
|
|
VulkanTexture* texture = rt->getColor(sc, i).texture;
|
|
if (rpkey.samples > 1 && texture && texture->samples == 1) {
|
|
rpkey.needsResolveMask |= (1 << i);
|
|
}
|
|
}
|
|
|
|
VkRenderPass renderPass = mFramebufferCache.getRenderPass(rpkey);
|
|
mPipelineCache.bindRenderPass(renderPass, 0);
|
|
|
|
// Create the VkFramebuffer or fetch it from cache.
|
|
VulkanFboCache::FboKey fbkey {
|
|
.renderPass = renderPass,
|
|
.width = (uint16_t) extent.width,
|
|
.height = (uint16_t) extent.height,
|
|
.layers = 1,
|
|
.samples = rpkey.samples
|
|
};
|
|
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
|
if (rt->getColor(sc, i).format == VK_FORMAT_UNDEFINED) {
|
|
fbkey.color[i] = VK_NULL_HANDLE;
|
|
fbkey.resolve[i] = VK_NULL_HANDLE;
|
|
} else if (fbkey.samples == 1) {
|
|
fbkey.color[i] = rt->getColor(sc, i).view;
|
|
fbkey.resolve[i] = VK_NULL_HANDLE;
|
|
assert_invariant(fbkey.color[i]);
|
|
} else {
|
|
fbkey.color[i] = rt->getMsaaColor(i).view;
|
|
VulkanTexture* texture = rt->getColor(sc, i).texture;
|
|
if (texture && texture->samples == 1) {
|
|
fbkey.resolve[i] = rt->getColor(sc, i).view;
|
|
assert_invariant(fbkey.resolve[i]);
|
|
}
|
|
assert_invariant(fbkey.color[i]);
|
|
}
|
|
}
|
|
if (depth.format != VK_FORMAT_UNDEFINED) {
|
|
fbkey.depth = rpkey.samples == 1 ? depth.view : rt->getMsaaDepth().view;
|
|
assert_invariant(fbkey.depth);
|
|
}
|
|
VkFramebuffer vkfb = mFramebufferCache.getFramebuffer(fbkey);
|
|
|
|
// Assign a label to the framebuffer for debugging purposes.
|
|
if (UTILS_UNLIKELY(mContext.debugUtilsSupported) && !mContext.currentDebugMarker.empty()) {
|
|
const VkDebugUtilsObjectNameInfoEXT info = {
|
|
VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
|
nullptr,
|
|
VK_OBJECT_TYPE_FRAMEBUFFER,
|
|
reinterpret_cast<uint64_t>(vkfb),
|
|
mContext.currentDebugMarker.c_str(),
|
|
};
|
|
vkSetDebugUtilsObjectNameEXT(mContext.device, &info);
|
|
}
|
|
|
|
// The current command buffer now owns a reference to the render target and its attachments.
|
|
mDisposer.acquire(rt);
|
|
mDisposer.acquire(depth.texture);
|
|
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
|
mDisposer.acquire(rt->getColor(sc, i).texture);
|
|
}
|
|
|
|
// 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(sc, &renderPassInfo.renderArea);
|
|
|
|
VkClearValue clearValues[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + 1] = {};
|
|
|
|
// 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 = mContext.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
|
|
};
|
|
|
|
mCurrentRenderTarget->transformClientRectToPlatform(sc, &viewport);
|
|
vkCmdSetViewport(cmdbuffer, 0, 1, &viewport);
|
|
|
|
mContext.currentRenderPass = {
|
|
.renderPass = renderPassInfo.renderPass,
|
|
.subpassMask = params.subpassMask,
|
|
.currentSubpass = 0,
|
|
.depthFeedback = depthFeedback
|
|
};
|
|
}
|
|
|
|
void VulkanDriver::endRenderPass(int) {
|
|
VkCommandBuffer cmdbuffer = mContext.commands->get().cmdbuffer;
|
|
vkCmdEndRenderPass(cmdbuffer);
|
|
|
|
assert_invariant(mCurrentRenderTarget);
|
|
|
|
VulkanTexture* depthFeedback = mContext.currentRenderPass.depthFeedback;
|
|
if (depthFeedback) {
|
|
const VulkanLayoutTransition transition = {
|
|
.image = depthFeedback->getVkImage(),
|
|
.oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL,
|
|
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
|
.subresources = {
|
|
.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT,
|
|
.levelCount = depthFeedback->levels,
|
|
.layerCount = depthFeedback->depth,
|
|
},
|
|
.srcStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
|
.dstStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT
|
|
};
|
|
transitionImageLayout(cmdbuffer, transition);
|
|
}
|
|
|
|
// 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. This is a memory barrier rather
|
|
// than an image barrier. If we were to use image barriers here, we would potentially need to
|
|
// issue several of them when considering MRT. This would be very complex to set up and would
|
|
// require more state tracking, so we've chosen to use a memory barrier for simplicity and
|
|
// correctness.
|
|
|
|
// NOTE: ideally dstStageMask would merely be VERTEX_SHADER_BIT | FRAGMENT_SHADER_BIT, but this
|
|
// seems to be insufficient on Mali devices. To work around this we are adding a more aggressive
|
|
// TOP_OF_PIPE barrier.
|
|
|
|
if (!mCurrentRenderTarget->isSwapChain()) {
|
|
VkMemoryBarrier barrier {
|
|
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
|
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
|
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
|
};
|
|
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
if (mCurrentRenderTarget->hasDepth()) {
|
|
barrier.srcAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
|
srcStageMask |= VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
|
}
|
|
vkCmdPipelineBarrier(cmdbuffer, srcStageMask,
|
|
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT | // <== For Mali
|
|
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
|
0, 1, &barrier, 0, nullptr, 0, nullptr);
|
|
}
|
|
|
|
mCurrentRenderTarget = VK_NULL_HANDLE;
|
|
if (mContext.currentRenderPass.currentSubpass > 0) {
|
|
for (uint32_t i = 0; i < VulkanPipelineCache::TARGET_BINDING_COUNT; i++) {
|
|
mPipelineCache.bindInputAttachment(i, {});
|
|
}
|
|
mContext.currentRenderPass.currentSubpass = 0;
|
|
}
|
|
mContext.currentRenderPass.renderPass = VK_NULL_HANDLE;
|
|
}
|
|
|
|
void VulkanDriver::nextSubpass(int) {
|
|
ASSERT_PRECONDITION(mContext.currentRenderPass.currentSubpass == 0,
|
|
"Only two subpasses are currently supported.");
|
|
|
|
VulkanSwapChain* const swapChain = mContext.currentSurface;
|
|
|
|
assert_invariant(swapChain);
|
|
assert_invariant(mCurrentRenderTarget);
|
|
assert_invariant(mContext.currentRenderPass.subpassMask);
|
|
|
|
vkCmdNextSubpass(mContext.commands->get().cmdbuffer, VK_SUBPASS_CONTENTS_INLINE);
|
|
|
|
mPipelineCache.bindRenderPass(mContext.currentRenderPass.renderPass,
|
|
++mContext.currentRenderPass.currentSubpass);
|
|
|
|
for (uint32_t i = 0; i < VulkanPipelineCache::TARGET_BINDING_COUNT; i++) {
|
|
if ((1 << i) & mContext.currentRenderPass.subpassMask) {
|
|
VulkanAttachment subpassInput = mCurrentRenderTarget->getColor(swapChain, i);
|
|
VkDescriptorImageInfo info = {
|
|
.imageView = subpassInput.view,
|
|
.imageLayout = subpassInput.layout,
|
|
};
|
|
mPipelineCache.bindInputAttachment(i, info);
|
|
}
|
|
}
|
|
}
|
|
|
|
void VulkanDriver::setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph,
|
|
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh) {
|
|
auto primitive = handle_cast<VulkanRenderPrimitive*>(rph);
|
|
primitive->setBuffers(handle_cast<VulkanVertexBuffer*>(vbh),
|
|
handle_cast<VulkanIndexBuffer*>(ibh));
|
|
}
|
|
|
|
void VulkanDriver::setRenderPrimitiveRange(Handle<HwRenderPrimitive> rph,
|
|
PrimitiveType pt, uint32_t offset,
|
|
uint32_t minIndex, uint32_t maxIndex, uint32_t count) {
|
|
auto& primitive = *handle_cast<VulkanRenderPrimitive*>(rph);
|
|
primitive.setPrimitiveType(pt);
|
|
primitive.offset = offset * primitive.indexBuffer->elementSize;
|
|
primitive.count = count;
|
|
primitive.minIndex = minIndex;
|
|
primitive.maxIndex = maxIndex > minIndex ? maxIndex : primitive.maxVertexCount - 1;
|
|
}
|
|
|
|
void VulkanDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> readSch) {
|
|
ASSERT_PRECONDITION_NON_FATAL(drawSch == readSch,
|
|
"Vulkan driver does not support distinct draw/read swap chains.");
|
|
VulkanSwapChain& surf = *handle_cast<VulkanSwapChain*>(drawSch);
|
|
mContext.currentSurface = &surf;
|
|
|
|
// Leave early if the swap chain image has already been acquired but not yet presented.
|
|
if (surf.acquired) {
|
|
return;
|
|
}
|
|
|
|
// Query the surface caps to see if it has been resized. This handles not just resized windows,
|
|
// but also screen rotation on Android and dragging between low DPI and high DPI monitors.
|
|
if (surf.hasResized()) {
|
|
refreshSwapChain();
|
|
}
|
|
|
|
// Call vkAcquireNextImageKHR and insert its signal semaphore into the command manager's
|
|
// dependency chain.
|
|
surf.acquire();
|
|
}
|
|
|
|
void VulkanDriver::commit(Handle<HwSwapChain> sch) {
|
|
VulkanSwapChain& surface = *handle_cast<VulkanSwapChain*>(sch);
|
|
|
|
// Before swapping, transition the current swap chain image to the PRESENT layout. This cannot
|
|
// be done as part of the render pass because it does not know if it is last pass in the frame.
|
|
surface.makePresentable();
|
|
|
|
if (mContext.commands->flush()) {
|
|
collectGarbage();
|
|
}
|
|
|
|
surface.firstRenderPass = true;
|
|
|
|
if (surface.headlessQueue) {
|
|
return;
|
|
}
|
|
|
|
surface.acquired = false;
|
|
|
|
// Present the backbuffer after the most recent command buffer submission has finished.
|
|
VkSemaphore renderingFinished = mContext.commands->acquireFinishedSignal();
|
|
VkPresentInfoKHR presentInfo {
|
|
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
|
.waitSemaphoreCount = 1,
|
|
.pWaitSemaphores = &renderingFinished,
|
|
.swapchainCount = 1,
|
|
.pSwapchains = &surface.swapchain,
|
|
.pImageIndices = &surface.currentSwapIndex,
|
|
};
|
|
VkResult result = vkQueuePresentKHR(surface.presentQueue, &presentInfo);
|
|
|
|
// On Android Q and above, a suboptimal surface is always reported after screen rotation:
|
|
// https://android-developers.googleblog.com/2020/02/handling-device-orientation-efficiently.html
|
|
if (result == VK_SUBOPTIMAL_KHR && !surface.suboptimal) {
|
|
utils::slog.w << "Vulkan Driver: Suboptimal swap chain." << utils::io::endl;
|
|
surface.suboptimal = true;
|
|
}
|
|
|
|
// The surface can be "out of date" when it has been resized, which is not an error.
|
|
assert_invariant(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR ||
|
|
result == VK_ERROR_OUT_OF_DATE_KHR);
|
|
}
|
|
|
|
void VulkanDriver::bindUniformBuffer(uint32_t index, Handle<HwBufferObject> boh) {
|
|
auto* bo = handle_cast<VulkanBufferObject*>(boh);
|
|
// The driver API does not currently expose offset / range, but it will do so in the future.
|
|
const VkDeviceSize offset = 0;
|
|
const VkDeviceSize size = VK_WHOLE_SIZE;
|
|
mPipelineCache.bindUniformBuffer((uint32_t) index, bo->buffer.getGpuBuffer(), offset, size);
|
|
}
|
|
|
|
void VulkanDriver::bindUniformBufferRange(uint32_t index, Handle<HwBufferObject> boh,
|
|
uint32_t offset, uint32_t size) {
|
|
auto* bo = handle_cast<VulkanBufferObject*>(boh);
|
|
mPipelineCache.bindUniformBuffer((uint32_t)index, bo->buffer.getGpuBuffer(), offset, size);
|
|
}
|
|
|
|
void VulkanDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
|
|
auto* hwsb = handle_cast<VulkanSamplerGroup*>(sbh);
|
|
mSamplerBindings[index] = hwsb;
|
|
}
|
|
|
|
void VulkanDriver::insertEventMarker(char const* string, uint32_t len) {
|
|
constexpr float MARKER_COLOR[] = { 0.0f, 1.0f, 0.0f, 1.0f };
|
|
const VkCommandBuffer cmdbuffer = mContext.commands->get().cmdbuffer;
|
|
if (mContext.debugUtilsSupported) {
|
|
VkDebugUtilsLabelEXT labelInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
|
|
.pLabelName = string,
|
|
.color = {1, 1, 0, 1},
|
|
};
|
|
vkCmdInsertDebugUtilsLabelEXT(cmdbuffer, &labelInfo);
|
|
} else if (mContext.debugMarkersSupported) {
|
|
VkDebugMarkerMarkerInfoEXT markerInfo = {};
|
|
markerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
|
|
memcpy(markerInfo.color, &MARKER_COLOR[0], sizeof(MARKER_COLOR));
|
|
markerInfo.pMarkerName = string;
|
|
vkCmdDebugMarkerInsertEXT(cmdbuffer, &markerInfo);
|
|
}
|
|
}
|
|
|
|
void VulkanDriver::pushGroupMarker(char const* string, uint32_t len) {
|
|
// TODO: Add group marker color to the Driver API
|
|
constexpr float MARKER_COLOR[] = { 0.0f, 1.0f, 0.0f, 1.0f };
|
|
const VkCommandBuffer cmdbuffer = mContext.commands->get().cmdbuffer;
|
|
if (mContext.debugUtilsSupported) {
|
|
VkDebugUtilsLabelEXT labelInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
|
|
.pLabelName = string,
|
|
.color = {0, 1, 0, 1},
|
|
};
|
|
vkCmdBeginDebugUtilsLabelEXT(cmdbuffer, &labelInfo);
|
|
mContext.currentDebugMarker = string;
|
|
} else if (mContext.debugMarkersSupported) {
|
|
VkDebugMarkerMarkerInfoEXT markerInfo = {};
|
|
markerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
|
|
memcpy(markerInfo.color, &MARKER_COLOR[0], sizeof(MARKER_COLOR));
|
|
markerInfo.pMarkerName = string;
|
|
vkCmdDebugMarkerBeginEXT(cmdbuffer, &markerInfo);
|
|
}
|
|
}
|
|
|
|
void VulkanDriver::popGroupMarker(int) {
|
|
const VkCommandBuffer cmdbuffer = mContext.commands->get().cmdbuffer;
|
|
if (mContext.debugUtilsSupported) {
|
|
vkCmdEndDebugUtilsLabelEXT(cmdbuffer);
|
|
mContext.currentDebugMarker.clear();
|
|
} else if (mContext.debugMarkersSupported) {
|
|
vkCmdDebugMarkerEndEXT(cmdbuffer);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
const VkDevice device = mContext.device;
|
|
VulkanRenderTarget* srcTarget = handle_cast<VulkanRenderTarget*>(src);
|
|
VulkanTexture* srcTexture = srcTarget->getColor(mContext.currentSurface, 0).texture;
|
|
const VkFormat srcFormat = srcTexture ? srcTexture->getVkFormat() :
|
|
mContext.currentSurface->surfaceFormat.format;
|
|
const bool swizzle = srcFormat == VK_FORMAT_B8G8R8A8_UNORM;
|
|
|
|
// Create a host visible, linearly tiled image as a staging area.
|
|
|
|
VkImageCreateInfo imageInfo {
|
|
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
|
.imageType = VK_IMAGE_TYPE_2D,
|
|
.format = srcFormat,
|
|
.extent = { width, height, 1 },
|
|
.mipLevels = 1,
|
|
.arrayLayers = 1,
|
|
.samples = VK_SAMPLE_COUNT_1_BIT,
|
|
.tiling = VK_IMAGE_TILING_LINEAR,
|
|
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT,
|
|
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
|
};
|
|
|
|
VkImage stagingImage;
|
|
vkCreateImage(device, &imageInfo, VKALLOC, &stagingImage);
|
|
|
|
VkMemoryRequirements memReqs;
|
|
VkDeviceMemory stagingMemory;
|
|
vkGetImageMemoryRequirements(device, stagingImage, &memReqs);
|
|
VkMemoryAllocateInfo allocInfo = {
|
|
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
|
.allocationSize = memReqs.size,
|
|
.memoryTypeIndex = mContext.selectMemoryType(memReqs.memoryTypeBits,
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
|
|
};
|
|
|
|
vkAllocateMemory(device, &allocInfo, nullptr, &stagingMemory);
|
|
vkBindImageMemory(device, stagingImage, stagingMemory, 0);
|
|
|
|
// TODO: don't flush/wait here, this should be asynchronous
|
|
|
|
mContext.commands->flush();
|
|
mContext.commands->wait();
|
|
|
|
// Transition the staging image layout.
|
|
|
|
const VkCommandBuffer cmdbuffer = mContext.commands->get().cmdbuffer;
|
|
|
|
transitionImageLayout(cmdbuffer, {
|
|
.image = stagingImage,
|
|
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
|
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
|
.subresources = {
|
|
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
|
.baseMipLevel = 0,
|
|
.levelCount = 1,
|
|
.baseArrayLayer = 0,
|
|
.layerCount = 1,
|
|
},
|
|
.srcStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
|
.srcAccessMask = 0,
|
|
.dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
});
|
|
|
|
const VulkanAttachment srcAttachment = srcTarget->getColor(mContext.currentSurface, 0);
|
|
|
|
VkImageCopy imageCopyRegion = {
|
|
.srcSubresource = {
|
|
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
|
.mipLevel = srcAttachment.level,
|
|
.baseArrayLayer = srcAttachment.layer,
|
|
.layerCount = 1,
|
|
},
|
|
.srcOffset = {
|
|
.x = (int32_t) x,
|
|
.y = (int32_t) (srcTarget->getExtent(mContext.currentSurface).height - (height + y)),
|
|
},
|
|
.dstSubresource = {
|
|
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
|
.layerCount = 1,
|
|
},
|
|
.extent = {
|
|
.width = width,
|
|
.height = height,
|
|
.depth = 1,
|
|
},
|
|
};
|
|
|
|
// Transition the source image layout (which might be the swap chain)
|
|
|
|
// Since ReadPixels is always issued after at least one render pass, we know that the color
|
|
// attachment layout is COLOR_ATTACHMENT_OPTIMAL.
|
|
|
|
const VkImageSubresourceRange srcRange = {
|
|
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
|
.baseMipLevel = srcAttachment.level,
|
|
.levelCount = 1,
|
|
.baseArrayLayer = srcAttachment.layer,
|
|
.layerCount = 1,
|
|
};
|
|
|
|
VkImage srcImage = srcAttachment.image;
|
|
transitionImageLayout(cmdbuffer, {
|
|
.image = srcImage,
|
|
.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
.subresources = srcRange,
|
|
.srcStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
|
.srcAccessMask = 0,
|
|
.dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
|
|
});
|
|
|
|
// Perform the into the staging area. At this point we know that the src layout is
|
|
// TRANSFER_SRC_OPTIMAL and the staging area is GENERAL.
|
|
|
|
vkCmdCopyImage(cmdbuffer, srcTarget->getColor(mContext.currentSurface, 0).image,
|
|
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, stagingImage, VK_IMAGE_LAYOUT_GENERAL,
|
|
1, &imageCopyRegion);
|
|
|
|
// Restore the source image layout back to VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL.
|
|
|
|
if (UTILS_LIKELY(srcTexture)) {
|
|
srcTexture->transitionLayout(cmdbuffer, srcRange, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
|
} else {
|
|
transitionImageLayout(cmdbuffer, {
|
|
.image = srcImage,
|
|
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
.subresources = srcRange,
|
|
.srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
.dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
|
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
|
});
|
|
}
|
|
|
|
// TODO: don't flush/wait here -- we should do this asynchronously
|
|
|
|
// Flush and wait.
|
|
mContext.commands->flush();
|
|
mContext.commands->wait();
|
|
|
|
VkImageSubresource subResource { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT };
|
|
VkSubresourceLayout subResourceLayout;
|
|
vkGetImageSubresourceLayout(device, stagingImage, &subResource, &subResourceLayout);
|
|
|
|
// Map image memory so we can start copying from it.
|
|
|
|
const uint8_t* srcPixels;
|
|
vkMapMemory(device, stagingMemory, 0, VK_WHOLE_SIZE, 0, (void**) &srcPixels);
|
|
srcPixels += subResourceLayout.offset;
|
|
|
|
// TODO: investigate why this Y-flip exists. This conditional seems to work with both
|
|
// test_ReadPixels.cpp (readpixels from a normal render target with texture attachment) and
|
|
// viewer_basic_test.cc (readpixels from an offscreen swap chain)
|
|
const bool flipY = srcTexture ? true : false;
|
|
|
|
if (!DataReshaper::reshapeImage(&pbd, getComponentType(srcFormat), srcPixels,
|
|
subResourceLayout.rowPitch, width, height, swizzle, flipY)) {
|
|
utils::slog.e << "Unsupported PixelDataFormat or PixelDataType" << utils::io::endl;
|
|
}
|
|
|
|
vkUnmapMemory(device, stagingMemory);
|
|
|
|
mDisposer.createDisposable((void*)stagingImage, [=] () {
|
|
vkDestroyImage(device, stagingImage, nullptr);
|
|
vkFreeMemory(device, stagingMemory, nullptr);
|
|
});
|
|
|
|
scheduleDestroy(std::move(pbd));
|
|
}
|
|
|
|
void VulkanDriver::readStreamPixels(Handle<HwStream> sh, uint32_t x, uint32_t y, uint32_t width,
|
|
uint32_t height, PixelBufferDescriptor&& p) {
|
|
scheduleDestroy(std::move(p));
|
|
}
|
|
|
|
void VulkanDriver::blit(TargetBufferFlags buffers, Handle<HwRenderTarget> dst, Viewport dstRect,
|
|
Handle<HwRenderTarget> src, Viewport srcRect, SamplerMagFilter filter) {
|
|
assert_invariant(mContext.currentRenderPass.renderPass == VK_NULL_HANDLE);
|
|
|
|
// blit operation only support COLOR0 color buffer
|
|
assert_invariant(
|
|
!(buffers & (TargetBufferFlags::COLOR_ALL & ~TargetBufferFlags::COLOR0)));
|
|
|
|
if (UTILS_UNLIKELY(mContext.currentRenderPass.renderPass)) {
|
|
utils::slog.e << "Blits cannot be invoked inside a render pass." << utils::io::endl;
|
|
return;
|
|
}
|
|
|
|
VulkanRenderTarget* dstTarget = handle_cast<VulkanRenderTarget*>(dst);
|
|
VulkanRenderTarget* srcTarget = handle_cast<VulkanRenderTarget*>(src);
|
|
|
|
VkFilter vkfilter = filter == SamplerMagFilter::NEAREST ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;
|
|
|
|
const VkExtent2D srcExtent = srcTarget->getExtent(mContext.currentSurface);
|
|
const int32_t srcLeft = std::min(srcRect.left, (int32_t) srcExtent.width);
|
|
const int32_t srcBottom = std::min(srcRect.bottom, (int32_t) srcExtent.height);
|
|
const int32_t srcRight = std::min(srcRect.left + srcRect.width, srcExtent.width);
|
|
const int32_t srcTop = std::min(srcRect.bottom + srcRect.height, srcExtent.height);
|
|
const VkOffset3D srcOffsets[2] = { { srcLeft, srcBottom, 0 }, { srcRight, srcTop, 1 }};
|
|
|
|
const VkExtent2D dstExtent = dstTarget->getExtent(mContext.currentSurface);
|
|
const int32_t dstLeft = std::min(dstRect.left, (int32_t) dstExtent.width);
|
|
const int32_t dstBottom = std::min(dstRect.bottom, (int32_t) dstExtent.height);
|
|
const int32_t dstRight = std::min(dstRect.left + dstRect.width, dstExtent.width);
|
|
const int32_t dstTop = std::min(dstRect.bottom + dstRect.height, dstExtent.height);
|
|
const VkOffset3D dstOffsets[2] = { { dstLeft, dstBottom, 0 }, { dstRight, dstTop, 1 }};
|
|
|
|
if (any(buffers & TargetBufferFlags::DEPTH) && srcTarget->hasDepth() && dstTarget->hasDepth()) {
|
|
mBlitter.blitDepth({dstTarget, dstOffsets, srcTarget, srcOffsets});
|
|
}
|
|
|
|
if (any(buffers & TargetBufferFlags::COLOR0)) {
|
|
mBlitter.blitColor({ dstTarget, dstOffsets, srcTarget, srcOffsets, vkfilter, int(0) });
|
|
}
|
|
}
|
|
|
|
void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> rph) {
|
|
VulkanCommandBuffer const* commands = &mContext.commands->get();
|
|
VkCommandBuffer cmdbuffer = commands->cmdbuffer;
|
|
const VulkanRenderPrimitive& prim = *handle_cast<VulkanRenderPrimitive*>(rph);
|
|
|
|
Handle<HwProgram> programHandle = pipelineState.program;
|
|
RasterState rasterState = pipelineState.rasterState;
|
|
PolygonOffset depthOffset = pipelineState.polygonOffset;
|
|
const Viewport& viewportScissor = pipelineState.scissor;
|
|
|
|
auto* program = handle_cast<VulkanProgram*>(programHandle);
|
|
mDisposer.acquire(program);
|
|
mDisposer.acquire(prim.indexBuffer);
|
|
mDisposer.acquire(prim.vertexBuffer);
|
|
|
|
// If this is a debug build, validate the current shader.
|
|
#if !defined(NDEBUG)
|
|
if (program->bundle.vertex == VK_NULL_HANDLE || program->bundle.fragment == VK_NULL_HANDLE) {
|
|
utils::slog.e << "Binding missing shader: " << program->name.c_str() << utils::io::endl;
|
|
}
|
|
#endif
|
|
|
|
// Update the VK raster state.
|
|
|
|
const VulkanRenderTarget* rt = mCurrentRenderTarget;
|
|
|
|
mContext.rasterState.depthStencil = {
|
|
.depthTestEnable = VK_TRUE,
|
|
.depthWriteEnable = (VkBool32) rasterState.depthWrite,
|
|
.depthCompareOp = getCompareOp(rasterState.depthFunc),
|
|
.depthBoundsTestEnable = VK_FALSE,
|
|
.stencilTestEnable = VK_FALSE,
|
|
};
|
|
|
|
mContext.rasterState.multisampling = {
|
|
.rasterizationSamples = (VkSampleCountFlagBits) rt->getSamples(),
|
|
.alphaToCoverageEnable = rasterState.alphaToCoverage,
|
|
};
|
|
|
|
mContext.rasterState.blending = {
|
|
.blendEnable = (VkBool32) rasterState.hasBlending(),
|
|
.srcColorBlendFactor = getBlendFactor(rasterState.blendFunctionSrcRGB),
|
|
.dstColorBlendFactor = getBlendFactor(rasterState.blendFunctionDstRGB),
|
|
.colorBlendOp = (VkBlendOp) rasterState.blendEquationRGB,
|
|
.srcAlphaBlendFactor = getBlendFactor(rasterState.blendFunctionSrcAlpha),
|
|
.dstAlphaBlendFactor = getBlendFactor(rasterState.blendFunctionDstAlpha),
|
|
.alphaBlendOp = (VkBlendOp) rasterState.blendEquationAlpha,
|
|
.colorWriteMask = (VkColorComponentFlags) (rasterState.colorWrite ? 0xf : 0x0),
|
|
};
|
|
|
|
auto& vkraster = mContext.rasterState.rasterization;
|
|
vkraster.cullMode = getCullMode(rasterState.culling);
|
|
vkraster.frontFace = getFrontFace(rasterState.inverseFrontFaces);
|
|
vkraster.depthBiasEnable = (depthOffset.constant || depthOffset.slope) ? VK_TRUE : VK_FALSE;
|
|
vkraster.depthBiasConstantFactor = depthOffset.constant;
|
|
vkraster.depthBiasSlopeFactor = depthOffset.slope;
|
|
|
|
mContext.rasterState.colorTargetCount = rt->getColorTargetCount(mContext.currentRenderPass);
|
|
|
|
// Declare fixed-size arrays that get passed to the pipeCache and to vkCmdBindVertexBuffers.
|
|
VulkanPipelineCache::VertexArray varray = {};
|
|
VkBuffer buffers[backend::MAX_VERTEX_ATTRIBUTE_COUNT] = {};
|
|
VkDeviceSize offsets[backend::MAX_VERTEX_ATTRIBUTE_COUNT] = {};
|
|
|
|
// For each attribute, append to each of the above lists.
|
|
const uint32_t bufferCount = prim.vertexBuffer->attributes.size();
|
|
for (uint32_t attribIndex = 0; attribIndex < bufferCount; attribIndex++) {
|
|
Attribute attrib = prim.vertexBuffer->attributes[attribIndex];
|
|
|
|
const bool isInteger = attrib.flags & Attribute::FLAG_INTEGER_TARGET;
|
|
const bool isNormalized = attrib.flags & Attribute::FLAG_NORMALIZED;
|
|
|
|
VkFormat vkformat = getVkFormat(attrib.type, isNormalized, isInteger);
|
|
|
|
// HACK: Re-use the positions buffer as a dummy buffer for disabled attributes. Filament's
|
|
// vertex shaders declare all attributes as either vec4 or uvec4 (the latter for bone
|
|
// indices), and positions are always at least 32 bits per element. Therefore we can assign
|
|
// a dummy type of either R8G8B8A8_UINT or R8G8B8A8_SNORM, depending on whether the shader
|
|
// expects to receive floats or ints.
|
|
if (attrib.buffer == Attribute::BUFFER_UNUSED) {
|
|
vkformat = isInteger ? VK_FORMAT_R8G8B8A8_UINT : VK_FORMAT_R8G8B8A8_SNORM;
|
|
attrib = prim.vertexBuffer->attributes[0];
|
|
}
|
|
|
|
const VulkanBuffer* buffer = prim.vertexBuffer->buffers[attrib.buffer];
|
|
|
|
// If the vertex buffer is missing a constituent buffer object, skip the draw call.
|
|
// There is no need to emit an error message because this is not explicitly forbidden.
|
|
if (buffer == nullptr) {
|
|
return;
|
|
}
|
|
|
|
buffers[attribIndex] = buffer->getGpuBuffer();
|
|
offsets[attribIndex] = attrib.offset;
|
|
varray.attributes[attribIndex] = {
|
|
.location = attribIndex, // matches the GLSL layout specifier
|
|
.binding = attribIndex, // matches the position within vkCmdBindVertexBuffers
|
|
.format = vkformat,
|
|
};
|
|
varray.buffers[attribIndex] = {
|
|
.binding = attribIndex,
|
|
.stride = attrib.stride,
|
|
};
|
|
}
|
|
|
|
// Push state changes to the VulkanPipelineCache instance. This is fast and does not make VK calls.
|
|
mPipelineCache.bindProgramBundle(program->bundle);
|
|
mPipelineCache.bindRasterState(mContext.rasterState);
|
|
mPipelineCache.bindPrimitiveTopology(prim.primitiveTopology);
|
|
mPipelineCache.bindVertexArray(varray);
|
|
|
|
// Query the program for the mapping from (SamplerGroupBinding,Offset) to (SamplerBinding),
|
|
// where "SamplerBinding" is the integer in the GLSL, and SamplerGroupBinding is the abstract
|
|
// Filament concept used to form groups of samplers.
|
|
|
|
VkDescriptorImageInfo samplers[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
|
|
|
|
for (uint8_t samplerGroupIdx = 0; samplerGroupIdx < Program::BINDING_COUNT; samplerGroupIdx++) {
|
|
const auto& samplerGroup = program->samplerGroupInfo[samplerGroupIdx];
|
|
if (samplerGroup.empty()) {
|
|
continue;
|
|
}
|
|
VulkanSamplerGroup* vksb = mSamplerBindings[samplerGroupIdx];
|
|
if (!vksb) {
|
|
continue;
|
|
}
|
|
SamplerGroup* sb = vksb->sb.get();
|
|
assert_invariant(sb->getSize() == samplerGroup.size());
|
|
size_t samplerIdx = 0;
|
|
for (const auto& sampler : samplerGroup) {
|
|
size_t bindingPoint = sampler.binding;
|
|
const SamplerGroup::Sampler* boundSampler = sb->getSamplers() + samplerIdx;
|
|
samplerIdx++;
|
|
|
|
// Note that we always use a 2D texture for the fallback texture, which might not be
|
|
// appropriate. The fallback improves robustness but does not guarantee 100% success.
|
|
// It can be argued that clients are being malfeasant here anyway, since Vulkan does
|
|
// not allow sampling from a non-bound texture.
|
|
const VulkanTexture* texture;
|
|
if (UTILS_UNLIKELY(!boundSampler->t)) {
|
|
if (!sampler.strict) {
|
|
continue;
|
|
}
|
|
utils::slog.w << "No texture bound to '" << sampler.name.c_str() << "'";
|
|
#ifndef NDEBUG
|
|
utils::slog.w << " in material '" << program->name.c_str() << "'";
|
|
#endif
|
|
utils::slog.w << " at binding point " << +bindingPoint << utils::io::endl;
|
|
texture = mContext.emptyTexture;
|
|
} else {
|
|
texture = handle_cast<const VulkanTexture*>(boundSampler->t);
|
|
mDisposer.acquire(texture);
|
|
}
|
|
|
|
const SamplerParams& samplerParams = boundSampler->s;
|
|
VkSampler vksampler = mSamplerCache.getSampler(samplerParams);
|
|
|
|
samplers[bindingPoint] = {
|
|
.sampler = vksampler,
|
|
.imageView = texture->getPrimaryImageView(),
|
|
.imageLayout = getDefaultImageLayout(texture->usage)
|
|
};
|
|
|
|
if (mContext.currentRenderPass.depthFeedback == texture) {
|
|
samplers[bindingPoint].imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
mPipelineCache.bindSamplers(samplers);
|
|
|
|
// Bind new descriptor sets if they need to change.
|
|
// If descriptor set allocation failed, skip the draw call and bail. No need to emit an error
|
|
// message since the validation layers already do so.
|
|
if (!mPipelineCache.bindDescriptors(cmdbuffer)) {
|
|
return;
|
|
}
|
|
|
|
// Set scissoring.
|
|
// Compute the intersection of the requested scissor rectangle with the current viewport.
|
|
const int32_t x = std::max(viewportScissor.left, (int32_t)mContext.viewport.x);
|
|
const int32_t y = std::max(viewportScissor.bottom, (int32_t)mContext.viewport.y);
|
|
const int32_t right = std::min(viewportScissor.left + (int32_t)viewportScissor.width,
|
|
(int32_t)(mContext.viewport.x + mContext.viewport.width));
|
|
const int32_t top = std::min(viewportScissor.bottom + (int32_t)viewportScissor.height,
|
|
(int32_t)(mContext.viewport.y + mContext.viewport.height));
|
|
VkRect2D scissor{
|
|
.offset = { std::max(0, x), std::max(0, y) },
|
|
.extent = { (uint32_t)right - x, (uint32_t)top - y }
|
|
};
|
|
|
|
rt->transformClientRectToPlatform(mContext.currentSurface, &scissor);
|
|
mPipelineCache.bindScissor(cmdbuffer, scissor);
|
|
|
|
// Bind a new pipeline if the pipeline state changed.
|
|
mPipelineCache.bindPipeline(cmdbuffer);
|
|
|
|
// 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.getGpuBuffer(), 0,
|
|
prim.indexBuffer->indexType);
|
|
|
|
// Finally, make the actual draw call. TODO: support subranges
|
|
const uint32_t indexCount = prim.count;
|
|
const uint32_t instanceCount = 1;
|
|
const uint32_t firstIndex = prim.offset / prim.indexBuffer->elementSize;
|
|
const int32_t vertexOffset = 0;
|
|
const uint32_t firstInstId = 1;
|
|
vkCmdDrawIndexed(cmdbuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstId);
|
|
}
|
|
|
|
void VulkanDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
|
|
VulkanCommandBuffer const* commands = &mContext.commands->get();
|
|
VulkanTimerQuery* vtq = handle_cast<VulkanTimerQuery*>(tqh);
|
|
const uint32_t index = vtq->startingQueryIndex;
|
|
const VkPipelineStageFlagBits stage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
|
|
|
vkCmdResetQueryPool(commands->cmdbuffer, mContext.timestamps.pool, index, 2);
|
|
vkCmdWriteTimestamp(commands->cmdbuffer, stage, mContext.timestamps.pool, index);
|
|
vtq->cmdbuffer.store(commands);
|
|
}
|
|
|
|
void VulkanDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
|
|
VulkanCommandBuffer const* commands = &mContext.commands->get();
|
|
VulkanTimerQuery* vtq = handle_cast<VulkanTimerQuery*>(tqh);
|
|
const uint32_t index = vtq->stoppingQueryIndex;
|
|
const VkPipelineStageFlagBits stage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
|
vkCmdWriteTimestamp(commands->cmdbuffer, stage, mContext.timestamps.pool, index);
|
|
}
|
|
|
|
void VulkanDriver::refreshSwapChain() {
|
|
VulkanSwapChain& surface = *mContext.currentSurface;
|
|
|
|
assert_invariant(!surface.headlessQueue && "Resizing headless swap chains is not supported.");
|
|
surface.destroy();
|
|
surface.create();
|
|
|
|
mFramebufferCache.reset();
|
|
}
|
|
|
|
void VulkanDriver::debugCommandBegin(CommandStream* cmds, bool synchronous, const char* methodName) noexcept {
|
|
DriverBase::debugCommandBegin(cmds, synchronous, methodName);
|
|
#ifndef NDEBUG
|
|
static const std::set<utils::StaticString> OUTSIDE_COMMANDS = {
|
|
"loadUniformBuffer",
|
|
"updateBufferObject",
|
|
"updateIndexBuffer",
|
|
"update2DImage",
|
|
"updateCubeImage",
|
|
};
|
|
static const utils::StaticString BEGIN_COMMAND = "beginRenderPass";
|
|
static const utils::StaticString END_COMMAND = "endRenderPass";
|
|
static bool inRenderPass = false; // for debug only
|
|
const utils::StaticString command = utils::StaticString::make(methodName, strlen(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()) {
|
|
utils::slog.e << command.c_str() << " issued inside a render pass." << utils::io::endl;
|
|
utils::debug_trap();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// explicit instantiation of the Dispatcher
|
|
template class ConcreteDispatcher<VulkanDriver>;
|
|
|
|
} // namespace backend
|
|
} // namespace filament
|
|
|
|
#pragma clang diagnostic pop
|