// Copyright 2022 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include "common/vector_math.h" #include "video_core/renderer_vulkan/vk_blit_helper.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_renderpass_cache.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_shader_util.h" #include "video_core/renderer_vulkan/vk_texture_runtime.h" #include "video_core/host_shaders/format_reinterpreter/vulkan_d24s8_to_rgba8_comp_spv.h" #include "video_core/host_shaders/full_screen_triangle_vert_spv.h" #include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h" #include "video_core/host_shaders/vulkan_depth_to_buffer_comp_spv.h" namespace Vulkan { using VideoCore::PixelFormat; namespace { struct PushConstants { std::array tex_scale; std::array tex_offset; }; struct ComputeInfo { Common::Vec2i src_offset; Common::Vec2i dst_offset; Common::Vec2i src_extent; }; inline constexpr vk::PushConstantRange COMPUTE_PUSH_CONSTANT_RANGE{ .stageFlags = vk::ShaderStageFlagBits::eCompute, .offset = 0, .size = sizeof(ComputeInfo), }; constexpr std::array COMPUTE_BINDINGS = {{ {0, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eCompute}, {1, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eCompute}, {2, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eCompute}, }}; constexpr std::array COMPUTE_BUFFER_BINDINGS = {{ {0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eCompute}, {1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eCompute}, {2, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute}, }}; constexpr std::array TWO_TEXTURES_BINDINGS = {{ {0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, {1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, }}; inline constexpr vk::PushConstantRange PUSH_CONSTANT_RANGE{ .stageFlags = vk::ShaderStageFlagBits::eVertex, .offset = 0, .size = sizeof(PushConstants), }; constexpr vk::PipelineVertexInputStateCreateInfo PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO{ .vertexBindingDescriptionCount = 0, .pVertexBindingDescriptions = nullptr, .vertexAttributeDescriptionCount = 0, .pVertexAttributeDescriptions = nullptr, }; constexpr vk::PipelineInputAssemblyStateCreateInfo PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO{ .topology = vk::PrimitiveTopology::eTriangleList, .primitiveRestartEnable = VK_FALSE, }; constexpr vk::PipelineViewportStateCreateInfo PIPELINE_VIEWPORT_STATE_CREATE_INFO{ .viewportCount = 1, .pViewports = nullptr, .scissorCount = 1, .pScissors = nullptr, }; constexpr vk::PipelineRasterizationStateCreateInfo PIPELINE_RASTERIZATION_STATE_CREATE_INFO{ .depthClampEnable = VK_FALSE, .rasterizerDiscardEnable = VK_FALSE, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = VK_FALSE, .depthBiasConstantFactor = 0.0f, .depthBiasClamp = 0.0f, .depthBiasSlopeFactor = 0.0f, .lineWidth = 1.0f, }; constexpr vk::PipelineMultisampleStateCreateInfo PIPELINE_MULTISAMPLE_STATE_CREATE_INFO{ .rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = VK_FALSE, .minSampleShading = 0.0f, .pSampleMask = nullptr, .alphaToCoverageEnable = VK_FALSE, .alphaToOneEnable = VK_FALSE, }; constexpr std::array DYNAMIC_STATES{ vk::DynamicState::eViewport, vk::DynamicState::eScissor, }; constexpr vk::PipelineDynamicStateCreateInfo PIPELINE_DYNAMIC_STATE_CREATE_INFO{ .dynamicStateCount = static_cast(DYNAMIC_STATES.size()), .pDynamicStates = DYNAMIC_STATES.data(), }; constexpr vk::PipelineColorBlendStateCreateInfo PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO{ .logicOpEnable = VK_FALSE, .logicOp = vk::LogicOp::eClear, .attachmentCount = 0, .pAttachments = nullptr, .blendConstants = std::array{0.0f, 0.0f, 0.0f, 0.0f}, }; constexpr vk::PipelineDepthStencilStateCreateInfo PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO{ .depthTestEnable = VK_TRUE, .depthWriteEnable = VK_TRUE, .depthCompareOp = vk::CompareOp::eAlways, .depthBoundsTestEnable = VK_FALSE, .stencilTestEnable = VK_FALSE, .front = vk::StencilOpState{}, .back = vk::StencilOpState{}, .minDepthBounds = 0.0f, .maxDepthBounds = 0.0f, }; template inline constexpr vk::SamplerCreateInfo SAMPLER_CREATE_INFO{ .magFilter = filter, .minFilter = filter, .mipmapMode = vk::SamplerMipmapMode::eNearest, .addressModeU = vk::SamplerAddressMode::eClampToBorder, .addressModeV = vk::SamplerAddressMode::eClampToBorder, .addressModeW = vk::SamplerAddressMode::eClampToBorder, .mipLodBias = 0.0f, .anisotropyEnable = VK_FALSE, .maxAnisotropy = 0.0f, .compareEnable = VK_FALSE, .compareOp = vk::CompareOp::eNever, .minLod = 0.0f, .maxLod = 0.0f, .borderColor = vk::BorderColor::eFloatOpaqueWhite, .unnormalizedCoordinates = VK_FALSE, }; constexpr vk::PipelineLayoutCreateInfo PipelineLayoutCreateInfo( const vk::DescriptorSetLayout* set_layout, bool compute = false) { return vk::PipelineLayoutCreateInfo{ .setLayoutCount = 1, .pSetLayouts = set_layout, .pushConstantRangeCount = 1, .pPushConstantRanges = (compute ? &COMPUTE_PUSH_CONSTANT_RANGE : &PUSH_CONSTANT_RANGE), }; } constexpr std::array MakeStages( vk::ShaderModule vertex_shader, vk::ShaderModule fragment_shader) { return std::array{ vk::PipelineShaderStageCreateInfo{ .stage = vk::ShaderStageFlagBits::eVertex, .module = vertex_shader, .pName = "main", }, vk::PipelineShaderStageCreateInfo{ .stage = vk::ShaderStageFlagBits::eFragment, .module = fragment_shader, .pName = "main", }, }; } constexpr vk::PipelineShaderStageCreateInfo MakeStages(vk::ShaderModule compute_shader) { return vk::PipelineShaderStageCreateInfo{ .stage = vk::ShaderStageFlagBits::eCompute, .module = compute_shader, .pName = "main", }; } } // Anonymous namespace BlitHelper::BlitHelper(const Instance& instance_, Scheduler& scheduler_, DescriptorPool& pool, RenderpassCache& renderpass_cache_) : instance{instance_}, scheduler{scheduler_}, renderpass_cache{renderpass_cache_}, device{instance.GetDevice()}, compute_provider{instance, pool, COMPUTE_BINDINGS}, compute_buffer_provider{instance, pool, COMPUTE_BUFFER_BINDINGS}, two_textures_provider{instance, pool, TWO_TEXTURES_BINDINGS}, compute_pipeline_layout{ device.createPipelineLayout(PipelineLayoutCreateInfo(&compute_provider.Layout(), true))}, compute_buffer_pipeline_layout{device.createPipelineLayout( PipelineLayoutCreateInfo(&compute_buffer_provider.Layout(), true))}, two_textures_pipeline_layout{ device.createPipelineLayout(PipelineLayoutCreateInfo(&two_textures_provider.Layout()))}, full_screen_vert{CompileSPV(FULL_SCREEN_TRIANGLE_VERT_SPV, device)}, d24s8_to_rgba8_comp{CompileSPV(VULKAN_D24S8_TO_RGBA8_COMP_SPV, device)}, depth_to_buffer_comp{CompileSPV(VULKAN_DEPTH_TO_BUFFER_COMP_SPV, device)}, blit_depth_stencil_frag{CompileSPV(VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV, device)}, d24s8_to_rgba8_pipeline{MakeComputePipeline(d24s8_to_rgba8_comp, compute_pipeline_layout)}, depth_to_buffer_pipeline{ MakeComputePipeline(depth_to_buffer_comp, compute_buffer_pipeline_layout)}, depth_blit_pipeline{MakeDepthStencilBlitPipeline()}, linear_sampler{device.createSampler(SAMPLER_CREATE_INFO)}, nearest_sampler{device.createSampler(SAMPLER_CREATE_INFO)} {} BlitHelper::~BlitHelper() { device.destroyPipelineLayout(compute_pipeline_layout); device.destroyPipelineLayout(compute_buffer_pipeline_layout); device.destroyPipelineLayout(two_textures_pipeline_layout); device.destroyShaderModule(full_screen_vert); device.destroyShaderModule(d24s8_to_rgba8_comp); device.destroyShaderModule(depth_to_buffer_comp); device.destroyShaderModule(blit_depth_stencil_frag); device.destroyPipeline(depth_to_buffer_pipeline); device.destroyPipeline(d24s8_to_rgba8_pipeline); device.destroyPipeline(depth_blit_pipeline); device.destroySampler(linear_sampler); device.destroySampler(nearest_sampler); } void BindBlitState(vk::CommandBuffer cmdbuf, vk::PipelineLayout layout, const VideoCore::TextureBlit& blit) { const vk::Offset2D offset{ .x = std::min(blit.dst_rect.left, blit.dst_rect.right), .y = std::min(blit.dst_rect.bottom, blit.dst_rect.top), }; const vk::Extent2D extent{ .width = blit.dst_rect.GetWidth(), .height = blit.dst_rect.GetHeight(), }; const vk::Viewport viewport{ .x = static_cast(offset.x), .y = static_cast(offset.y), .width = static_cast(extent.width), .height = static_cast(extent.height), .minDepth = 0.0f, .maxDepth = 1.0f, }; const vk::Rect2D scissor{ .offset = offset, .extent = extent, }; const float scale_x = static_cast(blit.src_rect.GetWidth()); const float scale_y = static_cast(blit.src_rect.GetHeight()); const PushConstants push_constants{ .tex_scale = {scale_x, scale_y}, .tex_offset = {static_cast(blit.src_rect.left), static_cast(blit.src_rect.bottom)}, }; cmdbuf.setViewport(0, viewport); cmdbuf.setScissor(0, scissor); cmdbuf.pushConstants(layout, vk::ShaderStageFlagBits::eVertex, 0, sizeof(push_constants), &push_constants); } bool BlitHelper::BlitDepthStencil(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit) { if (!instance.IsShaderStencilExportSupported()) { LOG_ERROR(Render_Vulkan, "Unable to emulate depth stencil images"); return false; } const vk::Rect2D dst_render_area = { .offset = {0, 0}, .extent = {dest.GetScaledWidth(), dest.GetScaledHeight()}, }; std::array textures{}; textures[0].image_info = vk::DescriptorImageInfo{ .sampler = nearest_sampler, .imageView = source.DepthView(), .imageLayout = vk::ImageLayout::eGeneral, }; textures[1].image_info = vk::DescriptorImageInfo{ .sampler = nearest_sampler, .imageView = source.StencilView(), .imageLayout = vk::ImageLayout::eGeneral, }; const auto descriptor_set = two_textures_provider.Acquire(textures); const RenderPass depth_pass = { .framebuffer = dest.Framebuffer(), .render_pass = renderpass_cache.GetRenderpass(PixelFormat::Invalid, dest.pixel_format, false), .render_area = dst_render_area, }; renderpass_cache.BeginRendering(depth_pass); scheduler.Record([blit, descriptor_set, this](vk::CommandBuffer cmdbuf) { const vk::PipelineLayout layout = two_textures_pipeline_layout; cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, depth_blit_pipeline); cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, descriptor_set, {}); BindBlitState(cmdbuf, layout, blit); cmdbuf.draw(3, 1, 0, 0); }); scheduler.MakeDirty(StateFlags::Pipeline); return true; } bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest, const VideoCore::TextureCopy& copy) { std::array textures{}; textures[0].image_info = vk::DescriptorImageInfo{ .imageView = source.DepthView(), .imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal, }; textures[1].image_info = vk::DescriptorImageInfo{ .imageView = source.StencilView(), .imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal, }; textures[2].image_info = vk::DescriptorImageInfo{ .imageView = dest.ImageView(), .imageLayout = vk::ImageLayout::eGeneral, }; const auto descriptor_set = compute_provider.Acquire(textures); renderpass_cache.EndRendering(); scheduler.Record([this, descriptor_set, copy, src_image = source.Image(), dst_image = dest.Image()](vk::CommandBuffer cmdbuf) { const std::array pre_barriers = { vk::ImageMemoryBarrier{ .srcAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite, .dstAccessMask = vk::AccessFlagBits::eShaderRead, .oldLayout = vk::ImageLayout::eGeneral, .newLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = src_image, .subresourceRange{ .aspectMask = vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, .baseMipLevel = 0, .levelCount = VK_REMAINING_MIP_LEVELS, .baseArrayLayer = 0, .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }, vk::ImageMemoryBarrier{ .srcAccessMask = vk::AccessFlagBits::eNone, .dstAccessMask = vk::AccessFlagBits::eShaderWrite, .oldLayout = vk::ImageLayout::eUndefined, .newLayout = vk::ImageLayout::eGeneral, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = dst_image, .subresourceRange{ .aspectMask = vk::ImageAspectFlagBits::eColor, .baseMipLevel = 0, .levelCount = VK_REMAINING_MIP_LEVELS, .baseArrayLayer = 0, .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }, }; const std::array post_barriers = { vk::ImageMemoryBarrier{ .srcAccessMask = vk::AccessFlagBits::eShaderRead, .dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentRead, .oldLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal, .newLayout = vk::ImageLayout::eGeneral, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = src_image, .subresourceRange{ .aspectMask = vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, .baseMipLevel = 0, .levelCount = VK_REMAINING_MIP_LEVELS, .baseArrayLayer = 0, .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }, vk::ImageMemoryBarrier{ .srcAccessMask = vk::AccessFlagBits::eShaderWrite, .dstAccessMask = vk::AccessFlagBits::eTransferRead, .oldLayout = vk::ImageLayout::eGeneral, .newLayout = vk::ImageLayout::eGeneral, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = dst_image, .subresourceRange{ .aspectMask = vk::ImageAspectFlagBits::eColor, .baseMipLevel = 0, .levelCount = VK_REMAINING_MIP_LEVELS, .baseArrayLayer = 0, .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }}; cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests, vk::PipelineStageFlagBits::eComputeShader, vk::DependencyFlagBits::eByRegion, {}, {}, pre_barriers); cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eCompute, compute_pipeline_layout, 0, descriptor_set, {}); cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, d24s8_to_rgba8_pipeline); const ComputeInfo info = { .src_offset = Common::Vec2i{static_cast(copy.src_offset.x), static_cast(copy.src_offset.y)}, .dst_offset = Common::Vec2i{static_cast(copy.dst_offset.x), static_cast(copy.dst_offset.y)}, }; cmdbuf.pushConstants(compute_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0, sizeof(info), &info); cmdbuf.dispatch(copy.extent.width / 8, copy.extent.height / 8, 1); cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests | vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, {}, {}, post_barriers); }); return true; } bool BlitHelper::DepthToBuffer(Surface& source, vk::Buffer buffer, const VideoCore::BufferTextureCopy& copy) { std::array textures{}; textures[0].image_info = vk::DescriptorImageInfo{ .sampler = nearest_sampler, .imageView = source.DepthView(), .imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal, }; textures[1].image_info = vk::DescriptorImageInfo{ .sampler = nearest_sampler, .imageView = source.StencilView(), .imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal, }; textures[2].buffer_info = vk::DescriptorBufferInfo{ .buffer = buffer, .offset = copy.buffer_offset, .range = copy.buffer_size, }; const auto descriptor_set = compute_buffer_provider.Acquire(textures); renderpass_cache.EndRendering(); scheduler.Record([this, descriptor_set, copy, src_image = source.Image(), extent = source.RealExtent(false)](vk::CommandBuffer cmdbuf) { const vk::ImageMemoryBarrier pre_barrier = { .srcAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite, .dstAccessMask = vk::AccessFlagBits::eShaderRead, .oldLayout = vk::ImageLayout::eGeneral, .newLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = src_image, .subresourceRange{ .aspectMask = vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, .baseMipLevel = 0, .levelCount = VK_REMAINING_MIP_LEVELS, .baseArrayLayer = 0, .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }; const vk::ImageMemoryBarrier post_barrier = { .srcAccessMask = vk::AccessFlagBits::eShaderRead, .dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentRead, .oldLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal, .newLayout = vk::ImageLayout::eGeneral, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = src_image, .subresourceRange{ .aspectMask = vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, .baseMipLevel = 0, .levelCount = VK_REMAINING_MIP_LEVELS, .baseArrayLayer = 0, .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }; cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests, vk::PipelineStageFlagBits::eComputeShader, vk::DependencyFlagBits::eByRegion, {}, {}, pre_barrier); cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eCompute, compute_buffer_pipeline_layout, 0, descriptor_set, {}); cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, depth_to_buffer_pipeline); const ComputeInfo info = { .src_offset = Common::Vec2i{static_cast(copy.texture_rect.left), static_cast(copy.texture_rect.bottom)}, .src_extent = Common::Vec2i{static_cast(extent.width), static_cast(extent.height)}, }; cmdbuf.pushConstants(compute_buffer_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0, sizeof(ComputeInfo), &info); cmdbuf.dispatch(copy.texture_rect.GetWidth() / 8, copy.texture_rect.GetHeight() / 8, 1); cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests | vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, {}, {}, post_barrier); }); return true; } vk::Pipeline BlitHelper::MakeComputePipeline(vk::ShaderModule shader, vk::PipelineLayout layout) { const vk::ComputePipelineCreateInfo compute_info = { .stage = MakeStages(shader), .layout = layout, }; if (const auto result = device.createComputePipeline({}, compute_info); result.result == vk::Result::eSuccess) { return result.value; } else { LOG_CRITICAL(Render_Vulkan, "Compute pipeline creation failed!"); UNREACHABLE(); } } vk::Pipeline BlitHelper::MakeDepthStencilBlitPipeline() { if (!instance.IsShaderStencilExportSupported()) { return VK_NULL_HANDLE; } const std::array stages = MakeStages(full_screen_vert, blit_depth_stencil_frag); const auto renderpass = renderpass_cache.GetRenderpass(VideoCore::PixelFormat::Invalid, VideoCore::PixelFormat::D24S8, false); vk::GraphicsPipelineCreateInfo depth_stencil_info = { .stageCount = static_cast(stages.size()), .pStages = stages.data(), .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, .pTessellationState = nullptr, .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO, .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, .layout = two_textures_pipeline_layout, .renderPass = renderpass, }; if (const auto result = device.createGraphicsPipeline({}, depth_stencil_info); result.result == vk::Result::eSuccess) { return result.value; } else { LOG_CRITICAL(Render_Vulkan, "Depth stencil blit pipeline creation failed!"); UNREACHABLE(); } return VK_NULL_HANDLE; } } // namespace Vulkan