#include "common/assert.h"
#include "common/common.h"
#include "common/emulatorConfig.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/descriptorCache.h"
#include "graphics/host_gpu/renderer/pipelineCache.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/renderer/shaderSubgroup.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ShaderIR.h"
#include "graphics/shader/shader.h"

#include <algorithm>
#include <limits>
#include <span>

namespace Libs::Graphics {

// IDK: maybe we can remove it?
constexpr uint32_t kTemporaryPs5BufferFormat121 = 121u;

static bool NarrowInputFormat(vk::Format& format, uint32_t& size, uint32_t used_components) {
	if (used_components == 0 || used_components >= size) {
		return false;
	}

	switch (format) {
		case vk::Format::eR32G32B32A32Sfloat:
			switch (used_components) {
				case 1: format = vk::Format::eR32Sfloat; break;
				case 2: format = vk::Format::eR32G32Sfloat; break;
				case 3: format = vk::Format::eR32G32B32Sfloat; break;
				default: return false;
			}
			size = used_components;
			return true;
		case vk::Format::eR32G32B32Sfloat:
			switch (used_components) {
				case 1: format = vk::Format::eR32Sfloat; break;
				case 2: format = vk::Format::eR32G32Sfloat; break;
				default: return false;
			}
			size = used_components;
			return true;
		case vk::Format::eR16G16B16A16Sfloat:
			switch (used_components) {
				case 1: format = vk::Format::eR16Sfloat; break;
				case 2: format = vk::Format::eR16G16Sfloat; break;
				default: return false;
			}
			size = used_components;
			return true;
		case vk::Format::eR8G8B8A8Unorm:
			switch (used_components) {
				case 1: format = vk::Format::eR8Unorm; break;
				case 2: format = vk::Format::eR8G8Unorm; break;
				default: return false;
			}
			size = used_components;
			return true;
		case vk::Format::eR8G8B8A8Snorm:
			if (used_components != 2) {
				return false;
			}
			format = vk::Format::eR8G8Snorm;
			size   = 2;
			return true;
		case vk::Format::eR8G8B8A8Uint:
			switch (used_components) {
				case 1: format = vk::Format::eR8Uint; break;
				case 2: format = vk::Format::eR8G8Uint; break;
				default: return false;
			}
			size = used_components;
			return true;
		default: break;
	}

	return false;
}

static void GetInputFormat(const ShaderBufferResource& res, vk::Format& format, uint32_t& size,
                           uint32_t used_components) {
	auto fmt = res.Format();
	if (fmt == Prospero::GpuEnumValue(Prospero::VertexAttribFormat::k16_16SInt)) {
		static bool logged_113 = false;
		if (!logged_113) {
			LOGF("InputFormat: temporary: accepting invalid PS5 buffer format 113 as "
			     "vk::Format::eR32G32B32A32Sfloat\n");
			logged_113 = true;
		}
		format = vk::Format::eR32G32B32A32Sfloat;
		size   = 4;
		if (NarrowInputFormat(format, size, used_components)) {
			LOGF("InputFormat: narrowing fmt=%u to %s for used_components=%u\n", fmt,
			     VulkanToString(format).c_str(), used_components);
		}
		return;
	}
	if (fmt == kTemporaryPs5BufferFormat121) {
		static bool logged_121 = false;
		if (!logged_121) {
			LOGF("InputFormat: accepting PS5 buffer format 121 as vk::Format::eR16G16Sfloat\n");
			logged_121 = true;
		}
		format = vk::Format::eR16G16Sfloat;
		size   = 2;
		if (NarrowInputFormat(format, size, used_components)) {
			LOGF("InputFormat: narrowing fmt=%u to %s for used_components=%u\n", fmt,
			     VulkanToString(format).c_str(), used_components);
		}
		return;
	}

	switch (static_cast<Prospero::BufferFormat>(fmt)) {
		case Prospero::BufferFormat::k32_32_32_32Float:
			format = vk::Format::eR32G32B32A32Sfloat;
			size   = 4;
			break;
		case Prospero::BufferFormat::k32_32_32_32SInt:
			format = vk::Format::eR32G32B32A32Sint;
			size   = 4;
			break;
		case Prospero::BufferFormat::k32_32_32_32UInt:
			format = vk::Format::eR32G32B32A32Uint;
			size   = 4;
			break;
		case Prospero::BufferFormat::k32_32_32Float:
			format = vk::Format::eR32G32B32Sfloat;
			size   = 3;
			break;
		case Prospero::BufferFormat::k32_32_32SInt:
			format = vk::Format::eR32G32B32Sint;
			size   = 3;
			break;
		case Prospero::BufferFormat::k32_32_32UInt:
			format = vk::Format::eR32G32B32Uint;
			size   = 3;
			break;
		case Prospero::BufferFormat::k16_16_16_16Float:
			format = vk::Format::eR16G16B16A16Sfloat;
			size   = 4;
			break;
		case Prospero::BufferFormat::k16_16_16_16SInt:
			format = vk::Format::eR16G16B16A16Sint;
			size   = 4;
			break;
		case Prospero::BufferFormat::k16_16_16_16UInt:
			format = vk::Format::eR16G16B16A16Uint;
			size   = 4;
			break;
		case Prospero::BufferFormat::k16_16_16_16SScaled:
			format = vk::Format::eR16G16B16A16Sscaled;
			size   = 4;
			break;
		case Prospero::BufferFormat::k16_16_16_16UScaled:
			format = vk::Format::eR16G16B16A16Uscaled;
			size   = 4;
			break;
		case Prospero::BufferFormat::k16_16_16_16SNorm:
			format = vk::Format::eR16G16B16A16Snorm;
			size   = 4;
			break;
		case Prospero::BufferFormat::k16_16_16_16UNorm:
			format = vk::Format::eR16G16B16A16Unorm;
			size   = 4;
			break;
		case Prospero::BufferFormat::k32_32Float:
			format = vk::Format::eR32G32Sfloat;
			size   = 2;
			break;
		case Prospero::BufferFormat::k32_32SInt:
			format = vk::Format::eR32G32Sint;
			size   = 2;
			break;
		case Prospero::BufferFormat::k32_32UInt:
			format = vk::Format::eR32G32Uint;
			size   = 2;
			break;
		case Prospero::BufferFormat::k8_8_8_8UInt:
			format = vk::Format::eR8G8B8A8Uint;
			size   = 4;
			break;
		case Prospero::BufferFormat::k8_8_8_8SScaled:
			format = vk::Format::eR8G8B8A8Sscaled;
			size   = 4;
			break;
		case Prospero::BufferFormat::k8_8_8_8UScaled:
			format = vk::Format::eR8G8B8A8Uscaled;
			size   = 4;
			break;
		case Prospero::BufferFormat::k8_8_8_8SNorm:
			format = vk::Format::eR8G8B8A8Snorm;
			size   = 4;
			break;
		case Prospero::BufferFormat::k8_8_8_8UNorm:
			format = vk::Format::eR8G8B8A8Unorm;
			size   = 4;
			break;
		case Prospero::BufferFormat::k10_10_10_2UNorm:
			format = vk::Format::eA2B10G10R10UnormPack32;
			size   = 4;
			break;
		case Prospero::BufferFormat::k10_10_10_2SNorm:
			format = vk::Format::eA2B10G10R10SnormPack32;
			size   = 4;
			break;
		case Prospero::BufferFormat::k16_16Float:
			format = vk::Format::eR16G16Sfloat;
			size   = 2;
			break;
		case Prospero::BufferFormat::k16_16SInt:
			format = vk::Format::eR16G16Sint;
			size   = 2;
			break;
		case Prospero::BufferFormat::k16_16UInt:
			format = vk::Format::eR16G16Uint;
			size   = 2;
			break;
		case Prospero::BufferFormat::k16_16SScaled:
			format = vk::Format::eR16G16Sscaled;
			size   = 2;
			break;
		case Prospero::BufferFormat::k16_16UScaled:
			format = vk::Format::eR16G16Uscaled;
			size   = 2;
			break;
		case Prospero::BufferFormat::k16_16SNorm:
			format = vk::Format::eR16G16Snorm;
			size   = 2;
			break;
		case Prospero::BufferFormat::k16_16UNorm:
			format = vk::Format::eR16G16Unorm;
			size   = 2;
			break;
		case Prospero::BufferFormat::k32Float:
			format = vk::Format::eR32Sfloat;
			size   = 1;
			break;
		case Prospero::BufferFormat::k32SInt:
			format = vk::Format::eR32Sint;
			size   = 1;
			break;
		case Prospero::BufferFormat::k32UInt:
			format = vk::Format::eR32Uint;
			size   = 1;
			break;
		case Prospero::BufferFormat::k8_8SInt:
			format = vk::Format::eR8G8Sint;
			size   = 2;
			break;
		case Prospero::BufferFormat::k8_8UInt:
			format = vk::Format::eR8G8Uint;
			size   = 2;
			break;
		case Prospero::BufferFormat::k8_8SScaled:
			format = vk::Format::eR8G8Sscaled;
			size   = 2;
			break;
		case Prospero::BufferFormat::k8_8UScaled:
			format = vk::Format::eR8G8Uscaled;
			size   = 2;
			break;
		case Prospero::BufferFormat::k8_8SNorm:
			format = vk::Format::eR8G8Snorm;
			size   = 2;
			break;
		case Prospero::BufferFormat::k8_8UNorm:
			format = vk::Format::eR8G8Unorm;
			size   = 2;
			break;
		case Prospero::BufferFormat::k16Float:
			format = vk::Format::eR16Sfloat;
			size   = 1;
			break;
		case Prospero::BufferFormat::k16SInt:
			format = vk::Format::eR16Sint;
			size   = 1;
			break;
		case Prospero::BufferFormat::k16UInt:
			format = vk::Format::eR16Uint;
			size   = 1;
			break;
		case Prospero::BufferFormat::k16SScaled:
			format = vk::Format::eR16Sscaled;
			size   = 1;
			break;
		case Prospero::BufferFormat::k16UScaled:
			format = vk::Format::eR16Uscaled;
			size   = 1;
			break;
		case Prospero::BufferFormat::k16SNorm:
			format = vk::Format::eR16Snorm;
			size   = 1;
			break;
		case Prospero::BufferFormat::k16UNorm:
			format = vk::Format::eR16Unorm;
			size   = 1;
			break;
		case Prospero::BufferFormat::k8SInt:
			format = vk::Format::eR8Sint;
			size   = 1;
			break;
		case Prospero::BufferFormat::k8UInt:
			format = vk::Format::eR8Uint;
			size   = 1;
			break;
		case Prospero::BufferFormat::k8SScaled:
			format = vk::Format::eR8Sscaled;
			size   = 1;
			break;
		case Prospero::BufferFormat::k8UScaled:
			format = vk::Format::eR8Uscaled;
			size   = 1;
			break;
		case Prospero::BufferFormat::k8SNorm:
			format = vk::Format::eR8Snorm;
			size   = 1;
			break;
		case Prospero::BufferFormat::k8UNorm:
			format = vk::Format::eR8Unorm;
			size   = 1;
			break;
		default:
			EXIT("unknown format: fmt = %u\n", fmt);
			format = vk::Format::eUndefined;
			size   = 4;
			break;
	}

	if (NarrowInputFormat(format, size, used_components)) {
		static std::atomic<uint64_t> log_count = 0;
		auto                         log_id    = log_count.fetch_add(1);
		if (log_id < 32) {
			LOGF("VertexInput: narrowed vertex format to %" PRIu32
			     " component(s) for shader fetch\n",
			     used_components);
		}
	}
}

static vk::BlendFactor GetBlendFactor(uint32_t factor) {
	switch (static_cast<Prospero::BlendFactor>(factor)) {
		case Prospero::BlendFactor::kZero: return vk::BlendFactor::eZero;
		case Prospero::BlendFactor::kOne: return vk::BlendFactor::eOne;
		case Prospero::BlendFactor::kSrcColor: return vk::BlendFactor::eSrcColor;
		case Prospero::BlendFactor::kOneMinusSrcColor: return vk::BlendFactor::eOneMinusSrcColor;
		case Prospero::BlendFactor::kSrcAlpha: return vk::BlendFactor::eSrcAlpha;
		case Prospero::BlendFactor::kOneMinusSrcAlpha: return vk::BlendFactor::eOneMinusSrcAlpha;
		case Prospero::BlendFactor::kDstAlpha: return vk::BlendFactor::eDstAlpha;
		case Prospero::BlendFactor::kOneMinusDstAlpha: return vk::BlendFactor::eOneMinusDstAlpha;
		case Prospero::BlendFactor::kDstColor: return vk::BlendFactor::eDstColor;
		case Prospero::BlendFactor::kOneMinusDstColor: return vk::BlendFactor::eOneMinusDstColor;
		case Prospero::BlendFactor::kSrcAlphaSaturate: return vk::BlendFactor::eSrcAlphaSaturate;
		case Prospero::BlendFactor::kConstantColor: return vk::BlendFactor::eConstantColor;
		case Prospero::BlendFactor::kOneMinusConstantColor:
			return vk::BlendFactor::eOneMinusConstantColor;
		case Prospero::BlendFactor::kSrc1Color: return vk::BlendFactor::eSrc1Color;
		case Prospero::BlendFactor::kOneMinusSrc1Color: return vk::BlendFactor::eOneMinusSrc1Color;
		case Prospero::BlendFactor::kSrc1Alpha: return vk::BlendFactor::eSrc1Alpha;
		case Prospero::BlendFactor::kOneMinusSrc1Alpha: return vk::BlendFactor::eOneMinusSrc1Alpha;
		case Prospero::BlendFactor::kConstantAlpha: return vk::BlendFactor::eConstantAlpha;
		case Prospero::BlendFactor::kOneMinusConstantAlpha:
			return vk::BlendFactor::eOneMinusConstantAlpha;
		default: EXIT("unknown factor: %u\n", factor);
	}
	return vk::BlendFactor::eZero;
}

static vk::BlendOp GetBlendOp(uint32_t op) {
	switch (static_cast<Prospero::BlendOp>(op)) {
		case Prospero::BlendOp::kAdd: return vk::BlendOp::eAdd;
		case Prospero::BlendOp::kSubtract: return vk::BlendOp::eSubtract;
		case Prospero::BlendOp::kMin: return vk::BlendOp::eMin;
		case Prospero::BlendOp::kMax: return vk::BlendOp::eMax;
		case Prospero::BlendOp::kReverseSubtract: return vk::BlendOp::eReverseSubtract;
		default: EXIT("unknown op: %u\n", op);
	}
	return vk::BlendOp::eAdd;
}

static void CreateLayout(std::span<vk::DescriptorSetLayout> set_layouts, uint32_t& set_layouts_num,
                         std::span<vk::PushConstantRange>     push_constant_info,
                         uint32_t&                            push_constant_info_num,
                         const ShaderRecompiler::IR::Program& program,
                         vk::ShaderStageFlags vk_stage, DescriptorCache::Stage stage) {
	const auto& bindings        = program.bindings;
	const bool  need_descriptor = !bindings.descriptors.empty();

	if (bindings.push_constant_size != 0) {
		auto index = push_constant_info_num;

		push_constant_info[index].stageFlags = vk_stage;
		push_constant_info[index].offset     = bindings.push_constant_offset;
		push_constant_info[index].size       = bindings.push_constant_size;
		push_constant_info_num++;
	}

	if (need_descriptor) {
		EXIT_IF(bindings.descriptor_set != set_layouts_num);

		set_layouts[set_layouts_num] =
		    GetRenderContext().GetDescriptorCache().GetDescriptorSetLayout(stage, program);
		set_layouts_num++;
	}
}

static void ConfigureSubgroupSize(vk::ShaderStageFlagBits                                vk_stage,
                                  const ShaderRecompiler::IR::Program&                   program,
                                  vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo& required,
                                  vk::PipelineShaderStageCreateInfo&                     stage) {
	const auto& graphics = GetRenderContext().GetGraphics();
	const auto  config =
	    ConfigureShaderSubgroup(ShaderSubgroupCapabilities {graphics}, vk_stage, program);
	switch (config.mode) {
		case ShaderSubgroupMode::Natural: return;
		case ShaderSubgroupMode::PerInvocationGraphics: {
			static std::atomic<uint32_t> log_count {0};
			if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
				LOGF("Vulkan running %u-lane graphics shader stage 0x%08x with per-invocation "
				     "EXEC/VCC on the host-native subgroup\n",
				     program.wave_size, static_cast<uint32_t>(vk_stage));
			}
			return;
		}
		case ShaderSubgroupMode::FlattenedMasks: {
			static std::atomic<uint32_t> log_count {0};
			if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
				LOGF("Vulkan running %u-lane shader stage 0x%08x with flattened per-invocation "
				     "EXEC/VCC\n",
				     program.wave_size, static_cast<uint32_t>(vk_stage));
			}
			return;
		}
		case ShaderSubgroupMode::Controlled: break;
		case ShaderSubgroupMode::Unsupported:
		default:
			EXIT("Vulkan cannot run %u-lane shader stage 0x%08x: default=%u min=%u max=%u "
			     "controlled_stages=0x%08x\n",
			     program.wave_size, static_cast<uint32_t>(vk_stage), graphics.subgroup_size,
			     graphics.min_subgroup_size, graphics.max_subgroup_size,
			     static_cast<vk::ShaderStageFlags::MaskType>(
			         graphics.required_subgroup_size_stages));
	}

	required.sType = vk::StructureType::ePipelineShaderStageRequiredSubgroupSizeCreateInfo;
	required.requiredSubgroupSize = config.required_size;
	stage.pNext                   = &required;
}

// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::RenderPass render_pass,
                            const ShaderVertexInputInfo&    vs_input_info,
                            std::span<const uint32_t>       vs_shader,
                            const ShaderPixelInputInfo*     ps_input_info,
                            std::span<const uint32_t>       ps_shader,
                            const PipelineStaticParameters& static_params, uint32_t vs_hash0,
                            uint32_t vs_crc32, uint32_t ps_hash0, uint32_t ps_crc32,
                            bool ps_active) {
	EXIT_IF(render_pass == nullptr);
	EXIT_IF(ps_active && ps_input_info == nullptr);

	auto& graphics = GetRenderContext().GetGraphics();

	vk::ShaderModule vert_shader_module = nullptr;
	vk::ShaderModule frag_shader_module = nullptr;

	vk::ShaderModuleCreateInfo create_info {};

	create_info.sType = vk::StructureType::eShaderModuleCreateInfo;
	create_info.pNext = nullptr;
	create_info.flags = {};

	create_info.codeSize = vs_shader.size() * 4;
	create_info.pCode    = vs_shader.data();
	auto result = graphics.device.createShaderModule(&create_info, nullptr, &vert_shader_module);
	if (graphics_debug_dump_enabled()) {
		LOGF("PipelineTrace: vkCreateShaderModule VS done result=%s module=%p\n",
		     VulkanToString(result).c_str(), static_cast<void*>(vert_shader_module));
	}
	EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);

	if (ps_active) {
		create_info.codeSize = ps_shader.size() * 4;
		create_info.pCode    = ps_shader.data();
		result = graphics.device.createShaderModule(&create_info, nullptr, &frag_shader_module);
		if (graphics_debug_dump_enabled()) {
			LOGF("PipelineTrace: vkCreateShaderModule PS done result=%s module=%p\n",
			     VulkanToString(result).c_str(), static_cast<void*>(frag_shader_module));
		}
		EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
	}

	EXIT_NOT_IMPLEMENTED(vert_shader_module == nullptr);
	EXIT_NOT_IMPLEMENTED(ps_active && frag_shader_module == nullptr);

	vk::PipelineShaderStageCreateInfo                     vert_shader_stage_info {};
	vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo vert_subgroup_size {};

	vert_shader_stage_info.sType               = vk::StructureType::ePipelineShaderStageCreateInfo;
	vert_shader_stage_info.pNext               = nullptr;
	vert_shader_stage_info.flags               = {};
	vert_shader_stage_info.stage               = vk::ShaderStageFlagBits::eVertex;
	vert_shader_stage_info.module              = vert_shader_module;
	vert_shader_stage_info.pName               = "main";
	vert_shader_stage_info.pSpecializationInfo = nullptr;
	EXIT_IF(!vs_input_info.stage);
	ConfigureSubgroupSize(vk::ShaderStageFlagBits::eVertex, *vs_input_info.stage.program,
	                      vert_subgroup_size, vert_shader_stage_info);

	vk::PipelineShaderStageCreateInfo                     frag_shader_stage_info {};
	vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo frag_subgroup_size {};
	frag_shader_stage_info.sType               = vk::StructureType::ePipelineShaderStageCreateInfo;
	frag_shader_stage_info.pNext               = nullptr;
	frag_shader_stage_info.flags               = {};
	frag_shader_stage_info.stage               = vk::ShaderStageFlagBits::eFragment;
	frag_shader_stage_info.module              = frag_shader_module;
	frag_shader_stage_info.pName               = "main";
	frag_shader_stage_info.pSpecializationInfo = nullptr;
	if (ps_active) {
		EXIT_IF(!ps_input_info->stage);
		ConfigureSubgroupSize(vk::ShaderStageFlagBits::eFragment, *ps_input_info->stage.program,
		                      frag_subgroup_size, frag_shader_stage_info);
	}

	vk::PipelineShaderStageCreateInfo shader_stages[]    = {vert_shader_stage_info,
	                                                        frag_shader_stage_info};
	const uint32_t                    shader_stage_count = ps_active ? 2u : 1u;

	vk::VertexInputAttributeDescription input_attr[ShaderVertexInputInfo::RES_MAX];
	vk::VertexInputBindingDescription   input_desc[ShaderVertexInputInfo::RES_MAX];

	for (int bi = 0; bi < vs_input_info.buffers_num; bi++) {
		const auto& b          = vs_input_info.buffers[bi];
		input_desc[bi].binding = bi;
		input_desc[bi].stride  = b.stride;
		input_desc[bi].inputRate =
		    (b.fetch_index == 0 ? vk::VertexInputRate::eVertex : vk::VertexInputRate::eInstance);

		for (int ai = 0; ai < b.attr_num; ai++) {
			auto index                 = b.attr_indices[ai];
			input_attr[index].binding  = bi;
			input_attr[index].location = index;
			input_attr[index].offset   = b.attr_offsets[ai];

			uint32_t attr_size       = 4;
			auto     registers_num   = vs_input_info.resources_dst[index].registers_num;
			auto     used_components = (vs_input_info.resource_fetch_components[index] > 0
			                                ? vs_input_info.resource_fetch_components[index]
			                                : registers_num);
			GetInputFormat(vs_input_info.resources[index], input_attr[index].format, attr_size,
			               static_cast<uint32_t>(used_components));

			if (graphics_debug_dump_enabled()) {
				static std::atomic_uint log_count = 0;
				const auto              log_id = log_count.fetch_add(1, std::memory_order_relaxed);
				if (log_id < 128) {
					LOGF("VertexInputState[%u]: attr=%u binding=%u offset=%u stride=%u fmt=%d "
					     "src_fmt=%u dst=v%u regs=%u"
					     " fetched_components=%u attr_size=%u swizzle=%u,%u,%u,%u\n",
					     log_id, index, input_attr[index].binding, input_attr[index].offset,
					     input_desc[bi].stride, static_cast<int>(input_attr[index].format),
					     static_cast<uint32_t>(vs_input_info.resources[index].Format()),
					     static_cast<uint32_t>(vs_input_info.resources_dst[index].register_start),
					     static_cast<uint32_t>(registers_num),
					     static_cast<uint32_t>(used_components), attr_size,
					     static_cast<uint32_t>(vs_input_info.resources[index].DstSelX()),
					     static_cast<uint32_t>(vs_input_info.resources[index].DstSelY()),
					     static_cast<uint32_t>(vs_input_info.resources[index].DstSelZ()),
					     static_cast<uint32_t>(vs_input_info.resources[index].DstSelW()));
				}
			}

			if (vs_input_info.resources[index].OutOfBounds() != 0) {
				static bool logged = false;
				if (!logged) {
					LOGF("VertexInput: temporary: accepting PS5 out-of-bounds behavior %" PRIu8
					     "\n",
					     vs_input_info.resources[index].OutOfBounds());
					logged = true;
				}
			}

			EXIT_NOT_IMPLEMENTED(vs_input_info.resources[index].AddTid());
			EXIT_NOT_IMPLEMENTED(vs_input_info.resources[index].SwizzleEnabled());

			auto log_unsupported_vertex_swizzle = [index, attr_size, registers_num](
			                                          uint32_t swizzle, uint32_t expected) {
				static bool logged = false;
				if (!logged) {
					LOGF(
					    "VertexInput: temporary: accepting unsupported dst swizzle at attr %" PRIu32
					    " (attr_size=%" PRIu32 ", regs=%" PRIu32 ", swizzle=0x%03" PRIx32
					    ", expected=0x%03" PRIx32 ")\n",
					    static_cast<uint32_t>(index), attr_size, registers_num, swizzle, expected);
					logged = true;
				}
			};

			switch (registers_num) {
				case 1: {
					auto swizzle = vs_input_info.resources[index].DstSelX();
					if (swizzle != DstSel(4)) {
						log_unsupported_vertex_swizzle(swizzle, DstSel(4));
					}
					break;
				}
				case 2: {
					auto swizzle  = vs_input_info.resources[index].DstSelXY();
					auto expected = (attr_size == 1 ? DstSel(4, 0) : DstSel(4, 5));
					if (swizzle != expected) {
						log_unsupported_vertex_swizzle(swizzle, expected);
					}
					break;
				}
				case 3: {
					auto swizzle = vs_input_info.resources[index].DstSelXYZ();
					auto expected =
					    (attr_size == 1 ? DstSel(4, 0, 0)
					                    : (attr_size == 2 ? DstSel(4, 5, 0) : DstSel(4, 5, 6)));
					if (swizzle != expected) {
						log_unsupported_vertex_swizzle(swizzle, expected);
					}
					break;
				}
				case 4: {
					auto swizzle   = vs_input_info.resources[index].DstSelXYZW();
					auto expected  = DstSel(4, 5, 6, 7);
					bool supported = false;
					switch (attr_size) {
						case 1:
							expected  = DstSel(4, 0, 0, 1);
							supported = (swizzle == expected);
							break;
						case 2:
							expected  = DstSel(4, 5, 0, 1);
							supported = (swizzle == expected);
							break;
						case 3:
							expected  = DstSel(4, 5, 6, 1);
							supported = (swizzle == expected || swizzle == DstSel(4, 5, 6, 0));
							break;
						default:
							supported = (swizzle == expected || swizzle == DstSel(4, 5, 6, 1) ||
							             swizzle == DstSel(4, 5, 6, 0));
							break;
					}
					if (!supported) {
						log_unsupported_vertex_swizzle(swizzle, expected);
					}
					break;
				}
				default: EXIT("invalid registers_num");
			}
		}
	}

	vk::PipelineVertexInputStateCreateInfo vertex_input_info {};
	vertex_input_info.sType = vk::StructureType::ePipelineVertexInputStateCreateInfo;
	vertex_input_info.pNext = nullptr;
	vertex_input_info.flags = {};
	vertex_input_info.vertexBindingDescriptionCount   = vs_input_info.buffers_num;
	vertex_input_info.pVertexBindingDescriptions      = input_desc;
	vertex_input_info.vertexAttributeDescriptionCount = vs_input_info.resources_num;
	vertex_input_info.pVertexAttributeDescriptions    = input_attr;

	vk::PipelineInputAssemblyStateCreateInfo input_assembly {};
	input_assembly.sType    = vk::StructureType::ePipelineInputAssemblyStateCreateInfo;
	input_assembly.pNext    = nullptr;
	input_assembly.flags    = {};
	input_assembly.topology = static_params.topology;
	input_assembly.primitiveRestartEnable = VK_FALSE;

	vk::Viewport viewport {};
	viewport.x        = static_params.viewport_offset[0] - static_params.viewport_scale[0];
	viewport.y        = static_params.viewport_offset[1] - static_params.viewport_scale[1];
	viewport.width    = static_params.viewport_scale[0] * 2.0f;
	viewport.height   = static_params.viewport_scale[1] * 2.0f;
	viewport.minDepth = static_params.viewport_offset[2];
	viewport.maxDepth = static_params.viewport_scale[2] + static_params.viewport_offset[2];

	vk::Rect2D scissor {};
	scissor.offset = {static_params.scissor_ltrb[0], static_params.scissor_ltrb[1]};
	scissor.extent = {
	    static_cast<uint32_t>(static_params.scissor_ltrb[2] - static_params.scissor_ltrb[0]),
	    static_cast<uint32_t>(static_params.scissor_ltrb[3] - static_params.scissor_ltrb[1])};

	vk::PipelineViewportDepthClipControlCreateInfoEXT depth_clip_control {};
	depth_clip_control.sType = vk::StructureType::ePipelineViewportDepthClipControlCreateInfoEXT;
	depth_clip_control.pNext = nullptr;
	depth_clip_control.negativeOneToOne = (static_params.negative_one_to_one ? VK_TRUE : VK_FALSE);

	vk::PipelineViewportStateCreateInfo viewport_state {};
	viewport_state.sType         = vk::StructureType::ePipelineViewportStateCreateInfo;
	viewport_state.pNext         = &depth_clip_control;
	viewport_state.flags         = {};
	viewport_state.viewportCount = 1;
	viewport_state.pViewports    = &viewport;
	viewport_state.scissorCount  = 1;
	viewport_state.pScissors     = &scissor;

	vk::CullModeFlags cull_mode = vk::CullModeFlagBits::eNone;
	if (static_params.cull_back) {
		cull_mode |= vk::CullModeFlagBits::eBack;
	}
	if (static_params.cull_front) {
		cull_mode |= vk::CullModeFlagBits::eFront;
	}

	vk::FrontFace front_face =
	    (static_params.face ? vk::FrontFace::eClockwise : vk::FrontFace::eCounterClockwise);

	vk::PipelineRasterizationDepthClipStateCreateInfoEXT clip_ext {};
	clip_ext.sType           = vk::StructureType::ePipelineRasterizationDepthClipStateCreateInfoEXT;
	clip_ext.pNext           = nullptr;
	clip_ext.flags           = {};
	clip_ext.depthClipEnable = static_params.depth_clip_enable ? VK_TRUE : VK_FALSE;

	vk::PipelineRasterizationStateCreateInfo rasterizer {};
	rasterizer.sType                   = vk::StructureType::ePipelineRasterizationStateCreateInfo;
	rasterizer.pNext                   = &clip_ext;
	rasterizer.flags                   = {};
	rasterizer.depthClampEnable        = VK_FALSE;
	rasterizer.rasterizerDiscardEnable = VK_FALSE;
	rasterizer.polygonMode             = vk::PolygonMode::eFill;
	rasterizer.cullMode                = cull_mode;
	rasterizer.frontFace               = front_face;
	rasterizer.depthBiasEnable         = VK_FALSE;
	rasterizer.depthBiasConstantFactor = 0.0f;
	rasterizer.depthBiasClamp          = 0.0f;
	rasterizer.depthBiasSlopeFactor    = 0.0f;
	rasterizer.lineWidth               = 1.0f;

	vk::PipelineMultisampleStateCreateInfo multisampling {};
	multisampling.sType                 = vk::StructureType::ePipelineMultisampleStateCreateInfo;
	multisampling.pNext                 = nullptr;
	multisampling.flags                 = {};
	multisampling.sampleShadingEnable   = static_params.sample_shading_enable ? VK_TRUE : VK_FALSE;
	multisampling.rasterizationSamples  = vulkan_sample_count(static_params.samples);
	multisampling.minSampleShading      = 1.0f;
	multisampling.pSampleMask           = nullptr;
	multisampling.alphaToCoverageEnable = VK_FALSE;
	multisampling.alphaToOneEnable      = VK_FALSE;

	vk::PipelineColorBlendAttachmentState color_blend_attachment[RENDER_COLOR_ATTACHMENTS_MAX] = {};
	for (uint32_t i = 0; i < static_params.color_count; i++) {
		vk::ColorComponentFlags color_write_mask = {};
		EXIT_NOT_IMPLEMENTED((static_params.color_mask[i] & ~0x0fu) != 0);
		if ((static_params.color_mask[i] & 0x1u) != 0) {
			color_write_mask |=
			    static_cast<vk::ColorComponentFlags>(vk::ColorComponentFlagBits::eR);
		}
		if ((static_params.color_mask[i] & 0x2u) != 0) {
			color_write_mask |=
			    static_cast<vk::ColorComponentFlags>(vk::ColorComponentFlagBits::eG);
		}
		if ((static_params.color_mask[i] & 0x4u) != 0) {
			color_write_mask |=
			    static_cast<vk::ColorComponentFlags>(vk::ColorComponentFlagBits::eB);
		}
		if ((static_params.color_mask[i] & 0x8u) != 0) {
			color_write_mask |=
			    static_cast<vk::ColorComponentFlags>(vk::ColorComponentFlagBits::eA);
		}

		color_blend_attachment[i].colorWriteMask = color_write_mask;
		color_blend_attachment[i].blendEnable =
		    (static_params.blend_enable[i] && !static_params.blend_bypass[i]) ? VK_TRUE : VK_FALSE;
		color_blend_attachment[i].srcColorBlendFactor =
		    GetBlendFactor(static_params.color_srcblend[i]);
		color_blend_attachment[i].dstColorBlendFactor =
		    GetBlendFactor(static_params.color_destblend[i]);
		color_blend_attachment[i].colorBlendOp = GetBlendOp(static_params.color_comb_fcn[i]);
		color_blend_attachment[i].srcAlphaBlendFactor =
		    (static_params.separate_alpha_blend[i] ? GetBlendFactor(static_params.alpha_srcblend[i])
		                                           : color_blend_attachment[i].srcColorBlendFactor);
		color_blend_attachment[i].dstAlphaBlendFactor =
		    (static_params.separate_alpha_blend[i]
		         ? GetBlendFactor(static_params.alpha_destblend[i])
		         : color_blend_attachment[i].dstColorBlendFactor);
		color_blend_attachment[i].alphaBlendOp =
		    (static_params.separate_alpha_blend[i] ? GetBlendOp(static_params.alpha_comb_fcn[i])
		                                           : color_blend_attachment[i].colorBlendOp);
	}

	vk::Bool32 color_write_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
	for (uint32_t i = 0; i < static_params.color_count; i++) {
		color_write_enable[i] = VK_TRUE;
	}

	vk::PipelineColorWriteCreateInfoEXT color_write {};
	color_write.sType              = vk::StructureType::ePipelineColorWriteCreateInfoEXT;
	color_write.pNext              = nullptr;
	color_write.attachmentCount    = static_params.color_count;
	color_write.pColorWriteEnables = color_write_enable;

	vk::PipelineColorBlendStateCreateInfo color_blending {};
	color_blending.sType             = vk::StructureType::ePipelineColorBlendStateCreateInfo;
	color_blending.pNext             = &color_write;
	color_blending.flags             = {};
	color_blending.logicOpEnable     = VK_FALSE;
	color_blending.logicOp           = vk::LogicOp::eCopy;
	color_blending.attachmentCount   = static_params.color_count;
	color_blending.pAttachments      = color_blend_attachment;
	color_blending.blendConstants[0] = static_params.blend_color_red;
	color_blending.blendConstants[1] = static_params.blend_color_green;
	color_blending.blendConstants[2] = static_params.blend_color_blue;
	color_blending.blendConstants[3] = static_params.blend_color_alpha;

	vk::DescriptorSetLayout set_layouts[2]  = {};
	uint32_t                set_layouts_num = 0;

	vk::PushConstantRange push_constant_info[2];
	uint32_t              push_constant_info_num = 0;

	EXIT_IF(!vs_input_info.stage);
	CreateLayout(set_layouts, set_layouts_num, push_constant_info, push_constant_info_num,
	             *vs_input_info.stage.program, vk::ShaderStageFlagBits::eVertex,
	             DescriptorCache::Stage::Vertex);
	if (ps_active) {
		EXIT_IF(!ps_input_info->stage);
		CreateLayout(set_layouts, set_layouts_num, push_constant_info, push_constant_info_num,
		             *ps_input_info->stage.program, vk::ShaderStageFlagBits::eFragment,
		             DescriptorCache::Stage::Pixel);
	}

	vk::PipelineLayoutCreateInfo pipeline_layout_info {};
	pipeline_layout_info.sType                  = vk::StructureType::ePipelineLayoutCreateInfo;
	pipeline_layout_info.pNext                  = nullptr;
	pipeline_layout_info.flags                  = {};
	pipeline_layout_info.setLayoutCount         = set_layouts_num;
	pipeline_layout_info.pSetLayouts            = (set_layouts_num > 0 ? set_layouts : nullptr);
	pipeline_layout_info.pushConstantRangeCount = push_constant_info_num;
	pipeline_layout_info.pPushConstantRanges =
	    push_constant_info_num > 0 ? push_constant_info : nullptr;

	EXIT_IF(pipeline.pipeline_layout != nullptr);

	if (graphics_debug_dump_enabled()) {
		LOGF("PipelineTrace: vkCreatePipelineLayout begin VS=0x%08" PRIx32 "/0x%08" PRIx32
		     " PS=0x%08" PRIx32 "/0x%08" PRIx32 " set_layouts=%" PRIu32 " push_constants=%" PRIu32
		     "\n",
		     vs_hash0, vs_crc32, ps_hash0, ps_crc32, set_layouts_num, push_constant_info_num);
	}
	result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
	                                              &pipeline.pipeline_layout);
	if (graphics_debug_dump_enabled()) {
		LOGF("PipelineTrace: vkCreatePipelineLayout done result=%s layout=%p\n",
		     VulkanToString(result).c_str(), static_cast<void*>(pipeline.pipeline_layout));
	}
	EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);

	EXIT_NOT_IMPLEMENTED(pipeline.pipeline_layout == nullptr);

	vk::PipelineDepthStencilStateCreateInfo depth_stencil_info {};
	depth_stencil_info.sType            = vk::StructureType::ePipelineDepthStencilStateCreateInfo;
	depth_stencil_info.pNext            = nullptr;
	depth_stencil_info.flags            = {};
	depth_stencil_info.depthTestEnable  = (static_params.depth_test_enable ? VK_TRUE : VK_FALSE);
	depth_stencil_info.depthWriteEnable = (static_params.depth_write_enable ? VK_TRUE : VK_FALSE);
	depth_stencil_info.depthCompareOp   = static_params.depth_compare_op;
	depth_stencil_info.depthBoundsTestEnable =
	    (static_params.depth_bounds_test_enable ? VK_TRUE : VK_FALSE);
	depth_stencil_info.stencilTestEnable = (static_params.stencil_test_enable ? VK_TRUE : VK_FALSE);
	depth_stencil_info.front.failOp      = static_params.stencil_front.failOp;
	depth_stencil_info.front.passOp      = static_params.stencil_front.passOp;
	depth_stencil_info.front.depthFailOp = static_params.stencil_front.depthFailOp;
	depth_stencil_info.front.compareOp   = static_params.stencil_front.compareOp;
	depth_stencil_info.back.failOp       = static_params.stencil_back.failOp;
	depth_stencil_info.back.passOp       = static_params.stencil_back.passOp;
	depth_stencil_info.back.depthFailOp  = static_params.stencil_back.depthFailOp;
	depth_stencil_info.back.compareOp    = static_params.stencil_back.compareOp;
	depth_stencil_info.minDepthBounds    = static_params.depth_min_bounds;
	depth_stencil_info.maxDepthBounds    = static_params.depth_max_bounds;

	const vk::DynamicState dynamic_states[] = {
	    vk::DynamicState::eViewport,
	    vk::DynamicState::eScissor,
	    vk::DynamicState::eLineWidth,
	    vk::DynamicState::eStencilCompareMask,
	    vk::DynamicState::eStencilReference,
	    vk::DynamicState::eStencilWriteMask,
	    vk::DynamicState::eColorWriteEnableEXT,
	};
	const auto dynamic_states_count =
	    static_cast<uint32_t>(sizeof(dynamic_states) / sizeof(dynamic_states[0]));

	vk::PipelineDynamicStateCreateInfo dynamic_state {};
	dynamic_state.sType             = vk::StructureType::ePipelineDynamicStateCreateInfo;
	dynamic_state.pNext             = nullptr;
	dynamic_state.flags             = {};
	dynamic_state.dynamicStateCount = dynamic_states_count;
	dynamic_state.pDynamicStates    = dynamic_states;

	vk::GraphicsPipelineCreateInfo pipeline_info {};
	pipeline_info.sType               = vk::StructureType::eGraphicsPipelineCreateInfo;
	pipeline_info.pNext               = nullptr;
	pipeline_info.flags               = {};
	pipeline_info.stageCount          = shader_stage_count;
	pipeline_info.pStages             = shader_stages;
	pipeline_info.pVertexInputState   = &vertex_input_info;
	pipeline_info.pInputAssemblyState = &input_assembly;
	pipeline_info.pTessellationState  = nullptr;
	pipeline_info.pViewportState      = &viewport_state;
	pipeline_info.pRasterizationState = &rasterizer;
	pipeline_info.pMultisampleState   = &multisampling;
	pipeline_info.pDepthStencilState  = (static_params.with_depth ? &depth_stencil_info : nullptr);
	pipeline_info.pColorBlendState    = &color_blending;
	pipeline_info.pDynamicState       = &dynamic_state;
	pipeline_info.layout              = pipeline.pipeline_layout;
	pipeline_info.renderPass          = render_pass;
	pipeline_info.subpass             = 0;
	pipeline_info.basePipelineHandle  = nullptr;
	pipeline_info.basePipelineIndex   = -1;

	EXIT_IF(pipeline.pipeline != nullptr);

	if (graphics_debug_dump_enabled()) {
		LOGF("PipelineTrace: vkCreateGraphicsPipelines begin VS=0x%08" PRIx32 "/0x%08" PRIx32
		     " PS=0x%08" PRIx32 "/0x%08" PRIx32 " topology=%" PRIu32 " color_mask=0x%08" PRIx32
		     " depth=%s blend=%s dyn_states=%" PRIu32 " viewport=%f,%f %fx%f"
		     " scissor=%d,%d %ux%u\n",
		     vs_hash0, vs_crc32, ps_hash0, ps_crc32, static_cast<uint32_t>(static_params.topology),
		     static_params.color_mask[0], (static_params.with_depth ? "true" : "false"),
		     (static_params.blend_enable[0] ? "true" : "false"), dynamic_states_count, viewport.x,
		     viewport.y, viewport.width, viewport.height, scissor.offset.x, scissor.offset.y,
		     scissor.extent.width, scissor.extent.height);
	}
	result = graphics.device.createGraphicsPipelines(nullptr, 1, &pipeline_info, nullptr,
	                                                 &pipeline.pipeline);
	if (graphics_debug_dump_enabled()) {
		LOGF("PipelineTrace: vkCreateGraphicsPipelines done result=%s pipeline=%p\n",
		     VulkanToString(result).c_str(), static_cast<void*>(pipeline.pipeline));
	}
	EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);

	EXIT_NOT_IMPLEMENTED(pipeline.pipeline == nullptr);

	if (frag_shader_module != nullptr) {
		graphics.device.destroyShaderModule(frag_shader_module, nullptr);
	}
	graphics.device.destroyShaderModule(vert_shader_module, nullptr);
}

// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void CreatePipelineInternal(PipelineCache::ComputePipeline& pipeline,
                            const ShaderComputeInputInfo&   input_info,
                            std::span<const uint32_t>       cs_shader) {
	auto& graphics = GetRenderContext().GetGraphics();

	vk::ShaderModule comp_shader_module = nullptr;

	vk::ShaderModuleCreateInfo create_info {};

	create_info.sType    = vk::StructureType::eShaderModuleCreateInfo;
	create_info.pNext    = nullptr;
	create_info.flags    = {};
	create_info.codeSize = cs_shader.size() * 4;
	create_info.pCode    = cs_shader.data();
	LOGF("PipelineTrace: vkCreateShaderModule CS begin words=%" PRIu64 "\n",
	     static_cast<uint64_t>(cs_shader.size()));
	auto result = graphics.device.createShaderModule(&create_info, nullptr, &comp_shader_module);
	LOGF("PipelineTrace: vkCreateShaderModule CS done result=%s module=%p\n",
	     VulkanToString(result).c_str(), static_cast<void*>(comp_shader_module));
	EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);

	EXIT_NOT_IMPLEMENTED(comp_shader_module == nullptr);

	vk::PipelineShaderStageCreateInfo                     comp_shader_stage_info {};
	vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo comp_subgroup_size {};
	comp_shader_stage_info.sType               = vk::StructureType::ePipelineShaderStageCreateInfo;
	comp_shader_stage_info.pNext               = nullptr;
	comp_shader_stage_info.flags               = {};
	comp_shader_stage_info.stage               = vk::ShaderStageFlagBits::eCompute;
	comp_shader_stage_info.module              = comp_shader_module;
	comp_shader_stage_info.pName               = "main";
	comp_shader_stage_info.pSpecializationInfo = nullptr;
	EXIT_IF(!input_info.stage);
	ConfigureSubgroupSize(vk::ShaderStageFlagBits::eCompute, *input_info.stage.program,
	                      comp_subgroup_size, comp_shader_stage_info);

	vk::DescriptorSetLayout set_layouts[1]  = {};
	uint32_t                set_layouts_num = 0;

	vk::PushConstantRange push_constant_info[1];
	uint32_t              push_constant_info_num = 0;

	EXIT_IF(!input_info.stage);
	CreateLayout(set_layouts, set_layouts_num, push_constant_info, push_constant_info_num,
	             *input_info.stage.program, vk::ShaderStageFlagBits::eCompute,
	             DescriptorCache::Stage::Compute);

	vk::PipelineLayoutCreateInfo pipeline_layout_info {};
	pipeline_layout_info.sType                  = vk::StructureType::ePipelineLayoutCreateInfo;
	pipeline_layout_info.pNext                  = nullptr;
	pipeline_layout_info.flags                  = {};
	pipeline_layout_info.setLayoutCount         = set_layouts_num;
	pipeline_layout_info.pSetLayouts            = (set_layouts_num > 0 ? set_layouts : nullptr);
	pipeline_layout_info.pushConstantRangeCount = push_constant_info_num;
	pipeline_layout_info.pPushConstantRanges =
	    push_constant_info_num > 0 ? push_constant_info : nullptr;

	EXIT_IF(pipeline.pipeline_layout != nullptr);

	LOGF("PipelineTrace: vkCreatePipelineLayout CS begin set_layouts=%u push_constants=%u\n",
	     set_layouts_num, push_constant_info_num);
	result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
	                                              &pipeline.pipeline_layout);
	LOGF("PipelineTrace: vkCreatePipelineLayout CS done result=%s layout=%p\n",
	     VulkanToString(result).c_str(), static_cast<void*>(pipeline.pipeline_layout));
	EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);

	EXIT_NOT_IMPLEMENTED(pipeline.pipeline_layout == nullptr);

	vk::ComputePipelineCreateInfo info {};
	info.sType              = vk::StructureType::eComputePipelineCreateInfo;
	info.pNext              = nullptr;
	info.flags              = {};
	info.stage              = comp_shader_stage_info;
	info.layout             = pipeline.pipeline_layout;
	info.basePipelineHandle = nullptr;
	info.basePipelineIndex  = -1;

	EXIT_IF(pipeline.pipeline != nullptr);

	LOGF("PipelineTrace: vkCreateComputePipelines begin layout=%p\n",
	     static_cast<void*>(pipeline.pipeline_layout));
	result = graphics.device.createComputePipelines(nullptr, 1, &info, nullptr, &pipeline.pipeline);
	LOGF("PipelineTrace: vkCreateComputePipelines done result=%s pipeline=%p\n",
	     VulkanToString(result).c_str(), static_cast<void*>(pipeline.pipeline));
	EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);

	EXIT_NOT_IMPLEMENTED(pipeline.pipeline == nullptr);

	graphics.device.destroyShaderModule(comp_shader_module, nullptr);
}

} // namespace Libs::Graphics
