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 \