| /* |
| * Copyright 2025 Rive |
| */ |
| |
| #include <vulkan/vulkan.h> |
| #include "rive/renderer/vulkan/render_context_vulkan_impl.hpp" |
| #include "rive/renderer/stack_vector.hpp" |
| #include "shaders/constants.glsl" |
| #include "pipeline_manager_vulkan.hpp" |
| #include "render_pass_vulkan.hpp" |
| |
| namespace rive::gpu |
| { |
| constexpr static VkAttachmentLoadOp vk_load_op(gpu::LoadAction loadAction, |
| gpu::InterlockMode interlockMode) |
| { |
| switch (loadAction) |
| { |
| case gpu::LoadAction::preserveRenderTarget: |
| return (interlockMode == gpu::InterlockMode::msaa) |
| // In MSAA we need to implement the loadOp with a manual |
| // draw instead, since the MSAA attachment is transient |
| // and its color is seeded from the actual render target. |
| ? VK_ATTACHMENT_LOAD_OP_DONT_CARE |
| : VK_ATTACHMENT_LOAD_OP_LOAD; |
| case gpu::LoadAction::clear: |
| return VK_ATTACHMENT_LOAD_OP_CLEAR; |
| case gpu::LoadAction::dontCare: |
| return VK_ATTACHMENT_LOAD_OP_DONT_CARE; |
| } |
| RIVE_UNREACHABLE(); |
| } |
| |
| uint32_t RenderPassVulkan::Key(gpu::InterlockMode interlockMode, |
| DrawPipelineLayoutVulkan::Options layoutOptions, |
| VkFormat renderTargetFormat, |
| gpu::LoadAction loadAction) |
| { |
| uint32_t formatKey = static_cast<uint32_t>(renderTargetFormat); |
| if (formatKey > VK_FORMAT_ASTC_12x12_SRGB_BLOCK) |
| { |
| assert(formatKey >= VK_FORMAT_G8B8G8R8_422_UNORM); |
| formatKey -= |
| VK_FORMAT_G8B8G8R8_422_UNORM - VK_FORMAT_ASTC_12x12_SRGB_BLOCK - 1; |
| } |
| assert(formatKey <= 1 << FORMAT_BIT_COUNT); |
| |
| uint32_t drawPipelineLayoutIdx = |
| DrawPipelineLayoutIdx(interlockMode, layoutOptions); |
| assert(drawPipelineLayoutIdx < 1 << DrawPipelineLayoutVulkan::BIT_COUNT); |
| |
| assert(static_cast<uint32_t>(loadAction) < 1 << LOAD_OP_BIT_COUNT); |
| |
| return (formatKey << (DrawPipelineLayoutVulkan::BIT_COUNT + |
| LOAD_OP_BIT_COUNT)) | |
| (drawPipelineLayoutIdx << LOAD_OP_BIT_COUNT) | |
| static_cast<uint32_t>(loadAction); |
| } |
| |
| RenderPassVulkan::RenderPassVulkan( |
| PipelineManagerVulkan* pipelineManager, |
| gpu::InterlockMode interlockMode, |
| DrawPipelineLayoutVulkan::Options layoutOptions, |
| VkFormat renderTargetFormat, |
| gpu::LoadAction loadAction) : |
| m_vk(ref_rcp(pipelineManager->vulkanContext())) |
| { |
| m_drawPipelineLayout = |
| &pipelineManager->getDrawPipelineLayoutSynchronous(interlockMode, |
| layoutOptions); |
| |
| // COLOR attachment. |
| const VkImageLayout colorAttachmentLayout = |
| (layoutOptions & |
| DrawPipelineLayoutVulkan::Options::fixedFunctionColorOutput) |
| ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL |
| : VK_IMAGE_LAYOUT_GENERAL; |
| const VkSampleCountFlagBits msaaSampleCount = |
| (interlockMode == gpu::InterlockMode::msaa) ? VK_SAMPLE_COUNT_4_BIT |
| : VK_SAMPLE_COUNT_1_BIT; |
| StackVector<VkAttachmentDescription, PLS_PLANE_COUNT> attachments; |
| StackVector<VkAttachmentReference, PLS_PLANE_COUNT> colorAttachmentRefs; |
| StackVector<VkAttachmentReference, 1> plsResolveAttachmentRef; |
| StackVector<VkAttachmentReference, 1> depthStencilAttachmentRef; |
| StackVector<VkAttachmentReference, 1> msaaResolveAttachmentRef; |
| assert(attachments.size() == COLOR_PLANE_IDX); |
| assert(colorAttachmentRefs.size() == COLOR_PLANE_IDX); |
| attachments.push_back({ |
| .format = renderTargetFormat, |
| .samples = msaaSampleCount, |
| .loadOp = vk_load_op(loadAction, interlockMode), |
| .storeOp = |
| ((layoutOptions & |
| DrawPipelineLayoutVulkan::Options::coalescedResolveAndTransfer) || |
| interlockMode == gpu::InterlockMode::msaa) |
| ? VK_ATTACHMENT_STORE_OP_DONT_CARE |
| : VK_ATTACHMENT_STORE_OP_STORE, |
| // This could be VK_IMAGE_LAYOUT_UNDEFINED more often, but it would |
| // invalidate the portion outside the renderArea when it isn't the full |
| // renderTarget, and currently we don't have separate render passes for |
| // "full renderTarget bounds" and "partial renderTarget bounds". |
| // Instead, we rely on vkutil::ImageAccessAction::invalidateContents |
| // to invalidate the color attachment when we can. |
| .initialLayout = |
| (((layoutOptions & DrawPipelineLayoutVulkan::Options:: |
| coalescedResolveAndTransfer) && |
| loadAction != gpu::LoadAction::preserveRenderTarget) || |
| interlockMode == gpu::InterlockMode::msaa) |
| ? VK_IMAGE_LAYOUT_UNDEFINED |
| : colorAttachmentLayout, |
| .finalLayout = colorAttachmentLayout, |
| }); |
| colorAttachmentRefs.push_back({ |
| .attachment = COLOR_PLANE_IDX, |
| .layout = colorAttachmentLayout, |
| }); |
| |
| if (interlockMode == gpu::InterlockMode::rasterOrdering || |
| interlockMode == gpu::InterlockMode::atomics) |
| { |
| // CLIP attachment. |
| assert(attachments.size() == CLIP_PLANE_IDX); |
| assert(colorAttachmentRefs.size() == CLIP_PLANE_IDX); |
| attachments.push_back({ |
| // The clip buffer is encoded as RGBA8 in atomic mode so we can |
| // block writes by emitting alpha=0. |
| .format = (interlockMode == gpu::InterlockMode::atomics) |
| ? VK_FORMAT_R8G8B8A8_UNORM |
| : VK_FORMAT_R32_UINT, |
| .samples = VK_SAMPLE_COUNT_1_BIT, |
| .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, |
| .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, |
| .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, |
| .finalLayout = VK_IMAGE_LAYOUT_GENERAL, |
| }); |
| colorAttachmentRefs.push_back({ |
| .attachment = CLIP_PLANE_IDX, |
| .layout = VK_IMAGE_LAYOUT_GENERAL, |
| }); |
| } |
| |
| if (interlockMode == gpu::InterlockMode::rasterOrdering) |
| { |
| // SCRATCH_COLOR attachment. |
| assert(attachments.size() == SCRATCH_COLOR_PLANE_IDX); |
| assert(colorAttachmentRefs.size() == SCRATCH_COLOR_PLANE_IDX); |
| attachments.push_back({ |
| .format = renderTargetFormat, |
| .samples = VK_SAMPLE_COUNT_1_BIT, |
| .loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, |
| .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, |
| .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, |
| .finalLayout = VK_IMAGE_LAYOUT_GENERAL, |
| }); |
| colorAttachmentRefs.push_back({ |
| .attachment = SCRATCH_COLOR_PLANE_IDX, |
| .layout = VK_IMAGE_LAYOUT_GENERAL, |
| }); |
| |
| // COVERAGE attachment. |
| assert(attachments.size() == COVERAGE_PLANE_IDX); |
| assert(colorAttachmentRefs.size() == COVERAGE_PLANE_IDX); |
| attachments.push_back({ |
| .format = VK_FORMAT_R32_UINT, |
| .samples = VK_SAMPLE_COUNT_1_BIT, |
| .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, |
| .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, |
| .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, |
| .finalLayout = VK_IMAGE_LAYOUT_GENERAL, |
| }); |
| colorAttachmentRefs.push_back({ |
| .attachment = COVERAGE_PLANE_IDX, |
| .layout = VK_IMAGE_LAYOUT_GENERAL, |
| }); |
| } |
| else if (interlockMode == gpu::InterlockMode::atomics) |
| { |
| if (layoutOptions & |
| DrawPipelineLayoutVulkan::Options::coalescedResolveAndTransfer) |
| { |
| // COALESCED_ATOMIC_RESOLVE attachment (primary render target). |
| assert(attachments.size() == COALESCED_ATOMIC_RESOLVE_IDX); |
| attachments.push_back({ |
| .format = renderTargetFormat, |
| .samples = VK_SAMPLE_COUNT_1_BIT, |
| .loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, |
| .storeOp = VK_ATTACHMENT_STORE_OP_STORE, |
| // This could sometimes be VK_IMAGE_LAYOUT_UNDEFINED, but it |
| // would invalidate the portion outside the renderArea when it |
| // isn't the full renderTarget, and currently we don't have |
| // separate render passes for "full renderTarget bounds" and |
| // "partial renderTarget bounds". Instead, we rely on |
| // vkutil::ImageAccessAction::invalidateContents to invalidate |
| // the atomic resolve attachment when we can. |
| .initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, |
| .finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, |
| }); |
| |
| // The resolve subpass only renders to the resolve texture. |
| // And the "coalesced" resolve shader outputs to color |
| // attachment 0, so alias the COALESCED_ATOMIC_RESOLVE |
| // attachment on output 0 for this subpass. |
| assert(plsResolveAttachmentRef.size() == 0); |
| plsResolveAttachmentRef.push_back({ |
| .attachment = COALESCED_ATOMIC_RESOLVE_IDX, |
| .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, |
| }); |
| } |
| else |
| { |
| // When not in "coalesced" mode, the resolve texture is the |
| // same as the COLOR texture. |
| static_assert(COLOR_PLANE_IDX == 0); |
| assert(plsResolveAttachmentRef.size() == 0); |
| plsResolveAttachmentRef.push_back(colorAttachmentRefs[0]); |
| } |
| } |
| else if (interlockMode == gpu::InterlockMode::msaa) |
| { |
| // DEPTH attachment. |
| assert(attachments.size() == MSAA_DEPTH_STENCIL_IDX); |
| attachments.push_back({ |
| .format = vkutil::get_preferred_depth_stencil_format( |
| m_vk->supportsD24S8()), |
| .samples = msaaSampleCount, |
| .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, |
| .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, |
| .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, |
| .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, |
| .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, |
| .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, |
| }); |
| depthStencilAttachmentRef.push_back({ |
| .attachment = MSAA_DEPTH_STENCIL_IDX, |
| .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, |
| }); |
| |
| // MSAA_RESOLVE attachment. |
| const bool readsMSAAResolveAttachment = |
| loadAction == gpu::LoadAction::preserveRenderTarget && |
| !(layoutOptions & |
| DrawPipelineLayoutVulkan::Options::msaaSeedFromOffscreenTexture); |
| const VkImageLayout msaaResolveLayout = |
| readsMSAAResolveAttachment |
| ? VK_IMAGE_LAYOUT_GENERAL |
| : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; |
| assert(attachments.size() == MSAA_RESOLVE_IDX); |
| attachments.push_back({ |
| .format = renderTargetFormat, |
| .samples = VK_SAMPLE_COUNT_1_BIT, |
| .loadOp = readsMSAAResolveAttachment |
| ? VK_ATTACHMENT_LOAD_OP_LOAD |
| : VK_ATTACHMENT_LOAD_OP_DONT_CARE, |
| .storeOp = VK_ATTACHMENT_STORE_OP_STORE, |
| .initialLayout = |
| readsMSAAResolveAttachment |
| ? msaaResolveLayout |
| // NOTE: This can only be VK_IMAGE_LAYOUT_UNDEFINED because |
| // Vulkan does not support partial MSAA resolves, so every |
| // MSAA render pass covers the entire render area. |
| // TODO: If we add a new subpass that reads the MSAA buffer |
| // and resolves it manually for partial updates, this will |
| // need to change to "msaaResolveLayout". |
| : VK_IMAGE_LAYOUT_UNDEFINED, |
| .finalLayout = msaaResolveLayout, |
| }); |
| msaaResolveAttachmentRef.push_back({ |
| .attachment = MSAA_RESOLVE_IDX, |
| .layout = msaaResolveLayout, |
| }); |
| assert(msaaResolveAttachmentRef.size() == colorAttachmentRefs.size()); |
| |
| if (layoutOptions & |
| DrawPipelineLayoutVulkan::Options::msaaSeedFromOffscreenTexture) |
| { |
| // MSAA_SEED attachment. |
| assert(loadAction == gpu::LoadAction::preserveRenderTarget); |
| assert(attachments.size() == MSAA_COLOR_SEED_IDX); |
| attachments.push_back({ |
| .format = renderTargetFormat, |
| .samples = VK_SAMPLE_COUNT_1_BIT, |
| .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, |
| .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, |
| .initialLayout = VK_IMAGE_LAYOUT_GENERAL, |
| .finalLayout = VK_IMAGE_LAYOUT_GENERAL, |
| }); |
| } |
| } |
| |
| // Input attachments. |
| StackVector<VkAttachmentReference, PLS_PLANE_COUNT> inputAttachmentRefs; |
| StackVector<VkAttachmentReference, 1> msaaColorSeedInputAttachmentRef; |
| if (interlockMode != gpu::InterlockMode::clockwiseAtomic) |
| { |
| inputAttachmentRefs.push_back_n(colorAttachmentRefs.size(), |
| colorAttachmentRefs.data()); |
| if (layoutOptions & |
| DrawPipelineLayoutVulkan::Options::fixedFunctionColorOutput) |
| { |
| // COLOR is not an input attachment if we're using fixed function |
| // blending. |
| if (inputAttachmentRefs.size() > 1) |
| { |
| inputAttachmentRefs[0] = {.attachment = VK_ATTACHMENT_UNUSED}; |
| } |
| else |
| { |
| inputAttachmentRefs.clear(); |
| } |
| } |
| if (interlockMode == gpu::InterlockMode::msaa && |
| loadAction == gpu::LoadAction::preserveRenderTarget) |
| { |
| msaaColorSeedInputAttachmentRef.push_back({ |
| .attachment = |
| (layoutOptions & DrawPipelineLayoutVulkan::Options:: |
| msaaSeedFromOffscreenTexture) |
| ? MSAA_COLOR_SEED_IDX |
| : MSAA_RESOLVE_IDX, |
| .layout = VK_IMAGE_LAYOUT_GENERAL, |
| }); |
| } |
| } |
| |
| const bool rasterOrderedAttachmentAccess = |
| interlockMode == gpu::InterlockMode::rasterOrdering && |
| m_vk->features.rasterizationOrderColorAttachmentAccess; |
| |
| constexpr uint32_t MAX_SUBPASSES = 2; |
| StackVector<VkSubpassDescription, MAX_SUBPASSES> subpassDescs; |
| |
| constexpr uint32_t MAX_SUBPASS_DEPS = MAX_SUBPASSES + 1; |
| StackVector<VkSubpassDependency, MAX_SUBPASS_DEPS> subpassDeps; |
| |
| // MSAA color-load subpass. |
| if (interlockMode == gpu::InterlockMode::msaa && |
| loadAction == gpu::LoadAction::preserveRenderTarget) |
| { |
| assert(msaaColorSeedInputAttachmentRef.size() == |
| colorAttachmentRefs.size()); |
| assert(subpassDescs.size() == 0); |
| subpassDescs.push_back({ |
| .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, |
| .inputAttachmentCount = msaaColorSeedInputAttachmentRef.size(), |
| .pInputAttachments = msaaColorSeedInputAttachmentRef.data(), |
| .colorAttachmentCount = colorAttachmentRefs.size(), |
| .pColorAttachments = colorAttachmentRefs.data(), |
| }); |
| // The color-load subpass has a self dependency because it reads the |
| // result of seed attachment's loadOp when it draws it into the MSAA |
| // attachment. (loadOps always occur in |
| // VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT.) |
| subpassDeps.push_back({ |
| .srcSubpass = 0, |
| .dstSubpass = 0, |
| .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, |
| .dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, |
| .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, |
| .dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT, |
| .dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT, |
| }); |
| } |
| |
| // Main subpass. |
| const uint32_t mainSubpassIdx = subpassDescs.size(); |
| assert(colorAttachmentRefs.size() == |
| m_drawPipelineLayout->colorAttachmentCount(0)); |
| assert(msaaResolveAttachmentRef.size() == 0 || |
| msaaResolveAttachmentRef.size() == colorAttachmentRefs.size()); |
| subpassDescs.push_back({ |
| .flags = |
| rasterOrderedAttachmentAccess |
| ? VK_SUBPASS_DESCRIPTION_RASTERIZATION_ORDER_ATTACHMENT_COLOR_ACCESS_BIT_EXT |
| : 0u, |
| .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, |
| .inputAttachmentCount = inputAttachmentRefs.size(), |
| .pInputAttachments = inputAttachmentRefs.data(), |
| .colorAttachmentCount = colorAttachmentRefs.size(), |
| .pColorAttachments = colorAttachmentRefs.data(), |
| .pResolveAttachments = msaaResolveAttachmentRef.dataOrNull(), |
| .pDepthStencilAttachment = depthStencilAttachmentRef.dataOrNull(), |
| }); |
| if ((interlockMode == gpu::InterlockMode::rasterOrdering && |
| !rasterOrderedAttachmentAccess) || |
| interlockMode == gpu::InterlockMode::atomics || |
| (interlockMode == gpu::InterlockMode::msaa && |
| !(layoutOptions & |
| DrawPipelineLayoutVulkan::Options::fixedFunctionColorOutput))) |
| { |
| // Any subpass that reads the framebuffer or PLS planes has a self |
| // dependency. |
| // |
| // In implicit rasterOrdering mode (meaning |
| // EXT_rasterization_order_attachment_access is not present, but |
| // we're on ARM hardware and know the hardware is raster ordered |
| // anyway), we also need to declare this dependency even though |
| // we won't be issuing any barriers. |
| subpassDeps.push_back({ |
| .srcSubpass = mainSubpassIdx, |
| .dstSubpass = mainSubpassIdx, |
| .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, |
| .dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, |
| // TODO: We should add SHADER_READ/SHADER_WRITE flags for the |
| // coverage buffer as well, but ironically, adding those seems to |
| // cause artifacts on Qualcomm. Leave them out for now until we can |
| // investigate further. |
| .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, |
| .dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT, |
| .dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT, |
| }); |
| } |
| else if (interlockMode == gpu::InterlockMode::clockwiseAtomic) |
| { |
| // clockwiseAtomic mode has a dependency when we switch from |
| // borrowed coverage into forward. |
| subpassDeps.push_back({ |
| .srcSubpass = 0, |
| .dstSubpass = 0, |
| .srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, |
| .dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, |
| .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, |
| .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, |
| .dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT, |
| }); |
| } |
| |
| // PLS-resolve subpass (atomic mode only). |
| if (interlockMode == gpu::InterlockMode::atomics) |
| { |
| // The resolve happens in a separate subpass. |
| assert(subpassDescs.size() == 1); |
| assert(plsResolveAttachmentRef.size() == |
| m_drawPipelineLayout->colorAttachmentCount(1)); |
| subpassDescs.push_back({ |
| .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, |
| .inputAttachmentCount = inputAttachmentRefs.size(), |
| .pInputAttachments = inputAttachmentRefs.data(), |
| .colorAttachmentCount = plsResolveAttachmentRef.size(), |
| .pColorAttachments = plsResolveAttachmentRef.data(), |
| }); |
| } |
| |
| for (uint32_t i = 1; i < subpassDescs.size(); ++i) |
| { |
| // In the case of multiple subpasses, supbass N always consumes the |
| // color outputs from supbass N-1. |
| subpassDeps.push_back({ |
| .srcSubpass = i - 1, |
| .dstSubpass = i, |
| .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, |
| .dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, |
| // TODO: Atomic mode should add SHADER_READ/SHADER_WRITE flags for |
| // the coverage buffer as well, and add |
| // VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT to the srcStageMask, but |
| // ironically, adding those causes artifacts on Qualcomm. Leave them |
| // out for now unless we find a case where we don't work without |
| // them. |
| .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, |
| .dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT, |
| .dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT, |
| }); |
| } |
| |
| VkRenderPassCreateInfo renderPassCreateInfo = { |
| .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, |
| .attachmentCount = attachments.size(), |
| .pAttachments = attachments.data(), |
| .subpassCount = subpassDescs.size(), |
| .pSubpasses = subpassDescs.data(), |
| .dependencyCount = subpassDeps.size(), |
| .pDependencies = subpassDeps.data(), |
| }; |
| |
| VK_CHECK(m_vk->CreateRenderPass(m_vk->device, |
| &renderPassCreateInfo, |
| nullptr, |
| &m_renderPass)); |
| } |
| |
| RenderPassVulkan::~RenderPassVulkan() |
| { |
| // Don't touch m_drawPipelineLayout in the destructor since destruction |
| // order of us vs. impl->m_drawPipelineLayouts is uncertain. |
| m_vk->DestroyRenderPass(m_vk->device, m_renderPass, VK_NULL_HANDLE); |
| } |
| } // namespace rive::gpu |