Compare commits

...

21 commits

Author SHA1 Message Date
lizzie
42e967eb34 fixups 2026-06-04 01:09:46 +00:00
lizzie
bfa237220c fix license 2026-06-03 17:50:40 +00:00
lizzie
1a82215976 case stupidity in windows 2026-06-03 17:49:15 +00:00
lizzie
9cff7b8337 correct defines for d3d9 2026-06-03 17:49:15 +00:00
lizzie
2e857190dc fs 2026-06-03 17:49:15 +00:00
lizzie
e03f3dd25c fix msvc 2026-06-03 17:49:15 +00:00
lizzie
cd079ed97f public 2026-06-03 17:49:15 +00:00
lizzie
3a875e50d6 Trigger Build 2026-06-03 17:49:15 +00:00
lizzie
736fa0540e better inlining 2026-06-03 17:49:15 +00:00
lizzie
2c2d69887f maxwell macros 2026-06-03 17:49:15 +00:00
lizzie
9ab941fc23 remove implicit system saved in struct, pass as first param 2026-06-03 17:48:58 +00:00
lizzie
9702a87277 ffs 2026-06-03 17:48:29 +00:00
lizzie
7ab6c88ad9 extra 2026-06-03 17:48:29 +00:00
lizzie
09b9210739 extra fixups 2026-06-03 17:48:29 +00:00
lizzie
e4d16aadab less load, fix nv01 timer being kepler 2026-06-03 17:48:29 +00:00
lizzie
8f991ecf79 fx 2026-06-03 17:48:11 +00:00
lizzie
cd23ec0b07 [video_core] Remove redundant references in GPU engine structs
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-06-03 17:48:11 +00:00
maufeat
2f89c65c83 [kernel, hle] Initial 22.0.0 kernel changes and cmd stubs (#3761)
- KProcess::Run() and CreateThread() SVC now write the current thread handle to TLS+0x110
- KPageTableBase::LockForMapDeviceAddressSpace now checks for a new KPageTableBase boolean, m_allowed_exec_device_mapping
- Stub `am` + `acc` + `settings` cmd module that needs to work for qlaunch

Thanks to: @alula and @yellows8
Source for changes: https://github.com/Atmosphere-NX/Atmosphere/pull/2744, https://switchbrew.org/, https://yls8.mtheall.com/

Co-authored-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3761
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-06-03 17:46:56 +00:00
lizzie
ecc82cf0b7 llicense fix 2026-06-03 17:46:56 +00:00
lizzie
4baf6aa160 fx 2026-06-03 17:46:56 +00:00
lizzie
65f6dd2654 [audio_core, hle/services, video_core/compute] Inline std::unique_ptr<> allocs into std::optional<>
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-06-03 17:46:56 +00:00
68 changed files with 1025 additions and 1226 deletions

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -8,10 +11,11 @@
namespace AudioCore {
AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>()} {
AudioCore::AudioCore(Core::System& system) {
audio_manager.emplace();
CreateSinks();
// Must be created after the sinks
adsp = std::make_unique<ADSP::ADSP>(system, *output_sink);
adsp.emplace(system, *output_sink);
}
AudioCore ::~AudioCore() {

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -15,10 +18,7 @@ class System;
namespace AudioCore {
class AudioManager;
/**
* Main audio class, stored inside the core, and holding the audio manager, all sinks, and the ADSP.
*/
/// @brief Main audio class, stored inside the core, and holding the audio manager, all sinks, and the ADSP.
class AudioCore {
public:
explicit AudioCore(Core::System& system);
@ -50,27 +50,22 @@ public:
*/
Sink::Sink& GetInputSink();
/**
* Get the ADSP.
*
* @return Ref to the ADSP.
*/
/// @brief Get the ADSP.
/// @return Ref to the ADSP.
ADSP::ADSP& ADSP();
private:
/**
* Create the sinks on startup.
*/
/// @brief Create the sinks on startup.
void CreateSinks();
/// Main audio manager for audio in/out
std::unique_ptr<AudioManager> audio_manager;
std::optional<AudioManager> audio_manager;
/// Sink used for audio renderer and audio out
std::unique_ptr<Sink::Sink> output_sink;
/// Sink used for audio input
std::unique_ptr<Sink::Sink> input_sink;
/// The ADSP in the sysmodule
std::unique_ptr<ADSP::ADSP> adsp;
std::optional<ADSP::ADSP> adsp;
};
} // namespace AudioCore

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -41,8 +44,7 @@ void Manager::ReleaseSessionId(const size_t session_id) {
Result Manager::LinkToManager() {
std::scoped_lock l{mutex};
if (!linked_to_manager) {
AudioManager& manager{system.AudioCore().GetAudioManager()};
manager.SetInManager(std::bind(&Manager::BufferReleaseAndRegister, this));
system.AudioCore().GetAudioManager().SetInManager(std::bind(&Manager::BufferReleaseAndRegister, this));
linked_to_manager = true;
}

View file

@ -1,73 +1,28 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/audio_manager.h"
#include "common/thread.h"
#include "core/core.h"
#include "core/hle/service/audio/errors.h"
namespace AudioCore {
AudioManager::AudioManager() {
thread = std::jthread([this]() { ThreadFunc(); });
}
void AudioManager::Shutdown() {
running = false;
events.SetAudioEvent(Event::Type::Max, true);
thread.join();
}
Result AudioManager::SetOutManager(BufferEventFunc buffer_func) {
if (!running) {
return Service::Audio::ResultOperationFailed;
}
std::scoped_lock l{lock};
const auto index{events.GetManagerIndex(Event::Type::AudioOutManager)};
if (buffer_events[index] == nullptr) {
buffer_events[index] = std::move(buffer_func);
needs_update = true;
events.SetAudioEvent(Event::Type::AudioOutManager, true);
}
return ResultSuccess;
}
Result AudioManager::SetInManager(BufferEventFunc buffer_func) {
if (!running) {
return Service::Audio::ResultOperationFailed;
}
std::scoped_lock l{lock};
const auto index{events.GetManagerIndex(Event::Type::AudioInManager)};
if (buffer_events[index] == nullptr) {
buffer_events[index] = std::move(buffer_func);
needs_update = true;
events.SetAudioEvent(Event::Type::AudioInManager, true);
}
return ResultSuccess;
}
void AudioManager::SetEvent(const Event::Type type, const bool signalled) {
events.SetAudioEvent(type, signalled);
}
void AudioManager::ThreadFunc() {
thread = std::jthread([this](std::stop_token stop_token) {
Common::SetCurrentThreadName("AudioManager");
std::unique_lock l{events.GetAudioEventLock()};
events.ClearEvents();
running = true;
while (running) {
while (!stop_token.stop_requested()) {
const auto timed_out{events.Wait(l, std::chrono::seconds(2))};
if (events.CheckAudioEventSet(Event::Type::Max)) {
break;
}
for (size_t i = 0; i < buffer_events.size(); i++) {
const auto event_type = static_cast<Event::Type>(i);
const auto event_type = Event::Type(i);
if (events.CheckAudioEventSet(event_type) || timed_out) {
if (buffer_events[i]) {
buffer_events[i]();
@ -76,6 +31,47 @@ void AudioManager::ThreadFunc() {
events.SetAudioEvent(event_type, false);
}
}
});
}
void AudioManager::Shutdown() {
events.SetAudioEvent(Event::Type::Max, true);
if (thread.joinable()) {
thread.request_stop();
thread.join();
}
}
Result AudioManager::SetOutManager(BufferEventFunc buffer_func) {
if (thread.joinable()) {
std::scoped_lock l{lock};
const auto index{events.GetManagerIndex(Event::Type::AudioOutManager)};
if (buffer_events[index] == nullptr) {
buffer_events[index] = std::move(buffer_func);
needs_update = true;
events.SetAudioEvent(Event::Type::AudioOutManager, true);
}
return ResultSuccess;
}
return Service::Audio::ResultOperationFailed;
}
Result AudioManager::SetInManager(BufferEventFunc buffer_func) {
if (thread.joinable()) {
std::scoped_lock l{lock};
const auto index{events.GetManagerIndex(Event::Type::AudioInManager)};
if (buffer_events[index] == nullptr) {
buffer_events[index] = std::move(buffer_func);
needs_update = true;
events.SetAudioEvent(Event::Type::AudioInManager, true);
}
return ResultSuccess;
}
return Service::Audio::ResultOperationFailed;
}
void AudioManager::SetEvent(const Event::Type type, const bool signalled) {
events.SetAudioEvent(type, signalled);
}
} // namespace AudioCore

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -66,13 +69,6 @@ public:
void SetEvent(Event::Type type, bool signalled);
private:
/**
* Main thread, waiting on a manager signal and calling the registered function.
*/
void ThreadFunc();
/// Is the main thread running?
std::atomic<bool> running{};
/// Unused
bool needs_update{};
/// Events to be set and signalled

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -40,8 +43,7 @@ void Manager::ReleaseSessionId(const size_t session_id) {
Result Manager::LinkToManager() {
std::scoped_lock l{mutex};
if (!linked_to_manager) {
AudioManager& manager{system.AudioCore().GetAudioManager()};
manager.SetOutManager(std::bind(&Manager::BufferReleaseAndRegister, this));
system.AudioCore().GetAudioManager().SetOutManager(std::bind(&Manager::BufferReleaseAndRegister, this));
linked_to_manager = true;
}

View file

@ -271,7 +271,7 @@ struct System::Impl {
SystemResultStatus SetupForApplicationProcess(System& system, Frontend::EmuWindow& emu_window) {
host1x_core.emplace(system);
gpu_core = VideoCore::CreateGPU(emu_window, system);
VideoCore::CreateGPU(gpu_core, emu_window, system);
if (!gpu_core)
return SystemResultStatus::ErrorVideoCore;
@ -347,7 +347,7 @@ struct System::Impl {
// Register with applet manager
// All threads are started, begin main process execution, now that we're in the clear
applet_manager.CreateAndInsertByFrontendAppletParameters(std::move(process), params);
applet_manager.CreateAndInsertByFrontendAppletParameters(std::make_unique<Service::Process>(*std::move(process)), params);
if (Settings::values.gamecard_inserted) {
if (Settings::values.gamecard_current_game) {
@ -391,10 +391,8 @@ struct System::Impl {
is_powered_on = false;
exit_locked = false;
exit_requested = false;
if (gpu_core != nullptr) {
if (gpu_core)
gpu_core->NotifyShutdown();
}
stop_event.request_stop();
core_timing.SyncPause(false);
@ -478,6 +476,7 @@ struct System::Impl {
std::optional<Memory::CheatEngine> cheat_engine;
std::optional<Tools::Freezer> memory_freezer;
std::optional<Tools::RenderdocAPI> renderdoc_api;
std::optional<Tegra::GPU> gpu_core;
std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES> gpu_dirty_memory_managers;
std::vector<std::vector<u8>> user_channel;
@ -492,7 +491,6 @@ struct System::Impl {
std::unique_ptr<FileSys::ContentProviderUnion> content_provider;
/// AppLoader used to load the current executing application
std::unique_ptr<Loader::AppLoader> app_loader;
std::unique_ptr<Tegra::GPU> gpu_core;
std::stop_source stop_event;
mutable std::mutex suspend_guard;

View file

@ -25,9 +25,11 @@ CpuManager::~CpuManager() = default;
void CpuManager::Initialize() {
num_cores = is_multicore ? Core::Hardware::NUM_CPU_CORES : 1;
gpu_barrier = std::make_unique<Common::Barrier>(num_cores + 1);
gpu_barrier.emplace(num_cores + 1);
for (std::size_t core = 0; core < num_cores; core++)
core_data[core].host_thread = std::jthread([this, core](std::stop_token token) { RunThread(token, core); });
core_data[core].host_thread = std::jthread([this, core](std::stop_token token) {
RunThread(token, core);
});
}
void CpuManager::Shutdown() {
@ -63,7 +65,7 @@ void CpuManager::HandleInterrupt() {
auto& kernel = system.Kernel();
auto core_index = kernel.CurrentPhysicalCoreIndex();
Kernel::KInterruptManager::HandleInterrupt(kernel, static_cast<s32>(core_index));
Kernel::KInterruptManager::HandleInterrupt(kernel, s32(core_index));
}
///////////////////////////////////////////////////////////////////////////////

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -86,22 +89,20 @@ private:
void ShutdownThread();
void RunThread(std::stop_token stop_token, std::size_t core);
static constexpr std::size_t max_cycle_runs = 5;
std::optional<Common::Barrier> gpu_barrier{};
struct CoreData {
std::shared_ptr<Common::Fiber> host_context;
std::jthread host_thread;
};
std::unique_ptr<Common::Barrier> gpu_barrier{};
std::array<CoreData, Core::Hardware::NUM_CPU_CORES> core_data{};
bool is_async_gpu{};
bool is_multicore{};
System& system;
std::atomic<std::size_t> current_core{};
std::size_t idle_count{};
std::size_t num_cores{};
static constexpr std::size_t max_cycle_runs = 5;
System& system;
bool is_async_gpu{};
bool is_multicore{};
};
} // namespace Core

View file

@ -268,7 +268,7 @@ void AppletManager::SetWindowSystem(WindowSystem* window_system) {
if (Settings::values.enable_overlay && m_window_system->GetOverlayDisplayApplet() == nullptr) {
if (auto overlay_process = CreateProcess(m_system, static_cast<u64>(AppletProgramId::OverlayDisplay), 0, 0)) {
auto overlay_applet = std::make_shared<Applet>(m_system, std::move(overlay_process), false);
auto overlay_applet = std::make_shared<Applet>(m_system, std::make_unique<Service::Process>(*std::move(overlay_process)), false);
overlay_applet->program_id = static_cast<u64>(AppletProgramId::OverlayDisplay);
overlay_applet->applet_id = AppletId::OverlayDisplay;
overlay_applet->type = AppletType::OverlayApplet;

View file

@ -1,9 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
#include "common/thread.h"
#include "core/core.h"
#include "core/hle/service/am/am_types.h"
#include "core/hle/service/am/button_poller.h"
@ -34,14 +36,13 @@ ButtonPressDuration ClassifyPressDuration(std::chrono::steady_clock::time_point
} // namespace
ButtonPoller::ButtonPoller(Core::System& system, WindowSystem& window_system)
: m_window_system(window_system) {
ButtonPoller::ButtonPoller(Core::System& system, WindowSystem& window_system) {
// TODO: am reads this from the home button state in hid, which is controller-agnostic.
Core::HID::ControllerUpdateCallback engine_callback{
.on_change =
[this](Core::HID::ControllerTriggerType type) {
.on_change = [this, &window_system](Core::HID::ControllerTriggerType type) {
if (type == Core::HID::ControllerTriggerType::Button) {
this->OnButtonStateChanged();
std::unique_lock lk{m_mutex};
OnButtonStateChanged(window_system);
}
},
.is_npad_service = true,
@ -52,25 +53,35 @@ ButtonPoller::ButtonPoller(Core::System& system, WindowSystem& window_system)
m_player1 = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
m_player1_key = m_player1->SetCallback(engine_callback);
m_thread = std::thread([this] { this->ThreadLoop(); });
m_thread = std::jthread([this, &window_system](std::stop_token stop_token) {
Common::SetCurrentThreadName("ButtonPoller");
while (!stop_token.stop_requested()) {
using namespace std::chrono_literals;
std::unique_lock lk{m_mutex};
m_cv.wait_for(lk, 50ms);
if (stop_token.stop_requested())
break;
OnButtonStateChanged(window_system);
std::this_thread::sleep_for(5ms);
}
});
}
ButtonPoller::~ButtonPoller() {
m_handheld->DeleteCallback(m_handheld_key);
m_player1->DeleteCallback(m_player1_key);
m_stop = true;
m_cv.notify_all();
if (m_thread.joinable()) {
m_thread.request_stop();
m_thread.join();
}
}
void ButtonPoller::OnButtonStateChanged() {
std::lock_guard lk{m_mutex};
const bool home_button =
m_handheld->GetHomeButtons().home.Value() || m_player1->GetHomeButtons().home.Value();
const bool capture_button = m_handheld->GetCaptureButtons().capture.Value() ||
m_player1->GetCaptureButtons().capture.Value();
void ButtonPoller::OnButtonStateChanged(WindowSystem& window_system) {
auto const home_button = m_handheld->GetHomeButtons().home.Value()
|| m_player1->GetHomeButtons().home.Value();
auto const capture_button = m_handheld->GetCaptureButtons().capture.Value()
|| m_player1->GetCaptureButtons().capture.Value();
// Buttons pressed which were not previously pressed
if (home_button && !m_home_button_press_start) {
@ -90,7 +101,7 @@ void ButtonPoller::OnButtonStateChanged() {
if (home_button && m_home_button_press_start && !m_home_button_long_sent) {
const auto duration = ClassifyPressDuration(*m_home_button_press_start);
if (duration != ButtonPressDuration::ShortPressing) {
m_window_system.OnSystemButtonPress(SystemButtonType::HomeButtonLongPressing);
window_system.OnSystemButtonPress(SystemButtonType::HomeButtonLongPressing);
m_home_button_long_sent = true;
}
}
@ -98,7 +109,7 @@ void ButtonPoller::OnButtonStateChanged() {
if (capture_button && m_capture_button_press_start && !m_capture_button_long_sent) {
const auto duration = ClassifyPressDuration(*m_capture_button_press_start);
if (duration != ButtonPressDuration::ShortPressing) {
m_window_system.OnSystemButtonPress(SystemButtonType::CaptureButtonLongPressing);
window_system.OnSystemButtonPress(SystemButtonType::CaptureButtonLongPressing);
m_capture_button_long_sent = true;
}
}
@ -107,9 +118,8 @@ void ButtonPoller::OnButtonStateChanged() {
if (!home_button && m_home_button_press_start) {
if(!m_home_button_long_sent) {
const auto duration = ClassifyPressDuration(*m_home_button_press_start);
m_window_system.OnSystemButtonPress(
duration == ButtonPressDuration::ShortPressing ? SystemButtonType::HomeButtonShortPressing
: SystemButtonType::HomeButtonLongPressing);
window_system.OnSystemButtonPress(duration == ButtonPressDuration::ShortPressing
? SystemButtonType::HomeButtonShortPressing : SystemButtonType::HomeButtonLongPressing);
}
m_home_button_press_start = std::nullopt;
m_home_button_long_sent = false;
@ -117,9 +127,8 @@ void ButtonPoller::OnButtonStateChanged() {
if (!capture_button && m_capture_button_press_start) {
if (!m_capture_button_long_sent) {
const auto duration = ClassifyPressDuration(*m_capture_button_press_start);
m_window_system.OnSystemButtonPress(
duration == ButtonPressDuration::ShortPressing ? SystemButtonType::CaptureButtonShortPressing
: SystemButtonType::CaptureButtonLongPressing);
window_system.OnSystemButtonPress(duration == ButtonPressDuration::ShortPressing
? SystemButtonType::CaptureButtonShortPressing : SystemButtonType::CaptureButtonLongPressing);
}
m_capture_button_press_start = std::nullopt;
m_capture_button_long_sent = false;
@ -130,16 +139,4 @@ void ButtonPoller::OnButtonStateChanged() {
// }
}
void ButtonPoller::ThreadLoop() {
using namespace std::chrono_literals;
std::unique_lock lk{m_mutex};
while (!m_stop) {
m_cv.wait_for(lk, 50ms);
if (m_stop) break;
lk.unlock();
OnButtonStateChanged();
lk.lock();
}
}
} // namespace Service::AM

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@ -30,31 +30,23 @@ class ButtonPoller {
public:
explicit ButtonPoller(Core::System& system, WindowSystem& window_system);
~ButtonPoller();
void OnButtonStateChanged(WindowSystem& window_system);
private:
void OnButtonStateChanged();
void ThreadLoop();
private:
WindowSystem& m_window_system;
Core::HID::EmulatedController* m_handheld{};
int m_handheld_key{};
Core::HID::EmulatedController* m_player1{};
int m_player1_key{};
std::mutex m_mutex;
std::condition_variable m_cv;
std::jthread m_thread;
std::optional<std::chrono::steady_clock::time_point> m_home_button_press_start{};
std::optional<std::chrono::steady_clock::time_point> m_capture_button_press_start{};
std::optional<std::chrono::steady_clock::time_point> m_power_button_press_start{};
bool m_home_button_long_sent{};
bool m_capture_button_long_sent{};
bool m_power_button_long_sent{};
std::thread m_thread;
std::atomic<bool> m_stop{false};
std::condition_variable m_cv;
std::mutex m_mutex;
Core::HID::EmulatedController* m_handheld{};
Core::HID::EmulatedController* m_player1{};
int32_t m_handheld_key{};
int32_t m_player1_key{};
bool m_home_button_long_sent : 1 = false;
bool m_capture_button_long_sent : 1 = false;
bool m_power_button_long_sent : 1 = false;
};
} // namespace Service::AM

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -15,12 +18,24 @@ enum class UserDataTag : u32 {
};
EventObserver::EventObserver(Core::System& system, WindowSystem& window_system)
: m_system(system), m_context(system, "am:EventObserver"), m_window_system(window_system),
m_wakeup_event(m_context), m_wakeup_holder(m_wakeup_event.GetHandle()) {
: m_system(system), m_context(system, "am:EventObserver")
, m_window_system(window_system)
, m_wakeup_event(m_context)
, m_wakeup_holder(m_wakeup_event.GetHandle())
{
m_window_system.SetEventObserver(this);
m_wakeup_holder.SetUserData(static_cast<uintptr_t>(UserDataTag::WakeupEvent));
m_wakeup_holder.LinkToMultiWait(std::addressof(m_multi_wait));
m_thread = std::thread([&] { this->ThreadFunc(); });
m_thread = std::thread([this] {
Common::SetCurrentThreadName("am:EventObserver");
while (true) {
auto* signaled_holder = this->WaitSignaled();
if (!signaled_holder) {
break;
}
this->Process(signaled_holder);
}
});
}
EventObserver::~EventObserver() {
@ -146,17 +161,4 @@ void EventObserver::DestroyAppletProcessHolderLocked(ProcessHolder* holder) {
delete holder;
}
void EventObserver::ThreadFunc() {
Common::SetCurrentThreadName("am:EventObserver");
while (true) {
auto* signaled_holder = this->WaitSignaled();
if (!signaled_holder) {
break;
}
this->Process(signaled_holder);
}
}
} // namespace Service::AM

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -41,9 +44,6 @@ private:
private:
void DestroyAppletProcessHolderLocked(ProcessHolder* holder);
private:
void ThreadFunc();
private:
// System reference and context.
Core::System& m_system;

View file

@ -1,6 +1,10 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <optional>
#include "core/core.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h"
@ -16,7 +20,7 @@ namespace Service::AM {
namespace {
FileSys::StorageId GetStorageIdForFrontendSlot(
[[nodiscard]] FileSys::StorageId GetStorageIdForFrontendSlot(
std::optional<FileSys::ContentProviderUnionSlot> slot) {
if (!slot.has_value()) {
return FileSys::StorageId::None;
@ -36,31 +40,23 @@ FileSys::StorageId GetStorageIdForFrontendSlot(
}
}
std::unique_ptr<Process> CreateProcessImpl(std::unique_ptr<Loader::AppLoader>& out_loader,
Loader::ResultStatus& out_load_result,
Core::System& system, FileSys::VirtualFile file,
u64 program_id, u64 program_index) {
[[nodiscard]] inline std::optional<Process> CreateProcessImpl(std::unique_ptr<Loader::AppLoader>& out_loader, Loader::ResultStatus& out_load_result, Core::System& system, FileSys::VirtualFile file, u64 program_id, u64 program_index) {
// Get the appropriate loader to parse this NCA.
out_loader = Loader::GetLoader(system, file, program_id, program_index);
// Ensure we have a loader which can parse the NCA.
if (!out_loader) {
return nullptr;
}
if (out_loader) {
// Try to load the process.
auto process = std::make_unique<Process>(system);
auto process = std::make_optional<Process>(system);
if (process->Initialize(*out_loader, out_load_result)) {
return process;
}
return nullptr;
}
return std::nullopt;
}
} // Anonymous namespace
std::unique_ptr<Process> CreateProcess(Core::System& system, u64 program_id,
u8 minimum_key_generation, u8 maximum_key_generation) {
std::optional<Process> CreateProcess(Core::System& system, u64 program_id, u8 minimum_key_generation, u8 maximum_key_generation) {
// Attempt to load program NCA.
FileSys::VirtualFile nca_raw{};
@ -70,7 +66,7 @@ std::unique_ptr<Process> CreateProcess(Core::System& system, u64 program_id,
// Ensure we retrieved a program NCA.
if (!nca_raw) {
return nullptr;
return std::nullopt;
}
// Ensure we have a suitable version.
@ -79,9 +75,8 @@ std::unique_ptr<Process> CreateProcess(Core::System& system, u64 program_id,
if (nca.GetStatus() == Loader::ResultStatus::Success &&
(nca.GetKeyGeneration() < minimum_key_generation ||
nca.GetKeyGeneration() > maximum_key_generation)) {
LOG_WARNING(Service_LDR, "Skipping program {:016X} with generation {}", program_id,
nca.GetKeyGeneration());
return nullptr;
LOG_WARNING(Service_LDR, "Skipping program {:016X} with generation {}", program_id, nca.GetKeyGeneration());
return std::nullopt;
}
}
@ -90,17 +85,8 @@ std::unique_ptr<Process> CreateProcess(Core::System& system, u64 program_id,
return CreateProcessImpl(loader, status, system, nca_raw, program_id, 0);
}
std::unique_ptr<Process> CreateApplicationProcess(std::vector<u8>& out_control,
std::unique_ptr<Loader::AppLoader>& out_loader,
Loader::ResultStatus& out_load_result,
Core::System& system, FileSys::VirtualFile file,
u64 program_id, u64 program_index) {
auto process =
CreateProcessImpl(out_loader, out_load_result, system, file, program_id, program_index);
if (!process) {
return nullptr;
}
std::optional<Process> CreateApplicationProcess(std::vector<u8>& out_control, std::unique_ptr<Loader::AppLoader>& out_loader, Loader::ResultStatus& out_load_result, Core::System& system, FileSys::VirtualFile file, u64 program_id, u64 program_index) {
if (auto process = CreateProcessImpl(out_loader, out_load_result, system, file, program_id, program_index); process) {
FileSys::NACP nacp;
if (out_loader->ReadControlData(nacp) == Loader::ResultStatus::Success) {
out_control = nacp.GetRawBytes();
@ -118,14 +104,13 @@ std::unique_ptr<Process> CreateApplicationProcess(std::vector<u8>& out_control,
// TODO(DarkLordZach): When FSController/Game Card Support is added, if
// current_process_game_card use correct StorageId
launch.base_game_storage_id = GetStorageIdForFrontendSlot(
storage.GetSlotForEntry(launch.title_id, FileSys::ContentRecordType::Program));
launch.update_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry(
FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program));
launch.base_game_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry(launch.title_id, FileSys::ContentRecordType::Program));
launch.update_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry(FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program));
system.GetARPManager().Register(launch.title_id, launch, out_control);
return process;
}
return std::nullopt;
}
} // namespace Service::AM

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -24,12 +27,7 @@ class Process;
namespace Service::AM {
std::unique_ptr<Process> CreateProcess(Core::System& system, u64 program_id,
u8 minimum_key_generation, u8 maximum_key_generation);
std::unique_ptr<Process> CreateApplicationProcess(std::vector<u8>& out_control,
std::unique_ptr<Loader::AppLoader>& out_loader,
Loader::ResultStatus& out_load_result,
Core::System& system, FileSys::VirtualFile file,
u64 program_id, u64 program_index);
std::optional<Process> CreateProcess(Core::System& system, u64 program_id, u8 minimum_key_generation, u8 maximum_key_generation);
std::optional<Process> CreateApplicationProcess(std::vector<u8>& out_control, std::unique_ptr<Loader::AppLoader>& out_loader, Loader::ResultStatus& out_load_result, Core::System& system, FileSys::VirtualFile file, u64 program_id, u64 program_index);
} // namespace Service::AM

View file

@ -21,8 +21,7 @@ namespace Service::AM {
namespace {
Result CreateGuestApplication(SharedPointer<IApplicationAccessor>* out_application_accessor,
Core::System& system, WindowSystem& window_system, u64 program_id) {
Result CreateGuestApplication(SharedPointer<IApplicationAccessor>* out_application_accessor, Core::System& system, WindowSystem& window_system, u64 program_id) {
FileSys::VirtualFile nca_raw{};
// Get the program NCA from storage.
@ -35,11 +34,10 @@ Result CreateGuestApplication(SharedPointer<IApplicationAccessor>* out_applicati
std::vector<u8> control;
std::unique_ptr<Loader::AppLoader> loader;
Loader::ResultStatus result;
auto process =
CreateApplicationProcess(control, loader, result, system, nca_raw, program_id, 0);
R_UNLESS(process != nullptr, ResultUnknown);
auto process = CreateApplicationProcess(control, loader, result, system, nca_raw, program_id, 0);
R_UNLESS(process != std::nullopt, ResultUnknown);
const auto applet = std::make_shared<Applet>(system, std::move(process), true);
const auto applet = std::make_shared<Applet>(system, std::make_unique<Service::Process>(*std::move(process)), true);
applet->program_id = program_id;
applet->applet_id = AppletId::Application;
applet->type = AppletType::Application;
@ -47,8 +45,7 @@ Result CreateGuestApplication(SharedPointer<IApplicationAccessor>* out_applicati
window_system.TrackApplet(applet, true);
*out_application_accessor =
std::make_shared<IApplicationAccessor>(system, applet, window_system);
*out_application_accessor = std::make_shared<IApplicationAccessor>(system, applet, window_system);
R_SUCCEED();
}
@ -90,12 +87,10 @@ Result IApplicationCreator::CreateSystemApplication(
std::vector<u8> control;
std::unique_ptr<Loader::AppLoader> loader;
auto process = CreateProcess(system, application_id, 1, 22);
R_UNLESS(process != std::nullopt, ResultUnknown);
auto process =
CreateProcess(system, application_id, 1, 22);
R_UNLESS(process != nullptr, ResultUnknown);
const auto applet = std::make_shared<Applet>(system, std::move(process), true);
const auto applet = std::make_shared<Applet>(system, std::make_unique<Service::Process>(*std::move(process)), true);
applet->program_id = application_id;
applet->applet_id = AppletId::Starter;
applet->type = AppletType::LibraryApplet;
@ -103,8 +98,7 @@ Result IApplicationCreator::CreateSystemApplication(
m_window_system.TrackApplet(applet, true);
*out_application_accessor =
std::make_shared<IApplicationAccessor>(system, applet, m_window_system);
*out_application_accessor = std::make_shared<IApplicationAccessor>(system, applet, m_window_system);
Core::LaunchTimestampCache::SaveLaunchTimestamp(application_id);
R_SUCCEED();
}

View file

@ -122,12 +122,8 @@ std::shared_ptr<ILibraryAppletAccessor> CreateGuestApplet(Core::System& system,
};
auto process = CreateProcess(system, program_id, Firmware1400, Firmware2200);
if (!process) {
// Couldn't initialize the guest process
return {};
}
const auto applet = std::make_shared<Applet>(system, std::move(process), false);
if (process) {
const auto applet = std::make_shared<Applet>(system, std::make_unique<Service::Process>(*std::move(process)), false);
applet->program_id = program_id;
applet->applet_id = applet_id;
applet->type = AppletType::LibraryApplet;
@ -138,10 +134,11 @@ std::shared_ptr<ILibraryAppletAccessor> CreateGuestApplet(Core::System& system,
applet->caller_applet = caller_applet;
applet->caller_applet_broker = broker;
caller_applet->child_applets.push_back(applet);
window_system.TrackApplet(applet, false);
return std::make_shared<ILibraryAppletAccessor>(system, broker, applet);
}
// Couldn't initialize the guest process
return {};
}
std::shared_ptr<ILibraryAppletAccessor> CreateFrontendApplet(Core::System& system,

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@ -14,7 +14,9 @@ namespace Service::Audio {
using namespace AudioCore::AudioOut;
IAudioOutManager::IAudioOutManager(Core::System& system_)
: ServiceFramework{system_, "audout:u"}, impl{std::make_unique<Manager>(system_)} {
: ServiceFramework{system_, "audout:u"}
, impl(system_)
{
// clang-format off
static const FunctionInfo functions[] = {
{0, D<&IAudioOutManager::ListAudioOuts>, "ListAudioOuts"},

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@ -43,7 +43,7 @@ private:
AudioCore::AudioOut::AudioOutParameter parameter,
InCopyHandle<Kernel::KProcess> process_handle, ClientAppletResourceUserId aruid);
std::unique_ptr<AudioCore::AudioOut::Manager> impl;
std::optional<AudioCore::AudioOut::Manager> impl;
};
} // namespace Service::Audio

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -15,7 +18,9 @@ namespace Service::Audio {
using namespace AudioCore::Renderer;
IAudioRendererManager::IAudioRendererManager(Core::System& system_)
: ServiceFramework{system_, "audren:u"}, impl{std::make_unique<Manager>(system_)} {
: ServiceFramework{system_, "audren:u"}
, impl(system_)
{
// clang-format off
static const FunctionInfo functions[] = {
{0, D<&IAudioRendererManager::OpenAudioRenderer>, "OpenAudioRenderer"},

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -30,7 +33,7 @@ private:
Result GetAudioDeviceServiceWithRevisionInfo(Out<SharedPointer<IAudioDevice>> out_audio_device,
u32 revision, ClientAppletResourceUserId aruid);
std::unique_ptr<AudioCore::Renderer::Manager> impl;
std::optional<AudioCore::Renderer::Manager> impl;
u32 num_audio_devices{0};
};

View file

@ -138,8 +138,7 @@ NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) {
static_cast<u32>((vm.va_range_end - vm.va_range_split) >> vm.big_page_size_bits)};
vm.big_page_allocator.emplace(start_big_pages, end_big_pages);
gmmu = std::make_shared<Tegra::MemoryManager>(system, max_big_page_bits, vm.va_range_split,
vm.big_page_size_bits, VM::PAGE_SIZE_BITS);
gmmu = std::make_unique<Tegra::MemoryManager>(system, max_big_page_bits, vm.va_range_split, vm.big_page_size_bits, VM::PAGE_SIZE_BITS);
system.GPU().InitAddressSpace(*gmmu);
vm.initialised = true;
@ -416,7 +415,7 @@ NvResult nvhost_as_gpu::BindChannel(IoctlBindChannel& params) {
LOG_DEBUG(Service_NVDRV, "called, fd={:X}", params.fd);
auto gpu_channel_device = module.GetDevice<nvhost_gpu>(params.fd);
gpu_channel_device->channel_state->memory_manager = gmmu;
gpu_channel_device->channel_state->memory_manager = gmmu.get();
return NvResult::Success;
}

View file

@ -219,7 +219,7 @@ private:
bool initialised{};
} vm;
std::shared_ptr<Tegra::MemoryManager> gmmu;
std::unique_ptr<Tegra::MemoryManager> gmmu;
};
} // namespace Service::Nvidia::Devices

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -10,14 +13,6 @@
namespace Service {
Process::Process(Core::System& system)
: m_system(system), m_process(), m_main_thread_priority(), m_main_thread_stack_size(),
m_process_started() {}
Process::~Process() {
this->Finalize();
}
bool Process::Initialize(Loader::AppLoader& loader, Loader::ResultStatus& out_load_result) {
// First, ensure we are not holding another process.
this->Finalize();

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -22,8 +25,8 @@ namespace Service {
class Process {
public:
explicit Process(Core::System& system);
~Process();
inline explicit Process(Core::System& system) noexcept : m_system(system) {}
inline ~Process() { this->Finalize(); }
bool Initialize(Loader::AppLoader& loader, Loader::ResultStatus& out_load_result);
void Finalize();
@ -50,8 +53,8 @@ public:
private:
Core::System& m_system;
Kernel::KProcess* m_process{};
s32 m_main_thread_priority{};
u64 m_main_thread_stack_size{};
s32 m_main_thread_priority{};
bool m_process_started{};
};

View file

@ -13,6 +13,7 @@
#include "common/param_package.h"
#include "common/random.h"
#include "common/settings.h"
#include "common/thread.h"
#include "input_common/drivers/udp_client.h"
#include "input_common/helpers/udp_protocol.h"
@ -131,6 +132,7 @@ private:
};
static void SocketLoop(Socket* socket) {
Common::SetCurrentThreadName("cemuhookWorker");
socket->StartReceive();
socket->StartSend(Socket::clock::now());
socket->Loop();
@ -326,9 +328,11 @@ void UDPClient::OnPadData(Response::PadData data, std::size_t client) {
}
void UDPClient::StartCommunication(std::size_t client, const std::string& host, u16 port) {
SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
SocketCallback callback{
[this](Response::Version version) { OnVersion(version); },
[this](Response::PortInfo info) { OnPortInfo(info); },
[this, client](Response::PadData data) { OnPadData(data, client); }};
[this, client](Response::PadData data) { OnPadData(data, client); }
};
LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
clients[client].uuid = GetHostUUID(host);
clients[client].host = host;
@ -566,9 +570,7 @@ bool UDPClient::IsStickInverted(const Common::ParamPackage& params) {
return true;
}
void TestCommunication(const std::string& host, u16 port,
const std::function<void()>& success_callback,
const std::function<void()>& failure_callback) {
void TestCommunication(const std::string& host, u16 port, const std::function<void()>& success_callback, const std::function<void()>& failure_callback) {
std::thread([=] {
Common::Event success_event;
SocketCallback callback{
@ -601,8 +603,7 @@ CalibrationConfigurationJob::CalibrationConfigurationJob(
u16 max_y{};
Status current_status{Status::Initialized};
SocketCallback callback{[](Response::Version) {}, [](Response::PortInfo) {},
[&](Response::PadData data) {
SocketCallback callback{[](Response::Version) {}, [](Response::PortInfo) {}, [&](Response::PadData data) {
constexpr u16 CALIBRATION_THRESHOLD = 100;
if (current_status == Status::Initialized) {
@ -613,10 +614,9 @@ CalibrationConfigurationJob::CalibrationConfigurationJob(
if (data.touch[0].is_active == 0) {
return;
}
LOG_DEBUG(Input, "Current touch: {} {}", data.touch[0].x,
data.touch[0].y);
min_x = (std::min)(min_x, static_cast<u16>(data.touch[0].x));
min_y = (std::min)(min_y, static_cast<u16>(data.touch[0].y));
LOG_DEBUG(Input, "Current touch: {} {}", data.touch[0].x, data.touch[0].y);
min_x = (std::min)(min_x, u16(data.touch[0].x));
min_y = (std::min)(min_y, u16(data.touch[0].y));
if (current_status == Status::Ready) {
// First touch - min data (min_x/min_y)
current_status = Status::Stage1Completed;

View file

@ -334,7 +334,7 @@ if (YUZU_USE_EXTERNAL_FFMPEG)
add_dependencies(video_core ffmpeg-build)
endif()
target_include_directories(video_core PRIVATE ${FFmpeg_INCLUDE_DIR})
target_include_directories(video_core PUBLIC ${FFmpeg_INCLUDE_DIR})
target_link_libraries(video_core PRIVATE ${FFmpeg_LIBRARIES})
target_link_options(video_core PRIVATE ${FFmpeg_LDFLAGS})

View file

@ -20,8 +20,7 @@
namespace Tegra {
CDmaPusher::CDmaPusher(Host1x::Host1x& host1x_, s32 id)
: host_processor(host1x_)
, host1x{host1x_}
: host1x{host1x_}
, current_class{ChClassId(id)}
{
thread = std::jthread([this](std::stop_token stop_token) {
@ -99,7 +98,7 @@ void CDmaPusher::ExecuteCommand(u32 method, u32 arg) {
switch (current_class) {
case ChClassId::Control:
LOG_TRACE(Service_NVDRV, "Class {} method {:#X} arg 0x{:X}", u32(current_class), method, arg);
host_processor.ProcessMethod(Host1x::Control::Method(method), arg);
host_processor.ProcessMethod(host1x, Host1x::Control::Method(method), arg);
break;
default:
thi_regs.reg_array[method] = arg;

View file

@ -17,28 +17,36 @@
namespace Tegra::Control {
ChannelState::ChannelState(s32 bind_id_) : bind_id{bind_id_}, initialized{} {}
ChannelState::Payload::Payload(Core::System& system, MemoryManager& memory_manager, ChannelState& channel_state)
: maxwell_3d(memory_manager)
, fermi_2d(memory_manager)
, kepler_compute(memory_manager)
, maxwell_dma(memory_manager)
, kepler_memory(memory_manager)
, nv01_timer(memory_manager)
, dma_pusher(system, memory_manager, channel_state)
{}
void ChannelState::Init(Core::System& system, GPU& gpu, u64 program_id_) {
ChannelState::ChannelState(s32 bind_id_)
: bind_id{bind_id_}
{}
void ChannelState::Init(Core::System& system, u64 program_id_) {
ASSERT(memory_manager);
program_id = program_id_;
dma_pusher.emplace(system, gpu, *memory_manager, *this);
maxwell_3d.emplace(system, *memory_manager);
fermi_2d.emplace(*memory_manager);
kepler_compute.emplace(system, *memory_manager);
maxwell_dma.emplace(system, *memory_manager);
kepler_memory.emplace(system, *memory_manager);
payload.emplace(system, *memory_manager, *this);
initialized = true;
}
void ChannelState::BindRasterizer(VideoCore::RasterizerInterface* rasterizer) {
dma_pusher->BindRasterizer(rasterizer);
payload->dma_pusher.BindRasterizer(rasterizer);
memory_manager->BindRasterizer(rasterizer);
maxwell_3d->BindRasterizer(rasterizer);
fermi_2d->BindRasterizer(rasterizer);
kepler_memory->BindRasterizer(rasterizer);
kepler_compute->BindRasterizer(rasterizer);
maxwell_dma->BindRasterizer(rasterizer);
payload->maxwell_3d.BindRasterizer(rasterizer);
payload->fermi_2d.BindRasterizer(rasterizer);
payload->kepler_memory.BindRasterizer(rasterizer);
payload->kepler_compute.BindRasterizer(rasterizer);
payload->maxwell_dma.BindRasterizer(rasterizer);
//payload->nv01_timer.BindRasterizer(rasterizer);
}
} // namespace Tegra::Control

View file

@ -14,6 +14,7 @@
#include "video_core/engines/kepler_compute.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/maxwell_dma.h"
#include "video_core/engines/nv01_timer.h"
#include "video_core/dma_pusher.h"
namespace Core {
@ -34,28 +35,33 @@ namespace Control {
struct ChannelState {
explicit ChannelState(s32 bind_id);
void Init(Core::System& system, GPU& gpu, u64 program_id);
void Init(Core::System& system, u64 program_id);
void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
struct Payload {
explicit Payload(Core::System& system, MemoryManager& memory_manager, ChannelState& channel_state);
/// 3D engine
std::optional<Engines::Maxwell3D> maxwell_3d;
Engines::Maxwell3D maxwell_3d;
/// 2D engine
std::optional<Engines::Fermi2D> fermi_2d;
Engines::Fermi2D fermi_2d;
/// Compute engine
std::optional<Engines::KeplerCompute> kepler_compute;
Engines::KeplerCompute kepler_compute;
/// DMA engine
std::optional<Engines::MaxwellDMA> maxwell_dma;
Engines::MaxwellDMA maxwell_dma;
/// Inline memory engine
std::optional<Engines::KeplerMemory> kepler_memory;
Engines::KeplerMemory kepler_memory;
/// NV01 Timer
std::optional<Engines::KeplerMemory> nv01_timer;
std::optional<DmaPusher> dma_pusher;
std::shared_ptr<MemoryManager> memory_manager;
Engines::Nv01Timer nv01_timer;
DmaPusher dma_pusher;
};
std::optional<Payload> payload;
MemoryManager* memory_manager = nullptr;
s32 bind_id = -1;
u64 program_id = 0;
bool initialized{};
bool initialized = false;
};
} // namespace Control

View file

@ -1,3 +1,6 @@
// 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
@ -6,8 +9,11 @@
namespace VideoCommon {
ChannelInfo::ChannelInfo(Tegra::Control::ChannelState& channel_state)
: maxwell3d{*channel_state.maxwell_3d}, kepler_compute{*channel_state.kepler_compute},
gpu_memory{*channel_state.memory_manager}, program_id{channel_state.program_id} {}
: maxwell3d{channel_state.payload->maxwell_3d}
, kepler_compute{channel_state.payload->kepler_compute}
, gpu_memory{*channel_state.memory_manager}
, program_id{channel_state.program_id}
{}
template class VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo>;

View file

@ -1,3 +1,6 @@
// 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
@ -41,7 +44,7 @@ void ChannelSetupCaches<P>::CreateChannel(struct Tegra::Control::ChannelState& c
AddressSpaceRef new_gpu_mem_ref{
.ref_count = 1,
.storage_id = address_spaces.size(),
.gpu_memory = channel.memory_manager.get(),
.gpu_memory = channel.memory_manager,
};
address_spaces.emplace(channel.memory_manager->GetID(), new_gpu_mem_ref);
OnGPUASRegister(channel.memory_manager->GetID());

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
@ -12,11 +12,8 @@
#include "video_core/gpu.h"
namespace Tegra::Control {
Scheduler::Scheduler(GPU& gpu_) : gpu{gpu_} {}
Scheduler::~Scheduler() = default;
void Scheduler::Push(s32 channel, CommandList&& entries) {
void Scheduler::Push(GPU& gpu, s32 channel, CommandList&& entries) {
std::shared_ptr<ChannelState> channel_state;
{
std::unique_lock lk(scheduling_guard);
@ -27,8 +24,8 @@ void Scheduler::Push(s32 channel, CommandList&& entries) {
}
// Process commands outside the lock to reduce contention.
// Multiple channels can prepare their commands in parallel.
channel_state->dma_pusher->Push(std::move(entries));
channel_state->dma_pusher->DispatchCalls();
channel_state->payload->dma_pusher.Push(std::move(entries));
channel_state->payload->dma_pusher.DispatchCalls();
}
void Scheduler::DeclareChannel(std::shared_ptr<ChannelState> new_channel) {

View file

@ -22,17 +22,13 @@ struct ChannelState;
class Scheduler {
public:
explicit Scheduler(GPU& gpu_);
~Scheduler();
void Push(s32 channel, CommandList&& entries);
void Push(GPU& gpu, s32 channel, CommandList&& entries);
void DeclareChannel(std::shared_ptr<ChannelState> new_channel);
private:
ankerl::unordered_dense::map<s32, std::shared_ptr<ChannelState>> channels;
std::mutex scheduling_guard;
GPU& gpu;
};
} // namespace Control

View file

@ -12,37 +12,32 @@
#include "video_core/guest_memory.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/texture_cache/util.h"
#ifdef _MSC_VER
#include <intrin.h>
#endif
namespace Tegra {
constexpr u32 MacroRegistersStart = 0xE00;
[[maybe_unused]] constexpr u32 ComputeInline = 0x6D;
DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_,
Control::ChannelState& channel_state_)
: gpu{gpu_}, system{system_}, memory_manager{memory_manager_}, puller{gpu_, memory_manager_,
*this, channel_state_}, signal_sync{false}, synced{true} {}
DmaPusher::DmaPusher(Core::System& system_, MemoryManager& memory_manager_, Control::ChannelState& channel_state_)
: system{system_}
, memory_manager{memory_manager_}
, channel_state{channel_state_}
, signal_sync{false}
, synced{false}
{}
DmaPusher::~DmaPusher() = default;
void DmaPusher::DispatchCalls() {
dma_pushbuffer_subindex = 0;
dma_state.is_last_call = true;
while (system.IsPoweredOn()) {
if (!Step()) {
break;
}
}
gpu.FlushCommands();
gpu.OnCommandListEnd();
system.GPU().FlushCommands();
system.GPU().OnCommandListEnd();
}
bool DmaPusher::Step() {
@ -171,9 +166,9 @@ void DmaPusher::SetState(const CommandHeader& command_header) {
dma_state.method_count = command_header.method_count;
}
void DmaPusher::CallMethod(u32 argument) const {
void DmaPusher::CallMethod(u32 argument) {
if (dma_state.method < non_puller_methods) {
puller.CallPullerMethod(Engines::Puller::MethodCall{
puller.CallPullerMethod(*this, Engines::Puller::MethodCall{
dma_state.method,
argument,
dma_state.subchannel,
@ -181,30 +176,29 @@ void DmaPusher::CallMethod(u32 argument) const {
});
} else {
auto subchannel = subchannels[dma_state.subchannel];
if (subchannel->execution_mask[dma_state.method]) {
subchannel->ConsumeSink();
subchannel->current_dma_segment = dma_state.dma_get + dma_state.dma_word_offset;
subchannel->CallMethod(dma_state.method, argument, dma_state.is_last_call);
} else {
if (!subchannel->execution_mask[dma_state.method]) {
subchannel->method_sink.emplace_back(dma_state.method, argument);
} else {
subchannel->ConsumeSink(system);
subchannel->current_dma_segment = dma_state.dma_get + dma_state.dma_word_offset;
subchannel->CallMethod(system, dma_state.method, argument, dma_state.is_last_call);
}
}
}
void DmaPusher::CallMultiMethod(const u32* base_start, u32 num_methods) const {
void DmaPusher::CallMultiMethod(const u32* base_start, u32 num_methods) {
if (dma_state.method < non_puller_methods) {
puller.CallMultiMethod(dma_state.method, dma_state.subchannel, base_start, num_methods, dma_state.method_count);
puller.CallMultiMethod(*this, dma_state.method, dma_state.subchannel, base_start, num_methods, dma_state.method_count);
} else {
auto subchannel = subchannels[dma_state.subchannel];
subchannel->ConsumeSink();
subchannel->ConsumeSink(system);
subchannel->current_dma_segment = dma_state.dma_get + dma_state.dma_word_offset;
subchannel->CallMultiMethod(dma_state.method, base_start, num_methods, dma_state.method_count);
subchannel->CallMultiMethod(system, dma_state.method, base_start, num_methods, dma_state.method_count);
}
}
void DmaPusher::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) {
rasterizer = rasterizer_;
puller.BindRasterizer(rasterizer);
}
} // namespace Tegra

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@ -109,25 +109,21 @@ inline CommandHeader BuildCommandHeader(BufferMethods method, u32 arg_count, Sub
struct CommandList final {
CommandList() = default;
explicit CommandList(std::size_t size) : command_lists(size) {}
explicit CommandList(
boost::container::small_vector<CommandHeader, 512>&& prefetch_command_list_)
explicit CommandList(boost::container::small_vector<CommandHeader, 512>&& prefetch_command_list_)
: prefetch_command_list{std::move(prefetch_command_list_)} {}
boost::container::small_vector<CommandListHeader, 512> command_lists;
boost::container::small_vector<CommandHeader, 512> prefetch_command_list;
};
/**
* The DmaPusher class implements DMA submission to FIFOs, providing an area of memory that the
* emulated app fills with commands and tells PFIFO to process. The pushbuffers are then assembled
* into a "command stream" consisting of 32-bit words that make up "commands".
* See https://envytools.readthedocs.io/en/latest/hw/fifo/dma-pusher.html#fifo-dma-pusher for
* details on this implementation.
*/
/// @brief The DmaPusher class implements DMA submission to FIFOs, providing an area of memory that the
/// emulated app fills with commands and tells PFIFO to process. The pushbuffers are then assembled
/// into a "command stream" consisting of 32-bit words that make up "commands".
/// See https://envytools.readthedocs.io/en/latest/hw/fifo/dma-pusher.html#fifo-dma-pusher for
/// details on this implementation.
class DmaPusher final {
public:
explicit DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_,
Control::ChannelState& channel_state_);
explicit DmaPusher(Core::System& system_, MemoryManager& memory_manager_, Control::ChannelState& channel_state_);
~DmaPusher();
void Push(CommandList&& entries) {
@ -136,8 +132,7 @@ public:
void DispatchCalls();
void BindSubchannel(Engines::EngineInterface* engine, u32 subchannel_id,
Engines::EngineTypes engine_type) {
void BindSubchannel(Engines::EngineInterface* engine, u32 subchannel_id, Engines::EngineTypes engine_type) {
subchannels[subchannel_id] = engine;
subchannel_type[subchannel_id] = engine_type;
}
@ -152,11 +147,11 @@ private:
void SetState(const CommandHeader& command_header);
void CallMethod(u32 argument) const;
void CallMultiMethod(const u32* base_start, u32 num_methods) const;
void CallMethod(u32 argument);
void CallMultiMethod(const u32* base_start, u32 num_methods);
Common::ScratchBuffer<CommandHeader>
command_headers; ///< Buffer for list of commands fetched at once
public:
Common::ScratchBuffer<CommandHeader> command_headers; ///< Buffer for list of commands fetched at once
std::queue<CommandList> dma_pushbuffer; ///< Queue of command lists to be processed
std::size_t dma_pushbuffer_subindex{}; ///< Index within a command list within the pushbuffer
@ -172,24 +167,24 @@ private:
bool is_last_call;
};
Core::System& system;
MemoryManager& memory_manager;
Control::ChannelState& channel_state;
DmaState dma_state{};
bool dma_increment_once{};
const bool ib_enable{true}; ///< IB mode enabled
std::array<Engines::EngineInterface*, max_subchannels> subchannels{};
std::array<Engines::EngineTypes, max_subchannels> subchannel_type;
GPU& gpu;
Core::System& system;
MemoryManager& memory_manager;
mutable Engines::Puller puller;
VideoCore::RasterizerInterface* rasterizer;
bool signal_sync;
bool synced;
Engines::Puller puller;
std::mutex sync_mutex;
std::condition_variable sync_cv;
VideoCore::RasterizerInterface* rasterizer = nullptr;
const bool ib_enable : 1 = true; ///< IB mode enabled
bool dma_increment_once : 1 = false;
bool signal_sync : 1 = false;
bool synced : 1 = false;
};
} // namespace Tegra

View file

@ -12,6 +12,10 @@
#include "common/common_types.h"
namespace Core {
class System;
}
namespace Tegra::Engines {
enum class EngineTypes : u32 {
@ -28,28 +32,25 @@ public:
virtual ~EngineInterface() = default;
/// Write the value to the register identified by method.
virtual void CallMethod(u32 method, u32 method_argument, bool is_last_call) = 0;
virtual void CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) = 0;
/// Write multiple values to the register identified by method.
virtual void CallMultiMethod(u32 method, const u32* base_start, u32 amount,
u32 methods_pending) = 0;
virtual void CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) = 0;
void ConsumeSink() {
if (method_sink.empty()) {
return;
void ConsumeSink(Core::System& system) {
if (!method_sink.empty()) {
ConsumeSinkImpl(system);
}
ConsumeSinkImpl();
}
std::bitset<(std::numeric_limits<u16>::max)()> execution_mask{};
std::vector<std::pair<u32, u32>> method_sink{};
bool current_dirty{};
GPUVAddr current_dma_segment;
bool current_dirty{};
protected:
virtual void ConsumeSinkImpl() {
virtual void ConsumeSinkImpl(Core::System& system) {
for (auto [method, value] : method_sink) {
CallMethod(method, value, true);
CallMethod(system, method, value, true);
}
method_sink.clear();
}

View file

@ -36,9 +36,8 @@ void Fermi2D::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) {
rasterizer = rasterizer_;
}
void Fermi2D::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
ASSERT_MSG(method < Regs::NUM_REGS,
"Invalid Fermi2D register, increase the size of the Regs structure");
void Fermi2D::CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) {
ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Fermi2D register, increase the size of the Regs structure");
regs.reg_array[method] = method_argument;
if (method == FERMI2D_REG_INDEX(pixels_from_memory.src_y0) + 1) {
@ -46,13 +45,13 @@ void Fermi2D::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
}
}
void Fermi2D::CallMultiMethod(u32 method, const u32* base_start, u32 amount, u32 methods_pending) {
void Fermi2D::CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) {
for (u32 i = 0; i < amount; ++i) {
CallMethod(method, base_start[i], methods_pending - i <= 1);
CallMethod(system, method, base_start[i], methods_pending - i <= 1);
}
}
void Fermi2D::ConsumeSinkImpl() {
void Fermi2D::ConsumeSinkImpl(Core::System& system) {
for (auto [method, value] : method_sink) {
regs.reg_array[method] = value;
}
@ -60,8 +59,7 @@ void Fermi2D::ConsumeSinkImpl() {
}
void Fermi2D::Blit() {
LOG_DEBUG(HW_GPU, "called. source address=0x{:x}, destination address=0x{:x}",
regs.src.Address(), regs.dst.Address());
LOG_DEBUG(HW_GPU, "called. source address=0x{:x}, destination address=0x{:x}", regs.src.Address(), regs.dst.Address());
UNIMPLEMENTED_IF_MSG(regs.operation != Operation::SrcCopy, "Operation is not copy");
UNIMPLEMENTED_IF_MSG(regs.src.layer != 0, "Source layer is not zero");

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -44,11 +47,10 @@ public:
void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
/// Write the value to the register identified by method.
void CallMethod(u32 method, u32 method_argument, bool is_last_call) override;
void CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) override;
/// Write multiple values to the register identified by method.
void CallMultiMethod(u32 method, const u32* base_start, u32 amount,
u32 methods_pending) override;
void CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) override;
enum class Origin : u32 {
Center = 0,
@ -311,7 +313,7 @@ private:
/// registers.
void Blit();
void ConsumeSinkImpl() override;
void ConsumeSinkImpl(Core::System& system) override;
};
#define ASSERT_REG_POSITION(field_name, position) \

View file

@ -16,8 +16,10 @@
namespace Tegra::Engines {
KeplerCompute::KeplerCompute(Core::System& system_, MemoryManager& memory_manager_)
: system{system_}, memory_manager{memory_manager_}, upload_state{memory_manager, regs.upload} {
KeplerCompute::KeplerCompute(MemoryManager& memory_manager_)
: memory_manager{memory_manager_}
, upload_state{memory_manager, regs.upload}
{
execution_mask.reset();
execution_mask[KEPLER_COMPUTE_REG_INDEX(exec_upload)] = true;
execution_mask[KEPLER_COMPUTE_REG_INDEX(data_upload)] = true;
@ -31,16 +33,15 @@ void KeplerCompute::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_)
upload_state.BindRasterizer(rasterizer);
}
void KeplerCompute::ConsumeSinkImpl() {
void KeplerCompute::ConsumeSinkImpl(Core::System& system) {
for (auto [method, value] : method_sink) {
regs.reg_array[method] = value;
}
method_sink.clear();
}
void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
ASSERT_MSG(method < Regs::NUM_REGS,
"Invalid KeplerCompute register, increase the size of the Regs structure");
void KeplerCompute::CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) {
ASSERT_MSG(method < Regs::NUM_REGS, "Invalid KeplerCompute register, increase the size of the Regs structure");
regs.reg_array[method] = method_argument;
@ -78,8 +79,7 @@ void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_cal
}
}
void KeplerCompute::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
u32 methods_pending) {
void KeplerCompute::CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) {
switch (method) {
case KEPLER_COMPUTE_REG_INDEX(data_upload):
upload_address = current_dma_segment;
@ -87,7 +87,7 @@ void KeplerCompute::CallMultiMethod(u32 method, const u32* base_start, u32 amoun
return;
default:
for (u32 i = 0; i < amount; i++) {
CallMethod(method, base_start[i], methods_pending - i <= 1);
CallMethod(system, method, base_start[i], methods_pending - i <= 1);
}
break;
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -42,7 +45,7 @@ namespace Tegra::Engines {
class KeplerCompute final : public EngineInterface {
public:
explicit KeplerCompute(Core::System& system, MemoryManager& memory_manager);
explicit KeplerCompute(MemoryManager& memory_manager);
~KeplerCompute();
/// Binds a rasterizer to this engine.
@ -199,11 +202,10 @@ public:
"KeplerCompute LaunchParams has wrong size");
/// Write the value to the register identified by method.
void CallMethod(u32 method, u32 method_argument, bool is_last_call) override;
void CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) override;
/// Write multiple values to the register identified by method.
void CallMultiMethod(u32 method, const u32* base_start, u32 amount,
u32 methods_pending) override;
void CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) override;
std::optional<GPUVAddr> GetIndirectComputeAddress() const {
return indirect_compute;
@ -212,7 +214,7 @@ public:
private:
void ProcessLaunch();
void ConsumeSinkImpl() override;
void ConsumeSinkImpl(Core::System& system) override;
/// Retrieves information about a specific TIC entry from the TIC buffer.
Texture::TICEntry GetTICEntry(u32 tic_index) const;
@ -220,7 +222,6 @@ private:
/// Retrieves information about a specific TSC entry from the TSC buffer.
Texture::TSCEntry GetTSCEntry(u32 tsc_index) const;
Core::System& system;
MemoryManager& memory_manager;
VideoCore::RasterizerInterface* rasterizer = nullptr;
Upload::State upload_state;

View file

@ -14,8 +14,9 @@
namespace Tegra::Engines {
KeplerMemory::KeplerMemory(Core::System& system_, MemoryManager& memory_manager)
: system{system_}, upload_state{memory_manager, regs.upload} {}
KeplerMemory::KeplerMemory(MemoryManager& memory_manager)
: upload_state{memory_manager, regs.upload}
{}
KeplerMemory::~KeplerMemory() = default;
@ -27,16 +28,15 @@ void KeplerMemory::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) {
execution_mask[KEPLERMEMORY_REG_INDEX(data)] = true;
}
void KeplerMemory::ConsumeSinkImpl() {
void KeplerMemory::ConsumeSinkImpl(Core::System& system) {
for (auto [method, value] : method_sink) {
regs.reg_array[method] = value;
}
method_sink.clear();
}
void KeplerMemory::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
ASSERT_MSG(method < Regs::NUM_REGS,
"Invalid KeplerMemory register, increase the size of the Regs structure");
void KeplerMemory::CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) {
ASSERT_MSG(method < Regs::NUM_REGS, "Invalid KeplerMemory register, increase the size of the Regs structure");
regs.reg_array[method] = method_argument;
@ -52,15 +52,14 @@ void KeplerMemory::CallMethod(u32 method, u32 method_argument, bool is_last_call
}
}
void KeplerMemory::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
u32 methods_pending) {
void KeplerMemory::CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) {
switch (method) {
case KEPLERMEMORY_REG_INDEX(data):
upload_state.ProcessData(base_start, amount);
return;
default:
for (u32 i = 0; i < amount; i++) {
CallMethod(method, base_start[i], methods_pending - i <= 1);
CallMethod(system, method, base_start[i], methods_pending - i <= 1);
}
break;
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -36,18 +39,17 @@ namespace Tegra::Engines {
class KeplerMemory final : public EngineInterface {
public:
explicit KeplerMemory(Core::System& system_, MemoryManager& memory_manager);
explicit KeplerMemory(MemoryManager& memory_manager);
~KeplerMemory() override;
/// Binds a rasterizer to this engine.
void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
/// Write the value to the register identified by method.
void CallMethod(u32 method, u32 method_argument, bool is_last_call) override;
void CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) override;
/// Write multiple values to the register identified by method.
void CallMultiMethod(u32 method, const u32* base_start, u32 amount,
u32 methods_pending) override;
void CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) override;
struct Regs {
static constexpr size_t NUM_REGS = 0x7F;
@ -73,9 +75,7 @@ public:
} regs{};
private:
void ConsumeSinkImpl() override;
Core::System& system;
void ConsumeSinkImpl(Core::System& system) override;
Upload::State upload_state;
};

View file

@ -25,9 +25,8 @@ namespace Tegra::Engines {
/// First register id that is actually a Macro call.
constexpr u32 MacroRegistersStart = 0xE00;
Maxwell3D::Maxwell3D(Core::System& system_, MemoryManager& memory_manager_)
Maxwell3D::Maxwell3D(MemoryManager& memory_manager_)
: draw_manager()
, system{system_}
, memory_manager{memory_manager_}
#ifdef ARCHITECTURE_x86_64
, macro_engine(bool(Settings::values.disable_macro_jit))
@ -201,11 +200,10 @@ bool Maxwell3D::IsMethodExecutable(u32 method) {
}
}
void Maxwell3D::ProcessMacro(u32 method, const u32* base_start, u32 amount, bool is_last_call) {
void Maxwell3D::ProcessMacro(Core::System& system, u32 method, const u32* base_start, u32 amount, bool is_last_call) {
if (executing_macro == 0) {
// A macro call must begin by writing the macro method's register, not its argument.
ASSERT_MSG((method % 2) == 0,
"Can't start macro execution by writing to the ARGS register");
ASSERT((method % 2) == 0 && "Can't start macro execution by writing to the ARGS register");
executing_macro = method;
}
@ -219,8 +217,8 @@ void Maxwell3D::ProcessMacro(u32 method, const u32* base_start, u32 amount, bool
// Call the macro when there are no more parameters in the command buffer
if (is_last_call) {
ConsumeSink();
CallMacroMethod(executing_macro, macro_params);
ConsumeSink(system);
CallMacroMethod(system, executing_macro, macro_params);
macro_params.clear();
macro_addresses.clear();
macro_segments.clear();
@ -287,7 +285,7 @@ u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) {
return argument;
}
void Maxwell3D::ConsumeSinkImpl() {
void Maxwell3D::ConsumeSinkImpl(Core::System& system) {
const auto control = shadow_state.shadow_ram_control;
if (control == Regs::ShadowRamControl::Track || control == Regs::ShadowRamControl::TrackWithFilter) {
for (auto [method, value] : method_sink) {
@ -378,7 +376,7 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
}
}
void Maxwell3D::CallMacroMethod(u32 method, const std::vector<u32>& parameters) {
void Maxwell3D::CallMacroMethod(Core::System& system, u32 method, const std::vector<u32>& parameters) {
// Reset the current macro.
executing_macro = 0;
@ -387,11 +385,11 @@ void Maxwell3D::CallMacroMethod(u32 method, const std::vector<u32>& parameters)
((method - MacroRegistersStart) >> 1) % static_cast<u32>(macro_positions.size());
// Execute the current macro.
macro_engine.Execute(*this, macro_positions[entry], parameters);
macro_engine.Execute(system, *this, macro_positions[entry], parameters);
draw_manager.DrawDeferred(*this);
}
void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
void Maxwell3D::CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) {
// It is an error to write to a register other than the current macro's ARG register before
// it has finished execution.
if (executing_macro != 0) {
@ -401,7 +399,7 @@ void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
// Methods after 0xE00 are special, they're actually triggers for some microcode that was
// uploaded to the GPU during initialization.
if (method >= MacroRegistersStart) {
ProcessMacro(method, &method_argument, 1, is_last_call);
ProcessMacro(system, method, &method_argument, 1, is_last_call);
return;
}
@ -411,12 +409,11 @@ void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
ProcessMethodCall(method, argument, method_argument, is_last_call);
}
void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
u32 methods_pending) {
void Maxwell3D::CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) {
// Methods after 0xE00 are special, they're actually triggers for some microcode that was
// uploaded to the GPU during initialization.
if (method >= MacroRegistersStart) {
ProcessMacro(method, base_start, amount, amount == methods_pending);
ProcessMacro(system, method, base_start, amount, amount == methods_pending);
return;
}
switch (method) {
@ -445,7 +442,7 @@ void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
}
default:
for (u32 i = 0; i < amount; i++) {
CallMethod(method, base_start[i], methods_pending - i <= 1);
CallMethod(system, method, base_start[i], methods_pending - i <= 1);
}
break;
}
@ -467,7 +464,7 @@ void Maxwell3D::ProcessFirmwareCall4() {
regs.shadow_scratch[0] = 1;
}
void Maxwell3D::StampQueryResult(u64 payload, bool long_query) {
void Maxwell3D::StampQueryResult(Core::System& system, u64 payload, bool long_query) {
const GPUVAddr sequence_address{regs.report_semaphore.Address()};
if (long_query) {
memory_manager.Write<u64>(sequence_address + sizeof(u64), system.GPU().GetTicks());

View file

@ -55,7 +55,7 @@ namespace Tegra::Engines {
class Maxwell3D final : public EngineInterface {
public:
explicit Maxwell3D(Core::System& system, MemoryManager& memory_manager);
explicit Maxwell3D(MemoryManager& memory_manager);
~Maxwell3D();
/// Binds a rasterizer to this engine.
@ -3129,11 +3129,10 @@ public:
u32 GetRegisterValue(u32 method) const;
/// Write the value to the register identified by method.
void CallMethod(u32 method, u32 method_argument, bool is_last_call) override;
void CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) override;
/// Write multiple values to the register identified by method.
void CallMultiMethod(u32 method, const u32* base_start, u32 amount,
u32 methods_pending) override;
void CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) override;
bool ShouldExecute() const {
return execute_on;
@ -3190,13 +3189,13 @@ public:
private:
void InitializeRegisterDefaults();
void ProcessMacro(u32 method, const u32* base_start, u32 amount, bool is_last_call);
void ProcessMacro(Core::System& system, u32 method, const u32* base_start, u32 amount, bool is_last_call);
u32 ProcessShadowRam(u32 method, u32 argument);
void ProcessDirtyRegisters(u32 method, u32 argument);
void ConsumeSinkImpl() override;
void ConsumeSinkImpl(Core::System& system) override;
void ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argument, bool is_last_call);
@ -3212,7 +3211,7 @@ private:
* @param method Method to call
* @param parameters Arguments to the method call
*/
void CallMacroMethod(u32 method, const std::vector<u32>& parameters);
void CallMacroMethod(Core::System& system, u32 method, const std::vector<u32>& parameters);
/// Handles writes to the macro uploading register.
void ProcessMacroUpload(u32 data);
@ -3227,7 +3226,7 @@ private:
void ProcessQueryGet();
/// Writes the query result accordingly.
void StampQueryResult(u64 payload, bool long_query);
void StampQueryResult(Core::System& system, u64 payload, bool long_query);
/// Handles conditional rendering.
void ProcessQueryCondition();
@ -3242,7 +3241,6 @@ private:
bool IsMethodExecutable(u32 method);
Core::System& system;
MemoryManager& memory_manager;
VideoCore::RasterizerInterface* rasterizer = nullptr;

View file

@ -21,8 +21,9 @@ namespace Tegra::Engines {
using namespace Texture;
MaxwellDMA::MaxwellDMA(Core::System& system_, MemoryManager& memory_manager_)
: system{system_}, memory_manager{memory_manager_} {
MaxwellDMA::MaxwellDMA(MemoryManager& memory_manager_)
: memory_manager{memory_manager_}
{
execution_mask.reset();
execution_mask[offsetof(Regs, launch_dma) / sizeof(u32)] = true;
}
@ -33,14 +34,14 @@ void MaxwellDMA::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) {
rasterizer = rasterizer_;
}
void MaxwellDMA::ConsumeSinkImpl() {
void MaxwellDMA::ConsumeSinkImpl(Core::System& system) {
for (auto [method, value] : method_sink) {
regs.reg_array[method] = value;
}
method_sink.clear();
}
void MaxwellDMA::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
void MaxwellDMA::CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) {
ASSERT_MSG(method < NUM_REGS, "Invalid MaxwellDMA register");
regs.reg_array[method] = method_argument;
@ -50,16 +51,14 @@ void MaxwellDMA::CallMethod(u32 method, u32 method_argument, bool is_last_call)
}
}
void MaxwellDMA::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
u32 methods_pending) {
void MaxwellDMA::CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) {
for (u32 i = 0; i < amount; ++i) {
CallMethod(method, base_start[i], methods_pending - i <= 1);
CallMethod(system, method, base_start[i], methods_pending - i <= 1);
}
}
void MaxwellDMA::Launch() {
LOG_TRACE(Render_OpenGL, "DMA copy 0x{:x} -> 0x{:x}", static_cast<GPUVAddr>(regs.offset_in),
static_cast<GPUVAddr>(regs.offset_out));
LOG_TRACE(Render_OpenGL, "DMA copy 0x{:x} -> 0x{:x}", static_cast<GPUVAddr>(regs.offset_in), GPUVAddr(regs.offset_out));
// TODO(Subv): Perform more research and implement all features of this engine.
const LaunchDMA& launch = regs.launch_dma;

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -238,15 +241,14 @@ public:
void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
explicit MaxwellDMA(Core::System& system_, MemoryManager& memory_manager_);
explicit MaxwellDMA(MemoryManager& memory_manager_);
~MaxwellDMA() override;
/// Write the value to the register identified by method.
void CallMethod(u32 method, u32 method_argument, bool is_last_call) override;
void CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) override;
/// Write multiple values to the register identified by method.
void CallMultiMethod(u32 method, const u32* base_start, u32 amount,
u32 methods_pending) override;
void CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) override;
private:
/// Performs the copy from the source buffer to the destination buffer as configured in the
@ -261,9 +263,7 @@ private:
void ReleaseSemaphore();
void ConsumeSinkImpl() override;
Core::System& system;
void ConsumeSinkImpl(Core::System& system) override;
MemoryManager& memory_manager;
VideoCore::RasterizerInterface* rasterizer = nullptr;

View file

@ -26,18 +26,16 @@ class MemoryManager;
namespace Tegra::Engines {
class Nv01Timer final : public EngineInterface {
public:
explicit Nv01Timer(Core::System& system_, MemoryManager& memory_manager)
: system{system_}
{}
~Nv01Timer() override;
explicit Nv01Timer(MemoryManager& memory_manager) noexcept {}
~Nv01Timer() noexcept override {}
/// Write the value to the register identified by method.
void CallMethod(u32 method, u32 method_argument, bool is_last_call) override {
void CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) override {
LOG_DEBUG(HW_GPU, "method={}, argument={}, is_last_call={}", method, method_argument, is_last_call);
}
/// Write multiple values to the register identified by method.
void CallMultiMethod(u32 method, const u32* base_start, u32 amount, u32 methods_pending) override {
void CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) override {
LOG_DEBUG(HW_GPU, "method={}, base_start={}, amount={}, pending={}", method, fmt::ptr(base_start), amount, methods_pending);
}
@ -46,7 +44,6 @@ public:
INSERT_PADDING_BYTES_NOINIT(0x48);
} regs{};
private:
void ConsumeSinkImpl() override {}
Core::System& system;
void ConsumeSinkImpl(Core::System& system) override {}
};
}

View file

@ -22,37 +22,29 @@
namespace Tegra::Engines {
Puller::Puller(GPU& gpu_, MemoryManager& memory_manager_, DmaPusher& dma_pusher_,
Control::ChannelState& channel_state_)
: gpu{gpu_}, memory_manager{memory_manager_}, dma_pusher{dma_pusher_}, channel_state{
channel_state_} {}
Puller::~Puller() = default;
void Puller::ProcessBindMethod(const MethodCall& method_call) {
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);
LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", method_call.subchannel, method_call.argument);
const auto engine_id = static_cast<EngineID>(method_call.argument);
bound_engines[method_call.subchannel] = engine_id;
switch (engine_id) {
case EngineID::FERMI_TWOD_A:
dma_pusher.BindSubchannel(&*channel_state.fermi_2d, method_call.subchannel, EngineTypes::Fermi2D);
dma_pusher.BindSubchannel(&dma_pusher.channel_state.payload->fermi_2d, method_call.subchannel, EngineTypes::Fermi2D);
break;
case EngineID::MAXWELL_B:
dma_pusher.BindSubchannel(&*channel_state.maxwell_3d, method_call.subchannel, EngineTypes::Maxwell3D);
dma_pusher.BindSubchannel(&dma_pusher.channel_state.payload->maxwell_3d, method_call.subchannel, EngineTypes::Maxwell3D);
break;
case EngineID::KEPLER_COMPUTE_B:
dma_pusher.BindSubchannel(&*channel_state.kepler_compute, method_call.subchannel, EngineTypes::KeplerCompute);
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(&*channel_state.maxwell_dma, method_call.subchannel, EngineTypes::MaxwellDMA);
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(&*channel_state.kepler_memory, method_call.subchannel, EngineTypes::KeplerMemory);
dma_pusher.BindSubchannel(&dma_pusher.channel_state.payload->kepler_memory, method_call.subchannel, EngineTypes::KeplerMemory);
break;
case EngineID::NV01_TIMER:
dma_pusher.BindSubchannel(&*channel_state.nv01_timer, method_call.subchannel, EngineTypes::Nv01Timer);
dma_pusher.BindSubchannel(&dma_pusher.channel_state.payload->nv01_timer, method_call.subchannel, EngineTypes::Nv01Timer);
break;
default:
UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id);
@ -60,15 +52,15 @@ void Puller::ProcessBindMethod(const MethodCall& method_call) {
}
}
void Puller::ProcessFenceActionMethod() {
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);
rasterizer->ReleaseFences();
dma_pusher.rasterizer->ReleaseFences();
break;
case Puller::FenceOperation::Increment:
rasterizer->SignalSyncPoint(regs.fence_action.syncpoint_id);
dma_pusher.rasterizer->SignalSyncPoint(regs.fence_action.syncpoint_id);
break;
default:
UNIMPLEMENTED_MSG("Unimplemented operation {}", regs.fence_action.op.Value());
@ -76,37 +68,35 @@ void Puller::ProcessFenceActionMethod() {
}
}
void Puller::ProcessSemaphoreTriggerMethod() {
void Puller::ProcessSemaphoreTriggerMethod(DmaPusher& dma_pusher) {
const auto semaphoreOperationMask = 0xF;
const auto op =
static_cast<GpuSemaphoreOperation>(regs.semaphore_trigger & semaphoreOperationMask);
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;
rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload,
VideoCommon::QueryPropertiesFlags::HasTimeout, payload, 0);
dma_pusher.rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload, VideoCommon::QueryPropertiesFlags::HasTimeout, payload, 0);
} else {
do {
const u32 word{memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress())};
const u32 word = dma_pusher.memory_manager.Read<u32>(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) {
rasterizer->ReleaseFences();
dma_pusher.rasterizer->ReleaseFences();
continue;
}
} else if (op == GpuSemaphoreOperation::AcquireGequal) {
regs.acquire_active = true;
regs.acquire_mode = true;
if (word < regs.acquire_value) {
rasterizer->ReleaseFences();
dma_pusher.rasterizer->ReleaseFences();
continue;
}
} else if (op == GpuSemaphoreOperation::AcquireMask) {
if (word && regs.semaphore_sequence == 0) {
rasterizer->ReleaseFences();
dma_pusher.rasterizer->ReleaseFences();
continue;
}
} else {
@ -116,21 +106,20 @@ void Puller::ProcessSemaphoreTriggerMethod() {
}
}
void Puller::ProcessSemaphoreRelease() {
void Puller::ProcessSemaphoreRelease(DmaPusher& dma_pusher) {
const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()};
const u32 payload = regs.semaphore_release;
rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload,
VideoCommon::QueryPropertiesFlags::IsAFence, payload, 0);
dma_pusher.rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload, VideoCommon::QueryPropertiesFlags::IsAFence, payload, 0);
}
void Puller::ProcessSemaphoreAcquire() {
u32 word = memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress());
void Puller::ProcessSemaphoreAcquire(DmaPusher& dma_pusher) {
u32 word = dma_pusher.memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress());
const auto value = regs.semaphore_acquire;
while (word != value) {
regs.acquire_active = true;
regs.acquire_value = value;
rasterizer->ReleaseFences();
word = memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress());
dma_pusher.rasterizer->ReleaseFences();
word = dma_pusher.memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress());
// TODO(kemathe73) figure out how to do the acquire_timeout
regs.acquire_mode = false;
regs.acquire_source = false;
@ -138,13 +127,13 @@ void Puller::ProcessSemaphoreAcquire() {
}
/// Calls a GPU puller method.
void Puller::CallPullerMethod(const MethodCall& method_call) {
void Puller::CallPullerMethod(DmaPusher& dma_pusher, const MethodCall& method_call) {
regs.reg_array[method_call.method] = method_call.argument;
const auto method = static_cast<BufferMethods>(method_call.method);
switch (method) {
case BufferMethods::BindObject: {
ProcessBindMethod(method_call);
ProcessBindMethod(dma_pusher, method_call);
break;
}
case BufferMethods::Nop:
@ -155,16 +144,16 @@ void Puller::CallPullerMethod(const MethodCall& method_call) {
case BufferMethods::WrcacheFlush:
break;
case BufferMethods::RefCnt:
rasterizer->SignalReference();
dma_pusher.rasterizer->SignalReference();
break;
case BufferMethods::SyncpointOperation:
ProcessFenceActionMethod();
ProcessFenceActionMethod(dma_pusher);
break;
case BufferMethods::WaitForIdle:
rasterizer->WaitForIdle();
dma_pusher.rasterizer->WaitForIdle();
break;
case BufferMethods::SemaphoreOperation: {
ProcessSemaphoreTriggerMethod();
ProcessSemaphoreTriggerMethod(dma_pusher);
break;
}
case BufferMethods::NonStallInterrupt: {
@ -177,7 +166,7 @@ void Puller::CallPullerMethod(const MethodCall& method_call) {
}
case BufferMethods::MemOpB: {
// Implement this better.
rasterizer->InvalidateGPUCache();
dma_pusher.rasterizer->InvalidateGPUCache();
break;
}
case BufferMethods::MemOpC:
@ -186,11 +175,11 @@ void Puller::CallPullerMethod(const MethodCall& method_call) {
break;
}
case BufferMethods::SemaphoreAcquire: {
ProcessSemaphoreAcquire();
ProcessSemaphoreAcquire(dma_pusher);
break;
}
case BufferMethods::SemaphoreRelease: {
ProcessSemaphoreRelease();
ProcessSemaphoreRelease(dma_pusher);
break;
}
case BufferMethods::Yield: {
@ -205,27 +194,26 @@ void Puller::CallPullerMethod(const MethodCall& method_call) {
}
/// Calls a GPU engine method.
void Puller::CallEngineMethod(const MethodCall& method_call) {
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:
channel_state.fermi_2d->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
dma_pusher.channel_state.payload->fermi_2d.CallMethod(dma_pusher.system, method_call.method, method_call.argument, method_call.IsLastCall());
break;
case EngineID::MAXWELL_B:
channel_state.maxwell_3d->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
dma_pusher.channel_state.payload->maxwell_3d.CallMethod(dma_pusher.system, method_call.method, method_call.argument, method_call.IsLastCall());
break;
case EngineID::KEPLER_COMPUTE_B:
channel_state.kepler_compute->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
dma_pusher.channel_state.payload->kepler_compute.CallMethod(dma_pusher.system, method_call.method, method_call.argument, method_call.IsLastCall());
break;
case EngineID::MAXWELL_DMA_COPY_A:
channel_state.maxwell_dma->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
dma_pusher.channel_state.payload->maxwell_dma.CallMethod(dma_pusher.system, method_call.method, method_call.argument, method_call.IsLastCall());
break;
case EngineID::KEPLER_INLINE_TO_MEMORY_B:
channel_state.kepler_memory->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
dma_pusher.channel_state.payload->kepler_memory.CallMethod(dma_pusher.system, method_call.method, method_call.argument, method_call.IsLastCall());
break;
case EngineID::NV01_TIMER:
channel_state.nv01_timer->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
dma_pusher.channel_state.payload->nv01_timer.CallMethod(dma_pusher.system, method_call.method, method_call.argument, method_call.IsLastCall());
break;
default:
UNIMPLEMENTED_MSG("Unimplemented engine");
@ -234,28 +222,26 @@ void Puller::CallEngineMethod(const MethodCall& method_call) {
}
/// Calls a GPU engine multivalue method.
void Puller::CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
u32 methods_pending) {
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:
channel_state.fermi_2d->CallMultiMethod(method, base_start, amount, methods_pending);
dma_pusher.channel_state.payload->fermi_2d.CallMultiMethod(dma_pusher.system, method, base_start, amount, methods_pending);
break;
case EngineID::MAXWELL_B:
channel_state.maxwell_3d->CallMultiMethod(method, base_start, amount, methods_pending);
dma_pusher.channel_state.payload->maxwell_3d.CallMultiMethod(dma_pusher.system, method, base_start, amount, methods_pending);
break;
case EngineID::KEPLER_COMPUTE_B:
channel_state.kepler_compute->CallMultiMethod(method, base_start, amount, methods_pending);
dma_pusher.channel_state.payload->kepler_compute.CallMultiMethod(dma_pusher.system, method, base_start, amount, methods_pending);
break;
case EngineID::MAXWELL_DMA_COPY_A:
channel_state.maxwell_dma->CallMultiMethod(method, base_start, amount, methods_pending);
dma_pusher.channel_state.payload->maxwell_dma.CallMultiMethod(dma_pusher.system, method, base_start, amount, methods_pending);
break;
case EngineID::KEPLER_INLINE_TO_MEMORY_B:
channel_state.kepler_memory->CallMultiMethod(method, base_start, amount, methods_pending);
dma_pusher.channel_state.payload->kepler_memory.CallMultiMethod(dma_pusher.system, method, base_start, amount, methods_pending);
break;
case EngineID::NV01_TIMER:
channel_state.nv01_timer->CallMultiMethod(method, base_start, amount, methods_pending);
dma_pusher.channel_state.payload->nv01_timer.CallMultiMethod(dma_pusher.system, method, base_start, amount, methods_pending);
break;
default:
UNIMPLEMENTED_MSG("Unimplemented engine");
@ -264,31 +250,26 @@ void Puller::CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_s
}
/// Calls a GPU method.
void Puller::CallMethod(const MethodCall& method_call) {
LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method_call.method,
method_call.subchannel);
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(method_call.method)) {
CallEngineMethod(method_call);
if (ExecuteMethodOnEngine(dma_pusher, method_call.method)) {
CallEngineMethod(dma_pusher, method_call);
} else {
CallPullerMethod(method_call);
CallPullerMethod(dma_pusher, method_call);
}
}
/// Calls a GPU multivalue method.
void Puller::CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
u32 methods_pending) {
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(method)) {
CallEngineMultiMethod(method, subchannel, base_start, amount, methods_pending);
if (ExecuteMethodOnEngine(dma_pusher, method)) {
CallEngineMultiMethod(dma_pusher, method, subchannel, base_start, amount, methods_pending);
} else {
for (u32 i = 0; i < amount; i++) {
CallPullerMethod(MethodCall{
CallPullerMethod(dma_pusher, MethodCall{
method,
base_start[i],
subchannel,
@ -298,13 +279,9 @@ void Puller::CallMultiMethod(u32 method, u32 subchannel, const u32* base_start,
}
}
void Puller::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) {
rasterizer = rasterizer_;
}
/// Determines where the method should be executed.
[[nodiscard]] bool Puller::ExecuteMethodOnEngine(u32 method) {
const auto buffer_method = static_cast<BufferMethods>(method);
[[nodiscard]] bool Puller::ExecuteMethodOnEngine(DmaPusher& dma_pusher, u32 method) {
const auto buffer_method = BufferMethods(method);
return buffer_method >= BufferMethods::NonPullerMethods;
}

View file

@ -70,32 +70,13 @@ public:
BitField<8, 24, u32> syncpoint_id;
};
explicit Puller(GPU& gpu_, MemoryManager& memory_manager_, DmaPusher& dma_pusher,
Control::ChannelState& channel_state);
~Puller();
void CallMethod(const MethodCall& method_call);
void CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
u32 methods_pending);
void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
void CallPullerMethod(const MethodCall& method_call);
void CallEngineMethod(const MethodCall& method_call);
void CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
u32 methods_pending);
void CallMethod(DmaPusher& dma_pusher, const MethodCall& method_call);
void CallMultiMethod(DmaPusher& dma_pusher, u32 method, u32 subchannel, const u32* base_start, u32 amount, u32 methods_pending);
void BindRasterizer(DmaPusher& dma_pusher, VideoCore::RasterizerInterface* rasterizer);
void CallPullerMethod(DmaPusher& dma_pusher, const MethodCall& method_call);
void CallEngineMethod(DmaPusher& dma_pusher, const MethodCall& method_call);
void CallEngineMultiMethod(DmaPusher& dma_pusher, u32 method, u32 subchannel, const u32* base_start, u32 amount, u32 methods_pending);
private:
Tegra::GPU& gpu;
MemoryManager& memory_manager;
DmaPusher& dma_pusher;
Control::ChannelState& channel_state;
VideoCore::RasterizerInterface* rasterizer = nullptr;
static constexpr std::size_t NUM_REGS = 0x800;
struct Regs {
static constexpr size_t NUM_REGS = 0x40;
@ -139,12 +120,12 @@ private:
};
} regs{};
void ProcessBindMethod(const MethodCall& method_call);
void ProcessFenceActionMethod();
void ProcessSemaphoreAcquire();
void ProcessSemaphoreRelease();
void ProcessSemaphoreTriggerMethod();
[[nodiscard]] bool ExecuteMethodOnEngine(u32 method);
void ProcessBindMethod(DmaPusher& dma_pusher, const MethodCall& method_call);
void ProcessFenceActionMethod(DmaPusher& dma_pusher);
void ProcessSemaphoreAcquire(DmaPusher& dma_pusher);
void ProcessSemaphoreRelease(DmaPusher& dma_pusher);
void ProcessSemaphoreTriggerMethod(DmaPusher& dma_pusher);
[[nodiscard]] bool ExecuteMethodOnEngine(DmaPusher& dma_pusher, u32 method);
/// Mapping of command subchannels to their bound engine ids
std::array<EngineID, 8> bound_engines{};
@ -157,8 +138,7 @@ private:
};
#define ASSERT_REG_POSITION(field_name, position) \
static_assert(offsetof(Regs, field_name) == position * 4, \
"Field " #field_name " has invalid position")
static_assert(offsetof(Regs, field_name) == position * 4, "Field " #field_name " has invalid position")
ASSERT_REG_POSITION(semaphore_address, 0x4);
ASSERT_REG_POSITION(semaphore_sequence, 0x6);

View file

@ -40,30 +40,31 @@
namespace Tegra {
struct GPU::Impl {
explicit Impl(GPU& gpu_, Core::System& system_, bool is_async_, bool use_nvdec_)
: gpu{gpu_}, system{system_}, host1x{system.Host1x()}, use_nvdec{use_nvdec_},
shader_notify{std::make_unique<VideoCore::ShaderNotify>()}, is_async{is_async_},
gpu_thread{system_, is_async_}, scheduler{std::make_unique<Control::Scheduler>(gpu)} {}
explicit Impl(Core::System& system_, bool is_async_, bool use_nvdec_)
: system{system_}
, use_nvdec{use_nvdec_}
, shader_notify()
, is_async{is_async_}
, gpu_thread{system_}
{}
~Impl() = default;
std::shared_ptr<Control::ChannelState> CreateChannel(s32 channel_id) {
auto channel_state = std::make_shared<Tegra::Control::ChannelState>(channel_id);
channels.emplace(channel_id, channel_state);
scheduler->DeclareChannel(channel_state);
scheduler.DeclareChannel(channel_state);
return channel_state;
}
void BindChannel(s32 channel_id) {
if (bound_channel == channel_id) {
return;
}
if (bound_channel != channel_id) {
auto it = channels.find(channel_id);
ASSERT(it != channels.end());
bound_channel = channel_id;
current_channel = it->second.get();
rasterizer->BindChannel(*current_channel);
renderer->ReadRasterizer()->BindChannel(*current_channel);
}
}
std::shared_ptr<Control::ChannelState> AllocateChannel() {
@ -71,13 +72,13 @@ struct GPU::Impl {
}
void InitChannel(Control::ChannelState& to_init, u64 program_id) {
to_init.Init(system, gpu, program_id);
to_init.BindRasterizer(rasterizer);
rasterizer->InitializeChannel(to_init);
to_init.Init(system, program_id);
to_init.BindRasterizer(renderer->ReadRasterizer());
renderer->ReadRasterizer()->InitializeChannel(to_init);
}
void InitAddressSpace(Tegra::MemoryManager& memory_manager) {
memory_manager.BindRasterizer(rasterizer);
memory_manager.BindRasterizer(renderer->ReadRasterizer());
}
void ReleaseChannel(Control::ChannelState& to_release) {
@ -87,26 +88,26 @@ struct GPU::Impl {
/// Binds a renderer to the GPU.
void BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer_) {
renderer = std::move(renderer_);
rasterizer = renderer->ReadRasterizer();
host1x.MemoryManager().BindInterface(rasterizer);
host1x.gmmu_manager.BindRasterizer(rasterizer);
system.Host1x().memory_manager.BindInterface(renderer->ReadRasterizer());
system.Host1x().gmmu_manager.BindRasterizer(renderer->ReadRasterizer());
}
/// Flush all current written commands into the host GPU for execution.
void FlushCommands() {
rasterizer->FlushCommands();
renderer->ReadRasterizer()->FlushCommands();
}
/// Synchronizes CPU writes with Host GPU memory.
void InvalidateGPUCache() {
std::function<void(PAddr, size_t)> callback_writes(
[this](PAddr address, size_t size) { rasterizer->OnCacheInvalidation(address, size); });
std::function<void(PAddr, size_t)> callback_writes([this](PAddr address, size_t size) {
renderer->ReadRasterizer()->OnCacheInvalidation(address, size);
});
system.GatherGPUDirtyMemory(callback_writes);
}
/// Signal the ending of command list.
void OnCommandListEnd() {
rasterizer->ReleaseFences(false);
renderer->ReadRasterizer()->ReleaseFences(false);
Settings::UpdateGPUAccuracy();
}
@ -143,62 +144,6 @@ struct GPU::Impl {
}
}
/// Returns a reference to the Maxwell3D GPU engine.
[[nodiscard]] Engines::Maxwell3D& Maxwell3D() {
ASSERT(current_channel);
return *current_channel->maxwell_3d;
}
/// Returns a const reference to the Maxwell3D GPU engine.
[[nodiscard]] const Engines::Maxwell3D& Maxwell3D() const {
ASSERT(current_channel);
return *current_channel->maxwell_3d;
}
/// Returns a reference to the KeplerCompute GPU engine.
[[nodiscard]] Engines::KeplerCompute& KeplerCompute() {
ASSERT(current_channel);
return *current_channel->kepler_compute;
}
/// Returns a reference to the KeplerCompute GPU engine.
[[nodiscard]] const Engines::KeplerCompute& KeplerCompute() const {
ASSERT(current_channel);
return *current_channel->kepler_compute;
}
/// Returns a reference to the GPU DMA pusher.
[[nodiscard]] Tegra::DmaPusher& DmaPusher() {
ASSERT(current_channel);
return *current_channel->dma_pusher;
}
/// Returns a const reference to the GPU DMA pusher.
[[nodiscard]] const Tegra::DmaPusher& DmaPusher() const {
ASSERT(current_channel);
return *current_channel->dma_pusher;
}
/// Returns a reference to the underlying renderer.
[[nodiscard]] VideoCore::RendererBase& Renderer() {
return *renderer;
}
/// Returns a const reference to the underlying renderer.
[[nodiscard]] const VideoCore::RendererBase& Renderer() const {
return *renderer;
}
/// Returns a reference to the shader notifier.
[[nodiscard]] VideoCore::ShaderNotify& ShaderNotify() {
return *shader_notify;
}
/// Returns a const reference to the shader notifier.
[[nodiscard]] const VideoCore::ShaderNotify& ShaderNotify() const {
return *shader_notify;
}
[[nodiscard]] u64 GetTicks() const {
u64 gpu_tick = system.CoreTiming().GetGPUTicks();
Settings::GpuOverclock overclock = Settings::values.fast_gpu_time.GetValue();
@ -210,14 +155,6 @@ struct GPU::Impl {
return gpu_tick;
}
[[nodiscard]] bool IsAsync() const {
return is_async;
}
[[nodiscard]] bool UseNvdec() const {
return use_nvdec;
}
void RendererFrameEndNotify() {
system.GetPerfStats().EndGameFrame();
}
@ -227,7 +164,7 @@ struct GPU::Impl {
/// core timing events.
void Start() {
Settings::UpdateGPUAccuracy();
gpu_thread.StartThread(*renderer, renderer->Context(), *scheduler);
gpu_thread.StartThread(*renderer, renderer->Context(), scheduler);
}
void NotifyShutdown() {
@ -251,25 +188,24 @@ struct GPU::Impl {
/// Push GPU command entries to be processed
void PushGPUEntries(s32 channel, Tegra::CommandList&& entries) {
gpu_thread.SubmitList(channel, std::move(entries));
gpu_thread.SubmitList(channel, std::move(entries), is_async);
}
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
void FlushRegion(DAddr addr, u64 size) {
gpu_thread.FlushRegion(addr, size);
gpu_thread.FlushRegion(addr, size, is_async);
}
VideoCore::RasterizerDownloadArea OnCPURead(DAddr addr, u64 size) {
auto raster_area = rasterizer->GetFlushArea(addr, size);
auto raster_area = renderer->ReadRasterizer()->GetFlushArea(addr, size);
if (raster_area.preemtive) {
return raster_area;
}
raster_area.preemtive = true;
const u64 fence = RequestSyncOperation([this, &raster_area]() {
rasterizer->FlushRegion(raster_area.start_address,
raster_area.end_address - raster_area.start_address);
renderer->ReadRasterizer()->FlushRegion(raster_area.start_address, raster_area.end_address - raster_area.start_address);
});
gpu_thread.TickGPU();
gpu_thread.TickGPU(is_async);
WaitForSyncOperation(fence);
return raster_area;
}
@ -280,16 +216,15 @@ struct GPU::Impl {
}
bool OnCPUWrite(DAddr addr, u64 size) {
return rasterizer->OnCPUWrite(addr, size);
return renderer->ReadRasterizer()->OnCPUWrite(addr, size);
}
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
void FlushAndInvalidateRegion(DAddr addr, u64 size) {
gpu_thread.FlushAndInvalidateRegion(addr, size);
gpu_thread.FlushAndInvalidateRegion(addr, size, is_async);
}
void RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers,
std::vector<Service::Nvidia::NvFence>&& fences) {
void RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers, std::vector<Service::Nvidia::NvFence>&& fences) {
size_t num_fences{fences.size()};
size_t current_request_counter{};
{
@ -304,7 +239,7 @@ struct GPU::Impl {
}
}
const auto wait_fence = RequestSyncOperation([this, current_request_counter, &layers, &fences, num_fences] {
auto& syncpoint_manager = host1x.GetSyncpointManager();
auto& syncpoint_manager = system.Host1x().GetSyncpointManager();
if (num_fences == 0) {
renderer->Composite(layers);
}
@ -322,7 +257,7 @@ struct GPU::Impl {
syncpoint_manager.RegisterGuestAction(fences[i].id, fences[i].value, executer);
}
});
gpu_thread.TickGPU();
gpu_thread.TickGPU(is_async);
WaitForSyncOperation(wait_fence);
}
@ -331,23 +266,20 @@ struct GPU::Impl {
const auto wait_fence =
RequestSyncOperation([&] { out = renderer->GetAppletCaptureBuffer(); });
gpu_thread.TickGPU();
gpu_thread.TickGPU(is_async);
WaitForSyncOperation(wait_fence);
return out;
}
GPU& gpu;
Core::System& system;
Host1x::Host1x& host1x;
std::unique_ptr<VideoCore::RendererBase> renderer;
VideoCore::RasterizerInterface* rasterizer = nullptr;
const bool use_nvdec;
s32 new_channel_id{1};
/// Shader build notifier
std::unique_ptr<VideoCore::ShaderNotify> shader_notify;
VideoCore::ShaderNotify shader_notify;
/// When true, we are about to shut down emulation session, so terminate outstanding tasks
std::atomic_bool shutting_down{};
@ -371,7 +303,7 @@ struct GPU::Impl {
VideoCommon::GPUThread::ThreadManager gpu_thread;
std::unique_ptr<Core::Frontend::GraphicsContext> cpu_context;
std::unique_ptr<Tegra::Control::Scheduler> scheduler;
Tegra::Control::Scheduler scheduler;
ankerl::unordered_dense::map<s32, std::shared_ptr<Tegra::Control::ChannelState>> channels;
Tegra::Control::ChannelState* current_channel;
s32 bound_channel{-1};
@ -382,7 +314,8 @@ struct GPU::Impl {
};
GPU::GPU(Core::System& system, bool is_async, bool use_nvdec)
: impl{std::make_unique<Impl>(*this, system, is_async, use_nvdec)} {}
: impl{std::make_unique<Impl>(system, is_async, use_nvdec)}
{}
GPU::~GPU() = default;
@ -423,8 +356,9 @@ void GPU::OnCommandListEnd() {
}
u64 GPU::RequestFlush(DAddr addr, std::size_t size) {
return impl->RequestSyncOperation(
[this, addr, size]() { impl->rasterizer->FlushRegion(addr, size); });
return impl->RequestSyncOperation([this, addr, size]() {
impl->renderer->ReadRasterizer()->FlushRegion(addr, size);
});
}
u64 GPU::CurrentSyncRequestFence() const {
@ -441,52 +375,52 @@ void GPU::TickWork() {
/// Gets a mutable reference to the Host1x interface
Host1x::Host1x& GPU::Host1x() {
return impl->host1x;
return impl->system.Host1x();
}
/// Gets an immutable reference to the Host1x interface.
const Host1x::Host1x& GPU::Host1x() const {
return impl->host1x;
return impl->system.Host1x();
}
Engines::Maxwell3D& GPU::Maxwell3D() {
return impl->Maxwell3D();
return impl->current_channel->payload->maxwell_3d;
}
const Engines::Maxwell3D& GPU::Maxwell3D() const {
return impl->Maxwell3D();
return impl->current_channel->payload->maxwell_3d;
}
Engines::KeplerCompute& GPU::KeplerCompute() {
return impl->KeplerCompute();
return impl->current_channel->payload->kepler_compute;
}
const Engines::KeplerCompute& GPU::KeplerCompute() const {
return impl->KeplerCompute();
return impl->current_channel->payload->kepler_compute;
}
Tegra::DmaPusher& GPU::DmaPusher() {
return impl->DmaPusher();
return impl->current_channel->payload->dma_pusher;
}
const Tegra::DmaPusher& GPU::DmaPusher() const {
return impl->DmaPusher();
return impl->current_channel->payload->dma_pusher;
}
VideoCore::RendererBase& GPU::Renderer() {
return impl->Renderer();
return *impl->renderer;
}
const VideoCore::RendererBase& GPU::Renderer() const {
return impl->Renderer();
return *impl->renderer;
}
VideoCore::ShaderNotify& GPU::ShaderNotify() {
return impl->ShaderNotify();
return impl->shader_notify;
}
const VideoCore::ShaderNotify& GPU::ShaderNotify() const {
return impl->ShaderNotify();
return impl->shader_notify;
}
void GPU::RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers,
@ -503,11 +437,11 @@ u64 GPU::GetTicks() const {
}
bool GPU::IsAsync() const {
return impl->IsAsync();
return impl->is_async;
}
bool GPU::UseNvdec() const {
return impl->UseNvdec();
return impl->use_nvdec;
}
void GPU::RendererFrameEndNotify() {

View file

@ -19,8 +19,9 @@
namespace VideoCommon::GPUThread {
ThreadManager::ThreadManager(Core::System& system_, bool is_async_)
: system{system_}, is_async{is_async_} {}
ThreadManager::ThreadManager(Core::System& system_)
: system{system_}
{}
ThreadManager::~ThreadManager() = default;
@ -39,7 +40,7 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten
break;
}
if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) {
scheduler.Push(submit_list->channel, std::move(submit_list->entries));
scheduler.Push(system.GPU(), submit_list->channel, std::move(submit_list->entries));
} else if (std::holds_alternative<GPUTickCommand>(next.data)) {
system.GPU().TickWork();
} else if (const auto* flush = std::get_if<FlushRegionCommand>(&next.data)) {
@ -60,41 +61,40 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten
});
}
void ThreadManager::SubmitList(s32 channel, Tegra::CommandList&& entries) {
PushCommand(SubmitListCommand(channel, std::move(entries)));
void ThreadManager::SubmitList(s32 channel, Tegra::CommandList&& entries, bool is_async) {
PushCommand(SubmitListCommand(channel, std::move(entries)), false, is_async);
}
void ThreadManager::FlushRegion(DAddr addr, u64 size) {
void ThreadManager::FlushRegion(DAddr addr, u64 size, bool is_async) {
if (!is_async) {
// Always flush with synchronous GPU mode
PushCommand(FlushRegionCommand(addr, size));
PushCommand(FlushRegionCommand(addr, size), false, is_async);
}
return;
}
void ThreadManager::TickGPU() {
PushCommand(GPUTickCommand());
void ThreadManager::TickGPU(bool is_async) {
PushCommand(GPUTickCommand(), false, is_async);
}
void ThreadManager::InvalidateRegion(DAddr addr, u64 size) {
rasterizer->OnCacheInvalidation(addr, size);
}
void ThreadManager::FlushAndInvalidateRegion(DAddr addr, u64 size) {
void ThreadManager::FlushAndInvalidateRegion(DAddr addr, u64 size, bool is_async) {
if (Settings::IsGPULevelHigh()) {
if (!is_async) {
PushCommand(FlushRegionCommand(addr, size));
PushCommand(FlushRegionCommand(addr, size), false, is_async);
} else {
auto& gpu = system.GPU();
const u64 fence = gpu.RequestFlush(addr, size);
TickGPU();
TickGPU(is_async);
gpu.WaitForSyncOperation(fence);
}
}
rasterizer->OnCacheInvalidation(addr, size);
}
u64 ThreadManager::PushCommand(CommandData&& command_data, bool block) {
u64 ThreadManager::PushCommand(CommandData&& command_data, bool block, bool is_async) {
if (!is_async) {
// In synchronous GPU mode, block the caller until the command has executed
block = true;

View file

@ -15,6 +15,7 @@
#include "common/bounded_threadsafe_queue.h"
#include "common/polyfill_thread.h"
#include "video_core/dma_pusher.h"
#include "video_core/framebuffer_config.h"
namespace Tegra {
@ -103,7 +104,7 @@ struct SynchState final {
/// Class used to manage the GPU thread
class ThreadManager final {
public:
explicit ThreadManager(Core::System& system_, bool is_async_);
explicit ThreadManager(Core::System& system_);
~ThreadManager();
/// Creates and starts the GPU thread.
@ -111,27 +112,25 @@ public:
Tegra::Control::Scheduler& scheduler);
/// Push GPU command entries to be processed
void SubmitList(s32 channel, Tegra::CommandList&& entries);
void SubmitList(s32 channel, Tegra::CommandList&& entries, bool is_async);
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
void FlushRegion(DAddr addr, u64 size);
void FlushRegion(DAddr addr, u64 size, bool is_async);
/// Notify rasterizer that any caches of the specified region should be invalidated
void InvalidateRegion(DAddr addr, u64 size);
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
void FlushAndInvalidateRegion(DAddr addr, u64 size);
void FlushAndInvalidateRegion(DAddr addr, u64 size, bool is_async);
void TickGPU();
void TickGPU(bool is_async);
private:
/// Pushes a command to be executed by the GPU thread
u64 PushCommand(CommandData&& command_data, bool block = false);
u64 PushCommand(CommandData&& command_data, bool block, bool is_async);
Core::System& system;
const bool is_async;
VideoCore::RasterizerInterface* rasterizer = nullptr;
SynchState state;
std::jthread thread;
};

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
@ -10,26 +10,22 @@
namespace Tegra::Host1x {
Control::Control(Host1x& host1x_) : host1x(host1x_) {}
Control::~Control() = default;
void Control::ProcessMethod(Method method, u32 argument) {
void Control::ProcessMethod(Host1x& host1x, Method method, u32 argument) {
switch (method) {
case Method::LoadSyncptPayload32:
syncpoint_value = argument;
break;
case Method::WaitSyncpt:
case Method::WaitSyncpt32:
Execute(argument);
Execute(host1x, argument);
break;
default:
UNIMPLEMENTED_MSG("Control method {:#X}", static_cast<u32>(method));
UNIMPLEMENTED_MSG("Control method {:#X}", u32(method));
break;
}
}
void Control::Execute(u32 data) {
void Control::Execute(Host1x& host1x, u32 data) {
LOG_TRACE(Service_NVDRV, "Control wait syncpt {} value {}", data, syncpoint_value);
host1x.GetSyncpointManager().WaitHost(data, syncpoint_value);
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
// SPDX-License-Identifier: GPL-3.0-or-later
@ -19,17 +22,11 @@ public:
WaitSyncpt32 = 0x50,
};
explicit Control(Host1x& host1x);
~Control();
/// Writes the method into the state, Invoke Execute() if encountered
void ProcessMethod(Method method, u32 argument);
private:
void ProcessMethod(Host1x& host1x, Method method, u32 argument);
/// For Host1x, execute is waiting on a syncpoint previously written into the state
void Execute(u32 data);
void Execute(Host1x& host1x, u32 data);
Host1x& host1x;
u32 syncpoint_value{};
};

View file

@ -27,22 +27,22 @@ void Host1x::StartDevice(s32 fd, ChannelType type, u32 syncpt) {
#ifdef YUZU_LEGACY
std::call_once(nvdec_first_init, []() {std::this_thread::sleep_for(std::chrono::milliseconds{500});}); // HACK: For Astroneer
#endif
devices[fd] = std::make_unique<Tegra::Host1x::Nvdec>(*this, fd, syncpt);
devices[fd].emplace<Tegra::Host1x::Nvdec>(*this, fd, syncpt);
break;
case ChannelType::VIC:
#ifdef YUZU_LEGACY
std::call_once(vic_first_init, []() {std::this_thread::sleep_for(std::chrono::milliseconds{500});}); // HACK: For Astroneer
#endif
devices[fd] = std::make_unique<Tegra::Host1x::Vic>(*this, fd, syncpt);
devices[fd].emplace<Tegra::Host1x::Vic>(*this, fd, syncpt);
break;
default:
LOG_ERROR(HW_GPU, "Unimplemented host1x device {}", static_cast<u32>(type));
LOG_ERROR(HW_GPU, "Unimplemented host1x device {}", u32(type));
break;
}
}
void Host1x::StopDevice(s32 fd, ChannelType type) {
devices.erase(fd);
devices[fd].emplace<std::monostate>();
}
} // namespace Tegra::Host1x

View file

@ -8,10 +8,14 @@
#include <ankerl/unordered_dense.h>
#include <unordered_map>
#include <queue>
#include <variant>
#include "common/common_types.h"
// fd types?
#include "video_core/host1x/nvdec.h"
#include "video_core/host1x/vic.h"
#include "common/address_space.h"
#include "video_core/cdma_pusher.h"
#include "video_core/host1x/gpu_device_memory_manager.h"
@ -31,118 +35,90 @@ class Nvdec;
class FrameQueue {
public:
struct FrameDevice {
std::deque<std::pair<u64, std::shared_ptr<FFmpeg::Frame>>> m_presentation_order;
std::unordered_map<u64, std::shared_ptr<FFmpeg::Frame>> m_decode_order;
};
void Open(s32 fd) {
std::scoped_lock l{m_mutex};
m_presentation_order.insert({fd, {}});
m_decode_order.insert({fd, {}});
m_frame_devices.insert_or_assign(fd, FrameDevice{});
}
void Close(s32 fd) {
std::scoped_lock l{m_mutex};
m_presentation_order.erase(fd);
m_decode_order.erase(fd);
m_frame_devices.erase(fd);
}
s32 VicFindNvdecFdFromOffset(u64 search_offset) {
std::scoped_lock l{m_mutex};
for (auto& map : m_presentation_order) {
for (auto& [offset, frame] : map.second) {
if (offset == search_offset) {
return map.first;
}
}
}
for (auto& map : m_decode_order) {
for (auto& [offset, frame] : map.second) {
if (offset == search_offset) {
return map.first;
}
}
for (auto const& [fd, dev] : m_frame_devices) {
for (auto const& [offset, frame] : dev.m_presentation_order)
if (offset == search_offset)
return fd;
for (auto const& [offset, frame] : dev.m_decode_order)
if (offset == search_offset)
return fd;
}
return -1;
}
void PushPresentOrder(s32 fd, u64 offset, std::shared_ptr<FFmpeg::Frame>&& frame) {
std::scoped_lock l{m_mutex};
auto map = m_presentation_order.find(fd);
if (map == m_presentation_order.end()) {
return;
if (auto const it = m_frame_devices.find(fd); it != m_frame_devices.end()) {
if (it->second.m_presentation_order.size() >= MAX_PRESENT_QUEUE)
it->second.m_presentation_order.pop_front();
it->second.m_presentation_order.emplace_back(offset, std::move(frame));
}
if (map->second.size() >= MAX_PRESENT_QUEUE) {
map->second.pop_front();
}
map->second.emplace_back(offset, std::move(frame));
}
void PushDecodeOrder(s32 fd, u64 offset, std::shared_ptr<FFmpeg::Frame>&& frame) {
std::scoped_lock l{m_mutex};
auto map = m_decode_order.find(fd);
if (map == m_decode_order.end()) {
return;
if (auto const it = m_frame_devices.find(fd); it != m_frame_devices.end()) {
it->second.m_decode_order.insert_or_assign(offset, std::move(frame));
if (it->second.m_decode_order.size() > MAX_DECODE_MAP) {
auto it2 = it->second.m_decode_order.begin();
std::advance(it2, it->second.m_decode_order.size() - MAX_DECODE_MAP);
it->second.m_decode_order.erase(it->second.m_decode_order.begin(), it2);
}
map->second.insert_or_assign(offset, std::move(frame));
if (map->second.size() > MAX_DECODE_MAP) {
auto it = map->second.begin();
std::advance(it, map->second.size() - MAX_DECODE_MAP);
map->second.erase(map->second.begin(), it);
}
}
std::shared_ptr<FFmpeg::Frame> GetFrame(s32 fd, u64 offset) {
if (fd == -1) {
return {};
}
if (fd != -1) {
std::scoped_lock l{m_mutex};
auto present_map = m_presentation_order.find(fd);
if (present_map != m_presentation_order.end() && !present_map->second.empty()) {
if (auto const it = m_frame_devices.find(fd); it != m_frame_devices.end()) {
if (it->second.m_presentation_order.size() > 0)
return GetPresentOrderLocked(fd);
}
auto decode_map = m_decode_order.find(fd);
if (decode_map != m_decode_order.end() && !decode_map->second.empty()) {
if (it->second.m_decode_order.size() > 0)
return GetDecodeOrderLocked(fd, offset);
}
}
return {};
}
private:
std::shared_ptr<FFmpeg::Frame> GetPresentOrderLocked(s32 fd) {
auto map = m_presentation_order.find(fd);
if (map == m_presentation_order.end() || map->second.empty()) {
return {};
}
auto frame = std::move(map->second.front().second);
map->second.pop_front();
if (auto const it = m_frame_devices.find(fd); it != m_frame_devices.end()) {
auto frame = std::move(it->second.m_presentation_order.front().second);
it->second.m_presentation_order.pop_front();
return frame;
}
return {};
}
std::shared_ptr<FFmpeg::Frame> GetDecodeOrderLocked(s32 fd, u64 offset) {
auto map = m_decode_order.find(fd);
if (map == m_decode_order.end() || map->second.empty()) {
return {};
}
auto it = map->second.find(offset);
if (it == map->second.end()) {
return {};
}
if (auto const it = m_frame_devices.find(fd); it != m_frame_devices.end()) {
if (auto const it2 = it->second.m_decode_order.find(offset); it2 != it->second.m_decode_order.end()) {
// TODO: this "mapped" prevents us from fully embracing ankerl
return std::move(map->second.extract(it).mapped());
return std::move(it->second.m_decode_order.extract(it2).mapped());
}
}
return {};
}
using FramePtr = std::shared_ptr<FFmpeg::Frame>;
std::mutex m_mutex{};
ankerl::unordered_dense::map<s32, std::deque<std::pair<u64, FramePtr>>> m_presentation_order;
ankerl::unordered_dense::map<s32, std::unordered_map<u64, FramePtr>> m_decode_order;
ankerl::unordered_dense::map<s32, FrameDevice> m_frame_devices;
static constexpr size_t MAX_PRESENT_QUEUE = 100;
static constexpr size_t MAX_DECODE_MAP = 200;
@ -196,11 +172,11 @@ public:
void StopDevice(s32 fd, ChannelType type);
void PushEntries(s32 fd, ChCommandHeaderList&& entries) {
auto it = devices.find(fd);
if (it == devices.end()) {
return;
if (auto const nvdec = std::get_if<Tegra::Host1x::Nvdec>(&devices[fd])) {
nvdec->PushEntries(std::move(entries));
} else if (auto const vic = std::get_if<Tegra::Host1x::Vic>(&devices[fd])) {
vic->PushEntries(std::move(entries));
}
it->second->PushEntries(std::move(entries));
}
Core::System& system;
@ -209,7 +185,11 @@ public:
Tegra::MemoryManager gmmu_manager;
Common::FlatAllocator<u32, 0, 32> allocator;
FrameQueue frame_queue;
ankerl::unordered_dense::map<s32, std::unique_ptr<CDmaPusher>> devices;
std::array<std::variant<
std::monostate,
Tegra::Host1x::Nvdec,
Tegra::Host1x::Vic
>, 1024> devices;
#ifdef YUZU_LEGACY
std::once_flag nvdec_first_init;
std::once_flag vic_first_init;

View file

@ -407,15 +407,15 @@ void Vic::ReadInterlacedY8__V8U8_N420(const SlotStruct& slot, std::span<const Pl
};
switch (slot.config.deinterlace_mode) {
case DXVAHD_DEINTERLACE_MODE_PRIVATE::WEAVE:
case DxvhadDeinterlaceModePrivate::Weave:
// Due to the fact that we do not write to memory in nvdec, we cannot use Weave as it
// relies on the previous frame.
DecodeBobField();
break;
case DXVAHD_DEINTERLACE_MODE_PRIVATE::BOB_FIELD:
case DxvhadDeinterlaceModePrivate::BobField:
DecodeBobField();
break;
case DXVAHD_DEINTERLACE_MODE_PRIVATE::DISI1:
case DxvhadDeinterlaceModePrivate::Disi1:
// Due to the fact that we do not write to memory in nvdec, we cannot use DISI1 as it
// relies on previous/next frames.
DecodeBobField();
@ -431,13 +431,13 @@ void Vic::ReadInterlacedY8__V8U8_N420(const SlotStruct& slot, std::span<const Pl
void Vic::ReadY8__V8U8_N420(const SlotStruct& slot, std::span<const PlaneOffsets> offsets, std::shared_ptr<const FFmpeg::Frame> frame, bool planar) noexcept {
switch (slot.config.frame_format) {
case DXVAHD_FRAME_FORMAT::PROGRESSIVE:
case DxvhadFrameFormat::Progressive:
ReadProgressiveY8__V8U8_N420(slot, offsets, std::move(frame), planar, false);
break;
case DXVAHD_FRAME_FORMAT::TOP_FIELD:
case DxvhadFrameFormat::TopField:
ReadInterlacedY8__V8U8_N420(slot, offsets, std::move(frame), planar, true);
break;
case DXVAHD_FRAME_FORMAT::BOTTOM_FIELD:
case DxvhadFrameFormat::BottomField:
ReadInterlacedY8__V8U8_N420(slot, offsets, std::move(frame), planar, false);
break;
default:
@ -860,7 +860,7 @@ void Vic::WriteY8__V8U8_N420(const OutputSurfaceConfig& output_surface_config) n
};
switch (output_surface_config.out_block_kind) {
case BLK_KIND::GENERIC_16Bx2: {
case BlkKind::Generic_16Bx2: {
u32 const block_height = u32(output_surface_config.out_block_height);
auto const out_luma_swizzle_size = Texture::CalculateSize(true, BytesPerPixel, out_luma_width, out_luma_height, 1, block_height, 0);
auto const out_chroma_swizzle_size = Texture::CalculateSize(true, BytesPerPixel * 2, out_chroma_width, out_chroma_height, 1, block_height, 0);
@ -889,7 +889,7 @@ void Vic::WriteY8__V8U8_N420(const OutputSurfaceConfig& output_surface_config) n
Texture::SwizzleTexture(out_chroma, chroma_scratch, BytesPerPixel, out_chroma_width, out_chroma_height, 1, block_height, 0, 1);
}
} break;
case BLK_KIND::PITCH: {
case BlkKind::Pitch: {
LOG_TRACE(HW_GPU, "Writing Y8__V8U8_N420 swizzled frame\n"
"\tinput surface {}x{} stride {} size {:#X}\n"
"\toutput luma {}x{} stride {} size {:#X} block height {} swizzled size 0x{:X}\n",
@ -1032,7 +1032,7 @@ void Vic::WriteABGR(const OutputSurfaceConfig& output_surface_config, VideoPixel
};
switch (output_surface_config.out_block_kind) {
case BLK_KIND::GENERIC_16Bx2: {
case BlkKind::Generic_16Bx2: {
const u32 block_height = u32(output_surface_config.out_block_height);
auto const out_swizzle_size = Texture::CalculateSize(true, BytesPerPixel, out_luma_width, out_luma_height, 1, block_height, 0);
LOG_TRACE(HW_GPU, "Writing ABGR swizzled frame\n"
@ -1051,7 +1051,7 @@ void Vic::WriteABGR(const OutputSurfaceConfig& output_surface_config, VideoPixel
Texture::SwizzleTexture(out_luma, luma_scratch, BytesPerPixel, out_luma_width, out_luma_height, 1, block_height, 0, 1);
}
} break;
case BLK_KIND::PITCH: {
case BlkKind::Pitch: {
LOG_TRACE(HW_GPU, "Writing ABGR pitch frame\n"
"\tinput surface {}x{} stride {} size {:#X}"
"\toutput surface {}x{} stride {} size {:#X}",

View file

@ -6,16 +6,12 @@
#pragma once
#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>
#include "common/common_types.h"
#include "common/scratch_buffer.h"
#include "video_core/cdma_pusher.h"
#include "video_core/host1x/host1x.h"
#include "video_core/host1x/ffmpeg.h"
namespace Tegra::Host1x {
class Host1x;
@ -138,52 +134,53 @@ enum SurfaceIndex : u32 {
CombinedMotion = 7,
};
enum class DXVAHD_ALPHA_FILL_MODE : u32 {
OPAQUE = 0,
BACKGROUND = 1,
DESTINATION = 2,
SOURCE_STREAM = 3,
COMPOSITED = 4,
SOURCE_ALPHA = 5,
// Note: these will inevitably collide with Win32 defines if you use their UPPER_SNAKE_CASE naming
enum class DxvhadAlphaFillMode : u32 {
Opaque = 0,
Background = 1,
Destination = 2,
SourceStream = 3,
Composited = 4,
SourceAlpha = 5,
};
enum class DXVAHD_FRAME_FORMAT : u64 {
PROGRESSIVE = 0,
INTERLACED_TOP_FIELD_FIRST = 1,
INTERLACED_BOTTOM_FIELD_FIRST = 2,
TOP_FIELD = 3,
BOTTOM_FIELD = 4,
SUBPIC_PROGRESSIVE = 5,
SUBPIC_INTERLACED_TOP_FIELD_FIRST = 6,
SUBPIC_INTERLACED_BOTTOM_FIELD_FIRST = 7,
SUBPIC_TOP_FIELD = 8,
SUBPIC_BOTTOM_FIELD = 9,
TOP_FIELD_CHROMA_BOTTOM = 10,
BOTTOM_FIELD_CHROMA_TOP = 11,
SUBPIC_TOP_FIELD_CHROMA_BOTTOM = 12,
SUBPIC_BOTTOM_FIELD_CHROMA_TOP = 13,
enum class DxvhadFrameFormat : u64 {
Progressive = 0,
InterlacedTopFieldFirst = 1,
InterlacedBottomFieldFirst = 2,
TopField = 3,
BottomField = 4,
SubpicProgressive = 5,
SubpicInterlacedTopFieldFirst = 6,
SubpicInterlacedBottomFieldFirst = 7,
SubpicTopField = 8,
SubpicBottomField = 9,
TopFieldChromaBottom = 10,
BottomFieldChromaTop = 11,
SubpicTopFieldChromaBottom = 12,
SubpicBottomFieldChromaTop = 13,
};
enum class DXVAHD_DEINTERLACE_MODE_PRIVATE : u64 {
WEAVE = 0,
BOB_FIELD = 1,
BOB = 2,
NEWBOB = 3,
DISI1 = 4,
WEAVE_LUMA_BOB_FIELD_CHROMA = 5,
MAX = 0xF,
enum class DxvhadDeinterlaceModePrivate : u64 {
Weave = 0,
BobField = 1,
Bob = 2,
Newbob = 3,
Disi1 = 4,
WeaveLumaBobFieldChroma = 5,
Max = 0xF,
};
enum class BLK_KIND {
PITCH = 0,
GENERIC_16Bx2 = 1,
enum class BlkKind {
Pitch = 0,
Generic_16Bx2 = 1,
// These are unsupported in the vic
BL_NAIVE = 2,
BL_KEPLER_XBAR_RAW = 3,
VP2_TILED = 15,
BlNaive = 2,
BlKeplerXbarRaw = 3,
Vp2Tiled = 15,
};
enum class BLEND_SRCFACTC : u32 {
enum class BlendSrcFactC : u32 {
K1 = 0,
K1_TIMES_DST = 1,
NEG_K1_TIMES_DST = 2,
@ -191,7 +188,7 @@ enum class BLEND_SRCFACTC : u32 {
ZERO = 4,
};
enum class BLEND_DSTFACTC : u32 {
enum class BlendDstFactC : u32 {
K1 = 0,
K2 = 1,
K1_TIMES_DST = 2,
@ -201,7 +198,7 @@ enum class BLEND_DSTFACTC : u32 {
ONE = 6,
};
enum class BLEND_SRCFACTA : u32 {
enum class BlendSrcFactA : u32 {
K1 = 0,
K2 = 1,
NEG_K1_TIMES_DST = 2,
@ -209,7 +206,7 @@ enum class BLEND_SRCFACTA : u32 {
MAX = 7,
};
enum class BLEND_DSTFACTA : u32 {
enum class BlendDstFactA : u32 {
K2 = 0,
NEG_K1_TIMES_SRC = 1,
ZERO = 2,
@ -232,7 +229,7 @@ static_assert(sizeof(PipeConfig) == 0x10, "PipeConfig has the wrong size!");
struct OutputConfig {
union {
BitField<0, 3, DXVAHD_ALPHA_FILL_MODE> alpha_fill_mode;
BitField<0, 3, DxvhadAlphaFillMode> alpha_fill_mode;
BitField<3, 3, u64> alpha_fill_slot;
BitField<6, 10, u64> background_a;
BitField<16, 10, u64> background_r;
@ -265,7 +262,7 @@ struct OutputSurfaceConfig {
BitField<0, 7, VideoPixelFormat> out_pixel_format;
BitField<7, 2, u32> out_chroma_loc_horiz;
BitField<9, 2, u32> out_chroma_loc_vert;
BitField<11, 4, BLK_KIND> out_block_kind;
BitField<11, 4, BlkKind> out_block_kind;
BitField<15, 4, u32> out_block_height; // in gobs, log2
BitField<19, 3, u32> reserved0;
BitField<22, 10, u32> reserved1;
@ -365,7 +362,7 @@ struct SlotConfig {
BitField<14, 1, u64> prev_prev_motion_field_enable;
BitField<15, 1, u64> combined_motion_field_enable;
BitField<16, 4, DXVAHD_FRAME_FORMAT> frame_format;
BitField<16, 4, DxvhadFrameFormat> frame_format;
BitField<20, 2, u64> filter_length_y; // 0: 1-tap, 1: 2-tap, 2: 5-tap, 3: 10-tap
BitField<22, 2, u64> filter_length_x;
BitField<24, 12, u64> panoramic;
@ -377,7 +374,7 @@ struct SlotConfig {
BitField<10, 10, u64> filter_detail;
BitField<20, 10, u64> chroma_noise;
BitField<30, 10, u64> chroma_detail;
BitField<40, 4, DXVAHD_DEINTERLACE_MODE_PRIVATE> deinterlace_mode;
BitField<40, 4, DxvhadDeinterlaceModePrivate> deinterlace_mode;
BitField<44, 3, u64> motion_accumulation_weight;
BitField<47, 11, u64> noise_iir;
BitField<58, 4, u64> light_level;
@ -484,13 +481,13 @@ struct BlendingSlotStruct {
BitField<26, 6, u32> reserved1;
};
union {
BitField<0, 3, BLEND_SRCFACTC> src_factor_color_match_select;
BitField<0, 3, BlendSrcFactC> src_factor_color_match_select;
BitField<3, 1, u32> reserved2;
BitField<4, 3, BLEND_DSTFACTC> dst_factor_color_match_select;
BitField<4, 3, BlendDstFactC> dst_factor_color_match_select;
BitField<7, 1, u32> reserved3;
BitField<8, 3, BLEND_SRCFACTA> src_factor_a_match_select;
BitField<8, 3, BlendSrcFactA> src_factor_a_match_select;
BitField<11, 1, u32> reserved4;
BitField<12, 3, BLEND_DSTFACTA> dst_factor_a_match_select;
BitField<12, 3, BlendDstFactA> dst_factor_a_match_select;
BitField<15, 1, u32> reserved5;
BitField<16, 4, u32> reserved6;
BitField<20, 4, u32> reserved7;
@ -624,8 +621,8 @@ private:
VicRegisters regs{};
Common::ScratchBuffer<u8> swizzle_scratch;
Common::ScratchBuffer<Pixel> output_surface;
Common::ScratchBuffer<Pixel> slot_surface;
Common::ScratchBuffer<Tegra::Host1x::Pixel> output_surface;
Common::ScratchBuffer<Tegra::Host1x::Pixel> slot_surface;
Common::ScratchBuffer<u8> luma_scratch;
Common::ScratchBuffer<u8> chroma_scratch;

View file

@ -64,10 +64,10 @@ bool IsTopologySafe(Maxwell3D::Regs::PrimitiveTopology topology) {
} // Anonymous namespace
void HLE_DrawArraysIndirect::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
void HLE_DrawArraysIndirect::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[0]);
if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) {
Fallback(maxwell3d, parameters);
Fallback(system, maxwell3d, parameters);
return;
}
@ -93,7 +93,7 @@ void HLE_DrawArraysIndirect::Execute(Engines::Maxwell3D& maxwell3d, std::span<co
maxwell3d.replace_table.clear();
}
}
void HLE_DrawArraysIndirect::Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters) {
void HLE_DrawArraysIndirect::Fallback(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters) {
SCOPE_EXIT {
if (extended) {
maxwell3d.engine_state = Maxwell3D::EngineHint::None;
@ -123,10 +123,10 @@ void HLE_DrawArraysIndirect::Fallback(Engines::Maxwell3D& maxwell3d, std::span<c
}
}
void HLE_DrawIndexedIndirect::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
void HLE_DrawIndexedIndirect::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[0]);
if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) {
Fallback(maxwell3d, parameters);
Fallback(system, maxwell3d, parameters);
return;
}
@ -161,7 +161,7 @@ void HLE_DrawIndexedIndirect::Execute(Engines::Maxwell3D& maxwell3d, std::span<c
maxwell3d.replace_table.clear();
}
}
void HLE_DrawIndexedIndirect::Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters) {
void HLE_DrawIndexedIndirect::Fallback(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters) {
maxwell3d.RefreshParameters();
const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]);
const u32 element_base = parameters[4];
@ -184,7 +184,7 @@ void HLE_DrawIndexedIndirect::Fallback(Engines::Maxwell3D& maxwell3d, std::span<
maxwell3d.replace_table.clear();
}
}
void HLE_MultiLayerClear::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
void HLE_MultiLayerClear::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
maxwell3d.RefreshParameters();
ASSERT(parameters.size() == 1);
@ -196,27 +196,21 @@ void HLE_MultiLayerClear::Execute(Engines::Maxwell3D& maxwell3d, std::span<const
maxwell3d.regs.clear_surface.raw = clear_params.raw;
maxwell3d.draw_manager.Clear(maxwell3d, num_layers);
}
void HLE_MultiDrawIndexedIndirectCount::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
void HLE_MultiDrawIndexedIndirectCount::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
const auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[2]);
if (!IsTopologySafe(topology)) {
Fallback(maxwell3d, parameters);
return;
}
if (IsTopologySafe(topology)) {
const u32 start_indirect = parameters[0];
const u32 end_indirect = parameters[1];
if (start_indirect >= end_indirect) {
// Nothing to do.
return;
}
const u32 padding = parameters[3]; // padding is in words
// size of each indirect segment
const u32 indirect_words = 5 + padding;
const u32 stride = indirect_words * sizeof(u32);
const std::size_t draw_count = end_indirect - start_indirect;
const u32 estimate = static_cast<u32>(maxwell3d.EstimateIndexBufferSize());
const u32 estimate = u32(maxwell3d.EstimateIndexBufferSize());
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
auto& params = maxwell3d.draw_manager.indirect_state;
params.is_byte_count = false;
@ -235,8 +229,11 @@ void HLE_MultiDrawIndexedIndirectCount::Execute(Engines::Maxwell3D& maxwell3d, s
maxwell3d.draw_manager.DrawIndexedIndirect(maxwell3d, topology, 0, estimate);
maxwell3d.engine_state = Maxwell3D::EngineHint::None;
maxwell3d.replace_table.clear();
} else {
Fallback(system, maxwell3d, parameters);
}
}
void HLE_MultiDrawIndexedIndirectCount::Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters) {
void HLE_MultiDrawIndexedIndirectCount::Fallback(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters) {
SCOPE_EXIT {
// Clean everything.
maxwell3d.regs.vertex_id_base = 0x0;
@ -250,7 +247,7 @@ void HLE_MultiDrawIndexedIndirectCount::Fallback(Engines::Maxwell3D& maxwell3d,
// Nothing to do.
return;
}
const auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[2]);
const auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[2]);
const u32 padding = parameters[3];
const std::size_t max_draws = parameters[4];
const u32 indirect_words = 5 + padding;
@ -265,18 +262,15 @@ void HLE_MultiDrawIndexedIndirectCount::Fallback(Engines::Maxwell3D& maxwell3d,
maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro;
maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex);
maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance);
maxwell3d.CallMethod(0x8e3, 0x648, true);
maxwell3d.CallMethod(0x8e4, static_cast<u32>(index), true);
maxwell3d.CallMethod(system, 0x8e3, 0x648, true);
maxwell3d.CallMethod(system, 0x8e4, u32(index), true);
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
maxwell3d.draw_manager.DrawIndex(maxwell3d, topology, parameters[base + 2], parameters[base], base_vertex, base_instance, parameters[base + 1]);
}
}
void HLE_DrawIndirectByteCount::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
void HLE_DrawIndirectByteCount::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
const bool force = maxwell3d.Rasterizer().HasDrawTransformFeedback();
if (!force) {
Fallback(maxwell3d, parameters);
return;
}
if (force) {
auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[0] & 0xFFFFU);
auto& params = maxwell3d.draw_manager.indirect_state;
params.is_byte_count = true;
@ -291,15 +285,18 @@ void HLE_DrawIndirectByteCount::Execute(Engines::Maxwell3D& maxwell3d, std::span
maxwell3d.regs.draw_auto_stride = parameters[1];
maxwell3d.regs.draw_auto_byte_count = parameters[2];
maxwell3d.draw_manager.DrawArrayIndirect(maxwell3d, topology);
} else {
Fallback(system, maxwell3d, parameters);
}
}
void HLE_DrawIndirectByteCount::Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters) {
void HLE_DrawIndirectByteCount::Fallback(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters) {
maxwell3d.RefreshParameters();
maxwell3d.regs.draw.begin = parameters[0];
maxwell3d.regs.draw_auto_stride = parameters[1];
maxwell3d.regs.draw_auto_byte_count = parameters[2];
maxwell3d.draw_manager.DrawArray(maxwell3d, maxwell3d.regs.draw.topology, 0, maxwell3d.regs.draw_auto_byte_count / maxwell3d.regs.draw_auto_stride, 0, 1);
}
void HLE_C713C83D8F63CCF3::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
void HLE_C713C83D8F63CCF3::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
maxwell3d.RefreshParameters();
const u32 offset = (parameters[0] & 0x3FFFFFFF) << 2;
const u32 address = maxwell3d.regs.shadow_scratch[24];
@ -309,7 +306,7 @@ void HLE_C713C83D8F63CCF3::Execute(Engines::Maxwell3D& maxwell3d, std::span<cons
const_buffer.address_low = address << 8;
const_buffer.offset = offset;
}
void HLE_D7333D26E0A93EDE::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
void HLE_D7333D26E0A93EDE::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
maxwell3d.RefreshParameters();
const size_t index = parameters[0];
const u32 address = maxwell3d.regs.shadow_scratch[42 + index];
@ -319,7 +316,7 @@ void HLE_D7333D26E0A93EDE::Execute(Engines::Maxwell3D& maxwell3d, std::span<cons
const_buffer.address_high = (address >> 24) & 0xFF;
const_buffer.address_low = address << 8;
}
void HLE_BindShader::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
void HLE_BindShader::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
maxwell3d.RefreshParameters();
auto& regs = maxwell3d.regs;
const u32 index = parameters[0];
@ -343,7 +340,7 @@ void HLE_BindShader::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32>
bind_group.raw_config = 0x11;
maxwell3d.ProcessCBBind(bind_group_id);
}
void HLE_SetRasterBoundingBox::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
void HLE_SetRasterBoundingBox::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
maxwell3d.RefreshParameters();
const u32 raster_mode = parameters[0];
auto& regs = maxwell3d.regs;
@ -352,7 +349,7 @@ void HLE_SetRasterBoundingBox::Execute(Engines::Maxwell3D& maxwell3d, std::span<
regs.raster_bounding_box.raw = raster_mode & 0xFFFFF00F;
regs.raster_bounding_box.pad.Assign(scratch_data & raster_enabled);
}
void HLE_ClearConstBuffer::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
void HLE_ClearConstBuffer::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
static constexpr std::array<u32, 0x7000> zeroes{}; //must be bigger than either 7000 or 5F00
maxwell3d.RefreshParameters();
auto& regs = maxwell3d.regs;
@ -362,7 +359,7 @@ void HLE_ClearConstBuffer::Execute(Engines::Maxwell3D& maxwell3d, std::span<cons
regs.const_buffer.offset = 0;
maxwell3d.ProcessCBMultiData(zeroes.data(), parameters[2] * 4);
}
void HLE_ClearMemory::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
void HLE_ClearMemory::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
maxwell3d.RefreshParameters();
const u32 needed_memory = parameters[2] / sizeof(u32);
if (needed_memory > zero_memory.size()) {
@ -373,10 +370,10 @@ void HLE_ClearMemory::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32
regs.upload.line_count = 1;
regs.upload.dest.address_high = parameters[0];
regs.upload.dest.address_low = parameters[1];
maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true);
maxwell3d.CallMultiMethod(size_t(MAXWELL3D_REG_INDEX(inline_data)), zero_memory.data(), needed_memory, needed_memory);
maxwell3d.CallMethod(system, size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true);
maxwell3d.CallMultiMethod(system, size_t(MAXWELL3D_REG_INDEX(inline_data)), zero_memory.data(), needed_memory, needed_memory);
}
void HLE_TransformFeedbackSetup::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
void HLE_TransformFeedbackSetup::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
maxwell3d.RefreshParameters();
auto& regs = maxwell3d.regs;
regs.transform_feedback_enabled = 1;
@ -388,8 +385,8 @@ void HLE_TransformFeedbackSetup::Execute(Engines::Maxwell3D& maxwell3d, std::spa
regs.upload.line_count = 1;
regs.upload.dest.address_high = parameters[0];
regs.upload.dest.address_low = parameters[1];
maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true);
maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(inline_data)), regs.transform_feedback.controls[0].stride, true);
maxwell3d.CallMethod(system, size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true);
maxwell3d.CallMethod(system, size_t(MAXWELL3D_REG_INDEX(inline_data)), regs.transform_feedback.controls[0].stride, true);
maxwell3d.Rasterizer().RegisterTransformFeedback(regs.upload.dest.Address());
}
@ -429,7 +426,7 @@ void HLE_TransformFeedbackSetup::Execute(Engines::Maxwell3D& maxwell3d, std::spa
}
}
void MacroInterpreterImpl::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> params, u32 method) {
void MacroInterpreterImpl::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> params, u32 method) {
Reset();
registers[1] = params[0];
@ -439,7 +436,7 @@ void MacroInterpreterImpl::Execute(Engines::Maxwell3D& maxwell3d, std::span<cons
// Execute the code until we hit an exit condition.
bool keep_executing = true;
while (keep_executing) {
keep_executing = Step(maxwell3d, false);
keep_executing = Step(system, maxwell3d, false);
}
// Assert the the macro used all the input parameters
@ -462,7 +459,7 @@ void MacroInterpreterImpl::Reset() {
/// @brief Executes a single macro instruction located at the current program counter. Returns whether
/// the interpreter should keep running.
/// @param is_delay_slot Whether the current step is being executed due to a delay slot in a previous instruction.
bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slot) {
bool MacroInterpreterImpl::Step(Core::System& system, Engines::Maxwell3D& maxwell3d, bool is_delay_slot) {
u32 base_address = pc;
Macro::Opcode opcode = GetOpcode();
@ -478,11 +475,11 @@ bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slo
switch (opcode.operation) {
case Macro::Operation::ALU: {
u32 result = GetALUResult(opcode.alu_operation, GetRegister(opcode.src_a), GetRegister(opcode.src_b));
ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, result);
ProcessResult(system, maxwell3d, opcode.result_operation, opcode.dst, result);
break;
}
case Macro::Operation::AddImmediate: {
ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, GetRegister(opcode.src_a) + opcode.immediate);
ProcessResult(system, maxwell3d, opcode.result_operation, opcode.dst, GetRegister(opcode.src_a) + opcode.immediate);
break;
}
case Macro::Operation::ExtractInsert: {
@ -492,7 +489,7 @@ bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slo
src = (src >> opcode.bf_src_bit) & opcode.GetBitfieldMask();
dst &= ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit);
dst |= src << opcode.bf_dst_bit;
ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, dst);
ProcessResult(system, maxwell3d, opcode.result_operation, opcode.dst, dst);
break;
}
case Macro::Operation::ExtractShiftLeftImmediate: {
@ -501,7 +498,7 @@ bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slo
u32 result = ((src >> dst) & opcode.GetBitfieldMask()) << opcode.bf_dst_bit;
ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, result);
ProcessResult(system, maxwell3d, opcode.result_operation, opcode.dst, result);
break;
}
case Macro::Operation::ExtractShiftLeftRegister: {
@ -510,12 +507,12 @@ bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slo
u32 result = ((src >> opcode.bf_src_bit) & opcode.GetBitfieldMask()) << dst;
ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, result);
ProcessResult(system, maxwell3d, opcode.result_operation, opcode.dst, result);
break;
}
case Macro::Operation::Read: {
u32 result = Read(maxwell3d, GetRegister(opcode.src_a) + opcode.immediate);
ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, result);
ProcessResult(system, maxwell3d, opcode.result_operation, opcode.dst, result);
break;
}
case Macro::Operation::Branch: {
@ -531,7 +528,7 @@ bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slo
delayed_pc = base_address + opcode.GetBranchTarget();
// Execute one more instruction due to the delay slot.
return Step(maxwell3d, true);
return Step(system, maxwell3d, true);
}
break;
}
@ -544,7 +541,7 @@ bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slo
// cause an exit if it's executed inside a delay slot.
if (opcode.is_exit && !is_delay_slot) {
// Exit has a delay slot, execute the next instruction
Step(maxwell3d, true);
Step(system, maxwell3d, true);
return false;
}
return true;
@ -591,7 +588,7 @@ u32 MacroInterpreterImpl::GetALUResult(Macro::ALUOperation operation, u32 src_a,
}
/// Performs the result operation on the input result and stores it in the specified register (if necessary).
void MacroInterpreterImpl::ProcessResult(Engines::Maxwell3D& maxwell3d, Macro::ResultOperation operation, u32 reg, u32 result) {
void MacroInterpreterImpl::ProcessResult(Core::System& system, Engines::Maxwell3D& maxwell3d, Macro::ResultOperation operation, u32 reg, u32 result) {
switch (operation) {
case Macro::ResultOperation::IgnoreAndFetch:
// Fetch parameter and ignore result.
@ -609,12 +606,12 @@ void MacroInterpreterImpl::ProcessResult(Engines::Maxwell3D& maxwell3d, Macro::R
case Macro::ResultOperation::FetchAndSend:
// Fetch parameter and send result.
SetRegister(reg, FetchParameter());
Send(maxwell3d, result);
Send(system, maxwell3d, result);
break;
case Macro::ResultOperation::MoveAndSend:
// Move and send result.
SetRegister(reg, result);
Send(maxwell3d, result);
Send(system, maxwell3d, result);
break;
case Macro::ResultOperation::FetchAndSetMethod:
// Fetch parameter and use result as Method Address.
@ -625,13 +622,13 @@ void MacroInterpreterImpl::ProcessResult(Engines::Maxwell3D& maxwell3d, Macro::R
// Move result and use as Method Address, then fetch and send parameter.
SetRegister(reg, result);
SetMethodAddress(result);
Send(maxwell3d, FetchParameter());
Send(system, maxwell3d, FetchParameter());
break;
case Macro::ResultOperation::MoveAndSetMethodSend:
// Move result and use as Method Address, then send bits 12:17 of result.
SetRegister(reg, result);
SetMethodAddress(result);
Send(maxwell3d, (result >> 12) & 0b111111);
Send(system, maxwell3d, (result >> 12) & 0b111111);
break;
default:
UNIMPLEMENTED_MSG("Unimplemented result operation {}", operation);
@ -672,8 +669,8 @@ void MacroInterpreterImpl::SetRegister(u32 register_id, u32 value) {
}
/// Calls a GPU Engine method with the input parameter.
void MacroInterpreterImpl::Send(Engines::Maxwell3D& maxwell3d, u32 value) {
maxwell3d.CallMethod(method_address.address, value, true);
void MacroInterpreterImpl::Send(Core::System& system, Engines::Maxwell3D& maxwell3d, u32 value) {
maxwell3d.CallMethod(system, method_address.address, value, true);
// Increment the method address by the method increment.
method_address.address.Assign(method_address.address.Value() + method_address.increment.Value());
}
@ -724,34 +721,35 @@ static const auto default_cg_mode = nullptr; //Allow RWE
#endif
struct MacroJITx64Impl final : public Xbyak::CodeGenerator, public DynamicCachedMacro {
explicit MacroJITx64Impl(std::span<const u32> code_)
explicit MacroJITx64Impl(Core::System& system, std::span<const u32> code_)
: Xbyak::CodeGenerator(MAX_CODE_SIZE, default_cg_mode)
, code{code_}
{
Compile();
Compile(system);
}
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, u32 method) override;
void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, u32 method) override;
void Compile_ALU(Macro::Opcode opcode);
void Compile_AddImmediate(Macro::Opcode opcode);
void Compile_ExtractInsert(Macro::Opcode opcode);
void Compile_ExtractShiftLeftImmediate(Macro::Opcode opcode);
void Compile_ExtractShiftLeftRegister(Macro::Opcode opcode);
void Compile_Read(Macro::Opcode opcode);
void Compile_ALU(Core::System& system, Macro::Opcode opcode);
void Compile_AddImmediate(Core::System& system, Macro::Opcode opcode);
void Compile_ExtractInsert(Core::System& system, Macro::Opcode opcode);
void Compile_ExtractShiftLeftImmediate(Core::System& system, Macro::Opcode opcode);
void Compile_ExtractShiftLeftRegister(Core::System& system, Macro::Opcode opcode);
void Compile_Read(Core::System& system, Macro::Opcode opcode);
void Compile_Branch(Macro::Opcode opcode);
void Optimizer_ScanFlags();
void Compile();
bool Compile_NextInstruction();
void Compile(Core::System& system);
bool Compile_NextInstruction(Core::System& system);
Xbyak::Reg32 Compile_FetchParameter();
Xbyak::Reg32 Compile_GetRegister(u32 index, Xbyak::Reg32 dst);
void Compile_ProcessResult(Macro::ResultOperation operation, u32 reg);
void Compile_Send(Xbyak::Reg32 value);
void Compile_ProcessResult(Core::System& system, Macro::ResultOperation operation, u32 reg);
void Compile_Send(Core::System& system, Xbyak::Reg32 value);
Macro::Opcode GetOpCode() const;
struct JITState {
Engines::Maxwell3D* maxwell3d{};
Engines::Maxwell3D* maxwell3d = nullptr;
Core::System* system = nullptr;
std::array<u32, Macro::NUM_MACRO_REGISTERS> registers{};
u32 carry_flag{};
};
@ -777,15 +775,16 @@ struct MacroJITx64Impl final : public Xbyak::CodeGenerator, public DynamicCached
std::span<const u32> code;
};
void MacroJITx64Impl::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, u32 method) {
void MacroJITx64Impl::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, u32 method) {
ASSERT_OR_EXECUTE(program != nullptr, { return; });
JITState state{};
state.maxwell3d = &maxwell3d;
state.system = &system;
state.registers = {};
program(&state, parameters.data(), parameters.data() + parameters.size());
}
void MacroJITx64Impl::Compile_ALU(Macro::Opcode opcode) {
void MacroJITx64Impl::Compile_ALU(Core::System& system, Macro::Opcode opcode) {
const bool is_a_zero = opcode.src_a == 0;
const bool is_b_zero = opcode.src_b == 0;
const bool valid_operation = !is_a_zero && !is_b_zero;
@ -902,10 +901,10 @@ void MacroJITx64Impl::Compile_ALU(Macro::Opcode opcode) {
UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", opcode.alu_operation.Value());
break;
}
Compile_ProcessResult(opcode.result_operation, opcode.dst);
Compile_ProcessResult(system, opcode.result_operation, opcode.dst);
}
void MacroJITx64Impl::Compile_AddImmediate(Macro::Opcode opcode) {
void MacroJITx64Impl::Compile_AddImmediate(Core::System& system, Macro::Opcode opcode) {
if (optimizer.skip_dummy_addimmediate) {
// Games tend to use this as an exit instruction placeholder. It's to encode an instruction
// without doing anything. In our case we can just not emit anything.
@ -940,10 +939,10 @@ void MacroJITx64Impl::Compile_AddImmediate(Macro::Opcode opcode) {
sub(result, opcode.immediate * -1);
}
}
Compile_ProcessResult(opcode.result_operation, opcode.dst);
Compile_ProcessResult(system, opcode.result_operation, opcode.dst);
}
void MacroJITx64Impl::Compile_ExtractInsert(Macro::Opcode opcode) {
void MacroJITx64Impl::Compile_ExtractInsert(Core::System& system, Macro::Opcode opcode) {
auto dst = Compile_GetRegister(opcode.src_a, RESULT);
auto src = Compile_GetRegister(opcode.src_b, eax);
@ -954,10 +953,10 @@ void MacroJITx64Impl::Compile_ExtractInsert(Macro::Opcode opcode) {
shl(src, opcode.bf_dst_bit);
or_(dst, src);
Compile_ProcessResult(opcode.result_operation, opcode.dst);
Compile_ProcessResult(system, opcode.result_operation, opcode.dst);
}
void MacroJITx64Impl::Compile_ExtractShiftLeftImmediate(Macro::Opcode opcode) {
void MacroJITx64Impl::Compile_ExtractShiftLeftImmediate(Core::System& system, Macro::Opcode opcode) {
const auto dst = Compile_GetRegister(opcode.src_a, ecx);
const auto src = Compile_GetRegister(opcode.src_b, RESULT);
@ -965,10 +964,10 @@ void MacroJITx64Impl::Compile_ExtractShiftLeftImmediate(Macro::Opcode opcode) {
and_(src, opcode.GetBitfieldMask());
shl(src, opcode.bf_dst_bit);
Compile_ProcessResult(opcode.result_operation, opcode.dst);
Compile_ProcessResult(system, opcode.result_operation, opcode.dst);
}
void MacroJITx64Impl::Compile_ExtractShiftLeftRegister(Macro::Opcode opcode) {
void MacroJITx64Impl::Compile_ExtractShiftLeftRegister(Core::System& system, Macro::Opcode opcode) {
const auto dst = Compile_GetRegister(opcode.src_a, ecx);
const auto src = Compile_GetRegister(opcode.src_b, RESULT);
@ -976,10 +975,10 @@ void MacroJITx64Impl::Compile_ExtractShiftLeftRegister(Macro::Opcode opcode) {
and_(src, opcode.GetBitfieldMask());
shl(src, dst.cvt8());
Compile_ProcessResult(opcode.result_operation, opcode.dst);
Compile_ProcessResult(system, opcode.result_operation, opcode.dst);
}
void MacroJITx64Impl::Compile_Read(Macro::Opcode opcode) {
void MacroJITx64Impl::Compile_Read(Core::System& system, Macro::Opcode opcode) {
if (optimizer.zero_reg_skip && opcode.src_a == 0) {
if (opcode.immediate == 0) {
xor_(RESULT, RESULT);
@ -1005,23 +1004,21 @@ void MacroJITx64Impl::Compile_Read(Macro::Opcode opcode) {
int3();
L(pass_range_check);
}
mov(rax, qword[STATE]);
mov(RESULT,
dword[rax + offsetof(Engines::Maxwell3D, regs) +
offsetof(Engines::Maxwell3D::Regs, reg_array) + RESULT.cvt64() * sizeof(u32)]);
Compile_ProcessResult(opcode.result_operation, opcode.dst);
mov(rax, qword[STATE + offsetof(JITState, maxwell3d)]);
mov(RESULT, dword[rax + offsetof(Engines::Maxwell3D, regs) + offsetof(Engines::Maxwell3D::Regs, reg_array) + RESULT.cvt64() * sizeof(u32)]);
Compile_ProcessResult(system, opcode.result_operation, opcode.dst);
}
static void MacroJIT_SendThunk(Engines::Maxwell3D* maxwell3d, Macro::MethodAddress method_address, u32 value) {
maxwell3d->CallMethod(method_address.address, value, true);
static void MacroJIT_SendThunk(Core::System* system, Engines::Maxwell3D* maxwell3d, Macro::MethodAddress method_address, u32 value) {
maxwell3d->CallMethod(*system, method_address.address, value, true);
}
void MacroJITx64Impl::Compile_Send(Xbyak::Reg32 value) {
void MacroJITx64Impl::Compile_Send(Core::System& system, Xbyak::Reg32 value) {
Common::X64::ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
mov(Common::X64::ABI_PARAM1, qword[STATE]);
mov(Common::X64::ABI_PARAM2.cvt32(), METHOD_ADDRESS);
mov(Common::X64::ABI_PARAM3.cvt32(), value);
mov(Common::X64::ABI_PARAM1, qword[STATE + offsetof(JITState, system)]);
mov(Common::X64::ABI_PARAM2, qword[STATE + offsetof(JITState, maxwell3d)]);
mov(Common::X64::ABI_PARAM3.cvt32(), METHOD_ADDRESS);
mov(Common::X64::ABI_PARAM4.cvt32(), value);
Common::X64::CallFarFunction(*this, &MacroJIT_SendThunk);
Common::X64::ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
@ -1045,9 +1042,8 @@ void MacroJITx64Impl::Compile_Send(Xbyak::Reg32 value) {
}
void MacroJITx64Impl::Compile_Branch(Macro::Opcode opcode) {
ASSERT_MSG(!is_delay_slot, "Executing a branch in a delay slot is not valid");
const s32 jump_address =
static_cast<s32>(pc) + static_cast<s32>(opcode.GetBranchTarget() / sizeof(s32));
ASSERT(!is_delay_slot && "Executing a branch in a delay slot is not valid");
const s32 jump_address = s32(pc) + s32(opcode.GetBranchTarget() / sizeof(s32));
Xbyak::Label end;
auto value = Compile_GetRegister(opcode.src_a, eax);
@ -1116,7 +1112,7 @@ void MacroJITx64Impl::Optimizer_ScanFlags() {
}
}
void MacroJITx64Impl::Compile() {
void MacroJITx64Impl::Compile(Core::System& system) {
labels.fill(Xbyak::Label());
Common::X64::ABI_PushRegistersAndAdjustStack(*this, Common::X64::ABI_ALL_CALLEE_SAVED, 8);
@ -1156,7 +1152,7 @@ void MacroJITx64Impl::Compile() {
next_opcode = {};
}
pc = i;
Compile_NextInstruction();
Compile_NextInstruction(system);
}
L(end_of_code);
@ -1167,7 +1163,7 @@ void MacroJITx64Impl::Compile() {
program = getCode<ProgramType>();
}
bool MacroJITx64Impl::Compile_NextInstruction() {
bool MacroJITx64Impl::Compile_NextInstruction(Core::System& system) {
const auto opcode = GetOpCode();
if (labels[pc].getAddress()) {
return false;
@ -1177,22 +1173,22 @@ bool MacroJITx64Impl::Compile_NextInstruction() {
switch (opcode.operation) {
case Macro::Operation::ALU:
Compile_ALU(opcode);
Compile_ALU(system, opcode);
break;
case Macro::Operation::AddImmediate:
Compile_AddImmediate(opcode);
Compile_AddImmediate(system, opcode);
break;
case Macro::Operation::ExtractInsert:
Compile_ExtractInsert(opcode);
Compile_ExtractInsert(system, opcode);
break;
case Macro::Operation::ExtractShiftLeftImmediate:
Compile_ExtractShiftLeftImmediate(opcode);
Compile_ExtractShiftLeftImmediate(system, opcode);
break;
case Macro::Operation::ExtractShiftLeftRegister:
Compile_ExtractShiftLeftRegister(opcode);
Compile_ExtractShiftLeftRegister(system, opcode);
break;
case Macro::Operation::Read:
Compile_Read(opcode);
Compile_Read(system, opcode);
break;
case Macro::Operation::Branch:
Compile_Branch(opcode);
@ -1264,7 +1260,7 @@ Xbyak::Reg32 MacroJITx64Impl::Compile_GetRegister(u32 index, Xbyak::Reg32 dst) {
return dst;
}
void MacroJITx64Impl::Compile_ProcessResult(Macro::ResultOperation operation, u32 reg) {
void MacroJITx64Impl::Compile_ProcessResult(Core::System& system, Macro::ResultOperation operation, u32 reg) {
const auto SetRegister = [this](u32 reg_index, const Xbyak::Reg32& result) {
// Register 0 is supposed to always return 0. NOP is implemented as a store to the zero
// register.
@ -1289,12 +1285,12 @@ void MacroJITx64Impl::Compile_ProcessResult(Macro::ResultOperation operation, u3
case Macro::ResultOperation::FetchAndSend:
// Fetch parameter and send result.
SetRegister(reg, Compile_FetchParameter());
Compile_Send(RESULT);
Compile_Send(system, RESULT);
break;
case Macro::ResultOperation::MoveAndSend:
// Move and send result.
SetRegister(reg, RESULT);
Compile_Send(RESULT);
Compile_Send(system, RESULT);
break;
case Macro::ResultOperation::FetchAndSetMethod:
// Fetch parameter and use result as Method Address.
@ -1305,7 +1301,7 @@ void MacroJITx64Impl::Compile_ProcessResult(Macro::ResultOperation operation, u3
// Move result and use as Method Address, then fetch and send parameter.
SetRegister(reg, RESULT);
SetMethodAddress(RESULT);
Compile_Send(Compile_FetchParameter());
Compile_Send(system, Compile_FetchParameter());
break;
case Macro::ResultOperation::MoveAndSetMethodSend:
// Move result and use as Method Address, then send bits 12:17 of result.
@ -1313,7 +1309,7 @@ void MacroJITx64Impl::Compile_ProcessResult(Macro::ResultOperation operation, u3
SetMethodAddress(RESULT);
shr(RESULT, 12);
and_(RESULT, 0b111111);
Compile_Send(RESULT);
Compile_Send(system, RESULT);
break;
default:
UNIMPLEMENTED_MSG("Unimplemented macro operation {}", operation);
@ -1354,36 +1350,36 @@ static void Dump(u64 hash, std::span<const u32> code, bool decompiled = false) {
macro_file.write(reinterpret_cast<const char*>(code.data()), code.size_bytes());
}
void MacroEngine::Execute(Engines::Maxwell3D& maxwell3d, u32 method, std::span<const u32> parameters) {
auto const execute_variant = [&maxwell3d, &parameters, method](AnyCachedMacro& acm) {
void MacroEngine::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, u32 method, std::span<const u32> parameters) {
auto const execute_variant = [&system, &maxwell3d, &parameters, method](AnyCachedMacro& acm) {
if (auto a = std::get_if<HLE_DrawArraysIndirect>(&acm))
a->Execute(maxwell3d, parameters, method);
a->Execute(system, maxwell3d, parameters, method);
if (auto a = std::get_if<HLE_DrawIndexedIndirect>(&acm))
a->Execute(maxwell3d, parameters, method);
a->Execute(system, maxwell3d, parameters, method);
if (auto a = std::get_if<HLE_MultiDrawIndexedIndirectCount>(&acm))
a->Execute(maxwell3d, parameters, method);
a->Execute(system, maxwell3d, parameters, method);
if (auto a = std::get_if<HLE_MultiLayerClear>(&acm))
a->Execute(maxwell3d, parameters, method);
a->Execute(system, maxwell3d, parameters, method);
if (auto a = std::get_if<HLE_C713C83D8F63CCF3>(&acm))
a->Execute(maxwell3d, parameters, method);
a->Execute(system, maxwell3d, parameters, method);
if (auto a = std::get_if<HLE_D7333D26E0A93EDE>(&acm))
a->Execute(maxwell3d, parameters, method);
a->Execute(system, maxwell3d, parameters, method);
if (auto a = std::get_if<HLE_BindShader>(&acm))
a->Execute(maxwell3d, parameters, method);
a->Execute(system, maxwell3d, parameters, method);
if (auto a = std::get_if<HLE_SetRasterBoundingBox>(&acm))
a->Execute(maxwell3d, parameters, method);
a->Execute(system, maxwell3d, parameters, method);
if (auto a = std::get_if<HLE_ClearConstBuffer>(&acm))
a->Execute(maxwell3d, parameters, method);
a->Execute(system, maxwell3d, parameters, method);
if (auto a = std::get_if<HLE_ClearMemory>(&acm))
a->Execute(maxwell3d, parameters, method);
a->Execute(system, maxwell3d, parameters, method);
if (auto a = std::get_if<HLE_TransformFeedbackSetup>(&acm))
a->Execute(maxwell3d, parameters, method);
a->Execute(system, maxwell3d, parameters, method);
if (auto a = std::get_if<HLE_DrawIndirectByteCount>(&acm))
a->Execute(maxwell3d, parameters, method);
a->Execute(system, maxwell3d, parameters, method);
if (auto a = std::get_if<MacroInterpreterImpl>(&acm))
a->Execute(maxwell3d, parameters, method);
a->Execute(system, maxwell3d, parameters, method);
if (auto a = std::get_if<std::unique_ptr<DynamicCachedMacro>>(&acm))
a->get()->Execute(maxwell3d, parameters, method);
a->get()->Execute(system, maxwell3d, parameters, method);
};
if (auto const it = macro_cache.find(method); it != macro_cache.end()) {
auto& ci = it->second;
@ -1414,9 +1410,9 @@ void MacroEngine::Execute(Engines::Maxwell3D& maxwell3d, u32 method, std::span<c
code.resize(macro_cached.size() - rebased_method);
std::memcpy(code.data(), macro_cached.data() + rebased_method, code.size() * sizeof(u32));
ci.hash = Common::HashValue(code);
ci.program = Compile(maxwell3d, code);
ci.program = Compile(system, maxwell3d, code);
} else {
ci.program = Compile(maxwell3d, macro_code->second);
ci.program = Compile(system, maxwell3d, macro_code->second);
ci.hash = Common::HashValue(macro_code->second);
}
if (CanBeHLEProgram(ci.hash) && !Settings::values.disable_macro_hle) {
@ -1431,10 +1427,10 @@ void MacroEngine::Execute(Engines::Maxwell3D& maxwell3d, u32 method, std::span<c
}
}
AnyCachedMacro MacroEngine::Compile(Engines::Maxwell3D& maxwell3d, std::span<const u32> code) {
AnyCachedMacro MacroEngine::Compile(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> code) {
#ifdef ARCHITECTURE_x86_64
if (!is_interpreted)
return std::make_unique<MacroJITx64Impl>(code);
return std::make_unique<MacroJITx64Impl>(system, code);
#endif
return MacroInterpreterImpl(code);
}

View file

@ -14,6 +14,10 @@
#include "common/bit_field.h"
#include "common/common_types.h"
namespace Core {
class System;
}
namespace Tegra {
namespace Engines {
@ -106,61 +110,61 @@ struct HLEMacro {
/// also assigning the base vertex/instance.
struct HLE_DrawArraysIndirect final {
HLE_DrawArraysIndirect(bool extended_) noexcept : extended{extended_} {}
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Fallback(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
bool extended;
};
/// @note: these macros have two versions, a normal and extended version, with the extended version
/// also assigning the base vertex/instance.
struct HLE_DrawIndexedIndirect final {
explicit HLE_DrawIndexedIndirect(bool extended_) noexcept : extended{extended_} {}
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Fallback(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
bool extended;
};
struct HLE_MultiLayerClear final {
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
};
struct HLE_MultiDrawIndexedIndirectCount final {
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Fallback(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
};
struct HLE_DrawIndirectByteCount final {
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Fallback(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
};
struct HLE_C713C83D8F63CCF3 final {
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
};
struct HLE_D7333D26E0A93EDE final {
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
};
struct HLE_BindShader final {
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
};
struct HLE_SetRasterBoundingBox final {
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
};
struct HLE_ClearConstBuffer final {
HLE_ClearConstBuffer(size_t base_size_) noexcept : base_size{base_size_} {}
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
size_t base_size;
};
struct HLE_ClearMemory final {
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
std::vector<u32> zero_memory;
};
struct HLE_TransformFeedbackSetup final {
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
};
struct MacroInterpreterImpl final {
MacroInterpreterImpl() {}
MacroInterpreterImpl(std::span<const u32> code_) : code{code_} {}
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> params, u32 method);
void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> params, u32 method);
void Reset();
bool Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slot);
bool Step(Core::System& system, Engines::Maxwell3D& maxwell3d, bool is_delay_slot);
u32 GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b);
void ProcessResult(Engines::Maxwell3D& maxwell3d, Macro::ResultOperation operation, u32 reg, u32 result);
void ProcessResult(Core::System& system, Engines::Maxwell3D& maxwell3d, Macro::ResultOperation operation, u32 reg, u32 result);
bool EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const;
Macro::Opcode GetOpcode() const;
u32 GetRegister(u32 register_id) const;
@ -169,7 +173,7 @@ struct MacroInterpreterImpl final {
[[nodiscard]] inline void SetMethodAddress(u32 address) noexcept {
method_address.raw = address;
}
void Send(Engines::Maxwell3D& maxwell3d, u32 value);
void Send(Core::System& system, Engines::Maxwell3D& maxwell3d, u32 value);
u32 Read(Engines::Maxwell3D& maxwell3d, u32 method) const;
u32 FetchParameter();
/// General purpose macro registers.
@ -192,7 +196,7 @@ struct DynamicCachedMacro {
/// Executes the macro code with the specified input parameters.
/// @param parameters The parameters of the macro
/// @param method The method to execute
virtual void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, u32 method) = 0;
virtual void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, u32 method) = 0;
};
using AnyCachedMacro = std::variant<
@ -227,8 +231,8 @@ struct MacroEngine {
uploaded_macro_code.erase(method);
}
// Compiles the macro if its not in the cache, and executes the compiled macro
void Execute(Engines::Maxwell3D& maxwell3d, u32 method, std::span<const u32> parameters);
AnyCachedMacro Compile(Engines::Maxwell3D& maxwell3d, std::span<const u32> code);
void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, u32 method, std::span<const u32> parameters);
AnyCachedMacro Compile(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span<const u32> code);
struct CacheInfo {
AnyCachedMacro program;
u64 hash{};

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -204,7 +207,7 @@ void SetupDirtyMisc(Tables& tables) {
} // Anonymous namespace
void StateTracker::SetupTables(Tegra::Control::ChannelState& channel_state) {
auto& tables{channel_state.maxwell_3d->dirty.tables};
auto& tables{channel_state.payload->maxwell_3d.dirty.tables};
SetupDirtyFlags(tables);
SetupDirtyColorMasks(tables);
SetupDirtyViewports(tables);
@ -231,7 +234,7 @@ void StateTracker::SetupTables(Tegra::Control::ChannelState& channel_state) {
}
void StateTracker::ChangeChannel(Tegra::Control::ChannelState& channel_state) {
flags = &channel_state.maxwell_3d->dirty.flags;
flags = &channel_state.payload->maxwell_3d.dirty.flags;
}
void StateTracker::InvalidateState() {

View file

@ -126,16 +126,14 @@ public:
current_query = nullptr;
amend_value = 0;
accumulation_value = 0;
queries_prefix_scan_pass = std::make_unique<QueriesPrefixScanPass>(
device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
queries_prefix_scan_pass.emplace(device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
const VkBufferCreateInfo buffer_ci = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.size = 8,
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
@ -592,8 +590,7 @@ private:
VideoCommon::HostQueryBase* current_query;
bool has_started{};
std::mutex flush_guard;
std::unique_ptr<QueriesPrefixScanPass> queries_prefix_scan_pass;
std::optional<QueriesPrefixScanPass> queries_prefix_scan_pass;
};
// Transform feedback queries
@ -1266,41 +1263,23 @@ private:
} // namespace
struct QueryCacheRuntimeImpl {
QueryCacheRuntimeImpl(QueryCacheRuntime& runtime, VideoCore::RasterizerInterface* rasterizer_,
Tegra::MaxwellDeviceMemoryManager& device_memory_,
Vulkan::BufferCache& buffer_cache_, const Device& device_,
const MemoryAllocator& memory_allocator_, Scheduler& scheduler_,
StagingBufferPool& staging_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue,
DescriptorPool& descriptor_pool, TextureCache& texture_cache_)
: rasterizer{rasterizer_}, device_memory{device_memory_}, buffer_cache{buffer_cache_},
device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
staging_pool{staging_pool_}, guest_streamer(0, runtime),
sample_streamer(static_cast<size_t>(QueryType::ZPassPixelCount64), runtime, rasterizer,
texture_cache_, device, scheduler, memory_allocator,
compute_pass_descriptor_queue, descriptor_pool),
tfb_streamer(static_cast<size_t>(QueryType::StreamingByteCount), runtime, device,
scheduler, memory_allocator, staging_pool),
primitives_succeeded_streamer(
static_cast<size_t>(QueryType::StreamingPrimitivesSucceeded), runtime, tfb_streamer,
device_memory_),
primitives_needed_minus_succeeded_streamer(
static_cast<size_t>(QueryType::StreamingPrimitivesNeededMinusSucceeded), runtime, 0u),
hcr_setup{}, hcr_is_set{}, is_hcr_running{}, maxwell3d{} {
QueryCacheRuntimeImpl(QueryCacheRuntime& runtime, VideoCore::RasterizerInterface* rasterizer_, Tegra::MaxwellDeviceMemoryManager& device_memory_, Vulkan::BufferCache& buffer_cache_, const Device& device_, const MemoryAllocator& memory_allocator_, Scheduler& scheduler_, StagingBufferPool& staging_pool_, ComputePassDescriptorQueue& compute_pass_descriptor_queue, DescriptorPool& descriptor_pool, TextureCache& texture_cache_)
: rasterizer{rasterizer_}, device_memory{device_memory_}, buffer_cache{buffer_cache_}
, device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_}
, staging_pool{staging_pool_}, guest_streamer(0, runtime)
, sample_streamer(size_t(QueryType::ZPassPixelCount64), runtime, rasterizer, texture_cache_, device, scheduler, memory_allocator, compute_pass_descriptor_queue, descriptor_pool)
, tfb_streamer(size_t(QueryType::StreamingByteCount), runtime, device, scheduler, memory_allocator, staging_pool)
, primitives_succeeded_streamer(size_t(QueryType::StreamingPrimitivesSucceeded), runtime, tfb_streamer, device_memory_)
, primitives_needed_minus_succeeded_streamer(size_t(QueryType::StreamingPrimitivesNeededMinusSucceeded), runtime, 0u)
, hcr_setup{}, hcr_is_set{}, is_hcr_running{}, maxwell3d{} {
hcr_setup.sType = VK_STRUCTURE_TYPE_CONDITIONAL_RENDERING_BEGIN_INFO_EXT;
hcr_setup.pNext = nullptr;
hcr_setup.flags = 0;
const bool has_conditional_rendering = device.IsExtConditionalRendering();
if (has_conditional_rendering) {
conditional_resolve_pass = std::make_unique<ConditionalRenderingResolvePass>(
device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
}
VkBufferUsageFlags hcr_buffer_usage =
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
if (has_conditional_rendering) {
VkBufferUsageFlags hcr_buffer_usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
if (device.IsExtConditionalRendering()) {
conditional_resolve_pass.emplace(device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
hcr_buffer_usage |= VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT;
}
@ -1339,7 +1318,7 @@ struct QueryCacheRuntimeImpl {
std::vector<std::vector<VkBufferCopy>> copies_setup;
// Host conditional rendering data
std::unique_ptr<ConditionalRenderingResolvePass> conditional_resolve_pass;
std::optional<ConditionalRenderingResolvePass> conditional_resolve_pass;
vk::Buffer hcr_resolve_buffer;
VkConditionalRenderingBeginInfoEXT hcr_setup;
VkBuffer hcr_buffer;
@ -1351,13 +1330,7 @@ struct QueryCacheRuntimeImpl {
Maxwell3D* maxwell3d;
};
QueryCacheRuntime::QueryCacheRuntime(VideoCore::RasterizerInterface* rasterizer,
Tegra::MaxwellDeviceMemoryManager& device_memory_,
Vulkan::BufferCache& buffer_cache_, const Device& device_,
const MemoryAllocator& memory_allocator_,
Scheduler& scheduler_, StagingBufferPool& staging_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue,
DescriptorPool& descriptor_pool, TextureCache& texture_cache_) {
QueryCacheRuntime::QueryCacheRuntime(VideoCore::RasterizerInterface* rasterizer, Tegra::MaxwellDeviceMemoryManager& device_memory_, Vulkan::BufferCache& buffer_cache_, const Device& device_, const MemoryAllocator& memory_allocator_, Scheduler& scheduler_, StagingBufferPool& staging_pool_, ComputePassDescriptorQueue& compute_pass_descriptor_queue, DescriptorPool& descriptor_pool, TextureCache& texture_cache_) {
impl = std::make_unique<QueryCacheRuntimeImpl>(
*this, rasterizer, device_memory_, buffer_cache_, device_, memory_allocator_, scheduler_,
staging_pool_, compute_pass_descriptor_queue, descriptor_pool, texture_cache_);
@ -1686,8 +1659,7 @@ void QueryCacheRuntime::SyncValues(std::span<SyncValuesType> values, VkBuffer ba
}
impl->scheduler.RequestOutsideRenderPassOperationContext();
impl->scheduler.Record([src_buffer, dst_buffers = std::move(impl->buffers_to_upload_to),
vk_copies = std::move(impl->copies_setup)](vk::CommandBuffer cmdbuf) {
impl->scheduler.Record([src_buffer, dst_buffers = std::move(impl->buffers_to_upload_to), vk_copies = std::move(impl->copies_setup)](vk::CommandBuffer cmdbuf) {
size_t size = dst_buffers.size();
for (size_t i = 0; i < size; i++) {
cmdbuf.CopyBuffer(src_buffer, dst_buffers[i].first, vk_copies[i]);

View file

@ -235,7 +235,7 @@ void SetupRasterModes(Maxwell3D::DirtyState::Tables &tables) {
} // Anonymous namespace
void StateTracker::SetupTables(Tegra::Control::ChannelState& channel_state) {
auto& tables{channel_state.maxwell_3d->dirty.tables};
auto& tables{channel_state.payload->maxwell_3d.dirty.tables};
SetupDirtyFlags(tables);
SetupDirtyViewports(tables);
SetupDirtyScissors(tables);
@ -258,7 +258,7 @@ void StateTracker::SetupTables(Tegra::Control::ChannelState& channel_state) {
}
void StateTracker::ChangeChannel(Tegra::Control::ChannelState& channel_state) {
flags = &channel_state.maxwell_3d->dirty.flags;
flags = &channel_state.payload->maxwell_3d.dirty.flags;
}
void StateTracker::InvalidateState() {

View file

@ -1,10 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <variant>
#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
@ -12,20 +15,15 @@ namespace Vulkan {
class Device;
class Scheduler;
struct DescriptorUpdateEntry {
struct Empty {};
union DescriptorUpdateEntry {
DescriptorUpdateEntry() = default;
DescriptorUpdateEntry(VkDescriptorImageInfo image_) : image{image_} {}
DescriptorUpdateEntry(VkDescriptorBufferInfo buffer_) : buffer{buffer_} {}
DescriptorUpdateEntry(VkBufferView texel_buffer_) : texel_buffer{texel_buffer_} {}
union {
Empty empty{};
std::monostate empty{};
VkDescriptorImageInfo image;
VkDescriptorBufferInfo buffer;
VkBufferView texel_buffer;
};
};
class UpdateDescriptorQueue final {

View file

@ -45,23 +45,23 @@ std::unique_ptr<VideoCore::RendererBase> CreateRenderer(Core::System& system, Co
namespace VideoCore {
std::unique_ptr<Tegra::GPU> CreateGPU(Core::Frontend::EmuWindow& emu_window, Core::System& system) {
/// @brief Creates an emulated GPU instance using the given system context.
void CreateGPU(std::optional<Tegra::GPU>& gpu, Core::Frontend::EmuWindow& emu_window, Core::System& system) {
Settings::UpdateRescalingInfo();
const auto nvdec_value = Settings::values.nvdec_emulation.GetValue();
const bool use_nvdec = nvdec_value != Settings::NvdecEmulation::Off;
const bool use_async = Settings::values.use_asynchronous_gpu_emulation.GetValue();
auto gpu = std::make_unique<Tegra::GPU>(system, use_async, use_nvdec);
gpu.emplace(system, use_async, use_nvdec);
auto context = emu_window.CreateSharedContext();
auto scope = context->Acquire();
try {
auto renderer = CreateRenderer(system, emu_window, *gpu, std::move(context));
gpu->BindRenderer(std::move(renderer));
return gpu;
} catch (const std::runtime_error& exception) {
scope.Cancel();
LOG_ERROR(HW_GPU, "Failed to initialize GPU: {}", exception.what());
return nullptr;
gpu.reset();
}
}

View file

@ -1,9 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <optional>
namespace Core {
class System;
@ -20,8 +24,6 @@ class GPU;
namespace VideoCore {
class RendererBase;
/// Creates an emulated GPU instance using the given system context.
std::unique_ptr<Tegra::GPU> CreateGPU(Core::Frontend::EmuWindow& emu_window, Core::System& system);
void CreateGPU(std::optional<Tegra::GPU>& gpu, Core::Frontend::EmuWindow& emu_window, Core::System& system);
} // namespace VideoCore