/* * Copyright 2023 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "src/gpu/graphite/vk/VulkanRenderPass.h" #include "include/gpu/graphite/vk/VulkanGraphiteTypes.h" #include "src/gpu/graphite/RenderPassDesc.h" #include "src/gpu/graphite/Texture.h" #include "src/gpu/graphite/vk/VulkanCommandBuffer.h" #include "src/gpu/graphite/vk/VulkanGraphiteUtilsPriv.h" #include "src/gpu/graphite/vk/VulkanSharedContext.h" #include "src/gpu/graphite/vk/VulkanTexture.h" #include namespace skgpu::graphite { namespace { // anonymous namespace int determine_uint32_count(int rpAttachmentCount, int subpassCount, int subpassDependencyCount ) { // The key will be formed such that bigger-picture items (such as the total attachment count) // will be near the front of the key to more quickly eliminate incompatible keys. Each // renderpass key will start with the total number of attachments associated with it // followed by how many subpasses and subpass dependencies the renderpass has.Packed together, // these will use one uint32. int num32DataCnt = 1; SkASSERT(static_cast(rpAttachmentCount) <= (1u << 8)); SkASSERT(static_cast(subpassCount) <= (1u << 8)); SkASSERT(static_cast(subpassDependencyCount) <= (1u << 8)); // The key will then contain key information for each attachment. This includes format, sample // count, and load/store operation information. num32DataCnt += 3 * rpAttachmentCount; // Then, subpass information will be added in the form of attachment reference indices. Reserve // one int32 for each possible attachment reference type, of which there are 4. // There are 4 possible attachment reference types. Pack all 4 attachment reference indices into // one uint32. num32DataCnt += subpassCount; // Each subpass dependency will be allotted 6 int32s to store all its pertinent information. num32DataCnt += 6 * subpassDependencyCount; return num32DataCnt; } void add_attachment_description_info_to_key(ResourceKey::Builder& builder, const TextureInfo& textureInfo, int& builderIdx, LoadOp loadOp, StoreOp storeOp) { VulkanTextureInfo vkTexInfo; if (textureInfo.isValid() && TextureInfos::GetVulkanTextureInfo(textureInfo, &vkTexInfo)) { builder[builderIdx++] = vkTexInfo.fFormat; builder[builderIdx++] = vkTexInfo.fSampleCount; SkASSERT(sizeof(loadOp) < (1u << 8)); SkASSERT(sizeof(storeOp) < (1u << 8)); builder[builderIdx++] = static_cast(loadOp) << 8 | static_cast(storeOp); } // We only count attachments that are valid textures when calculating the total number of // render pass attachments, so if a texture is invalid, simply skip it rather than using // VK_ATTACHMENT_UNUSED and incrementing the builderIdx. Attachments can be differentiated from // one another by their sample count and format (i.e. depth/stencil attachments will have a // depth/stencil format). } void add_subpass_info_to_key(ResourceKey::Builder& builder, int& builderIdx, bool hasColorAttachment, bool hasColorResolveAttachment, bool hasDepthStencilAttachment, bool loadMSAAFromResolve, int subpassCount, int subpassDependencyCount) { // TODO: Fetch actual attachment reference and index information for each // subpass from RenderPassDesc. For now, determine subpass data based upon whether we are // loading from MSAA or not. const int mainSubpassIdx = loadMSAAFromResolve ? 1 : 0; // Assign a smaller value to represent VK_ATTACHMENT_UNUSED. static constexpr int kAttachmentUnused = std::numeric_limits::max(); // The following key structure assumes that we only have up to one reference of each type per // subpass and that attachments are indexed in order of color, resolve, depth/stencil, then // input attachments. These indices are statically defined in the VulkanRenderPass header file. for (int j = 0; j < subpassCount; j++) { if (j == mainSubpassIdx) { uint32_t attachmentIdxKeyInfo; attachmentIdxKeyInfo = hasColorAttachment ? VulkanRenderPass::kColorAttachmentIdx : kAttachmentUnused; attachmentIdxKeyInfo |= (hasColorResolveAttachment ? VulkanRenderPass::kColorResolveAttachmentIdx : kAttachmentUnused) << 8; attachmentIdxKeyInfo |= (hasDepthStencilAttachment ? VulkanRenderPass::kDepthStencilAttachmentIdx : kAttachmentUnused) << 16; // TODO: Add input attachment info to key once supported for use in main subpass attachmentIdxKeyInfo |= kAttachmentUnused << 24; builder[builderIdx++] = attachmentIdxKeyInfo; } else { // Loading MSAA from resolve subpass SkASSERT(hasColorAttachment); builder[builderIdx++] = VulkanRenderPass::kColorAttachmentIdx | // color attachment (kAttachmentUnused << 8) | // No color resolve attachment (kAttachmentUnused << 16) | // No depth/stencil attachment // The input attachment for the load subpass is the color resolve texture. (VulkanRenderPass::kColorResolveAttachmentIdx << 24); } } // TODO: Query RenderPassDesc for subpass dependency information & populate the key accordingly. // For now, we know that the only subpass dependency will be that expected for loading MSAA from // resolve. for (int i = 0; i < subpassDependencyCount; i++) { builder[builderIdx++] = 0 | (mainSubpassIdx << 8); // srcSubpass, dstSubpass builder[builderIdx++] = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; // srcStageMask builder[builderIdx++] = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; // dstStageMask builder[builderIdx++] = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; // srcAccessMask builder[builderIdx++] = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | // dstAccessMask VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; builder[builderIdx++] = VK_DEPENDENCY_BY_REGION_BIT; // dependencyFlags } } void populate_key(VulkanRenderPass::VulkanRenderPassMetaData& rpMetaData, ResourceKey::Builder& builder, int& builderIdx, bool compatibleOnly) { builder[builderIdx++] = rpMetaData.fAttachments.size() | (rpMetaData.fSubpassCount << 8) | (rpMetaData.fSubpassDependencyCount << 16); // Iterate through each renderpass attachment to add its information for (int i = 0; i < rpMetaData.fAttachments.size(); i++) { add_attachment_description_info_to_key( builder, rpMetaData.fAttachments[i]->fTextureInfo, builderIdx, // Assign LoadOp::kLoad and StoreOp::kStore as default load/store operations for // compatible render passes where load/store ops don't need to match. compatibleOnly ? LoadOp::kLoad : rpMetaData.fAttachments[i]->fLoadOp, compatibleOnly ? StoreOp::kStore : rpMetaData.fAttachments[i]->fStoreOp); } add_subpass_info_to_key(builder, builderIdx, rpMetaData.fHasColorAttachment, rpMetaData.fHasColorResolveAttachment, rpMetaData.fHasDepthStencilAttachment, rpMetaData.fLoadMSAAFromResolve, rpMetaData.fSubpassCount, rpMetaData.fSubpassDependencyCount); } } // anonymous namespace VulkanRenderPass::VulkanRenderPassMetaData::VulkanRenderPassMetaData( const RenderPassDesc& renderPassDesc) { fLoadMSAAFromResolve = renderPassDesc.fColorResolveAttachment.fTextureInfo.isValid() && renderPassDesc.fColorResolveAttachment.fLoadOp == LoadOp::kLoad; fHasColorAttachment = renderPassDesc.fColorAttachment.fTextureInfo.isValid(); fHasColorResolveAttachment = renderPassDesc.fColorResolveAttachment.fTextureInfo.isValid(); fHasDepthStencilAttachment = renderPassDesc.fDepthStencilAttachment.fTextureInfo.isValid(); // TODO: Query for more attachments once the RenderPassDesc struct contains that information. // For now, we only ever expect to see 0 or 1 of each attachment type (color, resolve, and // depth/stencil), so the count of each of those can simply be determined with a bool. fNumColorAttachments = fHasColorAttachment ? 1 : 0; fNumResolveAttachments = fHasColorResolveAttachment ? 1 : 0; fNumDepthStencilAttachments = fHasDepthStencilAttachment ? 1 : 0; // Accumulate attachments into a container to mimic future structure in RenderPassDesc fAttachments = skia_private::TArray(fNumColorAttachments + fNumResolveAttachments + fNumDepthStencilAttachments); if (fHasColorAttachment) { fAttachments.push_back(&renderPassDesc.fColorAttachment); } if (fHasColorResolveAttachment) { fAttachments.push_back(&renderPassDesc.fColorResolveAttachment); } if (fHasDepthStencilAttachment) { fAttachments.push_back(&renderPassDesc.fDepthStencilAttachment); } // TODO: Reference RenderPassDesc to determine number and makeup of subpasses and their // dependencies. For now, we only ever expect 1 (in most cases) or 2 (when loading MSAA). fSubpassCount = fLoadMSAAFromResolve ? 2 : 1; fSubpassDependencyCount = fLoadMSAAFromResolve ? 1 : 0; fUint32DataCnt = determine_uint32_count( fAttachments.size(), fSubpassCount, fSubpassDependencyCount); } GraphiteResourceKey VulkanRenderPass::MakeRenderPassKey( const RenderPassDesc& renderPassDesc, bool compatibleOnly) { VulkanRenderPassMetaData rpMetaData = VulkanRenderPassMetaData(renderPassDesc); static const ResourceType kType = GraphiteResourceKey::GenerateResourceType(); GraphiteResourceKey key; GraphiteResourceKey::Builder builder(&key, kType, rpMetaData.fUint32DataCnt, Shareable::kYes); int startingIdx = 0; populate_key(rpMetaData, builder, startingIdx, compatibleOnly); builder.finish(); return key; } void VulkanRenderPass::AddRenderPassInfoToKey(VulkanRenderPassMetaData& rpMetaData, ResourceKey::Builder& builder, int& builderIdx, bool compatibleOnly) { populate_key(rpMetaData, builder, builderIdx, /*compatibleOnly=*/true); } namespace { // anonymous namespace void setup_vk_attachment_description(VkAttachmentDescription* outAttachment, const VulkanTextureInfo& textureInfo, const AttachmentDesc& desc, const LoadOp loadOp, const StoreOp storeOp, const VkImageLayout initialLayout, const VkImageLayout finalLayout) { static_assert((int)LoadOp::kLoad == 0); static_assert((int)LoadOp::kClear == 1); static_assert((int)LoadOp::kDiscard == 2); static_assert(std::size(vkLoadOp) == kLoadOpCount); static_assert((int)StoreOp::kStore == 0); static_assert((int)StoreOp::kDiscard == 1); static_assert(std::size(vkStoreOp) == kStoreOpCount); outAttachment->flags = 0; outAttachment->format = textureInfo.fFormat; VkSampleCountFlagBits sampleCount; SkAssertResult( skgpu::SampleCountToVkSampleCount(textureInfo.fSampleCount, &sampleCount)); outAttachment->samples = sampleCount; switch (initialLayout) { case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: case VK_IMAGE_LAYOUT_GENERAL: outAttachment->loadOp = vkLoadOp[static_cast(loadOp)]; outAttachment->storeOp = vkStoreOp[static_cast(storeOp)]; outAttachment->stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; outAttachment->stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; break; case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: // The loadOp and storeOp refer to the depth part of the attachment and the stencil*Ops // refer to the stencil bits in the attachment. outAttachment->loadOp = vkLoadOp[static_cast(loadOp)]; outAttachment->storeOp = vkStoreOp[static_cast(storeOp)]; outAttachment->stencilLoadOp = vkLoadOp[static_cast(loadOp)]; outAttachment->stencilStoreOp = vkStoreOp[static_cast(storeOp)]; break; default: SK_ABORT("Unexpected attachment layout"); } outAttachment->initialLayout = initialLayout; outAttachment->finalLayout = finalLayout == VK_IMAGE_LAYOUT_UNDEFINED ? initialLayout : finalLayout; } } // anonymous namespace sk_sp VulkanRenderPass::MakeRenderPass(const VulkanSharedContext* context, const RenderPassDesc& renderPassDesc, bool compatibleOnly) { VkRenderPass renderPass; renderPass = VK_NULL_HANDLE; auto& colorAttachmentTextureInfo = renderPassDesc.fColorAttachment.fTextureInfo; auto& colorResolveAttachmentTextureInfo = renderPassDesc.fColorResolveAttachment.fTextureInfo; auto& depthStencilAttachmentTextureInfo = renderPassDesc.fDepthStencilAttachment.fTextureInfo; bool hasColorAttachment = colorAttachmentTextureInfo.isValid(); bool hasColorResolveAttachment = colorResolveAttachmentTextureInfo.isValid(); bool hasDepthStencilAttachment = depthStencilAttachmentTextureInfo.isValid(); skia_private::TArray attachmentDescs; // Create and track attachment references for the subpass. VkAttachmentReference colorRef; VkAttachmentReference resolveRef; VkAttachmentReference resolveLoadInputRef; VkAttachmentReference depthStencilRef; bool loadMSAAFromResolve = false; if (hasColorAttachment) { VulkanTextureInfo colorAttachTexInfo; SkAssertResult(TextureInfos::GetVulkanTextureInfo(colorAttachmentTextureInfo, &colorAttachTexInfo)); auto& colorAttachDesc = renderPassDesc.fColorAttachment; colorRef.attachment = attachmentDescs.size(); VkAttachmentDescription& vkColorAttachDesc = attachmentDescs.push_back(); memset(&vkColorAttachDesc, 0, sizeof(VkAttachmentDescription)); setup_vk_attachment_description( &vkColorAttachDesc, colorAttachTexInfo, colorAttachDesc, compatibleOnly ? LoadOp::kDiscard : colorAttachDesc.fLoadOp, compatibleOnly ? StoreOp::kDiscard : colorAttachDesc.fStoreOp, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); colorRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; if (hasColorResolveAttachment) { loadMSAAFromResolve = renderPassDesc.fColorResolveAttachment.fLoadOp == LoadOp::kLoad; SkASSERT(renderPassDesc.fColorResolveAttachment.fStoreOp == StoreOp::kStore); VulkanTextureInfo resolveAttachTexInfo; SkAssertResult(TextureInfos::GetVulkanTextureInfo(colorResolveAttachmentTextureInfo, &resolveAttachTexInfo)); auto& resolveAttachDesc = renderPassDesc.fColorResolveAttachment; resolveRef.attachment = attachmentDescs.size(); VkAttachmentDescription& vkResolveAttachDesc = attachmentDescs.push_back(); memset(&vkResolveAttachDesc, 0, sizeof(VkAttachmentDescription)); setup_vk_attachment_description( &vkResolveAttachDesc, resolveAttachTexInfo, resolveAttachDesc, compatibleOnly ? LoadOp::kDiscard : resolveAttachDesc.fLoadOp, compatibleOnly ? StoreOp::kDiscard : resolveAttachDesc.fStoreOp, loadMSAAFromResolve ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); resolveRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; } else { resolveRef.attachment = VK_ATTACHMENT_UNUSED; resolveRef.layout = VK_IMAGE_LAYOUT_UNDEFINED; } } else { SkASSERT(false); colorRef.attachment = VK_ATTACHMENT_UNUSED; colorRef.layout = VK_IMAGE_LAYOUT_UNDEFINED; resolveRef.attachment = VK_ATTACHMENT_UNUSED; resolveRef.layout = VK_IMAGE_LAYOUT_UNDEFINED; } if (hasDepthStencilAttachment) { VulkanTextureInfo depthStencilTexInfo; SkAssertResult(TextureInfos::GetVulkanTextureInfo(depthStencilAttachmentTextureInfo, &depthStencilTexInfo)); auto& depthStencilAttachDesc = renderPassDesc.fDepthStencilAttachment; depthStencilRef.attachment = attachmentDescs.size(); VkAttachmentDescription& vkDepthStencilAttachDesc = attachmentDescs.push_back(); setup_vk_attachment_description( &vkDepthStencilAttachDesc, depthStencilTexInfo, depthStencilAttachDesc, compatibleOnly ? LoadOp::kDiscard : depthStencilAttachDesc.fLoadOp, compatibleOnly ? StoreOp::kDiscard : depthStencilAttachDesc.fStoreOp, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); depthStencilRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; } else { depthStencilRef.attachment = VK_ATTACHMENT_UNUSED; depthStencilRef.layout = VK_IMAGE_LAYOUT_UNDEFINED; } // Create VkRenderPass VkRenderPassCreateInfo renderPassInfo; memset(&renderPassInfo, 0, sizeof(VkRenderPassCreateInfo)); renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.pNext = nullptr; renderPassInfo.flags = 0; renderPassInfo.subpassCount = loadMSAAFromResolve ? 2 : 1; skia_private::TArray subpassDescs(renderPassInfo.subpassCount); memset(subpassDescs.begin(), 0, renderPassInfo.subpassCount * sizeof(VkSubpassDescription)); // If we are loading MSAA from resolve, that subpass must always be first. VkSubpassDependency dependency; if (loadMSAAFromResolve) { resolveLoadInputRef.attachment = resolveRef.attachment; resolveLoadInputRef.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; VkSubpassDescription& loadSubpassDesc = subpassDescs.push_back(); memset(&loadSubpassDesc, 0, sizeof(VkSubpassDescription)); loadSubpassDesc.flags = 0; loadSubpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; loadSubpassDesc.inputAttachmentCount = 1; loadSubpassDesc.pInputAttachments = &resolveLoadInputRef; loadSubpassDesc.colorAttachmentCount = 1; loadSubpassDesc.pColorAttachments = &colorRef; loadSubpassDesc.pResolveAttachments = nullptr; loadSubpassDesc.pDepthStencilAttachment = nullptr; loadSubpassDesc.preserveAttachmentCount = 0; loadSubpassDesc.pPreserveAttachments = nullptr; // Set up the subpass dependency const int mainSubpassIdx = loadMSAAFromResolve ? 1 : 0; dependency.srcSubpass = 0; dependency.dstSubpass = mainSubpassIdx; dependency.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependency.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; } VkSubpassDescription& mainSubpassDesc = subpassDescs.push_back(); memset(&mainSubpassDesc, 0, sizeof(VkSubpassDescription)); mainSubpassDesc.flags = 0; mainSubpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; mainSubpassDesc.inputAttachmentCount = 0; // TODO: Add input attachment support in main subpass mainSubpassDesc.pInputAttachments = nullptr; mainSubpassDesc.colorAttachmentCount = 1; mainSubpassDesc.pColorAttachments = &colorRef; mainSubpassDesc.pResolveAttachments = &resolveRef; mainSubpassDesc.pDepthStencilAttachment = &depthStencilRef; mainSubpassDesc.preserveAttachmentCount = 0; mainSubpassDesc.pPreserveAttachments = nullptr; renderPassInfo.pSubpasses = subpassDescs.begin(); renderPassInfo.dependencyCount = loadMSAAFromResolve ? 1 : 0; renderPassInfo.pDependencies = loadMSAAFromResolve ? &dependency : VK_NULL_HANDLE; renderPassInfo.attachmentCount = attachmentDescs.size(); renderPassInfo.pAttachments = attachmentDescs.begin(); VkResult result; VULKAN_CALL_RESULT(context, result, CreateRenderPass(context->device(), &renderPassInfo, nullptr, &renderPass)); if (result != VK_SUCCESS) { return nullptr; } VkExtent2D granularity; VULKAN_CALL(context->interface(), GetRenderAreaGranularity(context->device(), renderPass, &granularity)); return sk_sp(new VulkanRenderPass(context, renderPass, granularity)); } VulkanRenderPass::VulkanRenderPass(const VulkanSharedContext* context, VkRenderPass renderPass, VkExtent2D granularity) : Resource(context, Ownership::kOwned, skgpu::Budgeted::kYes, /*gpuMemorySize=*/0) , fSharedContext(context) , fRenderPass(renderPass) , fGranularity(granularity) {} void VulkanRenderPass::freeGpuData() { VULKAN_CALL(fSharedContext->interface(), DestroyRenderPass(fSharedContext->device(), fRenderPass, nullptr)); } } // namespace skgpu::graphite