| /* | |
| * Copyright 2025 Rive | |
| */ | |
| #include "rive/math/bitwise.hpp" | |
| #include "rive/renderer/vulkan/render_context_vulkan_impl.hpp" | |
| #include "shaders/constants.glsl" | |
| #include "draw_pipeline_layout_vulkan.hpp" | |
| #include "pipeline_manager_vulkan.hpp" | |
| namespace rive::gpu | |
| { | |
| static VkSamplerMipmapMode vk_sampler_mipmap_mode(rive::ImageFilter option) | |
| { | |
| switch (option) | |
| { | |
| case rive::ImageFilter::nearest: | |
| case rive::ImageFilter::bilinear: | |
| return VK_SAMPLER_MIPMAP_MODE_NEAREST; | |
| } | |
| RIVE_UNREACHABLE(); | |
| } | |
| static VkSamplerAddressMode vk_sampler_address_mode(rive::ImageWrap option) | |
| { | |
| switch (option) | |
| { | |
| case rive::ImageWrap::clamp: | |
| return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; | |
| case rive::ImageWrap::repeat: | |
| return VK_SAMPLER_ADDRESS_MODE_REPEAT; | |
| case rive::ImageWrap::mirror: | |
| return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; | |
| } | |
| RIVE_UNREACHABLE(); | |
| } | |
| static VkFilter vk_filter(rive::ImageFilter option) | |
| { | |
| switch (option) | |
| { | |
| case rive::ImageFilter::bilinear: | |
| return VK_FILTER_LINEAR; | |
| case rive::ImageFilter::nearest: | |
| return VK_FILTER_NEAREST; | |
| } | |
| RIVE_UNREACHABLE(); | |
| } | |
| std::unique_ptr<PipelineManagerVulkan> PipelineManagerVulkan::make( | |
| rcp<VulkanContext> vk, | |
| ShaderCompilationMode mode, | |
| VkImageView nullTextureView) | |
| { | |
| std::unique_ptr<PipelineManagerVulkan> pipelineManager( | |
| new PipelineManagerVulkan(std::move(vk), mode)); | |
| if (!pipelineManager->init(nullTextureView)) | |
| { | |
| return nullptr; | |
| } | |
| return pipelineManager; | |
| } | |
| PipelineManagerVulkan::PipelineManagerVulkan(rcp<VulkanContext> vk, | |
| ShaderCompilationMode mode) : | |
| Super(mode), m_vk(std::move(vk)), m_featherAtlasFormat(VK_FORMAT_R16_SFLOAT) | |
| {} | |
| bool PipelineManagerVulkan::init(VkImageView nullTextureView) | |
| { | |
| // Create the immutable samplers. | |
| VkSamplerCreateInfo linearSamplerCreateInfo = { | |
| .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, | |
| .magFilter = VK_FILTER_LINEAR, | |
| .minFilter = VK_FILTER_LINEAR, | |
| .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, | |
| .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, | |
| .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, | |
| .minLod = 0, | |
| .maxLod = 0, | |
| }; | |
| m_linearSampler = | |
| VK_CREATE_HANDLE(m_vk, CreateSampler, &linearSamplerCreateInfo); | |
| if (m_linearSampler == VK_NULL_HANDLE) | |
| { | |
| return false; | |
| } | |
| for (size_t i = 0; i < ImageSampler::MAX_SAMPLER_PERMUTATIONS; ++i) | |
| { | |
| ImageWrap wrapX = ImageSampler::GetWrapXOptionFromKey(i); | |
| ImageWrap wrapY = ImageSampler::GetWrapYOptionFromKey(i); | |
| ImageFilter filter = ImageSampler::GetFilterOptionFromKey(i); | |
| VkFilter minMagFilter = vk_filter(filter); | |
| VkSamplerCreateInfo samplerCreateInfo = { | |
| .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, | |
| .magFilter = minMagFilter, | |
| .minFilter = minMagFilter, | |
| .mipmapMode = vk_sampler_mipmap_mode(filter), | |
| .addressModeU = vk_sampler_address_mode(wrapX), | |
| .addressModeV = vk_sampler_address_mode(wrapY), | |
| .minLod = 0, | |
| .maxLod = VK_LOD_CLAMP_NONE, | |
| }; | |
| m_imageSamplers[i] = | |
| VK_CREATE_HANDLE(m_vk, CreateSampler, &samplerCreateInfo); | |
| if (m_imageSamplers[i] == VK_NULL_HANDLE) | |
| { | |
| return false; | |
| } | |
| } | |
| // All pipelines share the same perFlush bindings. | |
| VkDescriptorSetLayoutBinding perFlushLayoutBindings[] = { | |
| { | |
| .binding = FLUSH_UNIFORM_BUFFER_IDX, | |
| .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, | |
| .descriptorCount = 1, | |
| .stageFlags = | |
| VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, | |
| }, | |
| { | |
| .binding = PATH_BUFFER_IDX, | |
| .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, | |
| .descriptorCount = 1, | |
| .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, | |
| }, | |
| { | |
| .binding = PAINT_BUFFER_IDX, | |
| .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, | |
| .descriptorCount = 1, | |
| .stageFlags = | |
| VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, | |
| }, | |
| { | |
| .binding = PAINT_AUX_BUFFER_IDX, | |
| .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, | |
| .descriptorCount = 1, | |
| .stageFlags = | |
| VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, | |
| }, | |
| { | |
| .binding = CONTOUR_BUFFER_IDX, | |
| .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, | |
| .descriptorCount = 1, | |
| .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, | |
| }, | |
| { | |
| .binding = COVERAGE_BUFFER_IDX, | |
| .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, | |
| .descriptorCount = 1, | |
| .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, | |
| }, | |
| { | |
| .binding = TESS_VERTEX_TEXTURE_IDX, | |
| .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, | |
| .descriptorCount = 1, | |
| .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, | |
| }, | |
| { | |
| .binding = GRAD_TEXTURE_IDX, | |
| .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, | |
| .descriptorCount = 1, | |
| .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, | |
| .pImmutableSamplers = &m_linearSampler, | |
| }, | |
| { | |
| .binding = GAUSSIAN_INTEGRAL_TEXTURE_IDX, | |
| .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, | |
| .descriptorCount = 1, | |
| .stageFlags = | |
| VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, | |
| .pImmutableSamplers = &m_linearSampler, | |
| }, | |
| { | |
| .binding = FEATHER_ATLAS_TEXTURE_IDX, | |
| .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, | |
| .descriptorCount = 1, | |
| .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, | |
| .pImmutableSamplers = &m_linearSampler, | |
| }, | |
| }; | |
| VkDescriptorSetLayoutCreateInfo perFlushLayoutInfo = { | |
| .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, | |
| .bindingCount = std::size(perFlushLayoutBindings), | |
| .pBindings = perFlushLayoutBindings, | |
| }; | |
| m_perFlushDescriptorSetLayout = | |
| VK_CREATE_HANDLE(m_vk, CreateDescriptorSetLayout, &perFlushLayoutInfo); | |
| if (m_perFlushDescriptorSetLayout == VK_NULL_HANDLE) | |
| { | |
| return false; | |
| } | |
| // The imageTexture gets updated with every draw that uses it. | |
| VkDescriptorSetLayoutBinding perDrawLayoutBindings[] = { | |
| { | |
| .binding = IMAGE_TEXTURE_IDX, | |
| .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, | |
| .descriptorCount = 1, | |
| .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, | |
| }, | |
| }; | |
| VkDescriptorSetLayoutCreateInfo perDrawLayoutInfo = { | |
| .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, | |
| .bindingCount = std::size(perDrawLayoutBindings), | |
| .pBindings = perDrawLayoutBindings, | |
| }; | |
| m_perDrawDescriptorSetLayout = | |
| VK_CREATE_HANDLE(m_vk, CreateDescriptorSetLayout, &perDrawLayoutInfo); | |
| if (m_perDrawDescriptorSetLayout == VK_NULL_HANDLE) | |
| { | |
| return false; | |
| } | |
| // For when a set isn't used at all by a shader. | |
| VkDescriptorSetLayoutCreateInfo emptyLayoutInfo = { | |
| .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, | |
| .bindingCount = 0, | |
| }; | |
| m_emptyDescriptorSetLayout = | |
| VK_CREATE_HANDLE(m_vk, CreateDescriptorSetLayout, &emptyLayoutInfo); | |
| if (m_emptyDescriptorSetLayout == VK_NULL_HANDLE) | |
| { | |
| return false; | |
| } | |
| // Create static descriptor sets. | |
| VkDescriptorPoolSize staticDescriptorPoolSizes[] = { | |
| { | |
| .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, | |
| .descriptorCount = 1, // m_nullImageTexture | |
| }, | |
| }; | |
| VkDescriptorPoolCreateInfo staticDescriptorPoolCreateInfo = { | |
| .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, | |
| .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, | |
| .maxSets = 2, | |
| .poolSizeCount = std::size(staticDescriptorPoolSizes), | |
| .pPoolSizes = staticDescriptorPoolSizes, | |
| }; | |
| m_staticDescriptorPool = VK_CREATE_HANDLE(m_vk, | |
| CreateDescriptorPool, | |
| &staticDescriptorPoolCreateInfo); | |
| if (m_staticDescriptorPool == VK_NULL_HANDLE) | |
| { | |
| return false; | |
| } | |
| // Create a descriptor set to bind m_nullImageTexture when there is no image | |
| // paint. | |
| VkDescriptorSetAllocateInfo nullImageDescriptorSetInfo = { | |
| .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, | |
| .descriptorPool = m_staticDescriptorPool, | |
| .descriptorSetCount = 1, | |
| .pSetLayouts = &m_perDrawDescriptorSetLayout, | |
| }; | |
| VK_RETURN_FALSE_ON_FAIL( | |
| m_vk->AllocateDescriptorSets(m_vk->device, | |
| &nullImageDescriptorSetInfo, | |
| &m_nullImageDescriptorSet)); | |
| m_vk->updateImageDescriptorSets( | |
| m_nullImageDescriptorSet, | |
| { | |
| .dstBinding = IMAGE_TEXTURE_IDX, | |
| .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, | |
| }, | |
| {{ | |
| .sampler = m_imageSamplers[ImageSampler::LINEAR_CLAMP_SAMPLER_KEY], | |
| .imageView = nullTextureView, | |
| .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, | |
| }}); | |
| return true; | |
| } | |
| PipelineManagerVulkan::~PipelineManagerVulkan() | |
| { | |
| shutdownBackgroundThread(); | |
| m_vk->DestroyDescriptorSetLayout(m_vk->device, | |
| m_perFlushDescriptorSetLayout, | |
| nullptr); | |
| m_vk->DestroyDescriptorSetLayout(m_vk->device, | |
| m_perDrawDescriptorSetLayout, | |
| nullptr); | |
| m_vk->DestroyDescriptorSetLayout(m_vk->device, | |
| m_emptyDescriptorSetLayout, | |
| nullptr); | |
| m_vk->DestroyDescriptorPool(m_vk->device, m_staticDescriptorPool, nullptr); | |
| for (VkSampler sampler : m_imageSamplers) | |
| { | |
| m_vk->DestroySampler(m_vk->device, sampler, nullptr); | |
| } | |
| m_vk->DestroySampler(m_vk->device, m_linearSampler, nullptr); | |
| } | |
| DrawPipelineLayoutVulkan& PipelineManagerVulkan:: | |
| getDrawPipelineLayoutSynchronous(InterlockMode interlockMode, | |
| RenderPassOptionsVulkan renderPassOptions) | |
| { | |
| // Mask off the options that don't affect layout. | |
| renderPassOptions &= RENDER_PASS_OPTIONS_LAYOUT_MASK; | |
| const uint32_t key = | |
| (static_cast<uint32_t>(interlockMode) << RENDER_PASS_OPTION_COUNT) | | |
| static_cast<uint32_t>(renderPassOptions); | |
| // Make sure our key doesn't overflow 32 bits. | |
| assert(key >> RENDER_PASS_OPTION_COUNT == | |
| static_cast<uint32_t>(interlockMode)); | |
| return getSharedObjectSynchronous(key, m_drawPipelineLayouts, [&]() { | |
| return std::make_unique<DrawPipelineLayoutVulkan>(this, | |
| interlockMode, | |
| renderPassOptions); | |
| }); | |
| } | |
| std::unique_ptr<DrawShaderVulkan> PipelineManagerVulkan::createVertexShader( | |
| DrawType drawType, | |
| ShaderFeatures shaderFeatures, | |
| InterlockMode interlockMode) | |
| { | |
| return std::make_unique<DrawShaderVulkan>(DrawShaderVulkan::Type::vertex, | |
| vulkanContext(), | |
| drawType, | |
| shaderFeatures, | |
| interlockMode, | |
| ShaderMiscFlags::none); | |
| } | |
| std::unique_ptr<DrawShaderVulkan> PipelineManagerVulkan::createFragmentShader( | |
| DrawType drawType, | |
| ShaderFeatures shaderFeatures, | |
| InterlockMode interlockMode, | |
| ShaderMiscFlags miscFlags) | |
| { | |
| return std::make_unique<DrawShaderVulkan>(DrawShaderVulkan::Type::fragment, | |
| vulkanContext(), | |
| drawType, | |
| shaderFeatures, | |
| interlockMode, | |
| miscFlags); | |
| } | |
| RenderPassVulkan& PipelineManagerVulkan::getRenderPassSynchronous( | |
| InterlockMode interlockMode, | |
| RenderPassOptionsVulkan renderPassOptions, | |
| VkFormat renderTargetFormat, | |
| LoadAction colorLoadAction) | |
| { | |
| const uint32_t key = RenderPassVulkan::Key(interlockMode, | |
| renderPassOptions, | |
| renderTargetFormat, | |
| colorLoadAction); | |
| return getSharedObjectSynchronous(key, m_renderPasses, [&]() { | |
| return std::make_unique<RenderPassVulkan>(this, | |
| interlockMode, | |
| renderPassOptions, | |
| renderTargetFormat, | |
| colorLoadAction); | |
| }); | |
| } | |
| std::unique_ptr<DrawPipelineVulkan> PipelineManagerVulkan::createPipeline( | |
| PipelineCreateType createType, | |
| uint64_t key, | |
| const PipelineProps& props, | |
| const PlatformFeatures& platformFeatures) | |
| { | |
| if (createType == PipelineCreateType::async) | |
| { | |
| this->queueBackgroundJob(key, props, platformFeatures); | |
| return nullptr; | |
| } | |
| auto& renderPass = getRenderPassSynchronous(props.interlockMode, | |
| props.renderPassOptions, | |
| props.renderTargetFormat, | |
| props.colorLoadAction); | |
| assert(createType == PipelineCreateType::sync); | |
| return std::make_unique<DrawPipelineVulkan>( | |
| this, | |
| *renderPass.drawPipelineLayout(), | |
| props, | |
| renderPass, | |
| platformFeatures | |
| #ifdef WITH_RIVE_TOOLS | |
| , | |
| props.synthesizedFailureType | |
| #endif | |
| ); | |
| } | |
| PipelineStatus PipelineManagerVulkan::getPipelineStatus( | |
| const DrawPipelineVulkan& pipeline) const | |
| { | |
| return (VkPipeline(pipeline) == VK_NULL_HANDLE) ? PipelineStatus::errored | |
| : PipelineStatus::ready; | |
| } | |
| void PipelineManagerVulkan::clearCacheInternal() | |
| { | |
| m_drawPipelineLayouts.clear(); | |
| m_renderPasses.clear(); | |
| } | |
| static Span<const BlendMode> get_relevant_blend_modes_for_pipeline_creation( | |
| InterlockMode interlockMode, | |
| DrawType drawType, | |
| ShaderMiscFlags miscFlags, | |
| DrawContents drawContents, | |
| const PlatformFeatures& platformFeatures) | |
| { | |
| constexpr BlendMode SRC_OVER_ONLY[] = {BlendMode::srcOver}; | |
| switch (interlockMode) | |
| { | |
| case InterlockMode::rasterOrdering: | |
| case InterlockMode::atomics: | |
| case InterlockMode::clockwise: | |
| case InterlockMode::clockwiseAtomic: | |
| return make_span(SRC_OVER_ONLY); | |
| case InterlockMode::depthStencil: | |
| // If this assert ever fires (i.e. if we ever support GPU fixed- | |
| // function advanced blend in Vulkan), we'll need to return a list | |
| // of all blend modes instead of just srcOver. | |
| assert( | |
| enums::is_flag_set(drawContents, DrawContents::opaquePaint) || | |
| !platformFeatures.supportsBlendAdvancedKHR); | |
| return make_span(SRC_OVER_ONLY); | |
| } | |
| RIVE_UNREACHABLE(); | |
| } | |
| void PipelineManagerVulkan::forEachUbershaderPermutation( | |
| InterlockMode interlockMode, | |
| VkFormat renderTargetFormat, | |
| VkImageUsageFlags renderTargetUsage, | |
| LoadAction colorLoadAction, | |
| const PlatformFeatures& platformFeatures, | |
| const std::function<bool(const PipelineProps&)>& func) | |
| { | |
| ForEachUbershaderPermutation( | |
| interlockMode, | |
| platformFeatures, | |
| [&](DrawType drawType, | |
| ShaderFeatures shaderFeatures, | |
| ShaderMiscFlags shaderMiscFlags) { | |
| PipelineProps props{ | |
| .drawType = drawType, | |
| .shaderFeatures = shaderFeatures, | |
| .interlockMode = interlockMode, | |
| .shaderMiscFlags = shaderMiscFlags, | |
| .drawPipelineOptions = DrawPipelineVulkan::Options::none, | |
| .renderTargetFormat = renderTargetFormat, | |
| .colorLoadAction = colorLoadAction, | |
| }; | |
| // only depthStencil has draw contents options that are relevant to | |
| // pipeline creation | |
| const auto validDrawContents = | |
| (interlockMode == InterlockMode::depthStencil) | |
| ? DrawContentsForDepthStencilPipelineState | |
| : DrawContents::none; | |
| RenderPassOptionsVulkan fixedPassOptions = | |
| RenderPassOptionsVulkan::none; | |
| if (interlockMode != InterlockMode::clockwiseAtomic && | |
| interlockMode != InterlockMode::depthStencil && | |
| enums::is_flag_set(shaderMiscFlags, | |
| ShaderMiscFlags::fixedFunctionColorOutput)) | |
| { | |
| // In non-clockwiseAtomic mode, the render pass | |
| // fixedFunctionColorOutput should match the one in | |
| // shaderMiscFlags | |
| fixedPassOptions |= | |
| RenderPassOptionsVulkan::fixedFunctionColorOutput; | |
| } | |
| RenderPassOptionsVulkan validPassOptions = | |
| RenderPassOptionsVulkan::none; | |
| switch (interlockMode) | |
| { | |
| case InterlockMode::rasterOrdering: | |
| validPassOptions |= | |
| RenderPassOptionsVulkan::manuallyResolved | | |
| RenderPassOptionsVulkan::rasterOrderingInterruptible | | |
| RenderPassOptionsVulkan::rasterOrderingResume; | |
| break; | |
| case InterlockMode::atomics: | |
| if (!(renderTargetUsage & | |
| VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT) && | |
| !enums::is_flag_set( | |
| shaderMiscFlags, | |
| ShaderMiscFlags::fixedFunctionColorOutput)) | |
| { | |
| fixedPassOptions |= RenderPassOptionsVulkan:: | |
| atomicCoalescedResolveAndTransfer; | |
| } | |
| break; | |
| case InterlockMode::clockwise: | |
| // no additional options | |
| break; | |
| case InterlockMode::clockwiseAtomic: | |
| // Clockwise atomic render passes are allowed to (not) have | |
| // this flag even if the shader has it specified (a shader | |
| // is allowed to say "I don't read from the framebuffer" | |
| // even if something else in the pipleine does) | |
| if (enums::is_flag_set( | |
| shaderMiscFlags, | |
| ShaderMiscFlags::fixedFunctionColorOutput)) | |
| { | |
| validPassOptions |= | |
| RenderPassOptionsVulkan::fixedFunctionColorOutput; | |
| } | |
| break; | |
| case InterlockMode::depthStencil: | |
| validPassOptions |= | |
| RenderPassOptionsVulkan::manuallyResolved | | |
| RenderPassOptionsVulkan::msaaSeedFromOffscreenTexture; | |
| if (enums::is_flag_set( | |
| shaderMiscFlags, | |
| ShaderMiscFlags::fixedFunctionColorOutput)) | |
| { | |
| // Like clockwiseAtomic, depthStencil render passes are | |
| // allowed to not have this flag even if a specific | |
| // shader specifies it. | |
| validPassOptions |= | |
| RenderPassOptionsVulkan::fixedFunctionColorOutput; | |
| } | |
| break; | |
| } | |
| for (auto drawContents : | |
| math::iterate_bit_combinations_in_mask(validDrawContents)) | |
| { | |
| const auto stencilInfo = | |
| get_stencil_info(interlockMode, drawType, drawContents); | |
| if (!stencilInfo.areDrawContentsValid) | |
| { | |
| continue; | |
| } | |
| props.drawContents = drawContents; | |
| for (auto variableRenderPassOptions : | |
| math::iterate_bit_combinations_in_mask(validPassOptions)) | |
| { | |
| props.renderPassOptions = | |
| variableRenderPassOptions | fixedPassOptions; | |
| if (enums::is_flag_set( | |
| props.renderPassOptions, | |
| RenderPassOptionsVulkan::manuallyResolved) && | |
| enums::any_flag_set( | |
| props.renderPassOptions, | |
| RenderPassOptionsVulkan::fixedFunctionColorOutput | | |
| RenderPassOptionsVulkan:: | |
| rasterOrderingInterruptible)) | |
| { | |
| // manuallyResolved and these other flags are mutually | |
| // exclusive | |
| continue; | |
| } | |
| if (enums::is_flag_set( | |
| props.renderPassOptions, | |
| RenderPassOptionsVulkan:: | |
| atomicCoalescedResolveAndTransfer)) | |
| { | |
| if (enums::is_flag_set(props.renderPassOptions, | |
| RenderPassOptionsVulkan:: | |
| fixedFunctionColorOutput)) | |
| { | |
| // atomicCoalescedResolveAndTransfer should never be | |
| // set when fixedFunctionColorOutput is set. | |
| continue; | |
| } | |
| if (drawType == DrawType::renderPassResolve && | |
| !enums::is_flag_set( | |
| props.shaderMiscFlags, | |
| ShaderMiscFlags::coalescedResolveAndTransfer)) | |
| { | |
| // ShaderMiscFlags::coalescedResolveAndTransfer will | |
| // never be set if atomicCoalescedResolveAndTransfer | |
| // is not set on the render pass | |
| continue; | |
| } | |
| } | |
| auto blendModes = | |
| get_relevant_blend_modes_for_pipeline_creation( | |
| interlockMode, | |
| drawType, | |
| shaderMiscFlags, | |
| props.drawContents, | |
| platformFeatures); | |
| for (auto blendMode : blendModes) | |
| { | |
| props.blendMode = blendMode; | |
| if (!func(props)) | |
| { | |
| return false; | |
| } | |
| } | |
| } | |
| } | |
| return true; | |
| }); | |
| } | |
| #if !defined(NDEBUG) | |
| bool PipelineManagerVulkan::isValidUbershaderPipelineProps( | |
| const PipelineProps& props, | |
| const PlatformFeatures& platformFeatures) | |
| { | |
| bool found = false; | |
| auto curKey = props.createKey(platformFeatures); | |
| forEachUbershaderPermutation( | |
| props.interlockMode, | |
| props.renderTargetFormat, | |
| enums::is_flag_set( | |
| props.renderPassOptions, | |
| RenderPassOptionsVulkan::atomicCoalescedResolveAndTransfer) | |
| ? VkImageUsageFlagBits(0) | |
| : VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT, | |
| props.colorLoadAction, | |
| platformFeatures, | |
| [&found, curKey, &platformFeatures](const PipelineProps& validProps) { | |
| auto testKey = validProps.createKey(platformFeatures); | |
| if (testKey == curKey) | |
| { | |
| found = true; | |
| } | |
| return !found; // Keep iterating if not found. | |
| }); | |
| return found; | |
| } | |
| #endif | |
| void PipelineManagerVulkan::queueUbershaderPipelineCreation( | |
| InterlockMode interlockMode, | |
| VkFormat renderTargetFormat, | |
| VkImageUsageFlags renderTargetUsage, | |
| LoadAction colorLoadAction, | |
| const PlatformFeatures& platformFeatures) | |
| { | |
| forEachUbershaderPermutation( | |
| interlockMode, | |
| renderTargetFormat, | |
| renderTargetUsage, | |
| colorLoadAction, | |
| platformFeatures, | |
| [this, &platformFeatures](const PipelineProps& props) { | |
| queuePipelineIfNotFound(props, platformFeatures); | |
| return true; | |
| }); | |
| } | |
| } // namespace rive::gpu |