From ec5c14dee844b18dc545b52c4bd705ae20cba5d7 Mon Sep 17 00:00:00 2001 From: baldurk Date: Thu, 10 Feb 2022 16:36:19 +0000 Subject: [PATCH] Add support for multiple batched presents on vulkan. Closes #2492 --- renderdoc/driver/vulkan/vk_core.h | 3 + renderdoc/driver/vulkan/vk_serialise.cpp | 7 +- .../driver/vulkan/wrappers/vk_wsi_funcs.cpp | 195 +++++++++++++----- util/test/demos/CMakeLists.txt | 1 + util/test/demos/demos.vcxproj | 1 + util/test/demos/demos.vcxproj.filters | 3 + util/test/demos/linux/linux_window.cpp | 1 + util/test/demos/test_common.h | 3 + util/test/demos/vk/vk_multi_present.cpp | 175 ++++++++++++++++ util/test/demos/vk/vk_test.cpp | 56 +++++ util/test/demos/vk/vk_test.h | 5 +- util/test/demos/win32/win32_window.cpp | 2 +- 12 files changed, 393 insertions(+), 59 deletions(-) create mode 100644 util/test/demos/vk/vk_multi_present.cpp diff --git a/renderdoc/driver/vulkan/vk_core.h b/renderdoc/driver/vulkan/vk_core.h index 70acce052..b2e9f2b85 100644 --- a/renderdoc/driver/vulkan/vk_core.h +++ b/renderdoc/driver/vulkan/vk_core.h @@ -1777,6 +1777,9 @@ public: VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore, VkFence fence, uint32_t *pImageIndex); + void HandlePresent(VkQueue queue, const VkPresentInfoKHR *pPresentInfo, + rdcarray &unwrappedWaitSems); + IMPLEMENT_FUNCTION_SERIALISED(VkResult, vkQueuePresentKHR, VkQueue queue, const VkPresentInfoKHR *pPresentInfo); diff --git a/renderdoc/driver/vulkan/vk_serialise.cpp b/renderdoc/driver/vulkan/vk_serialise.cpp index 09a3a96e0..a78f622ec 100644 --- a/renderdoc/driver/vulkan/vk_serialise.cpp +++ b/renderdoc/driver/vulkan/vk_serialise.cpp @@ -3852,7 +3852,12 @@ void DoSerialise(SerialiserType &ser, VkPresentInfoKHR &el) SERIALISE_MEMBER_ARRAY(pWaitSemaphores, waitSemaphoreCount); SERIALISE_MEMBER(swapchainCount); - SERIALISE_MEMBER_ARRAY_EMPTY(pSwapchains); + + { + // swapchains aren't really serialised, just get their Ids here for info's sake + OPTIONAL_RESOURCES(); + SERIALISE_MEMBER_ARRAY(pSwapchains, swapchainCount).Important(); + } SERIALISE_MEMBER_ARRAY(pImageIndices, swapchainCount).Important(); SERIALISE_MEMBER_ARRAY(pResults, swapchainCount); } diff --git a/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp index ecf53f7d1..ce1dfcf9a 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_wsi_funcs.cpp @@ -706,11 +706,23 @@ bool WrappedVulkan::Serialise_vkQueuePresentKHR(SerialiserType &ser, VkQueue que if(ser.IsWriting()) { - VkResourceRecord *swaprecord = GetRecord(pPresentInfo->pSwapchains[0]); + // use the image from the active window, or the first valid image if we don't find an active + // window + for(uint32_t i = 0; i < pPresentInfo->swapchainCount; i++) + { + VkResourceRecord *swaprecord = GetRecord(pPresentInfo->pSwapchains[i]); - SwapchainInfo &swapInfo = *swaprecord->swapInfo; + SwapchainInfo &swapInfo = *swaprecord->swapInfo; - PresentedImage = GetResID(swapInfo.images[pPresentInfo->pImageIndices[0]].im); + const bool activeWindow = + RenderDoc::Inst().IsActiveWindow(LayerDisp(m_Instance), swapInfo.wndHandle); + + if(activeWindow || PresentedImage == ResourceId()) + PresentedImage = GetResID(swapInfo.images[pPresentInfo->pImageIndices[i]].im); + + if(activeWindow) + break; + } } // we don't have all the information we need about swapchains on replay to get the presented image @@ -742,23 +754,17 @@ VkResult WrappedVulkan::vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR { AdvanceFrame(); - if(pPresentInfo->swapchainCount > 1 && (m_FrameCounter % 100) == 0) - { - RDCWARN("Presenting multiple swapchains at once - only first will be processed"); - } - rdcarray unwrappedSwaps; - rdcarray unwrappedSems; + rdcarray unwrappedWaitSems; VkPresentInfoKHR unwrappedInfo = *pPresentInfo; for(uint32_t i = 0; i < unwrappedInfo.swapchainCount; i++) unwrappedSwaps.push_back(Unwrap(unwrappedInfo.pSwapchains[i])); for(uint32_t i = 0; i < unwrappedInfo.waitSemaphoreCount; i++) - unwrappedSems.push_back(Unwrap(unwrappedInfo.pWaitSemaphores[i])); + unwrappedWaitSems.push_back(Unwrap(unwrappedInfo.pWaitSemaphores[i])); unwrappedInfo.pSwapchains = unwrappedSwaps.data(); - unwrappedInfo.pWaitSemaphores = unwrappedSems.data(); // Don't support any extensions for present info const VkBaseInStructure *next = (const VkBaseInStructure *)pPresentInfo->pNext; @@ -778,8 +784,108 @@ VkResult WrappedVulkan::vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR next = next->pNext; } - // TODO support multiple swapchains here - VkResourceRecord *swaprecord = GetRecord(pPresentInfo->pSwapchains[0]); + if(IsBackgroundCapturing(m_State)) + GetResourceManager()->CleanBackgroundFrameReferences(); + + m_LastSwap = ResourceId(); + + if(pPresentInfo->swapchainCount == 1) + { + HandlePresent(queue, pPresentInfo, unwrappedWaitSems); + } + else + { + VkPresentInfoKHR mutableInfo = *pPresentInfo; + + { + byte *tempMem = GetTempMemory(GetNextPatchSize(mutableInfo.pNext)); + CopyNextChainForPatching("VkPresentInfoKHR", tempMem, (VkBaseInStructure *)&mutableInfo); + } + + mutableInfo.swapchainCount = 1; + + VkDeviceGroupPresentInfoKHR *groups = (VkDeviceGroupPresentInfoKHR *)FindNextStruct( + &mutableInfo, VK_STRUCTURE_TYPE_DEVICE_GROUP_PRESENT_INFO_KHR); + if(groups) + groups->swapchainCount = 1; + + VkPresentRegionsKHR *regions = + (VkPresentRegionsKHR *)FindNextStruct(&mutableInfo, VK_STRUCTURE_TYPE_PRESENT_REGIONS_KHR); + if(regions) + regions->swapchainCount = 1; + + VkPresentTimesInfoGOOGLE *times = (VkPresentTimesInfoGOOGLE *)FindNextStruct( + &mutableInfo, VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE); + if(times) + times->swapchainCount = 1; + + VkPresentIdKHR *ids = + (VkPresentIdKHR *)FindNextStruct(&mutableInfo, VK_STRUCTURE_TYPE_PRESENT_ID_KHR); + if(ids) + ids->swapchainCount = 1; + + for(uint32_t i = 0; i < pPresentInfo->swapchainCount; i++) + { + HandlePresent(queue, &mutableInfo, unwrappedWaitSems); + + mutableInfo.pSwapchains++; + mutableInfo.pImageIndices++; + mutableInfo.pResults++; + if(groups) + groups->pDeviceMasks++; + if(regions) + regions->pRegions++; + if(ids) + ids->pPresentIds++; + if(times) + times->pTimes++; + } + } + + unwrappedInfo.pWaitSemaphores = unwrappedWaitSems.data(); + unwrappedInfo.waitSemaphoreCount = (uint32_t)unwrappedWaitSems.size(); + + VkResult vkr; + SERIALISE_TIME_CALL(vkr = ObjDisp(queue)->QueuePresentKHR(Unwrap(queue), &unwrappedInfo)); + + if(IsActiveCapturing(m_State)) + { + CACHE_THREAD_SERIALISER(); + + SCOPED_SERIALISE_CHUNK(VulkanChunk::vkQueuePresentKHR); + Serialise_vkQueuePresentKHR(ser, queue, pPresentInfo); + + GetResourceManager()->MarkResourceFrameReferenced(GetResID(queue), eFrameRef_Read); + + m_FrameCaptureRecord->AddChunk(scope.Get()); + } + + // do Present handling all the way after serialisation, so the present call is included in the + // captured frame. + + for(uint32_t i = 0; i < pPresentInfo->swapchainCount; i++) + { + VkSwapchainKHR swap = pPresentInfo->pSwapchains[i]; + + VkResourceRecord *swaprecord = GetRecord(swap); + RDCASSERT(swaprecord->swapInfo); + + SwapchainInfo &swapInfo = *swaprecord->swapInfo; + + Present(LayerDisp(m_Instance), swapInfo.wndHandle); + } + + return vkr; +} + +void WrappedVulkan::HandlePresent(VkQueue queue, const VkPresentInfoKHR *pPresentInfo, + rdcarray &unwrappedWaitSems) +{ + // any array is exploded above in vkQueuePresentKHR, so just look at the first element + VkSwapchainKHR swap = pPresentInfo->pSwapchains[0]; + uint32_t imgIndex = pPresentInfo->pImageIndices[0]; + + VkResourceRecord *swaprecord = GetRecord(swap); RDCASSERT(swaprecord->swapInfo); SwapchainInfo &swapInfo = *swaprecord->swapInfo; @@ -788,13 +894,14 @@ VkResult WrappedVulkan::vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR // need to record which image was last flipped so we can get the correct backbuffer // for a thumbnail in EndFrameCapture - swapInfo.lastPresent.imageIndex = pPresentInfo->pImageIndices[0]; + swapInfo.lastPresent.imageIndex = imgIndex; swapInfo.lastPresent.presentQueue = queue; swapInfo.lastPresent.waitSemaphores.resize(pPresentInfo->waitSemaphoreCount); for(size_t i = 0; i < swapInfo.lastPresent.waitSemaphores.size(); ++i) swapInfo.lastPresent.waitSemaphores[i] = pPresentInfo->pWaitSemaphores[i]; - m_LastSwap = swaprecord->GetResourceID(); + if(m_LastSwap == ResourceId()) + m_LastSwap = swaprecord->GetResourceID(); if(IsBackgroundCapturing(m_State)) { @@ -802,15 +909,12 @@ VkResult WrappedVulkan::vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR if(overlay & eRENDERDOC_Overlay_Enabled) { - // we'll do the wait ourselves before rendering the overlay - unwrappedInfo.waitSemaphoreCount = 0; - VkRenderPass rp = swapInfo.rp; - VkImage im = swapInfo.images[pPresentInfo->pImageIndices[0]].im; - VkFramebuffer fb = swapInfo.images[pPresentInfo->pImageIndices[0]].fb; - VkCommandBuffer cmd = swapInfo.images[pPresentInfo->pImageIndices[0]].cmd; - VkFence imfence = swapInfo.images[pPresentInfo->pImageIndices[0]].fence; - VkSemaphore sem = swapInfo.images[pPresentInfo->pImageIndices[0]].overlaydone; + VkImage im = swapInfo.images[imgIndex].im; + VkFramebuffer fb = swapInfo.images[imgIndex].fb; + VkCommandBuffer cmd = swapInfo.images[imgIndex].cmd; + VkFence imfence = swapInfo.images[imgIndex].fence; + VkSemaphore sem = swapInfo.images[imgIndex].overlaydone; VkResourceRecord *queueRecord = GetRecord(queue); uint32_t swapQueueIndex = queueRecord->queueFamilyIndex; @@ -867,7 +971,7 @@ VkResult WrappedVulkan::vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR DoPipelineBarrier(cmd, 1, &bbBarrier); rdcarray waitStage; - waitStage.fill(unwrappedSems.size(), VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); + waitStage.fill(unwrappedWaitSems.size(), VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); uint32_t ringIdx = 0; @@ -875,8 +979,8 @@ VkResult WrappedVulkan::vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR // wait on the present's semaphores submitInfo.pWaitDstStageMask = waitStage.data(); - submitInfo.pWaitSemaphores = unwrappedSems.data(); - submitInfo.waitSemaphoreCount = (uint32_t)unwrappedSems.size(); + submitInfo.pWaitSemaphores = unwrappedWaitSems.data(); + submitInfo.waitSemaphoreCount = (uint32_t)unwrappedWaitSems.size(); // and signal overlaydone submitInfo.pSignalSemaphores = UnwrapPtr(sem); @@ -920,12 +1024,12 @@ VkResult WrappedVulkan::vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR CheckVkResult(vkr); // next submit needs to wait on fromext - unwrappedSems.assign(submitInfo.pSignalSemaphores, 1); + unwrappedWaitSems.assign(submitInfo.pSignalSemaphores, 1); waitStage.resize(1); submitInfo.pWaitDstStageMask = waitStage.data(); - submitInfo.pWaitSemaphores = unwrappedSems.data(); - submitInfo.waitSemaphoreCount = (uint32_t)unwrappedSems.size(); + submitInfo.pWaitSemaphores = unwrappedWaitSems.data(); + submitInfo.waitSemaphoreCount = (uint32_t)unwrappedWaitSems.size(); // and signal toext submitInfo.pSignalSemaphores = @@ -976,12 +1080,12 @@ VkResult WrappedVulkan::vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR VkQueue q = m_ExternalQueues[swapQueueIndex].queue; // wait on toext which was signalled above - unwrappedSems.assign(submitInfo.pSignalSemaphores, 1); + unwrappedWaitSems.assign(submitInfo.pSignalSemaphores, 1); waitStage.resize(1); submitInfo.pWaitDstStageMask = waitStage.data(); - submitInfo.pWaitSemaphores = unwrappedSems.data(); - submitInfo.waitSemaphoreCount = (uint32_t)unwrappedSems.size(); + submitInfo.pWaitSemaphores = unwrappedWaitSems.data(); + submitInfo.waitSemaphoreCount = (uint32_t)unwrappedWaitSems.size(); // release to the external queue submitInfo.commandBufferCount = 1; @@ -997,32 +1101,11 @@ VkResult WrappedVulkan::vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR CheckVkResult(vkr); } - // the present waits on our new semaphore - unwrappedInfo.waitSemaphoreCount = 1; - unwrappedInfo.pWaitSemaphores = submitInfo.pSignalSemaphores; + // the next thing waits on our new semaphore - whether a subsequent overlay render or the + // present + unwrappedWaitSems = {submitInfo.pSignalSemaphores[0]}; } - - GetResourceManager()->CleanBackgroundFrameReferences(); } - - VkResult vkr; - SERIALISE_TIME_CALL(vkr = ObjDisp(queue)->QueuePresentKHR(Unwrap(queue), &unwrappedInfo)); - - if(IsActiveCapturing(m_State)) - { - CACHE_THREAD_SERIALISER(); - - SCOPED_SERIALISE_CHUNK(VulkanChunk::vkQueuePresentKHR); - Serialise_vkQueuePresentKHR(ser, queue, pPresentInfo); - - GetResourceManager()->MarkResourceFrameReferenced(GetResID(queue), eFrameRef_Read); - - m_FrameCaptureRecord->AddChunk(scope.Get()); - } - - Present(LayerDisp(m_Instance), swapInfo.wndHandle); - - return vkr; } // creation functions are in vk_.cpp diff --git a/util/test/demos/CMakeLists.txt b/util/test/demos/CMakeLists.txt index 7f85272af..505e259a0 100644 --- a/util/test/demos/CMakeLists.txt +++ b/util/test/demos/CMakeLists.txt @@ -33,6 +33,7 @@ set(VULKAN_SRC vk/vk_mesh_zoo.cpp vk/vk_misaligned_dirty.cpp vk/vk_multi_entry.cpp + vk/vk_multi_present.cpp vk/vk_multi_thread_windows.cpp vk/vk_overlay_test.cpp vk/vk_parameter_zoo.cpp diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj index 2a85f5f55..f5117b842 100644 --- a/util/test/demos/demos.vcxproj +++ b/util/test/demos/demos.vcxproj @@ -293,6 +293,7 @@ + diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters index 8c405c68e..c8f6fed3c 100644 --- a/util/test/demos/demos.vcxproj.filters +++ b/util/test/demos/demos.vcxproj.filters @@ -616,6 +616,9 @@ Vulkan\demos + + Vulkan\demos + diff --git a/util/test/demos/linux/linux_window.cpp b/util/test/demos/linux/linux_window.cpp index 45ee34c51..526cdaf51 100644 --- a/util/test/demos/linux/linux_window.cpp +++ b/util/test/demos/linux/linux_window.cpp @@ -55,6 +55,7 @@ Display *X11Window::GetDisplay() } X11Window::X11Window(int width, int height, int visualid_override, const char *title) + : GraphicsWindow(title) { uint32_t value_mask, value_list[32]; diff --git a/util/test/demos/test_common.h b/util/test/demos/test_common.h index 96e6d5cd5..77224426a 100644 --- a/util/test/demos/test_common.h +++ b/util/test/demos/test_common.h @@ -175,9 +175,12 @@ void LoadXPM(const char **XPM, Texture &tex); struct GraphicsWindow { + GraphicsWindow(const std::string &title) : title(title) {} virtual ~GraphicsWindow() {} virtual void Resize(int width, int height) = 0; virtual bool Update() = 0; + + std::string title; }; struct GraphicsTest diff --git a/util/test/demos/vk/vk_multi_present.cpp b/util/test/demos/vk/vk_multi_present.cpp new file mode 100644 index 000000000..0e9b8ecb1 --- /dev/null +++ b/util/test/demos/vk/vk_multi_present.cpp @@ -0,0 +1,175 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include +#include "vk_test.h" + +RD_TEST(VK_Multi_Present, VulkanGraphicsTest) +{ + static constexpr const char *Description = + "Draws to several windows and do batched presentation in vkQueuePresentKHR"; + + int main() + { + // initialise, create window, create context, etc + if(!Init()) + return 3; + + VkPipelineLayout layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo()); + + vkh::GraphicsPipelineCreateInfo pipeCreateInfo; + + pipeCreateInfo.layout = layout; + pipeCreateInfo.renderPass = mainWindow->rp; + + pipeCreateInfo.vertexInputState.vertexBindingDescriptions = {vkh::vertexBind(0, DefaultA2V)}; + pipeCreateInfo.vertexInputState.vertexAttributeDescriptions = { + vkh::vertexAttr(0, 0, DefaultA2V, pos), vkh::vertexAttr(1, 0, DefaultA2V, col), + vkh::vertexAttr(2, 0, DefaultA2V, uv), + }; + + pipeCreateInfo.stages = { + CompileShaderModule(VKDefaultVertex, ShaderLang::glsl, ShaderStage::vert, "main"), + CompileShaderModule(VKDefaultPixel, ShaderLang::glsl, ShaderStage::frag, "main"), + }; + + VkPipeline pipe = createGraphicsPipeline(pipeCreateInfo); + + const DefaultA2V red[3] = { + {Vec3f(-0.5f, -0.5f, 0.0f), Vec4f(1.0f, 0.0f, 0.0f, 1.0f), Vec2f(0.0f, 0.0f)}, + {Vec3f(0.0f, 0.5f, 0.0f), Vec4f(1.0f, 0.0f, 0.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(0.5f, -0.5f, 0.0f), Vec4f(1.0f, 0.0f, 0.0f, 1.0f), Vec2f(1.0f, 0.0f)}, + }; + + const DefaultA2V green[3] = { + {Vec3f(-0.5f, -0.5f, 0.0f), Vec4f(0.0f, 1.0f, 0.0f, 1.0f), Vec2f(0.0f, 0.0f)}, + {Vec3f(0.0f, 0.5f, 0.0f), Vec4f(0.0f, 1.0f, 0.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(0.5f, -0.5f, 0.0f), Vec4f(0.0f, 1.0f, 0.0f, 1.0f), Vec2f(1.0f, 0.0f)}, + }; + + const DefaultA2V blue[3] = { + {Vec3f(-0.5f, -0.5f, 0.0f), Vec4f(0.0f, 0.0f, 1.0f, 1.0f), Vec2f(0.0f, 0.0f)}, + {Vec3f(0.0f, 0.5f, 0.0f), Vec4f(0.0f, 0.0f, 1.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(0.5f, -0.5f, 0.0f), Vec4f(0.0f, 0.0f, 1.0f, 1.0f), Vec2f(1.0f, 0.0f)}, + }; + + AllocatedBuffer vb[3]; + + vb[0] = AllocatedBuffer(this, + vkh::BufferCreateInfo(sizeof(red), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU})); + vb[0].upload(red); + vb[1] = AllocatedBuffer( + this, vkh::BufferCreateInfo(sizeof(green), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU})); + vb[1].upload(green); + vb[2] = AllocatedBuffer( + this, vkh::BufferCreateInfo(sizeof(blue), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU})); + vb[2].upload(blue); + + std::vector windows = { + mainWindow, + MakeWindow(mainWindow->scissor.extent.width, mainWindow->scissor.extent.height, "green"), + MakeWindow(mainWindow->scissor.extent.width, mainWindow->scissor.extent.height, "blue")}; + + size_t frameDelay = 0; + + while(FrameLimit()) + { + bool any = false; + + frameDelay++; + + std::vector presentWindows; + + // delay each window by one to try and offset image indices (if they're round robin) for a + // better test. i.e. render only window 0 on frame 0 + for(size_t i = 0; i < std::min(windows.size(), frameDelay); i++) + { + VulkanWindow *win = windows[i]; + if(!win) + break; + + presentWindows.push_back(win); + + VkCommandBuffer cmd = GetCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, win); + + vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); + + VkImage swapimg = + StartUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL, win); + + vkCmdClearColorImage(cmd, swapimg, VK_IMAGE_LAYOUT_GENERAL, + vkh::ClearColorValue(0.2f, 0.2f, 0.2f, 1.0f), 1, + vkh::ImageSubresourceRange()); + + vkCmdBeginRenderPass(cmd, vkh::RenderPassBeginInfo(win->rp, win->GetFB(), win->scissor), + VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe); + vkCmdSetViewport(cmd, 0, 1, &win->viewport); + vkCmdSetScissor(cmd, 0, 1, &win->scissor); + vkh::cmdBindVertexBuffers(cmd, 0, {vb[i].buffer}, {0}); + vkCmdDraw(cmd, 3, 1, 0, 0); + + vkCmdEndRenderPass(cmd); + + FinishUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL, win); + + vkEndCommandBuffer(cmd); + + Submit(0, 1, {cmd}, {}, win); + } + + VulkanWindow::MultiPresent(queue, presentWindows); + + for(size_t i = 0; i < windows.size(); i++) + { + if(windows[i]->Update()) + { + any = true; + } + else + { + delete windows[i]; + windows[i] = NULL; + } + } + + if(!any) + break; + } + + for(size_t i = 0; i < windows.size(); i++) + delete windows[i]; + + return 0; + } +}; + +REGISTER_TEST(); diff --git a/util/test/demos/vk/vk_test.cpp b/util/test/demos/vk/vk_test.cpp index 816ff38e0..e5bfb15d5 100644 --- a/util/test/demos/vk/vk_test.cpp +++ b/util/test/demos/vk/vk_test.cpp @@ -878,6 +878,12 @@ void VulkanGraphicsTest::setName(VkBuffer obj, const std::string &name) setName(VK_OBJECT_TYPE_BUFFER, (uint64_t)obj, name); } +template <> +void VulkanGraphicsTest::setName(VkSemaphore obj, const std::string &name) +{ + setName(VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)obj, name); +} + void VulkanGraphicsTest::setName(VkObjectType objType, uint64_t obj, const std::string &name) { if(vkSetDebugUtilsObjectNameEXT) @@ -1113,6 +1119,7 @@ VkSampler VulkanGraphicsTest::createSampler(const VkSamplerCreateInfo *info) } VulkanWindow::VulkanWindow(VulkanGraphicsTest *test, GraphicsWindow *win) + : GraphicsWindow(win->title) { m_Test = test; m_Win = win; @@ -1129,6 +1136,9 @@ VulkanWindow::VulkanWindow(VulkanGraphicsTest *test, GraphicsWindow *win) CHECK_VKR( vkCreateSemaphore(m_Test->device, vkh::SemaphoreCreateInfo(), NULL, &renderEndSemaphore)); + test->setName(renderStartSemaphore, title + " renderStartSemaphore"); + test->setName(renderEndSemaphore, title + " renderEndSemaphore"); + #if defined(WIN32) VkWin32SurfaceCreateInfoKHR createInfo; @@ -1371,6 +1381,47 @@ void VulkanWindow::Submit(int index, int totalSubmits, const std::vector windows) +{ + std::vector swaps; + std::vector idxs; + std::vector waitSems; + std::vector vkrs; + + for(auto it : windows) + { + if(it->swap == VK_NULL_HANDLE) + continue; + + swaps.push_back(it->swap); + idxs.push_back(it->imgIndex); + waitSems.push_back(it->renderEndSemaphore); + vkrs.push_back(VK_SUCCESS); + } + + if(swaps.empty()) + return; + + VkPresentInfoKHR info = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR}; + info.swapchainCount = (uint32_t)swaps.size(); + info.waitSemaphoreCount = (uint32_t)waitSems.size(); + info.pSwapchains = swaps.data(); + info.pImageIndices = idxs.data(); + info.pWaitSemaphores = waitSems.data(); + info.pResults = vkrs.data(); + + vkQueuePresentKHR(queue, &info); + + size_t i = 0; + for(auto it : windows) + { + if(it->swap == VK_NULL_HANDLE) + continue; + + it->PostPresent(vkrs[i++]); + } +} + void VulkanWindow::Present(VkQueue queue) { if(swap == VK_NULL_HANDLE) @@ -1378,6 +1429,11 @@ void VulkanWindow::Present(VkQueue queue) VkResult vkr = vkQueuePresentKHR(queue, vkh::PresentInfoKHR(swap, imgIndex, &renderEndSemaphore)); + PostPresent(vkr); +} + +void VulkanWindow::PostPresent(VkResult vkr) +{ if(vkr == VK_SUBOPTIMAL_KHR || vkr == VK_ERROR_OUT_OF_DATE_KHR) { DestroySwapchain(); diff --git a/util/test/demos/vk/vk_test.h b/util/test/demos/vk/vk_test.h index 6acbc6c78..83dd57fd4 100644 --- a/util/test/demos/vk/vk_test.h +++ b/util/test/demos/vk/vk_test.h @@ -142,8 +142,8 @@ struct VulkanWindow : public GraphicsWindow VkCommandBuffer GetCommandBuffer(VkCommandBufferLevel level); void Submit(int index, int totalSubmits, const std::vector &cmds, const std::vector &seccmds, VkQueue q, bool sync2); + static void MultiPresent(VkQueue queue, std::vector windows); void Present(VkQueue q); - void Acquire(); // forward GraphicsWindow functions to internal window void Resize(int width, int height) { m_Win->Resize(width, height); } @@ -152,6 +152,9 @@ private: bool CreateSwapchain(); void DestroySwapchain(); + void Acquire(); + void PostPresent(VkResult vkr); + VkSurfaceKHR surface = VK_NULL_HANDLE; VkSwapchainKHR swap = VK_NULL_HANDLE; std::vector imgs; diff --git a/util/test/demos/win32/win32_window.cpp b/util/test/demos/win32/win32_window.cpp index 7aa02adae..5513f2a77 100644 --- a/util/test/demos/win32/win32_window.cpp +++ b/util/test/demos/win32/win32_window.cpp @@ -68,7 +68,7 @@ void regClass() } } -Win32Window::Win32Window(int width, int height, const char *title) +Win32Window::Win32Window(int width, int height, const char *title) : GraphicsWindow(title) { regClass();