/* ** Vulkan backend ** Copyright (c) 2016-2020 Magnus Norddahl ** ** This software is provided 'as-is', without any express or implied ** warranty. In no event will the authors be held liable for any damages ** arising from the use of this software. ** ** Permission is granted to anyone to use this software for any purpose, ** including commercial applications, and to alter it and redistribute it ** freely, subject to the following restrictions: ** ** 1. The origin of this software must not be misrepresented; you must not ** claim that you wrote the original software. If you use this software ** in a product, an acknowledgment in the product documentation would be ** appreciated but is not required. ** 2. Altered source versions must be plainly marked as such, and must not be ** misrepresented as being the original software. ** 3. This notice may not be removed or altered from any source distribution. ** */ #include "vk_commandbuffer.h" #include "vk_framebuffer.h" #include "vk_swapchain.h" #include "vk_builders.h" #include "vulkan/renderer/vk_renderstate.h" #include "vulkan/renderer/vk_postprocess.h" #include "hw_clock.h" #include "v_video.h" extern int rendered_commandbuffers; int current_rendered_commandbuffers; extern bool gpuStatActive; extern bool keepGpuStatActive; extern FString gpuStatOutput; VkCommandBufferManager::VkCommandBufferManager(VulkanFrameBuffer* fb) : fb(fb) { mCommandPool.reset(new VulkanCommandPool(fb->device, fb->device->graphicsFamily)); swapChain = std::make_unique(fb->device); mSwapChainImageAvailableSemaphore.reset(new VulkanSemaphore(fb->device)); mRenderFinishedSemaphore.reset(new VulkanSemaphore(fb->device)); for (auto& semaphore : mSubmitSemaphore) semaphore.reset(new VulkanSemaphore(fb->device)); for (auto& fence : mSubmitFence) fence.reset(new VulkanFence(fb->device)); for (int i = 0; i < maxConcurrentSubmitCount; i++) mSubmitWaitFences[i] = mSubmitFence[i]->fence; if (fb->device->graphicsTimeQueries) { mTimestampQueryPool = QueryPoolBuilder() .QueryType(VK_QUERY_TYPE_TIMESTAMP, MaxTimestampQueries) .Create(fb->device); GetDrawCommands()->resetQueryPool(mTimestampQueryPool.get(), 0, MaxTimestampQueries); } } VkCommandBufferManager::~VkCommandBufferManager() { } VulkanCommandBuffer* VkCommandBufferManager::GetTransferCommands() { if (!mTransferCommands) { mTransferCommands = mCommandPool->createBuffer(); mTransferCommands->SetDebugName("VulkanFrameBuffer.mTransferCommands"); mTransferCommands->begin(); } return mTransferCommands.get(); } VulkanCommandBuffer* VkCommandBufferManager::GetDrawCommands() { if (!mDrawCommands) { mDrawCommands = mCommandPool->createBuffer(); mDrawCommands->SetDebugName("VulkanFrameBuffer.mDrawCommands"); mDrawCommands->begin(); } return mDrawCommands.get(); } void VkCommandBufferManager::BeginFrame() { if (mNextTimestampQuery > 0) { GetDrawCommands()->resetQueryPool(mTimestampQueryPool.get(), 0, mNextTimestampQuery); mNextTimestampQuery = 0; } } void VkCommandBufferManager::FlushCommands(VulkanCommandBuffer** commands, size_t count, bool finish, bool lastsubmit) { int currentIndex = mNextSubmit % maxConcurrentSubmitCount; if (mNextSubmit >= maxConcurrentSubmitCount) { vkWaitForFences(fb->device->device, 1, &mSubmitFence[currentIndex]->fence, VK_TRUE, std::numeric_limits::max()); vkResetFences(fb->device->device, 1, &mSubmitFence[currentIndex]->fence); } QueueSubmit submit; for (size_t i = 0; i < count; i++) submit.AddCommandBuffer(commands[i]); if (mNextSubmit > 0) submit.AddWait(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, mSubmitSemaphore[(mNextSubmit - 1) % maxConcurrentSubmitCount].get()); if (finish && presentImageIndex != 0xffffffff) { submit.AddWait(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, mSwapChainImageAvailableSemaphore.get()); submit.AddSignal(mRenderFinishedSemaphore.get()); } if (!lastsubmit) submit.AddSignal(mSubmitSemaphore[currentIndex].get()); submit.Execute(fb->device, fb->device->graphicsQueue, mSubmitFence[currentIndex].get()); mNextSubmit++; } void VkCommandBufferManager::FlushCommands(bool finish, bool lastsubmit, bool uploadOnly) { if (!uploadOnly) fb->GetRenderState()->EndRenderPass(); if ((!uploadOnly && mDrawCommands) || mTransferCommands) { VulkanCommandBuffer* commands[2]; size_t count = 0; if (mTransferCommands) { mTransferCommands->end(); commands[count++] = mTransferCommands.get(); TransferDeleteList->Add(std::move(mTransferCommands)); } if (!uploadOnly && mDrawCommands) { mDrawCommands->end(); commands[count++] = mDrawCommands.get(); DrawDeleteList->Add(std::move(mDrawCommands)); } FlushCommands(commands, count, finish, lastsubmit); current_rendered_commandbuffers += (int)count; } } void VkCommandBufferManager::WaitForCommands(bool finish, bool uploadOnly) { if (finish) { Finish.Reset(); Finish.Clock(); presentImageIndex = swapChain->AcquireImage(fb->GetClientWidth(), fb->GetClientHeight(), fb->GetVSync(), mSwapChainImageAvailableSemaphore.get()); if (presentImageIndex != 0xffffffff) fb->GetPostprocess()->DrawPresentTexture(fb->mOutputLetterbox, true, false); } FlushCommands(finish, true, uploadOnly); if (finish) { fb->FPSLimit(); if (presentImageIndex != 0xffffffff) swapChain->QueuePresent(presentImageIndex, mRenderFinishedSemaphore.get()); } int numWaitFences = min(mNextSubmit, (int)maxConcurrentSubmitCount); if (numWaitFences > 0) { vkWaitForFences(fb->device->device, numWaitFences, mSubmitWaitFences, VK_TRUE, std::numeric_limits::max()); vkResetFences(fb->device->device, numWaitFences, mSubmitWaitFences); } DeleteFrameObjects(uploadOnly); mNextSubmit = 0; if (finish) { Finish.Unclock(); rendered_commandbuffers = current_rendered_commandbuffers; current_rendered_commandbuffers = 0; } } void VkCommandBufferManager::DeleteFrameObjects(bool uploadOnly) { TransferDeleteList = std::make_unique(); if (!uploadOnly) DrawDeleteList = std::make_unique(); } void VkCommandBufferManager::PushGroup(const FString& name) { if (!gpuStatActive) return; if (mNextTimestampQuery < MaxTimestampQueries && fb->device->graphicsTimeQueries) { TimestampQuery q; q.name = name; q.startIndex = mNextTimestampQuery++; q.endIndex = 0; GetDrawCommands()->writeTimestamp(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, mTimestampQueryPool.get(), q.startIndex); mGroupStack.push_back(timeElapsedQueries.size()); timeElapsedQueries.push_back(q); } } void VkCommandBufferManager::PopGroup() { if (!gpuStatActive || mGroupStack.empty()) return; TimestampQuery& q = timeElapsedQueries[mGroupStack.back()]; mGroupStack.pop_back(); if (mNextTimestampQuery < MaxTimestampQueries && fb->device->graphicsTimeQueries) { q.endIndex = mNextTimestampQuery++; GetDrawCommands()->writeTimestamp(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, mTimestampQueryPool.get(), q.endIndex); } } void VkCommandBufferManager::UpdateGpuStats() { uint64_t timestamps[MaxTimestampQueries]; if (mNextTimestampQuery > 0) mTimestampQueryPool->getResults(0, mNextTimestampQuery, sizeof(uint64_t) * mNextTimestampQuery, timestamps, sizeof(uint64_t), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); double timestampPeriod = fb->device->PhysicalDevice.Properties.limits.timestampPeriod; gpuStatOutput = ""; for (auto& q : timeElapsedQueries) { if (q.endIndex <= q.startIndex) continue; int64_t timeElapsed = max(static_cast(timestamps[q.endIndex] - timestamps[q.startIndex]), (int64_t)0); double timeNS = timeElapsed * timestampPeriod; FString out; out.Format("%s=%04.2f ms\n", q.name.GetChars(), timeNS / 1000000.0f); gpuStatOutput += out; } timeElapsedQueries.clear(); mGroupStack.clear(); gpuStatActive = keepGpuStatActive; keepGpuStatActive = false; }