// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include "common/assert.h" #include "common/logging.h" #include "common/settings.h" #include "core/core.h" #include "video_core/control/channel_state.h" #include "video_core/dma_pusher.h" #include "video_core/engines/fermi_2d.h" #include "video_core/engines/kepler_compute.h" #include "video_core/engines/kepler_memory.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/engines/maxwell_dma.h" #include "video_core/engines/puller.h" #include "video_core/gpu.h" #include "video_core/memory_manager.h" #include "video_core/rasterizer_interface.h" namespace Tegra::Engines { void Puller::ProcessBindMethod(DmaPusher& dma_pusher, const MethodCall& method_call) { // Bind the current subchannel to the desired engine id. LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", method_call.subchannel, method_call.argument); const auto engine_id = static_cast(method_call.argument); bound_engines[method_call.subchannel] = engine_id; switch (engine_id) { case EngineID::FERMI_TWOD_A: dma_pusher.BindSubchannel(&dma_pusher.channel_state.payload->fermi_2d, method_call.subchannel, EngineTypes::Fermi2D); break; case EngineID::MAXWELL_B: dma_pusher.BindSubchannel(&dma_pusher.channel_state.payload->maxwell_3d, method_call.subchannel, EngineTypes::Maxwell3D); break; case EngineID::KEPLER_COMPUTE_B: dma_pusher.BindSubchannel(&dma_pusher.channel_state.payload->kepler_compute, method_call.subchannel, EngineTypes::KeplerCompute); break; case EngineID::MAXWELL_DMA_COPY_A: dma_pusher.BindSubchannel(&dma_pusher.channel_state.payload->maxwell_dma, method_call.subchannel, EngineTypes::MaxwellDMA); break; case EngineID::KEPLER_INLINE_TO_MEMORY_B: dma_pusher.BindSubchannel(&dma_pusher.channel_state.payload->kepler_memory, method_call.subchannel, EngineTypes::KeplerMemory); break; case EngineID::NV01_TIMER: dma_pusher.BindSubchannel(&dma_pusher.channel_state.payload->nv01_timer, method_call.subchannel, EngineTypes::Nv01Timer); break; default: UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id); break; } } void Puller::ProcessFenceActionMethod(DmaPusher& dma_pusher) { switch (regs.fence_action.op) { case Puller::FenceOperation::Acquire: // UNIMPLEMENTED_MSG("Channel Scheduling pending."); // WaitFence(regs.fence_action.syncpoint_id, regs.fence_value); dma_pusher.rasterizer->ReleaseFences(); break; case Puller::FenceOperation::Increment: dma_pusher.rasterizer->SignalSyncPoint(regs.fence_action.syncpoint_id); break; default: UNIMPLEMENTED_MSG("Unimplemented operation {}", regs.fence_action.op.Value()); break; } } void Puller::ProcessSemaphoreTriggerMethod(DmaPusher& dma_pusher) { const auto semaphoreOperationMask = 0xF; const auto op = GpuSemaphoreOperation(regs.semaphore_trigger & semaphoreOperationMask); if (op == GpuSemaphoreOperation::WriteLong) { const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()}; const u32 payload = regs.semaphore_sequence; dma_pusher.rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload, VideoCommon::QueryPropertiesFlags::HasTimeout, payload, 0); } else { do { const u32 word = dma_pusher.memory_manager.Read(regs.semaphore_address.SemaphoreAddress()); regs.acquire_source = true; regs.acquire_value = regs.semaphore_sequence; if (op == GpuSemaphoreOperation::AcquireEqual) { regs.acquire_active = true; regs.acquire_mode = false; if (word != regs.acquire_value) { dma_pusher.rasterizer->ReleaseFences(); continue; } } else if (op == GpuSemaphoreOperation::AcquireGequal) { regs.acquire_active = true; regs.acquire_mode = true; if (word < regs.acquire_value) { dma_pusher.rasterizer->ReleaseFences(); continue; } } else if (op == GpuSemaphoreOperation::AcquireMask) { if (word && regs.semaphore_sequence == 0) { dma_pusher.rasterizer->ReleaseFences(); continue; } } else { LOG_ERROR(HW_GPU, "Invalid semaphore operation"); } } while (false); } } void Puller::ProcessSemaphoreRelease(DmaPusher& dma_pusher) { const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()}; const u32 payload = regs.semaphore_release; dma_pusher.rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload, VideoCommon::QueryPropertiesFlags::IsAFence, payload, 0); } void Puller::ProcessSemaphoreAcquire(DmaPusher& dma_pusher) { u32 word = dma_pusher.memory_manager.Read(regs.semaphore_address.SemaphoreAddress()); const auto value = regs.semaphore_acquire; while (word != value) { regs.acquire_active = true; regs.acquire_value = value; dma_pusher.rasterizer->ReleaseFences(); word = dma_pusher.memory_manager.Read(regs.semaphore_address.SemaphoreAddress()); // TODO(kemathe73) figure out how to do the acquire_timeout regs.acquire_mode = false; regs.acquire_source = false; } } /// Calls a GPU puller method. void Puller::CallPullerMethod(DmaPusher& dma_pusher, const MethodCall& method_call) { regs.reg_array[method_call.method] = method_call.argument; const auto method = static_cast(method_call.method); switch (method) { case BufferMethods::BindObject: { ProcessBindMethod(dma_pusher, method_call); break; } case BufferMethods::Nop: case BufferMethods::SemaphoreAddressHigh: case BufferMethods::SemaphoreAddressLow: case BufferMethods::SemaphoreSequencePayload: case BufferMethods::SyncpointPayload: case BufferMethods::WrcacheFlush: break; case BufferMethods::RefCnt: dma_pusher.rasterizer->SignalReference(); break; case BufferMethods::SyncpointOperation: ProcessFenceActionMethod(dma_pusher); break; case BufferMethods::WaitForIdle: dma_pusher.rasterizer->WaitForIdle(); break; case BufferMethods::SemaphoreOperation: { ProcessSemaphoreTriggerMethod(dma_pusher); break; } case BufferMethods::NonStallInterrupt: { LOG_ERROR(HW_GPU, "Special puller engine method NonStallInterrupt not implemented"); break; } case BufferMethods::MemOpA: { LOG_ERROR(HW_GPU, "Memory Operation A"); break; } case BufferMethods::MemOpB: { // Implement this better. dma_pusher.rasterizer->InvalidateGPUCache(); break; } case BufferMethods::MemOpC: case BufferMethods::MemOpD: { LOG_ERROR(HW_GPU, "Memory Operation C,D"); break; } case BufferMethods::SemaphoreAcquire: { ProcessSemaphoreAcquire(dma_pusher); break; } case BufferMethods::SemaphoreRelease: { ProcessSemaphoreRelease(dma_pusher); break; } case BufferMethods::Yield: { // TODO(Kmather73): Research and implement this method. LOG_ERROR(HW_GPU, "Special puller engine method Yield not implemented"); break; } default: LOG_ERROR(HW_GPU, "Special puller engine method {:X} not implemented", method); break; } } /// Calls a GPU engine method. void Puller::CallEngineMethod(DmaPusher& dma_pusher, const MethodCall& method_call) { const EngineID engine = bound_engines[method_call.subchannel]; switch (engine) { case EngineID::FERMI_TWOD_A: dma_pusher.channel_state.payload->fermi_2d.CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); break; case EngineID::MAXWELL_B: dma_pusher.channel_state.payload->maxwell_3d.CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); break; case EngineID::KEPLER_COMPUTE_B: dma_pusher.channel_state.payload->kepler_compute.CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); break; case EngineID::MAXWELL_DMA_COPY_A: dma_pusher.channel_state.payload->maxwell_dma.CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); break; case EngineID::KEPLER_INLINE_TO_MEMORY_B: dma_pusher.channel_state.payload->kepler_memory.CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); break; case EngineID::NV01_TIMER: dma_pusher.channel_state.payload->nv01_timer.CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); break; default: UNIMPLEMENTED_MSG("Unimplemented engine"); break; } } /// Calls a GPU engine multivalue method. void Puller::CallEngineMultiMethod(DmaPusher& dma_pusher, u32 method, u32 subchannel, const u32* base_start, u32 amount, u32 methods_pending) { const EngineID engine = bound_engines[subchannel]; switch (engine) { case EngineID::FERMI_TWOD_A: dma_pusher.channel_state.payload->fermi_2d.CallMultiMethod(method, base_start, amount, methods_pending); break; case EngineID::MAXWELL_B: dma_pusher.channel_state.payload->maxwell_3d.CallMultiMethod(method, base_start, amount, methods_pending); break; case EngineID::KEPLER_COMPUTE_B: dma_pusher.channel_state.payload->kepler_compute.CallMultiMethod(method, base_start, amount, methods_pending); break; case EngineID::MAXWELL_DMA_COPY_A: dma_pusher.channel_state.payload->maxwell_dma.CallMultiMethod(method, base_start, amount, methods_pending); break; case EngineID::KEPLER_INLINE_TO_MEMORY_B: dma_pusher.channel_state.payload->kepler_memory.CallMultiMethod(method, base_start, amount, methods_pending); break; case EngineID::NV01_TIMER: dma_pusher.channel_state.payload->nv01_timer.CallMultiMethod(method, base_start, amount, methods_pending); break; default: UNIMPLEMENTED_MSG("Unimplemented engine"); break; } } /// Calls a GPU method. void Puller::CallMethod(DmaPusher& dma_pusher, const MethodCall& method_call) { LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method_call.method, method_call.subchannel); ASSERT(method_call.subchannel < bound_engines.size()); if (ExecuteMethodOnEngine(dma_pusher, method_call.method)) { CallEngineMethod(dma_pusher, method_call); } else { CallPullerMethod(dma_pusher, method_call); } } /// Calls a GPU multivalue method. void Puller::CallMultiMethod(DmaPusher& dma_pusher, u32 method, u32 subchannel, const u32* base_start, u32 amount, u32 methods_pending) { LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method, subchannel); ASSERT(subchannel < bound_engines.size()); if (ExecuteMethodOnEngine(dma_pusher, method)) { CallEngineMultiMethod(dma_pusher, method, subchannel, base_start, amount, methods_pending); } else { for (u32 i = 0; i < amount; i++) { CallPullerMethod(dma_pusher, MethodCall{ method, base_start[i], subchannel, methods_pending - i, }); } } } /// Determines where the method should be executed. [[nodiscard]] bool Puller::ExecuteMethodOnEngine(DmaPusher& dma_pusher, u32 method) { const auto buffer_method = BufferMethods(method); return buffer_method >= BufferMethods::NonPullerMethods; } } // namespace Tegra::Engines