mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 07:00:30 +02:00
Implement GX Thread for Asynchronous Rendering
- Introduced a new GX thread to handle the rendering pipeline, allowing the game thread to post commands without blocking. - Added `gx_thread.h` and `gx_thread.cpp` to manage the command ring buffer and thread synchronization. - Updated `vi.cpp` to utilize the GX thread for rendering tasks, improving frame pacing and responsiveness. - Modified `main.cpp` to configure and start the GX thread, ensuring it integrates with the existing rendering workflow. - Enhanced `openxr_integration.cpp` to register the GX thread with OpenXR for better performance in VR scenarios. - Refactored `settings_overlay.cpp` to remove unnecessary waits for the frame worker, as the overlay now draws directly into the game thread's ImGui frame. - Improved error handling and logging in the GX thread to capture exceptions during command execution.
This commit is contained in:
1 parent
ffa63fa60a
commit
6a1641e0b7
33 files changed
+1532
-527
No files matched your search
@@ -3,6 +3,7 @@
|
||||
#include "settings_overlay.h"
|
||||
#include "runtime_config.h"
|
||||
#include "fiber_manager.h"
|
||||
#include "gx_thread.h"
|
||||
|
||||
#include <aurora/aurora.h>
|
||||
#include <aurora/event.h>
|
||||
@@ -142,7 +143,11 @@ inline bool BeginAuroraFrame() {
|
||||
if (!aurora_begin_frame()) {
|
||||
return false;
|
||||
}
|
||||
ApplyPendingMkwDynamicAspectSurface();
|
||||
// The viewport policy writes guest memory (EGG screen records), so the GX
|
||||
// thread leaves it to the game thread's present path.
|
||||
if (!GxThread::IsGxThread()) {
|
||||
ApplyPendingMkwDynamicAspectSurface();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
// GX thread: the host side of the GX pipeline (state tracking, FIFO parsing,
|
||||
// display-list scanning, texture object resolution and every aurora GX call)
|
||||
// runs on its own thread, fed by an ordered command ring the game thread posts
|
||||
// to. Each GX HLE override is split into a game-thread front (guest-visible
|
||||
// side effects: shadow registers, getters, display-list recording) and a
|
||||
// GX-thread back (the aurora work). When the thread is disabled, Post() runs
|
||||
// the back inline, so the split is behaviour-preserving in both modes.
|
||||
//
|
||||
// Data hazards follow the hardware: anything the GX library copies into the
|
||||
// FIFO at call time (immediate-mode vertices, matrices, colours, light objects,
|
||||
// texture object registers) is snapshotted at post time; anything the GP reads
|
||||
// from memory when it reaches the command (display lists, vertex arrays,
|
||||
// indexed matrices, texture data) is read when the GX thread executes it.
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
namespace GxThread {
|
||||
|
||||
// Decides whether posted work runs on the GX thread. Read once, before GXInit.
|
||||
void Configure(bool enabled);
|
||||
bool Enabled() noexcept;
|
||||
// True on the consumer thread.
|
||||
bool IsGxThread() noexcept;
|
||||
void Start();
|
||||
// Executes everything posted so far and joins the thread.
|
||||
void Stop();
|
||||
// Game thread: blocks until every posted record has executed (GXDrawDone).
|
||||
void Drain();
|
||||
// Publishes the open immediate-mode FIFO chunk. Post() does this itself.
|
||||
void FlushFifo();
|
||||
// Invoked at bounded intervals while the game thread blocks in Drain() or on
|
||||
// a full ring, so guest timing (VI retraces, OS alarms) keeps running.
|
||||
void SetWaitCallback(void (*callback)());
|
||||
// Native thread id of the consumer (Android: gettid), 0 until started.
|
||||
uint32_t NativeThreadId() noexcept;
|
||||
// One line for the frame-rate log; resets the window counters.
|
||||
std::string FormatStatsAndReset(double windowSeconds, uint32_t frames);
|
||||
|
||||
// Immediate-mode write-gather bytes. Only valid when Enabled().
|
||||
void PostFifoWord(uint32_t value, uint32_t sizeBytes);
|
||||
void PostFifoBytes(const uint8_t* data, uint32_t sizeBytes);
|
||||
|
||||
namespace detail {
|
||||
using Invoke = void (*)(const uint8_t* payload, uint32_t payloadBytes);
|
||||
void PostRecord(Invoke invoke, const void* payload, uint32_t payloadBytes);
|
||||
|
||||
template <typename... Ts>
|
||||
struct Pack;
|
||||
template <>
|
||||
struct Pack<> {
|
||||
template <typename F, typename... Prev>
|
||||
void Call(F f, Prev... prev) const {
|
||||
f(prev...);
|
||||
}
|
||||
};
|
||||
template <typename T, typename... Ts>
|
||||
struct Pack<T, Ts...> {
|
||||
T head;
|
||||
Pack<Ts...> tail;
|
||||
template <typename F, typename... Prev>
|
||||
void Call(F f, Prev... prev) const {
|
||||
tail.Call(f, prev..., head);
|
||||
}
|
||||
};
|
||||
template <typename... Ts>
|
||||
struct BuildPack;
|
||||
template <>
|
||||
struct BuildPack<> {
|
||||
static Pack<> Make() { return {}; }
|
||||
};
|
||||
template <typename T, typename... Ts>
|
||||
struct BuildPack<T, Ts...> {
|
||||
static Pack<T, Ts...> Make(T head, Ts... tail) {
|
||||
return Pack<T, Ts...>{head, BuildPack<Ts...>::Make(tail...)};
|
||||
}
|
||||
};
|
||||
template <typename R, typename... Params>
|
||||
struct CallRecord {
|
||||
R (*fn)(Params...);
|
||||
Pack<Params...> args;
|
||||
};
|
||||
template <typename R, typename... Params>
|
||||
void InvokeCall(const uint8_t* payload, uint32_t) {
|
||||
CallRecord<R, Params...> record;
|
||||
std::memcpy(&record, payload, sizeof(record));
|
||||
record.args.Call(record.fn);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
// Posts fn(args...) to the GX thread, or runs it now when the thread is off.
|
||||
// Parameters must be trivially copyable values (no pointers into guest memory
|
||||
// that the game may rewrite before the GX thread reads them).
|
||||
template <typename R, typename... Params, typename... Args>
|
||||
inline void Post(R (*fn)(Params...), Args&&... args) {
|
||||
if (!Enabled()) {
|
||||
fn(static_cast<Params>(args)...);
|
||||
return;
|
||||
}
|
||||
detail::CallRecord<R, Params...> record{fn, detail::BuildPack<Params...>::Make(static_cast<Params>(args)...)};
|
||||
detail::PostRecord(&detail::InvokeCall<R, Params...>, &record, sizeof(record));
|
||||
}
|
||||
|
||||
} // namespace GxThread
|
||||
@@ -47,6 +47,7 @@ struct RuntimeUserConfig {
|
||||
std::optional<bool> textureReplacements;
|
||||
std::optional<bool> textureDumps;
|
||||
std::optional<bool> showFps;
|
||||
std::optional<bool> gxThread;
|
||||
std::optional<uint32_t> disabledPostProcessingPaths;
|
||||
std::optional<bool> vrEnabled;
|
||||
std::optional<bool> vrRequired;
|
||||
@@ -410,6 +411,10 @@ inline void EnsureConfigFile() {
|
||||
"skip_unready_pipelines = true\n"
|
||||
"disable_copy_filter = true\n"
|
||||
"show_fps = true\n"
|
||||
"# Run the host side of the GX pipeline (state tracking, FIFO parsing,\n"
|
||||
"# texture uploads) on its own thread. On by default on the Quest, where\n"
|
||||
"# the game thread is the bottleneck; opt-in elsewhere.\n"
|
||||
"# gx_thread = true\n"
|
||||
"# Dolphin-style custom textures. When enabled, the renderer indexes\n"
|
||||
"# texture_replacements/ next to this file at startup and substitutes\n"
|
||||
"# any tex1_<W>x<H>_<hash>[_<tlut hash>]_<format>.dds or .png it finds\n"
|
||||
@@ -641,6 +646,7 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
|
||||
config.skipUnreadyPipelines = FindConfigValue<bool>(document, "video", "skip_unready_pipelines");
|
||||
config.disableCopyFilter = FindConfigValue<bool>(document, "video", "disable_copy_filter");
|
||||
config.showFps = FindConfigValue<bool>(document, "video", "show_fps");
|
||||
config.gxThread = FindConfigValue<bool>(document, "video", "gx_thread");
|
||||
config.textureReplacements = FindConfigValue<bool>(document, "video", "texture_replacements");
|
||||
config.textureDumps = FindConfigValue<bool>(document, "video", "texture_dumps");
|
||||
if (auto value = FindConfigUint(document, "video", "disabled_post_processing_paths");
|
||||
@@ -1294,6 +1300,16 @@ inline bool ShowFps(bool fallback = true) {
|
||||
return Get().showFps.value_or(fallback);
|
||||
}
|
||||
|
||||
// Runs the host side of the GX pipeline on its own thread (gx_thread.h). On by
|
||||
// default on the Quest, where the game thread is the bottleneck; opt-in elsewhere.
|
||||
inline bool GxThread() {
|
||||
#if defined(__ANDROID__)
|
||||
return Get().gxThread.value_or(true);
|
||||
#else
|
||||
return Get().gxThread.value_or(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool TextureReplacements(bool fallback = false) {
|
||||
return Get().textureReplacements.value_or(fallback);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user