| /* | |
| * Copyright 2025 Rive | |
| */ | |
| #include "rive/math/bitwise.hpp" | |
| #include "rive/renderer/vulkan/render_context_vulkan_impl.hpp" | |
| #include "rive/renderer/vulkan/vulkan_context.hpp" | |
| #include "rive/renderer/stack_vector.hpp" | |
| #include "shaders/constants.glsl" | |
| #include "common_layouts.hpp" | |
| #include "draw_pipeline_layout_vulkan.hpp" | |
| #include "pipeline_manager_vulkan.hpp" | |
| #include "render_pass_vulkan.hpp" | |
| namespace rive::gpu | |
| { | |
| constexpr static VkBlendOp vk_blend_op(gpu::BlendEquation equation) | |
| { | |
| switch (equation) | |
| { | |
| case gpu::BlendEquation::none: | |
| case gpu::BlendEquation::srcOver: | |
| case gpu::BlendEquation::plus: | |
| return VK_BLEND_OP_ADD; | |
| case gpu::BlendEquation::min: | |
| return VK_BLEND_OP_MIN; | |
| case gpu::BlendEquation::max: | |
| return VK_BLEND_OP_MAX; | |
| case gpu::BlendEquation::screen: | |
| case gpu::BlendEquation::overlay: | |
| case gpu::BlendEquation::darken: | |
| case gpu::BlendEquation::lighten: | |
| case gpu::BlendEquation::colorDodge: | |
| case gpu::BlendEquation::colorBurn: | |
| case gpu::BlendEquation::hardLight: | |
| case gpu::BlendEquation::softLight: | |
| case gpu::BlendEquation::difference: | |
| case gpu::BlendEquation::exclusion: | |
| case gpu::BlendEquation::multiply: | |
| case gpu::BlendEquation::hue: | |
| case gpu::BlendEquation::saturation: | |
| case gpu::BlendEquation::color: | |
| case gpu::BlendEquation::luminosity: | |
| break; | |
| } | |
| RIVE_UNREACHABLE(); | |
| } | |
| constexpr static VkBlendFactor vk_dst_blend_factor(gpu::BlendEquation equation) | |
| { | |
| switch (equation) | |
| { | |
| case gpu::BlendEquation::none: | |
| return VK_BLEND_FACTOR_ZERO; | |
| case gpu::BlendEquation::srcOver: | |
| return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; | |
| case gpu::BlendEquation::plus: | |
| case gpu::BlendEquation::min: | |
| case gpu::BlendEquation::max: | |
| return VK_BLEND_FACTOR_ONE; | |
| case gpu::BlendEquation::screen: | |
| case gpu::BlendEquation::overlay: | |
| case gpu::BlendEquation::darken: | |
| case gpu::BlendEquation::lighten: | |
| case gpu::BlendEquation::colorDodge: | |
| case gpu::BlendEquation::colorBurn: | |
| case gpu::BlendEquation::hardLight: | |
| case gpu::BlendEquation::softLight: | |
| case gpu::BlendEquation::difference: | |
| case gpu::BlendEquation::exclusion: | |
| case gpu::BlendEquation::multiply: | |
| case gpu::BlendEquation::hue: | |
| case gpu::BlendEquation::saturation: | |
| case gpu::BlendEquation::color: | |
| case gpu::BlendEquation::luminosity: | |
| break; | |
| } | |
| RIVE_UNREACHABLE(); | |
| } | |
| uint64_t DrawPipelineVulkan::PipelineProps::createKey( | |
| const PlatformFeatures& platformFeatures) const | |
| { | |
| uint64_t key = gpu::pipeline_unique_key( | |
| drawType, | |
| shaderFeatures, | |
| interlockMode, | |
| shaderMiscFlags, | |
| drawContents, | |
| enums::is_flag_set(renderPassOptions, | |
| RenderPassOptionsVulkan::fixedFunctionColorOutput), | |
| blendMode, | |
| platformFeatures); | |
| const uint64_t renderPassKeyNoInterlockMode = | |
| RenderPassVulkan::KeyNoInterlockMode(renderPassOptions, | |
| renderTargetFormat, | |
| colorLoadAction); | |
| key = math::add_bits_to_key( | |
| key, | |
| renderPassKeyNoInterlockMode, | |
| RenderPassVulkan::KEY_NO_INTERLOCK_MODE_BIT_COUNT); | |
| key = | |
| math::add_bits_to_key(key, uint64_t(drawPipelineOptions), OPTION_COUNT); | |
| // pipeline_unique_key() keys on baked depth/stencil/cull/color state, so a | |
| // dynamic-state pipeline collides with the static pipelines that share its | |
| // state (e.g. stencilDynamicMidpointFans vs. stencilMidpointFans). They | |
| // need distinct pipelines -- one uses the dynamic-state layout, the | |
| // other bakes -- so fold the layout choice into the key. | |
| key = math::add_bits_to_key( | |
| key, | |
| uint64_t(gpu::drawTypeHasPipelineDynamicState(drawType)), | |
| 1); | |
| return key; | |
| } | |
| uint32_t subpass_index(gpu::DrawType drawType, | |
| gpu::LoadAction colorLoadAction, | |
| gpu::InterlockMode interlockMode, | |
| gpu::ShaderMiscFlags shaderMiscFlags) | |
| { | |
| if (interlockMode == gpu::InterlockMode::clockwiseAtomic) | |
| { | |
| // In clockwiseAtomic mode, borrowed coverage is rendered in a separate | |
| // subpass prior to the main one. | |
| return enums::is_flag_set(shaderMiscFlags, | |
| gpu::ShaderMiscFlags::borrowedCoveragePass) | |
| ? 0 | |
| : 1; | |
| } | |
| const uint32_t mainSubpassIdx = | |
| (interlockMode == gpu::InterlockMode::depthStencil && | |
| colorLoadAction == gpu::LoadAction::preserveRenderTarget) | |
| ? 1 | |
| : 0; | |
| switch (drawType) | |
| { | |
| case gpu::DrawType::renderPassInitialize: | |
| assert(mainSubpassIdx == 1); | |
| return 0; | |
| case gpu::DrawType::midpointFanPatches: | |
| case gpu::DrawType::midpointFanCenterAAPatches: | |
| case gpu::DrawType::outerCurvePatches: | |
| case gpu::DrawType::interiorTriangulation: | |
| case gpu::DrawType::featherAtlasBlit: | |
| case gpu::DrawType::imageRect: | |
| case gpu::DrawType::imageMesh: | |
| case gpu::DrawType::depthStrokes: | |
| case gpu::DrawType::stencilMidpointFanBorrowedCoverage: | |
| case gpu::DrawType::stencilDynamicMidpointFans: | |
| case gpu::DrawType::stencilDynamicOuterCubics: | |
| case gpu::DrawType::stencilMidpointFans: | |
| case gpu::DrawType::stencilMidpointFanReset: | |
| case gpu::DrawType::stencilMidpointFanWinding: | |
| case gpu::DrawType::stencilMidpointFanCover: | |
| case gpu::DrawType::stencilOuterCubicBorrowedCoverage: | |
| case gpu::DrawType::stencilOuterCubicReset: | |
| case gpu::DrawType::stencilOuterCubicWinding: | |
| case gpu::DrawType::stencilOuterCubicCover: | |
| case gpu::DrawType::stencilOuterCubics: | |
| case gpu::DrawType::clipReset: | |
| return mainSubpassIdx; | |
| case gpu::DrawType::renderPassResolve: | |
| return mainSubpassIdx + 1; | |
| } | |
| RIVE_UNREACHABLE(); | |
| } | |
| DrawPipelineVulkan::DrawPipelineVulkan( | |
| PipelineManagerVulkan* pipelineManager, | |
| const DrawPipelineLayoutVulkan& pipelineLayout, | |
| const PipelineProps& props, | |
| VkRenderPass vkRenderPass, | |
| const PlatformFeatures& platformFeatures | |
| #ifdef WITH_RIVE_TOOLS | |
| , | |
| SynthesizedFailureType synthesizedFailureType | |
| #endif | |
| ) : | |
| m_vk(ref_rcp(pipelineManager->vulkanContext())) | |
| { | |
| #ifdef WITH_RIVE_TOOLS | |
| if (synthesizedFailureType == SynthesizedFailureType::pipelineCreation) | |
| { | |
| return; | |
| } | |
| #endif | |
| const bool pipelineWriteOnlyRenderTarget = | |
| enums::is_flag_set(props.renderPassOptions, | |
| RenderPassOptionsVulkan::fixedFunctionColorOutput); | |
| const gpu::PipelineState pipelineState = | |
| get_pipeline_state(props.drawType, | |
| props.interlockMode, | |
| props.shaderMiscFlags, | |
| props.drawContents, | |
| pipelineWriteOnlyRenderTarget, | |
| props.blendMode, | |
| platformFeatures); | |
| const gpu::InterlockMode interlockMode = pipelineLayout.interlockMode(); | |
| uint32_t subpassIndex = subpass_index(props.drawType, | |
| props.colorLoadAction, | |
| interlockMode, | |
| props.shaderMiscFlags); | |
| auto& vertShader = | |
| pipelineManager->getVertexShaderSynchronous(props.drawType, | |
| props.shaderFeatures, | |
| interlockMode); | |
| if (vertShader.module() == VK_NULL_HANDLE) | |
| { | |
| return; | |
| } | |
| auto& fragShader = | |
| pipelineManager->getFragmentShaderSynchronous(props.drawType, | |
| props.shaderFeatures, | |
| interlockMode, | |
| props.shaderMiscFlags); | |
| if (fragShader.module() == VK_NULL_HANDLE) | |
| { | |
| return; | |
| } | |
| uint32_t shaderPermutationFlags[] = { | |
| enums::is_flag_set(props.shaderFeatures, | |
| gpu::ShaderFeatures::ENABLE_CLIPPING), | |
| enums::is_flag_set(props.shaderFeatures, | |
| gpu::ShaderFeatures::ENABLE_CLIP_RECT), | |
| enums::is_flag_set(props.shaderFeatures, | |
| gpu::ShaderFeatures::ENABLE_ADVANCED_BLEND), | |
| enums::is_flag_set(props.shaderFeatures, | |
| gpu::ShaderFeatures::ENABLE_FEATHER), | |
| enums::is_flag_set(props.shaderFeatures, | |
| gpu::ShaderFeatures::ENABLE_EVEN_ODD), | |
| enums::is_flag_set(props.shaderFeatures, | |
| gpu::ShaderFeatures::ENABLE_NESTED_CLIPPING), | |
| enums::is_flag_set(props.shaderFeatures, | |
| gpu::ShaderFeatures::ENABLE_HSL_BLEND_MODES), | |
| enums::is_flag_set(props.shaderFeatures, | |
| gpu::ShaderFeatures::ENABLE_DITHER), | |
| enums::is_flag_set(props.shaderFeatures, | |
| gpu::ShaderFeatures::ENABLE_MODULATED_IMAGE), | |
| enums::is_flag_set(props.shaderMiscFlags, | |
| gpu::ShaderMiscFlags::clockwiseFill), | |
| enums::is_flag_set(props.shaderMiscFlags, | |
| gpu::ShaderMiscFlags::nestedClipUpdateOnly), | |
| enums::is_flag_set(props.shaderMiscFlags, | |
| gpu::ShaderMiscFlags::borrowedCoveragePass), | |
| enums::is_flag_set( | |
| props.shaderMiscFlags, | |
| gpu::ShaderMiscFlags::emulateDynamicColorWriteDisable), | |
| enums::is_flag_set(props.shaderMiscFlags, | |
| gpu::ShaderMiscFlags::storeColorClear), | |
| enums::is_flag_set(props.shaderMiscFlags, | |
| gpu::ShaderMiscFlags::loadColorFromDstTexture), | |
| pipelineManager->vendorID() == vkutil::vendors::ARM, | |
| }; | |
| static_assert(std::size(shaderPermutationFlags) == SPECIALIZATION_COUNT); | |
| static_assert(CLIPPING_SPECIALIZATION_IDX == 0); | |
| static_assert(CLIP_RECT_SPECIALIZATION_IDX == 1); | |
| static_assert(ADVANCED_BLEND_SPECIALIZATION_IDX == 2); | |
| static_assert(FEATHER_SPECIALIZATION_IDX == 3); | |
| static_assert(EVEN_ODD_SPECIALIZATION_IDX == 4); | |
| static_assert(NESTED_CLIPPING_SPECIALIZATION_IDX == 5); | |
| static_assert(HSL_BLEND_MODES_SPECIALIZATION_IDX == 6); | |
| static_assert(DITHER_SPECIALIZATION_IDX == 7); | |
| static_assert(MODULATED_IMAGE_SPECIALIZATION_IDX == 8); | |
| static_assert(CLOCKWISE_FILL_SPECIALIZATION_IDX == 9); | |
| static_assert(NESTED_CLIP_UPDATE_ONLY_SPECIALIZATION_IDX == 10); | |
| static_assert(BORROWED_COVERAGE_PASS_SPECIALIZATION_IDX == 11); | |
| static_assert(EMULATE_DYNAMIC_COLOR_WRITE_DISABLE_SPECIALIZATION_IDX == 12); | |
| static_assert(STORE_COLOR_CLEAR_SPECIALIZATION_IDX == 13); | |
| static_assert(LOAD_COLOR_FROM_DST_TEXTURE_SPECIALIZATION_IDX == 14); | |
| static_assert(VULKAN_VENDOR_ARM_SPECIALIZATION_IDX == 15); | |
| static_assert(SPECIALIZATION_COUNT == 16); | |
| VkSpecializationMapEntry permutationMapEntries[SPECIALIZATION_COUNT]; | |
| for (uint32_t i = 0; i < SPECIALIZATION_COUNT; ++i) | |
| { | |
| permutationMapEntries[i] = { | |
| .constantID = i, | |
| .offset = i * static_cast<uint32_t>(sizeof(uint32_t)), | |
| .size = sizeof(uint32_t), | |
| }; | |
| } | |
| VkSpecializationInfo specializationInfo = { | |
| .mapEntryCount = SPECIALIZATION_COUNT, | |
| .pMapEntries = permutationMapEntries, | |
| .dataSize = sizeof(shaderPermutationFlags), | |
| .pData = &shaderPermutationFlags, | |
| }; | |
| VkPipelineShaderStageCreateInfo stages[] = { | |
| { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, | |
| .stage = VK_SHADER_STAGE_VERTEX_BIT, | |
| .module = vertShader.module(), | |
| .pName = "main", | |
| .pSpecializationInfo = &specializationInfo, | |
| }, | |
| { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, | |
| .stage = VK_SHADER_STAGE_FRAGMENT_BIT, | |
| .module = fragShader.module(), | |
| .pName = "main", | |
| .pSpecializationInfo = &specializationInfo, | |
| }, | |
| }; | |
| VkPipelineRasterizationStateCreateInfo | |
| pipelineRasterizationStateCreateInfo = { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, | |
| .polygonMode = | |
| enums::is_flag_set(props.drawPipelineOptions, | |
| Options::wireframe) && | |
| // Wireframe is a debugging aid. The initialize/resolve | |
| // are fullscreen operations, so leave them solid even | |
| // in wireframe mode. | |
| props.drawType != gpu::DrawType::renderPassInitialize && | |
| props.drawType != gpu::DrawType::renderPassResolve | |
| ? VK_POLYGON_MODE_LINE | |
| : VK_POLYGON_MODE_FILL, | |
| .cullMode = vkutil::vkCullMode(pipelineState.cullFace), | |
| .frontFace = VK_FRONT_FACE_CLOCKWISE, | |
| .lineWidth = 1.0, | |
| }; | |
| gpu::BlendEquation blendEquation = pipelineState.blendEquation; | |
| bool colorWriteEnabled = pipelineState.colorWriteEnabled; | |
| if (interlockMode == gpu::InterlockMode::rasterOrdering || | |
| interlockMode == gpu::InterlockMode::atomics || | |
| (interlockMode == gpu::InterlockMode::clockwiseAtomic && | |
| !enums::is_flag_set(props.shaderMiscFlags, | |
| gpu::ShaderMiscFlags::borrowedCoveragePass))) | |
| { | |
| // The pipeline state gets generated under the assumption that pixel | |
| // local storage can still be written when colorWriteEnabled is false. | |
| // So when Vulkan implements PLS via color attachments, we need to | |
| // override the colorWriteEnabled state. | |
| colorWriteEnabled = true; | |
| } | |
| if (interlockMode == gpu::InterlockMode::atomics && | |
| !enums::is_flag_set(props.shaderMiscFlags, | |
| gpu::ShaderMiscFlags::coalescedResolveAndTransfer)) | |
| { | |
| // Vulkan deviates from the other renderers by enabling src-over | |
| // blending for PLS planes in atomic mode. | |
| // | |
| // Advanced blend modes are handled by rearranging the math such that | |
| // the correct color isn't reached until *AFTER* this blend state is | |
| // applied. This primarily benefits us by hinting to the hardware that | |
| // it doesn't need to read or write anything when a == 0, but it also | |
| // saves flops by offloading the blend work onto the ROP blending unit. | |
| // | |
| // Clip also has blend enabled, which allows us to preserve both clip | |
| // and color contents by just emitting a=0 (instead of loading and | |
| // re-emitting the current value) when a PLS plane needs to remain | |
| // unchanged during a fragment invocation. | |
| blendEquation = gpu::BlendEquation::srcOver; | |
| } | |
| #ifndef NDEBUG | |
| else if (enums::is_flag_set( | |
| props.shaderMiscFlags, | |
| gpu::ShaderMiscFlags::coalescedResolveAndTransfer)) | |
| { | |
| assert(interlockMode == gpu::InterlockMode::atomics); | |
| assert(blendEquation == gpu::BlendEquation::none); | |
| } | |
| #endif | |
| StackVector<VkPipelineColorBlendAttachmentState, PLS_PLANE_COUNT> | |
| blendStates; | |
| blendStates.push_back_n( | |
| pipelineLayout.colorAttachmentCount(subpassIndex, | |
| pipelineLayout.renderPassOptions()), | |
| { | |
| .blendEnable = blendEquation != gpu::BlendEquation::none, | |
| .srcColorBlendFactor = VK_BLEND_FACTOR_ONE, | |
| .dstColorBlendFactor = vk_dst_blend_factor(blendEquation), | |
| .colorBlendOp = vk_blend_op(blendEquation), | |
| .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, | |
| .dstAlphaBlendFactor = vk_dst_blend_factor(blendEquation), | |
| .alphaBlendOp = vk_blend_op(blendEquation), | |
| .colorWriteMask = colorWriteEnabled ? vkutil::kColorWriteMaskRGBA | |
| : vkutil::kColorWriteMaskNone, | |
| }); | |
| if (m_vk->features.independentBlend && | |
| interlockMode == gpu::InterlockMode::clockwiseAtomic) | |
| { | |
| // Since we support independentBlend, it will hopefully give us a perf | |
| // boost to disable color writes to clip/color when not being updated. | |
| // Otherwise, we will rely on the shader emitting no-op values for the | |
| // non-updated planes. | |
| if (enums::is_flag_set(props.shaderMiscFlags, | |
| gpu::ShaderMiscFlags::clipUpdateOnly)) | |
| { | |
| blendStates[COLOR_PLANE_IDX].colorWriteMask = | |
| vkutil::kColorWriteMaskNone; | |
| } | |
| else if (props.drawType != gpu::DrawType::renderPassInitialize) | |
| { | |
| assert(props.drawType != gpu::DrawType::clipReset); | |
| assert(!enums::is_flag_set( | |
| props.shaderMiscFlags, | |
| gpu::ShaderMiscFlags::nestedClipUpdateOnly)); | |
| blendStates[CLIP_PLANE_IDX].colorWriteMask = | |
| vkutil::kColorWriteMaskNone; | |
| } | |
| } | |
| VkPipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo = { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, | |
| .attachmentCount = blendStates.size(), | |
| .pAttachments = blendStates.data(), | |
| }; | |
| if (interlockMode == gpu::InterlockMode::rasterOrdering && | |
| m_vk->features.rasterizationOrderColorAttachmentAccess) | |
| { | |
| pipelineColorBlendStateCreateInfo.flags |= | |
| VK_PIPELINE_COLOR_BLEND_STATE_CREATE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_BIT_EXT; | |
| } | |
| VkPipelineDepthStencilStateCreateInfo depthStencilState = { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, | |
| .depthTestEnable = pipelineState.depthTestEnabled, | |
| .depthWriteEnable = pipelineState.depthWriteEnabled, | |
| .depthCompareOp = VK_COMPARE_OP_LESS, | |
| .depthBoundsTestEnable = VK_FALSE, | |
| .stencilTestEnable = pipelineState.stencilTestEnabled, | |
| .minDepthBounds = gpu::DEPTH_MIN, | |
| .maxDepthBounds = gpu::DEPTH_MAX, | |
| }; | |
| if (pipelineState.stencilTestEnabled) | |
| { | |
| depthStencilState.front = { | |
| .failOp = vkutil::vkStencilOp( | |
| pipelineState.stencilFrontOps.stencilFailOp), | |
| .passOp = vkutil::vkStencilOp( | |
| pipelineState.stencilFrontOps.depthStencilPassOp), | |
| .depthFailOp = | |
| vkutil::vkStencilOp(pipelineState.stencilFrontOps.depthFailOp), | |
| .compareOp = | |
| vkutil::vkCompareOp(pipelineState.stencilFrontOps.compareOp), | |
| .compareMask = pipelineState.stencilCompareMask, | |
| .writeMask = pipelineState.stencilWriteMask, | |
| .reference = pipelineState.stencilReference, | |
| }; | |
| depthStencilState.back = | |
| !pipelineState.stencilDoubleSided | |
| ? depthStencilState.front | |
| : VkStencilOpState{ | |
| .failOp = vkutil::vkStencilOp( | |
| pipelineState.stencilBackOps.stencilFailOp), | |
| .passOp = vkutil::vkStencilOp( | |
| pipelineState.stencilBackOps.depthStencilPassOp), | |
| .depthFailOp = vkutil::vkStencilOp( | |
| pipelineState.stencilBackOps.depthFailOp), | |
| .compareOp = vkutil::vkCompareOp( | |
| pipelineState.stencilBackOps.compareOp), | |
| .compareMask = pipelineState.stencilCompareMask, | |
| .writeMask = pipelineState.stencilWriteMask, | |
| .reference = pipelineState.stencilReference, | |
| }; | |
| } | |
| VkPipelineMultisampleStateCreateInfo msaaState = { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, | |
| .rasterizationSamples = | |
| (interlockMode == gpu::InterlockMode::depthStencil && | |
| props.drawType != gpu::DrawType::renderPassResolve) | |
| ? VK_SAMPLE_COUNT_4_BIT | |
| : VK_SAMPLE_COUNT_1_BIT, | |
| }; | |
| // Everything bakes except viewport and scissor, unless this pipeline | |
| // switches its constituent passes with dynamic state. | |
| StackVector<VkDynamicState, 8> dynamicStates; | |
| dynamicStates.push_back(VK_DYNAMIC_STATE_VIEWPORT); | |
| dynamicStates.push_back(VK_DYNAMIC_STATE_SCISSOR); | |
| if (gpu::drawTypeHasPipelineDynamicState(props.drawType)) | |
| { | |
| // Dynamic state is currently only used for multi-pass path draws, which | |
| // toggle depth-write, stencil, cull, and color-write per pass. | |
| // NOTE: depthCompareOp stays baked at LESS and stencilReference is a | |
| // constant 0x80, so neither is listed here. | |
| dynamicStates.push_back(VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE); | |
| dynamicStates.push_back(VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK); | |
| dynamicStates.push_back(VK_DYNAMIC_STATE_STENCIL_WRITE_MASK); | |
| dynamicStates.push_back(VK_DYNAMIC_STATE_STENCIL_OP); | |
| dynamicStates.push_back(VK_DYNAMIC_STATE_CULL_MODE); | |
| // NOTE: if VK_EXT_color_write_enable is NOT supported, the shader will | |
| // emulate it. See ShaderMiscFlags::emulateDynamicColorWriteDisable. | |
| if (m_vk->features.colorWriteEnable) | |
| { | |
| dynamicStates.push_back(VK_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT); | |
| } | |
| } | |
| const VkPipelineDynamicStateCreateInfo dynamicState = { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, | |
| .dynamicStateCount = dynamicStates.size(), | |
| .pDynamicStates = dynamicStates.data(), | |
| }; | |
| VkGraphicsPipelineCreateInfo pipelineCreateInfo = { | |
| .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, | |
| .stageCount = 2, | |
| .pStages = stages, | |
| .pViewportState = &layout::SINGLE_VIEWPORT, | |
| .pRasterizationState = &pipelineRasterizationStateCreateInfo, | |
| .pMultisampleState = &msaaState, | |
| .pDepthStencilState = interlockMode == gpu::InterlockMode::depthStencil | |
| ? &depthStencilState | |
| : nullptr, | |
| .pColorBlendState = &pipelineColorBlendStateCreateInfo, | |
| .pDynamicState = &dynamicState, | |
| .layout = *pipelineLayout, | |
| .renderPass = vkRenderPass, | |
| .subpass = subpassIndex, | |
| }; | |
| switch (props.drawType) | |
| { | |
| case DrawType::midpointFanPatches: | |
| case DrawType::midpointFanCenterAAPatches: | |
| case DrawType::outerCurvePatches: | |
| case DrawType::stencilOuterCubicBorrowedCoverage: | |
| case DrawType::stencilOuterCubicReset: | |
| case DrawType::stencilOuterCubicWinding: | |
| case DrawType::stencilOuterCubicCover: | |
| case DrawType::stencilOuterCubics: | |
| case DrawType::depthStrokes: | |
| case DrawType::stencilMidpointFanBorrowedCoverage: | |
| case DrawType::stencilDynamicMidpointFans: | |
| case DrawType::stencilDynamicOuterCubics: | |
| case DrawType::stencilMidpointFans: | |
| case DrawType::stencilMidpointFanReset: | |
| case DrawType::stencilMidpointFanWinding: | |
| case DrawType::stencilMidpointFanCover: | |
| pipelineCreateInfo.pVertexInputState = | |
| &layout::PATH_VERTEX_INPUT_STATE; | |
| pipelineCreateInfo.pInputAssemblyState = | |
| &layout::INPUT_ASSEMBLY_TRIANGLE_LIST; | |
| break; | |
| case DrawType::clipReset: | |
| case DrawType::interiorTriangulation: | |
| case DrawType::featherAtlasBlit: | |
| pipelineCreateInfo.pVertexInputState = | |
| &layout::INTERIOR_TRI_VERTEX_INPUT_STATE; | |
| pipelineCreateInfo.pInputAssemblyState = | |
| &layout::INPUT_ASSEMBLY_TRIANGLE_LIST; | |
| break; | |
| case DrawType::imageRect: | |
| pipelineCreateInfo.pVertexInputState = | |
| &layout::IMAGE_RECT_VERTEX_INPUT_STATE; | |
| pipelineCreateInfo.pInputAssemblyState = | |
| &layout::INPUT_ASSEMBLY_TRIANGLE_LIST; | |
| break; | |
| case DrawType::imageMesh: | |
| pipelineCreateInfo.pVertexInputState = | |
| &layout::IMAGE_MESH_VERTEX_INPUT_STATE; | |
| pipelineCreateInfo.pInputAssemblyState = | |
| &layout::INPUT_ASSEMBLY_TRIANGLE_LIST; | |
| break; | |
| case DrawType::renderPassResolve: | |
| case DrawType::renderPassInitialize: | |
| pipelineCreateInfo.pVertexInputState = | |
| &layout::EMPTY_VERTEX_INPUT_STATE; | |
| pipelineCreateInfo.pInputAssemblyState = | |
| &layout::INPUT_ASSEMBLY_TRIANGLE_STRIP; | |
| break; | |
| } | |
| if (m_vk->CreateGraphicsPipelines(m_vk->device, | |
| VK_NULL_HANDLE, | |
| 1, | |
| &pipelineCreateInfo, | |
| nullptr, | |
| &m_vkPipeline) != VK_SUCCESS) | |
| { | |
| m_vkPipeline = VK_NULL_HANDLE; | |
| } | |
| } | |
| DrawPipelineVulkan::~DrawPipelineVulkan() | |
| { | |
| m_vk->DestroyPipeline(m_vk->device, m_vkPipeline, nullptr); | |
| } | |
| } // namespace rive::gpu |