mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
Merge pull request #16443 from hrydgard/minor-vulkan-refactor
Break out VKRFramebuffer/VKRRenderpass from VulkanRenderManager
This commit is contained in:
commit
71410f2cb7
11 changed files with 507 additions and 483 deletions
|
@ -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
|
||||
|
|
|
@ -450,6 +450,7 @@
|
|||
<ClInclude Include="GPU\Vulkan\VulkanBarrier.h" />
|
||||
<ClInclude Include="GPU\Vulkan\VulkanContext.h" />
|
||||
<ClInclude Include="GPU\Vulkan\VulkanDebug.h" />
|
||||
<ClInclude Include="GPU\Vulkan\VulkanFramebuffer.h" />
|
||||
<ClInclude Include="GPU\Vulkan\VulkanFrameData.h" />
|
||||
<ClInclude Include="GPU\Vulkan\VulkanImage.h" />
|
||||
<ClInclude Include="GPU\Vulkan\VulkanLoader.h" />
|
||||
|
@ -881,6 +882,7 @@
|
|||
<ClCompile Include="GPU\Vulkan\VulkanBarrier.cpp" />
|
||||
<ClCompile Include="GPU\Vulkan\VulkanContext.cpp" />
|
||||
<ClCompile Include="GPU\Vulkan\VulkanDebug.cpp" />
|
||||
<ClCompile Include="GPU\Vulkan\VulkanFramebuffer.cpp" />
|
||||
<ClCompile Include="GPU\Vulkan\VulkanFrameData.cpp" />
|
||||
<ClCompile Include="GPU\Vulkan\VulkanImage.cpp" />
|
||||
<ClCompile Include="GPU\Vulkan\VulkanLoader.cpp" />
|
||||
|
|
|
@ -455,6 +455,9 @@
|
|||
<ClInclude Include="GPU\MiscTypes.h">
|
||||
<Filter>GPU</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GPU\Vulkan\VulkanFramebuffer.h">
|
||||
<Filter>GPU\Vulkan</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ABI.cpp" />
|
||||
|
@ -860,6 +863,9 @@
|
|||
<ClCompile Include="Render\ManagedTexture.cpp">
|
||||
<Filter>Render</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GPU\Vulkan\VulkanFramebuffer.cpp">
|
||||
<Filter>GPU\Vulkan</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Crypto">
|
||||
|
|
353
Common/GPU/Vulkan/VulkanFramebuffer.cpp
Normal file
353
Common/GPU/Vulkan/VulkanFramebuffer.cpp
Normal file
|
@ -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];
|
||||
}
|
140
Common/GPU/Vulkan/VulkanFramebuffer.h
Normal file
140
Common/GPU/Vulkan/VulkanFramebuffer.h
Normal file
|
@ -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_;
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 \
|
||||
|
|
Loading…
Add table
Reference in a new issue