diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0eee60a31..07e462e59 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -638,6 +638,8 @@ add_library(Common STATIC
Common/GPU/Vulkan/VulkanDebug.h
Common/GPU/Vulkan/VulkanContext.cpp
Common/GPU/Vulkan/VulkanContext.h
+ Common/GPU/Vulkan/VulkanFramebuffer.cpp
+ Common/GPU/Vulkan/VulkanFramebuffer.h
Common/GPU/Vulkan/VulkanImage.cpp
Common/GPU/Vulkan/VulkanImage.h
Common/GPU/Vulkan/VulkanLoader.cpp
diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj
index 15b6197cf..d51b49d58 100644
--- a/Common/Common.vcxproj
+++ b/Common/Common.vcxproj
@@ -450,6 +450,7 @@
+
@@ -881,6 +882,7 @@
+
diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters
index 12a2b0419..18cbbd998 100644
--- a/Common/Common.vcxproj.filters
+++ b/Common/Common.vcxproj.filters
@@ -455,6 +455,9 @@
GPU
+
+ GPU\Vulkan
+
@@ -860,6 +863,9 @@
Render
+
+ GPU\Vulkan
+
diff --git a/Common/GPU/Vulkan/VulkanFramebuffer.cpp b/Common/GPU/Vulkan/VulkanFramebuffer.cpp
new file mode 100644
index 000000000..8f277cf67
--- /dev/null
+++ b/Common/GPU/Vulkan/VulkanFramebuffer.cpp
@@ -0,0 +1,353 @@
+#include "Common/StringUtils.h"
+#include "Common/GPU/Vulkan/VulkanFramebuffer.h"
+#include "Common/GPU/Vulkan/VulkanQueueRunner.h"
+
+VKRFramebuffer::VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, int _numLayers, bool createDepthStencilBuffer, const char *tag)
+ : vulkan_(vk), tag_(tag), width(_width), height(_height), numLayers(_numLayers) {
+
+ _dbg_assert_(tag);
+
+ CreateImage(vulkan_, initCmd, color, width, height, numLayers, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true, tag);
+ if (createDepthStencilBuffer) {
+ CreateImage(vulkan_, initCmd, depth, width, height, numLayers, vulkan_->GetDeviceInfo().preferredDepthStencilFormat, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, tag);
+ }
+
+ UpdateTag(tag);
+
+ // We create the actual framebuffer objects on demand, because some combinations might not make sense.
+ // Framebuffer objects are just pointers to a set of images, so no biggie.
+}
+
+void VKRFramebuffer::UpdateTag(const char *newTag) {
+ char name[128];
+ snprintf(name, sizeof(name), "fb_color_%s", tag_.c_str());
+ vulkan_->SetDebugName(color.image, VK_OBJECT_TYPE_IMAGE, name);
+ vulkan_->SetDebugName(color.rtView, VK_OBJECT_TYPE_IMAGE_VIEW, name);
+ if (depth.image) {
+ snprintf(name, sizeof(name), "fb_depth_%s", tag_.c_str());
+ vulkan_->SetDebugName(depth.image, VK_OBJECT_TYPE_IMAGE, name);
+ vulkan_->SetDebugName(depth.rtView, VK_OBJECT_TYPE_IMAGE_VIEW, name);
+ }
+ for (size_t rpType = 0; rpType < (size_t)RenderPassType::TYPE_COUNT; rpType++) {
+ if (framebuf[rpType]) {
+ snprintf(name, sizeof(name), "fb_%s", tag_.c_str());
+ vulkan_->SetDebugName(framebuf[(int)rpType], VK_OBJECT_TYPE_FRAMEBUFFER, name);
+ }
+ }
+}
+
+VkFramebuffer VKRFramebuffer::Get(VKRRenderPass *compatibleRenderPass, RenderPassType rpType) {
+ bool multiview = RenderPassTypeHasMultiView(rpType);
+
+ if (framebuf[(int)rpType]) {
+ return framebuf[(int)rpType];
+ }
+
+ VkFramebufferCreateInfo fbci{ VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO };
+ VkImageView views[2]{};
+
+ bool hasDepth = RenderPassTypeHasDepth(rpType);
+ views[0] = color.rtView; // 2D array texture if multilayered.
+ if (hasDepth) {
+ if (!depth.rtView) {
+ WARN_LOG(G3D, "depth render type to non-depth fb: %p %p fmt=%d (%s %dx%d)", depth.image, depth.texAllLayersView, depth.format, tag_.c_str(), width, height);
+ // Will probably crash, depending on driver.
+ }
+ views[1] = depth.rtView;
+ }
+ fbci.renderPass = compatibleRenderPass->Get(vulkan_, rpType);
+ fbci.attachmentCount = hasDepth ? 2 : 1;
+ fbci.pAttachments = views;
+ fbci.width = width;
+ fbci.height = height;
+ fbci.layers = 1; // With multiview, this should be set as 1.
+
+ VkResult res = vkCreateFramebuffer(vulkan_->GetDevice(), &fbci, nullptr, &framebuf[(int)rpType]);
+ _assert_(res == VK_SUCCESS);
+
+ if (!tag_.empty() && vulkan_->Extensions().EXT_debug_utils) {
+ vulkan_->SetDebugName(framebuf[(int)rpType], VK_OBJECT_TYPE_FRAMEBUFFER, StringFromFormat("fb_%s", tag_.c_str()).c_str());
+ }
+
+ return framebuf[(int)rpType];
+}
+
+VKRFramebuffer::~VKRFramebuffer() {
+ // Get rid of the views first, feels cleaner (but in reality doesn't matter).
+ if (color.rtView)
+ vulkan_->Delete().QueueDeleteImageView(color.rtView);
+ if (depth.rtView)
+ vulkan_->Delete().QueueDeleteImageView(depth.rtView);
+ if (color.texAllLayersView)
+ vulkan_->Delete().QueueDeleteImageView(color.texAllLayersView);
+ if (depth.texAllLayersView)
+ vulkan_->Delete().QueueDeleteImageView(depth.texAllLayersView);
+ for (int i = 0; i < 2; i++) {
+ if (color.texLayerViews[i]) {
+ vulkan_->Delete().QueueDeleteImageView(color.texLayerViews[i]);
+ }
+ if (depth.texLayerViews[i]) {
+ vulkan_->Delete().QueueDeleteImageView(depth.texLayerViews[i]);
+ }
+ }
+
+ if (color.image) {
+ _dbg_assert_(color.alloc);
+ vulkan_->Delete().QueueDeleteImageAllocation(color.image, color.alloc);
+ }
+ if (depth.image) {
+ _dbg_assert_(depth.alloc);
+ vulkan_->Delete().QueueDeleteImageAllocation(depth.image, depth.alloc);
+ }
+ for (auto &fb : framebuf) {
+ if (fb) {
+ vulkan_->Delete().QueueDeleteFramebuffer(fb);
+ }
+ }
+}
+
+// NOTE: If numLayers > 1, it will create an array texture, rather than a normal 2D texture.
+// This requires a different sampling path!
+void VKRFramebuffer::CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int width, int height, int numLayers, VkFormat format, VkImageLayout initialLayout, bool color, const char *tag) {
+ // We don't support more exotic layer setups for now. Mono or stereo.
+ _dbg_assert_(numLayers == 1 || numLayers == 2);
+
+ VkImageCreateInfo ici{ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
+ ici.arrayLayers = numLayers;
+ ici.mipLevels = 1;
+ ici.extent.width = width;
+ ici.extent.height = height;
+ ici.extent.depth = 1;
+ ici.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ ici.imageType = VK_IMAGE_TYPE_2D;
+ ici.samples = VK_SAMPLE_COUNT_1_BIT;
+ ici.tiling = VK_IMAGE_TILING_OPTIMAL;
+ ici.format = format;
+ // Strictly speaking we don't yet need VK_IMAGE_USAGE_SAMPLED_BIT for depth buffers since we do not yet sample depth buffers.
+ ici.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+ if (color) {
+ ici.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
+ } else {
+ ici.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+ }
+
+ VmaAllocationCreateInfo allocCreateInfo{};
+ allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
+ VmaAllocationInfo allocInfo{};
+
+ VkResult res = vmaCreateImage(vulkan->Allocator(), &ici, &allocCreateInfo, &img.image, &img.alloc, &allocInfo);
+ _dbg_assert_(res == VK_SUCCESS);
+
+ VkImageAspectFlags aspects = color ? VK_IMAGE_ASPECT_COLOR_BIT : (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT);
+
+ VkImageViewCreateInfo ivci{ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
+ ivci.components = { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY };
+ ivci.format = ici.format;
+ ivci.image = img.image;
+ ivci.viewType = numLayers == 1 ? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_2D_ARRAY;
+ ivci.subresourceRange.aspectMask = aspects;
+ ivci.subresourceRange.layerCount = numLayers;
+ ivci.subresourceRange.levelCount = 1;
+ res = vkCreateImageView(vulkan->GetDevice(), &ivci, nullptr, &img.rtView);
+ vulkan->SetDebugName(img.rtView, VK_OBJECT_TYPE_IMAGE_VIEW, tag);
+ _dbg_assert_(res == VK_SUCCESS);
+
+ // Separate view for texture sampling all layers together.
+ if (!color) {
+ ivci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
+ }
+
+ ivci.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; // layered for consistency, even if single image.
+ res = vkCreateImageView(vulkan->GetDevice(), &ivci, nullptr, &img.texAllLayersView);
+ vulkan->SetDebugName(img.texAllLayersView, VK_OBJECT_TYPE_IMAGE_VIEW, tag);
+
+ // Create 2D views for both layers.
+ // Useful when multipassing shaders that don't yet exist in a single-pass-stereo version.
+ for (int i = 0; i < numLayers; i++) {
+ ivci.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ ivci.subresourceRange.layerCount = 1;
+ ivci.subresourceRange.baseArrayLayer = i;
+ res = vkCreateImageView(vulkan->GetDevice(), &ivci, nullptr, &img.texLayerViews[i]);
+ if (vulkan->DebugLayerEnabled()) {
+ char temp[128];
+ snprintf(temp, sizeof(temp), "%s_layer%d", tag, i);
+ vulkan->SetDebugName(img.texLayerViews[i], VK_OBJECT_TYPE_IMAGE_VIEW, temp);
+ }
+ _dbg_assert_(res == VK_SUCCESS);
+ }
+
+ VkPipelineStageFlags dstStage;
+ VkAccessFlagBits dstAccessMask;
+ switch (initialLayout) {
+ case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
+ dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ dstStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ break;
+ case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
+ dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+ dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
+ break;
+ case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
+ dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+ dstStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
+ break;
+ default:
+ Crash();
+ return;
+ }
+
+ TransitionImageLayout2(cmd, img.image, 0, 1, numLayers, aspects,
+ VK_IMAGE_LAYOUT_UNDEFINED, initialLayout,
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, dstStage,
+ 0, dstAccessMask);
+ img.layout = initialLayout;
+ img.format = format;
+ img.tag = tag ? tag : "N/A";
+ img.numLayers = numLayers;
+}
+
+static VkAttachmentLoadOp ConvertLoadAction(VKRRenderPassLoadAction action) {
+ switch (action) {
+ case VKRRenderPassLoadAction::CLEAR: return VK_ATTACHMENT_LOAD_OP_CLEAR;
+ case VKRRenderPassLoadAction::KEEP: return VK_ATTACHMENT_LOAD_OP_LOAD;
+ case VKRRenderPassLoadAction::DONT_CARE: return VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ }
+ return VK_ATTACHMENT_LOAD_OP_DONT_CARE; // avoid compiler warning
+}
+
+static VkAttachmentStoreOp ConvertStoreAction(VKRRenderPassStoreAction action) {
+ switch (action) {
+ case VKRRenderPassStoreAction::STORE: return VK_ATTACHMENT_STORE_OP_STORE;
+ case VKRRenderPassStoreAction::DONT_CARE: return VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ }
+ return VK_ATTACHMENT_STORE_OP_DONT_CARE; // avoid compiler warning
+}
+
+// Self-dependency: https://github.com/gpuweb/gpuweb/issues/442#issuecomment-547604827
+// Also see https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#synchronization-pipeline-barriers-subpass-self-dependencies
+
+VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPassType rpType) {
+ bool selfDependency = RenderPassTypeHasInput(rpType);
+ bool isBackbuffer = rpType == RenderPassType::BACKBUFFER;
+ bool hasDepth = RenderPassTypeHasDepth(rpType);
+ bool multiview = RenderPassTypeHasMultiView(rpType);
+
+ if (multiview) {
+ // TODO: Assert that the device has multiview support enabled.
+ }
+
+ VkAttachmentDescription attachments[2] = {};
+ attachments[0].format = isBackbuffer ? vulkan->GetSwapchainFormat() : VK_FORMAT_R8G8B8A8_UNORM;
+ attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
+ attachments[0].loadOp = ConvertLoadAction(key.colorLoadAction);
+ attachments[0].storeOp = ConvertStoreAction(key.colorStoreAction);
+ attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ attachments[0].initialLayout = isBackbuffer ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ attachments[0].finalLayout = isBackbuffer ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ attachments[0].flags = 0;
+
+ if (hasDepth) {
+ attachments[1].format = vulkan->GetDeviceInfo().preferredDepthStencilFormat;
+ attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
+ attachments[1].loadOp = ConvertLoadAction(key.depthLoadAction);
+ attachments[1].storeOp = ConvertStoreAction(key.depthStoreAction);
+ attachments[1].stencilLoadOp = ConvertLoadAction(key.stencilLoadAction);
+ attachments[1].stencilStoreOp = ConvertStoreAction(key.stencilStoreAction);
+ attachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ attachments[1].flags = 0;
+ }
+
+ VkAttachmentReference color_reference{};
+ color_reference.attachment = 0;
+ color_reference.layout = selfDependency ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+ VkAttachmentReference depth_reference{};
+ depth_reference.attachment = 1;
+ depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+ VkSubpassDescription subpass{};
+ subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+ subpass.flags = 0;
+ if (selfDependency) {
+ subpass.inputAttachmentCount = 1;
+ subpass.pInputAttachments = &color_reference;
+ } else {
+ subpass.inputAttachmentCount = 0;
+ subpass.pInputAttachments = nullptr;
+ }
+ subpass.colorAttachmentCount = 1;
+ subpass.pColorAttachments = &color_reference;
+ subpass.pResolveAttachments = nullptr;
+ if (hasDepth) {
+ subpass.pDepthStencilAttachment = &depth_reference;
+ }
+ subpass.preserveAttachmentCount = 0;
+ subpass.pPreserveAttachments = nullptr;
+
+ // Not sure if this is really necessary.
+ VkSubpassDependency deps[2]{};
+ size_t numDeps = 0;
+
+ VkRenderPassCreateInfo rp{ VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO };
+ rp.attachmentCount = hasDepth ? 2 : 1;
+ rp.pAttachments = attachments;
+ rp.subpassCount = 1;
+ rp.pSubpasses = &subpass;
+
+ VkRenderPassMultiviewCreateInfoKHR mv{ VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO_KHR };
+ uint32_t viewMask = 0x3; // Must be outside the 'if (multiview)' scope!
+ int viewOffset = 0;
+ if (multiview) {
+ rp.pNext = &mv;
+ mv.subpassCount = 1;
+ mv.pViewMasks = &viewMask;
+ mv.dependencyCount = 0;
+ mv.pCorrelationMasks = &viewMask; // same masks
+ mv.correlationMaskCount = 1;
+ mv.pViewOffsets = &viewOffset;
+ }
+
+ if (isBackbuffer) {
+ deps[numDeps].srcSubpass = VK_SUBPASS_EXTERNAL;
+ deps[numDeps].dstSubpass = 0;
+ deps[numDeps].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ deps[numDeps].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ deps[numDeps].srcAccessMask = 0;
+ deps[numDeps].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ numDeps++;
+ }
+
+ if (selfDependency) {
+ deps[numDeps].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+ deps[numDeps].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ deps[numDeps].dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
+ deps[numDeps].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ deps[numDeps].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+ deps[numDeps].srcSubpass = 0;
+ deps[numDeps].dstSubpass = 0;
+ numDeps++;
+ }
+
+ if (numDeps > 0) {
+ rp.dependencyCount = (u32)numDeps;
+ rp.pDependencies = deps;
+ }
+
+ VkRenderPass pass;
+ VkResult res = vkCreateRenderPass(vulkan->GetDevice(), &rp, nullptr, &pass);
+ _assert_(res == VK_SUCCESS);
+ _assert_(pass != VK_NULL_HANDLE);
+ return pass;
+}
+
+VkRenderPass VKRRenderPass::Get(VulkanContext *vulkan, RenderPassType rpType) {
+ // When we create a render pass, we create all "types" of it immediately,
+ // practical later when referring to it. Could change to on-demand if it feels motivated
+ // but I think the render pass objects are cheap.
+ if (!pass[(int)rpType]) {
+ pass[(int)rpType] = CreateRenderPass(vulkan, key_, (RenderPassType)rpType);
+ }
+ return pass[(int)rpType];
+}
diff --git a/Common/GPU/Vulkan/VulkanFramebuffer.h b/Common/GPU/Vulkan/VulkanFramebuffer.h
new file mode 100644
index 000000000..c52e93364
--- /dev/null
+++ b/Common/GPU/Vulkan/VulkanFramebuffer.h
@@ -0,0 +1,140 @@
+#pragma once
+
+#include "Common/Common.h"
+#include "Common/GPU/Vulkan/VulkanContext.h"
+
+class VKRRenderPass;
+
+// Pipelines need to be created for the right type of render pass.
+// TODO: Rename to RenderPassFlags?
+// When you add more flags, don't forget to update rpTypeDebugNames[].
+enum class RenderPassType {
+ DEFAULT = 0,
+ // These eight are organized so that bit 0 is DEPTH and bit 1 is INPUT and bit 2 is MULTIVIEW, so
+ // they can be OR-ed together in MergeRPTypes.
+ HAS_DEPTH = 1,
+ COLOR_INPUT = 2, // input attachment
+ MULTIVIEW = 4,
+
+ // This is the odd one out, and gets special handling in MergeRPTypes.
+ // If this flag is set, none of the other flags can be set.
+ // For the backbuffer we can always use CLEAR/DONT_CARE, so bandwidth cost for a depth channel is negligible
+ // so we don't bother with a non-depth version.
+ BACKBUFFER = 8,
+
+ TYPE_COUNT = BACKBUFFER + 1,
+};
+ENUM_CLASS_BITOPS(RenderPassType);
+
+// Simple independent framebuffer image.
+struct VKRImage {
+ // These four are "immutable".
+ VkImage image;
+
+ VkImageView rtView; // Used for rendering to, and readbacks of stencil. 2D if single layer, 2D_ARRAY if multiple. Includes both depth and stencil if depth/stencil.
+
+ // This is for texturing all layers at once. If aspect is depth/stencil, does not include stencil.
+ VkImageView texAllLayersView;
+
+ // If it's a layered image (for stereo), this is two 2D views of it, to make it compatible with shaders that don't yet support stereo.
+ // If there's only one layer, layerViews[0] only is initialized.
+ VkImageView texLayerViews[2]{};
+
+ VmaAllocation alloc;
+ VkFormat format;
+
+ // This one is used by QueueRunner's Perform functions to keep track. CANNOT be used anywhere else due to sync issues.
+ VkImageLayout layout;
+
+ int numLayers;
+
+ // For debugging.
+ std::string tag;
+};
+
+class VKRFramebuffer {
+public:
+ VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, int _numLayers, bool createDepthStencilBuffer, const char *tag);
+ ~VKRFramebuffer();
+
+ VkFramebuffer Get(VKRRenderPass *compatibleRenderPass, RenderPassType rpType);
+
+ int width = 0;
+ int height = 0;
+ int numLayers = 0;
+
+ VKRImage color{}; // color.image is always there.
+ VKRImage depth{}; // depth.image is allowed to be VK_NULL_HANDLE.
+
+ const char *Tag() const {
+ return tag_.c_str();
+ }
+
+ void UpdateTag(const char *newTag);
+
+ bool HasDepth() const {
+ return depth.image != VK_NULL_HANDLE;
+ }
+
+ // TODO: Hide.
+ VulkanContext *vulkan_;
+private:
+ static void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int width, int height, int numLayers, VkFormat format, VkImageLayout initialLayout, bool color, const char *tag);
+
+ VkFramebuffer framebuf[(size_t)RenderPassType::TYPE_COUNT]{};
+
+ std::string tag_;
+};
+
+inline bool RenderPassTypeHasDepth(RenderPassType type) {
+ return (type & RenderPassType::HAS_DEPTH) || type == RenderPassType::BACKBUFFER;
+}
+
+inline bool RenderPassTypeHasInput(RenderPassType type) {
+ return (type & RenderPassType::COLOR_INPUT) != 0;
+}
+
+inline bool RenderPassTypeHasMultiView(RenderPassType type) {
+ return (type & RenderPassType::MULTIVIEW) != 0;
+}
+
+// Must be the same order as Draw::RPAction
+enum class VKRRenderPassLoadAction : uint8_t {
+ KEEP, // default. avoid when possible.
+ CLEAR,
+ DONT_CARE,
+};
+
+enum class VKRRenderPassStoreAction : uint8_t {
+ STORE, // default. avoid when possible.
+ DONT_CARE,
+};
+
+struct RPKey {
+ // Only render-pass-compatibility-volatile things can be here.
+ VKRRenderPassLoadAction colorLoadAction;
+ VKRRenderPassLoadAction depthLoadAction;
+ VKRRenderPassLoadAction stencilLoadAction;
+ VKRRenderPassStoreAction colorStoreAction;
+ VKRRenderPassStoreAction depthStoreAction;
+ VKRRenderPassStoreAction stencilStoreAction;
+};
+
+class VKRRenderPass {
+public:
+ VKRRenderPass(const RPKey &key) : key_(key) {}
+
+ VkRenderPass Get(VulkanContext *vulkan, RenderPassType rpType);
+ void Destroy(VulkanContext *vulkan) {
+ for (size_t i = 0; i < (size_t)RenderPassType::TYPE_COUNT; i++) {
+ if (pass[i]) {
+ vulkan->Delete().QueueDeleteRenderPass(pass[i]);
+ }
+ }
+ }
+
+private:
+ // TODO: Might be better off with a hashmap once the render pass type count grows really large..
+ VkRenderPass pass[(size_t)RenderPassType::TYPE_COUNT]{};
+ RPKey key_;
+};
diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp
index b154a6bc0..617c349b7 100644
--- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp
+++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp
@@ -309,151 +309,6 @@ void VulkanQueueRunner::DestroyBackBuffers() {
INFO_LOG(G3D, "Backbuffers destroyed");
}
-static VkAttachmentLoadOp ConvertLoadAction(VKRRenderPassLoadAction action) {
- switch (action) {
- case VKRRenderPassLoadAction::CLEAR: return VK_ATTACHMENT_LOAD_OP_CLEAR;
- case VKRRenderPassLoadAction::KEEP: return VK_ATTACHMENT_LOAD_OP_LOAD;
- case VKRRenderPassLoadAction::DONT_CARE: return VK_ATTACHMENT_LOAD_OP_DONT_CARE;
- }
- return VK_ATTACHMENT_LOAD_OP_DONT_CARE; // avoid compiler warning
-}
-
-static VkAttachmentStoreOp ConvertStoreAction(VKRRenderPassStoreAction action) {
- switch (action) {
- case VKRRenderPassStoreAction::STORE: return VK_ATTACHMENT_STORE_OP_STORE;
- case VKRRenderPassStoreAction::DONT_CARE: return VK_ATTACHMENT_STORE_OP_DONT_CARE;
- }
- return VK_ATTACHMENT_STORE_OP_DONT_CARE; // avoid compiler warning
-}
-
-// Self-dependency: https://github.com/gpuweb/gpuweb/issues/442#issuecomment-547604827
-// Also see https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#synchronization-pipeline-barriers-subpass-self-dependencies
-
-VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPassType rpType) {
- bool selfDependency = RenderPassTypeHasInput(rpType);
- bool isBackbuffer = rpType == RenderPassType::BACKBUFFER;
- bool hasDepth = RenderPassTypeHasDepth(rpType);
- bool multiview = RenderPassTypeHasMultiView(rpType);
-
- if (multiview) {
- // TODO: Assert that the device has multiview support enabled.
- }
-
- VkAttachmentDescription attachments[2] = {};
- attachments[0].format = isBackbuffer ? vulkan->GetSwapchainFormat() : VK_FORMAT_R8G8B8A8_UNORM;
- attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
- attachments[0].loadOp = ConvertLoadAction(key.colorLoadAction);
- attachments[0].storeOp = ConvertStoreAction(key.colorStoreAction);
- attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
- attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
- attachments[0].initialLayout = isBackbuffer ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
- attachments[0].finalLayout = isBackbuffer ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
- attachments[0].flags = 0;
-
- if (hasDepth) {
- attachments[1].format = vulkan->GetDeviceInfo().preferredDepthStencilFormat;
- attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
- attachments[1].loadOp = ConvertLoadAction(key.depthLoadAction);
- attachments[1].storeOp = ConvertStoreAction(key.depthStoreAction);
- attachments[1].stencilLoadOp = ConvertLoadAction(key.stencilLoadAction);
- attachments[1].stencilStoreOp = ConvertStoreAction(key.stencilStoreAction);
- attachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
- attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
- attachments[1].flags = 0;
- }
-
- VkAttachmentReference color_reference{};
- color_reference.attachment = 0;
- color_reference.layout = selfDependency ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-
- VkAttachmentReference depth_reference{};
- depth_reference.attachment = 1;
- depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
-
- VkSubpassDescription subpass{};
- subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
- subpass.flags = 0;
- if (selfDependency) {
- subpass.inputAttachmentCount = 1;
- subpass.pInputAttachments = &color_reference;
- } else {
- subpass.inputAttachmentCount = 0;
- subpass.pInputAttachments = nullptr;
- }
- subpass.colorAttachmentCount = 1;
- subpass.pColorAttachments = &color_reference;
- subpass.pResolveAttachments = nullptr;
- if (hasDepth) {
- subpass.pDepthStencilAttachment = &depth_reference;
- }
- subpass.preserveAttachmentCount = 0;
- subpass.pPreserveAttachments = nullptr;
-
- // Not sure if this is really necessary.
- VkSubpassDependency deps[2]{};
- size_t numDeps = 0;
-
- VkRenderPassCreateInfo rp{ VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO };
- rp.attachmentCount = hasDepth ? 2 : 1;
- rp.pAttachments = attachments;
- rp.subpassCount = 1;
- rp.pSubpasses = &subpass;
-
- VkRenderPassMultiviewCreateInfoKHR mv{ VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO_KHR };
- uint32_t viewMask = 0x3; // Must be outside the 'if (multiview)' scope!
- int viewOffset = 0;
- if (multiview) {
- rp.pNext = &mv;
- mv.subpassCount = 1;
- mv.pViewMasks = &viewMask;
- mv.dependencyCount = 0;
- mv.pCorrelationMasks = &viewMask; // same masks
- mv.correlationMaskCount = 1;
- mv.pViewOffsets = &viewOffset;
- }
-
- if (isBackbuffer) {
- deps[numDeps].srcSubpass = VK_SUBPASS_EXTERNAL;
- deps[numDeps].dstSubpass = 0;
- deps[numDeps].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
- deps[numDeps].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
- deps[numDeps].srcAccessMask = 0;
- deps[numDeps].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
- numDeps++;
- }
-
- if (selfDependency) {
- deps[numDeps].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
- deps[numDeps].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
- deps[numDeps].dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
- deps[numDeps].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
- deps[numDeps].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
- deps[numDeps].srcSubpass = 0;
- deps[numDeps].dstSubpass = 0;
- numDeps++;
- }
-
- if (numDeps > 0) {
- rp.dependencyCount = (u32)numDeps;
- rp.pDependencies = deps;
- }
-
- VkRenderPass pass;
- VkResult res = vkCreateRenderPass(vulkan->GetDevice(), &rp, nullptr, &pass);
- _assert_(res == VK_SUCCESS);
- _assert_(pass != VK_NULL_HANDLE);
- return pass;
-}
-
-VkRenderPass VKRRenderPass::Get(VulkanContext *vulkan, RenderPassType rpType) {
- // When we create a render pass, we create all "types" of it immediately,
- // practical later when referring to it. Could change to on-demand if it feels motivated
- // but I think the render pass objects are cheap.
- if (!pass[(int)rpType]) {
- pass[(int)rpType] = CreateRenderPass(vulkan, key_, (RenderPassType)rpType);
- }
- return pass[(int)rpType];
-}
// Self-dependency: https://github.com/gpuweb/gpuweb/issues/442#issuecomment-547604827
// Also see https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#synchronization-pipeline-barriers-subpass-self-dependencies
diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.h b/Common/GPU/Vulkan/VulkanQueueRunner.h
index 6b7242cad..0f844bbd8 100644
--- a/Common/GPU/Vulkan/VulkanQueueRunner.h
+++ b/Common/GPU/Vulkan/VulkanQueueRunner.h
@@ -9,6 +9,7 @@
#include "Common/GPU/Vulkan/VulkanContext.h"
#include "Common/GPU/Vulkan/VulkanBarrier.h"
#include "Common/GPU/Vulkan/VulkanFrameData.h"
+#include "Common/GPU/Vulkan/VulkanFramebuffer.h"
#include "Common/Data/Convert/SmallDataConvert.h"
#include "Common/Data/Collections/TinySet.h"
#include "Common/GPU/DataFormat.h"
@@ -53,39 +54,6 @@ enum class PipelineFlags : u8 {
};
ENUM_CLASS_BITOPS(PipelineFlags);
-// Pipelines need to be created for the right type of render pass.
-// TODO: Rename to RenderPassFlags?
-// When you add more flags, don't forget to update rpTypeDebugNames[].
-enum class RenderPassType {
- DEFAULT = 0,
- // These eight are organized so that bit 0 is DEPTH and bit 1 is INPUT and bit 2 is MULTIVIEW, so
- // they can be OR-ed together in MergeRPTypes.
- HAS_DEPTH = 1,
- COLOR_INPUT = 2, // input attachment
- MULTIVIEW = 4,
-
- // This is the odd one out, and gets special handling in MergeRPTypes.
- // If this flag is set, none of the other flags can be set.
- // For the backbuffer we can always use CLEAR/DONT_CARE, so bandwidth cost for a depth channel is negligible
- // so we don't bother with a non-depth version.
- BACKBUFFER = 8,
-
- TYPE_COUNT = BACKBUFFER + 1,
-};
-ENUM_CLASS_BITOPS(RenderPassType);
-
-inline bool RenderPassTypeHasDepth(RenderPassType type) {
- return (type & RenderPassType::HAS_DEPTH) || type == RenderPassType::BACKBUFFER;
-}
-
-inline bool RenderPassTypeHasInput(RenderPassType type) {
- return (type & RenderPassType::COLOR_INPUT) != 0;
-}
-
-inline bool RenderPassTypeHasMultiView(RenderPassType type) {
- return (type & RenderPassType::MULTIVIEW) != 0;
-}
-
struct VkRenderData {
VKRRenderCommand cmd;
union {
@@ -168,18 +136,6 @@ enum class VKRStepType : uint8_t {
READBACK_IMAGE,
};
-// Must be the same order as Draw::RPAction
-enum class VKRRenderPassLoadAction : uint8_t {
- KEEP, // default. avoid when possible.
- CLEAR,
- DONT_CARE,
-};
-
-enum class VKRRenderPassStoreAction : uint8_t {
- STORE, // default. avoid when possible.
- DONT_CARE,
-};
-
struct TransitionRequest {
VKRFramebuffer *fb;
VkImageAspectFlags aspect; // COLOR or DEPTH
@@ -252,35 +208,6 @@ struct VKRStep {
};
};
-struct RPKey {
- // Only render-pass-compatibility-volatile things can be here.
- VKRRenderPassLoadAction colorLoadAction;
- VKRRenderPassLoadAction depthLoadAction;
- VKRRenderPassLoadAction stencilLoadAction;
- VKRRenderPassStoreAction colorStoreAction;
- VKRRenderPassStoreAction depthStoreAction;
- VKRRenderPassStoreAction stencilStoreAction;
-};
-
-class VKRRenderPass {
-public:
- VKRRenderPass(const RPKey &key) : key_(key) {}
-
- VkRenderPass Get(VulkanContext *vulkan, RenderPassType rpType);
- void Destroy(VulkanContext *vulkan) {
- for (size_t i = 0; i < (size_t)RenderPassType::TYPE_COUNT; i++) {
- if (pass[i]) {
- vulkan->Delete().QueueDeleteRenderPass(pass[i]);
- }
- }
- }
-
-private:
- // TODO: Might be better off with a hashmap once the render pass type count grows really large..
- VkRenderPass pass[(size_t)RenderPassType::TYPE_COUNT]{};
- RPKey key_;
-};
-
// These are enqueued from the main thread,
// and the render thread pops them off
struct VKRRenderThreadTask {
diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp
index df3f5e553..2b67cbfeb 100644
--- a/Common/GPU/Vulkan/VulkanRenderManager.cpp
+++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp
@@ -171,210 +171,6 @@ bool VKRComputePipeline::Create(VulkanContext *vulkan) {
return success;
}
-VKRFramebuffer::VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, int _numLayers, bool createDepthStencilBuffer, const char *tag)
- : vulkan_(vk), tag_(tag), width(_width), height(_height), numLayers(_numLayers) {
-
- _dbg_assert_(tag);
-
- CreateImage(vulkan_, initCmd, color, width, height, numLayers, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true, tag);
- if (createDepthStencilBuffer) {
- CreateImage(vulkan_, initCmd, depth, width, height, numLayers, vulkan_->GetDeviceInfo().preferredDepthStencilFormat, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, tag);
- }
-
- UpdateTag(tag);
-
- // We create the actual framebuffer objects on demand, because some combinations might not make sense.
- // Framebuffer objects are just pointers to a set of images, so no biggie.
-}
-
-void VKRFramebuffer::UpdateTag(const char *newTag) {
- char name[128];
- snprintf(name, sizeof(name), "fb_color_%s", tag_.c_str());
- vulkan_->SetDebugName(color.image, VK_OBJECT_TYPE_IMAGE, name);
- vulkan_->SetDebugName(color.rtView, VK_OBJECT_TYPE_IMAGE_VIEW, name);
- if (depth.image) {
- snprintf(name, sizeof(name), "fb_depth_%s", tag_.c_str());
- vulkan_->SetDebugName(depth.image, VK_OBJECT_TYPE_IMAGE, name);
- vulkan_->SetDebugName(depth.rtView, VK_OBJECT_TYPE_IMAGE_VIEW, name);
- }
- for (size_t rpType = 0; rpType < (size_t)RenderPassType::TYPE_COUNT; rpType++) {
- if (framebuf[rpType]) {
- snprintf(name, sizeof(name), "fb_%s", tag_.c_str());
- vulkan_->SetDebugName(framebuf[(int)rpType], VK_OBJECT_TYPE_FRAMEBUFFER, name);
- }
- }
-}
-
-VkFramebuffer VKRFramebuffer::Get(VKRRenderPass *compatibleRenderPass, RenderPassType rpType) {
- bool multiview = RenderPassTypeHasMultiView(rpType);
-
- if (framebuf[(int)rpType]) {
- return framebuf[(int)rpType];
- }
-
- VkFramebufferCreateInfo fbci{ VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO };
- VkImageView views[2]{};
-
- bool hasDepth = RenderPassTypeHasDepth(rpType);
- views[0] = color.rtView; // 2D array texture if multilayered.
- if (hasDepth) {
- if (!depth.rtView) {
- WARN_LOG(G3D, "depth render type to non-depth fb: %p %p fmt=%d (%s %dx%d)", depth.image, depth.texAllLayersView, depth.format, tag_.c_str(), width, height);
- // Will probably crash, depending on driver.
- }
- views[1] = depth.rtView;
- }
- fbci.renderPass = compatibleRenderPass->Get(vulkan_, rpType);
- fbci.attachmentCount = hasDepth ? 2 : 1;
- fbci.pAttachments = views;
- fbci.width = width;
- fbci.height = height;
- fbci.layers = 1; // With multiview, this should be set as 1.
-
- VkResult res = vkCreateFramebuffer(vulkan_->GetDevice(), &fbci, nullptr, &framebuf[(int)rpType]);
- _assert_(res == VK_SUCCESS);
-
- if (!tag_.empty() && vulkan_->Extensions().EXT_debug_utils) {
- vulkan_->SetDebugName(framebuf[(int)rpType], VK_OBJECT_TYPE_FRAMEBUFFER, StringFromFormat("fb_%s", tag_.c_str()).c_str());
- }
-
- return framebuf[(int)rpType];
-}
-
-VKRFramebuffer::~VKRFramebuffer() {
- // Get rid of the views first, feels cleaner (but in reality doesn't matter).
- if (color.rtView)
- vulkan_->Delete().QueueDeleteImageView(color.rtView);
- if (depth.rtView)
- vulkan_->Delete().QueueDeleteImageView(depth.rtView);
- if (color.texAllLayersView)
- vulkan_->Delete().QueueDeleteImageView(color.texAllLayersView);
- if (depth.texAllLayersView)
- vulkan_->Delete().QueueDeleteImageView(depth.texAllLayersView);
- for (int i = 0; i < 2; i++) {
- if (color.texLayerViews[i]) {
- vulkan_->Delete().QueueDeleteImageView(color.texLayerViews[i]);
- }
- if (depth.texLayerViews[i]) {
- vulkan_->Delete().QueueDeleteImageView(depth.texLayerViews[i]);
- }
- }
-
- if (color.image) {
- _dbg_assert_(color.alloc);
- vulkan_->Delete().QueueDeleteImageAllocation(color.image, color.alloc);
- }
- if (depth.image) {
- _dbg_assert_(depth.alloc);
- vulkan_->Delete().QueueDeleteImageAllocation(depth.image, depth.alloc);
- }
- for (auto &fb : framebuf) {
- if (fb) {
- vulkan_->Delete().QueueDeleteFramebuffer(fb);
- }
- }
-}
-
-// NOTE: If numLayers > 1, it will create an array texture, rather than a normal 2D texture.
-// This requires a different sampling path!
-void VKRFramebuffer::CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int width, int height, int numLayers, VkFormat format, VkImageLayout initialLayout, bool color, const char *tag) {
- // We don't support more exotic layer setups for now. Mono or stereo.
- _dbg_assert_(numLayers == 1 || numLayers == 2);
-
- VkImageCreateInfo ici{ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
- ici.arrayLayers = numLayers;
- ici.mipLevels = 1;
- ici.extent.width = width;
- ici.extent.height = height;
- ici.extent.depth = 1;
- ici.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
- ici.imageType = VK_IMAGE_TYPE_2D;
- ici.samples = VK_SAMPLE_COUNT_1_BIT;
- ici.tiling = VK_IMAGE_TILING_OPTIMAL;
- ici.format = format;
- // Strictly speaking we don't yet need VK_IMAGE_USAGE_SAMPLED_BIT for depth buffers since we do not yet sample depth buffers.
- ici.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
- if (color) {
- ici.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
- } else {
- ici.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
- }
-
- VmaAllocationCreateInfo allocCreateInfo{};
- allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
- VmaAllocationInfo allocInfo{};
-
- VkResult res = vmaCreateImage(vulkan->Allocator(), &ici, &allocCreateInfo, &img.image, &img.alloc, &allocInfo);
- _dbg_assert_(res == VK_SUCCESS);
-
- VkImageAspectFlags aspects = color ? VK_IMAGE_ASPECT_COLOR_BIT : (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT);
-
- VkImageViewCreateInfo ivci{ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
- ivci.components = { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY };
- ivci.format = ici.format;
- ivci.image = img.image;
- ivci.viewType = numLayers == 1 ? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_2D_ARRAY;
- ivci.subresourceRange.aspectMask = aspects;
- ivci.subresourceRange.layerCount = numLayers;
- ivci.subresourceRange.levelCount = 1;
- res = vkCreateImageView(vulkan->GetDevice(), &ivci, nullptr, &img.rtView);
- vulkan->SetDebugName(img.rtView, VK_OBJECT_TYPE_IMAGE_VIEW, tag);
- _dbg_assert_(res == VK_SUCCESS);
-
- // Separate view for texture sampling all layers together.
- if (!color) {
- ivci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
- }
-
- ivci.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; // layered for consistency, even if single image.
- res = vkCreateImageView(vulkan->GetDevice(), &ivci, nullptr, &img.texAllLayersView);
- vulkan->SetDebugName(img.texAllLayersView, VK_OBJECT_TYPE_IMAGE_VIEW, tag);
-
- // Create 2D views for both layers.
- // Useful when multipassing shaders that don't yet exist in a single-pass-stereo version.
- for (int i = 0; i < numLayers; i++) {
- ivci.viewType = VK_IMAGE_VIEW_TYPE_2D;
- ivci.subresourceRange.layerCount = 1;
- ivci.subresourceRange.baseArrayLayer = i;
- res = vkCreateImageView(vulkan->GetDevice(), &ivci, nullptr, &img.texLayerViews[i]);
- if (vulkan->DebugLayerEnabled()) {
- char temp[128];
- snprintf(temp, sizeof(temp), "%s_layer%d", tag, i);
- vulkan->SetDebugName(img.texLayerViews[i], VK_OBJECT_TYPE_IMAGE_VIEW, temp);
- }
- _dbg_assert_(res == VK_SUCCESS);
- }
-
- VkPipelineStageFlags dstStage;
- VkAccessFlagBits dstAccessMask;
- switch (initialLayout) {
- case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
- dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
- dstStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
- break;
- case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
- dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
- dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
- break;
- case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
- dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
- dstStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
- break;
- default:
- Crash();
- return;
- }
-
- TransitionImageLayout2(cmd, img.image, 0, 1, numLayers, aspects,
- VK_IMAGE_LAYOUT_UNDEFINED, initialLayout,
- VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, dstStage,
- 0, dstAccessMask);
- img.layout = initialLayout;
- img.format = format;
- img.tag = tag ? tag : "N/A";
- img.numLayers = numLayers;
-}
-
VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan)
: vulkan_(vulkan), queueRunner_(vulkan),
initTimeMs_("initTimeMs"),
diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h
index ca22dbab2..de36633c6 100644
--- a/Common/GPU/Vulkan/VulkanRenderManager.h
+++ b/Common/GPU/Vulkan/VulkanRenderManager.h
@@ -21,70 +21,11 @@
#include "Common/GPU/DataFormat.h"
#include "Common/GPU/MiscTypes.h"
#include "Common/GPU/Vulkan/VulkanQueueRunner.h"
+#include "Common/GPU/Vulkan/VulkanFramebuffer.h"
// Forward declaration
VK_DEFINE_HANDLE(VmaAllocation);
-// Simple independent framebuffer image.
-struct VKRImage {
- // These four are "immutable".
- VkImage image;
-
- VkImageView rtView; // Used for rendering to, and readbacks of stencil. 2D if single layer, 2D_ARRAY if multiple. Includes both depth and stencil if depth/stencil.
-
- // This is for texturing all layers at once. If aspect is depth/stencil, does not include stencil.
- VkImageView texAllLayersView;
-
- // If it's a layered image (for stereo), this is two 2D views of it, to make it compatible with shaders that don't yet support stereo.
- // If there's only one layer, layerViews[0] only is initialized.
- VkImageView texLayerViews[2]{};
-
- VmaAllocation alloc;
- VkFormat format;
-
- // This one is used by QueueRunner's Perform functions to keep track. CANNOT be used anywhere else due to sync issues.
- VkImageLayout layout;
-
- int numLayers;
-
- // For debugging.
- std::string tag;
-};
-
-class VKRFramebuffer {
-public:
- VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, int _numLayers, bool createDepthStencilBuffer, const char *tag);
- ~VKRFramebuffer();
-
- VkFramebuffer Get(VKRRenderPass *compatibleRenderPass, RenderPassType rpType);
-
- int width = 0;
- int height = 0;
- int numLayers = 0;
-
- VKRImage color{}; // color.image is always there.
- VKRImage depth{}; // depth.image is allowed to be VK_NULL_HANDLE.
-
- const char *Tag() const {
- return tag_.c_str();
- }
-
- void UpdateTag(const char *newTag);
-
- bool HasDepth() const {
- return depth.image != VK_NULL_HANDLE;
- }
-
- // TODO: Hide.
- VulkanContext *vulkan_;
-private:
- static void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int width, int height, int numLayers, VkFormat format, VkImageLayout initialLayout, bool color, const char *tag);
-
- VkFramebuffer framebuf[(size_t)RenderPassType::TYPE_COUNT]{};
-
- std::string tag_;
-};
-
struct BoundingRect {
int x1;
int y1;
diff --git a/android/jni/Android.mk b/android/jni/Android.mk
index cc507e101..5168e0c8d 100644
--- a/android/jni/Android.mk
+++ b/android/jni/Android.mk
@@ -56,6 +56,7 @@ VULKAN_FILES := \
$(SRC)/Common/GPU/Vulkan/VulkanContext.cpp \
$(SRC)/Common/GPU/Vulkan/VulkanDebug.cpp \
$(SRC)/Common/GPU/Vulkan/VulkanImage.cpp \
+ $(SRC)/Common/GPU/Vulkan/VulkanFramebuffer.cpp \
$(SRC)/Common/GPU/Vulkan/VulkanMemory.cpp \
$(SRC)/Common/GPU/Vulkan/VulkanProfiler.cpp \
$(SRC)/Common/GPU/Vulkan/VulkanBarrier.cpp
diff --git a/libretro/Makefile.common b/libretro/Makefile.common
index b310680f1..a3f925b10 100644
--- a/libretro/Makefile.common
+++ b/libretro/Makefile.common
@@ -262,6 +262,7 @@ SOURCES_CXX += \
$(COMMONDIR)/GPU/Vulkan/VulkanContext.cpp \
$(COMMONDIR)/GPU/Vulkan/VulkanDebug.cpp \
$(COMMONDIR)/GPU/Vulkan/VulkanImage.cpp \
+ $(COMMONDIR)/GPU/Vulkan/VulkanFramebuffer.cpp \
$(COMMONDIR)/GPU/Vulkan/VulkanMemory.cpp \
$(COMMONDIR)/GPU/Vulkan/VulkanProfiler.cpp \
$(COMMONDIR)/GPU/Vulkan/VulkanBarrier.cpp \