#include "libs/agc.h" #include "common/assert.h" #include "common/emulatorConfig.h" #include "common/file.h" #include "common/logging/log.h" #include "common/stringUtils.h" #include "common/virtualMemory.h" #include "graphics/guest_gpu/command_processor/commandProcessor.h" #include "graphics/guest_gpu/gpu_defs.h" #include "graphics/guest_gpu/graphicsRun.h" #include "graphics/guest_gpu/hardwareContext.h" #include "graphics/guest_gpu/pm4.h" #include "graphics/guest_gpu/tile.h" #include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/host_gpu/renderer/sync.h" #include "graphics/presentation/presenter.h" #include "graphics/presentation/renderDoc.h" #include "graphics/presentation/videoOut.h" #include "graphics/presentation/window.h" #include "graphics/shader/shader.h" #include "kernel/pthread.h" #include "libs/errno.h" #include "libs/libs.h" #include #include #include #include #include #include #include #include #ifdef min #undef min #endif #ifdef max #undef max #endif namespace Libs::Graphics { static RenderContext* g_renderer = nullptr; template static void AgcTrace(const char* format, const Args&... args) { if (Config::GraphicsDebugDumpEnabled()) { LOGF(format, args...); } } void Initialize() { // Some games lock up if this is not called first if (Config::RenderDocEnabled()) { RenderDocInit(); } auto width = Config::GetScreenWidth(); auto height = Config::GetScreenHeight(); auto& presenter = WindowInit(width, height); auto& video_out = VideoOut::VideoOutInit(width, height, presenter); g_renderer = &presenter.Renderer(); g_renderer->InitializeGpu(&video_out); ShaderInit(); } void Shutdown() { EXIT_IF(g_renderer == nullptr); g_renderer->ShutdownGpu(); VideoOut::VideoOutShutdown(); WindowShutdown(); g_renderer = nullptr; } void GraphicsDbgDumpDcb(const char* type, uint32_t num_dw, const uint32_t* cmd_buffer) { EXIT_IF(type == nullptr); static std::atomic_int id = 0; if (Config::CommandBufferDumpEnabled() && num_dw > 0 && cmd_buffer != nullptr) { Common::File f; auto file_name = Config::GetCommandBufferDumpFolder() / fmt::format("{:04d}_{:04d}_buffer_{}.log", g_renderer->GetGpu().GetFrameNum(), id++, type); Common::File::CreateDirectories(file_name.parent_path()); f.Create(file_name); if (f.IsInvalid()) { auto file_name_text = Common::PathToString(file_name); LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", file_name_text.c_str()); return; } Pm4::DumpPm4PacketStream(&f, cmd_buffer, 0, num_dw); f.Close(); } } namespace Gen5 { LIB_NAME("Graphics5", "Graphics5"); struct RegisterDefaults { ShaderRegister** tbl0 = nullptr; ShaderRegister** tbl1 = nullptr; ShaderRegister** tbl2 = nullptr; ShaderRegister** tbl3 = nullptr; uint32_t tbl0_register_count = 0; uint32_t tbl1_register_count = 0; uint32_t tbl2_register_count = 0; uint32_t tbl3_register_count = 0; uint32_t* types = nullptr; uint32_t count = 0; }; static_assert(offsetof(RegisterDefaults, count) == 0x38); struct CompactRegisterDefaults { ShaderRegister* tbl0_regs = nullptr; uint32_t tbl0_register_count = 0; const uint16_t* tbl0_pointer_offsets = nullptr; uint32_t tbl0_pointer_count = 0; ShaderRegister* tbl1_regs = nullptr; uint32_t tbl1_register_count = 0; const uint16_t* tbl1_pointer_offsets = nullptr; uint32_t tbl1_pointer_count = 0; ShaderRegister* tbl2_regs = nullptr; uint32_t tbl2_register_count = 0; const uint16_t* tbl2_pointer_offsets = nullptr; uint32_t tbl2_pointer_count = 0; ShaderRegister* tbl3_regs = nullptr; uint32_t tbl3_register_count = 0; const uint16_t* tbl3_pointer_offsets = nullptr; uint32_t tbl3_pointer_count = 0; uint32_t* types = nullptr; uint32_t count = 0; }; struct RegisterDefaultsStorage { RegisterDefaults defaults; std::vector tbl0; std::vector tbl1; std::vector tbl2; std::vector tbl3; bool initialized = false; }; #include "libs/agcRegisterDefaults.inc" #include static constexpr uint32_t GRAPHICS_REGISTER_DEFAULTS_MAX_VERSION = 13; static constexpr uint32_t GRAPHICS_REGISTER_DEFAULTS_FALLBACK_VERSION = 13; static std::mutex g_register_defaults_mutex; static ShaderRegister** init_register_default_pointer_table(std::vector* table, ShaderRegister* regs, uint32_t register_count, const uint16_t* offsets, uint32_t pointer_count) { EXIT_IF(table == nullptr); table->clear(); if (pointer_count == 0) { return nullptr; } EXIT_IF(regs == nullptr); EXIT_IF(offsets == nullptr); table->resize(pointer_count); for (uint32_t i = 0; i < pointer_count; i++) { EXIT_IF(offsets[i] >= register_count); (*table)[i] = regs + offsets[i]; } return table->data(); } static RegisterDefaults* get_register_defaults(CompactRegisterDefaults* compact, RegisterDefaultsStorage* storage) { EXIT_IF(compact == nullptr); EXIT_IF(storage == nullptr); std::lock_guard lock(g_register_defaults_mutex); if (!storage->initialized) { storage->defaults.tbl0 = init_register_default_pointer_table( &storage->tbl0, compact->tbl0_regs, compact->tbl0_register_count, compact->tbl0_pointer_offsets, compact->tbl0_pointer_count); storage->defaults.tbl1 = init_register_default_pointer_table( &storage->tbl1, compact->tbl1_regs, compact->tbl1_register_count, compact->tbl1_pointer_offsets, compact->tbl1_pointer_count); storage->defaults.tbl2 = init_register_default_pointer_table( &storage->tbl2, compact->tbl2_regs, compact->tbl2_register_count, compact->tbl2_pointer_offsets, compact->tbl2_pointer_count); storage->defaults.tbl3 = init_register_default_pointer_table( &storage->tbl3, compact->tbl3_regs, compact->tbl3_register_count, compact->tbl3_pointer_offsets, compact->tbl3_pointer_count); storage->defaults.tbl0_register_count = compact->tbl0_register_count; storage->defaults.tbl1_register_count = compact->tbl1_register_count; storage->defaults.tbl2_register_count = compact->tbl2_register_count; storage->defaults.tbl3_register_count = compact->tbl3_register_count; storage->defaults.types = compact->types; storage->defaults.count = compact->count; storage->initialized = true; } return &storage->defaults; } static uint32_t normalize_register_defaults_version(uint32_t ver) { return ver <= GRAPHICS_REGISTER_DEFAULTS_MAX_VERSION ? ver : GRAPHICS_REGISTER_DEFAULTS_FALLBACK_VERSION; } static RegisterDefaults* get_public_register_defaults(uint32_t ver) { static RegisterDefaultsStorage storage[GRAPHICS_REGISTER_DEFAULTS_MAX_VERSION + 1] = {}; const auto index = normalize_register_defaults_version(ver); return get_register_defaults(g_agc_public_reg_defaults_by_version[index], &storage[index]); } static RegisterDefaults* get_internal_register_defaults(uint32_t ver) { static RegisterDefaultsStorage storage[GRAPHICS_REGISTER_DEFAULTS_MAX_VERSION + 1] = {}; const auto index = normalize_register_defaults_version(ver); return get_register_defaults(g_agc_internal_reg_defaults_by_version[index], &storage[index]); } struct CommandBuffer { using Callback = KYTY_SYSV_ABI bool (*)(CommandBuffer*, uint32_t, void*); uint32_t* bottom; uint32_t* top; uint32_t* cursor_up; uint32_t* cursor_down; Callback callback; void* user_data; uint32_t reserved_dw; void DbgDump() const { if (!Config::GraphicsDebugDumpEnabled() || Config::GetPrintfDirection() == Config::LogDirection::Silent) { return; } static std::atomic log_count {0}; if (log_count.fetch_add(1) >= 64) { return; } LOGF("\t bottom = 0x%016" PRIx64 "\n" "\t top = 0x%016" PRIx64 "\n" "\t cursor_up = 0x%016" PRIx64 "\n" "\t cursor_down = 0x%016" PRIx64 "\n" "\t callback = 0x%016" PRIx64 "\n" "\t user_data = 0x%016" PRIx64 "\n" "\t reserved_dw = %" PRIu32 "\n", reinterpret_cast(bottom), reinterpret_cast(top), reinterpret_cast(cursor_up), reinterpret_cast(cursor_down), reinterpret_cast(callback), reinterpret_cast(user_data), reserved_dw); } [[nodiscard]] KYTY_SYSV_ABI uint64_t GetAvailableSizeDW() const { // Interpret the signed cursor distance as an unsigned 64-bit DWORD count. const auto distance = static_cast(reinterpret_cast(cursor_down) - reinterpret_cast(cursor_up)); const auto available = static_cast(distance >> 2); if (available <= reserved_dw) { return 0; } return available - reserved_dw; } KYTY_SYSV_ABI bool ReserveDW(uint32_t num_dw) { const uint64_t remaining = GetAvailableSizeDW(); if (num_dw > remaining) { if (callback == nullptr) { LOGF_COLOR( Log::Color::Red, "\t command buffer exhausted and has no grow callback: requested = %" PRIu32 ", remaining = %" PRIu64 ", reserved_dw = %" PRIu32 "\n", num_dw, remaining, reserved_dw); DbgDump(); return false; } bool result = callback(this, num_dw + reserved_dw, user_data); if (!result) { LOGF_COLOR(Log::Color::Red, "\t command buffer grow callback failed: requested = %" PRIu32 ", remaining = %" PRIu64 ", reserved_dw = %" PRIu32 "\n", num_dw, remaining, reserved_dw); DbgDump(); return false; } if (GetAvailableSizeDW() < num_dw) { LOGF_COLOR(Log::Color::Red, "\t command buffer grow callback did not provide enough space: " "requested = %" PRIu32 ", remaining = %" PRIu64 ", reserved_dw = %" PRIu32 "\n", num_dw, GetAvailableSizeDW(), reserved_dw); DbgDump(); return false; } } return true; } KYTY_SYSV_ABI uint32_t* AllocateDW(uint32_t size_dw) { if (size_dw == 0 || !ReserveDW(size_dw)) { LOGF_COLOR(Log::Color::Red, "\t command buffer AllocateDW failed: size_dw = %" PRIu32 "\n", size_dw); return nullptr; } auto* ret_ptr = cursor_up; cursor_up += size_dw; return ret_ptr; } }; struct Label { volatile uint64_t m_value; uint64_t m_reserved[3]; }; int KYTY_SYSV_ABI AgcInit(uint32_t* state, uint32_t ver) { PRINT_NAME(); LOGF("\t state = 0x%016" PRIx64 "\n" "\t ver = %u\n", reinterpret_cast(state), ver); if (ver > GRAPHICS_REGISTER_DEFAULTS_MAX_VERSION) { LOGF_COLOR(Log::Color::Red, "\t unsupported version %u\n", ver); } printf("version = %u\n", ver); return OK; } void* KYTY_SYSV_ABI AgcGetRegisterDefaults2(uint32_t ver) { PRINT_NAME(); if (ver > GRAPHICS_REGISTER_DEFAULTS_MAX_VERSION) { LOGF_COLOR(Log::Color::Red, "\t unsupported version %u\n", ver); } return get_public_register_defaults(ver); } void* KYTY_SYSV_ABI AgcGetRegisterDefaults2Internal(uint32_t ver) { PRINT_NAME(); if (ver > GRAPHICS_REGISTER_DEFAULTS_MAX_VERSION) { LOGF_COLOR(Log::Color::Red, "\t unsupported version %u\n", ver); } return get_internal_register_defaults(ver); } static void dbg_dump_shader(const Shader* h) { if (!Config::GraphicsDebugDumpEnabled()) { return; } LOGF("\t file_header = 0x%08" PRIx32 "\n" "\t version = 0x%08" PRIx32 "\n" "\t user_data = 0x%016" PRIx64 "\n", h->file_header, h->version, reinterpret_cast(h->user_data)); if (h->user_data != nullptr) { LOGF("\t\t direct_resource_count = 0x%04" PRIx16 "\n" "\t\t direct_resource_offset = 0x%016" PRIx64 "\n", h->user_data->direct_resource_count, reinterpret_cast(h->user_data->direct_resource_offset)); for (int i = 0; i < static_cast(h->user_data->direct_resource_count); i++) { LOGF("\t\t\t offset[%02d] = %04" PRIx16 "\n", i, h->user_data->direct_resource_offset[i]); } for (int imm = 0; imm < 4; imm++) { LOGF("\t\t sharp_resource_count [%d] = 0x%04" PRIx16 "\n", imm, h->user_data->sharp_resource_count[imm]); LOGF("\t\t sharp_resource_offset [%d] = 0x%016" PRIx64 "\n", imm, reinterpret_cast(h->user_data->sharp_resource_offset[imm])); for (int i = 0; i < static_cast(h->user_data->sharp_resource_count[imm]); i++) { LOGF("\t\t\t offset_dw[%d] = %04" PRIx16 ", size = %" PRIu16 "\n", i, static_cast(h->user_data->sharp_resource_offset[imm][i].offset_dw), static_cast(h->user_data->sharp_resource_offset[imm][i].size)); } } LOGF("\t\t eud_size_dw = 0x%04" PRIx16 "\n" "\t\t srt_size_dw = 0x%04" PRIx16 "\n", h->user_data->eud_size_dw, h->user_data->srt_size_dw); } LOGF("\t code = 0x%016" PRIx64 "\n" "\t num_cx_registers = 0x%02" PRIx8 "\n" "\t cx_registers = 0x%016" PRIx64 "\n", reinterpret_cast(h->code), h->num_cx_registers, reinterpret_cast(h->cx_registers)); for (int i = 0; i < static_cast(h->num_cx_registers); i++) { LOGF("\t\t cx[%d]: offset = %08" PRIx32 ", value = %08" PRIx32 "\n", i, h->cx_registers[i].offset, h->cx_registers[i].value); } LOGF("\t num_sh_registers = 0x%02" PRIx8 "\n" "\t sh_registers = 0x%016" PRIx64 "\n", h->num_sh_registers, reinterpret_cast(h->sh_registers)); for (int i = 0; i < static_cast(h->num_sh_registers); i++) { LOGF("\t\t sh[%d]: offset = %08" PRIx32 ", value = %08" PRIx32 "\n", i, h->sh_registers[i].offset, h->sh_registers[i].value); } LOGF("\t specials = 0x%016" PRIx64 "\n", reinterpret_cast(h->specials)); LOGF("\t\t ge_cntl: offset = %08" PRIx32 ", value = %08" PRIx32 "\n", h->specials->ge_cntl.offset, h->specials->ge_cntl.value); LOGF("\t\t vgt_shader_stages_en: offset = %08" PRIx32 ", value = %08" PRIx32 "\n", h->specials->vgt_shader_stages_en.offset, h->specials->vgt_shader_stages_en.value); LOGF("\t\t vgt_gs_out_prim_type: offset = %08" PRIx32 ", value = %08" PRIx32 "\n", h->specials->vgt_gs_out_prim_type.offset, h->specials->vgt_gs_out_prim_type.value); LOGF("\t\t ge_user_vgpr_en: offset = %08" PRIx32 ", value = %08" PRIx32 "\n", h->specials->ge_user_vgpr_en.offset, h->specials->ge_user_vgpr_en.value); LOGF("\t\t dispatch_modifier = %08" PRIx32 "\n", h->specials->dispatch_modifier); LOGF("\t\t user_data_range: start = %08" PRIx32 ", end = %08" PRIx32 "\n", h->specials->user_data_range.start, h->specials->user_data_range.end); LOGF("\t\t draw_modifier: enbl_start_vertex_offset = %08" PRIx32 "\n" "\t\t draw_modifier: enbl_start_index_offset = %08" PRIx32 "\n" "\t\t draw_modifier: enbl_start_instance_offset = %08" PRIx32 "\n" "\t\t draw_modifier: enbl_draw_index = %08" PRIx32 "\n" "\t\t draw_modifier: enbl_user_vgprs = %08" PRIx32 "\n" "\t\t draw_modifier: render_target_slice_offset = %08" PRIx32 "\n" "\t\t draw_modifier: fuse_draws = %08" PRIx32 "\n" "\t\t draw_modifier: compiler_flags = %08" PRIx32 "\n" "\t\t draw_modifier: is_default = %08" PRIx32 "\n" "\t\t draw_modifier: reserved = %08" PRIx32 "\n" "\t num_input_semantics = 0x%08" PRIx32 "\n" "\t input_semantics = 0x%016" PRIx64 "\n", static_cast(h->specials->draw_modifier.enbl_start_vertex_offset), static_cast(h->specials->draw_modifier.enbl_start_index_offset), static_cast(h->specials->draw_modifier.enbl_start_instance_offset), static_cast(h->specials->draw_modifier.enbl_draw_index), static_cast(h->specials->draw_modifier.enbl_user_vgprs), static_cast(h->specials->draw_modifier.render_target_slice_offset), static_cast(h->specials->draw_modifier.fuse_draws), static_cast(h->specials->draw_modifier.compiler_flags), static_cast(h->specials->draw_modifier.is_default), static_cast(h->specials->draw_modifier.reserved), h->num_input_semantics, reinterpret_cast(h->input_semantics)); for (int i = 0; i < static_cast(h->num_input_semantics); i++) { LOGF("\t\t input_semantics[%d]: semantic = %08" PRIx32 "\n" "\t\t input_semantics[%d]: hardware_mapping = %08" PRIx32 "\n" "\t\t input_semantics[%d]: size_in_elements = %08" PRIx32 "\n" "\t\t input_semantics[%d]: is_f16 = %08" PRIx32 "\n" "\t\t input_semantics[%d]: is_flat_shaded = %08" PRIx32 "\n" "\t\t input_semantics[%d]: is_linear = %08" PRIx32 "\n" "\t\t input_semantics[%d]: is_custom = %08" PRIx32 "\n" "\t\t input_semantics[%d]: static_vb_index = %08" PRIx32 "\n" "\t\t input_semantics[%d]: static_attribute = %08" PRIx32 "\n" "\t\t input_semantics[%d]: reserved = %08" PRIx32 "\n" "\t\t input_semantics[%d]: default_value = %08" PRIx32 "\n" "\t\t input_semantics[%d]: default_value_hi = %08" PRIx32 "\n", i, static_cast(h->input_semantics[i].semantic), i, static_cast(h->input_semantics[i].hardware_mapping), i, static_cast(h->input_semantics[i].size_in_elements), i, static_cast(h->input_semantics[i].is_f16), i, static_cast(h->input_semantics[i].is_flat_shaded), i, static_cast(h->input_semantics[i].is_linear), i, static_cast(h->input_semantics[i].is_custom), i, static_cast(h->input_semantics[i].static_vb_index), i, static_cast(h->input_semantics[i].static_attribute), i, static_cast(h->input_semantics[i].reserved), i, static_cast(h->input_semantics[i].default_value), i, static_cast(h->input_semantics[i].default_value_hi)); } LOGF("\t num_output_semantics = 0x%04" PRIx16 "\n" "\t output_semantics = 0x%016" PRIx64 "\n", h->num_output_semantics, reinterpret_cast(h->output_semantics)); for (int i = 0; i < static_cast(h->num_output_semantics); i++) { LOGF("\t\t output_semantics[%d]: semantic = %08" PRIx32 "\n" "\t\t output_semantics[%d]: hardware_mapping = %08" PRIx32 "\n" "\t\t output_semantics[%d]: size_in_elements = %08" PRIx32 "\n" "\t\t output_semantics[%d]: is_f16 = %08" PRIx32 "\n" "\t\t output_semantics[%d]: is_flat_shaded = %08" PRIx32 "\n" "\t\t output_semantics[%d]: is_linear = %08" PRIx32 "\n" "\t\t output_semantics[%d]: is_custom = %08" PRIx32 "\n" "\t\t output_semantics[%d]: static_vb_index = %08" PRIx32 "\n" "\t\t output_semantics[%d]: static_attribute = %08" PRIx32 "\n" "\t\t output_semantics[%d]: reserved = %08" PRIx32 "\n" "\t\t output_semantics[%d]: default_value = %08" PRIx32 "\n" "\t\t output_semantics[%d]: default_value_hi = %08" PRIx32 "\n", i, static_cast(h->output_semantics[i].semantic), i, static_cast(h->output_semantics[i].hardware_mapping), i, static_cast(h->output_semantics[i].size_in_elements), i, static_cast(h->output_semantics[i].is_f16), i, static_cast(h->output_semantics[i].is_flat_shaded), i, static_cast(h->output_semantics[i].is_linear), i, static_cast(h->output_semantics[i].is_custom), i, static_cast(h->output_semantics[i].static_vb_index), i, static_cast(h->output_semantics[i].static_attribute), i, static_cast(h->output_semantics[i].reserved), i, static_cast(h->output_semantics[i].default_value), i, static_cast(h->output_semantics[i].default_value_hi)); } LOGF("\t header_size = 0x%08" PRIx32 "\n" "\t shader_size = 0x%08" PRIx32 "\n" "\t embedded_constant_buffer_size_dqw = 0x%08" PRIx32 "\n" "\t target = 0x%08" PRIx32 "\n" "\t scratch_size_dw_per_thread = 0x%04" PRIx16 "\n" "\t special_sizes_bytes = 0x%04" PRIx16 "\n" "\t type = 0x%02" PRIx8 "\n", h->header_size, h->shader_size, h->embedded_constant_buffer_size_dqw, h->target, h->scratch_size_dw_per_thread, h->special_sizes_bytes, h->type); } static constexpr int GRAPHICS5_ERROR_INVALID_SHADER_PROGRAM = static_cast(0x8a6c0005u); static constexpr int GRAPHICS5_ERROR_INVALID_SHADER_HALVES = static_cast(0x8a6c0008u); static bool get_shader_program_address_register(uint8_t type, uint32_t* lo_offset) { if (lo_offset == nullptr) { return false; } switch (static_cast(type)) { case Prospero::ShaderBinaryType::kCs: *lo_offset = Pm4::COMPUTE_PGM_LO; return true; case Prospero::ShaderBinaryType::kPs: *lo_offset = Pm4::SPI_SHADER_PGM_LO_PS; return true; case Prospero::ShaderBinaryType::kGs: *lo_offset = Pm4::SPI_SHADER_PGM_LO_ES; return true; case Prospero::ShaderBinaryType::kHs: *lo_offset = Pm4::SPI_SHADER_PGM_LO_LS; return true; case Prospero::ShaderBinaryType::kGsBack: *lo_offset = Pm4::SPI_SHADER_PGM_LO_GS; return true; case Prospero::ShaderBinaryType::kHsBack: *lo_offset = Pm4::SPI_SHADER_PGM_LO_HS; return true; case Prospero::ShaderBinaryType::kGsFront: case Prospero::ShaderBinaryType::kHsFront: case Prospero::ShaderBinaryType::kFs: return false; } return false; } static int patch_program_address_register(ShaderRegister* regs, uint32_t num_regs, uint8_t type, uint64_t base) { uint32_t lo_offset = 0; if (!get_shader_program_address_register(type, &lo_offset)) { return OK; } if (regs == nullptr || num_regs == 0) { return GRAPHICS5_ERROR_INVALID_SHADER_PROGRAM; } for (auto lo_index = 0u; lo_index < num_regs; lo_index++) { if (regs[lo_index].offset != lo_offset) { continue; } const auto hi_index = lo_index + 1u; const auto hi_offset = lo_offset + 1u; if (hi_index >= num_regs || regs[hi_index].offset != hi_offset) { return GRAPHICS5_ERROR_INVALID_SHADER_PROGRAM; } const auto shader_offset = (static_cast(regs[lo_index].value) << 8u) | ((static_cast(regs[hi_index].value) & 0xffu) << 40u); const auto addr = base + shader_offset; regs[lo_index].value = static_cast((addr >> 8u) & 0xffffffffu); regs[hi_index].value &= 0xffffff00u; regs[hi_index].value |= static_cast((addr >> 40u) & 0xffu); return OK; } return GRAPHICS5_ERROR_INVALID_SHADER_PROGRAM; } static ShaderRegister* find_shader_register(ShaderRegister* regs, uint32_t num_regs, uint32_t offset, uint32_t occurrence = 0) { if (regs == nullptr) { return nullptr; } for (uint32_t i = 0; i < num_regs; i++) { if (regs[i].offset != offset) { continue; } if (occurrence == 0) { return regs + i; } occurrence--; } return nullptr; } static void patch_shader_register_address(ShaderRegister* regs, uint32_t num_regs, uint32_t lo_offset, uint64_t address) { auto* lo = find_shader_register(regs, num_regs, lo_offset); if (lo == nullptr) { return; } auto* hi = (lo + 1 < regs + num_regs && (lo + 1)->offset == lo_offset + 1u ? lo + 1 : nullptr); if (hi == nullptr) { return; } lo->value = static_cast((address >> 8u) & 0xffffffffu); hi->value &= 0xffffff00u; hi->value |= static_cast((address >> 40u) & 0xffu); } int KYTY_SYSV_ABI AgcCreateShader(Shader** dst, void* header, const volatile void* code) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(dst == nullptr); EXIT_NOT_IMPLEMENTED(header == nullptr); EXIT_NOT_IMPLEMENTED(code == nullptr); LOGF("\t header = 0x%016" PRIx64 "\n" "\t code = 0x%016" PRIx64 "\n", reinterpret_cast(header), reinterpret_cast(code)); auto* h = static_cast(header); auto update_addr = [](auto& m) { if (m != nullptr) { m = reinterpret_cast::type>( reinterpret_cast(m) + reinterpret_cast(&m)); } }; update_addr(h->cx_registers); update_addr(h->sh_registers); update_addr(h->user_data); update_addr(h->specials); update_addr(h->input_semantics); update_addr(h->output_semantics); if (h->user_data != nullptr) { update_addr(h->user_data->direct_resource_offset); update_addr(h->user_data->sharp_resource_offset[0]); update_addr(h->user_data->sharp_resource_offset[1]); update_addr(h->user_data->sharp_resource_offset[2]); update_addr(h->user_data->sharp_resource_offset[3]); } h->code = code; EXIT_NOT_IMPLEMENTED(h->file_header != 0x34333231); EXIT_NOT_IMPLEMENTED(h->version != 0x00000018); auto base = reinterpret_cast(code); LOGF("\t base = 0x%016" PRIx64 "\n", base); ShaderMappedData map; map.type = static_cast(h->type); map.user_data = h->user_data; map.input_semantics = h->input_semantics; map.num_input_semantics = h->num_input_semantics; map.code_size_bytes = h->shader_size; map.scratch_size_dwords = h->scratch_size_dw_per_thread; ShaderMapUserData(base, map); EXIT_NOT_IMPLEMENTED((base & 0xFFFF0000000000FFull) != 0); auto patch_result = patch_program_address_register(h->sh_registers, h->num_sh_registers, h->type, base); if (patch_result != OK) { return patch_result; } *dst = h; dbg_dump_shader(h); return OK; } int KYTY_SYSV_ABI AgcUnknownGetFusedShaderSize(SizeAlign* dst, const Shader* front, const Shader* back) { PRINT_NAME(); LOGF("\t dst = 0x%016" PRIx64 "\n" "\t front = 0x%016" PRIx64 "\n" "\t back = 0x%016" PRIx64 "\n", reinterpret_cast(dst), reinterpret_cast(front), reinterpret_cast(back)); EXIT_NOT_IMPLEMENTED(dst == nullptr); EXIT_NOT_IMPLEMENTED(front == nullptr); EXIT_NOT_IMPLEMENTED(back == nullptr); const auto front_type = static_cast(front->type); const auto back_type = static_cast(back->type); if (!((front_type == Prospero::ShaderBinaryType::kGsFront && back_type == Prospero::ShaderBinaryType::kGsBack) || (front_type == Prospero::ShaderBinaryType::kHsFront && back_type == Prospero::ShaderBinaryType::kHsBack))) { return GRAPHICS5_ERROR_INVALID_SHADER_HALVES; } dst->m_size = static_cast(back->num_sh_registers) * sizeof(ShaderRegister); dst->m_align = 4; return OK; } static void merge_shader_register_max_field(ShaderRegister* dst, const ShaderRegister* src, uint32_t shift, uint32_t mask) { const auto dst_field = (dst->value >> shift) & mask; const auto src_field = (src->value >> shift) & mask; const auto field = std::max(dst_field, src_field); dst->value &= ~(mask << shift); dst->value |= field << shift; } static int fuse_shader_halves(Shader* fused_result, const Shader* front, const Shader* back, void* scratch_mem, bool recompute_shared_vgprs) { LOGF("\t fused_result = 0x%016" PRIx64 "\n" "\t front = 0x%016" PRIx64 "\n" "\t back = 0x%016" PRIx64 "\n" "\t scratch_mem = 0x%016" PRIx64 "\n", reinterpret_cast(fused_result), reinterpret_cast(front), reinterpret_cast(back), reinterpret_cast(scratch_mem)); EXIT_NOT_IMPLEMENTED(fused_result == nullptr); EXIT_NOT_IMPLEMENTED(front == nullptr); EXIT_NOT_IMPLEMENTED(back == nullptr); const auto front_type = static_cast(front->type); const auto is_gs = front_type == Prospero::ShaderBinaryType::kGsFront; const auto is_hs = front_type == Prospero::ShaderBinaryType::kHsFront; if ((!is_gs && !is_hs) || (is_gs && back->type != static_cast(Prospero::ShaderBinaryType::kGsBack)) || (is_hs && back->type != static_cast(Prospero::ShaderBinaryType::kHsBack))) { return GRAPHICS5_ERROR_INVALID_SHADER_HALVES; } *fused_result = *back; fused_result->type = static_cast(is_gs ? Prospero::ShaderBinaryType::kGs : Prospero::ShaderBinaryType::kHs); const auto back_stages = back->specials->vgt_shader_stages_en.value; const auto front_stages = front->specials->vgt_shader_stages_en.value; const auto mismatch_bit = is_gs ? (1u << 22u) : (1u << 21u); if (((front_stages ^ back_stages) & mismatch_bit) != 0) { return GRAPHICS5_ERROR_INVALID_SHADER_HALVES; } if (scratch_mem != nullptr) { auto* sh_registers = static_cast(scratch_mem); memcpy(sh_registers, back->sh_registers, static_cast(back->num_sh_registers) * sizeof(ShaderRegister)); fused_result->sh_registers = sh_registers; } auto* fused_regs = fused_result->sh_registers; const auto fused_reg_count = static_cast(fused_result->num_sh_registers); const auto front_reg_count = static_cast(front->num_sh_registers); const auto checksum_offset = is_gs ? Pm4::SPI_SHADER_PGM_CHKSUM_GS : Pm4::SPI_SHADER_PGM_CHKSUM_HS; for (uint32_t occurrence = 0; occurrence < 2; occurrence++) { const auto* src = find_shader_register(front->sh_registers, front_reg_count, checksum_offset, occurrence); auto* dst = find_shader_register(fused_regs, fused_reg_count, checksum_offset, occurrence); dst->value = src->value; } const auto rsrc1_offset = is_gs ? Pm4::SPI_SHADER_PGM_RSRC1_GS : Pm4::SPI_SHADER_PGM_RSRC1_HS; const auto rsrc2_offset = is_gs ? Pm4::SPI_SHADER_PGM_RSRC2_GS : Pm4::SPI_SHADER_PGM_RSRC2_HS; const auto* front_rsrc1 = find_shader_register(front->sh_registers, front_reg_count, rsrc1_offset); const auto* front_rsrc2 = find_shader_register(front->sh_registers, front_reg_count, rsrc2_offset); auto* fused_rsrc1 = find_shader_register(fused_regs, fused_reg_count, rsrc1_offset); auto* fused_rsrc2 = find_shader_register(fused_regs, fused_reg_count, rsrc2_offset); if (recompute_shared_vgprs) { const auto front_vgprs = ((front_rsrc1->value & 0x3fu) + 1u) * 4u; const auto back_vgprs = ((fused_rsrc1->value & 0x3fu) + 1u) * 4u; const auto front_total = front_vgprs + (front_rsrc2->value >> 28u) * 8u; const auto back_total = back_vgprs + (fused_rsrc2->value >> 28u) * 8u; const auto max_total = std::max(front_total, back_total); // sceAgcFuseShaderHalves reallocates shared VGPRs; the older export takes the maximum. const auto shared = std::max(front_vgprs, back_vgprs) >= max_total ? 0u : (max_total - std::min(front_total, back_total) + 7u) / 64u; fused_rsrc2->value = (fused_rsrc2->value & 0x0fffffffu) | ((shared & 0xfu) << 28u); } else { merge_shader_register_max_field(fused_rsrc2, front_rsrc2, 28, 0x0fu); } merge_shader_register_max_field(fused_rsrc1, front_rsrc1, 0, 0x3fu); if (is_gs) { merge_shader_register_max_field(fused_rsrc1, front_rsrc1, 29, 0x03u); merge_shader_register_max_field(fused_rsrc2, front_rsrc2, 16, 0x03u); fused_rsrc2->value = (fused_rsrc2->value & 0xfffbffffu) | (front_rsrc2->value & 0x00040000u); } else { merge_shader_register_max_field(fused_rsrc1, front_rsrc1, 28, 0x03u); } fused_rsrc2->value = (fused_rsrc2->value & 0xf7ffffc1u) | (front_rsrc2->value & 0x0800003eu); patch_shader_register_address(fused_regs, fused_reg_count, is_gs ? Pm4::SPI_SHADER_PGM_LO_ES : Pm4::SPI_SHADER_PGM_LO_LS, reinterpret_cast(front->code)); fused_result->user_data = recompute_shared_vgprs ? nullptr : front->user_data; return OK; } int KYTY_SYSV_ABI AgcUnknownFuseShaderHalves(Shader* fused_result, const Shader* front, const Shader* back, void* scratch_mem) { PRINT_NAME(); return fuse_shader_halves(fused_result, front, back, scratch_mem, true); } int KYTY_SYSV_ABI AgcUnknownNApJjpKNBl4(Shader* fused_result, const Shader* front, const Shader* back, void* scratch_mem) { PRINT_NAME(); return fuse_shader_halves(fused_result, front, back, scratch_mem, false); } static constexpr int GRAPHICS5_ERROR_INVALID_PACKET = static_cast(0x8a6c000cu); enum class RegIndirectPacket : uint32_t { Cx, Sh, Uc, }; static uint32_t reg_indirect_native_op(RegIndirectPacket type) { switch (type) { case RegIndirectPacket::Cx: return Pm4::IT_SET_CONTEXT_REG_INDIRECT; case RegIndirectPacket::Sh: return Pm4::IT_SET_SH_REG_INDIRECT; case RegIndirectPacket::Uc: return Pm4::IT_SET_UCONFIG_REG_INDIRECT; } return 0; } static bool is_native_reg_indirect_packet(const uint32_t* cmd, RegIndirectPacket type) { return ((cmd[0] >> 8u) & 0xffu) == reg_indirect_native_op(type); } static constexpr bool is_trinity_mode() { return false; } static uint32_t reg_indirect_pm4_r(RegIndirectPacket type) { return (type == RegIndirectPacket::Uc && is_trinity_mode() ? 1u : 0u); } static void reg_indirect_write_packet(uint32_t* cmd, uint64_t vaddr, uint32_t num_regs, RegIndirectPacket type) { cmd[0] = KYTY_PM4(5, reg_indirect_native_op(type), reg_indirect_pm4_r(type)); cmd[1] = static_cast(vaddr) & 0xfffffffcu; cmd[2] = static_cast(vaddr >> 32u); cmd[3] = 0x80000000u; cmd[4] = num_regs & 0x3fffu; } static int reg_indirect_patch_set_address(uint32_t* cmd, uint64_t vaddr, RegIndirectPacket type) { EXIT_NOT_IMPLEMENTED(cmd == nullptr); if (is_native_reg_indirect_packet(cmd, type)) { cmd[1] = (cmd[1] & 0x3u) | (static_cast(vaddr) & 0xfffffffcu); cmd[2] = static_cast(vaddr >> 32u); return OK; } return GRAPHICS5_ERROR_INVALID_PACKET; } static int reg_indirect_patch_set_num_registers(uint32_t* cmd, uint32_t num_regs, RegIndirectPacket type) { EXIT_NOT_IMPLEMENTED(cmd == nullptr); if (is_native_reg_indirect_packet(cmd, type)) { cmd[4] = (cmd[4] & ~0x3fffu) | (num_regs & 0x3fffu); return OK; } return GRAPHICS5_ERROR_INVALID_PACKET; } static int reg_indirect_patch_add_registers(uint32_t* cmd, uint32_t num_regs, RegIndirectPacket type) { EXIT_NOT_IMPLEMENTED(cmd == nullptr); if (is_native_reg_indirect_packet(cmd, type)) { auto new_num_regs = ((cmd[4] & 0x3fffu) + num_regs) & 0x3fffu; cmd[4] = (cmd[4] & ~0x3fffu) | new_num_regs; return OK; } return GRAPHICS5_ERROR_INVALID_PACKET; } int KYTY_SYSV_ABI AgcSetCxRegIndirectPatchSetAddress(uint32_t* cmd, const volatile ShaderRegister* regs) { PRINT_NAME(); AgcTrace("\t cmd = 0x%016" PRIx64 "\n" "\t regs = 0x%016" PRIx64 "\n", reinterpret_cast(cmd), reinterpret_cast(regs)); EXIT_NOT_IMPLEMENTED(cmd == nullptr); EXIT_NOT_IMPLEMENTED(regs == nullptr); auto vaddr = reinterpret_cast(regs); return reg_indirect_patch_set_address(cmd, vaddr, RegIndirectPacket::Cx); } int KYTY_SYSV_ABI AgcSetShRegIndirectPatchSetAddress(uint32_t* cmd, const volatile ShaderRegister* regs) { PRINT_NAME(); AgcTrace("\t cmd = 0x%016" PRIx64 "\n" "\t regs = 0x%016" PRIx64 "\n", reinterpret_cast(cmd), reinterpret_cast(regs)); EXIT_NOT_IMPLEMENTED(cmd == nullptr); EXIT_NOT_IMPLEMENTED(regs == nullptr); auto vaddr = reinterpret_cast(regs); return reg_indirect_patch_set_address(cmd, vaddr, RegIndirectPacket::Sh); } int KYTY_SYSV_ABI AgcSetUcRegIndirectPatchSetAddress(uint32_t* cmd, const volatile ShaderRegister* regs) { PRINT_NAME(); AgcTrace("\t cmd = 0x%016" PRIx64 "\n" "\t regs = 0x%016" PRIx64 "\n", reinterpret_cast(cmd), reinterpret_cast(regs)); EXIT_NOT_IMPLEMENTED(cmd == nullptr); EXIT_NOT_IMPLEMENTED(regs == nullptr); auto vaddr = reinterpret_cast(regs); return reg_indirect_patch_set_address(cmd, vaddr, RegIndirectPacket::Uc); } int KYTY_SYSV_ABI AgcSetCxRegIndirectPatchSetNumRegisters(uint32_t* cmd, uint32_t num_regs) { PRINT_NAME(); AgcTrace("\t cmd = 0x%016" PRIx64 "\n" "\t num_regs = %" PRIu32 "\n", reinterpret_cast(cmd), num_regs); EXIT_NOT_IMPLEMENTED(cmd == nullptr); return reg_indirect_patch_set_num_registers(cmd, num_regs, RegIndirectPacket::Cx); } int KYTY_SYSV_ABI AgcSetShRegIndirectPatchSetNumRegisters(uint32_t* cmd, uint32_t num_regs) { PRINT_NAME(); AgcTrace("\t cmd = 0x%016" PRIx64 "\n" "\t num_regs = %" PRIu32 "\n", reinterpret_cast(cmd), num_regs); EXIT_NOT_IMPLEMENTED(cmd == nullptr); return reg_indirect_patch_set_num_registers(cmd, num_regs, RegIndirectPacket::Sh); } int KYTY_SYSV_ABI AgcSetUcRegIndirectPatchSetNumRegisters(uint32_t* cmd, uint32_t num_regs) { PRINT_NAME(); AgcTrace("\t cmd = 0x%016" PRIx64 "\n" "\t num_regs = %" PRIu32 "\n", reinterpret_cast(cmd), num_regs); EXIT_NOT_IMPLEMENTED(cmd == nullptr); return reg_indirect_patch_set_num_registers(cmd, num_regs, RegIndirectPacket::Uc); } int KYTY_SYSV_ABI AgcSetCxRegIndirectPatchAddRegisters(uint32_t* cmd, uint32_t num_regs) { PRINT_NAME(); AgcTrace("\t cmd = 0x%016" PRIx64 "\n" "\t num_regs = %" PRIu32 "\n", reinterpret_cast(cmd), num_regs); EXIT_NOT_IMPLEMENTED(cmd == nullptr); return reg_indirect_patch_add_registers(cmd, num_regs, RegIndirectPacket::Cx); } int KYTY_SYSV_ABI AgcSetShRegIndirectPatchAddRegisters(uint32_t* cmd, uint32_t num_regs) { PRINT_NAME(); AgcTrace("\t cmd = 0x%016" PRIx64 "\n" "\t num_regs = %" PRIu32 "\n", reinterpret_cast(cmd), num_regs); EXIT_NOT_IMPLEMENTED(cmd == nullptr); return reg_indirect_patch_add_registers(cmd, num_regs, RegIndirectPacket::Sh); } int KYTY_SYSV_ABI AgcSetUcRegIndirectPatchAddRegisters(uint32_t* cmd, uint32_t num_regs) { PRINT_NAME(); AgcTrace("\t cmd = 0x%016" PRIx64 "\n" "\t num_regs = %" PRIu32 "\n", reinterpret_cast(cmd), num_regs); EXIT_NOT_IMPLEMENTED(cmd == nullptr); return reg_indirect_patch_add_registers(cmd, num_regs, RegIndirectPacket::Uc); } static uint32_t GraphicsPrimitiveTypeToGsOut(uint32_t prim_type) { switch (static_cast(prim_type)) { case Prospero::PrimitiveType::kPointList: return static_cast(Prospero::GsOutputPrimitiveType::kPoints); case Prospero::PrimitiveType::kLineList: case Prospero::PrimitiveType::kLineStrip: case Prospero::PrimitiveType::kLineListAdjacency: case Prospero::PrimitiveType::kLineStripAdjacency: case Prospero::PrimitiveType::kLineLoop: return static_cast(Prospero::GsOutputPrimitiveType::kLines); case Prospero::PrimitiveType::kRectList: return static_cast(Prospero::GsOutputPrimitiveType::k2dRectangle); case Prospero::PrimitiveType::kRectListLegacy: return static_cast(Prospero::GsOutputPrimitiveType::kRectList); default: return static_cast(Prospero::GsOutputPrimitiveType::kTriangles); } } int KYTY_SYSV_ABI AgcCreatePrimState(ShaderRegister* cx_regs, ShaderRegister* uc_regs, const Shader* hs, const Shader* gs, uint32_t prim_type) { PRINT_NAME(); AgcTrace("\t cx_regs = 0x%016" PRIx64 "\n" "\t uc_regs = 0x%016" PRIx64 "\n" "\t hs = 0x%016" PRIx64 "\n" "\t gs = 0x%016" PRIx64 "\n" "\t prim_type = %" PRIu32 "\n", reinterpret_cast(cx_regs), reinterpret_cast(uc_regs), reinterpret_cast(hs), reinterpret_cast(gs), prim_type); if (cx_regs == nullptr && uc_regs == nullptr) { return OK; } EXIT_NOT_IMPLEMENTED(gs == nullptr); EXIT_NOT_IMPLEMENTED(hs != nullptr && static_cast(hs->type) != Prospero::ShaderBinaryType::kHs); EXIT_NOT_IMPLEMENTED(static_cast(gs->type) != Prospero::ShaderBinaryType::kGs); if (cx_regs != nullptr) { EXIT_NOT_IMPLEMENTED(hs != nullptr && hs->specials->vgt_shader_stages_en.offset != Pm4::VGT_SHADER_STAGES_EN); EXIT_NOT_IMPLEMENTED(hs != nullptr && hs->specials->vgt_gs_out_prim_type.offset != Pm4::VGT_GS_OUT_PRIM_TYPE); EXIT_NOT_IMPLEMENTED(gs->specials->vgt_shader_stages_en.offset != Pm4::VGT_SHADER_STAGES_EN); EXIT_NOT_IMPLEMENTED(gs->specials->vgt_gs_out_prim_type.offset != Pm4::VGT_GS_OUT_PRIM_TYPE); cx_regs[0] = gs->specials->vgt_shader_stages_en; if ((cx_regs[0].value & 0x20u) != 0) { cx_regs[1] = gs->specials->vgt_gs_out_prim_type; } else { cx_regs[1].offset = Pm4::VGT_GS_OUT_PRIM_TYPE; cx_regs[1].value = GraphicsPrimitiveTypeToGsOut(prim_type); } if (hs != nullptr) { cx_regs[0].value |= hs->specials->vgt_shader_stages_en.value; if ((cx_regs[0].value & 0x20u) == 0) { cx_regs[1] = hs->specials->vgt_gs_out_prim_type; } } } if (uc_regs != nullptr) { EXIT_NOT_IMPLEMENTED(gs->specials->ge_cntl.offset != Pm4::GE_CNTL); EXIT_NOT_IMPLEMENTED(gs->specials->ge_user_vgpr_en.offset != Pm4::GE_USER_VGPR_EN); EXIT_NOT_IMPLEMENTED(hs != nullptr && hs->specials->ge_user_vgpr_en.offset != Pm4::GE_USER_VGPR_EN); uc_regs[0] = gs->specials->ge_cntl; uc_regs[1] = gs->specials->ge_user_vgpr_en; uc_regs[2].offset = Pm4::VGT_PRIMITIVE_TYPE; uc_regs[2].value = prim_type; if (hs != nullptr) { uc_regs[1] = hs->specials->ge_user_vgpr_en; } } return OK; } int KYTY_SYSV_ABI AgcUpdatePrimState(ShaderRegister* cx_regs, ShaderRegister* uc_regs, uint32_t prim_type) { PRINT_NAME(); AgcTrace("\t cx_regs = 0x%016" PRIx64 "\n" "\t uc_regs = 0x%016" PRIx64 "\n" "\t prim_type = %" PRIu32 "\n", reinterpret_cast(cx_regs), reinterpret_cast(uc_regs), prim_type); if (cx_regs != nullptr && (cx_regs[0].value & 0x24u) == 0) { cx_regs[1].value &= ~0x7u; cx_regs[1].value |= GraphicsPrimitiveTypeToGsOut(prim_type); } if (uc_regs != nullptr) { uc_regs[2].value &= ~0x1fu; uc_regs[2].value |= prim_type; } return OK; } struct GsOccupancyLimits { uint32_t vertex; uint32_t export_count; }; static GsOccupancyLimits get_gs_occupancy_limits(const Shader* gs, uint32_t vertex_capacity, uint32_t export_capacity) { const auto* onchip = find_shader_register(gs->cx_registers, gs->num_cx_registers, Pm4::VGT_GS_ONCHIP_CNTL); const auto* subgroup = find_shader_register(gs->cx_registers, gs->num_cx_registers, Pm4::GE_NGG_SUBGRP_CNTL); const auto* vs_out = find_shader_register(gs->cx_registers, gs->num_cx_registers, Pm4::SPI_VS_OUT_CONFIG); const auto* cl_out = find_shader_register(gs->cx_registers, gs->num_cx_registers, Pm4::PA_CL_VS_OUT_CNTL); const auto* max_output = find_shader_register(gs->cx_registers, gs->num_cx_registers, Pm4::GE_MAX_OUTPUT_PER_SUBGROUP); const uint32_t output_words = ((max_output->value & 0x3ffu) + 31u) >> 5u; const uint32_t subgroup_work = ((((onchip->value >> 11u) & 0x7ffu) * (subgroup->value & 0x1ffu)) + 31u) >> 5u; uint32_t waves = std::max(subgroup_work, output_words); if ((gs->specials->vgt_shader_stages_en.value & 0x00400000u) == 0) { waves >>= 1u; } waves = std::max(waves, 1u); const uint32_t exports = 1u + ((cl_out->value >> 21u) & 1u) + ((cl_out->value >> 22u) & 1u) + ((cl_out->value >> 23u) & 1u); const uint32_t export_limit = (export_capacity / exports) * 4u; const uint32_t vertex_limit = (vs_out->value & 0x80u) != 0 ? 2048u : vertex_capacity / (((vs_out->value >> 2u) & 0xfu) + 1u); return {waves * (vertex_limit / output_words), waves * (export_limit / output_words)}; } int KYTY_SYSV_ABI AgcGetGsOversubscription(ShaderRegister* regs, const Shader* gs, uint32_t budget, float factor) { PRINT_NAME(); constexpr uint32_t FULL_PC_OVERSUBSCRIPTION = 0x7ffu; constexpr uint32_t FULL_SH_OVERSUBSCRIPTION = 0x007f0000u; regs[0] = {Pm4::UC_PARAMETER_OVERSUBSCRIPTION, 0u}; regs[1] = {Pm4::SPI_SHADER_PGM_RSRC4_GS, 0u}; if (budget == 0u) { return OK; } if (budget == UINT32_MAX) { regs[0].value |= FULL_PC_OVERSUBSCRIPTION; regs[1].value |= FULL_SH_OVERSUBSCRIPTION; return OK; } const auto base = get_gs_occupancy_limits(gs, 1024u, 128u); const auto expanded = get_gs_occupancy_limits(gs, 2048u, 382u); const auto base_min = std::min(base.vertex, base.export_count); const auto limit_shift = (gs->specials->vgt_shader_stages_en.value & 0x00400000u) != 0 ? 5u : 6u; const auto expanded_limit = std::min({expanded.vertex, expanded.export_count, budget >> limit_shift, 1024u}); const auto headroom = expanded_limit > base_min ? expanded_limit - base_min : 0u; const auto target = static_cast( std::fma(factor, static_cast(headroom), static_cast(base_min))); if (target <= base_min) { return OK; } if (target < base.export_count) { const auto range = std::max(expanded.vertex - base.vertex, 1u); auto value = static_cast( std::min((static_cast(target - base.vertex) << 10u) / range, 1024)); value = std::max(value, 1u); regs[0].value = (regs[0].value & ~FULL_PC_OVERSUBSCRIPTION) | (((value << 1u) - 1u) & FULL_PC_OVERSUBSCRIPTION); regs[1].value |= FULL_SH_OVERSUBSCRIPTION; } else { const auto range = std::max(expanded.export_count - base.export_count, 1u); const auto value = static_cast(std::min( (static_cast(target - base.export_count) * 127u) / range, 127)); regs[0].value |= FULL_PC_OVERSUBSCRIPTION; regs[1].value = (regs[1].value & ~FULL_SH_OVERSUBSCRIPTION) | (value << 16u); } return OK; } static uint32_t shader_semantic_word(const ShaderSemantic& semantic) { return ((semantic.semantic & 0xffu) << 0u) | ((semantic.hardware_mapping & 0xffu) << 8u) | ((semantic.size_in_elements & 0xfu) << 16u) | ((semantic.is_f16 & 0x3u) << 20u) | ((semantic.is_flat_shaded & 0x1u) << 22u) | ((semantic.is_linear & 0x1u) << 23u) | ((semantic.is_custom & 0x1u) << 24u) | ((semantic.static_vb_index & 0x1u) << 25u) | ((semantic.static_attribute & 0x1u) << 26u) | ((semantic.reserved & 0x1u) << 27u) | ((semantic.default_value & 0x3u) << 28u) | ((semantic.default_value_hi & 0x3u) << 30u); } static uint32_t apply_interpolant_default_value(uint32_t value, uint32_t ps_word) { value &= ~0x00000300u; value |= ((ps_word >> 28u) & 0x3u) << 8u; return value; } static uint32_t apply_interpolant_default_value_hi(uint32_t value, uint32_t ps_word) { value &= ~0x00600000u; value |= ((ps_word >> 30u) & 0x3u) << 21u; return value; } static uint32_t create_interpolant_mapping_value(uint32_t value, uint32_t ps_word, uint32_t gs_word) { const uint32_t flat_shade = ((ps_word & 0x00400000u) != 0 || (ps_word & 0x01000000u) != 0 ? 0x00000400u : 0u); value &= ~0x0000001fu; value |= (gs_word >> 8u) & 0x1fu; value &= ~0x00000400u; value |= flat_shade; return apply_interpolant_default_value(value, ps_word); } static uint32_t create_interpolant_default_value(uint32_t value, uint32_t ps_word) { value &= ~0x0000001fu; value &= ~0x00000400u; return apply_interpolant_default_value(value, ps_word); } static uint32_t create_interpolant_f16_value(uint32_t ps_word, const ShaderSemantic* gs_semantic) { uint32_t value = (ps_word << 4u) & 0x03000000u; if (gs_semantic == nullptr) { value |= 0x00180020u; } else { const uint32_t common_word = ps_word & shader_semantic_word(*gs_semantic); value &= 0xfff7ffdfu; value |= (common_word >> 15u) & 0x20u; value ^= 0x00080020u; value &= ~0x00100000u; value |= (~common_word >> 1u) & 0x00100000u; } return apply_interpolant_default_value_hi(value, ps_word); } static uint32_t create_interpolant_non_f16_value(uint32_t ps_word, const ShaderSemantic* gs_semantic) { uint32_t value = 0; if ((ps_word & 0x01000000u) != 0 || gs_semantic == nullptr) { value |= 0x20u; } return value; } static const ShaderSemantic* find_interpolant_output_semantic(const Shader* gs, uint32_t semantic) { if (gs == nullptr || gs->output_semantics == nullptr) { return nullptr; } for (uint16_t i = 0; i < gs->num_output_semantics; i++) { if (gs->output_semantics[i].semantic == semantic) { return &gs->output_semantics[i]; } } return nullptr; } static void set_interpolant_register(ShaderRegister* regs, uint32_t index, uint32_t value) { regs[index].offset = Pm4::SPI_PS_INPUT_CNTL_0 + index; regs[index].value = value; } static void set_identity_interpolant_registers(ShaderRegister* regs, uint32_t first_index) { for (uint32_t i = first_index; i < 32u; i++) { set_interpolant_register(regs, i, i); } } int KYTY_SYSV_ABI AgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps) { PRINT_NAME(); AgcTrace("\t regs = 0x%016" PRIx64 "\n" "\t gs = 0x%016" PRIx64 "\n" "\t ps = 0x%016" PRIx64 "\n", reinterpret_cast(regs), reinterpret_cast(gs), reinterpret_cast(ps)); EXIT_NOT_IMPLEMENTED(regs == nullptr); EXIT_NOT_IMPLEMENTED(ps != nullptr && ps->num_input_semantics != 0 && ps->input_semantics == nullptr); if (ps == nullptr || ps->num_input_semantics == 0) { set_identity_interpolant_registers(regs, 0); return OK; } EXIT_NOT_IMPLEMENTED(gs == nullptr); EXIT_NOT_IMPLEMENTED(gs->num_output_semantics != 0 && gs->output_semantics == nullptr); for (uint32_t ps_index = 0; ps_index < ps->num_input_semantics; ps_index++) { const auto& ps_semantic = ps->input_semantics[ps_index]; const auto* gs_semantic = find_interpolant_output_semantic(gs, ps_semantic.semantic); const auto ps_word = shader_semantic_word(ps_semantic); auto value = ((ps_word & 0x00300000u) != 0 ? create_interpolant_f16_value(ps_word, gs_semantic) : create_interpolant_non_f16_value(ps_word, gs_semantic)); value = (gs_semantic == nullptr ? create_interpolant_default_value(value, ps_word) : create_interpolant_mapping_value( value, ps_word, shader_semantic_word(*gs_semantic))); set_interpolant_register(regs, ps_index, value); } set_identity_interpolant_registers(regs, ps->num_input_semantics); return OK; } static uint32_t apply_interpolant_two_bit_field(uint32_t value, uint32_t field, uint32_t shift) { const auto mask = 0x3u << shift; return (value & ~mask) | ((field & 0x3u) << shift); } static uint32_t apply_interpolant_final_mask(uint32_t flags, uint32_t source, uint32_t mask) { flags = (flags & 0xffffffe0u) | ((mask >> 8u) & 0x1fu); flags = (flags & 0xfffffbffu) | ((source & 0x400000u) != 0 ? 0x400u : ((source >> 14u) & 0x400u)); return flags; } int KYTY_SYSV_ABI AgcCreateInterpolantMapping2(ShaderRegister* regs, const Shader* gs, const Shader* ps) { PRINT_NAME(); AgcTrace("\t regs = 0x%016" PRIx64 "\n" "\t gs = 0x%016" PRIx64 "\n" "\t ps = 0x%016" PRIx64 "\n", reinterpret_cast(regs), reinterpret_cast(gs), reinterpret_cast(ps)); EXIT_NOT_IMPLEMENTED(regs == nullptr); EXIT_NOT_IMPLEMENTED(ps != nullptr && ps->num_input_semantics != 0 && ps->input_semantics == nullptr); if (ps == nullptr || ps->num_input_semantics == 0) { set_identity_interpolant_registers(regs, 0); return OK; } EXIT_NOT_IMPLEMENTED(gs == nullptr); EXIT_NOT_IMPLEMENTED(gs->num_output_semantics != 0 && gs->output_semantics == nullptr); for (uint32_t i = 0; i < ps->num_input_semantics; i++) { const auto source = shader_semantic_word(ps->input_semantics[i]); auto mask_index = static_cast(gs->num_output_semantics); for (uint32_t j = 0; j < gs->num_output_semantics; j++) { if (gs->output_semantics[j].semantic == static_cast(source)) { mask_index = j; break; } } const bool has_mask = mask_index < gs->num_output_semantics; const auto mask = has_mask ? shader_semantic_word(gs->output_semantics[mask_index]) : 0u; const auto mode = (source >> 20u) & 0x3u; auto flags = 0u; if (mode == 0) { flags = (((source >> 24u) & 0x1u) | (has_mask ? 0u : 1u)) << 5u; flags = apply_interpolant_two_bit_field(flags, source >> 28u, 8u); } else { flags = ((source << 4u) & 0x03000000u) + 0x80000u; if (mode == 2) { flags &= 0xffefffdfu; flags |= has_mask ? ((~(mask & source) >> 16u) & 0x20u) : 0x20u; flags = apply_interpolant_two_bit_field(flags, source >> 30u, 8u); flags = apply_interpolant_two_bit_field(flags, source >> 30u, 21u); } else { if (has_mask) { const auto masked = mask & source; flags = (flags & 0xffffffdfu) | ((masked >> 15u) & 0x20u); flags ^= 0x20u; flags = (flags & 0xffefffffu) | ((~masked >> 1u) & 0x100000u); flags = apply_interpolant_two_bit_field(flags, source >> 28u, 8u); } else { flags |= 0x100020u; flags = apply_interpolant_two_bit_field(flags, source >> 28u, 8u); } flags = apply_interpolant_two_bit_field(flags, source >> 30u, 21u); } } flags = has_mask ? apply_interpolant_final_mask(flags, source, mask) : (flags & 0xfffffbe0u); set_interpolant_register(regs, i, flags); } if (ps->num_input_semantics < 32u) { set_identity_interpolant_registers(regs, ps->num_input_semantics); } return OK; } int KYTY_SYSV_ABI AgcGetDataPacketPayloadAddress(uint32_t** addr, uint32_t* cmd, int type) { PRINT_NAME(); static std::atomic log_count {0}; if (log_count.fetch_add(1) < 64) { LOGF("\t addr = 0x%016" PRIx64 "\n" "\t cmd = 0x%016" PRIx64 "\n" "\t type = %d\n", reinterpret_cast(addr), reinterpret_cast(cmd), type); } EXIT_NOT_IMPLEMENTED(addr == nullptr); if (type != 0) { *addr = cmd + 2; return OK; } auto cmd_id = cmd[0]; *addr = ((~cmd_id & 0x3fff0000u) != 0 ? cmd + 1 : nullptr); return OK; } int KYTY_SYSV_ABI AgcGetDataPacketPayloadRange(MemoryRange* range, uint32_t* cmd, int type) { PRINT_NAME(); static std::atomic log_count {0}; const bool should_log = (log_count.fetch_add(1) < 64); if (should_log) { LOGF("\t range = 0x%016" PRIx64 "\n" "\t cmd = 0x%016" PRIx64 "\n" "\t type = %d\n", reinterpret_cast(range), reinterpret_cast(cmd), type); } EXIT_NOT_IMPLEMENTED(range == nullptr); EXIT_NOT_IMPLEMENTED(cmd == nullptr); const auto cmd_id = cmd[0]; const auto len = KYTY_PM4_LEN(cmd_id); if (type != 0) { range->m_base = cmd + 2; range->m_size = static_cast((cmd_id >> 14u) & 0xfffcu); } else { if ((~cmd_id & 0x3fff0000u) == 0) { range->m_base = nullptr; range->m_size = 0; } else { range->m_base = cmd + 1; range->m_size = static_cast((cmd_id >> 14u) & 0xfffcu) + sizeof(uint32_t); } } if (should_log) { LOGF("\t cmd_id = 0x%08" PRIx32 ", len = %" PRIu32 "\n" "\t base = 0x%016" PRIx64 "\n" "\t size = 0x%016" PRIx64 "\n", cmd_id, len, reinterpret_cast(range->m_base), range->m_size); } return OK; } int KYTY_SYSV_ABI AgcWriteDataPatchSetAddressOrOffset(uint32_t* cmd, uint64_t address_or_offset) { EXIT_NOT_IMPLEMENTED(cmd == nullptr); auto op = (cmd[0] >> 8u) & 0xffu; if (op == Pm4::IT_WRITE_DATA) { cmd[2] = static_cast(address_or_offset & 0xffffffffu); cmd[3] = static_cast((address_or_offset >> 32u) & 0xffffffffu); return OK; } return GRAPHICS5_ERROR_INVALID_PACKET; } int KYTY_SYSV_ABI AgcJumpPatchSetTarget(uint32_t* cmd, const volatile uint32_t* target, uint32_t size_in_dwords) { PRINT_NAME(); LOGF("\t cmd = 0x%016" PRIx64 "\n" "\t target = 0x%016" PRIx64 "\n" "\t size_in_dwords = %" PRIu32 "\n", reinterpret_cast(cmd), reinterpret_cast(target), size_in_dwords); EXIT_NOT_IMPLEMENTED(cmd == nullptr); auto cmd_id = cmd[0]; auto op = (cmd_id >> 8u) & 0xffu; LOGF("\t cmd_id = 0x%08" PRIx32 ", op = 0x%02" PRIx32 ", len = %" PRIu32 "\n", cmd_id, op, KYTY_PM4_LEN(cmd_id)); if (op != Pm4::IT_INDIRECT_BUFFER) { return GRAPHICS5_ERROR_INVALID_PACKET; } auto vaddr = reinterpret_cast(target); cmd[1] = static_cast(vaddr & 0xffffffffu); cmd[2] = (cmd[2] & 0xffff0000u) | static_cast((vaddr >> 32u) & 0xffffu); cmd[3] = (cmd[3] & 0xfff00000u) | (size_in_dwords & 0xfffffu); return OK; } int KYTY_SYSV_ABI AgcSuspendPoint() { PRINT_NAME(); EXIT_IF(g_renderer == nullptr); g_renderer->GetGpu().Done(); return OK; } [[nodiscard]] static constexpr uint32_t context_state_op_size_dw(uint32_t operation) { switch (static_cast(operation)) { case ContextStateOperation::Clear: return 5; case ContextStateOperation::Push: case ContextStateOperation::Pop: return 27; case ContextStateOperation::PushClear: return 32; default: return 0; } } uint32_t* KYTY_SYSV_ABI AgcDcbContextStateOp(CommandBuffer* buf, uint32_t operation) { PRINT_NAME(); LOGF("\t operation = 0x%08" PRIx32 "\n", operation); const auto size_dw = context_state_op_size_dw(operation); if (buf == nullptr || size_dw == 0) { return nullptr; } buf->DbgDump(); uint32_t* first = nullptr; const auto append = [&](uint32_t segment_size_dw) { auto* segment = buf->AllocateDW(segment_size_dw); if (segment == nullptr) { return false; } std::fill_n(segment, segment_size_dw, 0u); if (first == nullptr) { first = segment; segment[0] = KYTY_PM4(segment_size_dw, Pm4::IT_NOP, Pm4::R_CONTEXT_STATE); segment[1] = operation; } else { segment[0] = KYTY_PM4(segment_size_dw, Pm4::IT_NOP, Pm4::R_ZERO); } return true; }; const auto append_save_restore = [&] { // The native helper reserves 22 dwords, then emits 5-, 8-, and 9-dword packets. return buf->ReserveDW(22) && append(5) && append(8) && append(9); }; // Preserve native packet sizes, allocation boundaries, Push/Pop ordering, and // returned first-packet address. The native packets reference process-private state buffers, // so their net Cx operation is represented by the first HLE packet and type-3 NOPs. bool complete = false; switch (static_cast(operation)) { case ContextStateOperation::Clear: complete = append(5); break; case ContextStateOperation::Push: complete = append_save_restore() && append(3) && append(2); break; case ContextStateOperation::Pop: complete = append(3) && append_save_restore() && append(2); break; case ContextStateOperation::PushClear: complete = append_save_restore() && append(3) && append(2) && append(5); break; default: break; } return complete ? first : nullptr; } uint64_t KYTY_SYSV_ABI AgcDcbContextStateOpGetSize(uint32_t operation) { PRINT_NAME(); return static_cast(context_state_op_size_dw(operation)) * sizeof(uint32_t); } void KYTY_SYSV_ABI AgcGetIsTrinityMode(uint8_t* result) { *result = is_trinity_mode(); } static constexpr int GRAPHICS5_DRIVER_ERROR_INVALID_VALUE = static_cast(0x8a6c0033u); static constexpr int GRAPHICS5_DRIVER_ERROR_INVALID_ARGUMENT = static_cast(0x8a6c0035u); static constexpr uint32_t WORKLOAD_STREAM_RECORD_SIZE = 32; static constexpr uint32_t WORKLOAD_ACTIVE_PACKET_SIZE_DW = 18; static constexpr uint32_t WORKLOAD_COMPLETE_PACKET_SIZE_DW = 12; static constexpr uint32_t WORKLOAD_STREAM_MIN_ID = 1; static constexpr uint32_t WORKLOAD_STREAM_MAX_ID = 31; static constexpr uint32_t WORKLOAD_ID_MAX = 63; static constexpr uint32_t WORKLOAD_ACTIVE_COUNT_MAX = 63; static std::mutex g_workload_stream_mutex; static uint32_t g_workload_stream_mask = 0; static uint8_t g_workload_streams[WORKLOAD_STREAM_MAX_ID + 1][WORKLOAD_STREAM_RECORD_SIZE] {}; uint32_t KYTY_SYSV_ABI AgcDriverGetDefaultOwner() { PRINT_NAME(); return 0x8a6c9018u; } uint32_t KYTY_SYSV_ABI AgcDriverGetResourceRegistrationMaxNameLength() { PRINT_NAME(); return 0x8a6c9018u; } uint32_t KYTY_SYSV_ABI AgcDriverInitResourceRegistration() { PRINT_NAME(); return 0x8a6c9018u; } uint32_t KYTY_SYSV_ABI AgcDriverQueryResourceRegistrationUserMemoryRequirements(uint64_t* size_in_bytes) { PRINT_NAME(); if (size_in_bytes != nullptr) { *size_in_bytes = 0; } return 0x8a6c9018u; } int KYTY_SYSV_ABI AgcDriverRegisterOwner() { PRINT_NAME(); return static_cast(0x8a6c9018u); } int KYTY_SYSV_ABI AgcDriverRegisterResource() { PRINT_NAME(); return static_cast(0x8a6c9018u); } int KYTY_SYSV_ABI AgcDriverUnregisterOwnerAndResources(uint32_t owner_handle) { PRINT_NAME(); (void)owner_handle; return static_cast(0x8a6c9018u); } int KYTY_SYSV_ABI AgcDriverUnregisterResource() { PRINT_NAME(); return static_cast(0x8a6c9018u); } int KYTY_SYSV_ABI AgcDriverRegisterWorkloadStream(uint32_t stream_id, const void* stream) { PRINT_NAME(); LOGF("\t stream_id = %" PRIu32 "\n" "\t stream = 0x%016" PRIx64 "\n", stream_id, reinterpret_cast(stream)); if (stream_id < WORKLOAD_STREAM_MIN_ID || stream_id > WORKLOAD_STREAM_MAX_ID) { return GRAPHICS5_DRIVER_ERROR_INVALID_VALUE; } if (stream == nullptr) { return GRAPHICS5_DRIVER_ERROR_INVALID_ARGUMENT; } std::lock_guard lock(g_workload_stream_mutex); const uint32_t stream_bit = 1u << stream_id; if ((g_workload_stream_mask & stream_bit) != 0) { return GRAPHICS5_DRIVER_ERROR_INVALID_VALUE; } std::memset(g_workload_streams[stream_id], 0, WORKLOAD_STREAM_RECORD_SIZE); std::memcpy(g_workload_streams[stream_id], stream, WORKLOAD_STREAM_RECORD_SIZE); g_workload_stream_mask |= stream_bit; return OK; } uint32_t* KYTY_SYSV_ABI AgcCbNop(CommandBuffer* buf, uint32_t size_in_dwords) { if (buf == nullptr || size_in_dwords < 2) { return nullptr; } auto* cmd = buf->AllocateDW(size_in_dwords); if (cmd == nullptr) { return nullptr; } cmd[0] = KYTY_PM4(size_in_dwords, Pm4::IT_NOP, Pm4::R_ZERO); if (size_in_dwords > 1) { memset(cmd + 1, 0, static_cast(size_in_dwords - 1) * 4); } return cmd; } uint32_t KYTY_SYSV_ABI AgcCbNopGetSize(uint32_t size_in_dwords) { return 4u * size_in_dwords; } uint32_t* KYTY_SYSV_ABI AgcCbDispatch(CommandBuffer* buf, uint32_t thread_group_x, uint32_t thread_group_y, uint32_t thread_group_z, uint32_t modifier) { PRINT_NAME(); static std::atomic log_count {0}; if (log_count.fetch_add(1) < 64) { LOGF("\t thread_group_x = %" PRIu32 "\n" "\t thread_group_y = %" PRIu32 "\n" "\t thread_group_z = %" PRIu32 "\n" "\t modifier = 0x%08" PRIx32 "\n", thread_group_x, thread_group_y, thread_group_z, modifier); } if (buf == nullptr) { return nullptr; } auto* cmd = buf->AllocateDW(5); if (cmd == nullptr) { return nullptr; } cmd[0] = KYTY_PM4(5, Pm4::IT_DISPATCH_DIRECT, 0u); cmd[1] = thread_group_x; cmd[2] = thread_group_y; cmd[3] = thread_group_z; cmd[4] = (modifier & 0xa038u) | 0x41u; return cmd; } uint32_t KYTY_SYSV_ABI AgcCbDispatchGetSize() { PRINT_NAME(); return 20; } uint32_t* KYTY_SYSV_ABI AgcCbBranch(CommandBuffer* buf, uint8_t mode, uint8_t compare_function, const volatile uint64_t* compare_addr, uint64_t mask, uint64_t reference, uint8_t cache_policy1, const volatile uint32_t* buffer1, uint32_t size_in_dwords1, uint8_t cache_policy2, const volatile uint32_t* buffer2, uint32_t size_in_dwords2) { PRINT_NAME(); LOGF("\t mode = 0x%02" PRIx8 "\n" "\t compare_function = 0x%02" PRIx8 "\n" "\t compare_addr = 0x%016" PRIx64 "\n" "\t mask = 0x%016" PRIx64 "\n" "\t reference = 0x%016" PRIx64 "\n" "\t cache_policy1 = 0x%02" PRIx8 "\n" "\t buffer1 = 0x%016" PRIx64 "\n" "\t size_in_dwords1 = %" PRIu32 "\n" "\t cache_policy2 = 0x%02" PRIx8 "\n" "\t buffer2 = 0x%016" PRIx64 "\n" "\t size_in_dwords2 = %" PRIu32 "\n", mode, compare_function, reinterpret_cast(compare_addr), mask, reference, cache_policy1, reinterpret_cast(buffer1), size_in_dwords1, cache_policy2, reinterpret_cast(buffer2), size_in_dwords2); EXIT_NOT_IMPLEMENTED(buf == nullptr); auto* cmd = buf->AllocateDW(14); if (cmd == nullptr) { LOGF_COLOR(Log::Color::Red, "\t failed to allocate branch packet\n"); return nullptr; } const auto compare_vaddr = reinterpret_cast(compare_addr); const auto then_vaddr = reinterpret_cast(buffer1); const auto else_vaddr = reinterpret_cast(buffer2); cmd[0] = KYTY_PM4(14, Pm4::IT_INDIRECT_BUFFER, 0u); cmd[1] = (mode & 0x3u) | ((static_cast(compare_function) & 0x7u) << 8u); cmd[2] = static_cast(compare_vaddr & 0xfffffff8u); cmd[3] = static_cast((compare_vaddr >> 32u) & 0xffffffffu); cmd[4] = static_cast(mask & 0xffffffffu); cmd[5] = static_cast((mask >> 32u) & 0xffffffffu); cmd[6] = static_cast(reference & 0xffffffffu); cmd[7] = static_cast((reference >> 32u) & 0xffffffffu); cmd[8] = static_cast(then_vaddr & 0xfffffffcu); cmd[9] = static_cast((then_vaddr >> 32u) & 0xffffffffu); cmd[10] = (size_in_dwords1 & 0xfffffu) | ((static_cast(cache_policy1) & 0x3u) << 28u); cmd[11] = static_cast(else_vaddr & 0xfffffffcu); cmd[12] = static_cast((else_vaddr >> 32u) & 0xffffffffu); cmd[13] = (size_in_dwords2 & 0xfffffu) | ((static_cast(cache_policy2) & 0x3u) << 28u); return cmd; } uint32_t* KYTY_SYSV_ABI AgcCbSetShRegisterRangeDirect(CommandBuffer* buf, uint32_t offset, const uint32_t* values, uint32_t num_values) { PRINT_NAME(); static std::atomic log_count {0}; if (log_count.fetch_add(1) < 64) { LOGF("\t buf = 0x%016" PRIx64 "\n" "\t offset = %" PRIx32 "\n" "\t values = 0x%016" PRIx64 "\n" "\t num_values = %" PRIu32 "\n", reinterpret_cast(buf), offset, reinterpret_cast(values), num_values); } EXIT_NOT_IMPLEMENTED(buf == nullptr); buf->DbgDump(); auto* cmd = buf->AllocateDW(num_values + 2); if (cmd == nullptr) { LOGF_COLOR(Log::Color::Red, "\t failed to allocate set-register-range command\n"); return nullptr; } cmd[0] = KYTY_PM4(num_values + 2, Pm4::IT_SET_SH_REG, 0u); cmd[1] = offset & 0xffffu; if (values != nullptr) { memcpy(cmd + 2, values, static_cast(num_values) * 4); } return cmd; } uint32_t KYTY_SYSV_ABI AgcCbSetShRegisterRangeDirectGetSize(uint32_t num_values) { PRINT_NAME(); LOGF("\t num_values = %" PRIu32 "\n", num_values); return 4u * num_values + 8u; } uint32_t* KYTY_SYSV_ABI AgcCbSetShRegistersDirect(CommandBuffer* buf, const volatile ShaderRegister* regs, uint32_t num_regs) { PRINT_NAME(); AgcTrace("\t regs = 0x%016" PRIx64 "\n" "\t num_regs = %" PRIu32 "\n", reinterpret_cast(regs), num_regs); if (num_regs == 0) { return nullptr; } EXIT_NOT_IMPLEMENTED(buf == nullptr); EXIT_NOT_IMPLEMENTED(regs == nullptr); buf->DbgDump(); std::vector local_regs(num_regs); for (uint32_t i = 0; i < num_regs; i++) { local_regs[i].offset = regs[i].offset; local_regs[i].value = regs[i].value; } uint32_t* first_cmd = nullptr; uint32_t run_start_index = 0; uint32_t run_start_offset = local_regs[0].offset; uint32_t prev_offset = run_start_offset; for (uint32_t i = 1; i <= num_regs; i++) { const bool flush = (i == num_regs || local_regs[i].offset != prev_offset + 1u); if (flush) { const auto run_count = i - run_start_index; auto* cmd = buf->AllocateDW(run_count + 2u); if (cmd == nullptr) { LOGF_COLOR(Log::Color::Red, "\t failed to allocate set-sh-registers-direct command\n"); return first_cmd; } if (first_cmd == nullptr) { first_cmd = cmd; } cmd[0] = KYTY_PM4(run_count + 2u, Pm4::IT_SET_SH_REG, 0u); cmd[1] = run_start_offset & 0xffffu; for (uint32_t j = 0; j < run_count; j++) { cmd[j + 2u] = local_regs[run_start_index + j].value; } if (i < num_regs) { run_start_index = i; run_start_offset = local_regs[i].offset; } } if (i < num_regs) { prev_offset = local_regs[i].offset; } } return first_cmd; } uint32_t* KYTY_SYSV_ABI AgcCbSetUcRegistersDirect(CommandBuffer* buf, const volatile ShaderRegister* regs, uint32_t num_regs) { PRINT_NAME(); AgcTrace("\t regs = 0x%016" PRIx64 "\n" "\t num_regs = %" PRIu32 "\n", reinterpret_cast(regs), num_regs); if (num_regs == 0) { return nullptr; } EXIT_NOT_IMPLEMENTED(buf == nullptr); EXIT_NOT_IMPLEMENTED(regs == nullptr); buf->DbgDump(); // Original implementation stages register values in a temporary DWORD array. It reads each new run only // after the preceding run has been allocated, so preserve that ordering around grow callbacks. std::vector values(num_regs); values[0] = regs[0].value; uint32_t* first_cmd = nullptr; uint32_t run_start_index = 0; uint32_t run_start_offset = regs[0].offset; uint32_t prev_offset = run_start_offset; uint32_t i = 1; for (;;) { if (i < num_regs) { const auto offset = regs[i].offset; if (offset == prev_offset + 1u) { values[i] = regs[i].value; prev_offset = offset; i++; continue; } } const auto run_count = i - run_start_index; auto* cmd = buf->AllocateDW(run_count + 2u); if (cmd != nullptr) { if (first_cmd == nullptr) { first_cmd = cmd; } cmd[0] = KYTY_PM4(run_count + 2u, Pm4::IT_SET_UCONFIG_REG, 0u); cmd[1] = run_start_offset & 0xffffu; memcpy(cmd + 2, values.data() + run_start_index, static_cast(run_count) * sizeof(uint32_t)); } else { LOGF_COLOR(Log::Color::Red, "\t failed to allocate set-uc-registers-direct command\n"); } if (i == num_regs) { return first_cmd; } // Original implementation skips an intermediate run whose grow callback fails, then keeps scanning. run_start_index = i; run_start_offset = regs[i].offset; prev_offset = run_start_offset; values[i] = regs[i].value; i++; } } int KYTY_SYSV_ABI AgcDebugRaiseException(uint32_t exception_id) { PRINT_NAME(); LOGF("\t exception_id = 0x%08" PRIx32 "\n", exception_id); return OK; } uint32_t* KYTY_SYSV_ABI AgcCbReleaseMem(CommandBuffer* buf, uint8_t action, uint16_t gcr_cntl, uint8_t dst, uint8_t cache_policy, const volatile Label* address, uint8_t data_sel, uint64_t data, uint16_t gds_offset, uint16_t gds_size, uint8_t interrupt, uint32_t interrupt_ctx_id) { PRINT_NAME(); static std::atomic log_count {0}; if (log_count.fetch_add(1) < 64) { LOGF("\t action = 0x%02" PRIx8 "\n" "\t gcr_cntl = 0x%04" PRIx16 "\n" "\t dst = %" PRIu8 "\n" "\t cache_policy = 0x%02" PRIx8 "\n" "\t address = 0x%016" PRIx64 "\n" "\t data_sel = 0x%02" PRIx8 "\n" "\t data = 0x%016" PRIx64 "\n" "\t gds_offset = %" PRIu16 "\n" "\t gds_size = %" PRIu16 "\n" "\t interrupt = 0x%02" PRIx8 "\n" "\t interrupt_ctx_id = %" PRIu32 "\n", action, gcr_cntl, dst, cache_policy, reinterpret_cast(address), data_sel, data, gds_offset, gds_size, interrupt, interrupt_ctx_id); } EXIT_NOT_IMPLEMENTED(buf == nullptr); EXIT_NOT_IMPLEMENTED(dst > 1); EXIT_NOT_IMPLEMENTED(data_sel != 0 && data_sel != 1 && data_sel != 2 && data_sel != 3 && data_sel != 5); EXIT_NOT_IMPLEMENTED(interrupt > 4); buf->DbgDump(); auto* cmd = buf->AllocateDW(8); if (cmd == nullptr) { LOGF_COLOR(Log::Color::Red, "\t failed to allocate release_mem packet\n"); return nullptr; } auto packet_gcr_cntl = static_cast(gcr_cntl); if ((packet_gcr_cntl & 0x300u) == 0x100u) { packet_gcr_cntl |= 0x200u; } auto address_value = reinterpret_cast(address); auto packet_data = data; if ((interrupt & 0x7u) == 4u) { address_value = 0; packet_data = 0; } else if ((data_sel & 0x7u) == 5u) { packet_data = static_cast(gds_offset) | (static_cast(gds_size) << 16u); } const uint32_t packet_action = static_cast(action) & 0x3fu; const uint32_t packet_event_index = (action >= 0x2fu ? 6u : 5u); cmd[0] = KYTY_PM4(8, Pm4::IT_NOP, Pm4::R_RELEASE_MEM); cmd[1] = packet_action | (packet_event_index << 8u) | ((packet_gcr_cntl & 0xfffu) << 12u) | ((static_cast(cache_policy) & 0x3u) << 25u); cmd[2] = ((static_cast(dst) & 0x3u) << 16u) | ((static_cast(interrupt) & 0x7u) << 24u) | ((static_cast(data_sel) & 0x7u) << 29u); cmd[3] = static_cast(address_value & 0xfffffffcu); cmd[4] = static_cast((address_value >> 32u) & 0xffffffffu); cmd[5] = static_cast(packet_data & 0xffffffffu); cmd[6] = static_cast((packet_data >> 32u) & 0xffffffffu); cmd[7] = interrupt_ctx_id & 0x07ffffffu; return cmd; } uint32_t KYTY_SYSV_ABI AgcCbQueueEndOfPipeActionGetSize() { PRINT_NAME(); return 32; } uint32_t* KYTY_SYSV_ABI AgcAcbResetQueue(CommandBuffer* buf, uint32_t op) { PRINT_NAME(); LOGF("\t op = 0x%08" PRIx32 "\n", op); EXIT_NOT_IMPLEMENTED(buf == nullptr); EXIT_NOT_IMPLEMENTED((op & ~0x1c2u) != 0); buf->DbgDump(); auto* cmd = buf->AllocateDW(2); EXIT_NOT_IMPLEMENTED(cmd == nullptr); cmd[0] = KYTY_PM4(2, Pm4::IT_NOP, Pm4::R_DISPATCH_RESET); cmd[1] = 0; return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbResetQueue(CommandBuffer* buf, uint32_t op, uint32_t state) { PRINT_NAME(); LOGF("\t op = 0x%08" PRIx32 "\n" "\t state = 0x%08" PRIx32 "\n", op, state); EXIT_NOT_IMPLEMENTED(buf == nullptr); EXIT_NOT_IMPLEMENTED((op & ~0xfffu) != 0); buf->DbgDump(); auto* cmd = buf->AllocateDW(2); EXIT_NOT_IMPLEMENTED(cmd == nullptr); cmd[0] = KYTY_PM4(2, Pm4::IT_CLEAR_STATE, 0u); cmd[1] = state & 0xfu; return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbWaitUntilSafeForRendering(CommandBuffer* buf, uint32_t video_out_handle, uint32_t display_buffer_index) { PRINT_NAME(); LOGF("\t video_out_handle = %" PRIu32 "\n" "\t display_buffer_index = %" PRIu32 "\n", video_out_handle, display_buffer_index); EXIT_NOT_IMPLEMENTED(buf == nullptr); buf->DbgDump(); auto* cmd = buf->AllocateDW(7); EXIT_NOT_IMPLEMENTED(cmd == nullptr); cmd[0] = KYTY_PM4(7, Pm4::IT_NOP, Pm4::R_WAIT_FLIP_DONE); cmd[1] = video_out_handle; cmd[2] = display_buffer_index; cmd[3] = 0; cmd[4] = 0; cmd[5] = 0; cmd[6] = 0; return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbSetWorkloadsActive(CommandBuffer* buf, uint32_t stream_id, const uint32_t* workload_ids, uint32_t workload_count) { PRINT_NAME(); LOGF("\t stream_id = %" PRIu32 "\n" "\t workload_ids = 0x%016" PRIx64 "\n" "\t workload_count = %" PRIu32 "\n", stream_id, reinterpret_cast(workload_ids), workload_count); if (buf == nullptr || workload_ids == nullptr || workload_count == 0 || workload_count > WORKLOAD_ACTIVE_COUNT_MAX || stream_id < WORKLOAD_STREAM_MIN_ID || stream_id > WORKLOAD_STREAM_MAX_ID) { return nullptr; } uint64_t workload_mask = 0; for (uint32_t i = 0; i < workload_count; i++) { const auto workload_id = workload_ids[i]; if (workload_id > WORKLOAD_ID_MAX) { return nullptr; } const uint64_t workload_bit = 1ull << workload_id; if ((workload_mask & workload_bit) != 0) { return nullptr; } workload_mask |= workload_bit; } { std::lock_guard lock(g_workload_stream_mutex); if ((g_workload_stream_mask & (1u << stream_id)) == 0) { return nullptr; } } buf->DbgDump(); auto* cmd = buf->AllocateDW(WORKLOAD_ACTIVE_PACKET_SIZE_DW); if (cmd == nullptr) { return nullptr; } cmd[0] = KYTY_PM4(WORKLOAD_ACTIVE_PACKET_SIZE_DW, Pm4::IT_NOP, Pm4::R_ZERO); std::memset(cmd + 1, 0, static_cast(WORKLOAD_ACTIVE_PACKET_SIZE_DW - 1) * sizeof(uint32_t)); cmd[1] = stream_id; cmd[2] = static_cast(workload_mask & 0xffffffffu); cmd[3] = static_cast((workload_mask >> 32u) & 0xffffffffu); return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbSetWorkloadComplete(CommandBuffer* buf, uint32_t stream_id, uint32_t workload_id) { PRINT_NAME(); LOGF("\t stream_id = %" PRIu32 "\n" "\t workload_id = %" PRIu32 "\n", stream_id, workload_id); if (buf == nullptr || stream_id < WORKLOAD_STREAM_MIN_ID || stream_id > WORKLOAD_STREAM_MAX_ID || workload_id > WORKLOAD_ID_MAX) { return nullptr; } { std::lock_guard lock(g_workload_stream_mutex); if ((g_workload_stream_mask & (1u << stream_id)) == 0) { return nullptr; } } buf->DbgDump(); auto* cmd = buf->AllocateDW(WORKLOAD_COMPLETE_PACKET_SIZE_DW); if (cmd == nullptr) { return nullptr; } const uint64_t workload_clear_mask = ~(1ull << workload_id); cmd[0] = KYTY_PM4(WORKLOAD_COMPLETE_PACKET_SIZE_DW, Pm4::IT_NOP, Pm4::R_ZERO); std::memset(cmd + 1, 0, static_cast(WORKLOAD_COMPLETE_PACKET_SIZE_DW - 1) * sizeof(uint32_t)); cmd[1] = stream_id; cmd[2] = workload_id; cmd[3] = static_cast(workload_clear_mask & 0xffffffffu); cmd[4] = static_cast((workload_clear_mask >> 32u) & 0xffffffffu); return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbSetShRegisterDirect(CommandBuffer* buf, ShaderRegister reg) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(buf == nullptr); buf->DbgDump(); auto* cmd = buf->AllocateDW(3); if (cmd == nullptr) { LOGF_COLOR(Log::Color::Red, "\t failed to allocate set-sh-register-direct command\n"); return nullptr; } cmd[0] = KYTY_PM4(3, Pm4::IT_SET_SH_REG, 0u); cmd[1] = reg.offset & 0xffffu; cmd[2] = reg.value; return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbSetCxRegisterDirect(CommandBuffer* buf, ShaderRegister reg) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(buf == nullptr); buf->DbgDump(); auto* cmd = buf->AllocateDW(3); if (cmd == nullptr) { LOGF_COLOR(Log::Color::Red, "\t failed to allocate set-cx-register-direct command\n"); return nullptr; } cmd[0] = KYTY_PM4(3, Pm4::IT_SET_CONTEXT_REG, 0u); cmd[1] = reg.offset & 0xffffu; cmd[2] = reg.value; return cmd; } uint32_t KYTY_SYSV_ABI AgcDcbSetCxRegisterDirectGetSize() { PRINT_NAME(); return 12; } uint32_t* KYTY_SYSV_ABI AgcDcbSetUcRegisterDirect(CommandBuffer* buf, ShaderRegister reg) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(buf == nullptr); buf->DbgDump(); auto* cmd = buf->AllocateDW(3); if (cmd == nullptr) { LOGF_COLOR(Log::Color::Red, "\t failed to allocate set-uc-register-direct command\n"); return nullptr; } cmd[0] = KYTY_PM4(3, Pm4::IT_SET_UCONFIG_REG, 0u); cmd[1] = reg.offset & 0xffffu; cmd[2] = reg.value; return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbSetCxRegistersIndirect(CommandBuffer* buf, const volatile ShaderRegister* regs, uint32_t num_regs) { PRINT_NAME(); AgcTrace("\t regs = 0x%016" PRIx64 "\n" "\t num_regs = %" PRIu32 "\n", reinterpret_cast(regs), num_regs); EXIT_NOT_IMPLEMENTED(buf == nullptr); buf->DbgDump(); auto* cmd = buf->AllocateDW(5); EXIT_NOT_IMPLEMENTED(cmd == nullptr); auto vaddr = reinterpret_cast(regs); reg_indirect_write_packet(cmd, vaddr, num_regs, RegIndirectPacket::Cx); return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbSetShRegistersIndirect(CommandBuffer* buf, const volatile ShaderRegister* regs, uint32_t num_regs) { PRINT_NAME(); AgcTrace("\t regs = 0x%016" PRIx64 "\n" "\t num_regs = %" PRIu32 "\n", reinterpret_cast(regs), num_regs); EXIT_NOT_IMPLEMENTED(buf == nullptr); buf->DbgDump(); auto* cmd = buf->AllocateDW(5); EXIT_NOT_IMPLEMENTED(cmd == nullptr); auto vaddr = reinterpret_cast(regs); reg_indirect_write_packet(cmd, vaddr, num_regs, RegIndirectPacket::Sh); return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbSetUcRegistersIndirect(CommandBuffer* buf, const volatile ShaderRegister* regs, uint32_t num_regs) { PRINT_NAME(); AgcTrace("\t regs = 0x%016" PRIx64 "\n" "\t num_regs = %" PRIu32 "\n", reinterpret_cast(regs), num_regs); EXIT_NOT_IMPLEMENTED(buf == nullptr); buf->DbgDump(); auto* cmd = buf->AllocateDW(5); EXIT_NOT_IMPLEMENTED(cmd == nullptr); auto vaddr = reinterpret_cast(regs); reg_indirect_write_packet(cmd, vaddr, num_regs, RegIndirectPacket::Uc); return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbSetIndexSize(CommandBuffer* buf, uint8_t index_size, uint8_t cache_policy) { PRINT_NAME(); AgcTrace("\t index_size = 0x%" PRIx8 "\n" "\t cache_policy = 0x%" PRIx8 "\n", index_size, cache_policy); EXIT_NOT_IMPLEMENTED(buf == nullptr); buf->DbgDump(); auto* cmd = buf->AllocateDW(3); EXIT_NOT_IMPLEMENTED(cmd == nullptr); cmd[0] = KYTY_PM4(3, Pm4::IT_SET_UCONFIG_REG_INDEX, 0u); cmd[1] = 0x20000000u | Pm4::VGT_INDEX_TYPE; cmd[2] = 0x400u | (index_size & 0x3u) | ((cache_policy & 0x3u) << 6u); return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbSetIndexBuffer(CommandBuffer* buf, uint64_t index_addr) { PRINT_NAME(); AgcTrace("\t index_addr = 0x%016" PRIx64 "\n", index_addr); EXIT_NOT_IMPLEMENTED(buf == nullptr); EXIT_NOT_IMPLEMENTED((index_addr & 1u) != 0); buf->DbgDump(); auto* cmd = buf->AllocateDW(3); EXIT_NOT_IMPLEMENTED(cmd == nullptr); cmd[0] = KYTY_PM4(3, Pm4::IT_INDEX_BASE, 0u); cmd[1] = static_cast(index_addr & 0xffffffffu); cmd[2] = static_cast((index_addr >> 32u) & 0xffffffffu); return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbSetIndexCount(CommandBuffer* buf, uint32_t index_count) { PRINT_NAME(); AgcTrace("\t index_count = 0x%" PRIx32 "\n", index_count); EXIT_NOT_IMPLEMENTED(buf == nullptr); buf->DbgDump(); auto* cmd = buf->AllocateDW(2); EXIT_NOT_IMPLEMENTED(cmd == nullptr); cmd[0] = KYTY_PM4(2, Pm4::IT_INDEX_BUFFER_SIZE, 0u); cmd[1] = index_count; return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbSetNumInstances(CommandBuffer* buf, uint32_t num_instances) { PRINT_NAME(); AgcTrace("\t num_instances = 0x%" PRIx32 "\n", num_instances); if (buf == nullptr) { return nullptr; } buf->DbgDump(); auto* cmd = buf->AllocateDW(2); if (cmd == nullptr) { return nullptr; } cmd[0] = KYTY_PM4(2, Pm4::IT_NUM_INSTANCES, 0u); cmd[1] = num_instances; return cmd; } uint32_t KYTY_SYSV_ABI AgcDcbSetNumInstancesGetSize() { PRINT_NAME(); return 8; } static uint32_t extract_modifier_bits(uint32_t modifier, uint32_t start, uint32_t count) { return (modifier >> start) & ((1u << count) - 1u); } static uint32_t indirect_modifier_sgpr_base(uint32_t modifier) { const auto stage = modifier >> 29u; return ((stage == 3u || stage == 5u) ? 0x80u : 0u) + 0x8cu; } static uint64_t decode_indirect_modifier_patch_offsets(uint64_t modifier, bool indexed) { const auto low = static_cast(modifier); const auto sgpr_base = indirect_modifier_sgpr_base(low); uint64_t base_vtx_loc = 0x280u; if ((low & 0x1u) != 0) { base_vtx_loc = sgpr_base + extract_modifier_bits(low, 9u, 5u); } uint64_t start_inst_loc = 0x280u; if ((low & 0x4u) != 0) { start_inst_loc = sgpr_base + extract_modifier_bits(low, 19u, 5u); } if (indexed && (low & 0x2u) != 0) { base_vtx_loc |= static_cast(sgpr_base + extract_modifier_bits(low, 14u, 5u)) << 16u; base_vtx_loc |= 1ull << 59u; } return base_vtx_loc | (start_inst_loc << 32u); } static uint32_t decode_indirect_draw_initiator(uint64_t modifier) { const auto low = static_cast(modifier); uint32_t initiator = 2u; if ((modifier & (1ull << 32u)) == 0) { initiator = ((low >> 3u) & 0x20u) | 2u; } return initiator; } static uint32_t decode_draw_index_initiator(uint64_t modifier) { if ((modifier & (1ull << 32u)) != 0) { return 0; } return (static_cast(modifier) >> 3u) & 0x20u; } static constexpr uint32_t AGC_INTERNAL_DATA_REGISTER = 0x342u; static constexpr uint32_t AGC_DRAW_INDIRECT_MULTI_BEGIN_TAG = 0xc6000008u; static constexpr uint32_t AGC_DRAW_INDIRECT_MULTI_END_TAG = 0xc6000000u; static constexpr uint32_t AGC_WAIT_USER_DATA_BEGIN_32_TAG = 0xc8010000u; static constexpr uint32_t AGC_WAIT_USER_DATA_BEGIN_64_TAG = 0xc8020000u; static constexpr uint32_t AGC_WAIT_USER_DATA_END_TAG = 0xc8000000u; static constexpr uint32_t AGC_WAIT_USER_DATA_BEGIN_DW = 4u; static constexpr uint32_t AGC_INTERNAL_DATA_PACKET_SIZE_DW = 3u; static void write_agc_internal_data_packet(uint32_t* cmd, uint32_t tag) { cmd[0] = KYTY_PM4(AGC_INTERNAL_DATA_PACKET_SIZE_DW, Pm4::IT_SET_UCONFIG_REG, 1u); cmd[1] = AGC_INTERNAL_DATA_REGISTER; cmd[2] = tag; } uint32_t* KYTY_SYSV_ABI AgcDcbDrawIndex(CommandBuffer* buf, uint32_t index_count, const volatile void* index_addr, uint64_t modifier) { PRINT_NAME(); AgcTrace("\t index_count = 0x%" PRIx32 "\n" "\t index_addr = 0x%016" PRIx64 "\n" "\t modifier = 0x%016" PRIx64 "\n", index_count, reinterpret_cast(index_addr), modifier); EXIT_NOT_IMPLEMENTED(buf == nullptr); EXIT_NOT_IMPLEMENTED(index_addr == nullptr); EXIT_NOT_IMPLEMENTED((reinterpret_cast(index_addr) & 1u) != 0); buf->DbgDump(); auto* cmd = buf->AllocateDW(6); EXIT_NOT_IMPLEMENTED(cmd == nullptr); auto vaddr = reinterpret_cast(index_addr); cmd[0] = KYTY_PM4(6, Pm4::IT_DRAW_INDEX_2, 0u); cmd[1] = (index_count == 0 ? 1u : index_count); cmd[2] = static_cast(vaddr & 0xffffffffu); cmd[3] = static_cast((vaddr >> 32u) & 0xffffffffu); cmd[4] = index_count; cmd[5] = decode_draw_index_initiator(modifier); return cmd; } uint32_t KYTY_SYSV_ABI AgcDcbDrawIndexGetSize() { PRINT_NAME(); return 6u * 4u; } uint32_t* KYTY_SYSV_ABI AgcDcbDrawIndexMultiInstanced(CommandBuffer* buf, uint32_t index_count, const volatile void* index_addr, const volatile void* object_ids, uint32_t instance_count, uint64_t modifier) { PRINT_NAME(); AgcTrace("\t index_count = 0x%" PRIx32 "\n" "\t index_addr = 0x%016" PRIx64 "\n" "\t instance_count = 0x%" PRIx32 "\n" "\t object_ids = 0x%016" PRIx64 "\n" "\t modifier = 0x%016" PRIx64 "\n", index_count, reinterpret_cast(index_addr), instance_count, reinterpret_cast(object_ids), modifier); EXIT_NOT_IMPLEMENTED(buf == nullptr); EXIT_NOT_IMPLEMENTED(index_addr == nullptr); EXIT_NOT_IMPLEMENTED(object_ids == nullptr); EXIT_NOT_IMPLEMENTED((reinterpret_cast(index_addr) & 1u) != 0); buf->DbgDump(); auto* cmd = buf->AllocateDW(9); EXIT_NOT_IMPLEMENTED(cmd == nullptr); auto index_vaddr = reinterpret_cast(index_addr); auto object_vaddr = reinterpret_cast(object_ids); cmd[0] = KYTY_PM4(9, Pm4::IT_DISPATCH_DRAW_PREAMBLE, 0u); cmd[1] = index_count; cmd[2] = static_cast(index_vaddr & 0xffffffffu); cmd[3] = static_cast((index_vaddr >> 32u) & 0xffffffffu); cmd[4] = (instance_count == 0 ? 1u : instance_count); cmd[5] = static_cast(object_vaddr & 0xffffffffu); cmd[6] = static_cast((object_vaddr >> 32u) & 0xffffffffu); cmd[7] = instance_count; cmd[8] = decode_draw_index_initiator(modifier) | 0x80u; return cmd; } uint32_t KYTY_SYSV_ABI AgcDcbDrawIndexMultiInstancedGetSize() { PRINT_NAME(); return 9u * 4u; } int KYTY_SYSV_ABI AgcUnknownIkfdtRIqCE(uint32_t* cmd, uint64_t arg1, const volatile uint32_t* target, uint32_t size_in_dwords, uint64_t arg4, uint64_t arg5) { PRINT_NAME(); LOGF("\t cmd = 0x%016" PRIx64 "\n" "\t arg1 = 0x%016" PRIx64 "\n" "\t target = 0x%016" PRIx64 "\n" "\t size_in_dwords = %" PRIu32 "\n" "\t arg4 = 0x%016" PRIx64 "\n" "\t arg5 = 0x%016" PRIx64 "\n", reinterpret_cast(cmd), arg1, reinterpret_cast(target), size_in_dwords, arg4, arg5); EXIT_NOT_IMPLEMENTED(cmd == nullptr); auto op = (cmd[0] >> 8u) & 0xffu; if (op != Pm4::IT_INDIRECT_BUFFER) { return GRAPHICS5_ERROR_INVALID_PACKET; } auto vaddr = reinterpret_cast(target); auto cache_policy = static_cast(arg1) & 0x3u; cmd[1] = (cmd[1] & 0x3u) | (static_cast(vaddr) & 0xfffffffcu); cmd[2] = static_cast(vaddr >> 32u); cmd[3] = (cmd[3] & 0xcff00000u) | (cache_policy << 28u) | (size_in_dwords & 0xfffffu); return OK; } uint32_t* KYTY_SYSV_ABI AgcDcbDrawIndexAuto(CommandBuffer* buf, uint32_t index_count, uint64_t modifier) { PRINT_NAME(); AgcTrace("\t index_count = 0x%" PRIx32 "\n" "\t modifier = 0x%016" PRIx64 "\n", index_count, modifier); EXIT_NOT_IMPLEMENTED(buf == nullptr); buf->DbgDump(); auto* cmd = buf->AllocateDW(3); EXIT_NOT_IMPLEMENTED(cmd == nullptr); cmd[0] = KYTY_PM4(3, Pm4::IT_DRAW_INDEX_AUTO, 0u); cmd[1] = index_count; cmd[2] = decode_draw_index_initiator(modifier) | 0x2u; return cmd; } uint32_t KYTY_SYSV_ABI AgcDcbDrawIndexAutoGetSize() { PRINT_NAME(); return 3u * 4u; } uint32_t* KYTY_SYSV_ABI AgcDcbDrawIndexOffset(CommandBuffer* buf, uint32_t index_offset, uint32_t index_count, uint64_t modifier) { PRINT_NAME(); AgcTrace("\t index_offset = 0x%" PRIx32 "\n" "\t index_count = 0x%" PRIx32 "\n" "\t modifier = 0x%016" PRIx64 "\n", index_offset, index_count, modifier); EXIT_NOT_IMPLEMENTED(buf == nullptr); buf->DbgDump(); auto* cmd = buf->AllocateDW(5); EXIT_NOT_IMPLEMENTED(cmd == nullptr); cmd[0] = KYTY_PM4(5, Pm4::IT_DRAW_INDEX_OFFSET_2, 0u); cmd[1] = (index_count == 0 ? 1u : index_count); cmd[2] = index_offset; cmd[3] = index_count; cmd[4] = decode_draw_index_initiator(modifier); return cmd; } uint32_t KYTY_SYSV_ABI AgcDcbDrawIndexOffsetGetSize() { PRINT_NAME(); return 5u * 4u; } uint32_t* KYTY_SYSV_ABI AgcDcbSetBaseIndirectArgs(CommandBuffer* buf, uint32_t shader_type, const volatile void* indirect_base_addr) { PRINT_NAME(); LOGF("\t shader_type = %" PRIu32 "\n" "\t indirect_base_addr = 0x%016" PRIx64 "\n", shader_type, reinterpret_cast(indirect_base_addr)); EXIT_NOT_IMPLEMENTED(buf == nullptr); buf->DbgDump(); auto* cmd = buf->AllocateDW(4); EXIT_NOT_IMPLEMENTED(cmd == nullptr); const auto addr = reinterpret_cast(indirect_base_addr); cmd[0] = KYTY_PM4(4, Pm4::IT_SET_BASE, 0u) | (shader_type << 1u); cmd[1] = 1u; cmd[2] = static_cast(addr) & ~0x7u; cmd[3] = static_cast(addr >> 32u); return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbDrawIndexIndirect(CommandBuffer* buf, uint32_t data_offset_in_bytes, uint64_t modifier) { PRINT_NAME(); AgcTrace("\t data_offset = 0x%" PRIx32 "\n" "\t modifier = 0x%016" PRIx64 "\n", data_offset_in_bytes, modifier); EXIT_NOT_IMPLEMENTED(buf == nullptr); buf->DbgDump(); auto* cmd = buf->AllocateDW(5); EXIT_NOT_IMPLEMENTED(cmd == nullptr); const auto patch_offsets = decode_indirect_modifier_patch_offsets(modifier, true); cmd[0] = KYTY_PM4(5, Pm4::IT_DRAW_INDEX_INDIRECT, 0u); cmd[1] = data_offset_in_bytes; cmd[2] = static_cast(patch_offsets); cmd[3] = static_cast(patch_offsets >> 32u); cmd[4] = decode_indirect_draw_initiator(modifier); return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbDrawIndirect(CommandBuffer* buf, uint32_t data_offset_in_bytes, uint64_t modifier) { PRINT_NAME(); AgcTrace("\t data_offset = 0x%" PRIx32 "\n" "\t modifier = 0x%016" PRIx64 "\n", data_offset_in_bytes, modifier); EXIT_NOT_IMPLEMENTED(buf == nullptr); buf->DbgDump(); auto* cmd = buf->AllocateDW(5); EXIT_NOT_IMPLEMENTED(cmd == nullptr); const auto patch_offsets = decode_indirect_modifier_patch_offsets(modifier, false); cmd[0] = KYTY_PM4(5, Pm4::IT_DRAW_INDIRECT, 0u); cmd[1] = data_offset_in_bytes; cmd[2] = static_cast(patch_offsets); cmd[3] = static_cast(patch_offsets >> 32u); cmd[4] = decode_indirect_draw_initiator(modifier); return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbDrawIndirectMulti(CommandBuffer* buf, uint32_t data_offset_in_bytes, uint32_t count_indirect, uint32_t max_count_or_count, const volatile void* count_addr, uint32_t stride_in_bytes, uint64_t modifier) { PRINT_NAME(); AgcTrace("\t data_offset = 0x%" PRIx32 "\n" "\t count_indirect = 0x%" PRIx32 "\n" "\t max_count_or_count = 0x%" PRIx32 "\n" "\t count_addr = 0x%016" PRIx64 "\n" "\t stride_in_bytes = 0x%" PRIx32 "\n" "\t modifier = 0x%016" PRIx64 "\n", data_offset_in_bytes, count_indirect, max_count_or_count, reinterpret_cast(count_addr), stride_in_bytes, modifier); if (buf == nullptr) { return nullptr; } buf->DbgDump(); constexpr uint32_t draw_packet_size_dw = 10u; constexpr uint32_t total_size_dw = AGC_INTERNAL_DATA_PACKET_SIZE_DW + draw_packet_size_dw + AGC_INTERNAL_DATA_PACKET_SIZE_DW; auto* cmd = buf->AllocateDW(total_size_dw); if (cmd == nullptr) { return nullptr; } write_agc_internal_data_packet(cmd, AGC_DRAW_INDIRECT_MULTI_BEGIN_TAG); auto* draw = cmd + AGC_INTERNAL_DATA_PACKET_SIZE_DW; auto* end = draw + draw_packet_size_dw; write_agc_internal_data_packet(end, AGC_DRAW_INDIRECT_MULTI_END_TAG); const auto low = static_cast(modifier); const auto sgpr_base = indirect_modifier_sgpr_base(low); const auto patch_offsets = decode_indirect_modifier_patch_offsets(modifier, false); const auto count_vaddr = reinterpret_cast(count_addr); uint32_t draw_index_location = 0x280u; if ((low & 0x8u) != 0) { draw_index_location = sgpr_base + extract_modifier_bits(low, 24u, 5u); } const auto draw_control = draw_index_location | ((low & 0x10u) << 23u) | ((count_indirect & 0x1u) << 30u) | ((low & 0x8u) << 28u); draw[0] = KYTY_PM4(draw_packet_size_dw, Pm4::IT_DRAW_INDIRECT_MULTI, 0u); draw[1] = data_offset_in_bytes; draw[2] = static_cast(patch_offsets); draw[3] = static_cast(patch_offsets >> 32u); draw[4] = draw_control; draw[5] = max_count_or_count; draw[6] = static_cast(count_vaddr) & ~0x3u; draw[7] = static_cast(count_vaddr >> 32u); draw[8] = stride_in_bytes; draw[9] = decode_indirect_draw_initiator(modifier); return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbDrawIndexIndirectMulti(CommandBuffer* buf, uint32_t data_offset_in_bytes, uint32_t count_indirect, uint32_t max_count_or_count, const volatile void* count_addr, uint32_t stride_in_bytes, uint64_t modifier) { PRINT_NAME(); AgcTrace("\t data_offset = 0x%" PRIx32 "\n" "\t count_indirect = 0x%" PRIx32 "\n" "\t max_count_or_count = 0x%" PRIx32 "\n" "\t count_addr = 0x%016" PRIx64 "\n" "\t stride_in_bytes = 0x%" PRIx32 "\n" "\t modifier = 0x%016" PRIx64 "\n", data_offset_in_bytes, count_indirect, max_count_or_count, reinterpret_cast(count_addr), stride_in_bytes, modifier); EXIT_NOT_IMPLEMENTED(buf == nullptr); EXIT_NOT_IMPLEMENTED((count_indirect & ~1u) != 0); buf->DbgDump(); auto* cmd = buf->AllocateDW(10); EXIT_NOT_IMPLEMENTED(cmd == nullptr); const auto patch_offsets = decode_indirect_modifier_patch_offsets(modifier, true); const auto count_vaddr = reinterpret_cast(count_addr); cmd[0] = KYTY_PM4(10, Pm4::IT_DRAW_INDEX_INDIRECT_MULTI, 0u); cmd[1] = data_offset_in_bytes; cmd[2] = static_cast(patch_offsets); cmd[3] = static_cast(patch_offsets >> 32u); cmd[4] = (count_indirect & 1u) << 30u; cmd[5] = max_count_or_count; cmd[6] = static_cast(count_vaddr) & ~0x3u; cmd[7] = static_cast(count_vaddr >> 32u); cmd[8] = stride_in_bytes; cmd[9] = decode_indirect_draw_initiator(modifier); return cmd; } uint32_t KYTY_SYSV_ABI AgcDcbDrawIndirectGetSize() { PRINT_NAME(); return 5u * 4u; } uint32_t* KYTY_SYSV_ABI AgcDcbDispatchIndirect(CommandBuffer* buf, uint32_t data_offset_in_bytes, uint32_t flags) { PRINT_NAME(); LOGF("\t data_offset = 0x%" PRIx32 "\n" "\t flags = 0x%08" PRIx32 "\n", data_offset_in_bytes, flags); EXIT_NOT_IMPLEMENTED(buf == nullptr); buf->DbgDump(); auto* cmd = buf->AllocateDW(3); EXIT_NOT_IMPLEMENTED(cmd == nullptr); cmd[0] = KYTY_PM4(3, Pm4::IT_DISPATCH_INDIRECT, 0u); cmd[1] = data_offset_in_bytes; cmd[2] = (flags & 0xa038u) | 0x41u; return cmd; } uint32_t KYTY_SYSV_ABI AgcDcbDispatchIndirectGetSize() { PRINT_NAME(); return 3u * 4u; } uint32_t* KYTY_SYSV_ABI AgcDcbEventWrite(CommandBuffer* buf, uint8_t event_type, const volatile void* address) { PRINT_NAME(); static std::atomic log_count {0}; if (log_count.fetch_add(1) < 64) { LOGF("\t event_type = 0x%02" PRIx8 "\n" "\t address = 0x%016" PRIx64 "\n", event_type, reinterpret_cast(address)); } EXIT_NOT_IMPLEMENTED(buf == nullptr); EXIT_NOT_IMPLEMENTED(event_type > 0x3fu); buf->DbgDump(); const bool addressed_event = ((event_type & 0xfeu) == 0x38u); auto* cmd = buf->AllocateDW(addressed_event ? 4u : 2u); EXIT_NOT_IMPLEMENTED(cmd == nullptr); cmd[0] = KYTY_PM4(addressed_event ? 4u : 2u, Pm4::IT_EVENT_WRITE, 0u); if (event_type == 7u || event_type == 15u || event_type == 16u) { cmd[1] = 0x400u | event_type; } else if (addressed_event) { auto addr = reinterpret_cast(address); cmd[1] = 0x100u | event_type; cmd[2] = static_cast(addr) & 0xfffffff8u; cmd[3] = static_cast(addr >> 32u); } else { cmd[1] = event_type & 0x3fu; } return cmd; } uint64_t KYTY_SYSV_ABI AgcDcbEventWriteGetSize(uint8_t event_type) { PRINT_NAME(); return (event_type & 0xfeu) == 0x38u ? 16u : 8u; } uint32_t* KYTY_SYSV_ABI AgcAcbEventWrite(CommandBuffer* buf, uint8_t event_type, const volatile void* /*address*/) { if (buf == nullptr) { return nullptr; } auto* cmd = buf->AllocateDW(2); if (cmd == nullptr) { return nullptr; } cmd[0] = KYTY_PM4(2, Pm4::IT_EVENT_WRITE, 0u); cmd[1] = (static_cast(event_type) & 0x3fu) | (event_type == 7u ? 0x400u : 0u); return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbAcquireMem(CommandBuffer* buf, uint8_t engine, uint32_t cb_db_op, uint32_t gcr_cntl, const volatile void* base, uint64_t size_bytes, uint32_t poll_cycles) { PRINT_NAME(); static std::atomic log_count {0}; if (log_count.fetch_add(1) < 64) { LOGF("\t engine = 0x%02" PRIx8 "\n" "\t cb_db_op = 0x%08" PRIx32 "\n" "\t gcr_cntl = 0x%08" PRIx32 "\n" "\t base = 0x%016" PRIx64 "\n" "\t size_bytes = 0x%016" PRIx64 "\n" "\t poll_cycles = %" PRIu32 "\n", engine, cb_db_op, gcr_cntl, reinterpret_cast(base), size_bytes, poll_cycles); } bool no_size = (static_cast(size_bytes) == -1); auto vaddr = reinterpret_cast(base); EXIT_NOT_IMPLEMENTED(buf == nullptr); static std::atomic warning_log_count {0}; const bool log_warning = (warning_log_count.fetch_add(1) < 64); if (!no_size && (size_bytes & 0xffu) != 0) { if (log_warning) { LOGF_COLOR(Log::Color::Red, "\t warning: size_bytes is not 256-byte aligned\n"); } } if (!no_size && (size_bytes >> 40u) != 0) { if (log_warning) { LOGF_COLOR(Log::Color::Red, "\t warning: size_bytes is too large\n"); } } if ((vaddr & 0xffu) != 0) { if (log_warning) { LOGF_COLOR(Log::Color::Red, "\t warning: base is not 256-byte aligned\n"); } } if ((vaddr >> 40u) != 0) { if (log_warning) { LOGF_COLOR(Log::Color::Red, "\t warning: base is too high\n"); } } if (engine > 1) { if (log_warning) { LOGF_COLOR(Log::Color::Red, "\t warning: unsupported engine 0x%02" PRIx8 "\n", engine); } } buf->DbgDump(); auto* cmd = buf->AllocateDW(8); if (cmd == nullptr) { LOGF_COLOR(Log::Color::Red, "\t failed to allocate acquire_mem packet\n"); return nullptr; } cmd[0] = KYTY_PM4(8, Pm4::IT_NOP, Pm4::R_ACQUIRE_MEM); cmd[1] = (static_cast(engine & 1u) << 31u) | cb_db_op; cmd[2] = (no_size ? 0 : static_cast(size_bytes >> 8u)); cmd[3] = 0; cmd[4] = static_cast(vaddr >> 8u); cmd[5] = 0; cmd[6] = poll_cycles / 40; cmd[7] = gcr_cntl; return cmd; } uint32_t KYTY_SYSV_ABI AgcDcbAcquireMemGetSize() { PRINT_NAME(); return 8u * 4u; } uint32_t* KYTY_SYSV_ABI AgcAcbAcquireMem(CommandBuffer* buf, uint32_t gcr_cntl, const volatile void* base, uint64_t size_bytes, uint32_t poll_cycles) { return AgcDcbAcquireMem(buf, 1, 0, gcr_cntl, base, size_bytes, poll_cycles); } uint32_t KYTY_SYSV_ABI AgcAcbAcquireMemGetSize() { PRINT_NAME(); return AgcDcbAcquireMemGetSize(); } uint32_t* KYTY_SYSV_ABI AgcDcbCondExec(CommandBuffer* buf, const volatile uint32_t* address, uint32_t num_dwords) { PRINT_NAME(); LOGF("\t address = 0x%016" PRIx64 "\n" "\t num_dwords = %" PRIu32 "\n", reinterpret_cast(address), num_dwords); if (buf == nullptr || address == nullptr) { LOGF_COLOR(Log::Color::Red, "\t invalid arguments\n"); return nullptr; } if ((reinterpret_cast(address) & 0x3u) != 0) { LOGF_COLOR(Log::Color::Red, "\t condExec command address is not 4-byte aligned\n"); return nullptr; } if (num_dwords > 0x3fffu) { LOGF_COLOR(Log::Color::Red, "\t condExec range is too large: %" PRIu32 "\n", num_dwords); return nullptr; } buf->DbgDump(); auto* cmd = buf->AllocateDW(5); if (cmd == nullptr) { LOGF_COLOR(Log::Color::Red, "\t failed to allocate condExec packet\n"); return nullptr; } auto addr = reinterpret_cast(address); cmd[0] = KYTY_PM4(5, Pm4::IT_COND_EXEC, 0u); cmd[1] = static_cast(addr) & 0xfffffffcu; cmd[2] = static_cast(addr >> 32u); cmd[3] = 0; cmd[4] = num_dwords & 0x3fffu; return cmd; } uint32_t KYTY_SYSV_ABI AgcDcbCondExecGetSize() { PRINT_NAME(); return 20; } uint32_t* KYTY_SYSV_ABI AgcAcbCondExec(CommandBuffer* buf, const volatile uint32_t* address, uint32_t num_dwords) { return AgcDcbCondExec(buf, address, num_dwords); } uint32_t KYTY_SYSV_ABI AgcAcbCondExecGetSize() { PRINT_NAME(); return AgcDcbCondExecGetSize(); } uint32_t* KYTY_SYSV_ABI AgcAcbJump(CommandBuffer* buf, uint8_t cache_policy, const uint32_t* target, uint32_t size_in_dwords) { if (buf == nullptr) { return nullptr; } auto* cmd = buf->AllocateDW(4); if (cmd == nullptr) { return nullptr; } auto target_address = reinterpret_cast(target); cmd[0] = KYTY_PM4(4, Pm4::IT_INDIRECT_BUFFER, 0u); cmd[1] = static_cast(target_address) & ~0x3u; cmd[2] = static_cast(target_address >> 32u); cmd[3] = 0x0f900000u | ((static_cast(cache_policy) & 0x3u) << 28u) | (size_in_dwords & 0xfffffu); return cmd; } uint32_t KYTY_SYSV_ABI AgcAcbJumpGetSize() { return 0x10u; } uint32_t* KYTY_SYSV_ABI AgcAcbWaitRegMem(CommandBuffer* buf, uint8_t size, uint8_t compare_function, uint8_t cache_policy, const volatile void* address, uint64_t reference, uint64_t mask, uint32_t poll_cycles) { return AgcDcbWaitRegMem(buf, size, compare_function, 0, cache_policy, address, reference, mask, poll_cycles); } uint64_t KYTY_SYSV_ABI AgcAcbWaitOnAddressGetSize(uint8_t size) { PRINT_NAME(); return AgcDcbWaitOnAddressGetSize(size); } uint32_t* KYTY_SYSV_ABI AgcAcbDmaData(CommandBuffer* buf, uint8_t dst, uint8_t dst_cache_policy, uint64_t dst_address_or_offset, uint8_t src, uint8_t src_cache_policy, uint64_t src_address_or_offset_or_immediate, uint32_t num_bytes, uint8_t wait_for_previous, uint8_t write_confirm) { if (buf == nullptr) { return nullptr; } auto src_address = src_address_or_offset_or_immediate; switch (src) { case 0x14: src_address = 0x30174u; break; case 0x24: src_address = 0x3017cu; break; case 0x25: src_address = 0x30184u; break; default: break; } auto* cmd = buf->AllocateDW(7); if (cmd == nullptr) { return nullptr; } cmd[0] = KYTY_PM4(7, Pm4::IT_DMA_DATA, 0u); cmd[1] = ((static_cast(src_cache_policy) & 0x3u) << 13u) | ((static_cast(dst) & 0x3u) << 20u) | ((static_cast(dst_cache_policy) & 0x3u) << 25u) | ((static_cast(src) & 0x3u) << 29u); cmd[2] = static_cast(src_address & 0xffffffffu); cmd[3] = static_cast((src_address >> 32u) & 0xffffffffu); cmd[4] = static_cast(dst_address_or_offset & 0xffffffffu); cmd[5] = static_cast((dst_address_or_offset >> 32u) & 0xffffffffu); cmd[6] = (num_bytes & 0x03ffffffu) | ((static_cast(src) & 0x4u) << 24u) | ((static_cast(dst) & 0x4u) << 25u) | ((static_cast(src) & 0x8u) << 25u) | ((static_cast(dst) & 0x8u) << 26u) | ((static_cast(wait_for_previous) & 0x1u) << 30u) | ((static_cast(write_confirm) & 0x1u) << 31u); return cmd; } uint32_t* KYTY_SYSV_ABI AgcAcbCopyData(CommandBuffer* buf, uint8_t dst, uint8_t dst_cache_policy, uint64_t dst_address, uint8_t src, uint8_t src_cache_policy, uint64_t src_address_or_immediate, uint8_t item_size, uint8_t write_confirm) { if (buf == nullptr) { return nullptr; } auto* cmd = buf->AllocateDW(6); if (cmd == nullptr) { return nullptr; } cmd[0] = KYTY_PM4(6, Pm4::IT_COPY_DATA, 0u); cmd[1] = (static_cast(src) & 0xfu) | ((static_cast(dst) & 0xfu) << 8u) | ((static_cast(src_cache_policy) & 0x3u) << 13u) | ((static_cast(item_size) & 0x1u) << 16u) | ((static_cast(write_confirm) & 0x1u) << 20u) | ((static_cast(dst_cache_policy) & 0x3u) << 25u); cmd[2] = static_cast(src_address_or_immediate & 0xffffffffu); cmd[3] = static_cast((src_address_or_immediate >> 32u) & 0xffffffffu); cmd[4] = static_cast(dst_address & 0xffffffffu); cmd[5] = static_cast((dst_address >> 32u) & 0xffffffffu); return cmd; } uint64_t KYTY_SYSV_ABI AgcAcbCopyDataGetSize() { PRINT_NAME(); return AgcDcbCopyDataGetSize(); } uint32_t* KYTY_SYSV_ABI AgcAcbDispatchIndirect(CommandBuffer* buf, const volatile void* indirect_args, uint32_t modifier) { PRINT_NAME(); LOGF("\t indirect_args = 0x%016" PRIx64 "\n" "\t modifier = 0x%08" PRIx32 "\n", reinterpret_cast(indirect_args), modifier); if (buf == nullptr) { return nullptr; } buf->DbgDump(); auto* cmd = buf->AllocateDW(4); if (cmd == nullptr) { return nullptr; } const auto args_addr = reinterpret_cast(indirect_args); cmd[0] = KYTY_PM4(4, Pm4::IT_DISPATCH_INDIRECT, 0u); cmd[1] = static_cast(args_addr & 0xffffffffu); cmd[2] = static_cast((args_addr >> 32u) & 0xffffffffu); cmd[3] = (modifier & 0xa038u) | 0x41u; return cmd; } uint32_t* KYTY_SYSV_ABI AgcAcbWriteData(CommandBuffer* buf, uint8_t dst, uint8_t cache_policy, uint64_t address_or_offset, const void* data, uint32_t num_dwords, uint8_t increment, uint8_t write_confirm) { if (buf == nullptr || data == nullptr || num_dwords == 0u || num_dwords > 0x3ffdu) { return nullptr; } auto* cmd = buf->AllocateDW(4u + num_dwords); if (cmd == nullptr) { return nullptr; } cmd[0] = KYTY_PM4(4u + num_dwords, Pm4::IT_WRITE_DATA, 0u); cmd[1] = ((static_cast(dst) & 0xfu) << 8u) | ((static_cast(increment) & 0x1u) << 16u) | ((dst == 0u ? 0u : static_cast(write_confirm) & 0x1u) << 20u) | ((static_cast(cache_policy) & 0x3u) << 25u); cmd[2] = static_cast(address_or_offset); cmd[3] = static_cast(address_or_offset >> 32u); memcpy(cmd + 4, data, static_cast(num_dwords) * 4u); return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbStallCommandBufferParser(CommandBuffer* buf) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(buf == nullptr); buf->DbgDump(); auto* cmd = buf->AllocateDW(2); EXIT_NOT_IMPLEMENTED(cmd == nullptr); cmd[0] = KYTY_PM4(2, Pm4::IT_PFP_SYNC_ME, 0u); cmd[1] = 0; return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbCopyData(CommandBuffer* buf, uint8_t dst, uint8_t dst_cache_policy, uint64_t dst_address, uint8_t src, uint8_t src_cache_policy, uint64_t src_address_or_immediate, uint8_t item_size, uint8_t write_confirm) { PRINT_NAME(); LOGF("\t dst = 0x%02" PRIx8 "\n" "\t dst_cache_policy = 0x%02" PRIx8 "\n" "\t dst_address = 0x%016" PRIx64 "\n" "\t src = 0x%02" PRIx8 "\n" "\t src_cache_policy = 0x%02" PRIx8 "\n" "\t src_address_or_immediate = 0x%016" PRIx64 "\n" "\t item_size = 0x%02" PRIx8 "\n" "\t write_confirm = 0x%02" PRIx8 "\n", dst, dst_cache_policy, dst_address, src, src_cache_policy, src_address_or_immediate, item_size, write_confirm); if (buf == nullptr) { return nullptr; } auto* cmd = buf->AllocateDW(6); if (cmd == nullptr) { return nullptr; } cmd[0] = KYTY_PM4(6, Pm4::IT_COPY_DATA, 0u); cmd[1] = ((static_cast(src) >> 1u) & 0xfu) | (((static_cast(dst) >> 1u) & 0xfu) << 8u) | ((static_cast(src_cache_policy) & 0x3u) << 13u) | ((static_cast(item_size) & 0x1u) << 16u) | ((static_cast(write_confirm) & 0x1u) << 20u) | ((static_cast(dst_cache_policy) & 0x3u) << 25u) | ((static_cast(src) & 0x1u) << 30u); cmd[2] = static_cast(src_address_or_immediate & 0xffffffffu); cmd[3] = static_cast((src_address_or_immediate >> 32u) & 0xffffffffu); cmd[4] = static_cast(dst_address & 0xffffffffu); cmd[5] = static_cast((dst_address >> 32u) & 0xffffffffu); return cmd; } uint64_t KYTY_SYSV_ABI AgcDcbCopyDataGetSize() { PRINT_NAME(); return 6 * sizeof(uint32_t); } uint32_t* KYTY_SYSV_ABI AgcDcbDmaData(CommandBuffer* buf, uint8_t engine, uint8_t dst, uint8_t dst_cache_policy, uint64_t dst_address_or_offset, uint8_t src, uint8_t src_cache_policy, uint64_t src_address_or_offset_or_immediate, uint32_t num_bytes, uint8_t wait_for_previous, uint8_t write_confirm, uint8_t block_engine) { if (buf == nullptr) { return nullptr; } auto src_address = src_address_or_offset_or_immediate; switch (src) { case 0x14: src_address = (engine == 1u ? 0x30148u : 0x30174u); break; case 0x24: src_address = (engine == 1u ? 0x30150u : 0x3017cu); break; case 0x64: src_address = (engine == 1u ? 0x30158u : 0x30184u); break; default: break; } auto* cmd = buf->AllocateDW(7); if (cmd == nullptr) { return nullptr; } cmd[0] = KYTY_PM4(7, Pm4::IT_DMA_DATA, 0u); cmd[1] = (static_cast(engine) & 0x1u) | ((static_cast(src_cache_policy) & 0x3u) << 13u) | ((static_cast(dst) & 0x3u) << 20u) | ((static_cast(dst_cache_policy) & 0x3u) << 25u) | ((static_cast(src) & 0x3u) << 29u) | ((static_cast(block_engine) & 0x1u) << 31u); cmd[2] = static_cast(src_address & 0xffffffffu); cmd[3] = static_cast((src_address >> 32u) & 0xffffffffu); cmd[4] = static_cast(dst_address_or_offset & 0xffffffffu); cmd[5] = static_cast((dst_address_or_offset >> 32u) & 0xffffffffu); cmd[6] = (num_bytes & 0x03ffffffu) | ((static_cast(src) & 0x4u) << 24u) | ((static_cast(dst) & 0x4u) << 25u) | ((static_cast(src) & 0x8u) << 25u) | ((static_cast(dst) & 0x8u) << 26u) | ((static_cast(wait_for_previous) & 0x1u) << 30u) | ((static_cast(write_confirm) & 0x1u) << 31u); return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbJump(CommandBuffer* buf, uint8_t mode, uint8_t cache_policy, const uint32_t* target, uint32_t size_in_dwords) { PRINT_NAME(); LOGF("\t mode = 0x%02" PRIx8 "\n" "\t cache_policy = 0x%02" PRIx8 "\n" "\t target = 0x%016" PRIx64 "\n" "\t size_in_dwords = %" PRIu32 "\n", mode, cache_policy, reinterpret_cast(target), size_in_dwords); if (buf == nullptr) { return nullptr; } auto* cmd = buf->AllocateDW(4); if (cmd == nullptr) { return nullptr; } auto vaddr = reinterpret_cast(target); cmd[0] = KYTY_PM4(4, Pm4::IT_INDIRECT_BUFFER, 0u); cmd[1] = static_cast(vaddr) & ~0x3u; cmd[2] = static_cast(vaddr >> 32u); cmd[3] = 0x0f200000u | ((static_cast(cache_policy) & 0x3u) << 28u) | ((static_cast(mode) & 0x1u) << 20u) | (size_in_dwords & 0xfffffu); return cmd; } uint32_t KYTY_SYSV_ABI AgcDcbJumpGetSize() { PRINT_NAME(); return 16; } uint32_t KYTY_SYSV_ABI AgcDcbRewindGetSize() { PRINT_NAME(); return 8; } uint32_t* KYTY_SYSV_ABI AgcDcbRewind(CommandBuffer* buf, uint32_t initial_state) { PRINT_NAME(); LOGF("\t initial_state = 0x%08" PRIx32 "\n", initial_state); if (buf == nullptr) { return nullptr; } auto* cmd = buf->AllocateDW(2); if (cmd == nullptr) { return nullptr; } cmd[0] = KYTY_PM4(2, Pm4::IT_REWIND, 0u); cmd[1] = (initial_state & 0x1u) << 31u; return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbSetPredication(CommandBuffer* buf, uint8_t condition, uint8_t op, uint8_t wait_op, const volatile void* address, uint32_t count_in_dwords) { PRINT_NAME(); LOGF("\t condition = 0x%02" PRIx8 "\n" "\t op = 0x%02" PRIx8 "\n" "\t wait_op = 0x%02" PRIx8 "\n" "\t address = 0x%016" PRIx64 "\n" "\t count_in_dwords = %" PRIu32 "\n", condition, op, wait_op, reinterpret_cast(address), count_in_dwords); if (buf == nullptr) { return nullptr; } auto* cmd = buf->AllocateDW(4); if (cmd == nullptr) { return nullptr; } auto addr = reinterpret_cast(address); cmd[0] = KYTY_PM4(4, Pm4::IT_SET_PREDICATION, 0u); cmd[1] = ((static_cast(condition) & 0x1u) << 8u) | ((static_cast(wait_op) & 0x1u) << 12u) | ((static_cast(op) & 0x7u) << 16u); cmd[2] = static_cast(addr) & ~0xfu; cmd[3] = static_cast(addr >> 32u); (void)count_in_dwords; return cmd; } uint32_t* KYTY_SYSV_ABI AgcUnknownKRzWekV120(CommandBuffer* buf, uint32_t arg1, uint32_t arg2, uint32_t arg3) { PRINT_NAME(); LOGF("\t argc = 4\n" "\t arg0 = 0x%016" PRIx64 "\n" "\t arg1 = 0x%08" PRIx32 "\n" "\t arg2 = 0x%08" PRIx32 "\n" "\t arg3 = 0x%08" PRIx32 "\n", reinterpret_cast(buf), arg1, arg2, arg3); if (buf == nullptr) { return nullptr; } auto* cmd = buf->AllocateDW(3); if (cmd == nullptr) { return nullptr; } cmd[0] = 0xc0017a00u; cmd[1] = 0x20000243u; uint32_t word8 = 0x400u; word8 |= (arg1 & 0x3u); word8 |= (arg2 & 0x3u) << 6u; word8 |= (arg3 & 0x1u) << 14u; cmd[2] = word8; return cmd; } int KYTY_SYSV_ABI AgcDmaDataPatchSetDstAddressOrOffset(uint32_t* cmd, uint64_t dst_address_or_offset) { EXIT_NOT_IMPLEMENTED(cmd == nullptr); auto op = (cmd[0] >> 8u) & 0xffu; if (op == Pm4::IT_DMA_DATA) { cmd[4] = static_cast(dst_address_or_offset & 0xffffffffu); cmd[5] = static_cast((dst_address_or_offset >> 32u) & 0xffffffffu); return OK; } return GRAPHICS5_ERROR_INVALID_PACKET; } int KYTY_SYSV_ABI AgcDmaDataPatchSetSrcAddressOrOffsetOrImmediate( uint32_t* cmd, uint64_t src_address_or_offset_or_immediate) { EXIT_NOT_IMPLEMENTED(cmd == nullptr); auto op = (cmd[0] >> 8u) & 0xffu; if (op == Pm4::IT_DMA_DATA) { cmd[2] = static_cast(src_address_or_offset_or_immediate & 0xffffffffu); cmd[3] = static_cast((src_address_or_offset_or_immediate >> 32u) & 0xffffffffu); return OK; } return GRAPHICS5_ERROR_INVALID_PACKET; } uint32_t KYTY_SYSV_ABI AgcGetPacketSize(uint32_t* packet) { const auto cmd_id = packet[0]; if ((cmd_id & 0x3fffff00u) == 0x3fff1000u) { return 1; } return KYTY_PM4_LEN(cmd_id); } int KYTY_SYSV_ABI AgcSetPacketPredication(uint32_t* packet, uint32_t predication) { PRINT_NAME(); LOGF("\t packet = 0x%016" PRIx64 "\n" "\t predication = 0x%08" PRIx32 "\n", reinterpret_cast(packet), predication); LOGF("\t packet0 = 0x%08" PRIx32 ", op = 0x%02" PRIx32 ", r = 0x%02" PRIx32 ", len = %" PRIu32 "\n", packet[0], (packet[0] >> 8u) & 0xffu, KYTY_PM4_R(packet[0]), AgcGetPacketSize(packet)); packet[0] = (packet[0] & ~1u) | (static_cast(predication) == 1 ? 1u : 0u); return OK; } int KYTY_SYSV_ABI AgcSetRangePredication(uint32_t* start, const volatile uint32_t* end, uint32_t predication) { PRINT_NAME(); LOGF("\t start = 0x%016" PRIx64 "\n" "\t end = 0x%016" PRIx64 "\n" "\t predication = 0x%08" PRIx32 "\n", reinterpret_cast(start), reinterpret_cast(end), predication); auto* packet = start; auto* range_end = const_cast(reinterpret_cast(end)); auto packet_va = reinterpret_cast(packet); auto end_va = reinterpret_cast(range_end); if (packet_va >= end_va) { return OK; } const uint32_t predication_bit = (static_cast(predication) == 1 ? 1u : 0u); while (packet_va < end_va) { const auto cmd_id = packet[0]; packet[0] = (cmd_id & ~1u) | predication_bit; auto size = KYTY_PM4_LEN(cmd_id); if ((cmd_id & 0x3fffff00u) == 0x3fff1000u) { size = 1; } packet_va += size * sizeof(uint32_t); packet = reinterpret_cast(packet_va); } return OK; } int KYTY_SYSV_ABI AgcRewindPatchSetRewindState(uint32_t* cmd, uint8_t state) { if (((cmd[0] >> 8u) & 0xffu) != Pm4::IT_REWIND) { return GRAPHICS5_ERROR_INVALID_PACKET; } cmd[1] = (cmd[1] & 0x7fffffffu) | (static_cast(state) << 31u); return OK; } int KYTY_SYSV_ABI AgcCondExecPatchSetEnd(uint32_t* cmd, const volatile uint32_t* buffer) { PRINT_NAME(); LOGF("\t cmd = 0x%016" PRIx64 "\n" "\t buffer = 0x%016" PRIx64 "\n", reinterpret_cast(cmd), reinterpret_cast(buffer)); if (cmd == nullptr || buffer == nullptr) { return GRAPHICS5_ERROR_INVALID_PACKET; } auto op = (cmd[0] >> 8u) & 0xffu; if (op != Pm4::IT_COND_EXEC) { return GRAPHICS5_ERROR_INVALID_PACKET; } auto* packet_end = cmd + 5; auto* range_end = const_cast(reinterpret_cast(buffer)); if (range_end < packet_end) { return GRAPHICS5_ERROR_INVALID_PACKET; } auto num_dwords = static_cast(range_end - packet_end); if (num_dwords > 0x3fffu) { return GRAPHICS5_ERROR_INVALID_PACKET; } cmd[4] = (cmd[4] & ~0x3fffu) | static_cast(num_dwords); return OK; } int KYTY_SYSV_ABI AgcCondExecPatchSetCommandAddress(uint32_t* cmd, const volatile uint32_t* command) { PRINT_NAME(); LOGF("\t cmd = 0x%016" PRIx64 "\n" "\t command = 0x%016" PRIx64 "\n", reinterpret_cast(cmd), reinterpret_cast(command)); if (cmd == nullptr || command == nullptr) { return GRAPHICS5_ERROR_INVALID_PACKET; } auto op = (cmd[0] >> 8u) & 0xffu; if (op != Pm4::IT_COND_EXEC || (reinterpret_cast(command) & 0x3u) != 0) { return GRAPHICS5_ERROR_INVALID_PACKET; } auto addr = reinterpret_cast(command); cmd[1] = (cmd[1] & 0x3u) | (static_cast(addr) & 0xfffffffcu); cmd[2] = static_cast(addr >> 32u); return OK; } uint32_t* KYTY_SYSV_ABI AgcDcbWriteData(CommandBuffer* buf, uint8_t dst, uint8_t cache_policy, uint64_t address_or_offset, const void* data, uint32_t num_dwords, uint8_t increment, uint8_t write_confirm) { PRINT_NAME(); AgcTrace("\t dst = 0x%02" PRIx8 "\n" "\t cache_policy = 0x%02" PRIx8 "\n" "\t address_or_offset = 0x%016" PRIx64 "\n" "\t data = 0x%016" PRIx64 "\n" "\t num_dwords = %" PRIu32 "\n" "\t increment = 0x%02" PRIx8 "\n" "\t write_confirm = 0x%02" PRIx8 "\n", dst, cache_policy, address_or_offset, reinterpret_cast(data), num_dwords, increment, write_confirm); if (buf == nullptr || data == nullptr) { LOGF_COLOR(Log::Color::Red, "\t invalid arguments\n"); return nullptr; } if ((4 + num_dwords - 2u) > 0x3fffu) { LOGF_COLOR(Log::Color::Red, "\t packet is too large\n"); return nullptr; } buf->DbgDump(); auto* cmd = buf->AllocateDW(4 + num_dwords); if (cmd == nullptr) { LOGF_COLOR(Log::Color::Red, "\t failed to allocate command\n"); return nullptr; } const auto dst_value = static_cast(dst); const auto write_confirm_value = (dst == 0u ? 0u : (static_cast(write_confirm) & 0x1u)); cmd[0] = KYTY_PM4(4 + num_dwords, Pm4::IT_WRITE_DATA, 0u); cmd[1] = ((dst_value & 0x1u) << 30u) | ((dst_value & 0x1eu) << 7u) | ((static_cast(increment) & 0x1u) << 16u) | (write_confirm_value << 20u) | ((static_cast(cache_policy) & 0x3u) << 25u); cmd[2] = static_cast(address_or_offset) & ~0x3u; cmd[3] = static_cast(address_or_offset >> 32u); memcpy(cmd + 4, data, static_cast(num_dwords) * 4); return cmd; } uint32_t KYTY_SYSV_ABI AgcDcbWriteDataGetSize(uint32_t num_dwords) { PRINT_NAME(); LOGF("\t num_dwords = %" PRIu32 "\n", num_dwords); return 4u * num_dwords + 16u; } uint32_t* KYTY_SYSV_ABI AgcDcbGetLodStats(CommandBuffer* buf, uint8_t cache_policy, const volatile void* buffer, uint32_t buffer_size_in_bytes, uint32_t reset_count, uint8_t force_reset, uint8_t report_and_reset, uint32_t reporting_interval_in_100k_clocks) { PRINT_NAME(); LOGF("\t cache_policy = 0x%02" PRIx8 "\n" "\t buffer = 0x%016" PRIx64 "\n" "\t buffer_size_in_bytes = %" PRIu32 "\n" "\t reset_count = %" PRIu32 "\n" "\t force_reset = 0x%02" PRIx8 "\n" "\t report_and_reset = 0x%02" PRIx8 "\n" "\t reporting_interval_in_100k_clocks = %" PRIu32 "\n", cache_policy, reinterpret_cast(buffer), buffer_size_in_bytes, reset_count, force_reset, report_and_reset, reporting_interval_in_100k_clocks); if (buf == nullptr) { return nullptr; } buf->DbgDump(); auto* cmd = buf->AllocateDW(5); if (cmd == nullptr) { return nullptr; } const auto buffer_addr = reinterpret_cast(buffer); cmd[0] = KYTY_PM4(5, Pm4::IT_GET_LOD_STATS, 0u); cmd[1] = buffer_size_in_bytes; cmd[2] = static_cast(buffer_addr & 0xffffffc0u); cmd[3] = static_cast((buffer_addr >> 32u) & 0xffffffffu); cmd[4] = ((static_cast(cache_policy) & 0x3u) << 28u) | ((static_cast(report_and_reset) & 0x1u) << 19u) | ((static_cast(force_reset) & 0x1u) << 18u) | ((reset_count & 0xffu) << 10u) | ((reporting_interval_in_100k_clocks & 0xffu) << 2u); return cmd; } static uint32_t* get_agc_wait_packet(uint32_t* cmd) { if (cmd == nullptr || KYTY_PM4_LEN(cmd[0]) != AGC_WAIT_USER_DATA_BEGIN_DW || !AgcIsInternalDataPacket(cmd[0], cmd + 1)) { return nullptr; } auto* wait = cmd + AGC_WAIT_USER_DATA_BEGIN_DW; const auto op = (wait[0] >> 8u) & 0xffu; const auto len = KYTY_PM4_LEN(wait[0]); const auto tag = cmd[2] & 0xffff0000u; if (KYTY_PM4_R(wait[0]) != 0u || !((op == Pm4::IT_WAIT_REG_MEM && len == 7u && tag == AGC_WAIT_USER_DATA_BEGIN_32_TAG) || (op == Pm4::IT_WAIT_REG_MEM_64 && len == 9u && tag == AGC_WAIT_USER_DATA_BEGIN_64_TAG))) { return nullptr; } return wait; } int KYTY_SYSV_ABI AgcWaitRegMemPatchAddress(uint32_t* cmd, const volatile void* address) { PRINT_NAME(); LOGF("\t cmd = 0x%016" PRIx64 "\n" "\t address = 0x%016" PRIx64 "\n", reinterpret_cast(cmd), reinterpret_cast(address)); auto* wait = get_agc_wait_packet(cmd); if (wait == nullptr) { return GRAPHICS5_ERROR_INVALID_PACKET; } const auto vaddr = reinterpret_cast(address); const auto op = (wait[0] >> 8u) & 0xffu; const auto align_mask = (op == Pm4::IT_WAIT_REG_MEM ? 0x3u : 0x7u); cmd[2] = (cmd[2] & 0xffff0000u) | static_cast((vaddr >> 32u) & 0xffffu); cmd[3] = static_cast(vaddr); wait[2] = (wait[2] & align_mask) | (static_cast(vaddr) & ~align_mask); wait[3] = (wait[3] & 0xfffc0000u) | (static_cast(vaddr >> 32u) & 0x3ffffu); return OK; } int KYTY_SYSV_ABI AgcWaitRegMemPatchReference(uint32_t* cmd, uint64_t reference) { PRINT_NAME(); LOGF("\t cmd = 0x%016" PRIx64 "\n" "\t reference = 0x%016" PRIx64 "\n", reinterpret_cast(cmd), reference); auto* wait = get_agc_wait_packet(cmd); if (wait == nullptr) { return GRAPHICS5_ERROR_INVALID_PACKET; } // The native patch helper changes only the low reference DWORD for both packet sizes. wait[4] = static_cast(reference); return OK; } int KYTY_SYSV_ABI AgcQueueEndOfPipeActionPatchAddress(uint32_t* cmd, const volatile Label* address) { PRINT_NAME(); // Not sure LOGF("\t cmd = 0x%016" PRIx64 "\n" "\t address = 0x%016" PRIx64 "\n", reinterpret_cast(cmd), reinterpret_cast(address)); EXIT_NOT_IMPLEMENTED(cmd == nullptr); auto vaddr = reinterpret_cast(address); auto op = (cmd[0] >> 8u) & 0xffu; if ((op == Pm4::IT_NOP && KYTY_PM4_R(cmd[0]) == Pm4::R_RELEASE_MEM) || op == Pm4::IT_RELEASE_MEM) { cmd[3] = static_cast(vaddr & 0xffffffffu); cmd[4] = static_cast((vaddr >> 32u) & 0xffffffffu); } else if (op == Pm4::IT_EVENT_WRITE_EOP) { cmd[2] = static_cast(vaddr & 0xffffffffu); cmd[3] = (cmd[3] & 0xffff0000u) | static_cast((vaddr >> 32u) & 0xffffu); } else { EXIT("unsupported queueEndOfPipeAction packet for address patch: 0x%08" PRIx32 "\n", cmd[0]); } return OK; } int KYTY_SYSV_ABI AgcQueueEndOfPipeActionPatchData(uint32_t* cmd, uint64_t data) { PRINT_NAME(); LOGF("\t cmd = 0x%016" PRIx64 "\n" "\t data = 0x%016" PRIx64 "\n", reinterpret_cast(cmd), data); EXIT_NOT_IMPLEMENTED(cmd == nullptr); const auto op = (cmd[0] >> 8u) & 0xffu; const bool is_release_mem = op == Pm4::IT_RELEASE_MEM || (op == Pm4::IT_NOP && KYTY_PM4_R(cmd[0]) == Pm4::R_RELEASE_MEM); if (!is_release_mem) { return GRAPHICS5_ERROR_INVALID_PACKET; } const auto interrupt = (cmd[2] >> 24u) & 0x7u; const auto data_sel = (cmd[2] >> 29u) & 0x7u; if (interrupt != 4u && data_sel != 5u) { cmd[5] = static_cast(data & 0xffffffffu); cmd[6] = static_cast((data >> 32u) & 0xffffffffu); } return OK; } static uint32_t wait_reg_mem_poll_cycles_to_packet(uint32_t poll_cycles) { return std::min(poll_cycles >> 4u, 0xffffu); } static uint32_t wait_reg_mem32_control(uint8_t compare_function, uint8_t op, uint8_t cache_policy) { return 0x10u | (static_cast(compare_function) & 0x7u) | ((static_cast(op) & 0x3u) << 8u) | ((static_cast(op) & 0xcu) << 4u) | ((static_cast(cache_policy) & 0x3u) << 25u); } static uint32_t wait_reg_mem64_control(uint8_t compare_function, uint8_t op, uint8_t cache_policy) { return 0x10u | (static_cast(compare_function) & 0x7u) | ((static_cast(op) & 0x1u) << 8u) | ((static_cast(op) & 0x6u) << 5u) | ((static_cast(cache_policy) & 0x3u) << 25u); } bool AgcIsInternalDataPacket(uint32_t cmd_id, const uint32_t* payload) { if (payload == nullptr || ((cmd_id >> 8u) & 0xffu) != Pm4::IT_SET_UCONFIG_REG || KYTY_PM4_R(cmd_id) != 1u || payload[0] != AGC_INTERNAL_DATA_REGISTER) { return false; } const auto size_dw = KYTY_PM4_LEN(cmd_id); if (size_dw == AGC_WAIT_USER_DATA_BEGIN_DW) { const auto tag = payload[1] & 0xffff0000u; return tag == AGC_WAIT_USER_DATA_BEGIN_32_TAG || tag == AGC_WAIT_USER_DATA_BEGIN_64_TAG; } return size_dw == AGC_INTERNAL_DATA_PACKET_SIZE_DW && (payload[1] == AGC_WAIT_USER_DATA_END_TAG || payload[1] == AGC_DRAW_INDIRECT_MULTI_BEGIN_TAG || payload[1] == AGC_DRAW_INDIRECT_MULTI_END_TAG); } uint32_t* KYTY_SYSV_ABI AgcDcbWaitRegMem(CommandBuffer* buf, uint8_t size, uint8_t compare_function, uint8_t op, uint8_t cache_policy, const volatile void* address, uint64_t reference, uint64_t mask, uint32_t poll_cycles) { PRINT_NAME(); AgcTrace("\t size = 0x%02" PRIx8 "\n" "\t compare_function = 0x%02" PRIx8 "\n" "\t op = 0x%02" PRIx8 "\n" "\t cache_policy = 0x%02" PRIx8 "\n" "\t address = 0x%016" PRIx64 "\n" "\t reference = 0x%016" PRIx64 "\n" "\t mask = 0x%016" PRIx64 "\n" "\t poll_cycles = %" PRIu32 "\n", size, compare_function, op, cache_policy, reinterpret_cast(address), reference, mask, poll_cycles); EXIT_NOT_IMPLEMENTED(buf == nullptr); if (size != 0 && size != 1) { LOGF_COLOR(Log::Color::Red, "\t warning: unsupported size 0x%02" PRIx8 ", expected 0 (32b) or 1 (64b)\n", size); return nullptr; } if (cache_policy > 3) { LOGF_COLOR(Log::Color::Red, "\t warning: unsupported cache_policy 0x%02" PRIx8 ", expected 0..3\n", cache_policy); } buf->DbgDump(); auto address_value = reinterpret_cast(address); bool wait32 = (size == 0); auto poll = wait_reg_mem_poll_cycles_to_packet(poll_cycles); const auto wait_dw = wait32 ? 7u : 9u; const auto total_dw = AGC_WAIT_USER_DATA_BEGIN_DW + wait_dw + AGC_INTERNAL_DATA_PACKET_SIZE_DW; auto* cmd = buf->AllocateDW(total_dw); if (cmd == nullptr) { return nullptr; } cmd[0] = KYTY_PM4(AGC_WAIT_USER_DATA_BEGIN_DW, Pm4::IT_SET_UCONFIG_REG, 1u); cmd[1] = AGC_INTERNAL_DATA_REGISTER; cmd[2] = (wait32 ? AGC_WAIT_USER_DATA_BEGIN_32_TAG : AGC_WAIT_USER_DATA_BEGIN_64_TAG) | static_cast((address_value >> 32u) & 0xffffu); cmd[3] = static_cast(address_value); auto* wait = cmd + AGC_WAIT_USER_DATA_BEGIN_DW; auto* end = wait + wait_dw; write_agc_internal_data_packet(end, AGC_WAIT_USER_DATA_END_TAG); if (wait32) { wait[0] = KYTY_PM4(7, Pm4::IT_WAIT_REG_MEM, 0u); wait[1] = wait_reg_mem32_control(compare_function, op, cache_policy); wait[2] = static_cast(address_value) & ~0x3u; wait[3] = static_cast(address_value >> 32u) & 0x3ffffu; wait[4] = static_cast(reference); wait[5] = static_cast(mask); wait[6] = poll; return cmd; } wait[0] = KYTY_PM4(9, Pm4::IT_WAIT_REG_MEM_64, 0u); wait[1] = wait_reg_mem64_control(compare_function, op, cache_policy); wait[2] = static_cast(address_value) & ~0x7u; wait[3] = static_cast(address_value >> 32u) & 0x3ffffu; wait[4] = static_cast(reference); wait[5] = static_cast(reference >> 32u); wait[6] = static_cast(mask); wait[7] = static_cast(mask >> 32u); wait[8] = poll; return cmd; } uint64_t KYTY_SYSV_ABI AgcDcbWaitOnAddressGetSize(uint32_t size) { PRINT_NAME(); switch (size) { case 0: return 14u * 4u; case 1: return 16u * 4u; default: return 0; } } uint32_t* KYTY_SYSV_ABI AgcDcbPushMarker(CommandBuffer* buf, const char* str, uint32_t /*color*/) { if (buf == nullptr) { return nullptr; } if (str == nullptr) { str = ""; } const auto len = strlen(str) + 1; const auto payload_dwords = static_cast((len + 3) / 4); const auto size = 1u + (payload_dwords == 0 ? 1u : payload_dwords); auto* cmd = buf->AllocateDW(size); if (cmd == nullptr) { return nullptr; } cmd[0] = KYTY_PM4(size, Pm4::IT_NOP, Pm4::R_PUSH_MARKER); memset(cmd + 1, 0, static_cast(size - 1) * sizeof(uint32_t)); memcpy(cmd + 1, str, len); return cmd; } uint32_t* KYTY_SYSV_ABI AgcDcbSetMarker(CommandBuffer* buf, const char* str, uint32_t color) { return AgcDcbPushMarker(buf, str, color); } uint32_t* KYTY_SYSV_ABI AgcAcbSetMarker(CommandBuffer* buf, const char* str, uint32_t color) { return AgcDcbSetMarker(buf, str, color); } uint32_t* KYTY_SYSV_ABI AgcAcbPushMarker(CommandBuffer* buf, const char* str, uint32_t color) { return AgcDcbPushMarker(buf, str, color); } uint32_t* KYTY_SYSV_ABI AgcDcbPopMarker(CommandBuffer* buf) { if (buf == nullptr) { return nullptr; } auto* cmd = buf->AllocateDW(2); if (cmd == nullptr) { return nullptr; } cmd[0] = KYTY_PM4(2, Pm4::IT_NOP, Pm4::R_POP_MARKER); cmd[1] = 0; return cmd; } uint32_t* KYTY_SYSV_ABI AgcAcbPopMarker(CommandBuffer* buf) { return AgcDcbPopMarker(buf); } uint32_t* KYTY_SYSV_ABI AgcDcbSetFlip(CommandBuffer* buf, uint32_t video_out_handle, int32_t display_buffer_index, uint32_t flip_mode, int64_t flip_arg) { PRINT_NAME(); AgcTrace("\t video_out_handle = %" PRIu32 "\n" "\t display_buffer_index = %" PRId32 "\n" "\t flip_mode = %" PRIu32 "\n" "\t flip_arg = %" PRId64 "\n", video_out_handle, display_buffer_index, flip_mode, flip_arg); EXIT_NOT_IMPLEMENTED(buf == nullptr); buf->DbgDump(); auto* cmd = buf->AllocateDW(6); EXIT_NOT_IMPLEMENTED(cmd == nullptr); cmd[0] = KYTY_PM4(6, Pm4::IT_NOP, Pm4::R_FLIP); cmd[1] = video_out_handle; cmd[2] = display_buffer_index; cmd[3] = flip_mode; cmd[4] = static_cast(static_cast(flip_arg) & 0xffffffffu); cmd[5] = static_cast((static_cast(flip_arg) >> 32u) & 0xffffffffu); return cmd; } } // namespace Gen5 namespace Gen5Driver { LIB_NAME("Graphics5Driver", "Graphics5Driver"); struct TessellationDriverState { uint64_t tf_ring_base = 0; uint32_t tf_ring_size = 0; uint64_t hs_offchip_value0 = 0; uint64_t hs_offchip_value1 = 0; uint64_t hs_offchip_value2 = 0; }; static TessellationDriverState g_tessellation_driver_state {}; static void submit_dcb(uint32_t* dcb, uint32_t size_in_dwords) { GraphicsDbgDumpDcb("d", size_in_dwords, dcb); EXIT_IF(g_renderer == nullptr); g_renderer->GetGpu().Submit(std::span {dcb, size_in_dwords}, {}); } int KYTY_SYSV_ABI AgcDriverSubmitDcb(const Packet* packet) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(packet == nullptr); AgcTrace("\t addr = 0x%016" PRIx64 "\n" "\t dw_num = 0x%08" PRIx32 "\n" "\t flags = 0x%02" PRIx8 "\n", reinterpret_cast(packet->addr), packet->dw_num, packet->flags); submit_dcb(packet->addr, packet->dw_num); return OK; } int KYTY_SYSV_ABI AgcDriverSubmitMultiDcbs(uint32_t* const* dcb_gpu_addrs, const uint32_t* dcb_sizes_in_dwords, uint32_t count) { PRINT_NAME(); AgcTrace("\t count = %" PRIu32 "\n", count); if (count == 0) { return OK; } if (dcb_gpu_addrs == nullptr || dcb_sizes_in_dwords == nullptr) { return LibKernel::KERNEL_ERROR_EINVAL; } for (uint32_t i = 0; i < count; i++) { auto* dcb = dcb_gpu_addrs[i]; uint32_t size_in_dwords = dcb_sizes_in_dwords[i]; AgcTrace("\t dcb[%" PRIu32 "] = 0x%016" PRIx64 "\n" "\t size[%" PRIu32 "] = 0x%08" PRIx32 "\n", i, reinterpret_cast(dcb), i, size_in_dwords); if (dcb != nullptr) { submit_dcb(dcb, size_in_dwords); } } return OK; } static void submit_acb(uint32_t queue, uint32_t* acb, uint32_t size_in_dwords) { if (acb == nullptr || size_in_dwords == 0) { return; } for (uint32_t i = 0; i < std::min(size_in_dwords, 8); i++) { LOGF("\t acb[%u] = 0x%08" PRIx32 "\n", i, acb[i]); } GraphicsDbgDumpDcb("a", size_in_dwords, acb); EXIT_IF(g_renderer == nullptr); g_renderer->GetGpu().SubmitCompute(queue, std::span {acb, size_in_dwords}); } static uint32_t get_driver_queue(const void* queue_context) { uint32_t queue = 0; std::memcpy(&queue, static_cast(queue_context) + 4, sizeof(queue)); return queue; } static void submit_command_buffer(uint32_t queue, uint32_t* commands, uint32_t size_in_dwords) { if (commands == nullptr || size_in_dwords == 0) { return; } if (queue >= 0x20 && queue < 0x58) { submit_acb(queue, commands, size_in_dwords); } else { submit_dcb(commands, size_in_dwords); } } int KYTY_SYSV_ABI AgcDriverSubmitCommandBuffer(void* queue_context, const Packet* packet) { PRINT_NAME(); LOGF("\t queue_context = 0x%016" PRIx64 "\n" "\t packet = 0x%016" PRIx64 "\n", reinterpret_cast(queue_context), reinterpret_cast(packet)); if (queue_context == nullptr || packet == nullptr) { return LibKernel::KERNEL_ERROR_EINVAL; } const uint32_t queue = get_driver_queue(queue_context); LOGF("\t queue = 0x%08" PRIx32 "\n" "\t addr = 0x%016" PRIx64 "\n" "\t size = 0x%08" PRIx32 "\n" "\t flags = 0x%02" PRIx8 "\n", queue, reinterpret_cast(packet->addr), packet->dw_num, packet->flags); submit_command_buffer(queue, packet->addr, packet->dw_num); return OK; } int KYTY_SYSV_ABI AgcDriverSubmitMultiCommandBuffers(void* queue_context, uint32_t* const* command_buffers, const uint32_t* sizes_in_dwords, uint32_t count) { PRINT_NAME(); LOGF("\t queue_context = 0x%016" PRIx64 "\n" "\t count = %" PRIu32 "\n", reinterpret_cast(queue_context), count); if (count == 0) { return OK; } if (queue_context == nullptr || command_buffers == nullptr || sizes_in_dwords == nullptr) { return LibKernel::KERNEL_ERROR_EINVAL; } const uint32_t queue = get_driver_queue(queue_context); for (uint32_t i = 0; i < count; i++) { submit_command_buffer(queue, command_buffers[i], sizes_in_dwords[i]); } return OK; } int KYTY_SYSV_ABI AgcDriverSubmitAcb(uint32_t queue, const Packet* packet) { PRINT_NAME(); LOGF("\t queue = 0x%08" PRIx32 "\n" "\t packet = 0x%016" PRIx64 "\n", queue, reinterpret_cast(packet)); if (packet == nullptr) { return OK; } LOGF("\t acb = 0x%016" PRIx64 "\n" "\t size = 0x%08" PRIx32 "\n" "\t flags = 0x%02" PRIx8 "\n", reinterpret_cast(packet->addr), packet->dw_num, packet->flags); submit_acb(queue, packet->addr, packet->dw_num); return OK; } int KYTY_SYSV_ABI AgcDriverSubmitMultiAcbs(uint32_t queue, uint32_t* const* acbs, const uint32_t* sizes_in_dwords, uint32_t count) { PRINT_NAME(); LOGF("\t queue = 0x%08" PRIx32 "\n" "\t count = %" PRIu32 "\n", queue, count); if (count == 0) { return OK; } if (acbs == nullptr || sizes_in_dwords == nullptr) { return LibKernel::KERNEL_ERROR_EINVAL; } for (uint32_t i = 0; i < count; i++) { submit_acb(queue, acbs[i], sizes_in_dwords[i]); } return OK; } int KYTY_SYSV_ABI AgcDriverAddEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id, void* udata) { PRINT_NAME(); if (eq == LibKernel::EventQueue::KERNEL_EQUEUE_INVALID) { return LibKernel::KERNEL_ERROR_EBADF; } EXIT_IF(g_renderer == nullptr); return Sync::AddEqEvent(*g_renderer, eq, id, udata); } int KYTY_SYSV_ABI AgcDriverDeleteEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id) { PRINT_NAME(); if (eq == LibKernel::EventQueue::KERNEL_EQUEUE_INVALID) { return LibKernel::KERNEL_ERROR_EBADF; } EXIT_IF(g_renderer == nullptr); return Sync::DeleteEqEvent(*g_renderer, eq, id); } int KYTY_SYSV_ABI AgcDriverGetEqEventType(const LibKernel::EventQueue::KernelEvent* ev) { PRINT_NAME(); if (ev == nullptr) { return 0; } if (ev->filter == LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS) { return static_cast(ev->ident); } return static_cast(ev->data); } uint32_t KYTY_SYSV_ABI AgcDriverGetEqContextId(const LibKernel::EventQueue::KernelEvent* ev) { PRINT_NAME(); if (ev == nullptr) { return 0; } if (ev->filter == LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS) { return static_cast(ev->data); } return static_cast(ev->ident); } int KYTY_SYSV_ABI AgcDriverSetTFRing(const volatile void* base, uint32_t size) { PRINT_NAME(); g_tessellation_driver_state.tf_ring_base = reinterpret_cast(base); g_tessellation_driver_state.tf_ring_size = size; LOGF("\t base = 0x%016" PRIx64 "\n" "\t size = 0x%08" PRIx32 "\n", g_tessellation_driver_state.tf_ring_base, g_tessellation_driver_state.tf_ring_size); return OK; } int KYTY_SYSV_ABI AgcDriverSetHsOffchipParam(uint64_t value0, uint64_t value1, uint64_t value2) { PRINT_NAME(); g_tessellation_driver_state.hs_offchip_value0 = value0; g_tessellation_driver_state.hs_offchip_value1 = value1; g_tessellation_driver_state.hs_offchip_value2 = value2; LOGF("\t value0 = 0x%016" PRIx64 "\n" "\t value1 = 0x%016" PRIx64 "\n" "\t value2 = 0x%016" PRIx64 "\n", value0, value1, value2); return OK; } bool KYTY_SYSV_ABI AgcDriverIsCaptureInProgress() { PRINT_NAME(); return false; } int KYTY_SYSV_ABI AgcDriverUnknownU9ueyEhSkF4() { PRINT_NAME(); return static_cast(0x8a6c9018u); } } // namespace Gen5Driver } // namespace Libs::Graphics