Panda3DS/src/core/renderer_vk/renderer_vk.cpp
Wunkolo 4b193c8d6b Add general purpose vulkan render cache
Takes in a general `vk::Format` rather than PICA-types
2023-08-20 23:01:43 -07:00

1083 lines
46 KiB
C++

#include "renderer_vk/renderer_vk.hpp"
#include <limits>
#include <span>
#include <unordered_set>
#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<const vk::QueueFamilyProperties> 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<u64>(format) << 60);
}
u32 RendererVK::depthBufferHash(u32 loc, u32 size, PICA::DepthFmt format) {
return rotl32(loc, 17) ^ ror32(size, 29) ^ (static_cast<u64>(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<vk::Format> depthFormat) {
u64 renderPassHash = static_cast<u32>(colorFormat);
if (depthFormat.has_value()) {
renderPassHash |= (static_cast<u64>(depthFormat.value()) << 32);
}
// Cache hit
if (renderPassCache.contains(renderPassHash)) {
return renderPassCache.at(renderPassHash).get();
}
// Cache miss
vk::RenderPassCreateInfo renderPassInfo = {};
vk::SubpassDescription subPass = {};
std::vector<vk::AttachmentDescription> 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<PICA::DepthFmt> 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<vk::PresentModeKHR>& 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<vk::SurfaceFormatKHR>& 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<vk::SurfaceFormatKHR>::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<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& 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<u32>::max();
u32 swapchainImageIndex = swapchainImageInvalid;
if (swapchain) {
if (const auto acquireResult =
device->acquireNextImageKHR(swapchain.get(), std::numeric_limits<u64>::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<float, 4> 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<float, 4> topClearColor = {{1.0f, 0.0f, 0.0f, 1.0f}};
static const std::array<float, 4> 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<float, 4> 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<float, 4> 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<vk::Semaphore> waitSemaphores;
std::vector<vk::PipelineStageFlags> 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<u64>::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<PFN_vkGetInstanceProcAddr>("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<std::string> 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<const char*> 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<const char*> 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<vk::PhysicalDevice> physicalDevices = std::move(enumerateResult.value);
std::vector<vk::PhysicalDevice>::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<vk::DeviceQueueCreateInfo> deviceQueueInfos;
{
const std::vector<vk::QueueFamilyProperties> queueFamilyProperties = physicalDevice.getQueueFamilyProperties();
std::unordered_set<u32> 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<const char*> deviceExtensions = {
#if defined(__APPLE__)
"VK_KHR_portability_subset",
#endif
// VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME
};
std::unordered_set<std::string> 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<vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceTimelineSemaphoreFeatures> deviceFeatureChain = {};
auto& deviceFeatures = deviceFeatureChain.get<vk::PhysicalDeviceFeatures2>().features;
auto& deviceTimelineFeatures = deviceFeatureChain.get<vk::PhysicalDeviceTimelineSemaphoreFeatures>();
// 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<vk::Image> 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<PICA::ColorFmt>(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<PICA::Scaling>(Helpers::getBits<24, 2>(flags));
u32 outputWidth = outputSize & 0xffff;
u32 outputHeight = outputSize >> 16;
Texture& srcFramebuffer = getColorRenderTexture(inputAddr, inputFormat, inputWidth, inputHeight);
Math::Rect<u32> 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<u32> 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<float, 4> 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<const PICA::Vertex> 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<vk::ImageView> 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<float, 4>{0.0f, 0.0f, 0.0f, 0.0f}),
vk::ClearDepthStencilValue(1.0f, 0),
vk::ClearColorValue(std::array<float, 4>{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) {}