#include "graphics/shader/shader.h" #include "common/assert.h" #include "common/common.h" #include "common/emulatorConfig.h" #include "common/file.h" #include "common/hash.h" #include "common/logging/log.h" #include "common/magicEnum.h" #include "common/profiler.h" #include "common/stringUtils.h" #include "graphics/guest_gpu/gpu_defs.h" #include "graphics/guest_gpu/graphicsRun.h" #include "graphics/guest_gpu/hardwareContext.h" #include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/shader/recompiler/ShaderRecompiler.h" #include "graphics/shader/recompiler/decompiler/ShaderDecoder.h" #include "graphics/shader/shaderVertexMetadata.h" #include "libs/errno.h" #include "spirv-tools/libspirv.h" #include "spirv-tools/libspirv.hpp" #include "spirv-tools/optimizer.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #ifndef NOMINMAX #define NOMINMAX #endif #include #undef min #undef max #endif namespace Libs::Graphics { namespace { constexpr uint32_t PsInputOffsetMask = 0x0000001fu; constexpr uint32_t PsInputFlatShade = 0x00000400u; } // namespace uint32_t ShaderPixelParameterMappedLocation(const ShaderPixelInputInfo& info, uint32_t input) { return input < info.input_num ? info.interpolator_settings[input] & PsInputOffsetMask : input; } uint32_t ShaderPixelParameterLocation(const ShaderPixelInputInfo& info, std::span active_inputs, uint32_t input) { std::array used_locations {}; for (const auto active_input: active_inputs) { auto location = ShaderPixelParameterMappedLocation(info, active_input); if (location < used_locations.size() && used_locations[location]) { location = active_input; while (location < used_locations.size() && used_locations[location]) { location++; } EXIT_NOT_IMPLEMENTED(location >= used_locations.size()); } if (active_input == input) { return location; } used_locations[location] = true; } return ShaderPixelParameterMappedLocation(info, input); } bool ShaderPixelParameterIsFlat(const ShaderPixelInputInfo& info, uint32_t input) { return input < info.input_num && (info.interpolator_settings[input] & PsInputFlatShade) != 0; } struct ShaderBinaryInfo { uint8_t signature[7]; uint8_t version; uint32_t pssl_or_cg : 1; uint32_t cached : 1; uint32_t type : 4; uint32_t source_type : 2; uint32_t length : 24; uint8_t chunk_usage_base_offset_dw; uint8_t num_input_usage_slots; uint8_t is_srt : 1; uint8_t is_srt_used_info_valid : 1; uint8_t is_extended_usage_info : 1; uint8_t reserved2 : 5; uint8_t reserved3; uint32_t hash0; uint32_t hash1; uint32_t crc32; }; static std::unique_ptr> g_shader_map; static std::mutex g_shader_map_mutex; struct ShaderStageProgramKey { ShaderType stage = ShaderType::Unknown; ShaderLaneMaskMode lane_mask_mode = ShaderLaneMaskMode::NativeWave; uint64_t shader_hash = 0; ShaderId program_id; Config::ShaderOptimizationType optimization_type = Config::ShaderOptimizationType::None; bool validation = false; bool operator==(const ShaderStageProgramKey& other) const { return stage == other.stage && lane_mask_mode == other.lane_mask_mode && shader_hash == other.shader_hash && program_id == other.program_id && optimization_type == other.optimization_type && validation == other.validation; } }; struct ShaderStageProgramKeyHash { size_t operator()(const ShaderStageProgramKey& key) const { uint32_t hash = 0; auto mix = [&hash](uint32_t value) { hash ^= Common::hash32(value) + 0x9e3779b9u + (hash << 6u) + (hash >> 2u); }; mix(static_cast(key.stage)); mix(static_cast(key.lane_mask_mode)); mix(Common::hash64(key.shader_hash)); mix(key.program_id.hash0); mix(key.program_id.crc32); mix(static_cast(key.program_id.ids.size())); for (auto value: key.program_id.ids) { mix(value); } mix(static_cast(key.optimization_type)); mix(key.validation ? 1u : 0u); return hash; } }; struct ShaderProgramPermutation { std::vector spirv; std::shared_ptr program; }; static std::unordered_map>, ShaderStageProgramKeyHash> g_shader_program_cache; static std::mutex g_shader_program_cache_mutex; static constexpr uint32_t ShaderMaxPermutationsPerProgram = 64; static std::span MakeShaderSpirvView(const std::vector& spirv) { return {spirv.data(), spirv.size()}; } void ShaderInit() { EXIT_IF(g_shader_map != nullptr); g_shader_map = std::make_unique>(); } void ShaderMapUserData(uint64_t addr, const ShaderMappedData& data) { EXIT_IF(g_shader_map == nullptr); std::scoped_lock lock(g_shader_map_mutex); (*g_shader_map)[addr] = data; } static bool ShaderGetMappedData(uint64_t addr, ShaderMappedData& data) { EXIT_IF(g_shader_map == nullptr); std::scoped_lock lock(g_shader_map_mutex); if (auto iter = g_shader_map->find(addr); iter != g_shader_map->end()) { data = iter->second; return true; } return false; } static bool SpirvDisassemble(const uint32_t* src_binary, size_t src_binary_size, std::string* dst_disassembly) { if (dst_disassembly != nullptr) { spvtools::SpirvTools core(SPV_ENV_VULKAN_1_2); std::string disassembly; if (!core.Disassemble(src_binary, src_binary_size, &disassembly, static_cast(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER) | static_cast(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) | static_cast(SPV_BINARY_TO_TEXT_OPTION_COMMENT) | static_cast(SPV_BINARY_TO_TEXT_OPTION_INDENT) | static_cast(SPV_BINARY_TO_TEXT_OPTION_COLOR))) { *dst_disassembly = disassembly.c_str(); LOGF("Disassemble failed\n"); return false; } *dst_disassembly = disassembly.c_str(); } return true; } static bool SpirvValidateBinary(const char* label, uint64_t shader_hash, const std::vector& spirv) { if (!Config::ShaderValidationEnabled()) { return true; } spvtools::SpirvTools core(SPV_ENV_VULKAN_1_3); std::string messages; core.SetMessageConsumer([&messages](spv_message_level_t /*level*/, const char* /*source*/, const spv_position_t& position, const char* message) { messages += fmt::format("{}: {} ({}) {}\n", static_cast(position.line), static_cast(position.column), static_cast(position.index), message); }); if (core.Validate(spirv)) { return true; } std::string disassembly; SpirvDisassemble(spirv.data(), spirv.size(), &disassembly); LOGF_COLOR(Log::Color::BrightRed, "%s SPIR-V validation failed hash=0x%016" PRIx64 ":\n%s", label, shader_hash, messages.c_str()); LOGF("%s\n", disassembly.c_str()); return false; } static void ExitShaderRecompilerFailure(const char* label, uint64_t shader_hash, const char* reason) { EXIT("%s failed hash=0x%016" PRIx64 ": %s\n", label, shader_hash, reason != nullptr ? reason : ""); } static const ShaderBinaryInfo* GetBinaryInfo(const uint32_t* code) { EXIT_IF(code == nullptr); if (code[0] == 0xBEEB03FF) { return reinterpret_cast(code + static_cast(code[1] + 1) * 2); } return nullptr; } static std::span ShaderGetMappedCode(uint64_t shader_addr, const char* label, uint64_t shader_hash) { ShaderMappedData data; if (!ShaderGetMappedData(shader_addr, data)) { EXIT("%s hash=0x%016" PRIx64 " shader=0x%016" PRIx64 " is missing from ShaderMap\n", label, shader_hash, shader_addr); } if (data.code_size_bytes == 0 || data.code_size_bytes % sizeof(uint32_t) != 0) { EXIT("%s hash=0x%016" PRIx64 " shader=0x%016" PRIx64 " has invalid AGC shader_size=0x%08" PRIx32 "\n", label, shader_hash, shader_addr, data.code_size_bytes); } const auto code_words = data.code_size_bytes / sizeof(uint32_t); LOGF("%s hash=0x%016" PRIx64 " shader=0x%016" PRIx64 " using AGC shader_size=0x%08" PRIx32 " (%" PRIu32 " dwords)\n", label, shader_hash, shader_addr, data.code_size_bytes, code_words); return {reinterpret_cast(shader_addr), code_words}; } #if 0 // Kept as disabled debugging guards for investigating unusual stage register state. static void vs_check(const HW::VertexShaderInfo& vs, const HW::ShaderRegisters& sh) { const auto is_zero_or_wave64_subgroup = [](uint32_t value) { return value == 0 || value <= 0x40; }; const auto is_known_gs_out_prim_type = [](uint32_t value) { switch (static_cast(value)) { case Prospero::GsOutputPrimitiveType::kPoints: case Prospero::GsOutputPrimitiveType::kLines: case Prospero::GsOutputPrimitiveType::kTriangles: case Prospero::GsOutputPrimitiveType::k2dRectangle: case Prospero::GsOutputPrimitiveType::kRectList: return true; } return false; }; const bool ps5_ngg_passthrough_triangle_path = vs.es_regs.data_addr != 0 && vs.gs_regs.data_addr == vs.es_regs.data_addr && vs.gs_regs.chksum != 0 && sh.m_geNggSubgrpCntl == 0x00000001 && sh.m_vgtGsMaxVertOut == 0x00000003 && sh.m_vgtGsOutPrimType == 0x00000002 && sh.m_geMaxOutputPerSubgroup <= 0x000000c0; if (vs.es_regs.data_addr != 0 || vs.gs_regs.data_addr != 0) { EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.priority != 0); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.float_mode != 192); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.dx10_clamp != true); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.debug_mode != false); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.ieee_mode != false); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.cu_group_enable != false); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.require_forward_progress != false); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.threadgroup_configuration != false); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.gs_vgpr_component_count != 3); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.fp16_overflow != false); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc2.scratch_en != false); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc2.offchip_lds != false); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc2.es_vgpr_component_count != 3); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc2.lds_size != 0); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc2.shared_vgprs != 0); } for (uint32_t value = sh.m_spiShaderPosFormat; value != 0; value >>= 4u) { EXIT_NOT_IMPLEMENTED((value & 0xfu) != 0 && (value & 0xfu) != 0x4u); } if (sh.m_paClVsOutCntl != 0x00000000) { static bool logged = false; if (!logged) { LOGF("\t temporary: accepting PA_CL_VS_OUT_CNTL = 0x%08" PRIx32 "\n", sh.m_paClVsOutCntl); logged = true; } } EXIT_NOT_IMPLEMENTED(sh.m_spiShaderIdxFormat != 0x00000000 && sh.m_spiShaderIdxFormat != 0x00000001); EXIT_NOT_IMPLEMENTED(sh.m_geNggSubgrpCntl != 0x00000000 && sh.m_geNggSubgrpCntl != 0x00000001); EXIT_NOT_IMPLEMENTED(sh.m_vgtGsInstanceCnt != 0x00000000); EXIT_NOT_IMPLEMENTED(!is_zero_or_wave64_subgroup(sh.GetEsVertsPerSubgrp())); EXIT_NOT_IMPLEMENTED(!is_zero_or_wave64_subgroup(sh.GetGsPrimsPerSubgrp())); EXIT_NOT_IMPLEMENTED(!is_zero_or_wave64_subgroup(sh.GetGsInstPrimsInSubgrp())); EXIT_NOT_IMPLEMENTED(!is_zero_or_wave64_subgroup(sh.m_geMaxOutputPerSubgroup) && !ps5_ngg_passthrough_triangle_path); EXIT_NOT_IMPLEMENTED(sh.m_vgtEsgsRingItemsize != 0x00000000 && sh.m_vgtEsgsRingItemsize != 0x00000004); EXIT_NOT_IMPLEMENTED(sh.m_vgtGsMaxVertOut != 0x00000000 && !ps5_ngg_passthrough_triangle_path); EXIT_NOT_IMPLEMENTED(!is_known_gs_out_prim_type(sh.m_vgtGsOutPrimType)); } static void ps_check(const HW::PsStageRegisters& ps, const HW::ShaderRegisters& sh) { if (sh.target_output_mode[0] != 0 && sh.target_output_mode[0] != 2 && sh.target_output_mode[0] != 4 && sh.target_output_mode[0] != 5 && sh.target_output_mode[0] != 7 && sh.target_output_mode[0] != 9) { EXIT("Not implemented (sh.target_output_mode[0] != 0 && sh.target_output_mode[0] != 2 && " "sh.target_output_mode[0] != 4 && sh.target_output_mode[0] != 5 && " "sh.target_output_mode[0] != 7 && sh.target_output_mode[0] != 9)\n"); } EXIT_NOT_IMPLEMENTED(sh.db_shader_control.conservative_z_export_value != 0x00000000); EXIT_NOT_IMPLEMENTED(sh.db_shader_control.shader_z_behavior != 0x00000001 && sh.db_shader_control.shader_z_behavior != 0x00000000); // EXIT_NOT_IMPLEMENTED(ps.shader_kill_enable != false); // EXIT_NOT_IMPLEMENTED(ps.shader_execute_on_noop != false); // EXIT_NOT_IMPLEMENTED(ps.m_spiShaderPgmRsrc1Ps != 0x002c0000); // EXIT_NOT_IMPLEMENTED(ps.m_spiShaderPgmRsrc2Ps != 0x00000000); // EXIT_NOT_IMPLEMENTED(ps.vgprs != 0x00 && ps.vgprs != 0x01); // EXIT_NOT_IMPLEMENTED(ps.sgprs != 0x00 && ps.sgprs != 0x01); EXIT_NOT_IMPLEMENTED(ps.rsrc1.priority != 0); EXIT_NOT_IMPLEMENTED(ps.rsrc1.float_mode != 192); EXIT_NOT_IMPLEMENTED(ps.rsrc1.dx10_clamp != true); EXIT_NOT_IMPLEMENTED(ps.rsrc1.debug_mode != false); EXIT_NOT_IMPLEMENTED(ps.rsrc1.ieee_mode != false); EXIT_NOT_IMPLEMENTED(ps.rsrc1.cu_group_disable != false); EXIT_NOT_IMPLEMENTED(ps.rsrc1.require_forward_progress != false); EXIT_NOT_IMPLEMENTED(ps.rsrc1.fp16_overflow != false); EXIT_NOT_IMPLEMENTED(ps.rsrc2.scratch_en != false); // EXIT_NOT_IMPLEMENTED(ps.user_sgpr != 0 && ps.user_sgpr != 4 && ps.user_sgpr != 12); EXIT_NOT_IMPLEMENTED(ps.rsrc2.wave_cnt_en != false); if (ps.rsrc2.extra_lds_size != 0) { static std::atomic_uint log_count {0}; if (log_count.fetch_add(1, std::memory_order_relaxed) < 32) { LOGF("\t PS extra LDS reservation = 0x%02" PRIx8 ", continuing\n", ps.rsrc2.extra_lds_size); } } EXIT_NOT_IMPLEMENTED(ps.rsrc2.raster_ordered_shading != 0); EXIT_NOT_IMPLEMENTED(ps.rsrc2.shared_vgprs != 0); if (sh.shader_z_format != 0x00000000 && sh.shader_z_format != 0x00000001 && !sh.db_shader_control.shader_z_export_enable) { static std::atomic_uint log_count {0}; if (log_count.fetch_add(1, std::memory_order_relaxed) < 32) { LOGF("\t shader_z_format = 0x%08" PRIx32 " with z export disabled, ignoring depth export format\n", sh.shader_z_format); } } EXIT_NOT_IMPLEMENTED(sh.db_shader_control.shader_z_export_enable && sh.shader_z_format != 0x00000000 && sh.shader_z_format != 0x00000001); constexpr uint32_t ps_input_linear_center = 0x00000020u; constexpr uint32_t ps_input_pos_w = 0x00000800u; constexpr uint32_t ps_input_front_face = 0x00001000u; constexpr uint32_t supported_ps_input_bits = 0x00000702u | ps_input_linear_center | ps_input_pos_w | ps_input_front_face; EXIT_NOT_IMPLEMENTED((sh.ps_input_ena & ~supported_ps_input_bits) != 0); EXIT_NOT_IMPLEMENTED((sh.ps_input_addr & ~supported_ps_input_bits) != 0); EXIT_NOT_IMPLEMENTED(sh.ps_input_ena != sh.ps_input_addr); // EXIT_NOT_IMPLEMENTED(ps.m_spiPsInControl != 0x00000000); constexpr uint32_t baryc_persp_mask = 0x00000003u | 0x00000030u | 0x00000300u | 0x00003000u; constexpr uint32_t baryc_linear_mask = 0x00030000u | 0x00300000u | 0x03000000u; constexpr uint32_t baryc_known_mask = baryc_persp_mask | baryc_linear_mask; EXIT_NOT_IMPLEMENTED((sh.baryc_cntl & ~baryc_known_mask) != 0); EXIT_NOT_IMPLEMENTED((sh.baryc_cntl & baryc_persp_mask) != 0); if ((sh.ps_input_ena & ps_input_linear_center) == 0 && (sh.baryc_cntl & baryc_linear_mask) != 0) { static std::atomic_uint log_count {0}; if (log_count.fetch_add(1, std::memory_order_relaxed) < 32) { LOGF("\t ignoring inactive linear SPI_BARYC_CNTL bits: 0x%08" PRIx32 "\n", sh.baryc_cntl & baryc_linear_mask); } } else { EXIT_NOT_IMPLEMENTED((sh.baryc_cntl & baryc_linear_mask) != 0x00000000 && (sh.baryc_cntl & baryc_linear_mask) != 0x01000000); } if ((sh.m_cbShaderMask & 0x0000000f) != 0x0000000f) { static bool logged = false; if (!logged) { LOGF("\t temporary: accepting partial CB_SHADER_MASK = 0x%08" PRIx32 "\n", sh.m_cbShaderMask); logged = true; } } if ((sh.m_cbShaderMask & ~0x0000000fu) != 0) { static bool logged = false; if (!logged) { LOGF("\t temporary: ignoring extra CB_SHADER_MASK MRT bits: 0x%08" PRIx32 "\n", sh.m_cbShaderMask); logged = true; } } if (sh.db_shader_control.other_bits != 0x00000000) { static std::atomic_uint log_count {0}; if (log_count.fetch_add(1, std::memory_order_relaxed) < 32) { LOGF("\t temporary: ignoring unsupported DB_SHADER_CONTROL bits 0x%08" PRIx32 "\n", sh.db_shader_control.other_bits); } } EXIT_NOT_IMPLEMENTED(sh.m_paScShaderControl != 0x00000000); } static void cs_check(const HW::CsStageRegisters& cs, const HW::ShaderRegisters& /*sh*/) { // EXIT_NOT_IMPLEMENTED(cs.num_thread_x != 0x00000040); // EXIT_NOT_IMPLEMENTED(cs.num_thread_y != 0x00000001); // EXIT_NOT_IMPLEMENTED(cs.num_thread_z != 0x00000001); // EXIT_NOT_IMPLEMENTED(cs.vgprs != 0x00 && cs.vgprs != 0x01); // EXIT_NOT_IMPLEMENTED(cs.sgprs != 0x01 && cs.sgprs != 0x02); EXIT_NOT_IMPLEMENTED(cs.bulky != 0x00); EXIT_NOT_IMPLEMENTED(cs.scratch_en != 0x00); // EXIT_NOT_IMPLEMENTED(cs.user_sgpr != 0x0c); if (cs.tgid_x_en == 0x00) { static bool logged = false; if (!logged) { LOGF("\t temporary: compute shader has TGID X disabled\n"); logged = true; } } else { EXIT_NOT_IMPLEMENTED(cs.tgid_x_en != 0x01); } // EXIT_NOT_IMPLEMENTED(cs.tgid_y_en != 0x00); // EXIT_NOT_IMPLEMENTED(cs.tgid_z_en != 0x00); EXIT_NOT_IMPLEMENTED(cs.tg_size_en != 0x00); EXIT_NOT_IMPLEMENTED(cs.tidig_comp_cnt > 2); // EXIT_NOT_IMPLEMENTED(cs.m_computePgmRsrc1 != 0x002c0040); // EXIT_NOT_IMPLEMENTED(cs.m_computePgmRsrc2 != 0x00000098); // EXIT_NOT_IMPLEMENTED(cs.m_computeNumThreadX != 0x00000040); // EXIT_NOT_IMPLEMENTED(cs.m_computeNumThreadY != 0x00000001); // EXIT_NOT_IMPLEMENTED(cs.m_computeNumThreadZ != 0x00000001); } #endif static void GetNextGenFallbackShaderId(uint64_t addr, uint32_t& hash0, uint32_t& crc32) { auto x = addr; x ^= x >> 33u; x *= 0xff51afd7ed558ccdull; x ^= x >> 33u; x *= 0xc4ceb9fe1a85ec53ull; x ^= x >> 33u; hash0 = static_cast((x >> 32u) & 0xffffffffu); crc32 = static_cast(x & 0xffffffffu); } static void ShaderDetectBuffers(ShaderVertexInputInfo& info) { KYTY_PROFILER_FUNCTION(); info.buffers_num = 0; for (int ri = 0; ri < info.resources_num; ri++) { const auto& r = info.resources[ri]; bool merged = false; for (int bi = 0; bi < info.buffers_num; bi++) { auto& b = info.buffers[bi]; uint64_t stride = b.stride; if (stride == r.Stride() && b.fetch_index == static_cast(info.resources_dst[ri].fetch_index)) { uint64_t rbase = r.Base48(); uint64_t base = std::min(rbase, b.addr); uint64_t offset1 = rbase - base; uint64_t offset2 = b.addr - base; if (offset1 < stride && offset2 < stride) { EXIT_NOT_IMPLEMENTED(b.num_records != r.NumRecords()); b.addr = base; EXIT_NOT_IMPLEMENTED(b.attr_num >= ShaderVertexInputBuffer::ATTR_MAX); b.attr_indices[b.attr_num++] = ri; merged = true; break; } } } if (!merged) { EXIT_NOT_IMPLEMENTED(info.buffers_num >= ShaderVertexInputInfo::RES_MAX); int bi = info.buffers_num++; info.buffers[bi].addr = r.Base48(); info.buffers[bi].stride = r.Stride(); info.buffers[bi].num_records = r.NumRecords(); info.buffers[bi].fetch_index = info.resources_dst[ri].fetch_index; info.buffers[bi].attr_num = 1; info.buffers[bi].attr_indices[0] = ri; } } for (int bi = 0; bi < info.buffers_num; bi++) { auto& b = info.buffers[bi]; for (int ri = 0; ri < b.attr_num; ri++) { b.attr_offsets[ri] = info.resources[b.attr_indices[ri]].Base48() - b.addr; } } } static uint32_t VertexAttribFormatToBufferFormat(uint32_t format) { struct FormatMap { Prospero::VertexAttribFormat vertex; Prospero::BufferFormat buffer; }; static constexpr FormatMap format_map[] = { {Prospero::VertexAttribFormat::kInvalid, Prospero::BufferFormat::kInvalid}, {Prospero::VertexAttribFormat::k8UNorm, Prospero::BufferFormat::k8UNorm}, {Prospero::VertexAttribFormat::k8SNorm, Prospero::BufferFormat::k8SNorm}, {Prospero::VertexAttribFormat::k8UScaled, Prospero::BufferFormat::k8UScaled}, {Prospero::VertexAttribFormat::k8SScaled, Prospero::BufferFormat::k8SScaled}, {Prospero::VertexAttribFormat::k8UInt, Prospero::BufferFormat::k8UInt}, {Prospero::VertexAttribFormat::k8SInt, Prospero::BufferFormat::k8SInt}, {Prospero::VertexAttribFormat::k16UNorm, Prospero::BufferFormat::k16UNorm}, {Prospero::VertexAttribFormat::k16SNorm, Prospero::BufferFormat::k16SNorm}, {Prospero::VertexAttribFormat::k16UScaled, Prospero::BufferFormat::k16UScaled}, {Prospero::VertexAttribFormat::k16SScaled, Prospero::BufferFormat::k16SScaled}, {Prospero::VertexAttribFormat::k16UInt, Prospero::BufferFormat::k16UInt}, {Prospero::VertexAttribFormat::k16SInt, Prospero::BufferFormat::k16SInt}, {Prospero::VertexAttribFormat::k16Float, Prospero::BufferFormat::k16Float}, {Prospero::VertexAttribFormat::k8_8UNorm, Prospero::BufferFormat::k8_8UNorm}, {Prospero::VertexAttribFormat::k8_8SNorm, Prospero::BufferFormat::k8_8SNorm}, {Prospero::VertexAttribFormat::k8_8UScaled, Prospero::BufferFormat::k8_8UScaled}, {Prospero::VertexAttribFormat::k8_8SScaled, Prospero::BufferFormat::k8_8SScaled}, {Prospero::VertexAttribFormat::k8_8UInt, Prospero::BufferFormat::k8_8UInt}, {Prospero::VertexAttribFormat::k8_8SInt, Prospero::BufferFormat::k8_8SInt}, {Prospero::VertexAttribFormat::k32UInt, Prospero::BufferFormat::k32UInt}, {Prospero::VertexAttribFormat::k32SInt, Prospero::BufferFormat::k32SInt}, {Prospero::VertexAttribFormat::k32Float, Prospero::BufferFormat::k32Float}, {Prospero::VertexAttribFormat::k16_16UNorm, Prospero::BufferFormat::k16_16UNorm}, {Prospero::VertexAttribFormat::k16_16SNorm, Prospero::BufferFormat::k16_16SNorm}, {Prospero::VertexAttribFormat::k16_16UScaled, Prospero::BufferFormat::k16_16UScaled}, {Prospero::VertexAttribFormat::k16_16SScaled, Prospero::BufferFormat::k16_16SScaled}, {Prospero::VertexAttribFormat::k16_16UInt, Prospero::BufferFormat::k16_16UInt}, {Prospero::VertexAttribFormat::k16_16SInt, Prospero::BufferFormat::k16_16SInt}, {Prospero::VertexAttribFormat::k16_16Float, Prospero::BufferFormat::k16_16Float}, {Prospero::VertexAttribFormat::k11_11_10UNorm, Prospero::BufferFormat::k11_11_10UNorm}, {Prospero::VertexAttribFormat::k11_11_10SNorm, Prospero::BufferFormat::k11_11_10SNorm}, {Prospero::VertexAttribFormat::k11_11_10UScaled, Prospero::BufferFormat::k11_11_10UScaled}, {Prospero::VertexAttribFormat::k11_11_10SScaled, Prospero::BufferFormat::k11_11_10SScaled}, {Prospero::VertexAttribFormat::k11_11_10UInt, Prospero::BufferFormat::k11_11_10UInt}, {Prospero::VertexAttribFormat::k11_11_10SInt, Prospero::BufferFormat::k11_11_10SInt}, {Prospero::VertexAttribFormat::k11_11_10Float, Prospero::BufferFormat::k11_11_10Float}, {Prospero::VertexAttribFormat::k10_11_11UNorm, Prospero::BufferFormat::k10_11_11UNorm}, {Prospero::VertexAttribFormat::k10_11_11SNorm, Prospero::BufferFormat::k10_11_11SNorm}, {Prospero::VertexAttribFormat::k10_11_11UScaled, Prospero::BufferFormat::k10_11_11UScaled}, {Prospero::VertexAttribFormat::k10_11_11SScaled, Prospero::BufferFormat::k10_11_11SScaled}, {Prospero::VertexAttribFormat::k10_11_11UInt, Prospero::BufferFormat::k10_11_11UInt}, {Prospero::VertexAttribFormat::k10_11_11SInt, Prospero::BufferFormat::k10_11_11SInt}, {Prospero::VertexAttribFormat::k10_11_11Float, Prospero::BufferFormat::k10_11_11Float}, {Prospero::VertexAttribFormat::k2_10_10_10UNorm, Prospero::BufferFormat::k2_10_10_10UNorm}, {Prospero::VertexAttribFormat::k2_10_10_10SNorm, Prospero::BufferFormat::k2_10_10_10SNorm}, {Prospero::VertexAttribFormat::k2_10_10_10UScaled, Prospero::BufferFormat::k2_10_10_10UScaled}, {Prospero::VertexAttribFormat::k2_10_10_10SScaled, Prospero::BufferFormat::k2_10_10_10SScaled}, {Prospero::VertexAttribFormat::k2_10_10_10UInt, Prospero::BufferFormat::k2_10_10_10UInt}, {Prospero::VertexAttribFormat::k2_10_10_10SInt, Prospero::BufferFormat::k2_10_10_10SInt}, {Prospero::VertexAttribFormat::k10_10_10_2UNorm, Prospero::BufferFormat::k10_10_10_2UNorm}, {Prospero::VertexAttribFormat::k10_10_10_2SNorm, Prospero::BufferFormat::k10_10_10_2SNorm}, {Prospero::VertexAttribFormat::k10_10_10_2UScaled, Prospero::BufferFormat::k10_10_10_2UScaled}, {Prospero::VertexAttribFormat::k10_10_10_2SScaled, Prospero::BufferFormat::k10_10_10_2SScaled}, {Prospero::VertexAttribFormat::k10_10_10_2UInt, Prospero::BufferFormat::k10_10_10_2UInt}, {Prospero::VertexAttribFormat::k10_10_10_2SInt, Prospero::BufferFormat::k10_10_10_2SInt}, {Prospero::VertexAttribFormat::k8_8_8_8UNorm, Prospero::BufferFormat::k8_8_8_8UNorm}, {Prospero::VertexAttribFormat::k8_8_8_8SNorm, Prospero::BufferFormat::k8_8_8_8SNorm}, {Prospero::VertexAttribFormat::k8_8_8_8UScaled, Prospero::BufferFormat::k8_8_8_8UScaled}, {Prospero::VertexAttribFormat::k8_8_8_8SScaled, Prospero::BufferFormat::k8_8_8_8SScaled}, {Prospero::VertexAttribFormat::k8_8_8_8UInt, Prospero::BufferFormat::k8_8_8_8UInt}, {Prospero::VertexAttribFormat::k8_8_8_8SInt, Prospero::BufferFormat::k8_8_8_8SInt}, {Prospero::VertexAttribFormat::k32_32UInt, Prospero::BufferFormat::k32_32UInt}, {Prospero::VertexAttribFormat::k32_32SInt, Prospero::BufferFormat::k32_32SInt}, {Prospero::VertexAttribFormat::k32_32Float, Prospero::BufferFormat::k32_32Float}, {Prospero::VertexAttribFormat::k16_16_16_16UNorm, Prospero::BufferFormat::k16_16_16_16UNorm}, {Prospero::VertexAttribFormat::k16_16_16_16SNorm, Prospero::BufferFormat::k16_16_16_16SNorm}, {Prospero::VertexAttribFormat::k16_16_16_16UScaled, Prospero::BufferFormat::k16_16_16_16UScaled}, {Prospero::VertexAttribFormat::k16_16_16_16SScaled, Prospero::BufferFormat::k16_16_16_16SScaled}, {Prospero::VertexAttribFormat::k16_16_16_16UInt, Prospero::BufferFormat::k16_16_16_16UInt}, {Prospero::VertexAttribFormat::k16_16_16_16SInt, Prospero::BufferFormat::k16_16_16_16SInt}, {Prospero::VertexAttribFormat::k16_16_16_16Float, Prospero::BufferFormat::k16_16_16_16Float}, {Prospero::VertexAttribFormat::k32_32_32UInt, Prospero::BufferFormat::k32_32_32UInt}, {Prospero::VertexAttribFormat::k32_32_32SInt, Prospero::BufferFormat::k32_32_32SInt}, {Prospero::VertexAttribFormat::k32_32_32Float, Prospero::BufferFormat::k32_32_32Float}, {Prospero::VertexAttribFormat::k32_32_32_32UInt, Prospero::BufferFormat::k32_32_32_32UInt}, {Prospero::VertexAttribFormat::k32_32_32_32SInt, Prospero::BufferFormat::k32_32_32_32SInt}, {Prospero::VertexAttribFormat::k32_32_32_32Float, Prospero::BufferFormat::k32_32_32_32Float}, }; for (const auto& entry: format_map) { if (format == Prospero::GpuEnumValue(entry.vertex)) { return Prospero::GpuEnumValue(entry.buffer); } } return format; } static void ShaderApplyAttribSemantics(ShaderVertexInputInfo& info, const ShaderSemantic* input_semantics, uint32_t num_input_semantics, const uint32_t* attrib, const uint32_t* buffer) { KYTY_PROFILER_FUNCTION(); EXIT_IF(attrib == nullptr || buffer == nullptr); for (uint32_t i = 0; i < num_input_semantics; i++) { const auto& in = input_semantics[i]; EXIT_NOT_IMPLEMENTED(in.static_vb_index == 1 || in.static_attribute == 1); uint32_t reg = in.hardware_mapping; uint32_t size = in.size_in_elements; LOGF("reg = %u, size = %u, va[%u] = 0x%08" PRIx32 "\n", reg, size, i, attrib[in.semantic]); size_t index = attrib[in.semantic] & 0x1fu; uint32_t format = (attrib[in.semantic] >> 5u) & 0x1ffu; uint32_t offset = (attrib[in.semantic] >> 14u) & 0xfffu; uint32_t fetch_index = (attrib[in.semantic] >> 26u) & 0x1u; if (fetch_index != 0) { static std::atomic log_count = 0; auto log_id = log_count.fetch_add(1); if (log_id < 64) { LOGF("\t temporary: PS5 vertex attrib semantic %u uses fetch index %u, buffer " "index %zu\n", static_cast(in.semantic), fetch_index, index); } } EXIT_NOT_IMPLEMENTED(index >= ShaderVertexInputInfo::RES_MAX); const auto* sharp = &buffer[index * 4]; EXIT_NOT_IMPLEMENTED(info.resources_num >= ShaderVertexInputInfo::RES_MAX); auto& r = info.resources[info.resources_num]; auto& rd = info.resources_dst[info.resources_num]; rd.register_start = static_cast(reg); rd.registers_num = static_cast(size); rd.attr_id = static_cast(in.semantic); rd.fetch_index = fetch_index; r.fields[0] = sharp[0]; r.fields[1] = sharp[1]; r.fields[2] = sharp[2]; r.fields[3] = sharp[3]; if (format != 0) { auto buffer_format = VertexAttribFormatToBufferFormat(format); static std::atomic log_count = 0; auto log_id = log_count.fetch_add(1); if (log_id < 64) { LOGF("\t temporary: PS5 vertex attrib semantic %u uses attrib format %u -> buffer " "format %u, offset %u, buffer index %zu\n", static_cast(in.semantic), format, buffer_format, offset, index); } r.fields[3] = (r.fields[3] & ~((0x7fu << 12u) | 0xfffu)) | ((buffer_format & 0x7fu) << 12u) | DstSel(4, 5, 6, 7); } if (offset != 0) { r.UpdateAddress48(r.Base48() + offset); } info.resources_num++; } } static uint32_t ShaderCalcPsSystemInputBase(const HW::ShaderRegisters& regs) { constexpr uint32_t ps_input_persp_sample = 0x00000001u; constexpr uint32_t ps_input_persp_center = 0x00000002u; constexpr uint32_t ps_input_persp_centroid = 0x00000004u; constexpr uint32_t ps_input_persp_pull = 0x00000008u; constexpr uint32_t ps_input_linear_sample = 0x00000010u; constexpr uint32_t ps_input_linear_center = 0x00000020u; constexpr uint32_t ps_input_linear_centroid = 0x00000040u; constexpr uint32_t ps_input_line_stipple = 0x00000080u; constexpr uint32_t ps_input_pos_xy = 0x00000300u; constexpr uint32_t ps_input_pos_z = 0x00000400u; constexpr uint32_t ps_input_pos_w = 0x00000800u; constexpr uint32_t ps_input_front_face = 0x00001000u; constexpr uint32_t ps_input_ancillary = 0x00002000u; constexpr uint32_t ps_input_sample_coverage = 0x00004000u; constexpr uint32_t ps_input_pos_fixed_pt = 0x00008000u; constexpr uint32_t supported_ps_input_bits = ps_input_persp_sample | ps_input_persp_center | ps_input_persp_centroid | ps_input_persp_pull | ps_input_linear_sample | ps_input_linear_center | ps_input_linear_centroid | ps_input_line_stipple | ps_input_pos_xy | ps_input_pos_z | ps_input_pos_w | ps_input_front_face | ps_input_ancillary | ps_input_sample_coverage | ps_input_pos_fixed_pt; EXIT_NOT_IMPLEMENTED((regs.ps_input_ena & ~supported_ps_input_bits) != 0); EXIT_NOT_IMPLEMENTED((regs.ps_input_addr & ~supported_ps_input_bits) != 0); EXIT_NOT_IMPLEMENTED(regs.ps_input_ena != regs.ps_input_addr); const uint32_t inputs = regs.ps_input_addr; uint32_t reg = 0; if ((inputs & ps_input_persp_sample) != 0) { reg += 2; } if ((inputs & ps_input_persp_center) != 0) { reg += 2; } if ((inputs & ps_input_persp_centroid) != 0) { reg += 2; } if ((inputs & ps_input_persp_pull) != 0) { reg += 3; } if ((inputs & ps_input_linear_sample) != 0) { reg += 2; } if ((inputs & ps_input_linear_center) != 0) { reg += 2; } if ((inputs & ps_input_linear_centroid) != 0) { reg += 2; } if ((inputs & ps_input_line_stipple) != 0) { reg += 1; } return reg; } static bool ShaderGetStaticInputInfoVS(const HW::VertexShaderInfo& regs, const HW::ShaderRegisters& sh, ShaderVertexInputInfo& info) { KYTY_PROFILER_FUNCTION(); info = {}; info.export_count = static_cast(sh.GetExportCount()); EXIT_NOT_IMPLEMENTED(regs.es_regs.data_addr == 0 || regs.gs_regs.chksum == 0); uint64_t shader_addr = regs.es_regs.data_addr; const HW::UserSgprInfo& user_sgpr = regs.gs_user_sgpr; auto user_sgpr_num = regs.gs_regs.rsrc2.user_sgpr; ShaderMappedData data; if (!ShaderGetMappedData(shader_addr, data)) { LOGF("ShaderGetInputInfoVS(): shader=0x%016" PRIx64 " is missing from ShaderMap\n", shader_addr); return false; } if (data.user_data == nullptr) { LOGF("ShaderGetInputInfoVS(): no AGC user data for shader=0x%016" PRIx64 " es=0x%016" PRIx64 " gs=0x%016" PRIx64 " chksum=0x%016" PRIx64 " user_sgpr_num=%u\n", shader_addr, regs.es_regs.data_addr, regs.gs_regs.data_addr, regs.gs_regs.chksum, static_cast(user_sgpr_num)); } ShaderVertexMetadata metadata; std::string metadata_error; if (!ShaderReadVertexMetadata(data, HW::UserSgprInfo::SGPRS_MAX, metadata, &metadata_error)) { LOGF("ShaderGetInputInfoVS(): invalid AGC metadata shader=0x%016" PRIx64 ": %s\n", shader_addr, metadata_error.c_str()); return false; } if (metadata.vertex_buffer_reg >= 0) { info.fetch_external = false; info.fetch_embedded = true; info.fetch_attrib_reg = metadata.vertex_attrib_reg; info.fetch_buffer_reg = metadata.vertex_buffer_reg; const auto* attrib = reinterpret_cast( static_cast(user_sgpr.value[metadata.vertex_attrib_reg]) | (static_cast(user_sgpr.value[metadata.vertex_attrib_reg + 1]) << 32u)); const auto* buffer = reinterpret_cast( static_cast(user_sgpr.value[metadata.vertex_buffer_reg]) | (static_cast(user_sgpr.value[metadata.vertex_buffer_reg + 1]) << 32u)); if (attrib == nullptr || buffer == nullptr) { LOGF("ShaderGetInputInfoVS(): null vertex table pointer shader=0x%016" PRIx64 "\n", shader_addr); return false; } ShaderApplyAttribSemantics(info, metadata.input_semantics.data(), metadata.input_semantics_count, attrib, buffer); ShaderDetectBuffers(info); } return true; } static void ShaderGetStaticInputInfoPS( const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh, const ShaderVertexInputInfo& vs_info, std::span target_export_mapping, ShaderPixelInputInfo& ps_info) { KYTY_PROFILER_FUNCTION(); ps_info = {}; // SPI_PS_IN_CONTROL.NUM_INTERP occupies bits 5:0. Keep the remaining control // flags in the hardware state and extract only the input count here. ps_info.input_num = sh.ps_in_control & 0x3fu; ps_info.ps_system_input_base = ShaderCalcPsSystemInputBase(sh); const uint32_t active_inputs = sh.ps_input_ena & sh.ps_input_addr; ps_info.ps_pos_x = (active_inputs & 0x00000100u) != 0; ps_info.ps_pos_y = (active_inputs & 0x00000200u) != 0; ps_info.ps_pos_xy = ps_info.ps_pos_x && ps_info.ps_pos_y; ps_info.ps_pos_z = (active_inputs & 0x00000400u) != 0; ps_info.ps_pos_w = (active_inputs & 0x00000800u) != 0; ps_info.ps_front_face = (active_inputs & 0x00001000u) != 0; ps_info.ps_sample_shading = (active_inputs & 0x00000011u) != 0; ps_info.ps_no_perspective = (sh.ps_input_ena & sh.ps_input_addr & 0x00000020u) != 0; ps_info.ps_pixel_kill_enable = sh.db_shader_control.shader_kill_enable; ps_info.ps_depth_export_enable = sh.db_shader_control.shader_z_export_enable; ps_info.ps_sample_mask_export_enable = sh.db_shader_control.shader_mask_export_enable; ps_info.ps_early_z = (sh.db_shader_control.shader_z_behavior == 1 && !sh.db_shader_control.shader_kill_enable && !sh.db_shader_control.shader_z_export_enable && !sh.db_shader_control.shader_mask_export_enable); ps_info.ps_execute_on_noop = sh.db_shader_control.shader_execute_on_noop; for (uint32_t i = 0; i < ps_info.input_num; i++) { ps_info.interpolator_settings[i] = sh.ps_interpolator_settings[i]; } ps_info.descriptor_set = vs_info.stage.program != nullptr && !vs_info.stage.program->bindings.descriptors.empty() ? 1 : 0; ps_info.push_constant_offset = vs_info.stage.program != nullptr ? vs_info.stage.program->bindings.push_constant_offset + vs_info.stage.program->bindings.push_constant_size : 0; for (int i = 0; i < 8; i++) { ps_info.target_output_mode[i] = sh.target_output_mode[i]; ps_info.target_export_mapping[i] = sh.target_output_mode[i] != 0 ? target_export_mapping[i] : Prospero::ColorComponentMapping {}; } ps_info.mrt_output_mask = 0; } static void ShaderGetStaticInputInfoCS(const HW::ComputeShaderInfo& regs, const HW::ShaderRegisters& /*sh*/, ShaderComputeInputInfo& info) { info = {}; info.threads_num[0] = regs.cs_regs.num_thread_x; info.threads_num[1] = regs.cs_regs.num_thread_y; info.threads_num[2] = regs.cs_regs.num_thread_z; info.group_id[0] = regs.cs_regs.tgid_x_en != 0; info.group_id[1] = regs.cs_regs.tgid_y_en != 0; info.group_id[2] = regs.cs_regs.tgid_z_en != 0; info.wave_size = regs.cs_regs.wave_size; info.thread_ids_num = regs.cs_regs.tidig_comp_cnt + 1; info.tg_size_en = regs.cs_regs.tg_size_en != 0; info.workgroup_register = regs.cs_regs.user_sgpr; } static ShaderStageProgramKey MakeShaderStageProgramKey(ShaderType stage, uint64_t shader_hash, const ShaderId& program_id, ShaderLaneMaskMode lane_mask_mode) { ShaderStageProgramKey key {}; key.stage = stage; key.lane_mask_mode = lane_mask_mode; key.shader_hash = shader_hash; key.program_id = program_id; key.optimization_type = Config::GetShaderOptimizationType(); key.validation = Config::ShaderValidationEnabled(); return key; } static void ApplyVertexOutputs(ShaderVertexInputInfo& info, const ShaderRecompiler::IR::Program& program) { info.export_count = 0; info.param_export_mask = 0; for (const auto& output: program.info.outputs) { if (output.kind == ShaderRecompiler::IR::StageOutputKind::Parameter && output.index < 32) { info.param_export_mask |= 1u << output.index; info.export_count = std::max(info.export_count, static_cast(output.index + 1)); } } } static void ApplyPixelOutputs(ShaderPixelInputInfo& info, const ShaderRecompiler::IR::Program& program) { info.mrt_output_mask = 0; for (const auto& output: program.info.outputs) { if (output.kind == ShaderRecompiler::IR::StageOutputKind::Mrt && output.index < 8) { info.mrt_output_mask |= 1u << output.index; } } } static std::string ShaderDescribeSpecialization(const ShaderRecompiler::IR::Program& program); static bool LogPermutationMismatch(const ShaderProgramPermutation& permutation, const char* stage, uint64_t shader_hash, const std::string& error) { static std::atomic log_count {0}; if (Config::GraphicsDebugDumpEnabled() && log_count.fetch_add(1, std::memory_order_relaxed) < 64) { LOGF("ShaderProgramCache native runtime mismatch %s shader=0x%016" PRIx64 ": %s\n specialization: %s\n", stage, shader_hash, error.c_str(), ShaderDescribeSpecialization(*permutation.program).c_str()); } return false; } static bool TryUseVertexPermutation(const ShaderProgramPermutation& permutation, const HW::VertexShaderInfo& regs, ShaderVertexInputInfo& info, uint64_t shader_hash) { std::string error; if (!ShaderMaterializeStageRuntime( permutation.program, std::span(regs.gs_user_sgpr.value, regs.gs_regs.rsrc2.user_sgpr), regs.es_regs.data_addr, info.stage, &error)) { return LogPermutationMismatch(permutation, "VS", shader_hash, error); } ApplyVertexOutputs(info, *permutation.program); return true; } static bool TryUsePixelPermutation(const ShaderProgramPermutation& permutation, const HW::PixelShaderInfo& regs, ShaderPixelInputInfo& info, uint64_t shader_hash) { std::string error; if (!ShaderMaterializeStageRuntime( permutation.program, std::span(regs.ps_user_sgpr.value, regs.ps_regs.rsrc2.user_sgpr), regs.ps_regs.data_addr, info.stage, &error)) { return LogPermutationMismatch(permutation, "PS", shader_hash, error); } ApplyPixelOutputs(info, *permutation.program); return true; } static bool TryUseComputePermutation(const ShaderProgramPermutation& permutation, const HW::ComputeShaderInfo& regs, ShaderComputeInputInfo& info, uint64_t shader_hash) { std::string error; if (!ShaderMaterializeStageRuntime( permutation.program, std::span(regs.cs_user_sgpr.value, regs.cs_regs.user_sgpr), regs.cs_regs.data_addr, info.stage, &error)) { return LogPermutationMismatch(permutation, "CS", shader_hash, error); } return true; } static void LogShaderProgramCacheHit(const char* stage, uint64_t shader_hash, uint64_t words) { if (!Config::GraphicsDebugDumpEnabled()) { return; } static std::atomic log_count {0}; if (log_count.fetch_add(1, std::memory_order_relaxed) >= 512) { return; } LOGF("ShaderProgramCache: reused %s shader=0x%016" PRIx64 " words=%" PRIu64 "\n", stage, shader_hash, words); } static std::string ShaderDescribeSpecialization(const ShaderRecompiler::IR::Program& program) { std::string ret = fmt::format( "set={} push={} groups={} user={} buffers={} images={} samplers={} addresses={} srt={}", program.bindings.descriptor_set, program.bindings.push_constant_size, program.bindings.descriptors.size(), program.bindings.user_data_registers.size(), program.info.buffers.size(), program.info.images.size(), program.info.samplers.size(), program.info.addresses.size(), program.srt.reads.size()); for (uint32_t i = 0; i < program.info.buffers.size(); i++) { const auto& buffer = program.info.buffers[i]; ret += fmt::format(" b{}[stride={} format={}]", i, buffer.packed_stride, buffer.descriptor_format); } for (uint32_t i = 0; i < program.info.images.size(); i++) { const auto& image = program.info.images[i]; ret += fmt::format(" i{}[kind={} dim={} swizzle=0x{:03x}]", i, static_cast(image.kind), static_cast(image.dimension), image.storage_swizzle); } for (uint32_t i = 0; i < program.info.addresses.size(); i++) { ret += fmt::format(" a{}[base=0x{:x}]", i, program.info.addresses[i].specialized_base); } return ret; } static void ShaderAppendNativeSpecialization(std::vector& ids, const ShaderRecompiler::IR::Program& program) { EXIT_IF(!program.binding_layout_complete); ids.push_back(static_cast(program.lane_mask_mode)); ids.push_back(program.bindings.descriptor_set); ids.push_back(program.bindings.push_constant_offset); ids.push_back(program.bindings.push_constant_size); ids.push_back(program.bindings.buffer_offset_dword); ids.push_back(program.bindings.buffer_offset_count); ids.push_back(static_cast(program.bindings.user_data_registers.size())); ids.insert(ids.end(), program.bindings.user_data_registers.begin(), program.bindings.user_data_registers.end()); ids.push_back(static_cast(program.bindings.descriptors.size())); for (const auto& binding: program.bindings.descriptors) { ids.push_back(static_cast(binding.kind)); ids.push_back(binding.binding); ids.push_back(static_cast(binding.resources.size())); ids.insert(ids.end(), binding.resources.begin(), binding.resources.end()); } ids.push_back(static_cast(program.info.buffers.size())); for (const auto& buffer: program.info.buffers) { ids.push_back(buffer.packed_stride); ids.push_back(buffer.descriptor_format); } ids.push_back(static_cast(program.info.images.size())); for (const auto& image: program.info.images) { ids.push_back(static_cast(image.kind)); ids.push_back(static_cast(image.dimension)); ids.push_back(static_cast(image.mip_mode)); ids.push_back(image.storage_swizzle); } ids.push_back(static_cast(program.info.addresses.size())); for (const auto& address: program.info.addresses) { ids.push_back(static_cast(address.specialized_base)); ids.push_back(static_cast(address.specialized_base >> 32u)); } } static std::span AddShaderProgramPermutation(const char* stage, uint64_t shader_hash, const ShaderStageProgramKey& key, ShaderProgramPermutation permutation) { static std::atomic compiled {0}; const auto compiled_count = compiled.fetch_add(1, std::memory_order_relaxed) + 1; std::printf("Num compiled %d shaders\n", compiled_count); std::scoped_lock lock(g_shader_program_cache_mutex); auto& permutations = g_shader_program_cache[key]; if (permutations.size() >= ShaderMaxPermutationsPerProgram) { LOGF("ShaderProgramCache overflow: %s\n", ShaderDescribeSpecialization(*permutation.program).c_str()); for (uint64_t i = 0; i < static_cast(permutations.size()); i++) { LOGF("ShaderProgramCache overflow existing[%" PRIu64 "]: %s\n", i, ShaderDescribeSpecialization(*permutations[static_cast(i)]->program) .c_str()); } EXIT("ShaderProgramCache: more than %u permutations for same %s shader: " "shader=0x%016" PRIx64 " existing=%" PRIu64 " incoming_words=%" PRIu64 " program_hash0=0x%08" PRIx32 " program_crc32=0x%08" PRIx32 " program_ids=%" PRIu64 "\n", ShaderMaxPermutationsPerProgram, stage, shader_hash, static_cast(permutations.size()), static_cast(permutation.program->bindings.descriptors.size()), key.program_id.hash0, key.program_id.crc32, static_cast(key.program_id.ids.size())); } auto cached = std::make_unique(std::move(permutation)); auto spirv = MakeShaderSpirvView(cached->spirv); permutations.push_back(std::move(cached)); return spirv; } bool ShaderCompileInfoVS(const HW::VertexShaderInfo& regs, const HW::ShaderRegisters& sh, ShaderLaneMaskMode lane_mask_mode, ShaderVertexInputInfo& info, std::span& spirv) { spirv = {}; if (!ShaderGetStaticInputInfoVS(regs, sh, info)) { return false; } const auto shader_hash = regs.gs_regs.chksum; const auto program_id = ShaderGetIdVS(regs, info, false); const auto key = MakeShaderStageProgramKey(ShaderType::Vertex, shader_hash, program_id, lane_mask_mode); { std::scoped_lock lock(g_shader_program_cache_mutex); if (auto iter = g_shader_program_cache.find(key); iter != g_shader_program_cache.end()) { for (const auto& permutation: iter->second) { if (TryUseVertexPermutation(*permutation, regs, info, shader_hash)) { spirv = MakeShaderSpirvView(permutation->spirv); LogShaderProgramCacheHit("VS", shader_hash, static_cast(spirv.size())); return true; } } } } std::vector compiled_spirv; if (!ShaderCompileSpirvVS(regs, sh, lane_mask_mode, info, compiled_spirv)) { return false; } ShaderProgramPermutation permutation {}; permutation.spirv = std::move(compiled_spirv); permutation.program = info.stage.program; spirv = AddShaderProgramPermutation("VS", shader_hash, key, std::move(permutation)); return true; } bool ShaderCompileInfoPS(const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh, ShaderLaneMaskMode lane_mask_mode, const ShaderVertexInputInfo& vs_info, std::span target_export_mapping, ShaderPixelInputInfo& ps_info, std::span& spirv) { spirv = {}; ShaderGetStaticInputInfoPS(regs, sh, vs_info, target_export_mapping, ps_info); const auto shader_hash = regs.ps_regs.chksum != 0 ? regs.ps_regs.chksum : regs.ps_regs.data_addr; const auto program_id = ShaderGetIdPS(regs, ps_info, false); const auto key = MakeShaderStageProgramKey(ShaderType::Pixel, shader_hash, program_id, lane_mask_mode); { std::scoped_lock lock(g_shader_program_cache_mutex); if (auto iter = g_shader_program_cache.find(key); iter != g_shader_program_cache.end()) { for (const auto& permutation: iter->second) { if (TryUsePixelPermutation(*permutation, regs, ps_info, shader_hash)) { spirv = MakeShaderSpirvView(permutation->spirv); LogShaderProgramCacheHit("PS", shader_hash, static_cast(spirv.size())); return true; } } } } std::vector compiled_spirv; if (!ShaderCompileSpirvPS(regs, sh, lane_mask_mode, ps_info, compiled_spirv)) { return false; } ShaderProgramPermutation permutation {}; permutation.spirv = std::move(compiled_spirv); permutation.program = ps_info.stage.program; spirv = AddShaderProgramPermutation("PS", shader_hash, key, std::move(permutation)); return true; } bool ShaderCompileInfoCS(const HW::ComputeShaderInfo& regs, const HW::ShaderRegisters& sh, ShaderComputeInputInfo& info, std::span& spirv) { spirv = {}; ShaderGetStaticInputInfoCS(regs, sh, info); const auto shader_hash = regs.cs_regs.data_addr; const auto program_id = ShaderGetIdCS(regs, info, false); const auto key = MakeShaderStageProgramKey(ShaderType::Compute, shader_hash, program_id, ShaderLaneMaskMode::NativeWave); { std::scoped_lock lock(g_shader_program_cache_mutex); if (auto iter = g_shader_program_cache.find(key); iter != g_shader_program_cache.end()) { for (const auto& permutation: iter->second) { if (TryUseComputePermutation(*permutation, regs, info, shader_hash)) { spirv = MakeShaderSpirvView(permutation->spirv); LogShaderProgramCacheHit("CS", shader_hash, static_cast(spirv.size())); return true; } } } } std::vector compiled_spirv; if (!ShaderCompileSpirvCS(regs, sh, info, compiled_spirv)) { return false; } ShaderProgramPermutation permutation {}; permutation.spirv = std::move(compiled_spirv); permutation.program = info.stage.program; spirv = AddShaderProgramPermutation("CS", shader_hash, key, std::move(permutation)); return true; } // NOLINTNEXTLINE(readability-function-cognitive-complexity) void ShaderDbgDumpInputInfo(const ShaderVertexInputInfo& info) { KYTY_PROFILER_BLOCK("ShaderDbgDumpInputInfo(Vs)"); LOGF("ShaderDbgDumpInputInfo()\n"); LOGF("\t fetch_external = %s\n" "\t fetch_embedded = %s\n" "\t param_export_mask = 0x%08" PRIx32 "\n" "\t export_count = %d\n", info.fetch_external ? "true" : "false", info.fetch_embedded ? "true" : "false", info.param_export_mask, info.export_count); for (int i = 0; i < info.resources_num; i++) { LOGF("\t input %d\n", i); const auto& r = info.resources[i]; const auto& rd = info.resources_dst[i]; LOGF("\t\t register_start = %d\n" "\t\t registers_num = %d\n" "\t\t fetch_index = %" PRIu32 "\n", rd.register_start, rd.registers_num, rd.fetch_index); LOGF("\t\t fields = %08" PRIx32 "%08" PRIx32 "%08" PRIx32 "%08" PRIx32 "\n", r.fields[3], r.fields[2], r.fields[1], r.fields[0]); LOGF("\t\t Base() = %" PRIx64 "\n" "\t\t Stride() = %" PRIu16 "\n" "\t\t SwizzleEnabled() = %s\n" "\t\t NumRecords() = %" PRIu32 "\n" "\t\t DstSelX() = %" PRIu8 "\n" "\t\t DstSelY() = %" PRIu8 "\n" "\t\t DstSelZ() = %" PRIu8 "\n" "\t\t DstSelW() = %" PRIu8 "\n", r.Base48(), r.Stride(), r.SwizzleEnabled() ? "true" : "false", r.NumRecords(), r.DstSelX(), r.DstSelY(), r.DstSelZ(), r.DstSelW()); LOGF("\t\t Format() = %" PRIu8 "\n" "\t\t OutOfBounds() = %" PRIu8 "\n", r.Format(), r.OutOfBounds()); LOGF("\t\t AddTid() = %s\n", r.AddTid() ? "true" : "false"); } for (int i = 0; i < info.buffers_num; i++) { LOGF("\t buffer %d\n", i); const auto& r = info.buffers[i]; LOGF("\t\t addr = %" PRIx64 "\n" "\t\t stride = %" PRIu32 "\n" "\t\t num_records = %" PRIu32 "\n" "\t\t fetch_index = %" PRIu32 "\n" "\t\t attr_num = %" PRId32 "\n", r.addr, r.stride, r.num_records, r.fetch_index, r.attr_num); for (int j = 0; j < r.attr_num; j++) { LOGF("\t\t attr_indices[%d] = %d\n" "\t\t attr_offsets[%d] = %u\n", j, r.attr_indices[j], j, r.attr_offsets[j]); } } } void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info) { KYTY_PROFILER_BLOCK("ShaderDbgDumpInputInfo(Ps)"); LOGF("ShaderDbgDumpInputInfo()\n"); LOGF("\t input_num = %u\n" "\t ps_system_input_base = %u\n" "\t ps_pos_x = %s\n" "\t ps_pos_y = %s\n" "\t ps_pos_z = %s\n" "\t ps_pos_w = %s\n" "\t ps_front_face = %s\n" "\t ps_sample_shading = %s\n" "\t ps_no_perspective = %s\n" "\t ps_pixel_kill_enable = %s\n" "\t ps_early_z = %s\n" "\t ps_execute_on_noop = %s\n", info.input_num, info.ps_system_input_base, info.ps_pos_x ? "true" : "false", info.ps_pos_y ? "true" : "false", info.ps_pos_z ? "true" : "false", info.ps_pos_w ? "true" : "false", info.ps_front_face ? "true" : "false", info.ps_sample_shading ? "true" : "false", info.ps_no_perspective ? "true" : "false", info.ps_pixel_kill_enable ? "true" : "false", info.ps_early_z ? "true" : "false", info.ps_execute_on_noop ? "true" : "false"); for (uint32_t i = 0; i < info.input_num; i++) { LOGF("\t interpolator_settings[%u] = %u\n", i, info.interpolator_settings[i]); } } void ShaderDbgDumpInputInfo(const ShaderComputeInputInfo& info) { LOGF("ShaderDbgDumpInputInfo()\n"); LOGF("\t workgroup_register = %d\n" "\t thread_ids_num = %d\n" "\t wave_size = %u\n" "\t threads_num = {%u, %u, %u}\n" "\t tg_size_en = %s\n", info.workgroup_register, info.thread_ids_num, info.wave_size, info.threads_num[0], info.threads_num[1], info.threads_num[2], info.tg_size_en ? "true" : "false"); LOGF("\t threadgroup_id = {%s, %s, %s}\n", info.group_id[0] ? "true" : "false", info.group_id[1] ? "true" : "false", info.group_id[2] ? "true" : "false"); } static bool ShaderRecompilerTextDumpEnabled() { // Graphics debug dump already writes SPIR-V binaries to _Shaders. Keep the very large // disassembly/IR text behind the shader-log switch so file logging cannot stall boot. return Config::GetShaderLogDirection() != Config::ShaderLogDirection::Silent; } static void DumpShaderRecompilerSpirv(const char* type, uint64_t shader_hash, const std::vector& bin) { if (!Config::GraphicsDebugDumpEnabled()) { return; } static std::atomic_int id = 0; const auto base_name = Config::GetShaderLogFolder() / fmt::format("{:04d}_new_shader_{}_{:016x}", id++, type, shader_hash); Common::File::CreateDirectories(base_name.parent_path()); Common::File spv_file; auto spv_name = base_name; spv_name += ".spv"; spv_file.Create(spv_name); if (spv_file.IsInvalid()) { auto spv_name_text = Common::PathToString(spv_name); LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", spv_name_text.c_str()); } else { spv_file.Write(bin.data(), bin.size() * 4); spv_file.Close(); } return; std::string text; if (!SpirvDisassemble(bin.data(), bin.size(), &text)) { auto spv_name_text = Common::PathToString(spv_name); LOGF_COLOR(Log::Color::BrightRed, "SpirvDisassemble() failed for %s\n", spv_name_text.c_str()); return; } Common::File asm_file; auto asm_name = base_name; asm_name += ".spvasm"; asm_file.Create(asm_name); if (asm_file.IsInvalid()) { auto asm_name_text = Common::PathToString(asm_name); LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", asm_name_text.c_str()); } else { asm_file.Printf("%s", text.c_str()); asm_file.Close(); } } static void DumpShaderRecompilerOriginal(const char* type, uint64_t shader_hash, std::span code, const std::string& decoded_dump) { // if (!Config::GraphicsDebugDumpEnabled()) { // return; // } return; EXIT_IF(code.empty()); static std::atomic_int id = 0; const auto base_name = Config::GetShaderLogFolder() / "original" / fmt::format("{:04d}_new_shader_{}_{:016x}", id++, type, shader_hash); Common::File::CreateDirectories(base_name.parent_path()); Common::File bin_file; auto bin_name = base_name; bin_name += ".bin"; bin_file.Create(bin_name); if (bin_file.IsInvalid()) { auto bin_name_text = Common::PathToString(bin_name); LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", bin_name_text.c_str()); } else { bin_file.Write(code.data(), code.size_bytes()); bin_file.Close(); } Common::File text_file; auto text_name = base_name; text_name += ".rdna2"; text_file.Create(text_name); if (text_file.IsInvalid()) { auto text_name_text = Common::PathToString(text_name); LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", text_name_text.c_str()); } else { text_file.Printf("%s", decoded_dump.c_str()); text_file.Close(); } } bool ShaderCompileSpirvVS(const HW::VertexShaderInfo& regs, const HW::ShaderRegisters& sh, ShaderLaneMaskMode lane_mask_mode, ShaderVertexInputInfo& input_info, std::vector& spirv) { KYTY_PROFILER_FUNCTION(profiler::colors::Amber300); EXIT_NOT_IMPLEMENTED(regs.es_regs.data_addr == 0 || regs.gs_regs.chksum == 0); const uint64_t shader_addr = regs.es_regs.data_addr; const auto code = ShaderGetMappedCode(shader_addr, "ShaderRecompiler VS", regs.gs_regs.chksum); ShaderRecompiler::CompileOptions options; options.stage = ShaderType::Vertex; options.lane_mask_mode = lane_mask_mode; options.shader_hash = regs.gs_regs.chksum; options.shader_base = shader_addr; options.user_data_base = 8; options.user_data_count = regs.gs_regs.rsrc2.user_sgpr; options.user_data = regs.gs_user_sgpr.value; options.descriptor_set = 0; options.push_constant_offset = 0; options.vertex_input_info = &input_info; options.dump_ir = ShaderRecompilerTextDumpEnabled(); options.early_dump = options.dump_ir; options.dump_label = "ShaderRecompiler VS"; ShaderRecompiler::CompileResult result; std::string error; if (!ShaderRecompiler::TryRecompile(code, options, result, &error)) { ExitShaderRecompilerFailure("ShaderRecompiler VS", options.shader_hash, error.c_str()); } DumpShaderRecompilerOriginal("vs", options.shader_hash, code, result.decoded_dump); if (!SpirvValidateBinary("ShaderRecompiler VS", options.shader_hash, result.spirv)) { DumpShaderRecompilerSpirv("vs", options.shader_hash, result.spirv); ExitShaderRecompilerFailure("ShaderRecompiler VS", options.shader_hash, "SPIR-V validation failed"); } input_info.stage.program = std::make_shared(std::move(result.program)); input_info.stage.resources = std::make_shared(std::move(result.resources)); ApplyVertexOutputs(input_info, *input_info.stage.program); spirv = std::move(result.spirv); DumpShaderRecompilerSpirv("vs", options.shader_hash, spirv); if (options.dump_ir) { if (!options.early_dump) { LOGF("ShaderRecompiler VS decoded RDNA2:\n%s", result.decoded_dump.c_str()); LOGF("ShaderRecompiler VS IR:\n%s", result.ir_dump.c_str()); } LOGF("ShaderRecompiler VS SPIR-V words=%" PRIu64 "\n", static_cast(spirv.size())); } return true; } bool ShaderCompileSpirvPS(const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh, ShaderLaneMaskMode lane_mask_mode, ShaderPixelInputInfo& input_info, std::vector& spirv) { KYTY_PROFILER_FUNCTION(profiler::colors::Blue300); const uint64_t shader_addr = regs.ps_regs.data_addr; const uint64_t shader_hash = regs.ps_regs.chksum != 0 ? regs.ps_regs.chksum : shader_addr; const auto code = ShaderGetMappedCode(shader_addr, "ShaderRecompiler PS", shader_hash); ShaderRecompiler::CompileOptions options; options.stage = ShaderType::Pixel; options.lane_mask_mode = lane_mask_mode; options.shader_hash = shader_hash; options.shader_base = shader_addr; options.user_data_count = regs.ps_regs.rsrc2.user_sgpr; options.user_data = regs.ps_user_sgpr.value; options.descriptor_set = input_info.descriptor_set; options.push_constant_offset = input_info.push_constant_offset; options.pixel_input_info = &input_info; options.dump_ir = ShaderRecompilerTextDumpEnabled(); options.early_dump = options.dump_ir; options.dump_label = "ShaderRecompiler PS"; ShaderRecompiler::CompileResult result; std::string error; if (!ShaderRecompiler::TryRecompile(code, options, result, &error)) { ExitShaderRecompilerFailure("ShaderRecompiler PS", options.shader_hash, error.c_str()); } DumpShaderRecompilerOriginal("ps", options.shader_hash, code, result.decoded_dump); if (!SpirvValidateBinary("ShaderRecompiler PS", options.shader_hash, result.spirv)) { DumpShaderRecompilerSpirv("ps", options.shader_hash, result.spirv); ExitShaderRecompilerFailure("ShaderRecompiler PS", options.shader_hash, "SPIR-V validation failed"); } input_info.stage.program = std::make_shared(std::move(result.program)); input_info.stage.resources = std::make_shared(std::move(result.resources)); ApplyPixelOutputs(input_info, *input_info.stage.program); spirv = std::move(result.spirv); DumpShaderRecompilerSpirv("ps", options.shader_hash, spirv); if (options.dump_ir) { if (!options.early_dump) { LOGF("ShaderRecompiler PS decoded RDNA2:\n%s", result.decoded_dump.c_str()); LOGF("ShaderRecompiler PS IR:\n%s", result.ir_dump.c_str()); } LOGF("ShaderRecompiler PS SPIR-V words=%" PRIu64 "\n", static_cast(spirv.size())); } return true; } bool ShaderCompileSpirvCS(const HW::ComputeShaderInfo& regs, const HW::ShaderRegisters& sh, ShaderComputeInputInfo& input_info, std::vector& spirv) { KYTY_PROFILER_FUNCTION(profiler::colors::CyanA700); const uint64_t shader_addr = regs.cs_regs.data_addr; const auto code = ShaderGetMappedCode(shader_addr, "ShaderRecompiler CS", shader_addr); ShaderRecompiler::CompileOptions options; options.stage = ShaderType::Compute; options.shader_hash = shader_addr; options.shader_base = shader_addr; options.user_data_count = regs.cs_regs.user_sgpr; options.user_data = regs.cs_user_sgpr.value; options.descriptor_set = 0; options.push_constant_offset = 0; options.compute_input_info = &input_info; options.wave_size = input_info.wave_size; options.dump_ir = ShaderRecompilerTextDumpEnabled(); options.early_dump = options.dump_ir; options.dump_label = "ShaderRecompiler CS"; ShaderRecompiler::CompileResult result; std::string error; if (!ShaderRecompiler::TryRecompile(code, options, result, &error)) { ExitShaderRecompilerFailure("ShaderRecompiler CS", options.shader_hash, error.c_str()); } DumpShaderRecompilerOriginal("cs", options.shader_hash, code, result.decoded_dump); if (!SpirvValidateBinary("ShaderRecompiler CS", options.shader_hash, result.spirv)) { DumpShaderRecompilerSpirv("cs", options.shader_hash, result.spirv); ExitShaderRecompilerFailure("ShaderRecompiler CS", options.shader_hash, "SPIR-V validation failed"); } input_info.stage.program = std::make_shared(std::move(result.program)); input_info.stage.resources = std::make_shared(std::move(result.resources)); spirv = std::move(result.spirv); DumpShaderRecompilerSpirv("cs", options.shader_hash, spirv); if (options.dump_ir) { if (!options.early_dump) { LOGF("ShaderRecompiler CS decoded RDNA2:\n%s", result.decoded_dump.c_str()); LOGF("ShaderRecompiler CS IR:\n%s", result.ir_dump.c_str()); } LOGF("ShaderRecompiler CS SPIR-V words=%" PRIu64 " wave_size=%u\n", static_cast(spirv.size()), options.wave_size); } return true; } ShaderId ShaderGetIdVS(const HW::VertexShaderInfo& regs, const ShaderVertexInputInfo& input_info, bool include_bind_specialization) { KYTY_PROFILER_FUNCTION(); ShaderId ret; ret.ids.reserve(64); EXIT_NOT_IMPLEMENTED(regs.es_regs.data_addr == 0 || regs.gs_regs.chksum == 0); ret.hash0 = (regs.gs_regs.chksum >> 32u) & 0xffffffffu; ret.crc32 = regs.gs_regs.chksum & 0xffffffffu; ret.ids.push_back(static_cast(input_info.fetch_external)); ret.ids.push_back(static_cast(input_info.fetch_embedded)); ret.ids.push_back(input_info.resources_num); ret.ids.push_back(input_info.export_count); for (int i = 0; i < input_info.resources_num; i++) { const auto& r = input_info.resources[i]; const auto& rd = input_info.resources_dst[i]; ret.ids.push_back(rd.register_start); ret.ids.push_back(rd.registers_num); ret.ids.push_back(rd.fetch_index); ret.ids.push_back(r.Stride()); ret.ids.push_back(static_cast(r.SwizzleEnabled())); ret.ids.push_back(r.DstSelX()); ret.ids.push_back(r.DstSelY()); ret.ids.push_back(r.DstSelZ()); ret.ids.push_back(r.DstSelW()); ret.ids.push_back(r.Format()); ret.ids.push_back(r.OutOfBounds()); ret.ids.push_back(static_cast(r.AddTid())); } ret.ids.push_back(input_info.buffers_num); for (int i = 0; i < input_info.buffers_num; i++) { const auto& r = input_info.buffers[i]; ret.ids.push_back(r.attr_num); ret.ids.push_back(r.stride); ret.ids.push_back(r.fetch_index); for (int j = 0; j < r.attr_num; j++) { ret.ids.push_back(r.attr_indices[j]); ret.ids.push_back(r.attr_offsets[j]); } } if (include_bind_specialization) { EXIT_IF(!input_info.stage); ShaderAppendNativeSpecialization(ret.ids, *input_info.stage.program); } return ret; } ShaderId ShaderGetIdPS(const HW::PixelShaderInfo& regs, const ShaderPixelInputInfo& input_info, bool include_bind_specialization) { KYTY_PROFILER_FUNCTION(); ShaderId ret; ret.ids.reserve(64); ret.hash0 = (regs.ps_regs.chksum >> 32u) & 0xffffffffu; ret.crc32 = regs.ps_regs.chksum & 0xffffffffu; ret.ids.push_back(input_info.descriptor_set); ret.ids.push_back(input_info.push_constant_offset); ret.ids.push_back(input_info.input_num); ret.ids.push_back(input_info.ps_system_input_base); ret.ids.push_back(static_cast(input_info.ps_pos_x)); ret.ids.push_back(static_cast(input_info.ps_pos_y)); ret.ids.push_back(static_cast(input_info.ps_pos_z)); ret.ids.push_back(static_cast(input_info.ps_pos_w)); ret.ids.push_back(static_cast(input_info.ps_front_face)); ret.ids.push_back(static_cast(input_info.ps_no_perspective)); ret.ids.push_back(static_cast(input_info.ps_pixel_kill_enable)); ret.ids.push_back(static_cast(input_info.ps_sample_mask_export_enable)); ret.ids.push_back(static_cast(input_info.ps_early_z)); ret.ids.push_back(static_cast(input_info.ps_execute_on_noop)); for (auto mode: input_info.target_output_mode) { ret.ids.push_back(mode); } for (uint32_t base = 0; base < input_info.target_export_mapping.size(); base += 4u) { uint32_t packed = 0; for (uint32_t i = 0; i < 4u; i++) { packed |= static_cast(input_info.target_export_mapping[base + i].packed) << (i * 8u); } ret.ids.push_back(packed); } ret.ids.push_back(input_info.mrt_output_mask); for (uint32_t i = 0; i < input_info.input_num; i++) { ret.ids.push_back(input_info.interpolator_settings[i]); } if (include_bind_specialization) { EXIT_IF(!input_info.stage); ShaderAppendNativeSpecialization(ret.ids, *input_info.stage.program); } return ret; } ShaderId ShaderGetIdCS(const HW::ComputeShaderInfo& regs, const ShaderComputeInputInfo& input_info, bool include_bind_specialization) { const auto* src = reinterpret_cast(regs.cs_regs.data_addr); EXIT_NOT_IMPLEMENTED(src == nullptr); const auto* header = GetBinaryInfo(src); ShaderId ret; ret.ids.reserve(64); if (header != nullptr) { ret.hash0 = header->hash0; ret.crc32 = header->crc32; ret.ids.push_back(header->length); } else { GetNextGenFallbackShaderId(regs.cs_regs.data_addr, ret.hash0, ret.crc32); ret.ids.push_back(static_cast(regs.cs_regs.data_addr & 0xffffffffu)); ret.ids.push_back(static_cast((regs.cs_regs.data_addr >> 32u) & 0xffffffffu)); } ret.ids.push_back(input_info.workgroup_register); ret.ids.push_back(input_info.wave_size); ret.ids.push_back(input_info.thread_ids_num); for (int i = 0; i < 3; i++) { ret.ids.push_back(input_info.threads_num[i]); ret.ids.push_back(static_cast(input_info.group_id[i])); } ret.ids.push_back(static_cast(input_info.dispatch_thread_dimensions)); for (uint32_t dim: input_info.dispatch_threads_num) { ret.ids.push_back(dim); } if (include_bind_specialization) { EXIT_IF(!input_info.stage); ShaderAppendNativeSpecialization(ret.ids, *input_info.stage.program); } return ret; } bool ShaderAddressValid(uint64_t addr) { return reinterpret_cast(addr) != nullptr; } } // namespace Libs::Graphics