Compare commits

...

1 Commits

Author SHA1 Message Date
Powei Feng
8c7a82f7b3 vk: make protected content work 2024-10-31 11:17:19 -07:00
20 changed files with 935 additions and 513 deletions

View File

@@ -88,6 +88,7 @@ public:
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
VkFormat depthFormat = VK_FORMAT_UNDEFINED;
VkExtent2D extent = {0, 0};
bool protection = false;
};
struct ImageSyncData {
@@ -199,6 +200,13 @@ public:
*/
virtual bool hasResized(SwapChainPtr handle);
/**
* Check if the surface is protected.
* @param handle The handle returned by createSwapChain()
* @return Whether the swapchain is protected
*/
virtual bool isProtected(SwapChainPtr handle);
/**
* Carry out a recreation of the swapchain.
* @param handle The handle returned by createSwapChain()
@@ -267,6 +275,23 @@ public:
*/
VkQueue getGraphicsQueue() const noexcept;
/**
* @return The family index of the protected graphics queue selected for the
* Vulkan backend.
*/
uint32_t getProtectedGraphicsQueueFamilyIndex() const noexcept;
/**
* @return The index of the protected graphics queue (if there are multiple
* graphics queues) selected for the Vulkan backend.
*/
uint32_t getProtectedGraphicsQueueIndex() const noexcept;
/**
* @return The protected queue that was selected for the Vulkan backend.
*/
VkQueue getProtectedGraphicsQueue() const noexcept;
private:
static ExtensionSet getSwapchainInstanceExtensions();

View File

@@ -151,7 +151,8 @@ void VulkanBlitter::resolve(VulkanAttachment dst, VulkanAttachment src) {
}
#endif
VulkanCommandBuffer& commands = mCommands->get();
VulkanCommandBuffer& commands = dst.texture->isProtected() ?
mCommands->getProtected() : mCommands->get();
commands.acquire(src.texture);
commands.acquire(dst.texture);
resolveFast(&commands, aspect, src, dst);
@@ -176,7 +177,8 @@ void VulkanBlitter::blit(VkFilter filter,
#endif
// src and dst should have the same aspect here
VkImageAspectFlags const aspect = src.texture->getImageAspect();
VulkanCommandBuffer& commands = mCommands->get();
VulkanCommandBuffer& commands = dst.texture->isProtected() ?
mCommands->getProtected() : mCommands->get();
commands.acquire(src.texture);
commands.acquire(dst.texture);
blitFast(&commands, aspect, filter, src, dst, srcRectPair, dstRectPair);

View File

@@ -33,48 +33,28 @@ using namespace utils;
namespace filament::backend {
namespace {
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
using Timestamp = VulkanGroupMarkers::Timestamp;
#endif
VulkanCmdFence::VulkanCmdFence(VkFence ifence)
: fence(ifence) {
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted". When this fence gets
// submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually finishes
// executing the command buffer, the status changes to VK_SUCCESS.
status.store(VK_INCOMPLETE);
}
VulkanCommandBuffer::VulkanCommandBuffer(VulkanResourceAllocator* allocator, VkDevice device,
VkCommandPool pool)
: mResourceManager(allocator),
mPipeline(VK_NULL_HANDLE) {
VkCommandBuffer createCommandBuffer(VkDevice device, VkCommandPool pool) {
VkCommandBuffer cmdbuffer;
// Create the low-level command buffer.
const VkCommandBufferAllocateInfo allocateInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = pool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
VkCommandBufferAllocateInfo const allocateInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = pool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
// The buffer allocated here will be implicitly reset when vkBeginCommandBuffer is called.
// We don't need to deallocate since destroying the pool will free all of the buffers.
vkAllocateCommandBuffers(device, &allocateInfo, &mBuffer);
vkAllocateCommandBuffers(device, &allocateInfo, &cmdbuffer);
return cmdbuffer;
}
CommandBufferObserver::~CommandBufferObserver() {}
static VkCommandPool createPool(VkDevice device, uint32_t queueFamilyIndex) {
VkCommandPoolCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT
| VK_COMMAND_POOL_CREATE_TRANSIENT_BIT,
.queueFamilyIndex = queueFamilyIndex,
};
VkCommandPool pool;
vkCreateCommandPool(device, &createInfo, VKALLOC, &pool);
return pool;
}
} // anonymous namespace
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
void VulkanGroupMarkers::push(std::string const& marker, Timestamp start) noexcept {
@@ -130,178 +110,147 @@ bool VulkanGroupMarkers::empty() const noexcept {
#endif // FVK_DEBUG_GROUP_MARKERS
VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
VulkanContext* context, VulkanResourceAllocator* allocator)
: mDevice(device),
VulkanCommandBuffer::VulkanCommandBuffer(VulkanContext* context, VulkanResourceAllocator* allocator,
VkDevice device, VkQueue queue, VkCommandPool pool, bool isProtected)
: mContext(context),
mMarkerCount(0),
isProtected(isProtected),
mDevice(device),
mQueue(queue),
mPool(createPool(mDevice, queueFamilyIndex)),
mContext(context),
mStorage(CAPACITY) {
mResourceManager(allocator),
mBuffer(createCommandBuffer(device, pool)),
mFenceStatus(std::make_shared<VulkanCmdFence>(VK_INCOMPLETE)) {
VkSemaphoreCreateInfo sci{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
for (auto& semaphore: mSubmissionSignals) {
vkCreateSemaphore(mDevice, &sci, nullptr, &semaphore);
}
vkCreateSemaphore(mDevice, &sci, VKALLOC, &mSubmission);
VkFenceCreateInfo fenceCreateInfo{.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
for (auto& fence: mFences) {
vkCreateFence(device, &fenceCreateInfo, VKALLOC, &fence);
}
for (size_t i = 0; i < CAPACITY; ++i) {
mStorage[i] = std::make_unique<VulkanCommandBuffer>(allocator, mDevice, mPool);
}
#if !FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
(void) mContext;
#endif
vkCreateFence(device, &fenceCreateInfo, VKALLOC, &mFence);
mIsProtected = isProtected;
}
void VulkanCommands::terminate() {
wait();
gc();
vkDestroyCommandPool(mDevice, mPool, VKALLOC);
for (VkSemaphore sema: mSubmissionSignals) {
vkDestroySemaphore(mDevice, sema, VKALLOC);
VulkanCommandBuffer::~VulkanCommandBuffer() {
vkDestroySemaphore(mDevice, mSubmission, VKALLOC);
vkDestroyFence(mDevice, mFence, VKALLOC);
}
void VulkanCommandBuffer::reset() noexcept {
mMarkerCount = 0;
mResourceManager.clear();
mWaitSemaphores.clear();
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted". When this fence
// gets, gets submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually
// finishes executing the command buffer, the status changes to VK_SUCCESS.
mFenceStatus = std::make_shared<VulkanCmdFence>(VK_INCOMPLETE);
vkResetFences(mDevice, 1, &mFence);
}
void VulkanCommandBuffer::pushMarker(char const* marker) noexcept {
if (mContext->isDebugUtilsSupported()) {
VkDebugUtilsLabelEXT labelInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
.pLabelName = marker,
.color = {0, 1, 0, 1},
};
vkCmdBeginDebugUtilsLabelEXT(mBuffer, &labelInfo);
} else if (mContext->isDebugMarkersSupported()) {
VkDebugMarkerMarkerInfoEXT markerInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT,
.pMarkerName = marker,
.color = {0.0f, 1.0f, 0.0f, 1.0f},
};
vkCmdDebugMarkerBeginEXT(mBuffer, &markerInfo);
}
for (VkFence fence: mFences) {
vkDestroyFence(mDevice, fence, VKALLOC);
mMarkerCount++;
}
void VulkanCommandBuffer::popMarker() noexcept{
assert_invariant(mMarkerCount > 0);
if (mContext->isDebugUtilsSupported()) {
vkCmdEndDebugUtilsLabelEXT(mBuffer);
} else if (mContext->isDebugMarkersSupported()) {
vkCmdDebugMarkerEndEXT(mBuffer);
}
mMarkerCount--;
}
void VulkanCommandBuffer::insertEvent(char const* marker) noexcept {
if (mContext->isDebugUtilsSupported()) {
VkDebugUtilsLabelEXT labelInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
.pLabelName = marker,
.color = {1, 1, 0, 1},
};
vkCmdInsertDebugUtilsLabelEXT(mBuffer, &labelInfo);
} else if (mContext->isDebugMarkersSupported()) {
VkDebugMarkerMarkerInfoEXT markerInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT,
.pMarkerName = marker,
.color = {0.0f, 1.0f, 0.0f, 1.0f},
};
vkCmdDebugMarkerInsertEXT(mBuffer, &markerInfo);
}
}
VulkanCommandBuffer& VulkanCommands::get() {
if (mCurrentCommandBufferIndex >= 0) {
return *mStorage[mCurrentCommandBufferIndex].get();
}
// If we ran out of available command buffers, stall until one finishes. This is very rare.
// It occurs only when Filament invokes commit() or endFrame() a large number of times without
// presenting the swap chain or waiting on a fence.
while (mAvailableBufferCount == 0) {
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
FVK_LOGI << "VulkanCommands has stalled. "
<< "If this occurs frequently, consider increasing VK_MAX_COMMAND_BUFFERS."
<< io::endl;
#endif
wait();
gc();
}
VulkanCommandBuffer* currentbuf = nullptr;
// Find an available slot.
for (size_t i = 0; i < CAPACITY; ++i) {
auto wrapper = mStorage[i].get();
if (wrapper->buffer() == VK_NULL_HANDLE) {
mCurrentCommandBufferIndex = static_cast<int8_t>(i);
currentbuf = wrapper;
break;
}
}
assert_invariant(currentbuf);
mAvailableBufferCount--;
// Note that the fence wrapper uses shared_ptr because a DriverAPI fence can also have ownership
// over it. The destruction of the low-level fence occurs either in VulkanCommands::gc(), or in
// VulkanDriver::destroyFence(), both of which are safe spots.
currentbuf->fence = std::make_shared<VulkanCmdFence>(mFences[mCurrentCommandBufferIndex]);
void VulkanCommandBuffer::begin() noexcept {
FVK_LOGE <<"[" << (mIsProtected ? "protected" : "regular") <<"]=" << buffer() << " begin"
<<utils::io::endl;
// Begin writing into the command buffer.
const VkCommandBufferBeginInfo binfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
VkCommandBufferBeginInfo const binfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
vkBeginCommandBuffer(currentbuf->buffer(), &binfo);
// Notify the observer that a new command buffer has been activated.
if (mObserver) {
mObserver->onCommandBuffer(*currentbuf);
}
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
// We push the current markers onto a temporary stack. This must be placed after currentbuf is
// set to the new command buffer since pushGroupMarker also calls get().
while (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) {
auto [marker, time] = mCarriedOverMarkers->pop();
pushGroupMarker(marker.c_str(), time);
}
#endif
return *currentbuf;
vkBeginCommandBuffer(mBuffer, &binfo);
}
bool VulkanCommands::flush() {
// It's perfectly fine to call flush when no commands have been written.
if (mCurrentCommandBufferIndex < 0) {
return false;
VkSemaphore VulkanCommandBuffer::submit() {
while (mMarkerCount > 0) {
popMarker();
}
// Before actually submitting, we need to pop any leftover group markers.
// Note that this needs to occur before vkEndCommandBuffer.
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
while (mGroupMarkers && !mGroupMarkers->empty()) {
if (!mCarriedOverMarkers) {
mCarriedOverMarkers = std::make_unique<VulkanGroupMarkers>();
}
auto const [marker, time] = mGroupMarkers->top();
mCarriedOverMarkers->push(marker, time);
// We still need to call through to vkCmdEndDebugUtilsLabelEXT.
popGroupMarker();
}
#endif
vkEndCommandBuffer(mBuffer);
int8_t const index = mCurrentCommandBufferIndex;
VulkanCommandBuffer const* currentbuf = mStorage[index].get();
VkSemaphore const renderingFinished = mSubmissionSignals[index];
FVK_LOGE <<"[" << (mIsProtected ? "protected" : "regular") <<"]=" << buffer() << " end"
<<utils::io::endl;
vkEndCommandBuffer(currentbuf->buffer());
// If the injected semaphore is an "image available" semaphore that has not yet been signaled,
// it is sometimes fine to start executing commands anyway, as along as we stall the GPU at the
// VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT stage. However we need to assume the worst
// here and use VK_PIPELINE_STAGE_ALL_COMMANDS_BIT. This is a more aggressive stall, but it is
// the only safe option because the previously submitted command buffer might have set up some
// state that the new command buffer depends on.
VkPipelineStageFlags waitDestStageMasks[2] = {
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VkPipelineStageFlags const waitDestStageMasks[2] = {
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
};
VkSemaphore signals[2] = {
VK_NULL_HANDLE,
VK_NULL_HANDLE,
};
uint32_t waitSemaphoreCount = 0;
if (mSubmissionSignal) {
signals[waitSemaphoreCount++] = mSubmissionSignal;
}
if (mInjectedSignal) {
signals[waitSemaphoreCount++] = mInjectedSignal;
}
VkCommandBuffer const cmdbuffer = currentbuf->buffer();
VkSubmitInfo submitInfo{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.waitSemaphoreCount = waitSemaphoreCount,
.pWaitSemaphores = waitSemaphoreCount > 0 ? signals : nullptr,
.pWaitDstStageMask = waitDestStageMasks,
.commandBufferCount = 1,
.pCommandBuffers = &cmdbuffer,
.signalSemaphoreCount = 1u,
.pSignalSemaphores = &renderingFinished,
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.waitSemaphoreCount = mWaitSemaphores.size(),
.pWaitSemaphores = mWaitSemaphores.data(),
.pWaitDstStageMask = waitDestStageMasks,
.commandBufferCount = 1u,
.pCommandBuffers = &mBuffer,
.signalSemaphoreCount = 1u,
.pSignalSemaphores = &mSubmission,
};
// add submit protection if needed
VkProtectedSubmitInfo protectedSubmitInfo{
.sType = VK_STRUCTURE_TYPE_PROTECTED_SUBMIT_INFO,
.protectedSubmit = VK_TRUE,
};
if (isProtected) {
submitInfo.pNext = &protectedSubmitInfo;
}
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
FVK_LOGI << "Submitting cmdbuffer=" << cmdbuffer
<< " wait=(" << signals[0] << ", " << signals[1] << ") "
<< " signal=" << renderingFinished
<< " fence=" << currentbuf->fence->fence
<< utils::io::endl;
FVK_LOGI << "Submitting cmdbuffer=" << mBuffer
<< " wait=(";
for (size_t s = 0, count = mWaitSemaphores.size(); s < count; ++s) {
FVK_LOGI << mWaitSemaphores[s] << " ";
}
FVK_LOGI << ") "
<< " signal=" << mSubmission
<< " fence=" << mFence << utils::io::endl;
#endif
auto& cmdfence = currentbuf->fence;
UTILS_UNUSED_IN_RELEASE VkResult result = VK_SUCCESS;
{
auto scope = cmdfence->setValue(VK_NOT_READY);
result = vkQueueSubmit(mQueue, 1, &submitInfo, cmdfence->getFence());
}
mFenceStatus->setStatus(VK_NOT_READY);
VkResult result = vkQueueSubmit(mQueue, 1, &submitInfo, mFence);
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
if (result != VK_SUCCESS) {
@@ -309,86 +258,212 @@ bool VulkanCommands::flush() {
}
#endif
assert_invariant(result == VK_SUCCESS);
mWaitSemaphores.clear();
return mSubmission;
}
mSubmissionSignal = renderingFinished;
mInjectedSignal = VK_NULL_HANDLE;
mCurrentCommandBufferIndex = -1;
CommandBufferPool::CommandBufferPool(VulkanContext* context, VulkanResourceAllocator* allocator,
VkDevice device, VkQueue queue, uint8_t queueFamilyIndex, bool isProtected)
: mDevice(device),
mRecording(INVALID) {
VkCommandPoolCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT |
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT |
(isProtected ? VK_COMMAND_POOL_CREATE_PROTECTED_BIT : 0u),
.queueFamilyIndex = queueFamilyIndex,
};
vkCreateCommandPool(device, &createInfo, VKALLOC, &mPool);
for (size_t i = 0; i < CAPACITY; ++i) {
mBuffers.emplace_back(std::make_unique<VulkanCommandBuffer>(context, allocator, device,
queue, mPool, isProtected));
}
}
CommandBufferPool::~CommandBufferPool() {
wait();
gc();
vkDestroyCommandPool(mDevice, mPool, VKALLOC);
}
VulkanCommandBuffer& CommandBufferPool::getRecording() {
if (isRecording()) {
return *mBuffers[mRecording];
}
auto const findNext = [this]() {
for (int8_t i = 0; i < CAPACITY; ++i) {
if (!mSubmitted[i]) {
return i;
}
}
return INVALID;
};
while ((mRecording = findNext()) == INVALID) {
wait();
gc();
}
auto& recording = *mBuffers[mRecording];
recording.begin();
return recording;
}
void CommandBufferPool::gc() {
ActiveBuffers reclaimed;
mSubmitted.forEachSetBit([this,&reclaimed] (size_t index) {
auto& buffer = mBuffers[index];
if (buffer->getStatus() == VK_SUCCESS) {
reclaimed.set(index, true);
buffer->reset();
}
});
mSubmitted &= ~reclaimed;
}
void CommandBufferPool::update() {
mSubmitted.forEachSetBit([this] (size_t index) {
auto& buffer = mBuffers[index];
VkResult status = vkGetFenceStatus(mDevice, buffer->getVkFence());
if (status == VK_SUCCESS) {
buffer->setComplete();
}
});
}
VkSemaphore CommandBufferPool::flush() {
// We're not recording right now.
if (!isRecording()) {
return VK_NULL_HANDLE;
}
auto submitSemaphore = mBuffers[mRecording]->submit();
mSubmitted.set(mRecording, true);
mRecording = INVALID;
return submitSemaphore;
}
void CommandBufferPool::wait() {
uint8_t count = 0;
VkFence fences[CAPACITY];
mSubmitted.forEachSetBit([this,&count,&fences] (size_t index) {
fences[count++] = mBuffers[index]->getVkFence();
});
vkWaitForFences(mDevice, count, fences, VK_TRUE, UINT64_MAX);
update();
}
void CommandBufferPool::waitFor(VkSemaphore previousAction) {
if (!isRecording()) {
return;
}
auto& recording = mBuffers[mRecording];
recording->insertWait(previousAction);
}
void CommandBufferPool::pushMarker(char const* marker) {
getRecording().pushMarker(marker);
}
void CommandBufferPool::popMarker() {
getRecording().popMarker();
}
void CommandBufferPool::insertEvent(char const* marker) {
getRecording().insertEvent(marker);
}
VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
VkQueue protectedQueue, uint32_t protectedQueueFamilyIndex, VulkanContext* context,
VulkanResourceAllocator* allocator)
: mDevice(device),
mProtectedQueue(protectedQueue),
mProtectedQueueFamilyIndex(protectedQueueFamilyIndex),
mAllocator(allocator),
mContext(context),
mPool(std::make_unique<CommandBufferPool>(context, allocator, device, queue, queueFamilyIndex,
false)) {
}
void VulkanCommands::terminate() {
mPool.reset();
mProtectedPool.reset();
}
VulkanCommandBuffer& VulkanCommands::get() {
auto& ret = mPool->getRecording();
return ret;
}
VulkanCommandBuffer& VulkanCommands::getProtected() {
assert_invariant(mProtectedQueue != VK_NULL_HANDLE);
if (!mProtectedPool) {
mProtectedPool = std::make_unique<CommandBufferPool>(mContext, mAllocator, mDevice,
mProtectedQueue, mProtectedQueueFamilyIndex, true);
}
auto& ret = mProtectedPool->getRecording();
return ret;
}
bool VulkanCommands::flush() {
VkSemaphore dependency = mInjectedDependency;
VkSemaphore lastSubmit = mLastSubmit;
bool hasFlushed = false;
// Note that we've ordered it so that the non-protected commands are followed by the protected
// commands. This assumes that the protected commands will be that one doing the rendering into
// the protected memory (i.e. protected render target).
for (auto pool: {mPool.get(), mProtectedPool.get()}) {
if (!pool || !pool->isRecording()) {
continue;
}
if (dependency != VK_NULL_HANDLE) {
pool->waitFor(dependency);
}
if (lastSubmit != VK_NULL_HANDLE) {
pool->waitFor(lastSubmit);
lastSubmit = VK_NULL_HANDLE;
}
dependency = pool->flush();
hasFlushed = true;
}
if (hasFlushed) {
mInjectedDependency = VK_NULL_HANDLE;
mLastSubmit = dependency;
}
return true;
}
VkSemaphore VulkanCommands::acquireFinishedSignal() {
VkSemaphore semaphore = mSubmissionSignal;
mSubmissionSignal = VK_NULL_HANDLE;
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
FVK_LOGI << "Acquiring " << semaphore << " (e.g. for vkQueuePresentKHR)" << io::endl;
#endif
return semaphore;
}
void VulkanCommands::injectDependency(VkSemaphore next) {
assert_invariant(mInjectedSignal == VK_NULL_HANDLE);
mInjectedSignal = next;
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
FVK_LOGI << "Injecting " << next << " (e.g. due to vkAcquireNextImageKHR)" << io::endl;
#endif
}
void VulkanCommands::wait() {
VkFence fences[CAPACITY];
size_t count = 0;
for (size_t i = 0; i < CAPACITY; i++) {
auto wrapper = mStorage[i].get();
if (wrapper->buffer() != VK_NULL_HANDLE
&& mCurrentCommandBufferIndex != static_cast<int8_t>(i)) {
fences[count++] = wrapper->fence->getFence();
}
}
if (count > 0) {
vkWaitForFences(mDevice, count, fences, VK_TRUE, UINT64_MAX);
updateFences();
FVK_SYSTRACE_CONTEXT();
FVK_SYSTRACE_START("commands::wait");
mPool->wait();
if (mProtectedPool) {
mProtectedPool->wait();
}
FVK_SYSTRACE_END();
}
void VulkanCommands::gc() {
FVK_SYSTRACE_CONTEXT();
FVK_SYSTRACE_START("commands::gc");
VkFence fences[CAPACITY];
size_t count = 0;
for (size_t i = 0; i < CAPACITY; i++) {
auto wrapper = mStorage[i].get();
if (wrapper->buffer() == VK_NULL_HANDLE) {
continue;
}
auto const vkfence = wrapper->fence->getFence();
VkResult const result = vkGetFenceStatus(mDevice, vkfence);
if (result != VK_SUCCESS) {
continue;
}
fences[count++] = vkfence;
wrapper->fence->setValue(VK_SUCCESS);
wrapper->reset();
mAvailableBufferCount++;
}
if (count > 0) {
vkResetFences(mDevice, count, fences);
mPool->gc();
if (mProtectedPool) {
mProtectedPool->gc();
}
FVK_SYSTRACE_END();
}
void VulkanCommands::updateFences() {
for (size_t i = 0; i < CAPACITY; i++) {
auto wrapper = mStorage[i].get();
if (wrapper->buffer() != VK_NULL_HANDLE) {
VulkanCmdFence* fence = wrapper->fence.get();
if (fence) {
VkResult status = vkGetFenceStatus(mDevice, fence->getFence());
// This is either VK_SUCCESS, VK_NOT_READY, or VK_ERROR_DEVICE_LOST.
fence->setValue(status);
}
}
mPool->update();
if (mProtectedPool) {
mProtectedPool->update();
}
}
@@ -402,29 +477,14 @@ void VulkanCommands::pushGroupMarker(char const* str, VulkanGroupMarkers::Timest
FVK_LOGD << "----> " << str << utils::io::endl;
}
#endif
// TODO: Add group marker color to the Driver API
VkCommandBuffer const cmdbuffer = get().buffer();
if (!mGroupMarkers) {
mGroupMarkers = std::make_unique<VulkanGroupMarkers>();
}
mGroupMarkers->push(str, timestamp);
if (mContext->isDebugUtilsSupported()) {
VkDebugUtilsLabelEXT labelInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
.pLabelName = str,
.color = {0, 1, 0, 1},
};
vkCmdBeginDebugUtilsLabelEXT(cmdbuffer, &labelInfo);
} else if (mContext->isDebugMarkersSupported()) {
VkDebugMarkerMarkerInfoEXT markerInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT,
.pMarkerName = str,
.color = {0.0f, 1.0f, 0.0f, 1.0f},
};
vkCmdDebugMarkerBeginEXT(cmdbuffer, &markerInfo);
mPool->pushMarker(str);
if (mProtectedPool) {
mProtectedPool->pushMarker(str);
}
}
@@ -442,10 +502,9 @@ void VulkanCommands::popGroupMarker() {
#else
mGroupMarkers->pop();
#endif
if (mContext->isDebugUtilsSupported()) {
vkCmdEndDebugUtilsLabelEXT(cmdbuffer);
} else if (mContext->isDebugMarkersSupported()) {
vkCmdDebugMarkerEndEXT(cmdbuffer);
mPool->popMarker();
if (mProtectedPool) {
mProtectedPool->popMarker();
}
} else if (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) {
// It could be that pop is called between flush() and get() (new command buffer), in which
@@ -455,22 +514,10 @@ void VulkanCommands::popGroupMarker() {
}
}
void VulkanCommands::insertEventMarker(char const* string, uint32_t len) {
VkCommandBuffer const cmdbuffer = get().buffer();
if (mContext->isDebugUtilsSupported()) {
VkDebugUtilsLabelEXT labelInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
.pLabelName = string,
.color = {1, 1, 0, 1},
};
vkCmdInsertDebugUtilsLabelEXT(cmdbuffer, &labelInfo);
} else if (mContext->isDebugMarkersSupported()) {
VkDebugMarkerMarkerInfoEXT markerInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT,
.pMarkerName = string,
.color = {0.0f, 1.0f, 0.0f, 1.0f},
};
vkCmdDebugMarkerInsertEXT(cmdbuffer, &markerInfo);
void VulkanCommands::insertEventMarker(char const* str, uint32_t len) {
mPool->insertEvent(str);
if (mProtectedPool) {
mProtectedPool->insertEvent(str);
}
}

View File

@@ -23,6 +23,7 @@
#include "VulkanConstants.h"
#include "VulkanResources.h"
#include "VulkanUtility.h"
#include <utils/Condition.h>
#include <utils/FixedCapacityVector.h>
@@ -61,43 +62,20 @@ private:
// Wrapper to enable use of shared_ptr for implementing shared ownership of low-level Vulkan fences.
struct VulkanCmdFence {
struct SetValueScope {
public:
~SetValueScope() {
mHolder->mutex.unlock();
mHolder->condition.notify_all();
}
VulkanCmdFence(VkResult initialStatus) {
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted". When this fence
// gets submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually
// finishes executing the command buffer, the status changes to VK_SUCCESS.
status.store(initialStatus);
}
private:
SetValueScope(VulkanCmdFence* fenceHolder, VkResult result) :
mHolder(fenceHolder) {
mHolder->mutex.lock();
mHolder->status.store(result);
}
VulkanCmdFence* mHolder;
friend struct VulkanCmdFence;
};
VulkanCmdFence(VkFence ifence);
~VulkanCmdFence() = default;
SetValueScope setValue(VkResult value) {
return {this, value};
}
void setStatus(VkResult value) { status.store(value); }
VkFence& getFence() {
return fence;
}
VkResult getStatus() {
std::unique_lock<utils::Mutex> lock(mutex);
return status.load(std::memory_order_acquire);
}
VkResult getStatus() { return status.load(std::memory_order_acquire); }
private:
VkFence fence;
utils::Condition condition;
utils::Mutex mutex;
std::atomic<VkResult> status;
};
@@ -105,11 +83,14 @@ private:
// DriverApi fence object and should not be destroyed until both the DriverApi object is freed and
// we're done waiting on the most recent submission of the given command buffer.
struct VulkanCommandBuffer {
VulkanCommandBuffer(VulkanResourceAllocator* allocator, VkDevice device, VkCommandPool pool);
VulkanCommandBuffer(VulkanContext* mContext, VulkanResourceAllocator* allocator,
VkDevice device, VkQueue queue, VkCommandPool pool, bool isProtected);
VulkanCommandBuffer(VulkanCommandBuffer const&) = delete;
VulkanCommandBuffer& operator=(VulkanCommandBuffer const&) = delete;
~VulkanCommandBuffer();
inline void acquire(VulkanResource* resource) {
mResourceManager.acquire(resource);
}
@@ -117,41 +98,89 @@ struct VulkanCommandBuffer {
inline void acquire(VulkanAcquireOnlyResourceManager* srcResources) {
mResourceManager.acquireAll(srcResources);
}
void reset() noexcept;
inline void reset() {
fence.reset();
mResourceManager.clear();
mPipeline = VK_NULL_HANDLE;
inline void insertWait(VkSemaphore sem) {
mWaitSemaphores.insert(sem);
}
inline void setPipeline(VkPipeline pipeline) {
mPipeline = pipeline;
void pushMarker(char const* marker) noexcept;
void popMarker() noexcept;
void insertEvent(char const* marker) noexcept;
void begin() noexcept;
VkSemaphore submit();
inline void setComplete() {
mFenceStatus->setStatus(VK_SUCCESS);
}
inline VkPipeline pipeline() const {
return mPipeline;
VkResult getStatus() {
return mFenceStatus->getStatus();
}
inline VkCommandBuffer buffer() const {
if (fence) {
return mBuffer;
}
return VK_NULL_HANDLE;
std::shared_ptr<VulkanCmdFence> getFenceStatus() const {
return mFenceStatus;
}
std::shared_ptr<VulkanCmdFence> fence;
VkFence getVkFence() const {
return mFence;
}
VkCommandBuffer buffer() const {
FVK_LOGE <<"-- getting " << mBuffer << " -- ";
return mBuffer;
}
// debug only
bool mIsProtected;
private:
VulkanContext* mContext;
uint8_t mMarkerCount;
bool const isProtected;
VkDevice mDevice;
VkQueue mQueue;
VulkanAcquireOnlyResourceManager mResourceManager;
CappedArray<VkSemaphore, 2> mWaitSemaphores;
VkCommandBuffer mBuffer;
VkPipeline mPipeline;
VkSemaphore mSubmission;
VkFence mFence;
std::shared_ptr<VulkanCmdFence> mFenceStatus;
};
// Allows classes to be notified after a new command buffer has been activated.
class CommandBufferObserver {
public:
virtual void onCommandBuffer(const VulkanCommandBuffer& cmdbuffer) = 0;
virtual ~CommandBufferObserver();
struct CommandBufferPool {
using ActiveBuffers = utils::bitset32;
static constexpr int8_t INVALID = -1;
CommandBufferPool(VulkanContext* context, VulkanResourceAllocator* allocator, VkDevice device,
VkQueue queue, uint8_t queueFamilyIndex, bool isProtected);
~CommandBufferPool();
VulkanCommandBuffer& getRecording();
void gc();
void update();
VkSemaphore flush();
void wait();
void waitFor(VkSemaphore previousAction);
void pushMarker(char const* marker);
void popMarker();
void insertEvent(char const* marker);
inline bool isRecording() const { return mRecording != INVALID; }
private:
static constexpr int CAPACITY = FVK_MAX_COMMAND_BUFFERS;
using BufferList = utils::FixedCapacityVector<std::unique_ptr<VulkanCommandBuffer>>;
VkDevice mDevice;
VkCommandPool mPool;
ActiveBuffers mSubmitted;
std::vector<std::unique_ptr<VulkanCommandBuffer>> mBuffers;
int8_t mRecording;
};
// Manages a set of command buffers and semaphores, exposing an API that is significantly simpler
@@ -165,9 +194,6 @@ public:
// - This creates a guarantee of in-order execution.
// - Semaphores are recycled to prevent create / destroy churn.
//
// - Notifies listeners when recording begins in a new VkCommandBuffer.
// - Used by PipelineCache so that it knows when to clear out its shadow state.
//
// - Allows 1 user to inject a "dependency" semaphore that stalls the next flush.
// - This is used for asynchronous acquisition of a swap chain image, since the GPU
// might require a valid swap chain image when it starts executing the command buffer.
@@ -183,13 +209,18 @@ public:
class VulkanCommands {
public:
VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
VulkanContext* context, VulkanResourceAllocator* allocator);
VkQueue protectedQueue, uint32_t protectedQueueFamilyIndex, VulkanContext* context,
VulkanResourceAllocator* allocator);
void terminate();
// Creates a "current" command buffer if none exists, otherwise returns the current one.
VulkanCommandBuffer& get();
// Creates a "current" protected capable command buffer if none exists, otherwise
// returns the current one.
VulkanCommandBuffer& getProtected();
// Submits the current command buffer if it exists, then sets "current" to null.
// If there are no outstanding commands then nothing happens and this returns false.
bool flush();
@@ -197,11 +228,17 @@ public:
// Returns the "rendering finished" semaphore for the most recent flush and removes
// it from the existing dependency chain. This is especially useful for setting up
// vkQueuePresentKHR.
VkSemaphore acquireFinishedSignal();
VkSemaphore acquireFinishedSignal() {
VkSemaphore ret= mLastSubmit;
mLastSubmit = VK_NULL_HANDLE;
return ret;
}
// Takes a semaphore that signals when the next flush can occur. Only one injected
// semaphore is allowed per flush. Useful after calling vkAcquireNextImageKHR.
void injectDependency(VkSemaphore next);
void injectDependency(VkSemaphore next) {
mInjectedDependency = next;
}
// Destroys all command buffers that are no longer in use.
void gc();
@@ -212,37 +249,31 @@ public:
// Updates the atomic "status" variable in every extant fence.
void updateFences();
// Sets an observer who is notified every time a new command buffer has been made "current".
// The observer's event handler can only be called during get().
void setObserver(CommandBufferObserver* observer) { mObserver = observer; }
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
void pushGroupMarker(char const* str, VulkanGroupMarkers::Timestamp timestamp = {});
void popGroupMarker();
void insertEventMarker(char const* string, uint32_t len);
std::string getTopGroupMarker() const;
#endif
private:
static constexpr int CAPACITY = FVK_MAX_COMMAND_BUFFERS;
VkDevice const mDevice;
VkQueue const mQueue;
VkCommandPool const mPool;
VulkanContext const* mContext;
VkQueue const mProtectedQueue;
// For defered initialization if/when we need protected content
uint32_t const mProtectedQueueFamilyIndex;
VulkanResourceAllocator* mAllocator;
VulkanContext* mContext;
// int8 only goes up to 127, therefore capacity must be less than that.
static_assert(CAPACITY < 128);
int8_t mCurrentCommandBufferIndex = -1;
VkSemaphore mSubmissionSignal = {};
VkSemaphore mInjectedSignal = {};
utils::FixedCapacityVector<std::unique_ptr<VulkanCommandBuffer>> mStorage;
VkFence mFences[CAPACITY] = {};
VkSemaphore mSubmissionSignals[CAPACITY] = {};
uint8_t mAvailableBufferCount = CAPACITY;
CommandBufferObserver* mObserver = nullptr;
std::unique_ptr<CommandBufferPool> mPool;
std::unique_ptr<CommandBufferPool> mProtectedPool;
VkSemaphore mInjectedDependency = VK_NULL_HANDLE;
VkSemaphore mLastSubmit = VK_NULL_HANDLE;
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
std::unique_ptr<VulkanGroupMarkers> mGroupMarkers;

View File

@@ -89,7 +89,7 @@
#endif
#ifndef NDEBUG
#define FVK_DEBUG_FLAGS (FVK_DEBUG_PERFORMANCE | FVK_DEBUG_FORWARDED_FLAG)
#define FVK_DEBUG_FLAGS (FVK_DEBUG_PERFORMANCE | FVK_DEBUG_FORWARDED_FLAG | FVK_DEBUG_VALIDATION | FVK_DEBUG_DUMP_API)
#else
#define FVK_DEBUG_FLAGS 0
#endif

View File

@@ -121,7 +121,7 @@ void VulkanTimestamps::beginQuery(VulkanCommandBuffer const* commands,
vkCmdWriteTimestamp(cmdbuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, index);
// We stash this because getResult might come before the query is actually processed.
query->setFence(commands->fence);
query->setFence(commands->getFenceStatus());
}
void VulkanTimestamps::endQuery(VulkanCommandBuffer const* commands,

View File

@@ -28,6 +28,9 @@
#include <memory>
#include <sstream>
#include <iomanip>
VK_DEFINE_HANDLE(VmaAllocator)
VK_DEFINE_HANDLE(VmaPool)
@@ -82,24 +85,53 @@ private:
};
struct VulkanRenderPass {
VulkanCommandBuffer* cmdBuffer;
VulkanRenderTarget* renderTarget;
VkRenderPass renderPass;
RenderPassParams params;
int currentSubpass;
};
namespace {
template< typename T >
std::string int_to_hex( T i )
{
std::stringstream stream;
stream << "0x"
<< std::setfill ('0') << std::setw(sizeof(T)*2)
<< std::hex << i;
return stream.str();
}
bool printed = false;
}
// This is a collection of immutable data about the vulkan context. This actual handles to the
// context are stored in VulkanPlatform.
struct VulkanContext {
public:
inline uint32_t selectMemoryType(uint32_t flags, VkFlags reqs) const {
for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) {
if (flags & 1) {
inline uint32_t selectMemoryType(uint32_t flags, VkMemoryPropertyFlags reqs) const {
if ((reqs & VK_MEMORY_PROPERTY_PROTECTED_BIT) != 0) {
assert_invariant(isProtectedMemorySupported()==true);
}
if (!printed) {
for (uint32_t i = 0; i < mMemoryProperties.memoryTypeCount; i++) {
utils::slog.e << "[" << int_to_hex((uint8_t) i) << "]="
<< int_to_hex(
(uint32_t) mMemoryProperties.memoryTypes[i].propertyFlags)
<< utils::io::endl;
}
printed = true;
}
for (uint32_t i = 0; i < mMemoryProperties.memoryTypeCount; i++) {
if (flags & (1ULL << i)) {
if ((mMemoryProperties.memoryTypes[i].propertyFlags & reqs) == reqs) {
return i;
}
}
flags >>= 1;
}
return (uint32_t) VK_MAX_MEMORY_TYPES;
}
@@ -113,19 +145,19 @@ public:
}
inline VkPhysicalDeviceLimits const& getPhysicalDeviceLimits() const noexcept {
return mPhysicalDeviceProperties.limits;
return mPhysicalDeviceProperties.properties.limits;
}
inline uint32_t getPhysicalDeviceVendorId() const noexcept {
return mPhysicalDeviceProperties.vendorID;
return mPhysicalDeviceProperties.properties.vendorID;
}
inline bool isImageCubeArraySupported() const noexcept {
return mPhysicalDeviceFeatures.imageCubeArray == VK_TRUE;
return mPhysicalDeviceFeatures.features.imageCubeArray == VK_TRUE;
}
inline bool isDepthClampSupported() const noexcept {
return mPhysicalDeviceFeatures.depthClamp == VK_TRUE;
return mPhysicalDeviceFeatures.features.depthClamp == VK_TRUE;
}
inline bool isDebugMarkersSupported() const noexcept {
@@ -141,21 +173,30 @@ public:
}
inline bool isClipDistanceSupported() const noexcept {
return mPhysicalDeviceFeatures.shaderClipDistance == VK_TRUE;
return mPhysicalDeviceFeatures.features.shaderClipDistance == VK_TRUE;
}
inline bool isLazilyAllocatedMemorySupported() const noexcept {
return mLazilyAllocatedMemorySupported;
}
inline bool isProtectedMemorySupported() const noexcept {
return mProtectedMemorySupported;
}
private:
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
VkPhysicalDeviceProperties mPhysicalDeviceProperties = {};
VkPhysicalDeviceFeatures mPhysicalDeviceFeatures = {};
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
};
VkPhysicalDeviceFeatures2 mPhysicalDeviceFeatures = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
};
bool mDebugMarkersSupported = false;
bool mDebugUtilsSupported = false;
bool mMultiviewEnabled = false;
bool mLazilyAllocatedMemorySupported = false;
bool mProtectedMemorySupported = false;
VkFormatList mDepthStencilFormats;
VkFormatList mBlittableDepthStencilFormats;

View File

@@ -198,8 +198,9 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
mResourceAllocator(driverConfig.handleArenaSize, driverConfig.disableHandleUseAfterFreeCheck),
mResourceManager(&mResourceAllocator),
mThreadSafeResourceManager(&mResourceAllocator),
mCommands(mPlatform->getDevice(), mPlatform->getGraphicsQueue(),
mPlatform->getGraphicsQueueFamilyIndex(), &mContext, &mResourceAllocator),
mCommands(mPlatform->getDevice(), mPlatform->getGraphicsQueue(), mPlatform->getGraphicsQueueFamilyIndex(),
mPlatform->getProtectedGraphicsQueue(), mPlatform->getProtectedGraphicsQueueFamilyIndex(),
&mContext, &mResourceAllocator),
mPipelineLayoutCache(mPlatform->getDevice(), &mResourceAllocator),
mPipelineCache(mPlatform->getDevice(), mAllocator),
mStagePool(mAllocator, &mCommands),
@@ -365,6 +366,7 @@ void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
FVK_SYSTRACE_START("beginFrame");
// Do nothing.
FVK_SYSTRACE_END();
FVK_LOGE <<"-----begin frame ---" << utils::io::endl;
}
void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, CallbackHandler* handler,
@@ -384,6 +386,7 @@ void VulkanDriver::endFrame(uint32_t frameId) {
mCommands.flush();
collectGarbage();
FVK_SYSTRACE_END();
FVK_LOGE <<"-----end frame ---" << utils::io::endl;
}
void VulkanDriver::updateDescriptorSetBuffer(
@@ -526,19 +529,6 @@ void VulkanDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
FVK_SYSTRACE_END();
}
//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 = mResourceAllocator.construct<VulkanTexture>(th, mPlatform->getDevice(),
// mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, &mResourceAllocator,
// target, levels, format, samples, w, h, depth, usage, mStagePool,
// false /*heap allocated */, swizzleMap);
// mResourceManager.acquire(vktexture);
//}
void VulkanDriver::createTextureViewR(Handle<HwTexture> th, Handle<HwTexture> srch,
uint8_t baseLevel, uint8_t levelCount) {
VulkanTexture const* src = mResourceAllocator.handle_cast<VulkanTexture const*>(srch);
@@ -690,8 +680,13 @@ void VulkanDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
}
void VulkanDriver::createFenceR(Handle<HwFence> fh, int) {
VulkanCommandBuffer const& commandBuffer = mCommands.get();
mResourceAllocator.construct<VulkanFence>(fh, commandBuffer.fence);
VulkanCommandBuffer* cmdbuf = nullptr;
if (mCurrentRenderPass.cmdBuffer) {
cmdbuf = mCurrentRenderPass.cmdBuffer;
} else {
cmdbuf = &mCommands.get();
}
mResourceAllocator.construct<VulkanFence>(fh, cmdbuf->getFenceStatus());
}
void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
@@ -703,6 +698,15 @@ void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
auto swapChain = mResourceAllocator.construct<VulkanSwapChain>(sch, mPlatform, mContext,
mAllocator, &mCommands, &mResourceAllocator, mStagePool, nativeWindow, flags);
mResourceManager.acquire(swapChain);
if (flags & backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
if (!isProtectedContentSupported()) {
utils::slog.w << "protected swapchain requested, but Vulkan does not support it"
<< utils::io::endl;
} else {
}
}
}
void VulkanDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t width,
@@ -977,8 +981,7 @@ bool VulkanDriver::isSRGBSwapChainSupported() {
}
bool VulkanDriver::isProtectedContentSupported() {
// the SWAP_CHAIN_CONFIG_PROTECTED_CONTENT flag is not supported
return false;
return mContext.isProtectedMemorySupported();
}
bool VulkanDriver::isStereoSupported() {
@@ -1242,6 +1245,8 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
FVK_SYSTRACE_CONTEXT();
FVK_SYSTRACE_START("beginRenderPass");
FVK_LOGE <<"begin render pass" << utils::io::endl;
VulkanRenderTarget* const rt = mResourceAllocator.handle_cast<VulkanRenderTarget*>(rth);
VkExtent2D const& extent = rt->getExtent();
assert_invariant(rt == mDefaultRenderTarget || extent.width > 0 && extent.height > 0);
@@ -1250,6 +1255,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
// 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()) {
VulkanSwapChain* sc = mCurrentSwapChain;
assert_invariant(sc);
@@ -1270,7 +1276,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
// 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.
VulkanCommandBuffer& commands = mCommands.get();
VulkanCommandBuffer& commands = rt->isProtected() ? mCommands.getProtected() : mCommands.get();
VkCommandBuffer const cmdbuffer = commands.buffer();
// Scissor is reset with each render pass
@@ -1379,6 +1385,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
vkCmdSetViewport(cmdbuffer, 0, 1, &viewport);
mCurrentRenderPass = {
.cmdBuffer = &commands,
.renderTarget = rt,
.renderPass = renderPassInfo.renderPass,
.params = params,
@@ -1391,8 +1398,10 @@ void VulkanDriver::endRenderPass(int) {
FVK_SYSTRACE_CONTEXT();
FVK_SYSTRACE_START("endRenderPass");
VulkanCommandBuffer& commands = mCommands.get();
VkCommandBuffer cmdbuffer = commands.buffer();
FVK_LOGE <<"end render pass" << utils::io::endl;
VulkanCommandBuffer* cmdbuf = mCurrentRenderPass.cmdBuffer;
VkCommandBuffer cmdbuffer = cmdbuf->buffer();
vkCmdEndRenderPass(cmdbuffer);
VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget;
@@ -1400,7 +1409,7 @@ void VulkanDriver::endRenderPass(int) {
// 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(commands);
rt->emitBarriersEndRenderPass(*cmdbuf);
mRenderPassFboInfo = {};
mCurrentRenderPass.renderTarget = nullptr;
@@ -1412,11 +1421,12 @@ void VulkanDriver::nextSubpass(int) {
FILAMENT_CHECK_PRECONDITION(mCurrentRenderPass.currentSubpass == 0)
<< "Only two subpasses are currently supported.";
VulkanCommandBuffer* cmdbuf = mCurrentRenderPass.cmdBuffer;
VulkanRenderTarget* renderTarget = mCurrentRenderPass.renderTarget;
assert_invariant(renderTarget);
assert_invariant(mCurrentRenderPass.params.subpassMask);
vkCmdNextSubpass(mCommands.get().buffer(), VK_SUBPASS_CONTENTS_INLINE);
vkCmdNextSubpass(cmdbuf->buffer(), VK_SUBPASS_CONTENTS_INLINE);
mPipelineCache.bindRenderPass(mCurrentRenderPass.renderPass,
++mCurrentRenderPass.currentSubpass);
@@ -1464,8 +1474,8 @@ void VulkanDriver::commit(Handle<HwSwapChain> sch) {
void VulkanDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
backend::PushConstantVariant value) {
assert_invariant(mBoundPipeline.program && "Expect a program when writing to push constants");
VulkanCommands* commands = &mCommands;
mBoundPipeline.program->writePushConstant(commands, mBoundPipeline.pipelineLayout, stage, index,
VulkanCommandBuffer* cmdbuf = mCurrentRenderPass.cmdBuffer;
mBoundPipeline.program->writePushConstant(cmdbuf, mBoundPipeline.pipelineLayout, stage, index,
value);
}
@@ -1476,6 +1486,7 @@ void VulkanDriver::insertEventMarker(char const* string) {
}
void VulkanDriver::pushGroupMarker(char const* string) {
FVK_LOGE <<"---> " << string << utils::io::endl;
// 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);
@@ -1654,9 +1665,9 @@ void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
FVK_SYSTRACE_CONTEXT();
FVK_SYSTRACE_START("draw");
FVK_SYSTRACE_START("bindPipeline");
VulkanCommandBuffer* commands = &mCommands.get();
VulkanCommandBuffer* commands = mCurrentRenderPass.cmdBuffer;
const VulkanVertexBufferInfo& vbi =
*mResourceAllocator.handle_cast<VulkanVertexBufferInfo*>(pipelineState.vertexBufferInfo);
@@ -1667,6 +1678,8 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
auto* program = mResourceAllocator.handle_cast<VulkanProgram*>(programHandle);
commands->acquire(program);
FVK_LOGE <<"bindpipeline with cmdbuf=" << commands->buffer() << utils::io::endl;
// Update the VK raster state.
const VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget;
@@ -1738,7 +1751,7 @@ void VulkanDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
FVK_SYSTRACE_CONTEXT();
FVK_SYSTRACE_START("bindRenderPrimitive");
VulkanCommandBuffer* commands = &mCommands.get();
VulkanCommandBuffer* commands = mCurrentRenderPass.cmdBuffer;
VkCommandBuffer cmdbuffer = commands->buffer();
const VulkanRenderPrimitive& prim = *mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
commands->acquire(prim.indexBuffer);
@@ -1775,10 +1788,12 @@ void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
FVK_SYSTRACE_CONTEXT();
FVK_SYSTRACE_START("draw2");
VulkanCommandBuffer& commands = mCommands.get();
VkCommandBuffer cmdbuffer = commands.buffer();
VulkanCommandBuffer* commands = mCurrentRenderPass.cmdBuffer;
VkCommandBuffer cmdbuffer = commands->buffer();
mDescriptorSetManager.commit(&commands, mBoundPipeline.pipelineLayout,
FVK_LOGE <<"draw2 with cmdbuf=" << cmdbuffer << utils::io::endl;
mDescriptorSetManager.commit(commands, mBoundPipeline.pipelineLayout,
mBoundPipeline.descriptorSetMask);
// Finally, make the actual draw call. TODO: support subranges
@@ -1806,8 +1821,8 @@ void VulkanDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGr
}
void VulkanDriver::scissor(Viewport scissorBox) {
VulkanCommandBuffer& commands = mCommands.get();
VkCommandBuffer cmdbuffer = commands.buffer();
VulkanCommandBuffer* commands = mCurrentRenderPass.cmdBuffer;
VkCommandBuffer cmdbuffer = commands->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

View File

@@ -162,9 +162,8 @@ PushConstantDescription::PushConstantDescription(backend::Program const& program
}
}
void PushConstantDescription::write(VulkanCommands* commands, VkPipelineLayout layout,
void PushConstantDescription::write(VulkanCommandBuffer* cmdbuf, VkPipelineLayout layout,
backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant const& value) {
VulkanCommandBuffer* cmdbuf = &(commands->get());
uint32_t binaryValue = 0;
UTILS_UNUSED_IN_RELEASE auto const& types = mTypes[(uint8_t) stage];
if (std::holds_alternative<bool>(value)) {
@@ -267,7 +266,6 @@ void VulkanRenderTarget::bindToSwapChain(VulkanSwapChain& swapChain) {
VkExtent2D const extent = swapChain.getExtent();
width = extent.width;
height = extent.height;
VulkanAttachment color = {.texture = swapChain.getCurrentColor()};
mInfo->attachments = {color};
@@ -293,6 +291,7 @@ void VulkanRenderTarget::bindToSwapChain(VulkanSwapChain& swapChain) {
fbkey.depth = depth.getImageView();
}
mInfo->colors.set(0);
mProtected = swapChain.isProtected();
}
VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice,
@@ -303,6 +302,7 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
: HwRenderTarget(width, height),
VulkanResource(VulkanResourceType::RENDER_TARGET),
mOffscreen(true),
mProtected(false),
mResources(handleAllocator),
mInfo(std::make_unique<Auxiliary>()) {
@@ -335,6 +335,8 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
continue;
}
mProtected = mProtected || texture->isProtected();
attachments.push_back(attachment);
mInfo->colors.set(index);
@@ -398,6 +400,7 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
mResources.acquire(msaa);
}
}
mProtected = mProtected || depthTexture->isProtected();
}
}

View File

@@ -185,7 +185,7 @@ struct PushConstantDescription {
uint32_t getVkRangeCount() const noexcept { return mRangeCount; }
void write(VulkanCommands* commands, VkPipelineLayout layout, backend::ShaderStage stage,
void write(VulkanCommandBuffer* cmdbuf, VkPipelineLayout layout, backend::ShaderStage stage,
uint8_t index, backend::PushConstantVariant const& value);
private:
@@ -218,9 +218,9 @@ struct VulkanProgram : public HwProgram, VulkanResource {
return mInfo->pushConstantDescription.getVkRanges();
}
inline void writePushConstant(VulkanCommands* commands, VkPipelineLayout layout,
inline void writePushConstant(VulkanCommandBuffer* cmdbuf, VkPipelineLayout layout,
backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant const& value) {
mInfo->pushConstantDescription.write(commands, layout, stage, index, value);
mInfo->pushConstantDescription.write(cmdbuf, layout, stage, index, value);
}
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
@@ -307,6 +307,8 @@ struct VulkanRenderTarget : private HwRenderTarget, VulkanResource {
inline bool isSwapChain() const { return !mOffscreen; }
bool isProtected() const { return mProtected; }
void bindToSwapChain(VulkanSwapChain& surf);
void emitBarriersBeginRenderPass(VulkanCommandBuffer& commands);
@@ -328,6 +330,7 @@ private:
int8_t msaaIndex = UNDEFINED_INDEX;
};
bool const mOffscreen;
bool mProtected;
VulkanAcquireOnlyResourceManager mResources;
std::unique_ptr<Auxiliary> mInfo;

View File

@@ -66,17 +66,12 @@ void VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) {
VkCommandBuffer const cmdbuffer = commands->buffer();
PipelineCacheEntry* cacheEntry = getOrCreatePipeline();
// Check if the required pipeline is already bound.
if (cacheEntry->handle == commands->pipeline()) {
return;
}
// If an error occurred, allow higher levels to handle it gracefully.
assert_invariant(cacheEntry != nullptr && "Failed to create/find pipeline");
mBoundPipeline = mPipelineRequirements;
vkCmdBindPipeline(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, cacheEntry->handle);
commands->setPipeline(cacheEntry->handle);
}
VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() noexcept {

View File

@@ -40,7 +40,8 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const&
mTransitionSwapChainImageLayoutForPresent(
platform->getCustomization().transitionSwapChainImageLayoutForPresent),
mAcquired(false),
mIsFirstRenderPass(true) {
mIsFirstRenderPass(true),
mIsProtected(false) {
swapChain = mPlatform->createSwapChain(nativeWindow, flags, extent);
FILAMENT_CHECK_POSTCONDITION(swapChain) << "Unable to create swapchain";
@@ -63,21 +64,28 @@ void VulkanSwapChain::update() {
mColors.reserve(bundle.colors.size());
VkDevice const device = mPlatform->getDevice();
mIsProtected = bundle.protection;
TextureUsage depthUsage = TextureUsage::DEPTH_ATTACHMENT;
TextureUsage colorUsage = TextureUsage::COLOR_ATTACHMENT;
if (bundle.protection) {
depthUsage |= TextureUsage::PROTECTED;
colorUsage |= TextureUsage::PROTECTED;
}
for (auto const color: bundle.colors) {
mColors.push_back(std::make_unique<VulkanTexture>(device, mAllocator, mCommands, mHandleAllocator,
color, bundle.colorFormat, 1, bundle.extent.width, bundle.extent.height,
TextureUsage::COLOR_ATTACHMENT, mStagePool, true /* heap allocated */));
colorUsage, mStagePool, true /* heap allocated */));
}
mDepth = std::make_unique<VulkanTexture>(device, mAllocator, mCommands, mHandleAllocator,
bundle.depth, bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height,
TextureUsage::DEPTH_ATTACHMENT, mStagePool, true /* heap allocated */);
depthUsage, mStagePool, true /* heap allocated */);
mExtent = bundle.extent;
}
void VulkanSwapChain::present() {
if (!mHeadless && mTransitionSwapChainImageLayoutForPresent) {
VulkanCommandBuffer& commands = mCommands->get();
VulkanCommandBuffer& commands = mIsProtected ? mCommands->getProtected() : mCommands->get();
VkImageSubresourceRange const subresources{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,

View File

@@ -74,6 +74,10 @@ struct VulkanSwapChain : public HwSwapChain, VulkanResource {
return mExtent;
}
inline bool isProtected() noexcept {
return mPlatform->isProtected(swapChain);
}
private:
static constexpr int IMAGE_READY_SEMAPHORE_COUNT = FVK_MAX_COMMAND_BUFFERS;
@@ -97,6 +101,7 @@ private:
std::function<void(Platform::SwapChain* handle)> mExplicitImageReadyWait = nullptr;
bool mAcquired;
bool mIsFirstRenderPass;
bool mIsProtected;
};

View File

@@ -147,12 +147,14 @@ inline VulkanLayout getDefaultLayoutImpl(VkImageUsageFlags vkusage) {
VulkanTextureState::VulkanTextureState(VkDevice device, VmaAllocator allocator,
VulkanCommands* commands, VulkanStagePool& stagePool, VkFormat format,
VkImageViewType viewType, uint8_t levels, uint8_t layerCount, VulkanLayout defaultLayout)
VkImageViewType viewType, uint8_t levels, uint8_t layerCount, VulkanLayout defaultLayout,
bool isProtected)
: VulkanResource(VulkanResourceType::HEAP_ALLOCATED),
mVkFormat(format),
mViewType(viewType),
mFullViewRange{filament::backend::getImageAspect(format), 0, levels, 0, layerCount},
mDefaultLayout(defaultLayout),
mIsProtected(isProtected),
mStagePool(stagePool),
mDevice(device),
mAllocator(allocator),
@@ -183,7 +185,8 @@ VulkanTexture::VulkanTexture(
mState(handleAllocator->initHandle<VulkanTextureState>(
device, allocator, commands, stagePool,
format, imgutil::getViewType(SamplerType::SAMPLER_2D), 1, 1,
getDefaultLayoutImpl(tusage))) {
getDefaultLayoutImpl(tusage),
any(usage& TextureUsage::PROTECTED))) {
auto* const state = getSharedState();
state->mTextureImage = image;
mPrimaryViewRange = state->mFullViewRange;
@@ -201,7 +204,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
mAllocator(handleAllocator),
mState(handleAllocator->initHandle<VulkanTextureState>(device, allocator, commands, stagePool,
backend::getVkFormat(tformat), imgutil::getViewType(target), levels,
getLayerCount(target, depth), VulkanLayout::UNDEFINED)) {
getLayerCount(target, depth), VulkanLayout::UNDEFINED, any(usage & TextureUsage::PROTECTED))) {
auto* const state = getSharedState();
// Create an appropriately-sized device-only VkImage, but do not fill it yet.
@@ -232,6 +235,9 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
imageInfo.arrayLayers = depth * 6;
imageInfo.extent.depth = 1;
}
if (state->mIsProtected) {
imageInfo.flags |= VK_IMAGE_CREATE_PROTECTED_BIT;
}
if (any(usage & TextureUsage::BLIT_SRC)) {
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
@@ -331,7 +337,8 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
const VkFlags requiredMemoryFlags =
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
(useTransientAttachment ? VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT : 0U);
(useTransientAttachment ? VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT : 0U) |
(state->mIsProtected ? VK_MEMORY_PROPERTY_PROTECTED_BIT : 0U);
uint32_t memoryTypeIndex
= context.selectMemoryType(memReqs.memoryTypeBits, requiredMemoryFlags);
@@ -354,7 +361,9 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
// Go ahead and create the primary image view.
getImageView(mPrimaryViewRange, state->mViewType, mSwizzle);
VulkanCommandBuffer& commandsBuf = state->mCommands->get();
VulkanCommandBuffer& commandsBuf = state->mIsProtected ? state->mCommands->getProtected()
: state->mCommands->get();
commandsBuf.acquire(this);
auto const defaultLayout = state->mDefaultLayout = getDefaultLayoutImpl(imageInfo.usage);
@@ -416,6 +425,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
assert_invariant(depth <= this->depth * ((target == SamplerType::SAMPLER_CUBEMAP ||
target == SamplerType::SAMPLER_CUBEMAP_ARRAY) ? 6 : 1));
auto* const state = getSharedState();
assert_invariant(!state->mIsProtected);
const PixelBufferDescriptor* hostData = &data;
PixelBufferDescriptor reshapedData;
@@ -447,7 +457,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
vmaUnmapMemory(state->mAllocator, stage->memory);
vmaFlushAllocation(state->mAllocator, stage->memory, 0, hostData->size);
VulkanCommandBuffer& commands = state->mCommands->get();
VulkanCommandBuffer& commands =state->mCommands->get();
VkCommandBuffer const cmdbuf = commands.buffer();
commands.acquire(this);
@@ -503,6 +513,8 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width,
uint32_t height, uint32_t depth, uint32_t miplevel) {
auto* const state = getSharedState();
assert_invariant(!state->mIsProtected);
void* mapped = nullptr;
VulkanStageImage const* stage
= state->mStagePool.acquireImage(hostData.format, hostData.type, width, height);

View File

@@ -35,7 +35,8 @@ class VulkanResourceAllocator;
struct VulkanTextureState : public VulkanResource {
VulkanTextureState(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanStagePool& stagePool, VkFormat format, VkImageViewType viewType, uint8_t levels,
uint8_t layerCount, VulkanLayout defaultLayout);
uint8_t layerCount, VulkanLayout defaultLayout,
bool isProtected = false);
struct ImageViewKey {
VkImageSubresourceRange range; // 4 * 5 bytes
@@ -70,6 +71,8 @@ struct VulkanTextureState : public VulkanResource {
VkImage mTextureImage = VK_NULL_HANDLE;
VulkanLayout mDefaultLayout;
bool mIsProtected = false;
// Track the image layout of each subresource using a sparse range map.
utils::RangeMap<uint32_t, VulkanLayout> mSubresourceLayouts;
@@ -111,6 +114,13 @@ struct VulkanTexture : public HwTexture, VulkanResource {
VulkanResourceAllocator* handleAllocator,
VulkanTexture const* src, VkComponentMapping swizzle);
// Specialized constructor for external images
VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VulkanResourceAllocator* handleAllocator,
VkImage image, VkDeviceMemory memory,
VkFormat format, uint32_t width, uint32_t height, TextureUsage tusage,
VulkanStagePool& stagePool);
~VulkanTexture();
// Uploads data into a subregion of a 2D or 3D texture.
@@ -179,6 +189,11 @@ struct VulkanTexture : public HwTexture, VulkanResource {
return state->mIsTransientAttachment;
}
bool isProtected() const {
VulkanTextureState const* state = getSharedState();
return state->mIsProtected;
}
bool transitionLayout(VulkanCommandBuffer* commands, VkImageSubresourceRange const& range,
VulkanLayout newLayout);

View File

@@ -159,6 +159,7 @@ ExtensionSet getInstanceExtensions(ExtensionSet const& externallyRequiredExts =
// Request all cross-platform extensions.
VK_KHR_SURFACE_EXTENSION_NAME,
VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME,
// Request these if available.
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
@@ -300,10 +301,11 @@ VkInstance createInstance(ExtensionSet const& requiredExts) {
}
VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
VkPhysicalDeviceFeatures const& features, uint32_t graphicsQueueFamilyIndex,
ExtensionSet const& deviceExtensions) {
VkPhysicalDeviceFeatures2 const& features, uint32_t graphicsQueueFamilyIndex,
uint32_t protectedGraphicsQueueFamilyIndex, ExtensionSet const&
deviceExtensions) {
VkDevice device;
VkDeviceQueueCreateInfo deviceQueueCreateInfo[1] = {};
VkDeviceQueueCreateInfo deviceQueueCreateInfo[2] = {};
const float queuePriority[] = {1.0f};
VkDeviceCreateInfo deviceCreateInfo = {};
FixedCapacityVector<const char*> requestExtensions;
@@ -314,22 +316,29 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
for (auto const& ext: deviceExtensions) {
requestExtensions.push_back(ext.data());
}
deviceQueueCreateInfo->sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
deviceQueueCreateInfo->queueFamilyIndex = graphicsQueueFamilyIndex;
deviceQueueCreateInfo->queueCount = 1;
deviceQueueCreateInfo->pQueuePriorities = &queuePriority[0];
deviceQueueCreateInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
deviceQueueCreateInfo[0].queueFamilyIndex = graphicsQueueFamilyIndex;
deviceQueueCreateInfo[0].queueCount = 1;
deviceQueueCreateInfo[0].pQueuePriorities = &queuePriority[0];
// Protected queue
deviceQueueCreateInfo[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
deviceQueueCreateInfo[1].flags = VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT;
deviceQueueCreateInfo[1].queueFamilyIndex = protectedGraphicsQueueFamilyIndex;
deviceQueueCreateInfo[1].queueCount = 1;
deviceQueueCreateInfo[1].pQueuePriorities = &queuePriority[0];
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
deviceCreateInfo.queueCreateInfoCount = 1;
deviceCreateInfo.queueCreateInfoCount = protectedGraphicsQueueFamilyIndex == INVALID_VK_INDEX ? 1 : 2;
deviceCreateInfo.pQueueCreateInfos = deviceQueueCreateInfo;
// We could simply enable all supported features, but since that may have performance
// consequences let's just enable the features we need.
VkPhysicalDeviceFeatures enabledFeatures{
.depthClamp = features.depthClamp,
.samplerAnisotropy = features.samplerAnisotropy,
.textureCompressionETC2 = features.textureCompressionETC2,
.textureCompressionBC = features.textureCompressionBC,
.shaderClipDistance = features.shaderClipDistance,
.depthClamp = features.features.depthClamp,
.samplerAnisotropy = features.features.samplerAnisotropy,
.textureCompressionETC2 = features.features.textureCompressionETC2,
.textureCompressionBC = features.features.textureCompressionBC,
.shaderClipDistance = features.features.shaderClipDistance,
};
deviceCreateInfo.pEnabledFeatures = &enabledFeatures;
@@ -360,8 +369,20 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
pNext = &multiview;
}
VkPhysicalDeviceProtectedMemoryFeatures protectedMemory = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES,
};
if (protectedGraphicsQueueFamilyIndex != INVALID_VK_INDEX) {
// Enable protected memory, if requested.
protectedMemory.protectedMemory = VK_TRUE;
protectedMemory.pNext = pNext;
pNext = &protectedMemory;
}
deviceCreateInfo.pNext = pNext;
VkResult result = vkCreateDevice(physicalDevice, &deviceCreateInfo, VKALLOC, &device);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "vkCreateDevice error=" << result << ".";
@@ -406,13 +427,13 @@ FixedCapacityVector<VkQueueFamilyProperties> getPhysicalDeviceQueueFamilyPropert
return queueFamiliesProperties;
}
uint32_t identifyGraphicsQueueFamilyIndex(VkPhysicalDevice physicalDevice) {
uint32_t identifyGraphicsQueueFamilyIndex(VkPhysicalDevice physicalDevice, VkQueueFlags flags = VK_QUEUE_GRAPHICS_BIT) {
const FixedCapacityVector<VkQueueFamilyProperties> queueFamiliesProperties
= getPhysicalDeviceQueueFamilyPropertiesHelper(physicalDevice);
uint32_t graphicsQueueFamilyIndex = INVALID_VK_INDEX;
for (uint32_t j = 0; j < queueFamiliesProperties.size(); ++j) {
VkQueueFamilyProperties props = queueFamiliesProperties[j];
if (props.queueCount != 0 && props.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
if (props.queueCount != 0 && props.queueFlags & flags) {
graphicsQueueFamilyIndex = j;
break;
}
@@ -541,12 +562,16 @@ VkFormatList findAttachmentDepthStencilFormats(VkPhysicalDevice device) {
VK_FORMAT_D32_SFLOAT_S8_UINT,
VK_FORMAT_D24_UNORM_S8_UINT,
// VK_FORMAT_D16_UNORM,
// VK_FORMAT_D16_UNORM_S8_UINT,
};
std::vector<VkFormat> selectedFormats;
for (VkFormat format: formats) {
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(device, format, &props);
if ((props.optimalTilingFeatures & features) == features) {
if ((props.optimalTilingFeatures & features) == features ||
(props.linearTilingFeatures & features) == features) {
selectedFormats.push_back(format);
}
}
@@ -584,6 +609,9 @@ struct VulkanPlatformPrivate {
uint32_t mGraphicsQueueFamilyIndex = INVALID_VK_INDEX;
uint32_t mGraphicsQueueIndex = INVALID_VK_INDEX;
VkQueue mGraphicsQueue = VK_NULL_HANDLE;
uint32_t mProtectedGraphicsQueueFamilyIndex = INVALID_VK_INDEX;
uint32_t mProtectedGraphicsQueueIndex = INVALID_VK_INDEX;
VkQueue mProtectedGraphicsQueue = VK_NULL_HANDLE;
VulkanContext mContext = {};
// We use a map to both map a handle (i.e. SwapChainPtr) to the concrete type and also to
@@ -686,10 +714,24 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
printDeviceInfo(mImpl->mInstance, mImpl->mPhysicalDevice);
VkPhysicalDeviceProtectedMemoryFeatures queryProtectedMemoryFeatures = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES,
};
// Chain to the pNext linked list
queryProtectedMemoryFeatures.pNext = context.mPhysicalDeviceFeatures.pNext;
context.mPhysicalDeviceFeatures.pNext = &queryProtectedMemoryFeatures;
VkPhysicalDeviceProtectedMemoryProperties protectedMemoryProperties = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES,
};
protectedMemoryProperties.pNext = context.mPhysicalDeviceProperties.pNext;
context.mPhysicalDeviceProperties.pNext = &protectedMemoryProperties;
// Initialize the following fields: physicalDeviceProperties, memoryProperties,
// physicalDeviceFeatures, graphicsQueueFamilyIndex.
vkGetPhysicalDeviceProperties(mImpl->mPhysicalDevice, &context.mPhysicalDeviceProperties);
vkGetPhysicalDeviceFeatures(mImpl->mPhysicalDevice, &context.mPhysicalDeviceFeatures);
vkGetPhysicalDeviceProperties2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceProperties);
vkGetPhysicalDeviceFeatures2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceFeatures);
vkGetPhysicalDeviceMemoryProperties(mImpl->mPhysicalDevice, &context.mMemoryProperties);
mImpl->mGraphicsQueueFamilyIndex
@@ -698,10 +740,20 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
: mImpl->mGraphicsQueueFamilyIndex;
assert_invariant(mImpl->mGraphicsQueueFamilyIndex != INVALID_VK_INDEX);
// We know we need to allocate the protected version of the VK objects
context.mProtectedMemorySupported = static_cast<bool>(queryProtectedMemoryFeatures.protectedMemory);
if (context.mProtectedMemorySupported) {
mImpl->mProtectedGraphicsQueueFamilyIndex
= mImpl->mProtectedGraphicsQueueFamilyIndex == INVALID_VK_INDEX
? identifyGraphicsQueueFamilyIndex(mImpl->mPhysicalDevice, (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_PROTECTED_BIT))
: mImpl->mProtectedGraphicsQueueFamilyIndex;
assert_invariant(mImpl->mProtectedGraphicsQueueFamilyIndex != INVALID_VK_INDEX);
}
// Only enable shaderClipDistance if we are doing instanced stereoscopic rendering.
if (context.mPhysicalDeviceFeatures.shaderClipDistance == VK_TRUE
if (context.mPhysicalDeviceFeatures.features.shaderClipDistance == VK_TRUE
&& driverConfig.stereoscopicType != StereoscopicType::INSTANCED) {
context.mPhysicalDeviceFeatures.shaderClipDistance = VK_FALSE;
context.mPhysicalDeviceFeatures.features.shaderClipDistance = VK_FALSE;
}
// At this point, we should have a family index that points to a family that has > 0 queues for
@@ -711,6 +763,10 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
mImpl->mGraphicsQueueIndex
= mImpl->mGraphicsQueueIndex == INVALID_VK_INDEX ? 0 : mImpl->mGraphicsQueueIndex;
// Applying the same logic to the protected queue index (Not sure about shared context and protection)
mImpl->mProtectedGraphicsQueueIndex
= mImpl->mProtectedGraphicsQueueIndex == INVALID_VK_INDEX ? 0 : mImpl->mProtectedGraphicsQueueIndex;
ExtensionSet deviceExts;
// If using a shared context, we do not assume any extensions.
if (!mImpl->mSharedContext) {
@@ -727,14 +783,23 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
mImpl->mDevice
= mImpl->mDevice == VK_NULL_HANDLE ? createLogicalDevice(mImpl->mPhysicalDevice,
context.mPhysicalDeviceFeatures, mImpl->mGraphicsQueueFamilyIndex, deviceExts)
: mImpl->mDevice;
context.mPhysicalDeviceFeatures, mImpl->mGraphicsQueueFamilyIndex,
mImpl->mProtectedGraphicsQueueFamilyIndex, deviceExts) : mImpl->mDevice;
assert_invariant(mImpl->mDevice != VK_NULL_HANDLE);
vkGetDeviceQueue(mImpl->mDevice, mImpl->mGraphicsQueueFamilyIndex, mImpl->mGraphicsQueueIndex,
&mImpl->mGraphicsQueue);
assert_invariant(mImpl->mGraphicsQueue != VK_NULL_HANDLE);
if (context.mProtectedMemorySupported) {
VkDeviceQueueInfo2 info = {};
info.queueFamilyIndex = mImpl->mProtectedGraphicsQueueFamilyIndex;
info.queueIndex = mImpl->mProtectedGraphicsQueueIndex;
info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2;
info.flags = VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT;
vkGetDeviceQueue2(mImpl->mDevice, &info, &mImpl->mProtectedGraphicsQueue);
}
// Store the extension support in the context
context.mDebugUtilsSupported = setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
@@ -800,6 +865,10 @@ bool VulkanPlatform::hasResized(SwapChainPtr handle) {
SWAPCHAIN_RET_FUNC(hasResized, handle, )
}
bool VulkanPlatform::isProtected(SwapChainPtr handle) {
SWAPCHAIN_RET_FUNC(isProtected, handle, )
}
VkResult VulkanPlatform::recreate(SwapChainPtr handle) {
SWAPCHAIN_RET_FUNC(recreate, handle, )
}
@@ -835,6 +904,16 @@ SwapChainPtr VulkanPlatform::createSwapChain(void* nativeWindow, uint64_t flags,
mImpl->mPhysicalDevice, mImpl->mDevice, mImpl->mGraphicsQueue, mImpl->mInstance,
surface, fallbackExtent, flags);
mImpl->mSurfaceSwapChains.insert(swapchain);
if (flags & backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
if (!mImpl->mContext.mProtectedMemorySupported) {
utils::slog.w << "protected swapchain requested, but VulkanPlatform does not support it"
<< utils::io::endl;
}
else {
}
}
return swapchain;
}
@@ -862,6 +941,18 @@ VkQueue VulkanPlatform::getGraphicsQueue() const noexcept {
return mImpl->mGraphicsQueue;
}
uint32_t VulkanPlatform::getProtectedGraphicsQueueFamilyIndex() const noexcept {
return mImpl->mProtectedGraphicsQueueFamilyIndex;
}
uint32_t VulkanPlatform::getProtectedGraphicsQueueIndex() const noexcept {
return mImpl->mProtectedGraphicsQueueIndex;
}
VkQueue VulkanPlatform::getProtectedGraphicsQueue() const noexcept {
return mImpl->mProtectedGraphicsQueue;
}
#undef SWAPCHAIN_RET_FUNC
}// namespace filament::backend

View File

@@ -195,15 +195,6 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWin
}
#endif
#elif defined(WIN32)
// On (at least) NVIDIA drivers, the Vulkan implementation (specifically the call to
// vkGetPhysicalDeviceSurfaceCapabilitiesKHR()) does not correctly handle the fact that
// each native window has its own DPI_AWARENESS_CONTEXT, and erroneously uses the context
// of the calling thread. As a workaround, we set the current thread's DPI_AWARENESS_CONTEXT
// to that of the native window we've been given. This isn't a perfect solution, because an
// application could create swap chains on multiple native windows with varying DPI-awareness,
// but even then, at least one of the windows would be guaranteed to work correctly.
SetThreadDpiAwarenessContext(GetWindowDpiAwarenessContext((HWND) nativeWindow));
VkWin32SurfaceCreateInfoKHR const createInfo = {
.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
.hinstance = GetModuleHandle(nullptr),

View File

@@ -28,13 +28,96 @@ namespace filament::backend {
namespace {
// std::tuple<VkImage, VkDeviceMemory> testing(VulkanContext const& context,
// VkDevice device, VkExtent2D extent) {
// // Filament expects blit() to work with any texture, so we almost always set these usage flags
// // (see copyFrame() and readPixels()).
// // The ordering here indicates the preference of choosing depth+stencil format.
// VkFormat const formats[] = {
// VK_FORMAT_D32_SFLOAT,
// VK_FORMAT_X8_D24_UNORM_PACK32,
// VK_FORMAT_D32_SFLOAT_S8_UINT,
// VK_FORMAT_D24_UNORM_S8_UINT,
// VK_FORMAT_D16_UNORM,
// VK_FORMAT_D16_UNORM_S8_UINT,
// VK_FORMAT_R8G8B8A8_SSCALED,
// VK_FORMAT_R8G8B8A8_UINT,
// VK_FORMAT_R8G8B8A8_SINT,
// VK_FORMAT_R8G8B8A8_SRGB,
// VK_FORMAT_B8G8R8A8_UNORM,
// VK_FORMAT_B8G8R8A8_SNORM,
// };
// uint32_t memoryTypeIndex = VK_MAX_MEMORY_TYPES;
// for (auto tiling: {VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_TILING_LINEAR}) {
// for (auto format: formats) {
// VkImageCreateInfo imageInfo{
// .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
// .imageType = VK_IMAGE_TYPE_2D,
// .format = format,
// .extent = {extent.width, extent.height, 1},
// .mipLevels = 1,
// .arrayLayers = 1,
// .samples = VK_SAMPLE_COUNT_1_BIT,
// .tiling = tiling,
// .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
// };
// VkImage image;
// VkResult result = vkCreateImage(device, &imageInfo, VKALLOC, &image);
// FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
// << "Unable to create image: " << static_cast<int32_t>(result);
// // Allocate memory for the VkImage and bind it.
// // VkDeviceMemory imageMemory;
// VkMemoryRequirements memReqs;
// vkGetImageMemoryRequirements(device, image, &memReqs);
// utils::slog.e << "tiling=" << tiling << utils::io::endl;
// utils::slog.e << "choosing format=" << format << utils::io::endl;
// utils::slog.e << "required bits=" << memReqs.memoryTypeBits << utils::io::endl;
// memoryTypeIndex = context.selectMemoryType(memReqs.memoryTypeBits,
// VK_MEMORY_PROPERTY_PROTECTED_BIT);
// if (memoryTypeIndex != VK_MAX_MEMORY_TYPES) {
// utils::slog.e << "found!" << utils::io::endl;
// break;
// }
// if (memoryTypeIndex != VK_MAX_MEMORY_TYPES) {
// break;
// }
// }
// }
// // FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES)
// // << "VulkanPlatformSwapChainImpl: unable to find a memory type that meets requirements.";
// // }
// // VkMemoryAllocateInfo allocInfo = {
// // .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
// // .allocationSize = memReqs.size,
// // .memoryTypeIndex = memoryTypeIndex,
// // };
// // result = vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory);
// // FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to allocate image memory.";
// // result = vkBindImageMemory(device, image, imageMemory, 0);
// // FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to bind image.";
// return std::tuple(VK_NULL_HANDLE, VK_NULL_HANDLE);
// // return std::tuple(image, imageMemory);
// }
std::tuple<VkImage, VkDeviceMemory> createImageAndMemory(VulkanContext const& context,
VkDevice device, VkExtent2D extent, VkFormat format) {
VkDevice device, VkExtent2D extent, VkFormat format, bool isProtected) {
bool const isDepth = isVkDepthFormat(format);
// if (isDepth) {
// return testing(context, device, extent);
// }
// Filament expects blit() to work with any texture, so we almost always set these usage flags
// (see copyFrame() and readPixels()).
VkImageUsageFlags const blittable =
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
(isProtected ? 0u
: (VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT));
VkImageCreateInfo imageInfo {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
@@ -58,16 +141,19 @@ std::tuple<VkImage, VkDeviceMemory> createImageAndMemory(VulkanContext const& co
VkMemoryRequirements memReqs;
vkGetImageMemoryRequirements(device, image, &memReqs);
uint32_t memoryTypeIndex
= context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
// uint32_t const memoryTypeIndex = context.selectMemoryType(memReqs.memoryTypeBits,
// (isProtected ? VK_MEMORY_PROPERTY_PROTECTED_BIT : VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT));
uint32_t const memoryTypeIndex =
context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
// (isProtected ? VK_MEMORY_PROPERTY_PROTECTED_BIT :
FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES)
<< "VulkanPlatformSwapChainImpl: unable to find a memory type that meets requirements.";
VkMemoryAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memReqs.size,
.memoryTypeIndex = memoryTypeIndex,
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memReqs.size,
.memoryTypeIndex = memoryTypeIndex,
};
result = vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to allocate image memory.";
@@ -77,11 +163,19 @@ std::tuple<VkImage, VkDeviceMemory> createImageAndMemory(VulkanContext const& co
}
VkFormat selectDepthFormat(VkFormatList const& depthFormats, bool hasStencil) {
for (auto f: depthFormats) {
utils::slog.e <<"formats=" << f << utils::io::endl;
}
/*
auto const formatItr = std::find_if(depthFormats.begin(), depthFormats.end(),
hasStencil ? isVkStencilFormat : isVkDepthFormat);
assert_invariant(
formatItr != depthFormats.end() && "Cannot find suitable swapchain depth format");
return *formatItr;
*/
// return VK_FORMAT_D16_UNORM_S8_UINT;
return VK_FORMAT_D16_UNORM;
// return *formatItr;
}
}// anonymous namespace
@@ -110,8 +204,9 @@ void VulkanPlatformSwapChainImpl::destroy() {
mSwapChainBundle.colors.clear();
}
VkImage VulkanPlatformSwapChainImpl::createImage(VkExtent2D extent, VkFormat format) {
auto [image, memory] = createImageAndMemory(mContext, mDevice, extent, format);
VkImage VulkanPlatformSwapChainImpl::createImage(VkExtent2D extent, VkFormat format,
bool isProtected) {
auto [image, memory] = createImageAndMemory(mContext, mDevice, extent, format, isProtected);
mMemory.insert({image, memory});
return image;
}
@@ -125,7 +220,8 @@ VulkanPlatformSurfaceSwapChain::VulkanPlatformSurfaceSwapChain(VulkanContext con
mSurface(surface),
mFallbackExtent(fallbackExtent),
mUsesRGB((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0),
mHasStencil((flags & backend::SWAP_CHAIN_HAS_STENCIL_BUFFER) != 0) {
mHasStencil((flags & backend::SWAP_CHAIN_HAS_STENCIL_BUFFER) != 0),
mIsProtected((flags & backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) != 0) {
assert_invariant(surface);
create();
}
@@ -138,16 +234,32 @@ VulkanPlatformSurfaceSwapChain::~VulkanPlatformSurfaceSwapChain() {
VkResult VulkanPlatformSurfaceSwapChain::create() {
VkSurfaceFormatKHR surfaceFormat = {};
VkSurfaceCapabilitiesKHR caps;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDevice, mSurface, &caps);
VkSurfaceProtectedCapabilitiesKHR protectedCaps = {
.sType = VK_STRUCTURE_TYPE_SURFACE_PROTECTED_CAPABILITIES_KHR,
};
VkSurfaceCapabilities2KHR caps = {
.sType = VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR,
.pNext = &protectedCaps,
};
VkPhysicalDeviceSurfaceInfo2KHR info = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR,
.surface = mSurface,
};
vkGetPhysicalDeviceSurfaceCapabilities2KHR(mPhysicalDevice, &info, &caps);
FILAMENT_CHECK_PRECONDITION(!mIsProtected || protectedCaps.supportsProtected == VK_TRUE)
<< "Requesting protected swapchain but is not supported by the hardware";
utils::slog.e << "------ protected surface=" << (protectedCaps.supportsProtected == VK_TRUE)
<< utils::io::endl;
// The general advice is to require one more than the minimum swap chain length, since the
// absolute minimum could easily require waiting for a driver or presentation layer to release
// the previous frame's buffer. The only situation in which we'd ask for the minimum length is
// when using a MAILBOX presentation strategy for low-latency situations where tearing is
// acceptable.
uint32_t const maxImageCount = caps.maxImageCount;
uint32_t const minImageCount = caps.minImageCount;
uint32_t const maxImageCount = caps.surfaceCapabilities.maxImageCount;
uint32_t const minImageCount = caps.surfaceCapabilities.minImageCount;
uint32_t desiredImageCount = minImageCount + 1;
// According to section 30.5 of VK 1.1, maxImageCount of zero means "that there is no limit on
@@ -156,7 +268,7 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
if (maxImageCount != 0 && desiredImageCount > maxImageCount) {
FVK_LOGE << "Swap chain does not support " << desiredImageCount << " images."
<< utils::io::endl;
desiredImageCount = caps.minImageCount;
desiredImageCount = caps.surfaceCapabilities.minImageCount;
}
// Find a suitable surface format.
@@ -198,44 +310,50 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
<< "Desired present mode is not supported by this device.";
// Create the low-level swap chain.
if (caps.currentExtent.width == VULKAN_UNDEFINED_EXTENT
|| caps.currentExtent.height == VULKAN_UNDEFINED_EXTENT) {
auto const& currentExtent = caps.surfaceCapabilities.currentExtent;
if (currentExtent.width == VULKAN_UNDEFINED_EXTENT
|| currentExtent.height == VULKAN_UNDEFINED_EXTENT) {
mSwapChainBundle.extent = mFallbackExtent;
} else {
mSwapChainBundle.extent = caps.currentExtent;
mSwapChainBundle.extent = currentExtent;
}
VkCompositeAlphaFlagBitsKHR const compositeAlpha
= (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
? VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
: VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
VkCompositeAlphaFlagBitsKHR const compositeAlpha =
(caps.surfaceCapabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
? VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
: VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
VkImageUsageFlags const transferBits =
(mIsProtected ? VkImageUsageFlagBits{}
: (VK_IMAGE_USAGE_TRANSFER_DST_BIT // Allows use as a blit destination
// (for copyFrame)
| VK_IMAGE_USAGE_TRANSFER_SRC_BIT // Allows use as a blit source (for
// readPixels)
));
VkSwapchainCreateInfoKHR const createInfo{
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.surface = mSurface,
.minImageCount = desiredImageCount,
.imageFormat = surfaceFormat.format,
.imageColorSpace = surfaceFormat.colorSpace,
.imageExtent = mSwapChainBundle.extent,
.imageArrayLayers = 1,
.imageUsage
= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
| VK_IMAGE_USAGE_TRANSFER_DST_BIT // Allows use as a blit destination (for copyFrame)
| VK_IMAGE_USAGE_TRANSFER_SRC_BIT,// Allows use as a blit source (for readPixels)
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.flags = mIsProtected ? VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR : VkSwapchainCreateFlagsKHR{},
.surface = mSurface,
.minImageCount = desiredImageCount,
.imageFormat = surfaceFormat.format,
.imageColorSpace = surfaceFormat.colorSpace,
.imageExtent = mSwapChainBundle.extent,
.imageArrayLayers = 1,
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | transferBits,
// TODO: Setting the preTransform to IDENTITY means we are letting the Android
// Compositor handle the rotation. In some situations it might be more efficient to
// handle this ourselves by setting this field to be equal to the currentTransform mask
// in the caps, but this would involve adjusting the MVP, derivatives in GLSL, and
// possibly more.
// https://android-developers.googleblog.com/2020/02/handling-device-orientation-efficiently.html
.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
// TODO: Setting the preTransform to IDENTITY means we are letting the
// Android Compositor handle the rotation. In some situations it might be
// more efficient to handle this ourselves by setting this field to be equal
// to the currentTransform mask in the caps, but this would involve
// adjusting the MVP, derivatives in GLSL, and possibly more.
// https://android-developers.googleblog.com/2020/02/handling-device-orientation-efficiently.html
.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
.compositeAlpha = compositeAlpha,
.presentMode = desiredPresentMode,
.clipped = VK_TRUE,
.compositeAlpha = compositeAlpha,
.presentMode = desiredPresentMode,
.clipped = VK_TRUE,
.oldSwapchain = mSwapchain,
.oldSwapchain = mSwapchain,
};
VkResult result = vkCreateSwapchainKHR(mDevice, &createInfo, VKALLOC, &mSwapchain);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
@@ -245,13 +363,15 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
mSwapChainBundle.colorFormat = surfaceFormat.format;
mSwapChainBundle.depthFormat =
selectDepthFormat(mContext.getAttachmentDepthStencilFormats(), mHasStencil);
mSwapChainBundle.depth = createImage(mSwapChainBundle.extent, mSwapChainBundle.depthFormat);
mSwapChainBundle.depth =
createImage(mSwapChainBundle.extent, mSwapChainBundle.depthFormat, mIsProtected);
mSwapChainBundle.protection = mIsProtected;
FVK_LOGI << "vkCreateSwapchain"
<< ": " << mSwapChainBundle.extent.width << "x" << mSwapChainBundle.extent.height << ", "
<< surfaceFormat.format << ", " << surfaceFormat.colorSpace << ", "
<< "swapchain-size=" << mSwapChainBundle.colors.size() << ", "
<< "identity-transform=" << (caps.currentTransform == 1) << ", "
<< "identity-transform=" << (caps.surfaceCapabilities.currentTransform == 1) << ", "
<< "depth=" << mSwapChainBundle.depthFormat
<< io::endl;
@@ -317,6 +437,10 @@ bool VulkanPlatformSurfaceSwapChain::hasResized() {
return !equivalent(mSwapChainBundle.extent, perceivedExtent);
}
bool VulkanPlatformSurfaceSwapChain::isProtected() {
return mIsProtected;
}
// Non-virtual override
VkResult VulkanPlatformSurfaceSwapChain::recreate() {
destroy();
@@ -338,6 +462,10 @@ VulkanPlatformHeadlessSwapChain::VulkanPlatformHeadlessSwapChain(VulkanContext c
VkDevice device, VkQueue queue, VkExtent2D extent, uint64_t flags)
: VulkanPlatformSwapChainImpl(context, device, queue),
mCurrentIndex(0) {
assert_invariant(!(flags & backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) &&
"Protected headless swapchain not supported");
mSwapChainBundle.extent = extent;
mSwapChainBundle.colorFormat = (flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0
? VK_FORMAT_R8G8B8A8_SRGB
@@ -347,13 +475,13 @@ VulkanPlatformHeadlessSwapChain::VulkanPlatformHeadlessSwapChain(VulkanContext c
images.reserve(HEADLESS_SWAPCHAIN_SIZE);
images.resize(HEADLESS_SWAPCHAIN_SIZE);
for (size_t i = 0; i < HEADLESS_SWAPCHAIN_SIZE; ++i) {
images[i] = createImage(extent, mSwapChainBundle.colorFormat);
images[i] = createImage(extent, mSwapChainBundle.colorFormat, false);
}
bool const hasStencil = (flags & backend::SWAP_CHAIN_HAS_STENCIL_BUFFER) != 0;
mSwapChainBundle.depthFormat =
selectDepthFormat(mContext.getAttachmentDepthStencilFormats(), hasStencil);
mSwapChainBundle.depth = createImage(extent, mSwapChainBundle.depthFormat);
mSwapChainBundle.depth = createImage(extent, mSwapChainBundle.depthFormat, false);
}
VulkanPlatformHeadlessSwapChain::~VulkanPlatformHeadlessSwapChain() {

View File

@@ -64,11 +64,16 @@ struct VulkanPlatformSwapChainImpl : public Platform::SwapChain {
return false;
}
// Non-virtual override-able method
bool isProtected() {
return false;
}
protected:
// Non-virtual override-able method
void destroy();
VkImage createImage(VkExtent2D extent, VkFormat format);
VkImage createImage(VkExtent2D extent, VkFormat format, bool isProtected);
VulkanContext const& mContext;
VkDevice mDevice;
@@ -97,6 +102,9 @@ struct VulkanPlatformSurfaceSwapChain : public VulkanPlatformSwapChainImpl {
// Non-virtual override-able method
bool hasResized();
// Non-virtual override-able method
bool isProtected();
protected:
// Non-virtual override-able method
void destroy();
@@ -117,6 +125,7 @@ private:
bool mUsesRGB = false;
bool mHasStencil = false;
bool mIsProtected = false;
bool mSuboptimal;
};

View File

@@ -280,13 +280,14 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN
SYSTRACE_CALL();
#if 0 && defined(__ANDROID__)
char scratch[PROP_VALUE_MAX + 1];
int length = __system_property_get("debug.filament.protected", scratch);
if (swapChain && length > 0) {
#if 1 && defined(__ANDROID__)
// char scratch[PROP_VALUE_MAX + 1];
// int length = __system_property_get("debug.filament.protected", scratch);
// if (swapChain && length > 0) {
if (swapChain) {
uint64_t flags = swapChain->getFlags();
bool value = bool(atoi(scratch));
if (value) {
// bool value = bool(atoi(scratch));
if (true) {
flags |= SwapChain::CONFIG_PROTECTED_CONTENT;
} else {
flags &= ~SwapChain::CONFIG_PROTECTED_CONTENT;