diff --git a/core/rend/gui_util.h b/core/rend/gui_util.h index 5e0853999..5ba4ccc57 100644 --- a/core/rend/gui_util.h +++ b/core/rend/gui_util.h @@ -37,20 +37,25 @@ static inline void ImGui_impl_RenderDrawData(ImDrawData *draw_data, bool save_ba if (!settings.pvr.IsOpenGL()) { VulkanContext *context = VulkanContext::Instance(); - bool rendering = context->IsRendering(); - const std::vector *vmuCmdBuffers = nullptr; - if (!rendering) - { - context->NewFrame(); - vmuCmdBuffers = context->PrepareVMUs(); - context->BeginRenderPass(); - context->PresentLastFrame(); - context->DrawVMUs(gui_get_scaling()); + if (!context->IsValid()) + return; + try { + bool rendering = context->IsRendering(); + const std::vector *vmuCmdBuffers = nullptr; + if (!rendering) + { + context->NewFrame(); + vmuCmdBuffers = context->PrepareVMUs(); + context->BeginRenderPass(); + context->PresentLastFrame(); + context->DrawVMUs(gui_get_scaling()); + } + // Record Imgui Draw Data and draw funcs into command buffer + ImGui_ImplVulkan_RenderDrawData(draw_data, (VkCommandBuffer)context->GetCurrentCommandBuffer()); + if (!rendering) + context->EndFrame(vmuCmdBuffers); + } catch (const InvalidVulkanContext& err) { } - // Record Imgui Draw Data and draw funcs into command buffer - ImGui_ImplVulkan_RenderDrawData(draw_data, (VkCommandBuffer)context->GetCurrentCommandBuffer()); - if (!rendering) - context->EndFrame(vmuCmdBuffers); } else #endif diff --git a/core/rend/vulkan/drawer.cpp b/core/rend/vulkan/drawer.cpp index c4bd435dd..74cdd2925 100644 --- a/core/rend/vulkan/drawer.cpp +++ b/core/rend/vulkan/drawer.cpp @@ -590,6 +590,9 @@ void ScreenDrawer::Init(SamplerManager *samplerManager, ShaderManager *shaderMan framebuffers.clear(); colorAttachments.clear(); depthAttachment.reset(); + transitionNeeded.clear(); + clearNeeded.clear(); + frameRendered = false; } this->viewport = viewport; if (!depthAttachment) @@ -600,7 +603,7 @@ void ScreenDrawer::Init(SamplerManager *samplerManager, ShaderManager *shaderMan vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eTransientAttachment); } - if (!renderPass) + if (!renderPassLoad) { vk::AttachmentDescription attachmentDescriptions[] = { // Color attachment @@ -629,7 +632,12 @@ void ScreenDrawer::Init(SamplerManager *samplerManager, ShaderManager *shaderMan dependencies.emplace_back(0, VK_SUBPASS_EXTERNAL, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eFragmentShader, vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eShaderRead, vk::DependencyFlagBits::eByRegion); - renderPass = GetContext()->GetDevice().createRenderPassUnique(vk::RenderPassCreateInfo(vk::RenderPassCreateFlags(), + renderPassLoad = GetContext()->GetDevice().createRenderPassUnique(vk::RenderPassCreateInfo(vk::RenderPassCreateFlags(), + ARRAY_SIZE(attachmentDescriptions), attachmentDescriptions, + ARRAY_SIZE(subpasses), subpasses, + dependencies.size(), dependencies.data())); + attachmentDescriptions[0].loadOp = vk::AttachmentLoadOp::eClear; + renderPassClear = GetContext()->GetDevice().createRenderPassUnique(vk::RenderPassCreateInfo(vk::RenderPassCreateFlags(), ARRAY_SIZE(attachmentDescriptions), attachmentDescriptions, ARRAY_SIZE(subpasses), subpasses, dependencies.size(), dependencies.data())); @@ -639,6 +647,8 @@ void ScreenDrawer::Init(SamplerManager *samplerManager, ShaderManager *shaderMan { colorAttachments.resize(size); framebuffers.resize(size); + transitionNeeded.resize(size); + clearNeeded.resize(size); } else { @@ -653,16 +663,18 @@ void ScreenDrawer::Init(SamplerManager *samplerManager, ShaderManager *shaderMan colorAttachments.back()->Init(viewport.width, viewport.height, GetContext()->GetColorFormat(), vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled); attachments[0] = colorAttachments.back()->GetImageView(); - vk::FramebufferCreateInfo createInfo(vk::FramebufferCreateFlags(), *renderPass, + vk::FramebufferCreateInfo createInfo(vk::FramebufferCreateFlags(), *renderPassLoad, ARRAY_SIZE(attachments), attachments, viewport.width, viewport.height, 1); framebuffers.push_back(GetContext()->GetDevice().createFramebufferUnique(createInfo)); - transitionsNeeded++; + transitionNeeded.push_back(true); + clearNeeded.resize(true); } } + frameRendered = false; if (!screenPipelineManager) screenPipelineManager = std::unique_ptr(new PipelineManager()); - screenPipelineManager->Init(shaderManager, *renderPass); + screenPipelineManager->Init(shaderManager, *renderPassLoad); Drawer::Init(samplerManager, screenPipelineManager.get()); } @@ -674,15 +686,17 @@ vk::CommandBuffer ScreenDrawer::BeginRenderPass() vk::CommandBuffer commandBuffer = commandPool->Allocate(); commandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); - if (transitionsNeeded) + if (transitionNeeded[GetCurrentImage()]) { - for (size_t i = colorAttachments.size() - transitionsNeeded; i < colorAttachments.size(); i++) - setImageLayout(commandBuffer, colorAttachments[i]->GetImage(), GetContext()->GetColorFormat(), 1, vk::ImageLayout::eUndefined, vk::ImageLayout::eShaderReadOnlyOptimal); - transitionsNeeded = 0; + setImageLayout(commandBuffer, colorAttachments[GetCurrentImage()]->GetImage(), GetContext()->GetColorFormat(), + 1, vk::ImageLayout::eUndefined, vk::ImageLayout::eShaderReadOnlyOptimal); + transitionNeeded[GetCurrentImage()] = false; } + vk::RenderPass renderPass = clearNeeded[GetCurrentImage()] ? *renderPassClear : *renderPassLoad; + clearNeeded[GetCurrentImage()] = false; const vk::ClearValue clear_colors[] = { vk::ClearColorValue(std::array { 0.f, 0.f, 0.f, 1.f }), vk::ClearDepthStencilValue { 0.f, 0 } }; - commandBuffer.beginRenderPass(vk::RenderPassBeginInfo(*renderPass, *framebuffers[GetCurrentImage()], + commandBuffer.beginRenderPass(vk::RenderPassBeginInfo(renderPass, *framebuffers[GetCurrentImage()], vk::Rect2D( { 0, 0 }, viewport), 2, clear_colors), vk::SubpassContents::eInline); commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, viewport.width, viewport.height, 1.0f, 0.0f)); diff --git a/core/rend/vulkan/drawer.h b/core/rend/vulkan/drawer.h index b7a0f8b16..eb5cefe0e 100644 --- a/core/rend/vulkan/drawer.h +++ b/core/rend/vulkan/drawer.h @@ -193,14 +193,13 @@ class ScreenDrawer : public Drawer { public: void Init(SamplerManager *samplerManager, ShaderManager *shaderManager); - vk::RenderPass GetRenderPass() const { return *renderPass; } virtual void EndRenderPass() override; bool PresentFrame() { if (!frameRendered) return false; frameRendered = false; - GetContext()->PresentFrame(colorAttachments[GetCurrentImage()]->GetImageView(), vk::Offset2D(viewport.width, viewport.height)); + GetContext()->PresentFrame(colorAttachments[GetCurrentImage()]->GetImageView(), viewport); NewImage(); return true; @@ -213,14 +212,16 @@ protected: private: std::unique_ptr screenPipelineManager; - vk::UniqueRenderPass renderPass; + vk::UniqueRenderPass renderPassLoad; + vk::UniqueRenderPass renderPassClear; std::vector framebuffers; std::vector> colorAttachments; std::unique_ptr depthAttachment; vk::Extent2D viewport; int currentScreenScaling = 0; ShaderManager *shaderManager = nullptr; - int transitionsNeeded = 0; + std::vector transitionNeeded; + std::vector clearNeeded; bool frameRendered = false; }; diff --git a/core/rend/vulkan/oit/oit_drawer.cpp b/core/rend/vulkan/oit/oit_drawer.cpp index fe4156620..ac0d36694 100644 --- a/core/rend/vulkan/oit/oit_drawer.cpp +++ b/core/rend/vulkan/oit/oit_drawer.cpp @@ -354,6 +354,14 @@ bool OITDrawer::Draw(const Texture *fogTexture, const Texture *paletteTexture) // Final subpass cmdBuffer.nextSubpass(vk::SubpassContents::eInline); GetCurrentDescSet().BindColorInputDescSet(cmdBuffer, (pvrrc.render_passes.used() - 1 - render_pass) % 2); + + if (initialPass && clearNeeded[GetCurrentImage()] && !pvrrc.isRTT) + { + clearNeeded[GetCurrentImage()] = false; + SetScissor(cmdBuffer, viewport); + cmdBuffer.clearAttachments(vk::ClearAttachment(vk::ImageAspectFlagBits::eColor, 0, clear_colors[0]), + vk::ClearRect(viewport, 0, 1)); + } SetScissor(cmdBuffer, baseScissor); if (!oitBuffers->isFirstFrameAfterInit()) @@ -460,6 +468,8 @@ void OITScreenDrawer::MakeFramebuffers() MakeBuffers(viewport.extent.width, viewport.extent.height); framebuffers.clear(); finalColorAttachments.clear(); + transitionNeeded.clear(); + clearNeeded.clear(); while (finalColorAttachments.size() < GetContext()->GetSwapChainSize()) { finalColorAttachments.push_back(std::unique_ptr( @@ -474,7 +484,8 @@ void OITScreenDrawer::MakeFramebuffers() vk::FramebufferCreateInfo createInfo(vk::FramebufferCreateFlags(), screenPipelineManager->GetRenderPass(true, true), ARRAY_SIZE(attachments), attachments, viewport.extent.width, viewport.extent.height, 1); framebuffers.push_back(GetContext()->GetDevice().createFramebufferUnique(createInfo)); - transitionsNeeded++; + transitionNeeded.push_back(true); + clearNeeded.push_back(true); } } @@ -676,11 +687,10 @@ vk::CommandBuffer OITScreenDrawer::NewFrame() vk::CommandBuffer commandBuffer = commandPool->Allocate(); commandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); - if (transitionsNeeded) + if (transitionNeeded[GetCurrentImage()]) { - for (size_t i = finalColorAttachments.size() - transitionsNeeded; i < finalColorAttachments.size(); i++) - setImageLayout(commandBuffer, finalColorAttachments[i]->GetImage(), GetColorFormat(), 1, vk::ImageLayout::eUndefined, vk::ImageLayout::eShaderReadOnlyOptimal); - transitionsNeeded = 0; + setImageLayout(commandBuffer, finalColorAttachments[GetCurrentImage()]->GetImage(), GetColorFormat(), 1, vk::ImageLayout::eUndefined, vk::ImageLayout::eShaderReadOnlyOptimal); + transitionNeeded[GetCurrentImage()] = false; } matrices.CalcMatrices(&pvrrc); diff --git a/core/rend/vulkan/oit/oit_drawer.h b/core/rend/vulkan/oit/oit_drawer.h index f95819187..8310e97a3 100644 --- a/core/rend/vulkan/oit/oit_drawer.h +++ b/core/rend/vulkan/oit/oit_drawer.h @@ -118,6 +118,7 @@ protected: std::array, 2> colorAttachments; std::unique_ptr depthAttachment; vk::CommandBuffer currentCommandBuffer; + std::vector clearNeeded; private: void DrawPoly(const vk::CommandBuffer& cmdBuffer, u32 listType, bool sortTriangles, Pass pass, @@ -166,6 +167,7 @@ public: currentScreenScaling = 0; MakeFramebuffers(); + GetContext()->PresentFrame(vk::ImageView(), viewport.extent); } void Term() { @@ -191,7 +193,7 @@ public: if (!frameRendered) return false; frameRendered = false; - GetContext()->PresentFrame(finalColorAttachments[GetCurrentImage()]->GetImageView(), vk::Offset2D(viewport.extent.width, viewport.extent.height)); + GetContext()->PresentFrame(finalColorAttachments[GetCurrentImage()]->GetImageView(), viewport.extent); NewImage(); return true; @@ -208,7 +210,7 @@ private: std::vector framebuffers; std::unique_ptr screenPipelineManager; int currentScreenScaling = 0; - int transitionsNeeded = 0; + std::vector transitionNeeded; bool frameRendered = false; }; diff --git a/core/rend/vulkan/oit/oit_pipeline.h b/core/rend/vulkan/oit/oit_pipeline.h index ebc9ef5d5..342e25e84 100644 --- a/core/rend/vulkan/oit/oit_pipeline.h +++ b/core/rend/vulkan/oit/oit_pipeline.h @@ -148,12 +148,11 @@ public: { colorInputDescSets[index] = std::move(GetContext()->GetDevice().allocateDescriptorSetsUnique( vk::DescriptorSetAllocateInfo(GetContext()->GetDescriptorPool(), 1, &colorInputLayout)).front()); - std::vector writeDescriptorSets; - vk::DescriptorImageInfo colorImageInfo(vk::Sampler(), colorImageView, vk::ImageLayout::eShaderReadOnlyOptimal); - writeDescriptorSets.push_back(vk::WriteDescriptorSet(*colorInputDescSets[index], 0, 0, 1, vk::DescriptorType::eInputAttachment, &colorImageInfo, nullptr, nullptr)); - - GetContext()->GetDevice().updateDescriptorSets(writeDescriptorSets, nullptr); } + vk::DescriptorImageInfo colorImageInfo(vk::Sampler(), colorImageView, vk::ImageLayout::eShaderReadOnlyOptimal); + vk::WriteDescriptorSet writeDescriptorSet(*colorInputDescSets[index], 0, 0, 1, vk::DescriptorType::eInputAttachment, &colorImageInfo, nullptr, nullptr); + + GetContext()->GetDevice().updateDescriptorSets(1, &writeDescriptorSet, 0, nullptr); } void SetTexture(u64 textureId0, TSP tsp0, u64 textureId1, TSP tsp1) diff --git a/core/rend/vulkan/oit/oit_renderer.cpp b/core/rend/vulkan/oit/oit_renderer.cpp index ad053105e..e8591f8b4 100644 --- a/core/rend/vulkan/oit/oit_renderer.cpp +++ b/core/rend/vulkan/oit/oit_renderer.cpp @@ -51,14 +51,13 @@ public: void Resize(int w, int h) override { - NOTICE_LOG(RENDERER, "OIT Resize %d x %d", w, h); BaseVulkanRenderer::Resize(w, h); screenDrawer.Init(&samplerManager, &oitShaderManager, &oitBuffers); } void Term() override { - DEBUG_LOG(RENDERER, "VulkanRenderer::Term"); + DEBUG_LOG(RENDERER, "OITVulkanRenderer::Term"); GetContext()->WaitIdle(); screenDrawer.Term(); textureDrawer.Term(); @@ -68,17 +67,24 @@ public: bool Render() override { - OITDrawer *drawer; - if (pvrrc.isRTT) - drawer = &textureDrawer; - else - drawer = &screenDrawer; + try { + OITDrawer *drawer; + if (pvrrc.isRTT) + drawer = &textureDrawer; + else + drawer = &screenDrawer; - drawer->Draw(fogTexture.get(), paletteTexture.get()); + drawer->Draw(fogTexture.get(), paletteTexture.get()); - drawer->EndFrame(); + drawer->EndFrame(); - return !pvrrc.isRTT; + return !pvrrc.isRTT; + } catch (const vk::SystemError& e) { + // Sometimes happens when resizing the window + WARN_LOG(RENDERER, "Vulkan system error %s", e.what()); + + return false; + } } bool Present() override diff --git a/core/rend/vulkan/pipeline.h b/core/rend/vulkan/pipeline.h index 99b623526..b0f9d7693 100644 --- a/core/rend/vulkan/pipeline.h +++ b/core/rend/vulkan/pipeline.h @@ -142,7 +142,7 @@ private: std::vector perPolyDescSets; std::map, vk::UniqueDescriptorSet> perPolyDescSetsInFlight; - SamplerManager* samplerManager; + SamplerManager* samplerManager = nullptr; }; class PipelineManager @@ -278,7 +278,7 @@ protected: VulkanContext *GetContext() const { return VulkanContext::Instance(); } vk::RenderPass renderPass; - ShaderManager *shaderManager; + ShaderManager *shaderManager = nullptr; }; class RttPipelineManager : public PipelineManager @@ -326,7 +326,7 @@ public: private: vk::UniqueRenderPass rttRenderPass; - bool renderToTextureBuffer; + bool renderToTextureBuffer = false; }; class OSDPipeline @@ -391,5 +391,5 @@ private: vk::UniqueDescriptorSet descriptorSet; vk::UniquePipelineLayout pipelineLayout; vk::UniqueDescriptorSetLayout descSetLayout; - ShaderManager *shaderManager; + ShaderManager *shaderManager = nullptr; }; diff --git a/core/rend/vulkan/quad.cpp b/core/rend/vulkan/quad.cpp index a5379c122..e1a44e6ce 100644 --- a/core/rend/vulkan/quad.cpp +++ b/core/rend/vulkan/quad.cpp @@ -164,6 +164,8 @@ void QuadDrawer::Init(QuadPipeline *pipeline) this->pipeline = pipeline; buffer = std::unique_ptr(new QuadBuffer()); descriptorSets.resize(VulkanContext::Instance()->GetSwapChainSize()); + for (auto& descSet : descriptorSets) + descSet.reset(); } void QuadDrawer::Draw(vk::CommandBuffer commandBuffer, vk::ImageView imageView, QuadVertex vertices[], bool nearestFilter) diff --git a/core/rend/vulkan/vulkan_context.cpp b/core/rend/vulkan/vulkan_context.cpp index 0479f9cda..ba0d0592c 100644 --- a/core/rend/vulkan/vulkan_context.cpp +++ b/core/rend/vulkan/vulkan_context.cpp @@ -94,7 +94,7 @@ VKAPI_ATTR static VkBool32 VKAPI_CALL debugUtilsMessengerCallback(VkDebugUtilsMe ERROR_LOG(RENDERER, "%s", msg.c_str()); break; } - return VK_TRUE; + return VK_FALSE; } #else #if HOST_CPU == CPU_ARM @@ -137,7 +137,8 @@ bool VulkanContext::InitInstance(const char** extensions, uint32_t extensions_co { u32 apiVersion; if (vk::enumerateInstanceVersion(&apiVersion) == vk::Result::eSuccess) - vulkan11 = VK_VERSION_MINOR(apiVersion) == 1; + vulkan11 = VK_VERSION_MAJOR(apiVersion) > 1 + || (VK_VERSION_MAJOR(apiVersion) == 1 && VK_VERSION_MINOR(apiVersion) >= 1); } vk::ApplicationInfo applicationInfo("Flycast", 1, "Flycast", 1, vulkan11 ? VK_API_VERSION_1_1 : VK_API_VERSION_1_0); std::vector vext; @@ -410,6 +411,15 @@ bool VulkanContext::InitDevice() deviceExtensions.push_back(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME); dedicatedAllocationSupported = true; } +#ifdef VK_DEBUG + else if (!strcmp(property.extensionName, VK_EXT_DEBUG_MARKER_EXTENSION_NAME) + || !strcmp(property.extensionName, VK_EXT_DEBUG_REPORT_EXTENSION_NAME) + || !strcmp(property.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) + { + NOTICE_LOG(RENDERER, "Debug extension %s available", property.extensionName); + deviceExtensions.push_back(property.extensionName); + } +#endif } dedicatedAllocationSupported &= getMemReq2Supported; @@ -482,6 +492,9 @@ bool VulkanContext::InitDevice() { ERROR_LOG(RENDERER, "Vulkan error: %s", err.what()); } + catch (const InvalidVulkanContext& err) + { + } catch (...) { ERROR_LOG(RENDERER, "Unknown error"); @@ -501,7 +514,8 @@ void VulkanContext::CreateSwapChain() renderCompleteSemaphores.clear(); commandBuffers.clear(); commandPools.clear(); - imageViews.clear(); + for (auto& img : imageViews) + img.reset(); // get the supported VkFormats std::vector formats = physicalDevice.getSurfaceFormatsKHR(GetSurface()); @@ -539,6 +553,9 @@ void VulkanContext::CreateSwapChain() swapchainExtent = surfaceCapabilities.currentExtent; } SetWindowSize(swapchainExtent.width, swapchainExtent.height); + resized = false; + if (swapchainExtent.width == 0 || swapchainExtent.height == 0) + throw InvalidVulkanContext(); // The FIFO present mode is guaranteed by the spec to be supported vk::PresentModeKHR swapchainPresentMode = vk::PresentModeKHR::eFifo; @@ -598,22 +615,23 @@ void VulkanContext::CreateSwapChain() { DEBUG_LOG(RENDERER, "createSwapchainKHRUnique failed: %s", err.what()); if (++tries > 10) - throw err; + throw InvalidVulkanContext(); } } while (!swapChain); std::vector swapChainImages = device->getSwapchainImagesKHR(*swapChain); - imageViews.reserve(swapChainImages.size()); + imageViews.resize(swapChainImages.size()); commandPools.reserve(swapChainImages.size()); commandBuffers.reserve(swapChainImages.size()); vk::ComponentMapping componentMapping(vk::ComponentSwizzle::eR, vk::ComponentSwizzle::eG, vk::ComponentSwizzle::eB, vk::ComponentSwizzle::eA); vk::ImageSubresourceRange subResourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1); + u32 imageIdx = 0; for (auto image : swapChainImages) { vk::ImageViewCreateInfo imageViewCreateInfo(vk::ImageViewCreateFlags(), image, vk::ImageViewType::e2D, colorFormat, componentMapping, subResourceRange); - imageViews.push_back(device->createImageViewUnique(imageViewCreateInfo)); + imageViews[imageIdx++] = device->createImageViewUnique(imageViewCreateInfo); // create a UniqueCommandPool to allocate a CommandBuffer from commandPools.push_back(device->createCommandPoolUnique(vk::CommandPoolCreateInfo(vk::CommandPoolCreateFlagBits::eTransient, graphicsQueueIndex))); @@ -663,10 +681,8 @@ void VulkanContext::CreateSwapChain() catch (const vk::SystemError& err) { ERROR_LOG(RENDERER, "Vulkan error: %s", err.what()); - } - catch (...) - { - ERROR_LOG(RENDERER, "Unknown error"); + SetWindowSize(0, 0); + throw InvalidVulkanContext(); } } @@ -696,8 +712,9 @@ bool VulkanContext::Init() #if defined(USE_SDL) VkSurfaceKHR surface; - if (SDL_Vulkan_CreateSurface((SDL_Window *)window, (VkInstance)*instance, (VkSurfaceKHR *)&this->surface) == 0) + if (SDL_Vulkan_CreateSurface((SDL_Window *)window, (VkInstance)*instance, &surface) == 0) return false; + this->surface.reset(surface); #elif defined(_WIN32) vk::Win32SurfaceCreateInfoKHR createInfo(vk::Win32SurfaceCreateFlagsKHR(), GetModuleHandle(NULL), (HWND)window); surface = instance->createWin32SurfaceKHRUnique(createInfo); @@ -715,11 +732,14 @@ bool VulkanContext::Init() void VulkanContext::NewFrame() { - if (HasSurfaceDimensionChanged()) + if (resized || HasSurfaceDimensionChanged()) { CreateSwapChain(); - rend_resize(width, height); + rend_resize(screen_width, screen_height); + lastFrameView = vk::ImageView(); } + if (!IsValid()) + throw InvalidVulkanContext(); device->acquireNextImageKHR(*swapChain, UINT64_MAX, *imageAcquiredSemaphores[currentSemaphore], nullptr, ¤tImage); device->waitForFences(1, &(*drawFences[currentImage]), true, UINT64_MAX); device->resetFences(1, &(*drawFences[currentImage])); @@ -732,6 +752,8 @@ void VulkanContext::NewFrame() void VulkanContext::BeginRenderPass() { + if (!IsValid()) + return; const vk::ClearValue clear_colors[] = { vk::ClearColorValue(std::array{0.f, 0.f, 0.f, 1.f}), vk::ClearDepthStencilValue{ 0.f, 0 } }; vk::CommandBuffer commandBuffer = *commandBuffers[currentImage]; commandBuffer.beginRenderPass(vk::RenderPassBeginInfo(*renderPass, *framebuffers[currentImage], vk::Rect2D({0, 0}, {width, height}), 2, clear_colors), @@ -740,6 +762,8 @@ void VulkanContext::BeginRenderPass() void VulkanContext::EndFrame(const std::vector *cmdBuffers) { + if (!IsValid()) + return; vk::CommandBuffer commandBuffer = *commandBuffers[currentImage]; commandBuffer.endRenderPass(); commandBuffer.end(); @@ -756,7 +780,7 @@ void VulkanContext::EndFrame(const std::vector *cmdBuff renderDone = true; } -void VulkanContext::Present() +void VulkanContext::Present() noexcept { if (renderDone) { @@ -764,18 +788,23 @@ void VulkanContext::Present() DoSwapAutomation(); presentQueue.presentKHR(vk::PresentInfoKHR(1, &(*renderCompleteSemaphores[currentSemaphore]), 1, &(*swapChain), ¤tImage)); currentSemaphore = (currentSemaphore + 1) % imageViews.size(); - } catch (const vk::OutOfDateKHRError& e) { - // Sometimes happens when resizing the window - INFO_LOG(RENDERER, "vk::OutOfDateKHRError"); - CreateSwapChain(); + } catch (const vk::SystemError& e) { + // Happens when resizing the window + INFO_LOG(RENDERER, "vk::SystemError %s", e.what()); + resized = true; } renderDone = false; } + if (resized) + try { + CreateSwapChain(); + } catch (const InvalidVulkanContext& err) { + } } -void VulkanContext::DrawFrame(vk::ImageView imageView, vk::Offset2D extent) +void VulkanContext::DrawFrame(vk::ImageView imageView, vk::Extent2D extent) { - float marginWidth = ((float)extent.y / extent.x * width / height - 1.f) / 2.f; + float marginWidth = ((float)extent.height / extent.width * width / height - 1.f) / 2.f; QuadVertex vtx[] = { { { -1, -1, 0 }, { 0 - marginWidth, 0 } }, { { 1, -1, 0 }, { 1 + marginWidth, 0 } }, @@ -795,6 +824,31 @@ void VulkanContext::DrawFrame(vk::ImageView imageView, vk::Offset2D extent) quadDrawer->Draw(commandBuffer, imageView, vtx); } +void VulkanContext::WaitIdle() const +{ + try { + graphicsQueue.waitIdle(); + } catch (const vk::Error &err) { + WARN_LOG(RENDERER, "WaitIdle: %s", err.what()); + } +} + +std::string VulkanContext::GetDriverName() const +{ + vk::PhysicalDeviceProperties props; + physicalDevice.getProperties(&props); + return std::string(props.deviceName); +} + +std::string VulkanContext::GetDriverVersion() const +{ + vk::PhysicalDeviceProperties props; + physicalDevice.getProperties(&props); + return std::to_string(VK_VERSION_MAJOR(props.driverVersion)) + "." + + std::to_string(VK_VERSION_MINOR(props.driverVersion)) + "." + + std::to_string(VK_VERSION_PATCH(props.driverVersion)); +} + const std::vector *VulkanContext::PrepareVMUs() { return vmus->PrepareVMUs(*commandPools[GetCurrentImageIndex()]); @@ -802,36 +856,42 @@ const std::vector *VulkanContext::PrepareVMUs() void VulkanContext::DrawVMUs(float scaling) { - vmus->DrawVMUs(vk::Extent2D(width, height), scaling); + if (IsValid()) + vmus->DrawVMUs(vk::Extent2D(width, height), scaling); } extern Renderer *renderer; -void VulkanContext::PresentFrame(vk::ImageView imageView, vk::Offset2D extent) +void VulkanContext::PresentFrame(vk::ImageView imageView, vk::Extent2D extent) noexcept { - if (imageView) - { - NewFrame(); - const std::vector *vmuCmdBuffers = nullptr; - if (settings.rend.FloatVMUs) - vmuCmdBuffers = PrepareVMUs(); - - BeginRenderPass(); - - DrawFrame(imageView, extent); - if (settings.rend.FloatVMUs) - DrawVMUs(gui_get_scaling()); - renderer->DrawOSD(false); - EndFrame(vmuCmdBuffers); - } - lastFrameView = imageView; lastFrameExtent = extent; + + if (imageView && IsValid()) + { + try { + NewFrame(); + const std::vector *vmuCmdBuffers = nullptr; + if (settings.rend.FloatVMUs) + vmuCmdBuffers = PrepareVMUs(); + + BeginRenderPass(); + + if (lastFrameView) // Might have been nullified if swap chain recreated + DrawFrame(imageView, extent); + + if (settings.rend.FloatVMUs) + DrawVMUs(gui_get_scaling()); + renderer->DrawOSD(false); + EndFrame(vmuCmdBuffers); + } catch (const InvalidVulkanContext& err) { + } + } } void VulkanContext::PresentLastFrame() { - if (lastFrameView) + if (lastFrameView && IsValid()) DrawFrame(lastFrameView, lastFrameExtent); } @@ -840,7 +900,7 @@ void VulkanContext::Term() lastFrameView = nullptr; if (!device) return; - device->waitIdle(); + WaitIdle(); ImGui_ImplVulkan_Shutdown(); gui_term(); if (device && pipelineCache) @@ -875,6 +935,8 @@ void VulkanContext::Term() allocator.Term(); #ifndef USE_SDL surface.reset(); +#else + ::vkDestroySurfaceKHR(*instance, surface.release(), nullptr); #endif pipelineCache.reset(); device.reset(); @@ -1011,3 +1073,46 @@ void VulkanContext::DoSwapAutomation() } #endif } + +bool VulkanContext::HasSurfaceDimensionChanged() const +{ + vk::SurfaceCapabilitiesKHR surfaceCapabilities = + physicalDevice.getSurfaceCapabilitiesKHR(GetSurface()); + VkExtent2D swapchainExtent; + if (surfaceCapabilities.currentExtent.width == std::numeric_limits < uint32_t > ::max()) + { + // If the surface size is undefined, the size is set to the size of the images requested. + swapchainExtent.width = std::min( + std::max(width, surfaceCapabilities.minImageExtent.width), + surfaceCapabilities.maxImageExtent.width); + swapchainExtent.height = std::min( + std::max(height, surfaceCapabilities.minImageExtent.height), + surfaceCapabilities.maxImageExtent.height); + } + else + { + // If the surface size is defined, the swap chain size must match + swapchainExtent = surfaceCapabilities.currentExtent; + } + return width != swapchainExtent.width || height != swapchainExtent.height; +} + +void VulkanContext::SetWindowSize(u32 width, u32 height) +{ + if (width != this->width || height != this->height) + { + this->width = width; + this->height = height; + // When the window is minimized, it can happen that the max surface dimension is 0,0 + // In this case, the context becomes invalid but we keep the previous + // dimensions to not confuse the renderer and imgui + if (width != 0) + screen_width = width; + + if (height != 0) + screen_height = height; + + SetResized(); + } +} + diff --git a/core/rend/vulkan/vulkan_context.h b/core/rend/vulkan/vulkan_context.h index 96e26c36a..d9b3482f3 100644 --- a/core/rend/vulkan/vulkan_context.h +++ b/core/rend/vulkan/vulkan_context.h @@ -20,7 +20,7 @@ */ #pragma once #ifdef USE_VULKAN - +#include #include "vulkan.h" #include "vmallocator.h" #include "quad.h" @@ -42,20 +42,18 @@ public: ~VulkanContext() { verify(contextInstance == this); contextInstance = nullptr; } bool Init(); - bool InitInstance(const char** extensions, uint32_t extensions_count); - bool InitDevice(); - void CreateSwapChain(); void Term(); void SetWindow(void *window, void *display) { this->window = window; this->display = display; } VkInstance GetInstance() const { return static_cast(instance.get()); } u32 GetGraphicsQueueFamilyIndex() const { return graphicsQueueIndex; } - void SetWindowSize(u32 width, u32 height) { this->width = screen_width = width; this->height = screen_height = height; } + void SetResized() { resized = true; } + bool IsValid() { return width != 0 && height != 0; } void NewFrame(); void BeginRenderPass(); void EndFrame(const std::vector *cmdBuffers = nullptr); - void Present(); - void PresentFrame(vk::ImageView imageView, vk::Offset2D extent); + void Present() noexcept; + void PresentFrame(vk::ImageView imageView, vk::Extent2D extent) noexcept; void PresentLastFrame(); vk::PhysicalDevice GetPhysicalDevice() const { return physicalDevice; } @@ -64,10 +62,10 @@ public: vk::RenderPass GetRenderPass() const { return *renderPass; } vk::CommandBuffer GetCurrentCommandBuffer() const { return *commandBuffers[GetCurrentImageIndex()]; } vk::DescriptorPool GetDescriptorPool() const { return *descriptorPool; } - vk::Extent2D GetViewPort() const { return { width, height }; } + vk::Extent2D GetViewPort() const { return { (u32)screen_width, (u32)screen_height }; } size_t GetSwapChainSize() const { return imageViews.size(); } int GetCurrentImageIndex() const { return currentImage; } - void WaitIdle() const { graphicsQueue.waitIdle(); } + void WaitIdle() const; bool IsRendering() const { return rendering; } vk::Queue GetGraphicsQueue() const { return graphicsQueue; } vk::DeviceSize GetUniformBufferAlignment() const { return uniformBufferAlignment; } @@ -86,15 +84,8 @@ public: return true; } } - std::string GetDriverName() const { vk::PhysicalDeviceProperties props; physicalDevice.getProperties(&props); return std::string(props.deviceName); } - std::string GetDriverVersion() const { - vk::PhysicalDeviceProperties props; - physicalDevice.getProperties(&props); - - return std::to_string(VK_VERSION_MAJOR(props.driverVersion)) - + "." + std::to_string(VK_VERSION_MINOR(props.driverVersion)) - + "." + std::to_string(VK_VERSION_PATCH(props.driverVersion)); - } + std::string GetDriverName() const; + std::string GetDriverVersion() const; vk::Format GetColorFormat() const { return colorFormat; } vk::Format GetDepthFormat() const { return depthFormat; } static VulkanContext *Instance() { return contextInstance; } @@ -109,42 +100,30 @@ public: const std::vector *PrepareVMUs(); void DrawVMUs(float scaling); +#ifdef VK_DEBUG + void setObjectName(u64 object, VkDebugReportObjectTypeEXT objectType, const std::string& name) + { + VkDebugMarkerObjectNameInfoEXT nameInfo = {}; + nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT; + nameInfo.objectType = objectType; + nameInfo.object = object; + nameInfo.pObjectName = name.c_str(); + vkDebugMarkerSetObjectNameEXT((VkDevice)*device, &nameInfo); + } +#endif + private: + void CreateSwapChain(); + bool InitDevice(); + bool InitInstance(const char** extensions, uint32_t extensions_count); vk::Format FindDepthFormat(); void InitImgui(); void DoSwapAutomation(); - void DrawFrame(vk::ImageView imageView, vk::Offset2D extent); - vk::SurfaceKHR GetSurface() { -#ifdef USE_SDL - return surface; -#else - return *surface; -#endif - } + void DrawFrame(vk::ImageView imageView, vk::Extent2D extent); + vk::SurfaceKHR GetSurface() const { return *surface; } - bool HasSurfaceDimensionChanged() - { - vk::SurfaceCapabilitiesKHR surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(GetSurface()); - VkExtent2D swapchainExtent; - if (surfaceCapabilities.currentExtent.width == std::numeric_limits::max()) - { - // If the surface size is undefined, the size is set to the size of the images requested. - swapchainExtent.width = std::min(std::max(width, surfaceCapabilities.minImageExtent.width), surfaceCapabilities.maxImageExtent.width); - swapchainExtent.height = std::min(std::max(height, surfaceCapabilities.minImageExtent.height), surfaceCapabilities.maxImageExtent.height); - } - else - { - // If the surface size is defined, the swap chain size must match - swapchainExtent = surfaceCapabilities.currentExtent; - } - if (width == swapchainExtent.width && height == swapchainExtent.height) - return false; - - screen_width = width = swapchainExtent.width; - screen_height = height = swapchainExtent.height; - - return true; - } + bool HasSurfaceDimensionChanged() const; + void SetWindowSize(u32 width, u32 height); VMAllocator allocator; void *window = nullptr; @@ -153,6 +132,7 @@ private: bool renderDone = false; u32 width = 0; u32 height = 0; + bool resized = false; vk::UniqueInstance instance; vk::PhysicalDevice physicalDevice; @@ -172,11 +152,7 @@ private: u32 vendorID = 0; vk::UniqueDevice device; -#ifdef USE_SDL - vk::SurfaceKHR surface; -#else vk::UniqueSurfaceKHR surface; -#endif vk::UniqueSwapchainKHR swapChain; std::vector imageViews; @@ -208,7 +184,7 @@ private: std::unique_ptr shaderManager; vk::ImageView lastFrameView; - vk::Offset2D lastFrameExtent; + vk::Extent2D lastFrameExtent; std::unique_ptr vmus; @@ -222,4 +198,9 @@ private: static VulkanContext *contextInstance; }; +class InvalidVulkanContext : public std::runtime_error { +public: + InvalidVulkanContext() : std::runtime_error("Invalid Vulkan context") {} +}; + #endif // USE_VULKAN diff --git a/core/rend/vulkan/vulkan_renderer.cpp b/core/rend/vulkan/vulkan_renderer.cpp index 2b0daf4ff..615b3d81e 100644 --- a/core/rend/vulkan/vulkan_renderer.cpp +++ b/core/rend/vulkan/vulkan_renderer.cpp @@ -55,17 +55,24 @@ public: bool Render() override { - Drawer *drawer; - if (pvrrc.isRTT) - drawer = &textureDrawer; - else - drawer = &screenDrawer; + try { + Drawer *drawer; + if (pvrrc.isRTT) + drawer = &textureDrawer; + else + drawer = &screenDrawer; - drawer->Draw(fogTexture.get(), paletteTexture.get()); + drawer->Draw(fogTexture.get(), paletteTexture.get()); - drawer->EndRenderPass(); + drawer->EndRenderPass(); - return !pvrrc.isRTT; + return !pvrrc.isRTT; + } catch (const vk::SystemError& e) { + // Sometimes happens when resizing the window + WARN_LOG(RENDERER, "Vulkan system error %s", e.what()); + + return false; + } } bool Present() override diff --git a/core/rend/vulkan/vulkan_renderer.h b/core/rend/vulkan/vulkan_renderer.h index a4f074942..9b50d097d 100644 --- a/core/rend/vulkan/vulkan_renderer.h +++ b/core/rend/vulkan/vulkan_renderer.h @@ -65,7 +65,7 @@ public: virtual void Term() override { - GetContext()->PresentFrame(nullptr, vk::Offset2D()); + GetContext()->PresentFrame(nullptr, vk::Extent2D()); osdBuffer.reset(); vjoyTexture.reset(); textureCache.Clear(); @@ -147,40 +147,43 @@ public: gui_display_osd(); if (!vjoyTexture) return; - if (clear_screen) - { - GetContext()->NewFrame(); - GetContext()->BeginRenderPass(); + try { + if (clear_screen) + { + GetContext()->NewFrame(); + GetContext()->BeginRenderPass(); + } + const float dc2s_scale_h = screen_height / 480.0f; + const float sidebarWidth = (screen_width - dc2s_scale_h * 640.0f) / 2; + + std::vector osdVertices = GetOSDVertices(); + const float x1 = 2.0f / (screen_width / dc2s_scale_h); + const float y1 = 2.0f / 480; + const float x2 = 1 - 2 * sidebarWidth / screen_width; + const float y2 = 1; + for (OSDVertex& vtx : osdVertices) + { + vtx.x = vtx.x * x1 - x2; + vtx.y = vtx.y * y1 - y2; + } + + const vk::CommandBuffer cmdBuffer = GetContext()->GetCurrentCommandBuffer(); + cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, osdPipeline.GetPipeline()); + + osdPipeline.BindDescriptorSets(cmdBuffer); + const vk::Viewport viewport(0, 0, (float)screen_width, (float)screen_height, 0, 1.f); + cmdBuffer.setViewport(0, 1, &viewport); + const vk::Rect2D scissor({ 0, 0 }, { (u32)screen_width, (u32)screen_height }); + cmdBuffer.setScissor(0, 1, &scissor); + osdBuffer->upload(osdVertices.size() * sizeof(OSDVertex), osdVertices.data()); + const vk::DeviceSize zero = 0; + cmdBuffer.bindVertexBuffers(0, 1, &osdBuffer->buffer.get(), &zero); + for (size_t i = 0; i < osdVertices.size(); i += 4) + cmdBuffer.draw(4, 1, i, 0); + if (clear_screen) + GetContext()->EndFrame(); + } catch (const InvalidVulkanContext& err) { } - const float dc2s_scale_h = screen_height / 480.0f; - const float sidebarWidth = (screen_width - dc2s_scale_h * 640.0f) / 2; - - std::vector osdVertices = GetOSDVertices(); - const float x1 = 2.0f / (screen_width / dc2s_scale_h); - const float y1 = 2.0f / 480; - const float x2 = 1 - 2 * sidebarWidth / screen_width; - const float y2 = 1; - for (OSDVertex& vtx : osdVertices) - { - vtx.x = vtx.x * x1 - x2; - vtx.y = vtx.y * y1 - y2; - } - - const vk::CommandBuffer cmdBuffer = GetContext()->GetCurrentCommandBuffer(); - cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, osdPipeline.GetPipeline()); - - osdPipeline.BindDescriptorSets(cmdBuffer); - const vk::Viewport viewport(0, 0, (float)screen_width, (float)screen_height, 0, 1.f); - cmdBuffer.setViewport(0, 1, &viewport); - const vk::Rect2D scissor({ 0, 0 }, { (u32)screen_width, (u32)screen_height }); - cmdBuffer.setScissor(0, 1, &scissor); - osdBuffer->upload(osdVertices.size() * sizeof(OSDVertex), osdVertices.data()); - const vk::DeviceSize zero = 0; - cmdBuffer.bindVertexBuffers(0, 1, &osdBuffer->buffer.get(), &zero); - for (size_t i = 0; i < osdVertices.size(); i += 4) - cmdBuffer.draw(4, 1, i, 0); - if (clear_screen) - GetContext()->EndFrame(); } protected: diff --git a/core/sdl/sdl.cpp b/core/sdl/sdl.cpp index 8de7d47b4..1b0cc0e89 100644 --- a/core/sdl/sdl.cpp +++ b/core/sdl/sdl.cpp @@ -145,6 +145,17 @@ void input_sdl_handle() for (int i = 0; event.text.text[i] != '\0'; i++) sdl_keyboard->keyboard_character(event.text.text[i]); break; +#ifdef USE_VULKAN + case SDL_WINDOWEVENT: + if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED + || event.window.event == SDL_WINDOWEVENT_RESTORED + || event.window.event == SDL_WINDOWEVENT_MINIMIZED + || event.window.event == SDL_WINDOWEVENT_MAXIMIZED) + { + theVulkanContext.SetResized(); + } + break; +#endif #endif case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: