#include "graphics/guest_gpu/graphicsRun.h" #include "common/assert.h" #include "common/emulatorConfig.h" #include "common/logging/log.h" #include "common/profiler.h" #include "common/stringUtils.h" #include "common/threads.h" #include "graphics/guest_gpu/command_processor/commandProcessor.h" #include "graphics/guest_gpu/command_processor/pm4Dispatch.h" #include "graphics/guest_gpu/hardwareContext.h" #include "graphics/guest_gpu/pm4.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/videoOut.h" #include "graphics/presentation/window.h" #include "graphics/shader/shader.h" #include "kernel/memory.h" #include "libs/agc.h" #include "libs/errno.h" #include #include #include #include #include #include #include #include #include #include namespace Libs::Graphics { static thread_local CommandProcessor* g_current_processor = nullptr; static thread_local Pm4Execution* g_current_execution = nullptr; static thread_local bool g_gpu_mutex_owned = false; static thread_local bool g_gpu_thread = false; static thread_local GuestGpu* g_gpu_state = nullptr; struct DrawIndirectArgs { uint32_t vertex_count_per_instance; uint32_t instance_count; uint32_t start_vertex_location; uint32_t start_instance_location; }; struct DrawIndexedIndirectArgs { uint32_t index_count_per_instance; uint32_t instance_count; uint32_t start_index_location; uint32_t base_vertex_location; uint32_t start_instance_location; }; class GpuMutexLock final { public: explicit GpuMutexLock(Common::Mutex& mutex): m_mutex(mutex) { if (g_gpu_mutex_owned) { EXIT("recursive GPU mutex acquisition\n"); } g_gpu_mutex_owned = true; m_mutex.Lock(); } ~GpuMutexLock() { if (!g_gpu_mutex_owned) { EXIT("invalid GPU mutex release\n"); } m_mutex.Unlock(); g_gpu_mutex_owned = false; } private: Common::Mutex& m_mutex; }; static bool GraphicsRunDebugDumpEnabled() { return Config::GraphicsDebugDumpEnabled() && Config::GetPrintfDirection() != Config::LogDirection::Silent; } GuestGpu::GuestGpu(RenderContext& renderer): m_renderer(renderer) { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); GraphicsInitJmpTables(); m_gfx_cp = std::make_unique(renderer, 0); m_thread = std::jthread(ThreadRun, this); } GuestGpu::~GuestGpu() { Shutdown(); } void GuestGpu::Shutdown() { std::lock_guard shutdown_lock(m_shutdown_mutex); if (m_shutdown_complete) { return; } { Common::LockGuard lock(m_queue_mutex); m_accepting = false; m_stopping = true; m_work_available.SignalAll(); } if (m_thread.joinable()) { m_thread.join(); } m_shutdown_complete = true; } bool GuestGpu::IsStopping() { Common::LockGuard lock(m_queue_mutex); return m_stopping; } void GuestGpu::SendCommand(Common::UniqueFunction&& command) { EXIT_IF(!command); if (IsGpuThread()) { command(); return; } Common::LockGuard lock(m_queue_mutex); EXIT_IF(!m_accepting); m_commands.push_back(std::move(command)); m_pending_commands.fetch_add(1, std::memory_order_release); m_work_available.Signal(); } void GuestGpu::ProcessCommands() { EXIT_IF(!IsGpuThread()); while (m_pending_commands.load(std::memory_order_acquire) != 0) { Common::UniqueFunction command; { Common::LockGuard lock(m_queue_mutex); EXIT_IF(m_commands.empty()); command = std::move(m_commands.front()); m_commands.pop_front(); EXIT_IF(m_pending_commands.fetch_sub(1, std::memory_order_acq_rel) == 0); } command(); } } void GuestGpu::SendCommandSync(Common::UniqueFunction&& command) { EXIT_IF(!command); if (IsGpuThread()) { command(); return; } std::binary_semaphore done {0}; SendCommand([operation = std::move(command), &done]() mutable { operation(); done.release(); }); done.acquire(); } void GuestGpu::Submit(std::span draw_commands, std::span constant_commands) { if (draw_commands.empty()) { return; } GpuMutexLock lock(m_submission_mutex); Submission submission; submission.type = SubmissionType::Graphics; submission.queue_id = 0; submission.commands = draw_commands; submission.constant_commands = constant_commands; submission.reset_processor = m_graphics_done; m_graphics_done = false; Enqueue(std::move(submission)); } void GuestGpu::SubmitCompute(uint32_t queue, std::span commands) { EXIT_IF(commands.empty()); GpuMutexLock lock(m_submission_mutex); EXIT_NOT_IMPLEMENTED(queue < ComputeQueueBase || queue >= ComputeQueueBase + ComputeQueueCount); const auto compute_queue = queue - ComputeQueueBase; Submission submission; submission.type = SubmissionType::Compute; submission.queue_id = 1 + compute_queue; submission.commands = commands; Enqueue(std::move(submission)); } void GuestGpu::SubmitFlipPreparation(uint64_t request_id) { GpuMutexLock lock(m_submission_mutex); Submission submission; submission.type = SubmissionType::FlipPreparation; submission.queue_id = 0; submission.reset_processor = m_graphics_done; submission.flip_request_id = request_id; m_graphics_done = false; Enqueue(std::move(submission)); } void GuestGpu::Done() { GpuMutexLock lock(m_submission_mutex); if (!IsGpuThread()) { WaitForIdle(); } m_graphics_done = true; m_done_num++; } int GuestGpu::GetFrameNum() const { return m_done_num; } CommandProcessor& GuestGpu::GetProcessor(uint32_t queue_id) { EXIT_IF(queue_id >= QueueCount); if (queue_id == 0) { return *m_gfx_cp; } auto& processor = m_compute_cp[queue_id - 1]; if (processor == nullptr) { processor = std::make_unique(m_renderer, ComputeQueueBase + queue_id - 1); } return *processor; } void CommandProcessor::Reset() { m_sh_ctx.Reset(); m_ucfg.Reset(); m_ctx.Reset(); m_saved_ctx.Reset(); m_context_state_pushed = false; m_index_type_and_size = 0; m_index_buffer_size = 0; m_user_data_marker = HW::UserSgprType::Unknown; m_draw_indirect_args_base_addr = 0; m_dispatch_indirect_args_base_addr = 0; std::memset(m_const_ram, 0, sizeof(m_const_ram)); } void CommandProcessor::ApplyContextStateOperation(ContextStateOperation operation) { switch (operation) { case ContextStateOperation::Clear: m_ctx.Reset(); break; case ContextStateOperation::Push: EXIT_IF(m_context_state_pushed); m_saved_ctx = m_ctx; m_context_state_pushed = true; break; case ContextStateOperation::Pop: EXIT_IF(!m_context_state_pushed); m_ctx = m_saved_ctx; m_saved_ctx = {}; m_context_state_pushed = false; break; case ContextStateOperation::PushClear: EXIT_IF(m_context_state_pushed); m_saved_ctx = m_ctx; m_context_state_pushed = true; m_ctx.Reset(); break; default: EXIT("unknown context state operation: %u\n", static_cast(operation)); } } void CommandProcessor::BufferInit() { GetScheduler().Begin(m_ctx, m_ucfg, m_sh_ctx); } void CommandProcessor::BufferFlush() { GetScheduler().Flush(); } void CommandProcessor::BufferFlushAndWait() { GetScheduler().FlushAndWait(); } void CommandProcessor::BufferWait() { BufferInit(); GetScheduler().Finish(); } void CommandProcessor::ResetDeCe() { m_de_count = 0; m_ce_count = 0; m_ce_complete = false; } void CommandProcessor::WaitCe() { if (m_ce_count <= m_de_count && !m_ce_complete) { SuspendPm4(); } } void CommandProcessor::WaitDeDiff(uint32_t diff) { EXIT_IF(m_de_count > m_ce_count); if (m_ce_count - m_de_count >= diff) { SuspendPm4(); } } void CommandProcessor::WaitForRewind(bool valid) { if (!valid) { SuspendPm4(); } } void CommandProcessor::IncrementDe() { m_de_count++; } void CommandProcessor::IncrementCe() { m_ce_count++; } void CommandProcessor::WriteConstRam(uint32_t offset, const uint32_t* src, uint32_t dw_num) { memcpy(m_const_ram + offset / 4, src, static_cast(dw_num) * 4); } void CommandProcessor::DumpConstRam(uint32_t* dst, uint32_t offset, uint32_t dw_num) { memcpy(dst, m_const_ram + offset / 4, static_cast(dw_num) * 4); } bool TestWaitRegMemValue(uint64_t value, uint64_t ref, uint64_t mask, uint32_t func) { switch (func) { case 0: return true; case 1: return (value & mask) < ref; case 2: return (value & mask) <= ref; case 3: return (value & mask) == ref; case 4: return (value & mask) != ref; case 5: return (value & mask) >= ref; case 6: return (value & mask) > ref; default: EXIT("unknown wait compare function: %" PRIu32 "\n", func); } return false; } template void CommandProcessor::WaitRegMem(uint32_t func, const T* addr, T ref, T mask, uint32_t poll, uint32_t wait_op) { EXIT_IF(addr == nullptr); if ((wait_op & ~1u) != 0) { EXIT("unsupported wait_reg_mem operation: 0x%08" PRIx32 "\n", wait_op); } (void)poll; if (!TestWaitRegMemValue(*addr, ref, mask, func)) { SuspendPm4(); } } template void CommandProcessor::WaitRegMem(uint32_t, const uint32_t*, uint32_t, uint32_t, uint32_t, uint32_t); template void CommandProcessor::WaitRegMem(uint32_t, const uint64_t*, uint64_t, uint64_t, uint32_t, uint32_t); void CommandProcessor::WriteData(uint32_t* dst, const uint32_t* src, uint32_t dw_num, uint32_t write_control) { const uint32_t dst_sel = ((write_control >> 30u) & 0x1u) | ((write_control >> 7u) & 0x1eu); const bool write_one_address = ((write_control >> 16u) & 0x1u) != 0; switch (dst_sel) { case 0: case 2: case 4: case 5: case 6: break; default: EXIT("unsupported writeData destination selector 0x%02" PRIx32 "\n", dst_sel); } if (dw_num == 0) { return; } if (write_one_address) { for (uint32_t i = 0; i < dw_num; i++) { dst[0] = src[i]; } } else { memcpy(dst, src, static_cast(dw_num) * sizeof(uint32_t)); } } void CommandProcessor::WriteReferenceClock(uint64_t dst_address, uint32_t num_bytes) { if (dst_address == 0 || (num_bytes != sizeof(uint32_t) && num_bytes != sizeof(uint64_t)) || (dst_address & (num_bytes - 1u)) != 0) { EXIT("invalid reference-clock copy, dst=0x%016" PRIx64 " size=%u\n", dst_address, num_bytes); } const auto value = Sync::ReadReferenceClock(); std::memcpy(reinterpret_cast(dst_address), &value, num_bytes); LOGF("\t copy_data reference clock: dst=0x%016" PRIx64 " value=0x%016" PRIx64 " size=%u\n", dst_address, value, num_bytes); } void CommandProcessor::DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cache_policy, uint64_t dst_address_or_offset, uint8_t src_sel, 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) { EXIT_NOT_IMPLEMENTED(engine > 1); if (num_bytes == 0) { return; } EXIT_NOT_IMPLEMENTED(dst_cache_policy > 3); EXIT_NOT_IMPLEMENTED(src_cache_policy > 3); EXIT_NOT_IMPLEMENTED(wait_for_previous > 1); EXIT_NOT_IMPLEMENTED(write_confirm > 1); EXIT_NOT_IMPLEMENTED(block_engine > 1); if (static_cast(dst_address_or_offset) == 0x3022cu) { return; } auto decode_gds = [](uint8_t selector, bool& is_gds) { switch (selector) { case 0: case 3: is_gds = false; return true; case 1: is_gds = true; return true; default: return false; } }; if (dst_sel == 2) { // kNowhere discards the GL2 prefetch destination without a guest-visible write. if (src_sel != 3) { EXIT("unsupported dmaData nowhere source selector 0x%02" PRIx8 "\n", src_sel); } return; } bool dst_gds = false; if (!decode_gds(dst_sel, dst_gds)) { EXIT("unsupported dmaData destination selector 0x%02" PRIx8 "\n", dst_sel); } auto& buffer_cache = m_renderer.GetBufferCache(); if (src_sel == 2) { buffer_cache.FillBuffer( dst_address_or_offset, num_bytes, static_cast(src_address_or_offset_or_immediate & 0xffffffffu), dst_gds); return; } bool src_gds = false; if (!decode_gds(src_sel, src_gds)) { EXIT("unsupported dmaData source selector 0x%02" PRIx8 "\n", src_sel); } if (src_gds && dst_gds) { EXIT("unsupported dmaData GDS-to-GDS copy\n"); } buffer_cache.CopyBuffer(dst_address_or_offset, src_address_or_offset_or_immediate, num_bytes, dst_gds, src_gds); } void GuestGpu::Enqueue(Submission submission) { EXIT_IF(submission.queue_id >= QueueCount); Common::LockGuard lock(m_queue_mutex); EXIT_IF(!m_accepting); m_queues[submission.queue_id].push_back(std::move(submission)); m_submission_count++; m_work_available.Signal(); } void GuestGpu::WaitForIdle() { Common::LockGuard lock(m_queue_mutex); while (m_processing || !m_commands.empty() || m_submission_count != 0) { m_idle.Wait(&m_queue_mutex); } } void GuestGpu::ThreadRun(void* data) { auto* gpu = static_cast(data); EXIT_IF(gpu == nullptr); KYTY_PROFILER_THREAD("Thread_Gpu"); g_gpu_thread = true; g_gpu_state = gpu; for (;;) { Submission submission; Common::UniqueFunction command; bool has_submission = false; bool should_stop = false; { Common::LockGuard lock(gpu->m_queue_mutex); while (gpu->m_commands.empty() && gpu->m_submission_count == 0 && !gpu->m_stopping) { gpu->m_processing = false; gpu->m_idle.Signal(); gpu->m_work_available.Wait(&gpu->m_queue_mutex); } if (gpu->m_stopping && gpu->m_commands.empty() && gpu->m_submission_count == 0) { gpu->m_processing = false; gpu->m_idle.SignalAll(); should_stop = true; } else if (!gpu->m_commands.empty()) { command = std::move(gpu->m_commands.front()); gpu->m_commands.pop_front(); EXIT_IF(gpu->m_pending_commands.fetch_sub(1, std::memory_order_acq_rel) == 0); gpu->m_processing = true; } else { int selected_queue = -1; for (uint32_t offset = 0; offset < QueueCount; offset++) { const auto id = (gpu->m_next_queue + offset) % QueueCount; if (!gpu->m_queues[id].empty() && !gpu->m_queues[id].front().blocked) { selected_queue = static_cast(id); break; } } if (selected_queue < 0) { gpu->m_processing = false; gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100); for (auto& queue: gpu->m_queues) { if (!queue.empty()) { queue.front().blocked = false; } } continue; } auto& queue = gpu->m_queues[static_cast(selected_queue)]; submission = std::move(queue.front()); queue.pop_front(); gpu->m_submission_count--; gpu->m_next_queue = (static_cast(selected_queue) + 1) % QueueCount; gpu->m_processing = true; has_submission = true; } } if (should_stop) { gpu->m_gfx_cp->BufferWait(); g_gpu_state = nullptr; g_gpu_thread = false; return; } if (command) { EXIT_IF(g_current_processor != nullptr); command(); Common::LockGuard lock(gpu->m_queue_mutex); gpu->m_processing = false; if (gpu->m_commands.empty() && gpu->m_submission_count == 0) { gpu->m_idle.SignalAll(); } continue; } EXIT_IF(!has_submission); const bool complete = gpu->Process(submission); Common::LockGuard lock(gpu->m_queue_mutex); if (!complete) { submission.blocked = true; gpu->m_queues[submission.queue_id].push_front(std::move(submission)); gpu->m_submission_count++; } else { for (auto& queue: gpu->m_queues) { if (!queue.empty()) { queue.front().blocked = false; } } } gpu->m_processing = false; if (gpu->m_commands.empty() && gpu->m_submission_count == 0) { gpu->m_idle.SignalAll(); } } } bool GuestGpu::Process(Submission& submission) { const bool first_slice = !submission.started; auto& cp = GetProcessor(submission.queue_id); if (first_slice && submission.reset_processor) { cp.Reset(); } if (first_slice) { submission.started = true; cp.SetSubmitId(++m_submit_id); cp.ResetDeCe(); cp.SetFlip({}); } cp.BufferInit(); bool complete = true; switch (submission.type) { case SubmissionType::Graphics: { bool progressed = false; submission.constant_complete |= submission.constant_commands.empty(); for (;;) { bool round_progress = false; if (!submission.constant_complete) { submission.constant_complete = cp.Process(submission.constant_execution, submission.constant_commands) == Pm4ProcessResult::Complete; round_progress |= submission.constant_execution.MadeProgress(); } cp.SetCeComplete(submission.constant_complete); if (!submission.command_complete) { submission.command_complete = cp.Process(submission.command_execution, submission.commands) == Pm4ProcessResult::Complete; round_progress |= submission.command_execution.MadeProgress(); } progressed |= round_progress; complete = submission.command_complete && submission.constant_complete; if (complete || !round_progress) { break; } } if (progressed) { if (complete) { m_renderer.RunGarbageCollector(); } cp.BufferFlush(); } else if (complete) { m_renderer.RunGarbageCollector(); } break; } case SubmissionType::Compute: { const auto num_dw = static_cast(submission.commands.size()); const auto* buffer = submission.commands.data(); static uint32_t compute_batch_log_count = 0; if (first_slice && num_dw <= 128 && compute_batch_log_count++ < 32) { LOGF("compute direct batch: data=0x%016" PRIx64 ", num_dw=%" PRIu32 "\n", reinterpret_cast(buffer), num_dw); for (uint32_t i = 0; i < std::min(num_dw, 16); i++) { LOGF("\t compute[%02" PRIu32 "] = 0x%08" PRIx32 "\n", i, buffer[i]); } } if (first_slice) { GraphicsDbgDumpDcb("cc", num_dw, buffer); } complete = cp.Process(submission.command_execution, submission.commands) == Pm4ProcessResult::Complete; if (submission.command_execution.MadeProgress()) { if (complete) { m_renderer.RunGarbageCollector(); } cp.BufferFlush(); } else if (complete) { m_renderer.RunGarbageCollector(); } break; } case SubmissionType::FlipPreparation: m_renderer.RunGarbageCollector(); cp.PrepareCpuFlip(submission.flip_request_id); break; } return complete; } Pm4ProcessResult CommandProcessor::Process(Pm4Execution& execution, std::span commands) { KYTY_PROFILER_BLOCK("CommandProcessor::Process"); EXIT_IF(g_current_execution != nullptr); EXIT_IF(commands.size() > UINT32_MAX); if (execution.m_buffer_stack.empty() && !commands.empty()) { execution.m_buffer_stack.push_back({commands}); } execution.m_suspended = false; execution.m_made_progress = false; struct ExecutionScope { ExecutionScope(CommandProcessor& processor, Pm4Execution& execution) : previous_processor(g_current_processor), previous_execution(g_current_execution) { g_current_processor = &processor; g_current_execution = &execution; } ~ExecutionScope() { g_current_processor = previous_processor; g_current_execution = previous_execution; } CommandProcessor* previous_processor; Pm4Execution* previous_execution; } execution_scope(*this, execution); ProcessPm4(execution); return execution.m_buffer_stack.empty() ? Pm4ProcessResult::Complete : Pm4ProcessResult::Blocked; } void CommandProcessor::ProcessIndirectBuffer(std::span commands, bool chain) { EXIT_IF(g_current_execution == nullptr); EXIT_IF(!g_current_execution->m_next_buffer.empty()); g_current_execution->m_next_buffer = commands; g_current_execution->m_chain = chain; } void CommandProcessor::SuspendPm4() { EXIT_IF(g_current_execution == nullptr); g_current_execution->m_suspended = true; } void CommandProcessor::ProcessPm4(Pm4Execution& execution) { while (!execution.m_buffer_stack.empty()) { if (g_gpu_state != nullptr) { g_gpu_state->ProcessCommands(); } auto& cursor = execution.m_buffer_stack.back(); EXIT_IF(cursor.offset_dw > cursor.commands.size()); if (cursor.offset_dw == cursor.commands.size()) { execution.m_buffer_stack.pop_back(); continue; } const auto* const packet = cursor.commands.data() + cursor.offset_dw; const auto total_dw = static_cast(cursor.commands.size()); const auto remaining_dw = total_dw - cursor.offset_dw; const auto packet_header = packet[0]; const auto opcode = (packet_header >> 8u) & 0xffu; EXIT_NOT_IMPLEMENTED(remaining_dw > total_dw); if (packet_header == 0x80000000u) { cursor.offset_dw++; execution.m_made_progress = true; continue; } EXIT_NOT_IMPLEMENTED(remaining_dw < 2); if (GraphicsRunDebugDumpEnabled()) { LOGF("CP packet: offset=0x%05" PRIx32 " cmd_id=0x%08" PRIx32 " op=0x%02" PRIx32 " len=%" PRIu32 "\n", total_dw - remaining_dw, packet_header, opcode, KYTY_PM4_LEN(packet_header)); } if ((packet_header & 1u) != 0 && ShouldSkipPredicatedPackets()) { auto packet_dw = KYTY_PM4_LEN(packet_header); EXIT_NOT_IMPLEMENTED(packet_dw == 0 || packet_dw > remaining_dw); static std::atomic skip_log_count {0}; if (skip_log_count.fetch_add(1) < 2048) { LOGF("\t predicated skip: op=0x%02" PRIx32 ", r=0x%02" PRIx32 ", len=%" PRIu32 ", packet=0x%016" PRIx64 ", cmd_id=0x%08" PRIx32 "\n", opcode, KYTY_PM4_R(packet_header), packet_dw, reinterpret_cast(packet), packet_header); } if (opcode == Pm4::IT_NOP && KYTY_PM4_R(packet_header) == Pm4::R_RELEASE_MEM && packet_dw >= 7) { static std::atomic log_count {0}; if (log_count.fetch_add(1) < 128) { const auto dst = packet[3] | (static_cast(packet[4]) << 32u); const auto val = packet[5] | (static_cast(packet[6]) << 32u); LOGF("\t predicated skip: R_RELEASE_MEM dst=0x%016" PRIx64 ", value=0x%016" PRIx64 ", action=0x%08" PRIx32 ", gcr/data/int=0x%08" PRIx32 "\n", dst, val, packet[1], packet[2]); } } cursor.offset_dw += packet_dw; execution.m_made_progress = true; continue; } auto handler = g_cp_op_func[opcode]; if (handler == nullptr) { const auto offset = total_dw - remaining_dw; LOGF("unknown PM4 packet: data=0x%016" PRIx64 ", num_dw=%" PRIu32 ", offset=0x%05" PRIx32 ", current=0x%016" PRIx64 "\n", reinterpret_cast(packet - offset), total_dw, offset, reinterpret_cast(packet)); const auto dump_begin = (offset > 8 ? offset - 8 : 0); const auto dump_end = std::min(total_dw, offset + 16); auto* const base = packet - offset; for (uint32_t i = dump_begin; i < dump_end; i++) { LOGF("\t%05" PRIx32 "%s %08" PRIx32 "\n", i, (i == offset ? ":" : " "), base[i]); } EXIT("unknown op\n\t%05" PRIx32 ":\n\tcmd_id = %08" PRIx32 "\n", total_dw - remaining_dw, packet_header); } const auto packet_dw = handler(*this, packet_header & ~1u, packet + 1, remaining_dw, total_dw) + 1; EXIT_IF(packet_dw > remaining_dw); if (execution.m_suspended) { return; } cursor.offset_dw += packet_dw; execution.m_made_progress = true; if (!execution.m_next_buffer.empty()) { // Chains and taken branches reuse the fetcher; only calls retain a return cursor. if (execution.m_chain) { cursor = {execution.m_next_buffer}; } else { execution.m_buffer_stack.push_back({execution.m_next_buffer}); } execution.m_next_buffer = {}; } } } void CommandProcessor::SetIndexType(uint32_t index_type_and_size) { m_index_type_and_size = index_type_and_size & 0x3u; } void CommandProcessor::SetIndexBaseAddress(uint64_t index_base_addr) { m_index_base_addr = index_base_addr; } void CommandProcessor::SetIndexBufferSize(uint32_t index_buffer_size) { m_index_buffer_size = index_buffer_size; } void CommandProcessor::SetDrawIndirectArgsBaseAddress(uint64_t draw_indirect_args_base_addr) { m_draw_indirect_args_base_addr = draw_indirect_args_base_addr; } void CommandProcessor::SetDispatchIndirectArgsBaseAddress( uint64_t dispatch_indirect_args_base_addr) { m_dispatch_indirect_args_base_addr = dispatch_indirect_args_base_addr; } void CommandProcessor::SetNumInstances(uint32_t num_instances) { if (num_instances == 0) { num_instances = 1; } m_num_instances = num_instances; } void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t wait_op, const volatile void* address, uint32_t count_in_dwords) { (void)count_in_dwords; uint64_t value = 0; switch (op) { case 0x00: m_predicate_skip = false; return; case 0x01: { EXIT_NOT_IMPLEMENTED(address == nullptr); // One begin/end pair per DB; bit 63 marks each counter ready. constexpr uint64_t ready_bit = 1ull << 63u; const auto* results = reinterpret_cast(address); for (uint32_t db = 0; db < 16u; db++) { const auto begin = results[db * 2u]; const auto end = results[db * 2u + 1u]; if ((begin & end & ready_bit) == 0) { if (wait_op == 0) { SuspendPm4(); } else { m_predicate_skip = false; } return; } value += end - begin; } } break; case 0x03: if (wait_op != 0) { BufferFlushAndWait(); } EXIT_NOT_IMPLEMENTED(address == nullptr); value = *reinterpret_cast(address); break; default: EXIT("unknown predication op: 0x%08" PRIx32 "\n", op); } switch (condition) { case 0x00: m_predicate_skip = (value != 0); break; case 0x01: m_predicate_skip = (value == 0); break; default: EXIT("unknown predication condition: 0x%08" PRIx32 "\n", condition); } if (op == 0x03) { static std::atomic log_count {0}; if (log_count.fetch_add(1) < 128) { LOGF("\t bool predication: addr=0x%016" PRIx64 ", value=0x%016" PRIx64 ", condition=%" PRIu32 ", skip=%u, wait_op=%" PRIu32 "\n", reinterpret_cast(address), value, condition, m_predicate_skip ? 1u : 0u, wait_op); } } } void CommandProcessor::DrawIndex(DrawIndexArgs args) { CheckBuffer(); args.index_type_and_size = m_index_type_and_size; if (args.instance_count == 0) { args.instance_count = m_num_instances; } if (args.base_vertex != 0 || args.first_instance != 0) { LOGF("\t draw indexed offsets: base_vertex = %" PRId32 ", first_instance = %" PRIu32 "\n", args.base_vertex, args.first_instance); } m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(), args); } void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_count) { uint64_t index_size = 0; switch (m_index_type_and_size) { case 0: index_size = 2; break; case 1: index_size = 4; break; case 2: index_size = 1; break; default: EXIT("unknown index_type_and_size: %u\n", m_index_type_and_size); } auto* index_addr = reinterpret_cast( m_index_base_addr + static_cast(index_offset) * index_size); DrawIndex({.index_count = index_count, .index_addr = index_addr}); } void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed) { EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != 2u); EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0); const auto* args_addr = reinterpret_cast(m_draw_indirect_args_base_addr + data_offset); if (!indexed) { DrawIndirectArgs args {}; std::memcpy(&args, args_addr, sizeof(args)); if (args.instance_count != 1u || args.start_vertex_location != 0u || args.start_instance_location != 0u) { static std::atomic log_count {0}; if (log_count.fetch_add(1) < 64) { LOGF("\t warning: partial DrawIndirect args: vertex_count=%" PRIu32 ", instance_count=%" PRIu32 ", start_vertex=%" PRIu32 ", start_instance=%" PRIu32 "\n", args.vertex_count_per_instance, args.instance_count, args.start_vertex_location, args.start_instance_location); } } m_num_instances = args.instance_count; DrawIndexAuto({.vertex_count = args.vertex_count_per_instance, .instance_count = args.instance_count, .first_vertex = args.start_vertex_location, .first_instance = args.start_instance_location, .offset_source = DrawOffsetSource::IndirectArgs}); return; } DrawIndexedIndirectArgs args {}; std::memcpy(&args, args_addr, sizeof(args)); if (args.base_vertex_location != 0u || args.start_instance_location != 0u) { static std::atomic log_count {0}; if (log_count.fetch_add(1) < 64) { LOGF("\t warning: partial DrawIndexIndirect args: index_count=%" PRIu32 ", instance_count=%" PRIu32 ", start_index=%" PRIu32 ", base_vertex=%" PRIu32 ", start_instance=%" PRIu32 "\n", args.index_count_per_instance, args.instance_count, args.start_index_location, args.base_vertex_location, args.start_instance_location); } } uint64_t index_size = 0; switch (m_index_type_and_size) { case 0: index_size = 2; break; case 1: index_size = 4; break; case 2: index_size = 1; break; default: EXIT("unknown index_type_and_size: %u\n", m_index_type_and_size); } auto* index_addr = reinterpret_cast( m_index_base_addr + static_cast(args.start_index_location) * index_size); const uint32_t index_count = (m_index_buffer_size != 0 ? std::min(args.index_count_per_instance, m_index_buffer_size) : args.index_count_per_instance); if (GraphicsRunDebugDumpEnabled() && index_count != args.index_count_per_instance) { static std::atomic log_count {0}; if (log_count.fetch_add(1, std::memory_order_relaxed) < 64) { LOGF("\t DrawIndexIndirect: clamped index_count from %" PRIu32 " to %" PRIu32 " using INDEX_BUFFER_SIZE\n", args.index_count_per_instance, index_count); } } m_num_instances = args.instance_count; DrawIndex({.index_count = index_count, .index_addr = index_addr, .instance_count = args.instance_count, .base_vertex = static_cast(args.base_vertex_location), .first_instance = args.start_instance_location, .offset_source = DrawOffsetSource::IndirectArgs}); } void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_count_or_count, const volatile uint32_t* count_addr, uint32_t stride_in_bytes, uint32_t draw_initiator, bool indexed) { EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != 2u); EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0); uint32_t draw_count = max_count_or_count; if (count_addr != nullptr) { draw_count = *count_addr; if (draw_count > max_count_or_count) { draw_count = max_count_or_count; } } if (draw_count == 0) { return; } const auto args_size = indexed ? sizeof(DrawIndexedIndirectArgs) : sizeof(DrawIndirectArgs); EXIT_NOT_IMPLEMENTED(stride_in_bytes < args_size); for (uint32_t i = 0; i < draw_count; i++) { const auto args_addr = m_draw_indirect_args_base_addr + data_offset + static_cast(i) * stride_in_bytes; if (!indexed) { auto* args = reinterpret_cast(args_addr); if (args->instance_count != 1u || args->start_vertex_location != 0u || args->start_instance_location != 0u) { static std::atomic log_count {0}; if (log_count.fetch_add(1) < 64) { LOGF("\t warning: partial DrawIndirectMulti args[%u]: vertex_count=%" PRIu32 ", instance_count=%" PRIu32 ", start_vertex=%" PRIu32 ", start_instance=%" PRIu32 "\n", i, args->vertex_count_per_instance, args->instance_count, args->start_vertex_location, args->start_instance_location); } } m_num_instances = args->instance_count; DrawIndexAuto({.vertex_count = args->vertex_count_per_instance, .instance_count = args->instance_count, .first_vertex = args->start_vertex_location, .first_instance = args->start_instance_location, .offset_source = DrawOffsetSource::IndirectArgs}); continue; } auto* args = reinterpret_cast(args_addr); if (args->base_vertex_location != 0u || args->start_instance_location != 0u) { static std::atomic log_count {0}; if (log_count.fetch_add(1) < 64) { LOGF("\t warning: partial DrawIndexIndirectMulti args[%u]: index_count=%" PRIu32 ", instance_count=%" PRIu32 ", start_index=%" PRIu32 ", base_vertex=%" PRIu32 ", start_instance=%" PRIu32 "\n", i, args->index_count_per_instance, args->instance_count, args->start_index_location, args->base_vertex_location, args->start_instance_location); } } uint64_t index_size = 0; switch (m_index_type_and_size) { case 0: index_size = 2; break; case 1: index_size = 4; break; case 2: index_size = 1; break; default: EXIT("unknown index_type_and_size: %u\n", m_index_type_and_size); } auto* index_addr = reinterpret_cast( m_index_base_addr + static_cast(args->start_index_location) * index_size); const uint32_t index_count = (m_index_buffer_size != 0 ? std::min(args->index_count_per_instance, m_index_buffer_size) : args->index_count_per_instance); if (GraphicsRunDebugDumpEnabled() && index_count != args->index_count_per_instance) { static std::atomic log_count {0}; if (log_count.fetch_add(1, std::memory_order_relaxed) < 64) { LOGF("\t DrawIndexIndirectMulti: clamped index_count from %" PRIu32 " to %" PRIu32 " using INDEX_BUFFER_SIZE\n", args->index_count_per_instance, index_count); } } m_num_instances = args->instance_count; DrawIndex({.index_count = index_count, .index_addr = index_addr, .instance_count = args->instance_count, .base_vertex = static_cast(args->base_vertex_location), .first_instance = args->start_instance_location, .offset_source = DrawOffsetSource::IndirectArgs}); } } void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode) { m_sh_ctx.SetCsWaveSize(Pm4::ComputeWaveSize(mode)); uint32_t frame_num = 0; // uint32_t local_x = 1; // uint32_t local_y = 1; // uint32_t local_z = 1; { CheckBuffer(); frame_num = m_renderer.GetGpu().GetFrameNum(); if (GraphicsRunDebugDumpEnabled()) { static std::atomic log_count {0}; if (log_count.fetch_add(1, std::memory_order_relaxed) < 1024) { const auto& cs = m_sh_ctx.GetCs().cs_regs; const auto& oa = m_ucfg.GetGdsOaCounter(m_ucfg.GetGdsOaState().GetIndex()); LOGF("QueuePoint DispatchDirect: frame=%u submit=%" PRIu64 " groups=%ux%ux%u local=%ux%ux%u mode=0x%08" PRIx32 " wave=%u cs=0x%016" PRIx64 " oa_index=%u oa_enabled=%s oa_addr=0x%04" PRIx32 " oa_space=0x%08" PRIx32 "\n", frame_num, m_submit_id, thread_group_x, thread_group_y, thread_group_z, std::max(cs.num_thread_x, 1u), std::max(cs.num_thread_y, 1u), std::max(cs.num_thread_z, 1u), mode, static_cast(cs.wave_size), cs.data_addr, m_ucfg.GetGdsOaState().GetIndex(), oa.IsCounterEnabled() ? "true" : "false", oa.GetAddressBytes(), oa.GetSpaceAvailable()); } } const auto& cs = m_sh_ctx.GetCs().cs_regs; // local_x = std::max(cs.num_thread_x, 1u); // local_y = std::max(cs.num_thread_y, 1u); // local_z = std::max(cs.num_thread_z, 1u); m_renderer.GetRenderExecutor().DispatchDirect(m_submit_id, CurrentBuffer(), thread_group_x, thread_group_y, thread_group_z, mode); } /*constexpr uint32_t DispatchInitiatorUseThreadDimensions = 1u << 5u; auto group_count = [](uint32_t threads, uint32_t group_size) { return (threads == 0 ? 0u : (threads + std::max(group_size, 1u) - 1u) / std::max(group_size, 1u)); }; auto groups_x = thread_group_x; auto groups_y = thread_group_y; auto groups_z = thread_group_z; if ((mode & DispatchInitiatorUseThreadDimensions) != 0) { groups_x = group_count(thread_group_x, local_x); groups_y = group_count(thread_group_y, local_y); groups_z = group_count(thread_group_z, local_z); } const uint64_t invocations = static_cast(groups_x) * groups_y * groups_z * local_x * local_y * local_z; if (invocations != 0) { BufferFlushAndWait(); }*/ } void CommandProcessor::DispatchIndirect(uint32_t data_offset, uint32_t mode) { struct DispatchIndirectArgs { uint32_t thread_group_x; uint32_t thread_group_y; uint32_t thread_group_z; }; EXIT_NOT_IMPLEMENTED(m_dispatch_indirect_args_base_addr == 0); const auto args_addr = m_dispatch_indirect_args_base_addr + data_offset; auto* args = reinterpret_cast(args_addr); DispatchDirect(args->thread_group_x, args->thread_group_y, args->thread_group_z, mode); } void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) { CheckBuffer(); if (args.instance_count == 0) { args.instance_count = m_num_instances; } m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), args); } void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) { BufferFlush(); m_renderer.GetVideoOut().WaitFlipDone(static_cast(video_out_handle), static_cast(display_buffer_index)); } template void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_write_dest, uint32_t eop_event_type, uint32_t cache_action, uint32_t event_index, uint32_t event_write_source, void* dst_gpu_addr, T value, uint32_t interrupt_selector, uint32_t interrupt_context_id) { static_assert(sizeof(T) == sizeof(uint32_t) || sizeof(T) == sizeof(uint64_t)); CheckBuffer(); if (GraphicsRunDebugDumpEnabled()) { const auto bits = static_cast(sizeof(T) * 8u); const auto log_width = static_cast(sizeof(T) * 2u); LOGF("CommandProcessor::WriteAtEndOfPipe%u()\n" "\t cache_policy = 0x%08" PRIx32 "\n" "\t event_write_dest = 0x%08" PRIx32 "\n" "\t eop_event_type = 0x%08" PRIx32 "\n" "\t cache_action = 0x%08" PRIx32 "\n" "\t event_index = 0x%08" PRIx32 "\n" "\t event_write_source = 0x%08" PRIx32 "\n" "\t interrupt_selector = 0x%08" PRIx32 "\n" "\t interrupt_context = 0x%08" PRIx32 "\n" "\t dst_gpu_addr = 0x%016" PRIx64 "\n" "\t value = 0x%0*" PRIx64 "\n", bits, cache_policy, event_write_dest, eop_event_type, cache_action, event_index, event_write_source, interrupt_selector, interrupt_context_id, reinterpret_cast(dst_gpu_addr), log_width, static_cast(value)); } EXIT_NOT_IMPLEMENTED(cache_policy != 0x00000000); EXIT_NOT_IMPLEMENTED(event_write_dest != 0x00000000); bool with_interrupt = false; switch (interrupt_selector) { case 0x00: case 0x03: with_interrupt = false; break; case 0x01: if (!IsAsyncComputeQueue()) { Sync::TriggerEopEventAtEndOfPipe(CurrentBuffer(), m_interrupt_event_id, interrupt_context_id); return; } with_interrupt = true; break; case 0x02: with_interrupt = true; break; default: EXIT("unknown interrupt_selector\n"); } auto write32 = [&](bool with_writeback) { auto* dst = static_cast(dst_gpu_addr); auto data = static_cast(value); std::memcpy(dst, &data, sizeof(data)); if (with_interrupt) { if (with_writeback) { Sync::WriteAtEndOfPipeWithInterruptWriteBack32(m_submit_id, CurrentBuffer(), dst, data, m_interrupt_event_id, interrupt_context_id); } else { Sync::WriteAtEndOfPipeWithInterrupt32(m_submit_id, CurrentBuffer(), dst, data, m_interrupt_event_id, interrupt_context_id); } } else if (with_writeback) { Sync::WriteAtEndOfPipeWithWriteBack32(m_submit_id, CurrentBuffer(), dst, data); } else { Sync::WriteAtEndOfPipe32(m_submit_id, CurrentBuffer(), dst, data); } }; switch (event_write_source) { case 0x01: if constexpr (sizeof(T) == sizeof(uint32_t)) { if (eop_event_type == 0x2f && cache_action == 0x00 && event_index == 0x06) { auto* dst = static_cast(dst_gpu_addr); SynchronizeGpu(); Sync::ReadGds(*m_renderer.GetBufferCache().GetGdsBuffer(), dst, value & 0xffffu, value >> 16u); Sync::WriteAtEndOfPipeGds32(m_submit_id, CurrentBuffer(), dst, value & 0xffffu, value >> 16u); if (with_interrupt) { m_renderer.TriggerInterrupt(m_interrupt_event_id, interrupt_context_id); } return; } } else if (eop_event_type == 0x04 && cache_action == 0x00 && event_index == 0x05) { write32(false); return; } break; case 0x02: if constexpr (sizeof(T) == sizeof(uint32_t)) { if (eop_event_type == 0x2f && event_index == 0x06) { switch (cache_action) { case 0x00: write32(false); return; case 0x38: write32(true); return; default: break; } } } else { auto write64 = [&](bool with_writeback) { auto* dst = static_cast(dst_gpu_addr); std::memcpy(dst, &value, sizeof(value)); if (with_interrupt) { if (with_writeback) { Sync::WriteAtEndOfPipeWithInterruptWriteBack64( m_submit_id, CurrentBuffer(), dst, value, m_interrupt_event_id, interrupt_context_id); } else { Sync::WriteAtEndOfPipeWithInterrupt64(m_submit_id, CurrentBuffer(), dst, value, m_interrupt_event_id, interrupt_context_id); } } else if (with_writeback) { Sync::WriteAtEndOfPipeWithWriteBack64(m_submit_id, CurrentBuffer(), dst, value); } else { Sync::WriteAtEndOfPipe64(m_submit_id, CurrentBuffer(), dst, value); } }; switch (cache_action) { case 0x00: switch (eop_event_type) { case 0x04: if (event_index == 0x05) { write64(false); return; } break; case 0x14: case 0x28: case 0x2f: if (event_index == 0x00) { write64(false); return; } break; case 0x2b: case 0x2d: case 0x30: if (event_index == 0x00 && !with_interrupt) { write64(false); return; } break; default: break; } break; case 0x38: switch (eop_event_type) { case 0x04: case 0x14: case 0x28: if (((eop_event_type == 0x04 || eop_event_type == 0x28) && event_index == 0x05 && !with_interrupt) || (event_index == 0x00)) { write64(true); return; } break; case 0x2b: case 0x2d: if (event_index == 0x00 && !with_interrupt) { write64(true); return; } break; case 0x2f: if (event_index == 0x06 && !with_interrupt) { write64(true); return; } break; default: break; } break; case 0x3b: if (eop_event_type == 0x04 && event_index == 0x05 && with_interrupt) { write64(true); return; } break; default: break; } } break; case 0x04: if constexpr (sizeof(T) == sizeof(uint64_t)) { const auto clock = Sync::ReadReferenceClock(); auto* dst = static_cast(dst_gpu_addr); std::memcpy(dst, &clock, sizeof(clock)); switch (cache_action) { case 0x00: if ((eop_event_type == 0x04 && event_index == 0x05) || (eop_event_type == 0x28 && event_index == 0x00)) { if (with_interrupt) { Sync::WriteAtEndOfPipeWithInterrupt64( m_submit_id, CurrentBuffer(), dst, clock, m_interrupt_event_id, interrupt_context_id); } else { Sync::WriteAtEndOfPipeClockCounter(m_submit_id, CurrentBuffer(), dst, clock); } return; } break; case 0x38: if ((eop_event_type == 0x04 && (event_index == 0x00 || event_index == 0x05)) || (eop_event_type == 0x28 && event_index == 0x00)) { if (with_interrupt) { Sync::WriteAtEndOfPipeWithInterruptWriteBack64( m_submit_id, CurrentBuffer(), dst, clock, m_interrupt_event_id, interrupt_context_id); } else { Sync::WriteAtEndOfPipeClockCounterWithWriteBack( m_submit_id, CurrentBuffer(), dst, clock); } return; } break; default: break; } } break; default: break; } EXIT("unknown event type\n"); } void CommandProcessor::WriteAtEndOfPipe32(uint32_t cache_policy, uint32_t event_write_dest, uint32_t eop_event_type, uint32_t cache_action, uint32_t event_index, uint32_t event_write_source, void* dst_gpu_addr, uint32_t value, uint32_t interrupt_selector, uint32_t interrupt_context_id) { WriteAtEndOfPipe(cache_policy, event_write_dest, eop_event_type, cache_action, event_index, event_write_source, dst_gpu_addr, value, interrupt_selector, interrupt_context_id); } void CommandProcessor::WriteAtEndOfPipe64(uint32_t cache_policy, uint32_t event_write_dest, uint32_t eop_event_type, uint32_t cache_action, uint32_t event_index, uint32_t event_write_source, void* dst_gpu_addr, uint64_t value, uint32_t interrupt_selector, uint32_t interrupt_context_id) { WriteAtEndOfPipe(cache_policy, event_write_dest, eop_event_type, cache_action, event_index, event_write_source, dst_gpu_addr, value, interrupt_selector, interrupt_context_id); } void CommandProcessor::EmitGlobalBarrier() { CheckBuffer(); Common::LockGuard lock(m_renderer.GetMutex()); vk::MemoryBarrier2 barrier {}; barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands; barrier.srcAccessMask = vk::AccessFlagBits2::eMemoryWrite; barrier.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands; barrier.dstAccessMask = vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite; vk::DependencyInfo dependency {}; dependency.memoryBarrierCount = 1; dependency.pMemoryBarriers = &barrier; GetScheduler().EndRendering(); CurrentBuffer().Handle().pipelineBarrier2(dependency); } void CommandProcessor::TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id) { CheckBuffer(); Sync::TriggerEopEventAtEndOfPipe(CurrentBuffer(), m_interrupt_event_id, interrupt_context_id); } void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index, uint64_t event_address) { if (GraphicsRunDebugDumpEnabled()) { LOGF("CommandProcessor::TriggerEvent()\n" "\t event_type = 0x%08" PRIx32 "\n" "\t event_index = 0x%08" PRIx32 "\n" "\t address = 0x%016" PRIx64 "\n", event_type, event_index, event_address); } const auto valid_cache_event_index = event_index == 0x00000000 || event_index == 0x00000007; switch (event_type) { // CsPartialFlush, GsPartialFlush, PsPartialFlush. case 0x00000007: case 0x0000000f: case 0x00000010: EmitGlobalBarrier(); break; // CbDbDataWritebackInvalidate, CbDataWritebackInvalidate. case 0x00000016: case 0x00000031: if (!valid_cache_event_index) { EXIT("unknown event type: 0x%08" PRIx32 ", 0x%08" PRIx32 "\n", event_type, event_index); } EmitGlobalBarrier(); break; // DbDataWritebackInvalidate, DbMetadataWritebackInvalidate, CbMetadataWritebackInvalidate. case 0x0000002a: case 0x0000002c: case 0x0000002e: if (!valid_cache_event_index) { EXIT("unknown event type: 0x%08" PRIx32 ", 0x%08" PRIx32 "\n", event_type, event_index); } EmitGlobalBarrier(); break; case 0x0000000d: case 0x0000000e: case 0x00000012: case 0x00000017: case 0x00000018: case 0x00000019: case 0x0000001a: case 0x0000001b: case 0x00000038: case 0x0000003a: LOGF("\t temporary: ignoring unsupported event_write type 0x%08" PRIx32 ", index 0x%08" PRIx32 "\n", event_type, event_index); break; case 0x00000039: { if (event_index != 0x00000001 || event_address == 0 || (event_address & 0x7u) != 0) { EXIT("invalid occlusion-counter dump: index=0x%08" PRIx32 ", address=0x%016" PRIx64 "\n", event_index, event_address); } static std::once_flag warning_once; std::call_once(warning_once, [] { std::printf("Warning: game uses occlusion queries, which are currently treated as " "always visible; GPU usage may be higher and FPS may be lower.\n"); }); // Until host occlusion queries are implemented, publish an always-visible result. The // PS5 layout contains one interleaved begin/end pair per DB, and bit 63 marks a result // ready. constexpr uint64_t ready_bit = 1ull << 63u; constexpr uint64_t counter_mask = ready_bit - 1u; auto* results = reinterpret_cast(event_address); const auto value = ready_bit | m_synthetic_occlusion_counter; for (uint32_t db = 0; db < 16u; db++) { results[db * 2u] = value; } m_synthetic_occlusion_counter = (m_synthetic_occlusion_counter + 1u) & counter_mask; break; } default: EXIT("unknown event type: 0x%08" PRIx32 ", 0x%08" PRIx32 "\n", event_type, event_index); } } void CommandProcessor::Flip() { CheckBuffer(); if (GraphicsRunDebugDumpEnabled()) { LOGF("CommandProcessor::Flip()\n"); } auto& command = CurrentBuffer(); auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode, m_flip.flip_arg); Sync::WriteAtEndOfPipeOnlyFlip(m_submit_id, command, m_flip.handle, m_flip.index, m_flip.flip_mode, m_flip.flip_arg, request); GetScheduler().Flush(); } void CommandProcessor::Flip(void* dst_gpu_addr, uint32_t value) { CheckBuffer(); if (GraphicsRunDebugDumpEnabled()) { LOGF("CommandProcessor::Flip()\n" "\t dst_gpu_addr = 0x%016" PRIx64 "\n" "\t value = 0x%08" PRIx32 "\n", reinterpret_cast(dst_gpu_addr), value); } std::memcpy(dst_gpu_addr, &value, sizeof(value)); auto& command = CurrentBuffer(); auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode, m_flip.flip_arg); Sync::WriteAtEndOfPipeWithFlip32(m_submit_id, command, static_cast(dst_gpu_addr), value, m_flip.handle, m_flip.index, m_flip.flip_mode, m_flip.flip_arg, request); GetScheduler().Flush(); } void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache_action, void* dst_gpu_addr, uint32_t value) { CheckBuffer(); if (GraphicsRunDebugDumpEnabled()) { LOGF("CommandProcessor::FlipWithInterrupt()\n" "\t eop_event_type = 0x%08" PRIx32 "\n" "\t cache_action = 0x%08" PRIx32 "\n" "\t dst_gpu_addr = 0x%016" PRIx64 "\n" "\t value = 0x%08" PRIx32 "\n", eop_event_type, cache_action, reinterpret_cast(dst_gpu_addr), value); } if (eop_event_type != 0x00000004 || cache_action != 0x00000038) { EXIT("unknown event type\n"); } std::memcpy(dst_gpu_addr, &value, sizeof(value)); auto& command = CurrentBuffer(); auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode, m_flip.flip_arg); Sync::WriteAtEndOfPipeWithInterruptWriteBackFlip32( m_submit_id, command, static_cast(dst_gpu_addr), value, m_flip.handle, m_flip.index, m_flip.flip_mode, m_flip.flip_arg, request, m_interrupt_event_id); GetScheduler().Flush(); } void CommandProcessor::PrepareCpuFlip(uint64_t request_id) { CheckBuffer(); if (g_current_processor != nullptr) { EXIT("invalid graphics-thread CPU flip preparation\n"); } struct ProcessorScope { explicit ProcessorScope(CommandProcessor& processor) { g_current_processor = &processor; } ~ProcessorScope() { g_current_processor = nullptr; } }; ProcessorScope processor_scope(*this); m_renderer.GetVideoOut().PrepareFlip(request_id, CurrentBuffer()); GetScheduler().Flush(); m_renderer.GetVideoOut().CompleteFlip(request_id); } void CommandProcessor::SynchronizeGpu() { GetScheduler().Finish(); } bool GuestGpu::IsGpuThread() noexcept { return g_gpu_thread; } } // namespace Libs::Graphics