#include "renderer_vk/renderer_vk.hpp" #include #include #include #include "SDL_vulkan.h" #include "helpers.hpp" #include "renderer_vk/vk_debug.hpp" #include "renderer_vk/vk_memory.hpp" #include "renderer_vk/vk_pica.hpp" // Finds the first queue family that satisfies `queueMask` and excludes `queueExcludeMask` bits // Returns -1 if not found // Todo: Smarter selection for present/graphics/compute/transfer static s32 findQueueFamily( std::span queueFamilies, vk::QueueFlags queueMask, vk::QueueFlags queueExcludeMask = vk::QueueFlagBits::eProtected ) { for (usize i = 0; i < queueFamilies.size(); ++i) { if (((queueFamilies[i].queueFlags & queueMask) == queueMask) && !(queueFamilies[i].queueFlags & queueExcludeMask)) { return i; } } return -1; } static u32 rotl32(u32 x, u32 n) { return (x << n) | (x >> (32 - n)); } static u32 ror32(u32 x, u32 n) { return (x >> n) | (x << (32 - n)); } u32 RendererVK::colorBufferHash(u32 loc, u32 size, PICA::ColorFmt format) { return rotl32(loc, 17) ^ ror32(size, 23) ^ (static_cast(format) << 60); } u32 RendererVK::depthBufferHash(u32 loc, u32 size, PICA::DepthFmt format) { return rotl32(loc, 17) ^ ror32(size, 29) ^ (static_cast(format) << 60); } RendererVK::Texture& RendererVK::getColorRenderTexture(u32 addr, PICA::ColorFmt format, u32 width, u32 height) { const u32 renderTextureHash = colorBufferHash(addr, width * height * PICA::sizePerPixel(format), format); // Cache hit if (textureCache.contains(renderTextureHash)) { return textureCache.at(renderTextureHash); } // Cache miss Texture& newTexture = textureCache[renderTextureHash]; newTexture.loc = addr; newTexture.sizePerPixel = PICA::sizePerPixel(format); newTexture.size = fbSize; vk::ImageCreateInfo textureInfo = {}; textureInfo.setImageType(vk::ImageType::e2D); textureInfo.setFormat(Vulkan::colorFormatToVulkan(format)); textureInfo.setExtent(vk::Extent3D(width, height, 1)); textureInfo.setMipLevels(1); textureInfo.setArrayLayers(1); textureInfo.setSamples(vk::SampleCountFlagBits::e1); textureInfo.setTiling(vk::ImageTiling::eOptimal); textureInfo.setUsage( vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eInputAttachment | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst ); textureInfo.setSharingMode(vk::SharingMode::eExclusive); textureInfo.setInitialLayout(vk::ImageLayout::eUndefined); if (auto createResult = device->createImageUnique(textureInfo); createResult.result == vk::Result::eSuccess) { newTexture.image = std::move(createResult.value); } else { Helpers::panic("Error creating color render-texture image: %s\n", vk::to_string(createResult.result).c_str()); } vk::ImageViewCreateInfo viewInfo = {}; viewInfo.image = newTexture.image.get(); viewInfo.viewType = vk::ImageViewType::e2D; viewInfo.format = textureInfo.format; viewInfo.components = vk::ComponentMapping(); viewInfo.subresourceRange = vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1); if (auto [result, imageMemory] = Vulkan::commitImageHeap(device.get(), physicalDevice, {&newTexture.image.get(), 1}); result == vk::Result::eSuccess) { newTexture.imageMemory = std::move(imageMemory); } else { Helpers::panic("Error allocating color render-texture memory: %s\n", vk::to_string(result).c_str()); } if (auto createResult = device->createImageViewUnique(viewInfo); createResult.result == vk::Result::eSuccess) { newTexture.imageView = std::move(createResult.value); } else { Helpers::panic("Error creating color render-texture: %s\n", vk::to_string(createResult.result).c_str()); } return newTexture; } RendererVK::Texture& RendererVK::getDepthRenderTexture(u32 addr, PICA::DepthFmt format, u32 width, u32 height) { const u32 renderTextureHash = depthBufferHash(addr, width * height * PICA::sizePerPixel(format), format); // Cache hit if (textureCache.contains(renderTextureHash)) { return textureCache.at(renderTextureHash); } // Cache miss Texture& newTexture = textureCache[renderTextureHash]; newTexture.loc = addr; newTexture.sizePerPixel = PICA::sizePerPixel(format); newTexture.size = fbSize; vk::ImageCreateInfo textureInfo = {}; textureInfo.setImageType(vk::ImageType::e2D); textureInfo.setFormat(Vulkan::depthFormatToVulkan(format)); textureInfo.setExtent(vk::Extent3D(width, height, 1)); textureInfo.setMipLevels(1); textureInfo.setArrayLayers(1); textureInfo.setSamples(vk::SampleCountFlagBits::e1); textureInfo.setTiling(vk::ImageTiling::eOptimal); textureInfo.setUsage( vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eInputAttachment | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst ); textureInfo.setSharingMode(vk::SharingMode::eExclusive); textureInfo.setInitialLayout(vk::ImageLayout::eUndefined); if (auto createResult = device->createImageUnique(textureInfo); createResult.result == vk::Result::eSuccess) { newTexture.image = std::move(createResult.value); } else { Helpers::panic("Error creating depth render-texture image: %s\n", vk::to_string(createResult.result).c_str()); } vk::ImageViewCreateInfo viewInfo = {}; viewInfo.image = newTexture.image.get(); viewInfo.viewType = vk::ImageViewType::e2D; viewInfo.format = textureInfo.format; viewInfo.components = vk::ComponentMapping(); viewInfo.subresourceRange = vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1); if (auto [result, imageMemory] = Vulkan::commitImageHeap(device.get(), physicalDevice, {&newTexture.image.get(), 1}); result == vk::Result::eSuccess) { newTexture.imageMemory = std::move(imageMemory); } else { Helpers::panic("Error allocating depth render-texture memory: %s\n", vk::to_string(result).c_str()); } if (auto createResult = device->createImageViewUnique(viewInfo); createResult.result == vk::Result::eSuccess) { newTexture.imageView = std::move(createResult.value); } else { Helpers::panic("Error creating depth render-texture: %s\n", vk::to_string(createResult.result).c_str()); } return newTexture; } vk::RenderPass RendererVK::getRenderPass(vk::Format colorFormat, std::optional depthFormat) { u64 renderPassHash = static_cast(colorFormat); if (depthFormat.has_value()) { renderPassHash |= (static_cast(depthFormat.value()) << 32); } // Cache hit if (renderPassCache.contains(renderPassHash)) { return renderPassCache.at(renderPassHash).get(); } // Cache miss vk::RenderPassCreateInfo renderPassInfo = {}; vk::SubpassDescription subPass = {}; std::vector renderPassAttachments = {}; vk::AttachmentDescription colorAttachment = {}; colorAttachment.format = colorFormat; colorAttachment.samples = vk::SampleCountFlagBits::e1; colorAttachment.loadOp = vk::AttachmentLoadOp::eLoad; colorAttachment.storeOp = vk::AttachmentStoreOp::eStore; colorAttachment.stencilLoadOp = vk::AttachmentLoadOp::eLoad; colorAttachment.stencilStoreOp = vk::AttachmentStoreOp::eStore; colorAttachment.initialLayout = vk::ImageLayout::eColorAttachmentOptimal; colorAttachment.finalLayout = vk::ImageLayout::eColorAttachmentOptimal; renderPassAttachments.emplace_back(colorAttachment); if (depthFormat.has_value()) { vk::AttachmentDescription depthAttachment = {}; depthAttachment.format = depthFormat.value(); depthAttachment.samples = vk::SampleCountFlagBits::e1; depthAttachment.loadOp = vk::AttachmentLoadOp::eLoad; depthAttachment.storeOp = vk::AttachmentStoreOp::eStore; depthAttachment.stencilLoadOp = vk::AttachmentLoadOp::eLoad; depthAttachment.stencilStoreOp = vk::AttachmentStoreOp::eStore; depthAttachment.initialLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; depthAttachment.finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; renderPassAttachments.emplace_back(depthAttachment); } renderPassInfo.setAttachments(renderPassAttachments); static const vk::AttachmentReference colorAttachmentReference = {0, vk::ImageLayout::eColorAttachmentOptimal}; static const vk::AttachmentReference depthAttachmentReference = {1, vk::ImageLayout::eDepthStencilReadOnlyOptimal}; subPass.setColorAttachments(colorAttachmentReference); if (depthFormat.has_value()) { subPass.setPDepthStencilAttachment(&depthAttachmentReference); } subPass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics; renderPassInfo.setSubpasses(subPass); // We only have one sub-pass and we want all render-passes to be sequential, // so input/output depends on VK_SUBPASS_EXTERNAL static const vk::SubpassDependency subpassDependencies[2] = { vk::SubpassDependency( VK_SUBPASS_EXTERNAL, 0, vk::PipelineStageFlagBits::eAllGraphics, vk::PipelineStageFlagBits::eAllGraphics, vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eColorAttachmentWrite, vk::DependencyFlagBits::eByRegion ), vk::SubpassDependency( 0, VK_SUBPASS_EXTERNAL, vk::PipelineStageFlagBits::eAllGraphics, vk::PipelineStageFlagBits::eAllGraphics, vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eColorAttachmentWrite, vk::DependencyFlagBits::eByRegion )}; renderPassInfo.setDependencies(subpassDependencies); if (auto createResult = device->createRenderPassUnique(renderPassInfo); createResult.result == vk::Result::eSuccess) { return (renderPassCache[renderPassHash] = std::move(createResult.value)).get(); } else { Helpers::panic("Error creating render pass: %s\n", vk::to_string(createResult.result).c_str()); } return {}; } vk::RenderPass RendererVK::getRenderPass(PICA::ColorFmt colorFormat, std::optional depthFormat) { if (depthFormat.has_value()) { return getRenderPass(Vulkan::colorFormatToVulkan(colorFormat), Vulkan::depthFormatToVulkan(depthFormat.value())); } else { return getRenderPass(Vulkan::colorFormatToVulkan(colorFormat), {}); } } vk::Result RendererVK::recreateSwapchain(vk::SurfaceKHR surface, vk::Extent2D swapchainExtent) { static constexpr u32 screenTextureWidth = 400; // Top screen is 400 pixels wide, bottom is 320 static constexpr u32 screenTextureHeight = 2 * 240; // Both screens are 240 pixels tall static constexpr vk::ImageUsageFlags swapchainUsageFlagsRequired = (vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst); // Extent + Image count + Usage + Surface Transform vk::ImageUsageFlags swapchainImageUsage; vk::SurfaceTransformFlagBitsKHR swapchainSurfaceTransform; if (const auto getResult = physicalDevice.getSurfaceCapabilitiesKHR(surface); getResult.result == vk::Result::eSuccess) { const vk::SurfaceCapabilitiesKHR& surfaceCapabilities = getResult.value; // In the case if width == height == -1, we define the extent ourselves but must fit within the limits if (surfaceCapabilities.currentExtent.width == -1 || surfaceCapabilities.currentExtent.height == -1) { swapchainExtent.width = std::max(swapchainExtent.width, surfaceCapabilities.minImageExtent.width); swapchainExtent.height = std::max(swapchainExtent.height, surfaceCapabilities.minImageExtent.height); swapchainExtent.width = std::min(swapchainExtent.width, surfaceCapabilities.maxImageExtent.width); swapchainExtent.height = std::min(swapchainExtent.height, surfaceCapabilities.maxImageExtent.height); } swapchainImageCount = surfaceCapabilities.minImageCount + 1; if ((surfaceCapabilities.maxImageCount > 0) && (swapchainImageCount > surfaceCapabilities.maxImageCount)) { swapchainImageCount = surfaceCapabilities.maxImageCount; } swapchainImageUsage = surfaceCapabilities.supportedUsageFlags & swapchainUsageFlagsRequired; if ((swapchainImageUsage & swapchainUsageFlagsRequired) != swapchainUsageFlagsRequired) { Helpers::panic( "Unsupported swapchain image usage. Could not acquire %s\n", vk::to_string(swapchainImageUsage ^ swapchainUsageFlagsRequired).c_str() ); } if (surfaceCapabilities.supportedTransforms & vk::SurfaceTransformFlagBitsKHR::eIdentity) { swapchainSurfaceTransform = vk::SurfaceTransformFlagBitsKHR::eIdentity; } else { swapchainSurfaceTransform = surfaceCapabilities.currentTransform; } } else { Helpers::panic("Error getting surface capabilities: %s\n", vk::to_string(getResult.result).c_str()); } // Preset Mode // Fifo support is required by all vulkan implementations, waits for vsync vk::PresentModeKHR swapchainPresentMode = vk::PresentModeKHR::eFifo; if (auto getResult = physicalDevice.getSurfacePresentModesKHR(surface); getResult.result == vk::Result::eSuccess) { const std::vector& presentModes = getResult.value; // Use mailbox if available, lowest-latency vsync-enabled mode if (std::find(presentModes.begin(), presentModes.end(), vk::PresentModeKHR::eMailbox) != presentModes.end()) { swapchainPresentMode = vk::PresentModeKHR::eMailbox; } } else { Helpers::panic("Error enumerating surface present modes: %s\n", vk::to_string(getResult.result).c_str()); } // Surface format vk::SurfaceFormatKHR swapchainSurfaceFormat; if (auto getResult = physicalDevice.getSurfaceFormatsKHR(surface); getResult.result == vk::Result::eSuccess) { std::vector& surfaceFormats = getResult.value; // A singular undefined surface format means we can use any format we want if ((surfaceFormats.size() == 1) && surfaceFormats[0].format == vk::Format::eUndefined) { // Assume R8G8B8A8-SRGB by default swapchainSurfaceFormat = {vk::Format::eR8G8B8A8Unorm, vk::ColorSpaceKHR::eSrgbNonlinear}; } else { // Find the next-best R8G8B8A8-SRGB format std::vector::iterator partitionEnd = surfaceFormats.end(); const auto preferR8G8B8A8 = [](const vk::SurfaceFormatKHR& surfaceFormat) -> bool { return surfaceFormat.format == vk::Format::eR8G8B8A8Snorm; }; partitionEnd = std::stable_partition(surfaceFormats.begin(), partitionEnd, preferR8G8B8A8); const auto preferSrgbNonLinear = [](const vk::SurfaceFormatKHR& surfaceFormat) -> bool { return surfaceFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }; partitionEnd = std::stable_partition(surfaceFormats.begin(), partitionEnd, preferSrgbNonLinear); swapchainSurfaceFormat = surfaceFormats.front(); } } else { Helpers::panic("Error enumerating surface formats: %s\n", vk::to_string(getResult.result).c_str()); } vk::SwapchainCreateInfoKHR swapchainInfo = {}; swapchainInfo.surface = surface; swapchainInfo.minImageCount = swapchainImageCount; swapchainInfo.imageFormat = swapchainSurfaceFormat.format; swapchainInfo.imageColorSpace = swapchainSurfaceFormat.colorSpace; swapchainInfo.imageExtent = swapchainExtent; swapchainInfo.imageArrayLayers = 1; swapchainInfo.imageUsage = swapchainImageUsage; swapchainInfo.imageSharingMode = vk::SharingMode::eExclusive; swapchainInfo.preTransform = swapchainSurfaceTransform; swapchainInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; swapchainInfo.presentMode = swapchainPresentMode; swapchainInfo.clipped = true; swapchainInfo.oldSwapchain = swapchain.get(); if (auto createResult = device->createSwapchainKHRUnique(swapchainInfo); createResult.result == vk::Result::eSuccess) { swapchain = std::move(createResult.value); } else { Helpers::panic("Error creating swapchain: %s\n", vk::to_string(createResult.result).c_str()); } // Get swapchain images if (auto getResult = device->getSwapchainImagesKHR(swapchain.get()); getResult.result == vk::Result::eSuccess) { swapchainImages = getResult.value; swapchainImageViews.resize(swapchainImages.size()); // Create image-views for (usize i = 0; i < swapchainImages.size(); i++) { vk::ImageViewCreateInfo viewInfo = {}; viewInfo.image = swapchainImages[i]; viewInfo.viewType = vk::ImageViewType::e2D; viewInfo.format = swapchainSurfaceFormat.format; viewInfo.components = vk::ComponentMapping(); viewInfo.subresourceRange = vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1); if (auto createResult = device->createImageViewUnique(viewInfo); createResult.result == vk::Result::eSuccess) { swapchainImageViews[i] = std::move(createResult.value); } else { Helpers::panic("Error creating swapchain image-view: #%zu %s\n", i, vk::to_string(getResult.result).c_str()); } } } else { Helpers::panic("Error creating acquiring swapchain images: %s\n", vk::to_string(getResult.result).c_str()); } return vk::Result::eSuccess; } RendererVK::RendererVK(GPU& gpu, const std::array& internalRegs, const std::array& externalRegs) : Renderer(gpu, internalRegs, externalRegs) {} RendererVK::~RendererVK() {} void RendererVK::reset() { renderPassCache.clear(); } void RendererVK::display() { // Get the next available swapchain image, and signal the semaphore when it's ready static constexpr u32 swapchainImageInvalid = std::numeric_limits::max(); u32 swapchainImageIndex = swapchainImageInvalid; if (swapchain) { if (const auto acquireResult = device->acquireNextImageKHR(swapchain.get(), std::numeric_limits::max(), swapImageFreeSemaphore[frameBufferingIndex].get(), {}); acquireResult.result == vk::Result::eSuccess) { swapchainImageIndex = acquireResult.value; } else { switch (acquireResult.result) { case vk::Result::eSuboptimalKHR: case vk::Result::eErrorOutOfDateKHR: { // Surface resized vk::Extent2D swapchainExtent; { int windowWidth, windowHeight; // Block until we have a valid surface-area to present to // Usually this is because the window has been minimized // Todo: We should still be rendering even without a valid swapchain do { SDL_Vulkan_GetDrawableSize(targetWindow, &windowWidth, &windowHeight); } while (!windowWidth || !windowHeight); swapchainExtent.width = windowWidth; swapchainExtent.height = windowHeight; } recreateSwapchain(swapchainSurface, swapchainExtent); break; } default: { Helpers::panic("Error acquiring next swapchain image: %s\n", vk::to_string(acquireResult.result).c_str()); } } } } const bool topActiveFb = externalRegs[PICA::ExternalRegs::Framebuffer0Select] & 1; const u32 topScreenAddr = externalRegs[topActiveFb ? PICA::ExternalRegs::Framebuffer0AFirstAddr : PICA::ExternalRegs::Framebuffer0ASecondAddr]; const bool bottomActiveFb = externalRegs[PICA::ExternalRegs::Framebuffer1Select] & 1; const u32 bottomScreenAddr = externalRegs[bottomActiveFb ? PICA::ExternalRegs::Framebuffer1AFirstAddr : PICA::ExternalRegs::Framebuffer1ASecondAddr]; //// Render Frame(Simulated - just clear the images to different colors for now) { static const std::array frameScopeColor = {{1.0f, 0.0f, 1.0f, 1.0f}}; Vulkan::DebugLabelScope debugScope(getCurrentCommandBuffer(), frameScopeColor, "Frame"); // Prepare images for color-clear getCurrentCommandBuffer().pipelineBarrier( vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags(), {}, {}, { vk::ImageMemoryBarrier( vk::AccessFlagBits::eMemoryRead, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, screenTexture[frameBufferingIndex].get(), vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) ), } ); static const std::array topClearColor = {{1.0f, 0.0f, 0.0f, 1.0f}}; static const std::array bottomClearColor = {{0.0f, 1.0f, 0.0f, 1.0f}}; getCurrentCommandBuffer().clearColorImage( screenTexture[frameBufferingIndex].get(), vk::ImageLayout::eTransferDstOptimal, topClearColor, vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) ); } //// Present if (swapchainImageIndex != swapchainImageInvalid) { static const std::array presentScopeColor = {{1.0f, 1.0f, 0.0f, 1.0f}}; Vulkan::DebugLabelScope debugScope(getCurrentCommandBuffer(), presentScopeColor, "Present"); // Prepare swapchain image for color-clear/blit-dst, prepare top/bottom screen for blit-src getCurrentCommandBuffer().pipelineBarrier( vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags(), {}, {}, { vk::ImageMemoryBarrier( vk::AccessFlagBits::eMemoryRead, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, swapchainImages[swapchainImageIndex], vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) ), vk::ImageMemoryBarrier( vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eTransferRead, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eTransferSrcOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, screenTexture[frameBufferingIndex].get(), vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) ), } ); static const std::array clearColor = {{0.0f, 0.0f, 0.0f, 1.0f}}; getCurrentCommandBuffer().clearColorImage( swapchainImages[swapchainImageIndex], vk::ImageLayout::eTransferDstOptimal, clearColor, vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) ); // Blit top/bottom screen into swapchain image static const vk::ImageBlit screenBlit( vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1), {vk::Offset3D{}, vk::Offset3D{400, 240 * 2, 1}}, vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1), {vk::Offset3D{}, vk::Offset3D{400, 240 * 2, 1}} ); getCurrentCommandBuffer().blitImage( screenTexture[frameBufferingIndex].get(), vk::ImageLayout::eTransferSrcOptimal, swapchainImages[swapchainImageIndex], vk::ImageLayout::eTransferDstOptimal, {screenBlit}, vk::Filter::eNearest ); // Prepare swapchain image for present getCurrentCommandBuffer().pipelineBarrier( vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::DependencyFlags(), {}, {}, {vk::ImageMemoryBarrier( vk::AccessFlagBits::eNone, vk::AccessFlagBits::eColorAttachmentWrite, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::ePresentSrcKHR, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, swapchainImages[swapchainImageIndex], vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) )} ); } if (const vk::Result endResult = getCurrentCommandBuffer().end(); endResult != vk::Result::eSuccess) { Helpers::panic("Error ending command buffer recording: %s\n", vk::to_string(endResult).c_str()); } vk::SubmitInfo submitInfo = {}; // Wait for any previous uses of the image image to finish presenting std::vector waitSemaphores; std::vector waitSemaphoreStages; { if (swapchainImageIndex != swapchainImageInvalid) { waitSemaphores.emplace_back(swapImageFreeSemaphore[frameBufferingIndex].get()); static const vk::PipelineStageFlags waitStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; waitSemaphoreStages.emplace_back(waitStageMask); } // Ensure a proper semaphore wait on render-finished // We already wait on the fence, but this must be done to be compliant // to validation layers waitSemaphores.emplace_back(renderFinishedSemaphore[frameBufferingIndex].get()); waitSemaphoreStages.emplace_back(vk::PipelineStageFlagBits::eColorAttachmentOutput); submitInfo.setWaitSemaphores(waitSemaphores); submitInfo.setWaitDstStageMask(waitSemaphoreStages); } // Signal when finished submitInfo.setSignalSemaphores(renderFinishedSemaphore[frameBufferingIndex].get()); submitInfo.setCommandBuffers(getCurrentCommandBuffer()); device->resetFences({frameFinishedFences[frameBufferingIndex].get()}); if (const vk::Result submitResult = graphicsQueue.submit({submitInfo}, frameFinishedFences[frameBufferingIndex].get()); submitResult != vk::Result::eSuccess) { Helpers::panic("Error submitting to graphics queue: %s\n", vk::to_string(submitResult).c_str()); } if (swapchainImageIndex != swapchainImageInvalid) { vk::PresentInfoKHR presentInfo = {}; presentInfo.setWaitSemaphores(renderFinishedSemaphore[frameBufferingIndex].get()); presentInfo.setSwapchains(swapchain.get()); presentInfo.setImageIndices(swapchainImageIndex); if (const auto presentResult = presentQueue.presentKHR(presentInfo); presentResult == vk::Result::eSuccess) { } else { switch (presentResult) { case vk::Result::eSuboptimalKHR: case vk::Result::eErrorOutOfDateKHR: { // Surface resized vk::Extent2D swapchainExtent; { int windowWidth, windowHeight; SDL_Vulkan_GetDrawableSize(targetWindow, &windowWidth, &windowHeight); swapchainExtent.width = windowWidth; swapchainExtent.height = windowHeight; } recreateSwapchain(swapchainSurface, swapchainExtent); break; } default: { Helpers::panic("Error presenting swapchain image: %s\n", vk::to_string(presentResult).c_str()); } } } } // We are now working on the next frame frameBufferingIndex = ((frameBufferingIndex + 1) % frameBufferingCount); // Wait for next frame to be ready // Block, on the CPU, to ensure that this buffered-frame is ready for more work if (auto waitResult = device->waitForFences({frameFinishedFences[frameBufferingIndex].get()}, true, std::numeric_limits::max()); waitResult != vk::Result::eSuccess) { Helpers::panic("Error waiting on swapchain fence: %s\n", vk::to_string(waitResult).c_str()); } { frameFramebuffers[frameBufferingIndex].clear(); getCurrentCommandBuffer().reset(); vk::CommandBufferBeginInfo beginInfo = {}; beginInfo.flags = vk::CommandBufferUsageFlagBits::eSimultaneousUse; if (const vk::Result beginResult = getCurrentCommandBuffer().begin(beginInfo); beginResult != vk::Result::eSuccess) { Helpers::panic("Error beginning command buffer recording: %s\n", vk::to_string(beginResult).c_str()); } } } void RendererVK::initGraphicsContext(SDL_Window* window) { targetWindow = window; // Resolve all instance function pointers static vk::DynamicLoader dl; VULKAN_HPP_DEFAULT_DISPATCHER.init(dl.getProcAddress("vkGetInstanceProcAddr")); // Create Instance vk::ApplicationInfo applicationInfo = {}; applicationInfo.apiVersion = VK_API_VERSION_1_1; applicationInfo.pEngineName = "Alber"; applicationInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); applicationInfo.pApplicationName = "Alber"; applicationInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); vk::InstanceCreateInfo instanceInfo = {}; instanceInfo.pApplicationInfo = &applicationInfo; std::unordered_set instanceExtensionsAvailable = {}; if (const auto enumerateResult = vk::enumerateInstanceExtensionProperties(); enumerateResult.result == vk::Result::eSuccess) { for (const auto& curExtension : enumerateResult.value) { instanceExtensionsAvailable.emplace(curExtension.extensionName.data()); } } std::vector instanceExtensions = {}; if (instanceExtensionsAvailable.contains(VK_KHR_SURFACE_EXTENSION_NAME)) { instanceExtensions.emplace_back(VK_KHR_SURFACE_EXTENSION_NAME); } bool debugUtils = false; if (instanceExtensionsAvailable.contains(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) { instanceExtensions.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); debugUtils = true; } #if defined(__APPLE__) if (instanceExtensionsAvailable.contains(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) { instanceExtensions.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); } #endif // Get any additional extensions that SDL wants as well if (targetWindow) { unsigned int extensionCount = 0; SDL_Vulkan_GetInstanceExtensions(targetWindow, &extensionCount, nullptr); std::vector sdlInstanceExtensions(extensionCount); SDL_Vulkan_GetInstanceExtensions(targetWindow, &extensionCount, sdlInstanceExtensions.data()); instanceExtensions.insert(instanceExtensions.end(), sdlInstanceExtensions.begin(), sdlInstanceExtensions.end()); } #if defined(__APPLE__) instanceInfo.flags |= vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR; #endif instanceInfo.ppEnabledExtensionNames = instanceExtensions.data(); instanceInfo.enabledExtensionCount = instanceExtensions.size(); if (auto createResult = vk::createInstanceUnique(instanceInfo); createResult.result == vk::Result::eSuccess) { instance = std::move(createResult.value); } else { Helpers::panic("Error creating Vulkan instance: %s\n", vk::to_string(createResult.result).c_str()); } // Initialize instance-specific function pointers VULKAN_HPP_DEFAULT_DISPATCHER.init(instance.get()); // Enable debug messenger if the instance was able to be created with debug_utils if (debugUtils) { vk::DebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; debugCreateInfo.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning; debugCreateInfo.messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation | vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral; debugCreateInfo.pfnUserCallback = &Vulkan::debugMessageCallback; if (auto createResult = instance->createDebugUtilsMessengerEXTUnique(debugCreateInfo); createResult.result == vk::Result::eSuccess) { debugMessenger = std::move(createResult.value); } else { Helpers::warn("Error registering debug messenger: %s", vk::to_string(createResult.result).c_str()); } } // Create surface if (window) { if (VkSurfaceKHR newSurface; SDL_Vulkan_CreateSurface(window, instance.get(), &newSurface)) { swapchainSurface = newSurface; } else { Helpers::warn("Error creating Vulkan surface"); } } // Pick physical device if (auto enumerateResult = instance->enumeratePhysicalDevices(); enumerateResult.result == vk::Result::eSuccess) { std::vector physicalDevices = std::move(enumerateResult.value); std::vector::iterator partitionEnd = physicalDevices.end(); // Prefer GPUs that can access the surface if (swapchainSurface) { const auto surfaceSupport = [this](const vk::PhysicalDevice& physicalDevice) -> bool { const usize queueCount = physicalDevice.getQueueFamilyProperties().size(); for (usize queueIndex = 0; queueIndex < queueCount; ++queueIndex) { if (auto supportResult = physicalDevice.getSurfaceSupportKHR(queueIndex, swapchainSurface); supportResult.result == vk::Result::eSuccess) { return supportResult.value; } } return false; }; partitionEnd = std::stable_partition(physicalDevices.begin(), partitionEnd, surfaceSupport); } // Prefer Discrete GPUs const auto isDiscrete = [](const vk::PhysicalDevice& physicalDevice) -> bool { return physicalDevice.getProperties().deviceType == vk::PhysicalDeviceType::eDiscreteGpu; }; partitionEnd = std::stable_partition(physicalDevices.begin(), partitionEnd, isDiscrete); // Pick the "best" out of all of the previous criteria, preserving the order that the // driver gave us the devices in(ex: optimus configuration) physicalDevice = physicalDevices.front(); } else { Helpers::panic("Error enumerating physical devices: %s\n", vk::to_string(enumerateResult.result).c_str()); } // Get device queues std::vector deviceQueueInfos; { const std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); std::unordered_set queueFamilyRequests; // Get present queue family if (swapchainSurface) { for (usize queueFamilyIndex = 0; queueFamilyIndex < queueFamilyProperties.size(); ++queueFamilyIndex) { if (auto supportResult = physicalDevice.getSurfaceSupportKHR(queueFamilyIndex, swapchainSurface); supportResult.result == vk::Result::eSuccess) { if (supportResult.value) { presentQueueFamily = queueFamilyIndex; break; } } } queueFamilyRequests.emplace(presentQueueFamily); } static const float queuePriority = 1.0f; graphicsQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eGraphics); queueFamilyRequests.emplace(graphicsQueueFamily); computeQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eCompute); queueFamilyRequests.emplace(computeQueueFamily); transferQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eTransfer); queueFamilyRequests.emplace(transferQueueFamily); // Requests a singular queue for each unique queue-family for (const u32 queueFamilyIndex : queueFamilyRequests) { deviceQueueInfos.emplace_back(vk::DeviceQueueCreateInfo({}, queueFamilyIndex, 1, &queuePriority)); } } // Create Device vk::DeviceCreateInfo deviceInfo = {}; // Device extensions std::vector deviceExtensions = { #if defined(__APPLE__) "VK_KHR_portability_subset", #endif // VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME }; std::unordered_set physicalDeviceExtensions; if (const auto enumerateResult = physicalDevice.enumerateDeviceExtensionProperties(); enumerateResult.result == vk::Result::eSuccess) { for (const auto& extension : enumerateResult.value) { physicalDeviceExtensions.insert(extension.extensionName); } } else { Helpers::panic("Error enumerating physical devices extensions: %s\n", vk::to_string(enumerateResult.result).c_str()); } // Opertional extensions // Optionally enable the swapchain, to support "headless" rendering if (physicalDeviceExtensions.contains(VK_KHR_SWAPCHAIN_EXTENSION_NAME)) { deviceExtensions.emplace_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); } deviceInfo.setPEnabledExtensionNames(deviceExtensions); vk::StructureChain deviceFeatureChain = {}; auto& deviceFeatures = deviceFeatureChain.get().features; auto& deviceTimelineFeatures = deviceFeatureChain.get(); // deviceTimelineFeatures.timelineSemaphore = true; deviceInfo.pNext = &deviceFeatureChain.get(); deviceInfo.setQueueCreateInfos(deviceQueueInfos); if (auto createResult = physicalDevice.createDeviceUnique(deviceInfo); createResult.result == vk::Result::eSuccess) { device = std::move(createResult.value); } else { Helpers::panic("Error creating logical device: %s\n", vk::to_string(createResult.result).c_str()); } // Initialize device-specific function pointers VULKAN_HPP_DEFAULT_DISPATCHER.init(device.get()); if (presentQueueFamily != VK_QUEUE_FAMILY_IGNORED) { presentQueue = device->getQueue(presentQueueFamily, 0); } graphicsQueue = device->getQueue(graphicsQueueFamily, 0); computeQueue = device->getQueue(computeQueueFamily, 0); transferQueue = device->getQueue(transferQueueFamily, 0); // Command pool vk::CommandPoolCreateInfo commandPoolInfo = {}; commandPoolInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; if (auto createResult = device->createCommandPoolUnique(commandPoolInfo); createResult.result == vk::Result::eSuccess) { commandPool = std::move(createResult.value); } else { Helpers::panic("Error creating command pool: %s\n", vk::to_string(createResult.result).c_str()); } // Create swapchain if (targetWindow && swapchainSurface) { vk::Extent2D swapchainExtent; { int windowWidth, windowHeight; SDL_Vulkan_GetDrawableSize(window, &windowWidth, &windowHeight); swapchainExtent.width = windowWidth; swapchainExtent.height = windowHeight; } recreateSwapchain(swapchainSurface, swapchainExtent); } // Create frame-buffering data // Frame-buffering Command buffer(s) vk::CommandBufferAllocateInfo commandBuffersInfo = {}; commandBuffersInfo.commandPool = commandPool.get(); commandBuffersInfo.level = vk::CommandBufferLevel::ePrimary; commandBuffersInfo.commandBufferCount = frameBufferingCount; if (auto allocateResult = device->allocateCommandBuffersUnique(commandBuffersInfo); allocateResult.result == vk::Result::eSuccess) { frameCommandBuffers = std::move(allocateResult.value); } else { Helpers::panic("Error allocating command buffer: %s\n", vk::to_string(allocateResult.result).c_str()); } // Initialize the first command buffer to be in the RECORDING state vk::CommandBufferBeginInfo beginInfo = {}; beginInfo.flags = vk::CommandBufferUsageFlagBits::eSimultaneousUse; if (const vk::Result beginResult = frameCommandBuffers[frameBufferingIndex]->begin(beginInfo); beginResult != vk::Result::eSuccess) { Helpers::panic("Error beginning command buffer recording: %s\n", vk::to_string(beginResult).c_str()); } // Frame-buffering synchronization primitives vk::FenceCreateInfo fenceInfo = {}; fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled; vk::SemaphoreCreateInfo semaphoreInfo = {}; swapImageFreeSemaphore.resize(frameBufferingCount); renderFinishedSemaphore.resize(frameBufferingCount); frameFinishedFences.resize(frameBufferingCount); frameFramebuffers.resize(frameBufferingCount); frameCommandBuffers.resize(frameBufferingCount); vk::ImageCreateInfo screenTextureInfo = {}; screenTextureInfo.setImageType(vk::ImageType::e2D); screenTextureInfo.setFormat(vk::Format::eR8G8B8A8Unorm); screenTextureInfo.setExtent(vk::Extent3D(400, 240 * 2, 1)); screenTextureInfo.setMipLevels(1); screenTextureInfo.setArrayLayers(1); screenTextureInfo.setSamples(vk::SampleCountFlagBits::e1); screenTextureInfo.setTiling(vk::ImageTiling::eOptimal); screenTextureInfo.setUsage( vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eInputAttachment | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst ); screenTextureInfo.setSharingMode(vk::SharingMode::eExclusive); screenTextureInfo.setInitialLayout(vk::ImageLayout::eUndefined); screenTexture.resize(frameBufferingCount); for (usize i = 0; i < frameBufferingCount; i++) { if (auto createResult = device->createSemaphoreUnique(semaphoreInfo); createResult.result == vk::Result::eSuccess) { swapImageFreeSemaphore[i] = std::move(createResult.value); Vulkan::setObjectName(device.get(), swapImageFreeSemaphore[i].get(), "swapImageFreeSemaphore#%zu", i); } else { Helpers::panic("Error creating 'present-ready' semaphore: %s\n", vk::to_string(createResult.result).c_str()); } if (auto createResult = device->createSemaphoreUnique(semaphoreInfo); createResult.result == vk::Result::eSuccess) { renderFinishedSemaphore[i] = std::move(createResult.value); Vulkan::setObjectName(device.get(), renderFinishedSemaphore[i].get(), "renderFinishedSemaphore#%zu", i); } else { Helpers::panic("Error creating 'post-render' semaphore: %s\n", vk::to_string(createResult.result).c_str()); } if (auto createResult = device->createFenceUnique(fenceInfo); createResult.result == vk::Result::eSuccess) { frameFinishedFences[i] = std::move(createResult.value); } else { Helpers::panic("Error creating 'frame-finished' fence: %s\n", vk::to_string(createResult.result).c_str()); } if (auto createResult = device->createImageUnique(screenTextureInfo); createResult.result == vk::Result::eSuccess) { screenTexture[i] = std::move(createResult.value); } else { Helpers::panic("Error creating top-screen image: %s\n", vk::to_string(createResult.result).c_str()); } } // Commit memory to all of our images { const auto getImage = [](const vk::UniqueImage& image) -> vk::Image { return image.get(); }; std::vector images; std::transform(screenTexture.begin(), screenTexture.end(), std::back_inserter(images), getImage); if (auto [result, imageMemory] = Vulkan::commitImageHeap(device.get(), physicalDevice, images); result == vk::Result::eSuccess) { framebufferMemory = std::move(imageMemory); } else { Helpers::panic("Error allocating framebuffer memory: %s\n", vk::to_string(result).c_str()); } } } void RendererVK::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {} // NOTE: The GPU format has RGB5551 and RGB655 swapped compared to internal regs format static PICA::ColorFmt ToColorFmt(u32 format) { switch (format) { case 2: return PICA::ColorFmt::RGB565; case 3: return PICA::ColorFmt::RGBA5551; default: return static_cast(format); } } void RendererVK::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) { const u32 inputWidth = inputSize & 0xffff; const u32 inputHeight = inputSize >> 16; const PICA::ColorFmt inputFormat = ToColorFmt(Helpers::getBits<8, 3>(flags)); const PICA::ColorFmt outputFormat = ToColorFmt(Helpers::getBits<12, 3>(flags)); const bool verticalFlip = flags & 1; const PICA::Scaling scaling = static_cast(Helpers::getBits<24, 2>(flags)); u32 outputWidth = outputSize & 0xffff; u32 outputHeight = outputSize >> 16; Texture& srcFramebuffer = getColorRenderTexture(inputAddr, inputFormat, inputWidth, inputHeight); Math::Rect srcRect = srcFramebuffer.getSubRect(inputAddr, outputWidth, outputHeight); if (verticalFlip) { std::swap(srcRect.bottom, srcRect.top); } // Apply scaling for the destination rectangle. if (scaling == PICA::Scaling::X || scaling == PICA::Scaling::XY) { outputWidth >>= 1; } if (scaling == PICA::Scaling::XY) { outputHeight >>= 1; } Texture& destFramebuffer = getColorRenderTexture(outputAddr, outputFormat, outputWidth, outputHeight); Math::Rect destRect = destFramebuffer.getSubRect(outputAddr, outputWidth, outputHeight); if (inputWidth != outputWidth) { // Helpers::warn("Strided display transfer is not handled correctly!\n"); } const vk::ImageBlit blitRegion( vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1), {vk::Offset3D{(int)srcRect.left, (int)srcRect.top, 0}, vk::Offset3D{(int)srcRect.right, (int)srcRect.bottom, 1}}, vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1), {vk::Offset3D{(int)destRect.left, (int)destRect.top, 0}, vk::Offset3D{(int)destRect.right, (int)destRect.bottom, 1}} ); const vk::CommandBuffer& blitCommandBuffer = getCurrentCommandBuffer(); static const std::array displayTransferColor = {{1.0f, 1.0f, 0.0f, 1.0f}}; Vulkan::DebugLabelScope scope( blitCommandBuffer, displayTransferColor, "DisplayTransfer inputAddr 0x%08X outputAddr 0x%08X inputWidth %d outputWidth %d inputHeight %d outputHeight %d", inputAddr, outputAddr, inputWidth, outputWidth, inputHeight, outputHeight ); blitCommandBuffer.pipelineBarrier( vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags(), {}, {}, { vk::ImageMemoryBarrier( vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eTransferRead, vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::eTransferSrcOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, srcFramebuffer.image.get(), vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) ), vk::ImageMemoryBarrier( vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::eTransferDstOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, destFramebuffer.image.get(), vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) ), } ); blitCommandBuffer.blitImage( srcFramebuffer.image.get(), vk::ImageLayout::eTransferSrcOptimal, destFramebuffer.image.get(), vk::ImageLayout::eTransferDstOptimal, {blitRegion}, vk::Filter::eLinear ); blitCommandBuffer.pipelineBarrier( vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::DependencyFlags(), {}, {}, { vk::ImageMemoryBarrier( vk::AccessFlagBits::eTransferRead, vk::AccessFlagBits::eColorAttachmentWrite, vk::ImageLayout::eTransferSrcOptimal, vk::ImageLayout::eColorAttachmentOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, srcFramebuffer.image.get(), vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) ), vk::ImageMemoryBarrier( vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eColorAttachmentWrite, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eColorAttachmentOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, destFramebuffer.image.get(), vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) ), } ); } void RendererVK::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) {} void RendererVK::drawVertices(PICA::PrimType primType, std::span vertices) { using namespace Helpers; const u32 depthControl = regs[PICA::InternalRegs::DepthAndColorMask]; const bool depthTestEnable = depthControl & 1; const bool depthWriteEnable = getBit<12>(depthControl); const int depthFunc = getBits<4, 3>(depthControl); const vk::ColorComponentFlags colorMask = vk::ColorComponentFlags(getBits<8, 4>(depthControl)); const vk::RenderPass curRenderPass = getRenderPass(colourBufferFormat, depthTestEnable ? std::make_optional(depthBufferFormat) : std::nullopt); // Create framebuffer, find a way to cache this! vk::Framebuffer curFramebuffer = {}; { std::vector renderTargets; const auto& colorTexture = getColorRenderTexture(colourBufferLoc, colourBufferFormat, fbSize[0], fbSize[1]); renderTargets.emplace_back(colorTexture.imageView.get()); if (depthTestEnable) { const auto& depthTexture = getDepthRenderTexture(depthBufferLoc, depthBufferFormat, fbSize[0], fbSize[1]); renderTargets.emplace_back(depthTexture.imageView.get()); } vk::FramebufferCreateInfo framebufferInfo = {}; framebufferInfo.setRenderPass(curRenderPass); framebufferInfo.setAttachments(renderTargets); framebufferInfo.setWidth(fbSize[0]); framebufferInfo.setHeight(fbSize[1]); framebufferInfo.setLayers(1); if (auto createResult = device->createFramebufferUnique(framebufferInfo); createResult.result == vk::Result::eSuccess) { curFramebuffer = (frameFramebuffers[frameBufferingIndex].emplace_back(std::move(createResult.value))).get(); } else { Helpers::panic("Error creating render-texture framebuffer: %s\n", vk::to_string(createResult.result).c_str()); } } vk::RenderPassBeginInfo renderBeginInfo = {}; renderBeginInfo.renderPass = curRenderPass; static const vk::ClearValue ClearColors[] = { vk::ClearColorValue(std::array{0.0f, 0.0f, 0.0f, 0.0f}), vk::ClearDepthStencilValue(1.0f, 0), vk::ClearColorValue(std::array{0.0f, 0.0f, 0.0f, 0.0f}), }; renderBeginInfo.pClearValues = ClearColors; renderBeginInfo.clearValueCount = std::size(ClearColors); renderBeginInfo.renderArea.extent.width = fbSize[0]; renderBeginInfo.renderArea.extent.height = fbSize[1]; renderBeginInfo.framebuffer = curFramebuffer; const vk::CommandBuffer& commandBuffer = getCurrentCommandBuffer(); commandBuffer.beginRenderPass(renderBeginInfo, vk::SubpassContents::eInline); commandBuffer.endRenderPass(); } void RendererVK::screenshot(const std::string& name) {}