mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-10-20 21:49:51 -04:00
run gx on its own thread
This commit is contained in:
+1
-1
Submodule aurora-main updated: 648e77d7e8...78850a8d30
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "settings_overlay.h"
|
||||
#include "runtime_config.h"
|
||||
#include "gx_worker.h"
|
||||
|
||||
#include <aurora/aurora.h>
|
||||
#include <aurora/event.h>
|
||||
@@ -131,7 +132,20 @@ inline void ApplyPendingMkwDynamicAspectSurface() {
|
||||
UpdateMkwDynamicAspectSurface(surfaceWidth, surfaceHeight);
|
||||
}
|
||||
|
||||
void GxWorkerBeginFrame();
|
||||
void GxWorkerEndFrame(AuroraImguiFrame* imgui);
|
||||
|
||||
inline bool BeginAuroraFrame() {
|
||||
if (GxWorker::ShouldDefer()) {
|
||||
// The window half can touch the surface and render targets the worker draws into.
|
||||
GxWorker::Sync();
|
||||
if (!aurora_begin_frame_window()) {
|
||||
return false;
|
||||
}
|
||||
GxWorker::Submit([] { GxWorkerBeginFrame(); });
|
||||
ApplyPendingMkwDynamicAspectSurface();
|
||||
return true;
|
||||
}
|
||||
if (!aurora_begin_frame()) {
|
||||
return false;
|
||||
}
|
||||
@@ -139,6 +153,18 @@ inline bool BeginAuroraFrame() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ends the frame; with the GX worker, the game thread joins it here so the next frame never
|
||||
// overlaps guest memory the worker may still read.
|
||||
inline void EndAuroraFrame() {
|
||||
if (GxWorker::ShouldDefer()) {
|
||||
AuroraImguiFrame* imgui = aurora_end_frame_window();
|
||||
GxWorker::Submit([imgui] { GxWorkerEndFrame(imgui); });
|
||||
GxWorker::Sync();
|
||||
return;
|
||||
}
|
||||
aurora_end_frame();
|
||||
}
|
||||
|
||||
// Poll Aurora events and update cached window/framebuffer dimensions.
|
||||
inline void UpdateAuroraAndProcessEvents() {
|
||||
ProcessAuroraEvents(aurora_update());
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
// Dedicated GX thread (Dolphin "dual core"): the game thread records GX HLE calls in order and
|
||||
// the worker executes them. While active, GX work runs on the worker except inside inline scopes,
|
||||
// which run on the game thread after the worker has caught up.
|
||||
namespace GxWorker {
|
||||
|
||||
namespace detail {
|
||||
inline constexpr size_t kAlign = 16;
|
||||
|
||||
extern std::atomic<bool> g_active;
|
||||
extern constinit thread_local bool t_onWorker;
|
||||
extern constinit thread_local int t_inlineDepth;
|
||||
extern bool g_fifoPending;
|
||||
|
||||
void* Reserve(size_t payloadSize, void (*run)(void*));
|
||||
void Commit();
|
||||
void FlushFifo();
|
||||
} // namespace detail
|
||||
|
||||
// Starts the worker unless [video] gx_worker or MKW_GX_WORKER=0 disables it. Call once Aurora is up.
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
inline bool Active() noexcept { return detail::g_active.load(std::memory_order_relaxed); }
|
||||
inline bool OnWorker() noexcept { return detail::t_onWorker; }
|
||||
// True when a GX entry point on this thread must be recorded instead of executed.
|
||||
inline bool ShouldDefer() noexcept {
|
||||
return Active() && !detail::t_onWorker && detail::t_inlineDepth == 0;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void Submit(F&& fn) {
|
||||
using Fn = std::decay_t<F>;
|
||||
static_assert(alignof(Fn) <= detail::kAlign, "over-aligned GX command");
|
||||
if (detail::g_fifoPending) {
|
||||
detail::FlushFifo();
|
||||
}
|
||||
void* storage = detail::Reserve(sizeof(Fn), [](void* p) {
|
||||
struct Destroy {
|
||||
Fn* f;
|
||||
~Destroy() { f->~Fn(); }
|
||||
} guard{static_cast<Fn*>(p)};
|
||||
(*guard.f)();
|
||||
});
|
||||
new (storage) Fn(std::forward<F>(fn));
|
||||
detail::Commit();
|
||||
}
|
||||
|
||||
// Records fn when deferring, otherwise runs it now. Read call-time guest memory before calling.
|
||||
template <typename F>
|
||||
void Run(F&& fn) {
|
||||
if (ShouldDefer()) {
|
||||
Submit(std::forward<F>(fn));
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
}
|
||||
|
||||
// Gather-pipe writes, coalesced into one record per run of consecutive writes and replayed
|
||||
// with their original sizes.
|
||||
void FifoWrite(uint32_t value, uint32_t sizeBytes);
|
||||
void FifoBurst(const uint8_t* data, uint32_t sizeBytes);
|
||||
|
||||
// Called about every millisecond while the game thread waits in Sync.
|
||||
void SetWaitCallback(void (*callback)());
|
||||
|
||||
// Blocks until every recorded command has executed.
|
||||
void Sync();
|
||||
|
||||
// GX entry points run inline on the game thread while inside (after a Sync), for work whose
|
||||
// results the guest needs immediately.
|
||||
void BeginInline();
|
||||
void EndInline();
|
||||
|
||||
class InlineScope {
|
||||
public:
|
||||
InlineScope() { BeginInline(); }
|
||||
~InlineScope() { EndInline(); }
|
||||
InlineScope(const InlineScope&) = delete;
|
||||
InlineScope& operator=(const InlineScope&) = delete;
|
||||
};
|
||||
|
||||
} // namespace GxWorker
|
||||
|
||||
// Records the enclosing call for the GX worker and returns, or falls through when it should run
|
||||
// here (worker thread, inline scope, worker disabled).
|
||||
#define GX_DEFER_CALL(fn, ...) \
|
||||
do { \
|
||||
if (GxWorker::ShouldDefer()) { \
|
||||
GxWorker::Submit([=] { fn(__VA_ARGS__); }); \
|
||||
return; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define GX_DEFER_CALL0(fn) \
|
||||
do { \
|
||||
if (GxWorker::ShouldDefer()) { \
|
||||
GxWorker::Submit([] { fn(); }); \
|
||||
return; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
// Runs the rest of the enclosing function on this thread once the worker has caught up.
|
||||
#define GX_SYNC_INLINE() GxWorker::InlineScope gxInlineScope_
|
||||
@@ -44,6 +44,7 @@ struct RuntimeUserConfig {
|
||||
std::optional<uint32_t> frameInterpolationFps;
|
||||
std::optional<bool> metalFxSpatialUpscaling;
|
||||
std::optional<bool> skipUnreadyPipelines;
|
||||
std::optional<bool> gxWorker;
|
||||
std::optional<bool> disableCopyFilter;
|
||||
std::optional<bool> textureReplacements;
|
||||
std::optional<bool> textureDumps;
|
||||
@@ -275,6 +276,8 @@ inline void EnsureConfigFile() {
|
||||
"display_mode = \"windowed\"\n"
|
||||
"graphics_api = \"auto\"\n"
|
||||
"skip_unready_pipelines = true\n"
|
||||
"# Runs GX command processing on its own thread. Read once, at startup.\n"
|
||||
"gx_worker = true\n"
|
||||
"disable_copy_filter = true\n"
|
||||
"show_fps = true\n"
|
||||
"# Dolphin-style custom textures. When enabled, the renderer indexes\n"
|
||||
@@ -432,6 +435,7 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
|
||||
config.metalFxSpatialUpscaling =
|
||||
FindConfigValue<bool>(document, "video", "metalfx_spatial_upscaling");
|
||||
config.skipUnreadyPipelines = FindConfigValue<bool>(document, "video", "skip_unready_pipelines");
|
||||
config.gxWorker = FindConfigValue<bool>(document, "video", "gx_worker");
|
||||
config.disableCopyFilter = FindConfigValue<bool>(document, "video", "disable_copy_filter");
|
||||
config.showFps = FindConfigValue<bool>(document, "video", "show_fps");
|
||||
config.textureReplacements = FindConfigValue<bool>(document, "video", "texture_replacements");
|
||||
@@ -811,6 +815,10 @@ inline bool AudioMixWorkerEnabled(bool fallback = true) {
|
||||
return Get().audioMixWorker.value_or(fallback);
|
||||
}
|
||||
|
||||
inline bool GxWorkerEnabled(bool fallback = true) {
|
||||
return Get().gxWorker.value_or(fallback);
|
||||
}
|
||||
|
||||
// Whether background music should duck automatically for other media playback.
|
||||
inline bool AttenuateMusicWhenMediaPlays(bool fallback = false) {
|
||||
return Get().attenuateMusicWhenMediaPlays.value_or(fallback);
|
||||
|
||||
@@ -29,6 +29,8 @@ constexpr uint32_t kAIDmaCallbackAddr = 0x80386480u;
|
||||
// Max completed 3 ms DMA blocks delivered per tick. Draining several at once catches up
|
||||
// backlog from a long frame without letting a large stall spiral into an unbounded loop.
|
||||
constexpr int kMaxBlocksPerTick = 4;
|
||||
// Backlog (in blocks) tolerated before a catch-up block waits on an in-flight mix.
|
||||
constexpr double kMaxDeferredBlocks = 2.0;
|
||||
|
||||
struct AIDmaState {
|
||||
std::mutex mutex;
|
||||
@@ -407,11 +409,15 @@ void Audio_HLE_Tick(CpuContext* ctx, uint32_t deltaMicros)
|
||||
|
||||
int blocksCompleted = 0;
|
||||
while (true) {
|
||||
// A block due while the previous mix is still running would block on it; leave it for the
|
||||
// next service point unless the backlog is getting long.
|
||||
const bool deferWhileMixing = AxDspHle::MixWorkerBusy();
|
||||
// Claim the block in one critical section; sample-then-consume separately gains
|
||||
// nothing since the callback below (the only reentrancy point) runs mutex-released.
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_ai.mutex);
|
||||
if (g_ai.accumulatorSeconds < blockDuration) {
|
||||
if (g_ai.accumulatorSeconds < blockDuration ||
|
||||
(deferWhileMixing && g_ai.accumulatorSeconds < blockDuration * kMaxDeferredBlocks)) {
|
||||
break;
|
||||
}
|
||||
g_ai.accumulatorSeconds -= blockDuration;
|
||||
|
||||
@@ -25,6 +25,9 @@ void ServiceDeferredCallbacks();
|
||||
// Cheap when the mix already finished (the normal case).
|
||||
void JoinMixWorker();
|
||||
|
||||
// True while a deferred mix is queued or still running.
|
||||
bool MixWorkerBusy();
|
||||
|
||||
// Joins and tears down the mix worker thread. Call before the guest memory map
|
||||
// is destroyed or re-created.
|
||||
void ShutdownMixWorker();
|
||||
|
||||
@@ -112,6 +112,14 @@ public:
|
||||
ConfigureFromTaskLocked(taskPtr);
|
||||
}
|
||||
|
||||
bool MixBusy() {
|
||||
if (!m_mixThread.joinable()) {
|
||||
return false;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(m_mixMutex);
|
||||
return m_mixPending || m_mixBusy;
|
||||
}
|
||||
|
||||
void JoinMix() {
|
||||
if (m_mixThread.joinable()) {
|
||||
std::unique_lock<std::mutex> lock(m_mixMutex);
|
||||
@@ -1560,6 +1568,10 @@ void JoinMixWorker() {
|
||||
Instance().JoinMix();
|
||||
}
|
||||
|
||||
bool MixWorkerBusy() {
|
||||
return Instance().MixBusy();
|
||||
}
|
||||
|
||||
void ShutdownMixWorker() {
|
||||
Instance().ShutdownMixWorker();
|
||||
}
|
||||
|
||||
@@ -71,11 +71,14 @@ void InvalidateEfbCopyDestinationsForRange(uint32_t addr, uint32_t size) {
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetDispCopySrc_8016f438(uint32_t l, uint32_t t, uint32_t w, uint32_t h) {
|
||||
GX_DEFER_CALL(GX__SetDispCopySrc_8016f438, l, t, w, h);
|
||||
GXSetDispCopySrc((u16)l, (u16)t, (u16)w, (u16)h);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f438, GX__SetDispCopySrc_8016f438, (uint32_t l, uint32_t t, uint32_t w, uint32_t h), (l, t, w, h));
|
||||
|
||||
extern "C" void GX__SetDispCopyDst_8016f4b8(uint32_t w, uint32_t h) { GXSetDispCopyDst((u16)w, (u16)h); }
|
||||
extern "C" void GX__SetDispCopyDst_8016f4b8(uint32_t w, uint32_t h) {
|
||||
GxWorker::Run([w, h] { GXSetDispCopyDst((u16)w, (u16)h); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f4b8, GX__SetDispCopyDst_8016f4b8, (uint32_t w, uint32_t h), (w, h));
|
||||
|
||||
// ============================================================================
|
||||
@@ -83,14 +86,14 @@ PPC_NATIVE_OVERRIDE_VOID(8016f4b8, GX__SetDispCopyDst_8016f4b8, (uint32_t w, uin
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetTexCopySrc_8016f478(uint32_t l, uint32_t t, uint32_t w, uint32_t h) {
|
||||
GXSetTexCopySrc((u16)l, (u16)t, (u16)w, (u16)h);
|
||||
GxWorker::Run([l, t, w, h] { GXSetTexCopySrc((u16)l, (u16)t, (u16)w, (u16)h); });
|
||||
g_texCopyState.srcLeft=(u16)l; g_texCopyState.srcTop=(u16)t;
|
||||
g_texCopyState.srcWidth=(u16)w; g_texCopyState.srcHeight=(u16)h;
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f478, GX__SetTexCopySrc_8016f478, (uint32_t l, uint32_t t, uint32_t w, uint32_t h), (l, t, w, h));
|
||||
|
||||
extern "C" void GX__SetTexCopyDst_8016f4dc(uint32_t w, uint32_t h, uint32_t f, uint32_t m) {
|
||||
GXSetTexCopyDst((u16)w, (u16)h, (GXTexFmt)f, (GXBool)m);
|
||||
GxWorker::Run([w, h, f, m] { GXSetTexCopyDst((u16)w, (u16)h, (GXTexFmt)f, (GXBool)m); });
|
||||
g_texCopyState.dstWidth=(u16)w; g_texCopyState.dstHeight=(u16)h;
|
||||
g_texCopyState.dstFormat=f; g_texCopyState.dstMipmap=m;
|
||||
}
|
||||
@@ -100,11 +103,19 @@ extern "C" void GX__SetCopyFilter_8016fa40(uint32_t aa, uint32_t spa, uint32_t v
|
||||
uint8_t sp[12][2]={}, vfb[7]={};
|
||||
if(spa) std::memcpy(sp, GuestToHostPtr(spa, 24), 24);
|
||||
if(vfa) std::memcpy(vfb, GuestToHostPtr(vfa, 7), 7);
|
||||
GXSetCopyFilter((GXBool)aa, sp, (GXBool)vf, vfb);
|
||||
std::array<uint8_t, 24> spCopy{};
|
||||
std::array<uint8_t, 7> vfCopy{};
|
||||
std::memcpy(spCopy.data(), sp, 24);
|
||||
std::memcpy(vfCopy.data(), vfb, 7);
|
||||
GxWorker::Run([aa, vf, spCopy, vfCopy]() mutable {
|
||||
GXSetCopyFilter((GXBool)aa, reinterpret_cast<u8(*)[2]>(spCopy.data()), (GXBool)vf, vfCopy.data());
|
||||
});
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016fa40, GX__SetCopyFilter_8016fa40, (uint32_t aa, uint32_t spa, uint32_t vf, uint32_t vfa), (aa, spa, vf, vfa));
|
||||
|
||||
extern "C" void GX__SetDispCopyGamma_8016fc24(uint32_t g) { GXSetDispCopyGamma((GXGamma)g); }
|
||||
extern "C" void GX__SetDispCopyGamma_8016fc24(uint32_t g) {
|
||||
GxWorker::Run([g] { GXSetDispCopyGamma((GXGamma)g); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016fc24, GX__SetDispCopyGamma_8016fc24, (uint32_t g), (g));
|
||||
|
||||
// ============================================================================
|
||||
@@ -113,18 +124,24 @@ PPC_NATIVE_OVERRIDE_VOID(8016fc24, GX__SetDispCopyGamma_8016fc24, (uint32_t g),
|
||||
|
||||
extern "C" void GX__CopyDisp_8016fc38(uint32_t da, uint32_t c) {
|
||||
EnsureAuroraFrameActive();
|
||||
// GX copies are FIFO-ordered on hardware. Drain submitted draws before
|
||||
// resolving the EFB so high-level copies see the same contents.
|
||||
GXDrawDone();
|
||||
GXCopyDisp(GuestToHostPtr(da), (GXBool)c);
|
||||
// No second GXDrawDone here: the frame-worker wait below is for the DONE
|
||||
// phase, which strictly subsumes the drain this call would perform.
|
||||
if (GxWorker::ShouldDefer()) {
|
||||
// The worker drains like the inline path; EndAuroraFrame joins it before the next frame.
|
||||
GxWorker::Submit([da, c] {
|
||||
GXDrawDone();
|
||||
GXCopyDisp(GuestToHostPtr(da), (GXBool)c);
|
||||
});
|
||||
} else {
|
||||
// GX copies are FIFO-ordered on hardware. Drain submitted draws before
|
||||
// resolving the EFB so high-level copies see the same contents.
|
||||
GXDrawDone();
|
||||
GXCopyDisp(GuestToHostPtr(da), (GXBool)c);
|
||||
// Present immediately so post-copy draws don't leak into this frame. Join at the DONE phase
|
||||
// (not the cheaper SEALED phase GXDrawDone waits for) because ImGui's draw lists, owned by
|
||||
// Aurora's render worker, replay during encode; aurora_end_frame would join here anyway.
|
||||
aurora_wait_for_frame_worker();
|
||||
}
|
||||
++g_gxFrameCount;
|
||||
VI_HLE_SetXfbReady(da);
|
||||
// Present immediately so post-copy draws don't leak into this frame. Join at the DONE phase
|
||||
// (not the cheaper SEALED phase GXDrawDone waits for) because ImGui's draw lists, owned by
|
||||
// Aurora's render worker, replay during encode; aurora_end_frame would join here anyway.
|
||||
aurora_wait_for_frame_worker();
|
||||
settings_overlay::Draw();
|
||||
// Seal, pace to the VI retrace boundary (Aurora renders the sealed frame
|
||||
// during the wait), and pre-warm the next frame.
|
||||
@@ -135,7 +152,9 @@ PPC_NATIVE_OVERRIDE_VOID(8016fc38, GX__CopyDisp_8016fc38, (uint32_t da, uint32_t
|
||||
|
||||
|
||||
extern "C" void GX__CopyTex_8016fd74(uint32_t da, uint32_t c) {
|
||||
// Begin the frame before going inline so a deferred begin reaches the worker first.
|
||||
EnsureAuroraFrameActive();
|
||||
GX_SYNC_INLINE();
|
||||
// Match GX FIFO ordering: texture copies observe all prior draws.
|
||||
GXDrawDone();
|
||||
const uint16_t rawSrcLeft = g_texCopyState.srcLeft;
|
||||
|
||||
@@ -89,39 +89,42 @@ static inline uint32_t ReadModulatedLytColor(uint32_t colorAddr, int index, uint
|
||||
static_cast<uint32_t>(a);
|
||||
}
|
||||
|
||||
static inline void AppendLytTexCoords(uint8_t* packet, uint32_t& pos, uint32_t texCoordAddr,
|
||||
int texCoordCount, uint32_t cornerOffset) {
|
||||
if (texCoordCount <= 0) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < texCoordCount; ++i) {
|
||||
const uint32_t coordAddr = texCoordAddr + static_cast<uint32_t>(i * 32) + cornerOffset;
|
||||
BigEndian::AppendFloat32(packet, pos, Memory::ReadFloat32(coordAddr));
|
||||
BigEndian::AppendFloat32(packet, pos, Memory::ReadFloat32(coordAddr + 4));
|
||||
// An nw4r::lyt quad as read on the game thread: texcoords are 8 sets of 4 corners x (s, t).
|
||||
struct LytQuad {
|
||||
float x0 = 0, y0 = 0, x1 = 0, y1 = 0;
|
||||
int texCoordCount = 0;
|
||||
std::array<float, 8 * 4 * 2> texCoords{};
|
||||
bool hasColors = false;
|
||||
std::array<uint32_t, 4> colors{};
|
||||
};
|
||||
|
||||
static inline void AppendLytTexCoords(uint8_t* packet, uint32_t& pos, const LytQuad& quad,
|
||||
uint32_t cornerOffset) {
|
||||
for (int i = 0; i < quad.texCoordCount; ++i) {
|
||||
const size_t at = static_cast<size_t>(i) * 8u + cornerOffset / 4u;
|
||||
BigEndian::AppendFloat32(packet, pos, quad.texCoords[at]);
|
||||
BigEndian::AppendFloat32(packet, pos, quad.texCoords[at + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void AppendLytQuadVertex(uint8_t* packet, uint32_t& pos, float x, float y,
|
||||
uint32_t texCoordAddr, int texCoordCount,
|
||||
uint32_t cornerOffset, const uint32_t* colors, int colorIndex) {
|
||||
static inline void AppendLytQuadVertex(uint8_t* packet, uint32_t& pos, float x, float y, const LytQuad& quad,
|
||||
uint32_t cornerOffset, int colorIndex) {
|
||||
BigEndian::AppendFloat32(packet, pos, x);
|
||||
BigEndian::AppendFloat32(packet, pos, y);
|
||||
if (colors) {
|
||||
BigEndian::Append32(packet, pos, colors[colorIndex]);
|
||||
if (quad.hasColors) {
|
||||
BigEndian::Append32(packet, pos, quad.colors[colorIndex]);
|
||||
}
|
||||
AppendLytTexCoords(packet, pos, texCoordAddr, texCoordCount, cornerOffset);
|
||||
AppendLytTexCoords(packet, pos, quad, cornerOffset);
|
||||
}
|
||||
|
||||
// The four corners of an nw4r::lyt quad, in the order GX_QUADS expects, with
|
||||
// each corner's texcoord byte offset and colour index. The winding order is
|
||||
// load-bearing and both emit paths below share this one copy of it.
|
||||
static inline void AppendLytQuadVertices(uint8_t* packet, uint32_t& pos, float x0, float y0,
|
||||
float x1, float y1, uint32_t texCoordAddr,
|
||||
int texCoordCount, const uint32_t* colors) {
|
||||
AppendLytQuadVertex(packet, pos, x0, y0, texCoordAddr, texCoordCount, 0, colors, 0);
|
||||
AppendLytQuadVertex(packet, pos, x1, y0, texCoordAddr, texCoordCount, 8, colors, 1);
|
||||
AppendLytQuadVertex(packet, pos, x1, y1, texCoordAddr, texCoordCount, 24, colors, 3);
|
||||
AppendLytQuadVertex(packet, pos, x0, y1, texCoordAddr, texCoordCount, 16, colors, 2);
|
||||
static inline void AppendLytQuadVertices(uint8_t* packet, uint32_t& pos, const LytQuad& quad) {
|
||||
AppendLytQuadVertex(packet, pos, quad.x0, quad.y0, quad, 0, 0);
|
||||
AppendLytQuadVertex(packet, pos, quad.x1, quad.y0, quad, 8, 1);
|
||||
AppendLytQuadVertex(packet, pos, quad.x1, quad.y1, quad, 24, 3);
|
||||
AppendLytQuadVertex(packet, pos, quad.x0, quad.y1, quad, 16, 2);
|
||||
}
|
||||
|
||||
static bool CanSubmitLytDrawDirect(int texCoordCount, const uint32_t* colors) {
|
||||
@@ -175,9 +178,8 @@ static bool CanSubmitLytDrawDirect(int texCoordCount, const uint32_t* colors) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool SubmitLytDrawDirect(float x0, float y0, float x1, float y1, int texCoordCount,
|
||||
uint32_t texCoordAddr, const uint32_t* colors) {
|
||||
if (!CanSubmitLytDrawDirect(texCoordCount, colors)) {
|
||||
static bool SubmitLytDrawDirect(const LytQuad& quad) {
|
||||
if (!CanSubmitLytDrawDirect(quad.texCoordCount, quad.hasColors ? quad.colors.data() : nullptr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -193,7 +195,7 @@ static bool SubmitLytDrawDirect(float x0, float y0, float x1, float y1, int texC
|
||||
|
||||
std::array<uint8_t, 4u * (8u + 4u + 8u * 8u)> vertices{};
|
||||
uint32_t pos = 0;
|
||||
AppendLytQuadVertices(vertices.data(), pos, x0, y0, x1, y1, texCoordAddr, texCoordCount, colors);
|
||||
AppendLytQuadVertices(vertices.data(), pos, quad);
|
||||
|
||||
if (!aurora::gx::fifo::submit_raw_draw(GX_QUADS, GX_VTXFMT0, vertices.data(), 4, pos)) {
|
||||
return false;
|
||||
@@ -204,23 +206,8 @@ static bool SubmitLytDrawDirect(float x0, float y0, float x1, float y1, int texC
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void EmitLytDrawQuad(uint32_t posAddr, uint32_t sizeAddr, int texCoordCount,
|
||||
uint32_t texCoordAddr, const uint32_t* colors) {
|
||||
const float x0 = Memory::ReadFloat32(posAddr);
|
||||
const float y0 = Memory::ReadFloat32(posAddr + 4);
|
||||
// The casts pin the arithmetic to PPC single precision regardless of the
|
||||
// host's FLT_EVAL_METHOD.
|
||||
const float x1 = static_cast<float>(x0 + Memory::ReadFloat32(sizeAddr));
|
||||
const float y1 = static_cast<float>(y0 - Memory::ReadFloat32(sizeAddr + 4));
|
||||
|
||||
if (SubmitLytDrawDirect(x0, y0, x1, y1, texCoordCount, texCoordAddr, colors)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// GX has exactly 8 texture coordinates, so nw4r::lyt cannot ask for more.
|
||||
// The fixed packet buffer below is sized for that maximum; bail rather than
|
||||
// overrun it if the guest ever hands us something else.
|
||||
if (texCoordCount > 8) {
|
||||
static void EmitLytQuad(const LytQuad& quad) {
|
||||
if (SubmitLytDrawDirect(quad)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -228,10 +215,39 @@ static inline void EmitLytDrawQuad(uint32_t posAddr, uint32_t sizeAddr, int texC
|
||||
uint32_t pos = 0;
|
||||
packet[pos++] = GX_DRAW_QUADS_CMD | GX_VTXFMT0;
|
||||
BigEndian::Append16(packet.data(), pos, 4);
|
||||
AppendLytQuadVertices(packet.data(), pos, x0, y0, x1, y1, texCoordAddr, texCoordCount, colors);
|
||||
AppendLytQuadVertices(packet.data(), pos, quad);
|
||||
SubmitLytDrawPacket(packet.data(), pos);
|
||||
}
|
||||
|
||||
static inline void EmitLytDrawQuad(uint32_t posAddr, uint32_t sizeAddr, int texCoordCount,
|
||||
uint32_t texCoordAddr, const uint32_t* colors) {
|
||||
// GX has exactly 8 texture coordinates, so nw4r::lyt cannot ask for more; the packet buffer
|
||||
// is sized for that maximum.
|
||||
if (texCoordCount > 8) {
|
||||
return;
|
||||
}
|
||||
LytQuad quad;
|
||||
quad.x0 = Memory::ReadFloat32(posAddr);
|
||||
quad.y0 = Memory::ReadFloat32(posAddr + 4);
|
||||
// The casts pin the arithmetic to PPC single precision regardless of the
|
||||
// host's FLT_EVAL_METHOD.
|
||||
quad.x1 = static_cast<float>(quad.x0 + Memory::ReadFloat32(sizeAddr));
|
||||
quad.y1 = static_cast<float>(quad.y0 - Memory::ReadFloat32(sizeAddr + 4));
|
||||
quad.texCoordCount = std::max(texCoordCount, 0);
|
||||
for (int i = 0; i < quad.texCoordCount; ++i) {
|
||||
for (uint32_t f = 0; f < 8; ++f) {
|
||||
quad.texCoords[static_cast<size_t>(i) * 8u + f] =
|
||||
Memory::ReadFloat32(texCoordAddr + static_cast<uint32_t>(i * 32) + f * 4u);
|
||||
}
|
||||
}
|
||||
if (colors != nullptr) {
|
||||
quad.hasColors = true;
|
||||
std::copy(colors, colors + 4, quad.colors.begin());
|
||||
}
|
||||
EnsureAuroraFrameActive();
|
||||
GxWorker::Run([quad] { EmitLytQuad(quad); });
|
||||
}
|
||||
|
||||
static uint32_t SubmitDLVertex(const uint8_t* ptr, GXVtxFmt vtxfmt, const GXAttrType* sourceVtxDesc) {
|
||||
uint32_t offset = 0;
|
||||
for (int attr = 0; attr < 26; ++attr) {
|
||||
@@ -1284,6 +1300,12 @@ extern "C" void GxNotifyDisplayListMemoryWrite(uint32_t addr, uint32_t size) {
|
||||
|
||||
extern "C" void GX__CallDisplayList_80172f64(uint32_t listAddr, uint32_t nbytes) {
|
||||
if (nbytes == 0 || listAddr == 0) return;
|
||||
// List bytes and the arrays it indexes are GPU-side reads, so they are read when the worker runs.
|
||||
if (GxWorker::ShouldDefer()) {
|
||||
EnsureAuroraFrameActive();
|
||||
GxWorker::Submit([=] { GX__CallDisplayList_80172f64(listAddr, nbytes); });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const uint8_t* list = static_cast<const uint8_t*>(GuestToHostPtr(listAddr, nbytes));
|
||||
if (!list) return;
|
||||
|
||||
@@ -81,8 +81,10 @@ extern "C" void EGG__LightTexture__SetupTevFinish_HLE_8022e2bc(CpuContext* ctx)
|
||||
if (remainder > 0) {
|
||||
const GXColor black{0, 0, 0, 255};
|
||||
while (remainder < stageCount) {
|
||||
GXSetTevColor(static_cast<GXTevRegID>(remainder + 1), black);
|
||||
GXSetTevKColor(static_cast<GXTevKColorID>(remainder), black);
|
||||
GxWorker::Run([remainder, black] {
|
||||
GXSetTevColor(static_cast<GXTevRegID>(remainder + 1), black);
|
||||
GXSetTevKColor(static_cast<GXTevKColorID>(remainder), black);
|
||||
});
|
||||
++remainder;
|
||||
}
|
||||
|
||||
|
||||
@@ -775,11 +775,25 @@ static bool WriteDisplayListBurst(const uint8_t* data, uint32_t sizeBytes) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void FifoWriteBurstImpl(const uint8_t* data, uint32_t sizeBytes);
|
||||
|
||||
extern "C" void GX_HLE_FIFO_WriteBurst(const uint8_t* data, uint32_t sizeBytes) {
|
||||
if (data == nullptr || sizeBytes == 0) {
|
||||
return;
|
||||
}
|
||||
if (GxWorker::ShouldDefer()) {
|
||||
EnsureAuroraFrameForFifo();
|
||||
GxWorker::FifoBurst(data, sizeBytes);
|
||||
return;
|
||||
}
|
||||
FifoWriteBurstImpl(data, sizeBytes);
|
||||
}
|
||||
|
||||
void GxWorkerReplayFifoWrite(uint32_t value, uint32_t sizeBytes) { HleFifoWrite(value, sizeBytes); }
|
||||
|
||||
void GxWorkerReplayFifoBurst(const uint8_t* data, uint32_t sizeBytes) { FifoWriteBurstImpl(data, sizeBytes); }
|
||||
|
||||
static void FifoWriteBurstImpl(const uint8_t* data, uint32_t sizeBytes) {
|
||||
if (IsDisplayListActive() && WriteDisplayListBurst(data, sizeBytes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -87,8 +87,51 @@ void BeginNextAuroraFrameWithRetry(std::chrono::milliseconds timeout) {
|
||||
++s_beginExpiredLogCount;
|
||||
}
|
||||
|
||||
void EnsureAuroraFrameForFifo() {
|
||||
// The game thread can't see where draws start in recorded FIFO bytes, so any write may begin the
|
||||
// frame. A failed begin is only retried once the next frame is presented.
|
||||
static int s_attemptedFrame = -1;
|
||||
if (g_auroraFrameActive.load(std::memory_order_acquire) || s_attemptedFrame == g_gxFrameCount) {
|
||||
return;
|
||||
}
|
||||
s_attemptedFrame = g_gxFrameCount;
|
||||
UpdateAuroraAndProcessEvents();
|
||||
if (BeginAuroraFrame()) {
|
||||
g_auroraFrameActive.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
void EnsureAuroraFrameActive() {
|
||||
// The game thread begins frames before it records work for the GX worker.
|
||||
if (GxWorker::OnWorker()) {
|
||||
return;
|
||||
}
|
||||
if (!g_auroraFrameActive.load(std::memory_order_acquire)) {
|
||||
BeginNextAuroraFrameWithRetry();
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
// GX worker only.
|
||||
bool g_workerFrameBegun = false;
|
||||
}
|
||||
|
||||
void GxWorkerBeginFrame() {
|
||||
// gfx::begin_frame can briefly fail to map a staging buffer; retry like the inline path does.
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
|
||||
while (!(g_workerFrameBegun = aurora_begin_frame_gx()) && std::chrono::steady_clock::now() < deadline) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
if (!g_workerFrameBegun) {
|
||||
RT_LOGF(RT_TAG_GX, "GX worker could not begin an aurora frame\n");
|
||||
}
|
||||
}
|
||||
|
||||
void GxWorkerEndFrame(AuroraImguiFrame* imgui) {
|
||||
if (g_workerFrameBegun) {
|
||||
aurora_end_frame_gx(imgui);
|
||||
} else {
|
||||
aurora_release_imgui_frame(imgui);
|
||||
}
|
||||
g_workerFrameBegun = false;
|
||||
}
|
||||
|
||||
@@ -5,22 +5,25 @@
|
||||
// Indirect Texture Stages
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetNumIndStages_80171b38(uint32_t n) { GXSetNumIndStages((u8)n); }
|
||||
extern "C" void GX__SetNumIndStages_80171b38(uint32_t n) {
|
||||
GX_DEFER_CALL(GX__SetNumIndStages_80171b38, n); GXSetNumIndStages((u8)n); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171b38, GX__SetNumIndStages_80171b38, (uint32_t n), (n));
|
||||
|
||||
extern "C" void GX__SetIndTexOrder_80171a6c(uint32_t s, uint32_t c, uint32_t m) {
|
||||
GX_DEFER_CALL(GX__SetIndTexOrder_80171a6c, s, c, m);
|
||||
GXSetIndTexOrder((GXIndTexStageID)s, (GXTexCoordID)(c==0xFFu?0:c), (GXTexMapID)(m==0xFFu?0:m));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171a6c, GX__SetIndTexOrder_80171a6c, (uint32_t s, uint32_t c, uint32_t m), (s, c, m));
|
||||
|
||||
extern "C" void GX__SetIndTexCoordScale_80171968(uint32_t s, uint32_t ss, uint32_t ts) {
|
||||
GX_DEFER_CALL(GX__SetIndTexCoordScale_80171968, s, ss, ts);
|
||||
GXSetIndTexCoordScale((GXIndTexStageID)s, (GXIndTexScale)ss, (GXIndTexScale)ts);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171968, GX__SetIndTexCoordScale_80171968, (uint32_t s, uint32_t ss, uint32_t ts), (s, ss, ts));
|
||||
|
||||
extern "C" void GX__SetIndTexMtx_80171814(uint32_t id, uint32_t ma, uint32_t se) {
|
||||
float m[6]; for(int i=0; i<6; ++i) m[i]=Memory::ReadFloat32(ma+i*4);
|
||||
GXSetIndTexMtx((GXIndTexMtxID)id, m, (s8)se);
|
||||
GxWorker::Run([id, se, mtx = std::to_array(m)]() mutable { GXSetIndTexMtx((GXIndTexMtxID)id, mtx.data(), (s8)se); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171814, GX__SetIndTexMtx_80171814, (uint32_t id, uint32_t ma, uint32_t se), (id, ma, se));
|
||||
|
||||
@@ -28,16 +31,19 @@ PPC_NATIVE_OVERRIDE_VOID(80171814, GX__SetIndTexMtx_80171814, (uint32_t id, uint
|
||||
// TEV Indirect Texture Control
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetTevDirect_80171b58(uint32_t s) { GXSetTevDirect((GXTevStageID)s); }
|
||||
extern "C" void GX__SetTevDirect_80171b58(uint32_t s) {
|
||||
GX_DEFER_CALL(GX__SetTevDirect_80171b58, s); GXSetTevDirect((GXTevStageID)s); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171b58, GX__SetTevDirect_80171b58, (uint32_t s), (s));
|
||||
|
||||
extern "C" void GX__SetTevIndWarp_80171ba0(uint32_t ts, uint32_t is, uint32_t so, uint32_t rm, uint32_t ms) {
|
||||
GX_DEFER_CALL(GX__SetTevIndWarp_80171ba0, ts, is, so, rm, ms);
|
||||
GXSetTevIndWarp((GXTevStageID)std::min(ts, 15u), (GXIndTexStageID)std::min(is, 3u),
|
||||
(GXBool)so, (GXBool)rm, (GXIndTexMtxID)ms);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171ba0, GX__SetTevIndWarp_80171ba0, (uint32_t ts, uint32_t is, uint32_t so, uint32_t rm, uint32_t ms), (ts, is, so, rm, ms));
|
||||
|
||||
extern "C" void GX__SetTevIndirect_801717ac(uint32_t ts, uint32_t is, uint32_t f, uint32_t bs, uint32_t ms, uint32_t ws, uint32_t wt, uint32_t ap, uint32_t il, uint32_t as) {
|
||||
GX_DEFER_CALL(GX__SetTevIndirect_801717ac, ts, is, f, bs, ms, ws, wt, ap, il, as);
|
||||
GXSetTevIndirect((GXTevStageID)std::min(ts,15u), (GXIndTexStageID)std::min(is,3u), (GXIndTexFormat)f,
|
||||
(GXIndTexBiasSel)bs, (GXIndTexMtxID)ms, (GXIndTexWrap)ws, (GXIndTexWrap)wt,
|
||||
(GXBool)ap, (GXBool)il, (GXIndTexAlphaSel)as);
|
||||
|
||||
@@ -24,6 +24,7 @@ extern "C" GXFifoObj* GXInit(void* base, u32 size);
|
||||
*/
|
||||
extern "C" uint32_t GX__Init_8016b850(uint32_t fifoBase, uint32_t fifoSize)
|
||||
{
|
||||
GX_SYNC_INLINE();
|
||||
constexpr uint32_t kFifoObjAddr = 0x80343740u;
|
||||
constexpr uint32_t kGXDataAddr = 0x803437C0u;
|
||||
constexpr uint32_t kGXDataSize = 0x600u;
|
||||
@@ -91,16 +92,16 @@ PPC_NATIVE_OVERRIDE(8016b850, GX__Init_8016b850, uint32_t, (uint32_t fifoBase, u
|
||||
|
||||
extern "C" void GX__InitFifoBase_8016c7c8(uint32_t fa, uint32_t ba, uint32_t s) { GXInitFifoBase((GXFifoObj*)GuestToHostPtr(fa, sizeof(GXFifoObj)), GuestToHostPtr(ba, s), s); }
|
||||
|
||||
extern "C" void GX__SetCPUFifo_8016c94c(uint32_t fa) { GXSetCPUFifo((GXFifoObj*)GuestToHostPtr(fa, sizeof(GXFifoObj))); }
|
||||
extern "C" void GX__SetCPUFifo_8016c94c(uint32_t fa) { GX_SYNC_INLINE(); GXSetCPUFifo((GXFifoObj*)GuestToHostPtr(fa, sizeof(GXFifoObj))); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016c94c, GX__SetCPUFifo_8016c94c, (uint32_t fa), (fa));
|
||||
|
||||
extern "C" void GX__SetGPFifo_8016cb2c(uint32_t fa) { GXSetGPFifo((GXFifoObj*)GuestToHostPtr(fa, sizeof(GXFifoObj))); }
|
||||
extern "C" void GX__SetGPFifo_8016cb2c(uint32_t fa) { GX_SYNC_INLINE(); GXSetGPFifo((GXFifoObj*)GuestToHostPtr(fa, sizeof(GXFifoObj))); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016cb2c, GX__SetGPFifo_8016cb2c, (uint32_t fa), (fa));
|
||||
|
||||
extern "C" void __GX__SaveFifo_8016cdbc(uint32_t fa) { GXSaveCPUFifo((GXFifoObj*)GuestToHostPtr(fa, sizeof(GXFifoObj))); }
|
||||
extern "C" void __GX__SaveFifo_8016cdbc(uint32_t fa) { GX_SYNC_INLINE(); GXSaveCPUFifo((GXFifoObj*)GuestToHostPtr(fa, sizeof(GXFifoObj))); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016cdbc, __GX__SaveFifo_8016cdbc, (uint32_t fa), (fa));
|
||||
|
||||
extern "C" void GX__GetCPUFifo_8016cf10(uint32_t fa) { auto* d=(GXFifoObj*)GuestToHostPtr(fa, sizeof(GXFifoObj)); auto* s=GXGetCPUFifo(); if(d&&s) std::memcpy(d,s,sizeof(GXFifoObj)); }
|
||||
extern "C" void GX__GetCPUFifo_8016cf10(uint32_t fa) { GX_SYNC_INLINE(); auto* d=(GXFifoObj*)GuestToHostPtr(fa, sizeof(GXFifoObj)); auto* s=GXGetCPUFifo(); if(d&&s) std::memcpy(d,s,sizeof(GXFifoObj)); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016cf10, GX__GetCPUFifo_8016cf10, (uint32_t fa), (fa));
|
||||
|
||||
extern "C" void __GX__FifoInit_8016d180()
|
||||
@@ -137,6 +138,7 @@ extern "C" void __GX__FifoInit_8016d180()
|
||||
|
||||
extern "C" void __GX__PEInit_8016ee14()
|
||||
{
|
||||
GX_SYNC_INLINE();
|
||||
constexpr uint32_t kPeTokenInterruptId = 0x12u;
|
||||
constexpr uint32_t kPeFinishInterruptId = 0x13u;
|
||||
constexpr uint32_t kPeTokenInterruptMask = 0x1000u;
|
||||
@@ -165,7 +167,16 @@ PPC_NATIVE_OVERRIDE_VOID(8016ee14, __GX__PEInit_8016ee14, (), ());
|
||||
// Display List Recording
|
||||
// ============================================================================
|
||||
|
||||
namespace {
|
||||
// Display-list recording runs inline: the guest list buffer and its size are needed synchronously.
|
||||
bool g_dlInline = false;
|
||||
}
|
||||
|
||||
extern "C" void GX__BeginDisplayList_80172e00(uint32_t la, uint32_t s) {
|
||||
if (!g_dlInline) {
|
||||
GxWorker::BeginInline();
|
||||
g_dlInline = true;
|
||||
}
|
||||
try {
|
||||
uint32_t gd = Memory::Read32(kGXDataPtrAddr);
|
||||
if (!gd) return;
|
||||
@@ -188,6 +199,13 @@ extern "C" void GX__BeginDisplayList_80172e00(uint32_t la, uint32_t s) {
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80172e00, GX__BeginDisplayList_80172e00, (uint32_t la, uint32_t s), (la, s));
|
||||
|
||||
static void EndDisplayListInline() {
|
||||
if (g_dlInline) {
|
||||
g_dlInline = false;
|
||||
GxWorker::EndInline();
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" uint32_t GX__EndDisplayList_80172eb4() {
|
||||
try {
|
||||
GXFlush();
|
||||
@@ -214,12 +232,14 @@ extern "C" uint32_t GX__EndDisplayList_80172eb4() {
|
||||
// "inactive" whenever the GXData pointer was null, so recording must
|
||||
// stop here even on the gd == 0 path.
|
||||
EndDisplayListRecording();
|
||||
EndDisplayListInline();
|
||||
|
||||
return wrapped == 0 ? Memory::Read32(kDlCountAddr) : 0;
|
||||
} catch (...) {
|
||||
// Leaving the shadow active would silently swallow every subsequent
|
||||
// FIFO write into a dead list.
|
||||
EndDisplayListRecording();
|
||||
EndDisplayListInline();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -229,5 +249,8 @@ PPC_NATIVE_OVERRIDE(80172EB4, GX__EndDisplayList_80172eb4, uint32_t, (), ());
|
||||
// Flush
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__Flush_8016e654() { GXFlush(); }
|
||||
extern "C" void GX__Flush_8016e654() {
|
||||
GX_DEFER_CALL0(GX__Flush_8016e654);
|
||||
GXFlush();
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016e654, GX__Flush_8016e654, (), ());
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <vector>
|
||||
#include <type_traits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
@@ -300,6 +301,9 @@ void EndDisplayListRecording();
|
||||
void WriteDisplayListData(uint32_t val, uint32_t sizeBytes);
|
||||
void BeginNextAuroraFrameWithRetry(std::chrono::milliseconds timeout = std::chrono::milliseconds(100));
|
||||
void EnsureAuroraFrameActive();
|
||||
void EnsureAuroraFrameForFifo();
|
||||
// Array base/stride as last set through GXSetArray on the game thread.
|
||||
bool ApiVtxArray(uint32_t attr, uint32_t& base, uint32_t& stride);
|
||||
|
||||
GXColor DecodeGxColor(uint32_t colorWord);
|
||||
void WriteGuestFloat(uint32_t addr, float value, const char* label = nullptr);
|
||||
@@ -342,6 +346,17 @@ bool ValidateTlutData(uint32_t objAddr, const TlutObjMeta& meta);
|
||||
TexObjMeta& GetTexObjMeta(uint32_t addr);
|
||||
TexObjMeta ExtractTexObjMetaFromGuest(uint32_t addr);
|
||||
bool TryGetOrExtractTexObjMeta(uint32_t addr, TexObjMeta& outMeta);
|
||||
|
||||
// Guest GXTexObj bytes captured on the game thread; while a scope is active on a thread, texobj
|
||||
// decoding for that address reads the captured bytes instead of live guest memory.
|
||||
using TexObjBytes = std::array<uint8_t, 32>;
|
||||
TexObjBytes CaptureTexObjBytes(uint32_t addr);
|
||||
struct TexObjSnapshotScope {
|
||||
TexObjSnapshotScope(uint32_t addr, const TexObjBytes& bytes) noexcept;
|
||||
~TexObjSnapshotScope();
|
||||
TexObjSnapshotScope(const TexObjSnapshotScope&) = delete;
|
||||
TexObjSnapshotScope& operator=(const TexObjSnapshotScope&) = delete;
|
||||
};
|
||||
TlutObjMeta& GetTlutObjMeta(uint32_t addr);
|
||||
GXTexObj* GetHostTexObj(uint32_t addr);
|
||||
GXTexObj* TryGetHostTexObj(uint32_t addr);
|
||||
|
||||
@@ -66,7 +66,7 @@ extern "C" void GX__LoadLightObjImm_80170320(uint32_t la, uint32_t lid) {
|
||||
if (!ValidateGuestLightObj(la, "GXLoadLightObjImm")) return;
|
||||
GXLightObj host{};
|
||||
InitializeHostLightFromGuest(host, la);
|
||||
GXLoadLightObjImm(&host, static_cast<GXLightID>(lid));
|
||||
GxWorker::Run([host, lid]() mutable { GXLoadLightObjImm(&host, static_cast<GXLightID>(lid)); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170320, GX__LoadLightObjImm_80170320, (uint32_t la, uint32_t lid), (la, lid));
|
||||
|
||||
@@ -77,21 +77,23 @@ PPC_NATIVE_OVERRIDE_VOID(80170320, GX__LoadLightObjImm_80170320, (uint32_t la, u
|
||||
extern "C" void GX__SetChanAmbColor_8017039c(uint32_t c, uint32_t cp) {
|
||||
EnsureAuroraFrameActive();
|
||||
GXColor color = DecodeGxColor(Memory::Read32(cp));
|
||||
GXSetChanAmbColor((GXChannelID)c, color);
|
||||
GxWorker::Run([c, color] { GXSetChanAmbColor((GXChannelID)c, color); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8017039c, GX__SetChanAmbColor_8017039c, (uint32_t c, uint32_t cp), (c, cp));
|
||||
|
||||
extern "C" void GX__SetChanMatColor_80170474(uint32_t c, uint32_t cp) {
|
||||
EnsureAuroraFrameActive();
|
||||
GXColor color = DecodeGxColor(Memory::Read32(cp));
|
||||
GXSetChanMatColor((GXChannelID)c, color);
|
||||
GxWorker::Run([c, color] { GXSetChanMatColor((GXChannelID)c, color); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170474, GX__SetChanMatColor_80170474, (uint32_t c, uint32_t cp), (c, cp));
|
||||
|
||||
extern "C" void GX__SetNumChans_8017054c(uint32_t n) { GXSetNumChans((u8)n); }
|
||||
extern "C" void GX__SetNumChans_8017054c(uint32_t n) {
|
||||
GX_DEFER_CALL(GX__SetNumChans_8017054c, n); GXSetNumChans((u8)n); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(8017054c, GX__SetNumChans_8017054c, (uint32_t n), (n));
|
||||
|
||||
extern "C" void GX__SetChanCtrl_80170570(uint32_t ch, uint32_t en, uint32_t as, uint32_t ms, uint32_t lm, uint32_t df, uint32_t af) {
|
||||
GX_DEFER_CALL(GX__SetChanCtrl_80170570, ch, en, as, ms, lm, df, af);
|
||||
GXSetChanCtrl((GXChannelID)ch, en!=0, (GXColorSrc)as, (GXColorSrc)ms, lm, (GXDiffuseFn)df, (GXAttnFn)af);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170570, GX__SetChanCtrl_80170570, (uint32_t ch, uint32_t en, uint32_t as, uint32_t ms, uint32_t lm, uint32_t df, uint32_t af), (ch, en, as, ms, lm, df, af));
|
||||
|
||||
@@ -110,17 +110,65 @@ bool g_tlutObjBoundsDirty = true;
|
||||
// word3 data address (<<5), word4 user data, word5 texFmt, word6 tlut, byte 0x1F flags. Exact
|
||||
// bit positions are decoded inline below.
|
||||
|
||||
namespace {
|
||||
thread_local uint32_t t_texObjSnapshotAddr = 0;
|
||||
thread_local const uint8_t* t_texObjSnapshotBytes = nullptr;
|
||||
|
||||
const uint8_t* TexObjSnapshotFor(uint32_t addr) noexcept {
|
||||
return (t_texObjSnapshotBytes != nullptr && addr == t_texObjSnapshotAddr) ? t_texObjSnapshotBytes : nullptr;
|
||||
}
|
||||
|
||||
uint32_t ReadTexObj32(uint32_t addr, uint32_t offset) {
|
||||
if (const uint8_t* bytes = TexObjSnapshotFor(addr)) return BigEndian::Read32(bytes + offset);
|
||||
return Memory::Read32(addr + offset);
|
||||
}
|
||||
|
||||
uint8_t ReadTexObj8(uint32_t addr, uint32_t offset) {
|
||||
if (const uint8_t* bytes = TexObjSnapshotFor(addr)) return bytes[offset];
|
||||
return Memory::Read8(addr + offset);
|
||||
}
|
||||
|
||||
uint64_t ReadTexObj64(uint32_t addr, uint32_t offset) {
|
||||
if (const uint8_t* bytes = TexObjSnapshotFor(addr)) {
|
||||
return (static_cast<uint64_t>(BigEndian::Read32(bytes + offset)) << 32) | BigEndian::Read32(bytes + offset + 4);
|
||||
}
|
||||
return Memory::Read64(addr + offset);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TexObjBytes CaptureTexObjBytes(uint32_t addr) {
|
||||
TexObjBytes bytes{};
|
||||
for (uint32_t offset = 0; offset < bytes.size(); offset += 4) {
|
||||
const uint32_t word = Memory::Read32(addr + offset);
|
||||
bytes[offset + 0] = static_cast<uint8_t>(word >> 24);
|
||||
bytes[offset + 1] = static_cast<uint8_t>(word >> 16);
|
||||
bytes[offset + 2] = static_cast<uint8_t>(word >> 8);
|
||||
bytes[offset + 3] = static_cast<uint8_t>(word);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
TexObjSnapshotScope::TexObjSnapshotScope(uint32_t addr, const TexObjBytes& bytes) noexcept {
|
||||
t_texObjSnapshotAddr = addr;
|
||||
t_texObjSnapshotBytes = bytes.data();
|
||||
}
|
||||
|
||||
TexObjSnapshotScope::~TexObjSnapshotScope() {
|
||||
t_texObjSnapshotAddr = 0;
|
||||
t_texObjSnapshotBytes = nullptr;
|
||||
}
|
||||
|
||||
TexObjMeta ExtractTexObjMetaFromGuest(uint32_t addr) {
|
||||
TexObjMeta meta{};
|
||||
try {
|
||||
uint32_t word0 = Memory::Read32(addr + 0x00);
|
||||
uint32_t word1 = Memory::Read32(addr + 0x04);
|
||||
uint32_t word2 = Memory::Read32(addr + 0x08);
|
||||
uint32_t word3 = Memory::Read32(addr + 0x0C);
|
||||
uint32_t word4 = Memory::Read32(addr + 0x10);
|
||||
uint32_t word5 = Memory::Read32(addr + 0x14);
|
||||
uint32_t word6 = Memory::Read32(addr + 0x18);
|
||||
uint8_t flags = Memory::Read8(addr + 0x1F);
|
||||
uint32_t word0 = ReadTexObj32(addr, 0x00);
|
||||
uint32_t word1 = ReadTexObj32(addr, 0x04);
|
||||
uint32_t word2 = ReadTexObj32(addr, 0x08);
|
||||
uint32_t word3 = ReadTexObj32(addr, 0x0C);
|
||||
uint32_t word4 = ReadTexObj32(addr, 0x10);
|
||||
uint32_t word5 = ReadTexObj32(addr, 0x14);
|
||||
uint32_t word6 = ReadTexObj32(addr, 0x18);
|
||||
uint8_t flags = ReadTexObj8(addr, 0x1F);
|
||||
|
||||
meta.wrapS = word0 & 0x3;
|
||||
meta.wrapT = (word0 >> 2) & 0x3;
|
||||
@@ -449,10 +497,10 @@ TexObjMeta MergeGuestTexObjMeta(const TexObjMeta& cached, const TexObjMeta& gues
|
||||
// caller must treat the shadow as absent) when the address is unreadable.
|
||||
bool ReadTexObjShadow(uint32_t addr, uint64_t (&out)[4]) noexcept {
|
||||
try {
|
||||
out[0] = Memory::Read64(addr + 0x00);
|
||||
out[1] = Memory::Read64(addr + 0x08);
|
||||
out[2] = Memory::Read64(addr + 0x10);
|
||||
out[3] = Memory::Read64(addr + 0x18);
|
||||
out[0] = ReadTexObj64(addr, 0x00);
|
||||
out[1] = ReadTexObj64(addr, 0x08);
|
||||
out[2] = ReadTexObj64(addr, 0x10);
|
||||
out[3] = ReadTexObj64(addr, 0x18);
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
@@ -642,8 +690,11 @@ extern "C" void GxNotifyGuestRamDmaWrite(uint32_t addr, uint32_t size) {
|
||||
if (size == 0) {
|
||||
return;
|
||||
}
|
||||
MarkTexObjsDirtyForRange(addr, size);
|
||||
MarkTlutObjsDirtyForRange(addr, size);
|
||||
// Ordered with recorded GX work: retiring EFB copy textures emits aurora commands.
|
||||
GxWorker::Run([addr, size] {
|
||||
MarkTexObjsDirtyForRange(addr, size);
|
||||
MarkTlutObjsDirtyForRange(addr, size);
|
||||
});
|
||||
GxNotifyDisplayListMemoryWrite(addr, size);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,17 +6,20 @@
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetBlendMode_8017277c(uint32_t t, uint32_t s, uint32_t d, uint32_t op) {
|
||||
GX_DEFER_CALL(GX__SetBlendMode_8017277c, t, s, d, op);
|
||||
GXSetBlendMode(static_cast<GXBlendMode>(t), static_cast<GXBlendFactor>(s),
|
||||
static_cast<GXBlendFactor>(d), static_cast<GXLogicOp>(op));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8017277c, GX__SetBlendMode_8017277c, (uint32_t t, uint32_t s, uint32_t d, uint32_t op), (t, s, d, op));
|
||||
|
||||
extern "C" void GX__SetColorUpdate_801727cc(uint32_t en) {
|
||||
GX_DEFER_CALL(GX__SetColorUpdate_801727cc, en);
|
||||
GXSetColorUpdate(static_cast<GXBool>(en));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801727cc, GX__SetColorUpdate_801727cc, (uint32_t en), (en));
|
||||
|
||||
extern "C" void GX__SetAlphaUpdate_801727f8(uint32_t en) {
|
||||
GX_DEFER_CALL(GX__SetAlphaUpdate_801727f8, en);
|
||||
GXSetAlphaUpdate(static_cast<GXBool>(en));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801727f8, GX__SetAlphaUpdate_801727f8, (uint32_t en), (en));
|
||||
@@ -26,11 +29,13 @@ PPC_NATIVE_OVERRIDE_VOID(801727f8, GX__SetAlphaUpdate_801727f8, (uint32_t en), (
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetZMode_80172824(uint32_t ce, uint32_t f, uint32_t ue) {
|
||||
GX_DEFER_CALL(GX__SetZMode_80172824, ce, f, ue);
|
||||
GXSetZMode(static_cast<GXBool>(ce), static_cast<GXCompare>(f), static_cast<GXBool>(ue));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80172824, GX__SetZMode_80172824, (uint32_t ce, uint32_t f, uint32_t ue), (ce, f, ue));
|
||||
|
||||
extern "C" void GX__SetZCompLoc_80172858(uint32_t bt) {
|
||||
GX_DEFER_CALL(GX__SetZCompLoc_80172858, bt);
|
||||
GXSetZCompLoc(static_cast<GXBool>(bt));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80172858, GX__SetZCompLoc_80172858, (uint32_t bt), (bt));
|
||||
@@ -40,16 +45,19 @@ PPC_NATIVE_OVERRIDE_VOID(80172858, GX__SetZCompLoc_80172858, (uint32_t bt), (bt)
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetPixelFmt_80172888(uint32_t pf, uint32_t zf) {
|
||||
GX_DEFER_CALL(GX__SetPixelFmt_80172888, pf, zf);
|
||||
GXSetPixelFmt(static_cast<GXPixelFmt>(pf), static_cast<GXZFmt16>(zf));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80172888, GX__SetPixelFmt_80172888, (uint32_t pf, uint32_t zf), (pf, zf));
|
||||
|
||||
extern "C" void GX__SetDither_80172930(uint32_t d) {
|
||||
GX_DEFER_CALL(GX__SetDither_80172930, d);
|
||||
GXSetDither(static_cast<GXBool>(d));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80172930, GX__SetDither_80172930, (uint32_t d), (d));
|
||||
|
||||
extern "C" void GX__SetDstAlpha_8017295c(uint32_t en, uint32_t a) {
|
||||
GX_DEFER_CALL(GX__SetDstAlpha_8017295c, en, a);
|
||||
GXSetDstAlpha(static_cast<GXBool>(en), static_cast<u8>(a));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8017295c, GX__SetDstAlpha_8017295c, (uint32_t en, uint32_t a), (en, a));
|
||||
@@ -59,17 +67,20 @@ PPC_NATIVE_OVERRIDE_VOID(8017295c, GX__SetDstAlpha_8017295c, (uint32_t en, uint3
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetFog_801722cc(uint32_t t, float sz, float ez, float nz, float fz, uint32_t cp) {
|
||||
GXSetFog((GXFogType)t, sz, ez, nz, fz, DecodeGxColor(Memory::Read32(cp)));
|
||||
const GXColor color = DecodeGxColor(Memory::Read32(cp));
|
||||
GxWorker::Run([=] { GXSetFog((GXFogType)t, sz, ez, nz, fz, color); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801722cc, GX__SetFog_801722cc, (uint32_t t, float sz, float ez, float nz, float fz, uint32_t cp), (t, sz, ez, nz, fz, cp));
|
||||
|
||||
extern "C" void GX__SetAlphaCompare_80172088(uint32_t c0, uint32_t r0, uint32_t op, uint32_t c1, uint32_t r1) {
|
||||
GX_DEFER_CALL(GX__SetAlphaCompare_80172088, c0, r0, op, c1, r1);
|
||||
g_alphaCompareValid = true;
|
||||
GXSetAlphaCompare((GXCompare)c0, (u8)r0, (GXAlphaOp)op, (GXCompare)c1, (u8)r1);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80172088, GX__SetAlphaCompare_80172088, (uint32_t c0, uint32_t r0, uint32_t op, uint32_t c1, uint32_t r1), (c0, r0, op, c1, r1));
|
||||
|
||||
extern "C" void GX__SetZTexture_801720c0(uint32_t op, uint32_t f, uint32_t b) { GXSetZTexture((GXZTexOp)op, (GXTexFmt)f, b); }
|
||||
extern "C" void GX__SetZTexture_801720c0(uint32_t op, uint32_t f, uint32_t b) {
|
||||
GX_DEFER_CALL(GX__SetZTexture_801720c0, op, f, b); GXSetZTexture((GXZTexOp)op, (GXTexFmt)f, b); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(801720c0, GX__SetZTexture_801720c0, (uint32_t op, uint32_t f, uint32_t b), (op, f, b));
|
||||
|
||||
// ============================================================================
|
||||
@@ -77,12 +88,15 @@ PPC_NATIVE_OVERRIDE_VOID(801720c0, GX__SetZTexture_801720c0, (uint32_t op, uint3
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetCullMode_8016f3b8(uint32_t m) {
|
||||
GX_DEFER_CALL(GX__SetCullMode_8016f3b8, m);
|
||||
GXSetCullMode(static_cast<GXCullMode>(m));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f3b8, GX__SetCullMode_8016f3b8, (uint32_t m), (m));
|
||||
|
||||
extern "C" void GX__SetCoPlanar_8016f3e0(uint32_t en) { GXSetCoPlanar((GXBool)en); }
|
||||
extern "C" void GX__SetCoPlanar_8016f3e0(uint32_t en) {
|
||||
GX_DEFER_CALL(GX__SetCoPlanar_8016f3e0, en); GXSetCoPlanar((GXBool)en); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f3e0, GX__SetCoPlanar_8016f3e0, (uint32_t en), (en));
|
||||
|
||||
extern "C" void GX__SetClipMode_8017351c(uint32_t m) { GXSetClipMode((GXClipMode)m); }
|
||||
extern "C" void GX__SetClipMode_8017351c(uint32_t m) {
|
||||
GX_DEFER_CALL(GX__SetClipMode_8017351c, m); GXSetClipMode((GXClipMode)m); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(8017351c, GX__SetClipMode_8017351c, (uint32_t m), (m));
|
||||
|
||||
@@ -7,14 +7,23 @@ extern "C" void __GXSetSUTexRegs();
|
||||
// FIFO Write Helpers
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX_HLE_FIFO_WriteFloat(float val) {
|
||||
u32 raw; std::memcpy(&raw, &val, 4);
|
||||
try { HleFifoWrite(raw, 4); } catch (...) { RT_LOGF(RT_TAG_GX, "FIFO write float failed\n"); }
|
||||
static void FifoWrite(u32 val, uint32_t sizeBytes) {
|
||||
if (GxWorker::ShouldDefer()) {
|
||||
EnsureAuroraFrameForFifo();
|
||||
GxWorker::FifoWrite(val, sizeBytes);
|
||||
return;
|
||||
}
|
||||
HleFifoWrite(val, sizeBytes);
|
||||
}
|
||||
|
||||
extern "C" void GX_HLE_FIFO_Write32(uint32_t val) { HleFifoWrite(val, 4); }
|
||||
extern "C" void GX_HLE_FIFO_Write16(uint16_t val) { HleFifoWrite(static_cast<u32>(val), 2); }
|
||||
extern "C" void GX_HLE_FIFO_Write8(uint8_t val) { HleFifoWrite(static_cast<u32>(val), 1); }
|
||||
extern "C" void GX_HLE_FIFO_WriteFloat(float val) {
|
||||
u32 raw; std::memcpy(&raw, &val, 4);
|
||||
try { FifoWrite(raw, 4); } catch (...) { RT_LOGF(RT_TAG_GX, "FIFO write float failed\n"); }
|
||||
}
|
||||
|
||||
extern "C" void GX_HLE_FIFO_Write32(uint32_t val) { FifoWrite(val, 4); }
|
||||
extern "C" void GX_HLE_FIFO_Write16(uint16_t val) { FifoWrite(static_cast<u32>(val), 2); }
|
||||
extern "C" void GX_HLE_FIFO_Write8(uint8_t val) { FifoWrite(static_cast<u32>(val), 1); }
|
||||
|
||||
extern "C" void GX__SetDrawSync_8016ed08(uint32_t token) {
|
||||
(void)token;
|
||||
@@ -37,6 +46,7 @@ extern "C" void GX__FinishInterruptHandler_8016ed94() {
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016ed94, GX__FinishInterruptHandler_8016ed94, (), ());
|
||||
|
||||
extern "C" void GX__DrawDone_8016eab0() {
|
||||
GX_SYNC_INLINE();
|
||||
try { Memory::Write8(kGxDrawDoneFlagAddr, 0); } catch (...) {}
|
||||
GXDrawDone(); GX__FinishInterruptHandler_8016ed94();
|
||||
}
|
||||
@@ -44,7 +54,7 @@ PPC_NATIVE_OVERRIDE_VOID(8016eab0, GX__DrawDone_8016eab0, (), ());
|
||||
|
||||
extern "C" void GX__PixModeSync_8016eb70() {
|
||||
try { uint32_t gd = Memory::Read32(kGXDataPtrAddr); if (gd) Memory::Write16(gd + 2, 0); } catch (...) {}
|
||||
GXPixModeSync();
|
||||
GxWorker::Run([] { GXPixModeSync(); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016eb70, GX__PixModeSync_8016eb70, (), ());
|
||||
|
||||
@@ -60,7 +70,7 @@ PPC_NATIVE_OVERRIDE_VOID(8016b720, __GX__InitRevisionBits_8016b720, (), ());
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void __GX__SetSUTexRegs_801712f0() {
|
||||
__GXSetSUTexRegs();
|
||||
GxWorker::Run([] { __GXSetSUTexRegs(); });
|
||||
try { uint32_t gd = Memory::Read32(kGXDataPtrAddr); if (gd) Memory::Write16(gd + 2, 0); } catch (...) {}
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801712f0, __GX__SetSUTexRegs_801712f0, (), ());
|
||||
@@ -82,7 +92,7 @@ PPC_NATIVE_OVERRIDE_VOID(80171c28, __GX__FlushTextureState_80171c28, (), ());
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetDispCopyFrame2Field_8016f5f8(uint32_t f) {
|
||||
GXSetDispCopyFrame2Field(f);
|
||||
GxWorker::Run([f] { GXSetDispCopyFrame2Field(f); });
|
||||
try {
|
||||
const uint32_t gd = Memory::Read32(kGXDataPtrAddr);
|
||||
if (gd) {
|
||||
@@ -94,7 +104,7 @@ extern "C" void GX__SetDispCopyFrame2Field_8016f5f8(uint32_t f) {
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f5f8, GX__SetDispCopyFrame2Field_8016f5f8, (uint32_t f), (f));
|
||||
|
||||
extern "C" void GX__SetCopyClamp_8016f618(uint32_t c) {
|
||||
GXSetCopyClamp(static_cast<GXFBClamp>(c));
|
||||
GxWorker::Run([c] { GXSetCopyClamp(static_cast<GXFBClamp>(c)); });
|
||||
try {
|
||||
const uint32_t gd = Memory::Read32(kGXDataPtrAddr);
|
||||
if (gd) {
|
||||
@@ -107,7 +117,7 @@ extern "C" void GX__SetCopyClamp_8016f618(uint32_t c) {
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f618, GX__SetCopyClamp_8016f618, (uint32_t c), (c));
|
||||
|
||||
extern "C" void GX__ClearBoundingBox_8016fecc() {
|
||||
GXClearBoundingBox();
|
||||
GxWorker::Run([] { GXClearBoundingBox(); });
|
||||
try {
|
||||
const uint32_t gd = Memory::Read32(kGXDataPtrAddr);
|
||||
if (gd) Memory::Write16(gd + 2, 0);
|
||||
|
||||
@@ -25,6 +25,7 @@ inline bool TevSwapOk(uint32_t id) { return GxTevIdOk(id, GX_MAX_TEVSWAP, "TEV s
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetNumTevStages_801722a8(uint32_t n) {
|
||||
GX_DEFER_CALL(GX__SetNumTevStages_801722a8, n);
|
||||
// GXSetNumTevStages takes a count, not an index, so the inclusive bound is
|
||||
// GX_MAX_TEVSTAGE itself.
|
||||
if (n > GX_MAX_TEVSTAGE) {
|
||||
@@ -36,12 +37,14 @@ extern "C" void GX__SetNumTevStages_801722a8(uint32_t n) {
|
||||
PPC_NATIVE_OVERRIDE_VOID(801722a8, GX__SetNumTevStages_801722a8, (uint32_t n), (n));
|
||||
|
||||
extern "C" void GX__SetTevOp_80171c4c(uint32_t s, uint32_t m) {
|
||||
GX_DEFER_CALL(GX__SetTevOp_80171c4c, s, m);
|
||||
if (!TevStageOk(s)) return;
|
||||
GXSetTevOp((GXTevStageID)s, (GXTevMode)m);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171c4c, GX__SetTevOp_80171c4c, (uint32_t s, uint32_t m), (s, m));
|
||||
|
||||
extern "C" void GX__SetTevOrder_8017214c(uint32_t s, uint32_t c, uint32_t m, uint32_t col) {
|
||||
GX_DEFER_CALL(GX__SetTevOrder_8017214c, s, c, m, col);
|
||||
if (!TevStageOk(s)) return;
|
||||
GXSetTevOrder((GXTevStageID)s, (GXTexCoordID)c, (GXTexMapID)m, (GXChannelID)col);
|
||||
}
|
||||
@@ -52,12 +55,14 @@ PPC_NATIVE_OVERRIDE_VOID(8017214c, GX__SetTevOrder_8017214c, (uint32_t s, uint32
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetTevColorIn_80171ce0(uint32_t s, uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
|
||||
GX_DEFER_CALL(GX__SetTevColorIn_80171ce0, s, a, b, c, d);
|
||||
if (!TevStageOk(s)) return;
|
||||
GXSetTevColorIn((GXTevStageID)s, (GXTevColorArg)a, (GXTevColorArg)b, (GXTevColorArg)c, (GXTevColorArg)d);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171ce0, GX__SetTevColorIn_80171ce0, (uint32_t s, uint32_t a, uint32_t b, uint32_t c, uint32_t d), (s, a, b, c, d));
|
||||
|
||||
extern "C" void GX__SetTevAlphaIn_80171d20(uint32_t s, uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
|
||||
GX_DEFER_CALL(GX__SetTevAlphaIn_80171d20, s, a, b, c, d);
|
||||
if (!TevStageOk(s)) return;
|
||||
GXSetTevAlphaIn((GXTevStageID)s, (GXTevAlphaArg)a, (GXTevAlphaArg)b, (GXTevAlphaArg)c, (GXTevAlphaArg)d);
|
||||
}
|
||||
@@ -68,12 +73,14 @@ PPC_NATIVE_OVERRIDE_VOID(80171d20, GX__SetTevAlphaIn_80171d20, (uint32_t s, uint
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetTevColorOp_80171d60(uint32_t s, uint32_t op, uint32_t b, uint32_t sc, uint32_t cl, uint32_t or_) {
|
||||
GX_DEFER_CALL(GX__SetTevColorOp_80171d60, s, op, b, sc, cl, or_);
|
||||
if (!TevStageOk(s) || !TevRegOk(or_)) return;
|
||||
GXSetTevColorOp((GXTevStageID)s, (GXTevOp)op, (GXTevBias)b, (GXTevScale)sc, (GXBool)cl, (GXTevRegID)or_);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171d60, GX__SetTevColorOp_80171d60, (uint32_t s, uint32_t op, uint32_t b, uint32_t sc, uint32_t cl, uint32_t or_), (s, op, b, sc, cl, or_));
|
||||
|
||||
extern "C" void GX__SetTevAlphaOp_80171db8(uint32_t s, uint32_t op, uint32_t b, uint32_t sc, uint32_t cl, uint32_t or_) {
|
||||
GX_DEFER_CALL(GX__SetTevAlphaOp_80171db8, s, op, b, sc, cl, or_);
|
||||
if (!TevStageOk(s) || !TevRegOk(or_)) return;
|
||||
GXSetTevAlphaOp((GXTevStageID)s, (GXTevOp)op, (GXTevBias)b, (GXTevScale)sc, (GXBool)cl, (GXTevRegID)or_);
|
||||
}
|
||||
@@ -86,7 +93,7 @@ PPC_NATIVE_OVERRIDE_VOID(80171db8, GX__SetTevAlphaOp_80171db8, (uint32_t s, uint
|
||||
extern "C" void GX__SetTevColor_80171e10(uint32_t id, uint32_t cp) {
|
||||
if (!TevRegOk(id)) return;
|
||||
const uint8_t* p=Memory::GetPointer(cp, 4); GXColor c; c.r=p[0]; c.g=p[1]; c.b=p[2]; c.a=p[3];
|
||||
GXSetTevColor((GXTevRegID)id, c);
|
||||
GxWorker::Run([id, c] { GXSetTevColor((GXTevRegID)id, c); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171e10, GX__SetTevColor_80171e10, (uint32_t id, uint32_t cp), (id, cp));
|
||||
|
||||
@@ -94,21 +101,23 @@ extern "C" void GX__SetTevColorS10_80171e70(uint32_t id, uint32_t cp) {
|
||||
if (!TevRegOk(id)) return;
|
||||
const uint8_t* p=Memory::GetPointer(cp, 8); GXColorS10 c;
|
||||
c.r=(p[0]<<8)|p[1]; c.g=(p[2]<<8)|p[3]; c.b=(p[4]<<8)|p[5]; c.a=(p[6]<<8)|p[7];
|
||||
GXSetTevColorS10((GXTevRegID)id, c);
|
||||
GxWorker::Run([id, c] { GXSetTevColorS10((GXTevRegID)id, c); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171e70, GX__SetTevColorS10_80171e70, (uint32_t id, uint32_t cp), (id, cp));
|
||||
|
||||
extern "C" void GX__SetTevKColor_80171ed4(uint32_t id, uint32_t cp) {
|
||||
if (!TevKColorOk(id)) return;
|
||||
const uint8_t* p=Memory::GetPointer(cp, 4); GXColor c; c.r=p[0]; c.g=p[1]; c.b=p[2]; c.a=p[3];
|
||||
GXSetTevKColor((GXTevKColorID)id, c);
|
||||
GxWorker::Run([id, c] { GXSetTevKColor((GXTevKColorID)id, c); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171ed4, GX__SetTevKColor_80171ed4, (uint32_t id, uint32_t cp), (id, cp));
|
||||
|
||||
extern "C" void GX__SetTevKColorSel_80171f30(uint32_t s, uint32_t sel) { if (!TevStageOk(s)) return; GXSetTevKColorSel((GXTevStageID)s, (GXTevKColorSel)sel); }
|
||||
extern "C" void GX__SetTevKColorSel_80171f30(uint32_t s, uint32_t sel) {
|
||||
GX_DEFER_CALL(GX__SetTevKColorSel_80171f30, s, sel); if (!TevStageOk(s)) return; GXSetTevKColorSel((GXTevStageID)s, (GXTevKColorSel)sel); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171f30, GX__SetTevKColorSel_80171f30, (uint32_t s, uint32_t sel), (s, sel));
|
||||
|
||||
extern "C" void GX__SetTevKAlphaSel_80171f80(uint32_t s, uint32_t sel) { if (!TevStageOk(s)) return; GXSetTevKAlphaSel((GXTevStageID)s, (GXTevKAlphaSel)sel); }
|
||||
extern "C" void GX__SetTevKAlphaSel_80171f80(uint32_t s, uint32_t sel) {
|
||||
GX_DEFER_CALL(GX__SetTevKAlphaSel_80171f80, s, sel); if (!TevStageOk(s)) return; GXSetTevKAlphaSel((GXTevStageID)s, (GXTevKAlphaSel)sel); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171f80, GX__SetTevKAlphaSel_80171f80, (uint32_t s, uint32_t sel), (s, sel));
|
||||
|
||||
// ============================================================================
|
||||
@@ -116,12 +125,14 @@ PPC_NATIVE_OVERRIDE_VOID(80171f80, GX__SetTevKAlphaSel_80171f80, (uint32_t s, ui
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetTevSwapModeTable_8017200c(uint32_t id, uint32_t r, uint32_t g, uint32_t b, uint32_t a) {
|
||||
GX_DEFER_CALL(GX__SetTevSwapModeTable_8017200c, id, r, g, b, a);
|
||||
if (!TevSwapOk(id)) return;
|
||||
GXSetTevSwapModeTable((GXTevSwapSel)id, (GXTevColorChan)r, (GXTevColorChan)g, (GXTevColorChan)b, (GXTevColorChan)a);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8017200c, GX__SetTevSwapModeTable_8017200c, (uint32_t id, uint32_t r, uint32_t g, uint32_t b, uint32_t a), (id, r, g, b, a));
|
||||
|
||||
extern "C" void GX__SetTevSwapMode_80171fd0(uint32_t s, uint32_t rs, uint32_t ts) {
|
||||
GX_DEFER_CALL(GX__SetTevSwapMode_80171fd0, s, rs, ts);
|
||||
if (!TevStageOk(s) || !TevSwapOk(rs) || !TevSwapOk(ts)) return;
|
||||
GXSetTevSwapMode((GXTevStageID)s, (GXTevSwapSel)rs, (GXTevSwapSel)ts);
|
||||
}
|
||||
|
||||
+179
-102
@@ -242,6 +242,31 @@ static uint32_t ComputeMaxMipLevel(uint16_t width, uint16_t height) noexcept {
|
||||
return levels;
|
||||
}
|
||||
|
||||
// GXInitTexObj* writes the guest struct synchronously; the host half runs on the GX worker with the
|
||||
// struct bytes from before that write, which is what an inline call's shadow capture sees.
|
||||
template <typename Hle, typename Guest>
|
||||
static void InitTexObjSplit(uint32_t oa, Hle&& hle, Guest&& guest) {
|
||||
if (!GxWorker::ShouldDefer()) {
|
||||
hle();
|
||||
guest();
|
||||
return;
|
||||
}
|
||||
std::optional<TexObjBytes> before;
|
||||
try {
|
||||
before = CaptureTexObjBytes(oa);
|
||||
} catch (...) {
|
||||
}
|
||||
guest();
|
||||
GxWorker::Submit([oa, before, hle = std::forward<Hle>(hle)] {
|
||||
if (before) {
|
||||
TexObjSnapshotScope snapshot(oa, *before);
|
||||
hle();
|
||||
} else {
|
||||
hle();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
extern "C" void GX__InitTexObj_801707f8(uint32_t oa, uint32_t da, uint32_t w, uint32_t h, uint32_t f, uint32_t ws, uint32_t wt, uint32_t m) {
|
||||
if (w == 0 || h == 0) {
|
||||
auto* cpu = TryGetCpuContext();
|
||||
@@ -250,12 +275,15 @@ extern "C" void GX__InitTexObj_801707f8(uint32_t oa, uint32_t da, uint32_t w, ui
|
||||
oa, da, w, h, f, ws, wt, m, cpu ? cpu->pc : 0u, cpu ? cpu->lr : 0u);
|
||||
}
|
||||
const uint32_t canonicalDataAddr = CanonicalizeGxMainRamAddress(da);
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex); GXTexObj* obj = CreateHostTexObj(oa); TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.dataAddr=canonicalDataAddr; meta.width=(u16)w; meta.height=(u16)h; meta.format=f; meta.wrapS=ws; meta.wrapT=wt; meta.mipmap=(m!=0); meta.userData=0; meta.needsUpload=true;
|
||||
GXInitTexObj(obj, GuestToHostPtr(da), (u16)w, (u16)h, (GXTexFmt)f, (GXTexWrapMode)ws, (GXTexWrapMode)wt, (GXBool)m); MarkHostTexObjConstructed(oa);
|
||||
// Also write to guest memory so reads work
|
||||
WriteGuestTexObj(oa, canonicalDataAddr, (u16)w, (u16)h, f, ws, wt, m != 0, false, 0);
|
||||
InitTexObjSplit(oa, [=] {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex); GXTexObj* obj = CreateHostTexObj(oa); TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.dataAddr=canonicalDataAddr; meta.width=(u16)w; meta.height=(u16)h; meta.format=f; meta.wrapS=ws; meta.wrapT=wt; meta.mipmap=(m!=0); meta.userData=0; meta.needsUpload=true;
|
||||
GXInitTexObj(obj, GuestToHostPtr(da), (u16)w, (u16)h, (GXTexFmt)f, (GXTexWrapMode)ws, (GXTexWrapMode)wt, (GXBool)m); MarkHostTexObjConstructed(oa);
|
||||
}, [=] {
|
||||
WriteGuestTexObj(oa, canonicalDataAddr, (u16)w, (u16)h, f, ws, wt, m != 0, false, 0);
|
||||
});
|
||||
}
|
||||
|
||||
PPC_NATIVE_OVERRIDE_VOID(801707f8, GX__InitTexObj_801707f8, (uint32_t oa, uint32_t da, uint32_t w, uint32_t h, uint32_t f, uint32_t ws, uint32_t wt, uint32_t m), (oa, da, w, h, f, ws, wt, m));
|
||||
|
||||
extern "C" void GX__InitTexObj_switch_80170938(uint32_t oa, uint32_t da, uint32_t w, uint32_t h, uint32_t f, uint32_t ws, uint32_t wt, uint32_t m) { GX__InitTexObj_801707f8(oa, da, w, h, f, ws, wt, m); }
|
||||
@@ -291,132 +319,151 @@ extern "C" void GX__InitTexObjCI_80170a04(uint32_t oa, uint32_t da, uint32_t w,
|
||||
oa, da, w, h, f, ws, wt, m, tl, cpu ? cpu->pc : 0u, cpu ? cpu->lr : 0u);
|
||||
}
|
||||
const uint32_t canonicalDataAddr = CanonicalizeGxMainRamAddress(da);
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex); GXTexObj* obj = CreateHostTexObj(oa); TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.dataAddr=canonicalDataAddr; meta.width=(u16)w; meta.height=(u16)h; meta.format=f; meta.wrapS=ws; meta.wrapT=wt; meta.mipmap=(m!=0); meta.tlut=tl; meta.userData=0; meta.needsUpload=true;
|
||||
GXInitTexObjCI(obj, GuestToHostPtr(da), (u16)w, (u16)h, (GXCITexFmt)f, (GXTexWrapMode)ws, (GXTexWrapMode)wt, (GXBool)m, tl); MarkHostTexObjConstructed(oa);
|
||||
// Also write to guest memory so reads work
|
||||
WriteGuestTexObj(oa, canonicalDataAddr, (u16)w, (u16)h, f, ws, wt, m != 0, true, tl);
|
||||
// GXInitTexObjCI clears bit1 in the flags byte; keep guest memory consistent.
|
||||
try {
|
||||
uint8_t flags = Memory::Read8(oa + 0x1F);
|
||||
Memory::Write8(oa + 0x1F, static_cast<uint8_t>(flags & 0xFD));
|
||||
} catch (...) {
|
||||
}
|
||||
InitTexObjSplit(oa, [=] {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex); GXTexObj* obj = CreateHostTexObj(oa); TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.dataAddr=canonicalDataAddr; meta.width=(u16)w; meta.height=(u16)h; meta.format=f; meta.wrapS=ws; meta.wrapT=wt; meta.mipmap=(m!=0); meta.tlut=tl; meta.userData=0; meta.needsUpload=true;
|
||||
GXInitTexObjCI(obj, GuestToHostPtr(da), (u16)w, (u16)h, (GXCITexFmt)f, (GXTexWrapMode)ws, (GXTexWrapMode)wt, (GXBool)m, tl); MarkHostTexObjConstructed(oa);
|
||||
}, [=] {
|
||||
WriteGuestTexObj(oa, canonicalDataAddr, (u16)w, (u16)h, f, ws, wt, m != 0, true, tl);
|
||||
// GXInitTexObjCI clears bit1 in the flags byte; keep guest memory consistent.
|
||||
try {
|
||||
uint8_t flags = Memory::Read8(oa + 0x1F);
|
||||
Memory::Write8(oa + 0x1F, static_cast<uint8_t>(flags & 0xFD));
|
||||
} catch (...) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170a04, GX__InitTexObjCI_80170a04, (uint32_t oa, uint32_t da, uint32_t w, uint32_t h, uint32_t f, uint32_t ws, uint32_t wt, uint32_t m, uint32_t tl), (oa, da, w, h, f, ws, wt, m, tl));
|
||||
|
||||
extern "C" void GX__InitTexObjLOD_80170a4c(uint32_t oa, uint32_t mif, uint32_t maf, float mil, float mal, float lb, uint32_t bc, uint32_t el, uint32_t ma) {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex); GXTexObj* obj = GetHostTexObj(oa); TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
float fmal = std::isfinite(mal) && mal >= 0.f ? mal : 0.f, fmil = std::isfinite(mil) && mil >= 0.f ? std::min(mil, fmal) : 0.f;
|
||||
meta.minFilter=mif; meta.magFilter=maf; meta.minLod=fmil; meta.maxLod=fmal; meta.lodBias=lb; meta.biasClamp=(bc!=0); meta.edgeLod=(el!=0); meta.maxAniso=ma;
|
||||
GXInitTexObjLOD(obj, (GXTexFilter)mif, (GXTexFilter)maf, fmil, fmal, lb, (GXBool)bc, (GXBool)el, (GXAnisotropy)ma);
|
||||
// Also write LOD info to guest memory
|
||||
WriteGuestTexObjLOD(oa, mif, maf, fmil, fmal, lb, bc != 0, el != 0, ma);
|
||||
const float fmal = std::isfinite(mal) && mal >= 0.f ? mal : 0.f, fmil = std::isfinite(mil) && mil >= 0.f ? std::min(mil, fmal) : 0.f;
|
||||
InitTexObjSplit(oa, [=] {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex); GXTexObj* obj = GetHostTexObj(oa); TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.minFilter=mif; meta.magFilter=maf; meta.minLod=fmil; meta.maxLod=fmal; meta.lodBias=lb; meta.biasClamp=(bc!=0); meta.edgeLod=(el!=0); meta.maxAniso=ma;
|
||||
GXInitTexObjLOD(obj, (GXTexFilter)mif, (GXTexFilter)maf, fmil, fmal, lb, (GXBool)bc, (GXBool)el, (GXAnisotropy)ma);
|
||||
}, [=] {
|
||||
WriteGuestTexObjLOD(oa, mif, maf, fmil, fmal, lb, bc != 0, el != 0, ma);
|
||||
});
|
||||
}
|
||||
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170a4c, GX__InitTexObjLOD_80170a4c, (uint32_t oa, uint32_t mif, uint32_t maf, float mil, float mal, float lb, uint32_t bc, uint32_t el, uint32_t ma), (oa, mif, maf, mil, mal, lb, bc, el, ma));
|
||||
|
||||
extern "C" void GX__InitTexObjWrapMode_80170b50(uint32_t oa, uint32_t ws, uint32_t wt) {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
GXTexObj* obj = GetHostTexObj(oa);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.wrapS = ws;
|
||||
meta.wrapT = wt;
|
||||
try {
|
||||
uint32_t word0 = Memory::Read32(oa + 0x00);
|
||||
word0 = (word0 & ~0xFu) | (ws & 0x3u) | ((wt & 0x3u) << 2);
|
||||
Memory::Write32(oa + 0x00, word0);
|
||||
} catch (...) {
|
||||
}
|
||||
GXInitTexObjWrapMode(obj, (GXTexWrapMode)ws, (GXTexWrapMode)wt);
|
||||
InitTexObjSplit(oa, [=] {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
GXTexObj* obj = GetHostTexObj(oa);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.wrapS = ws;
|
||||
meta.wrapT = wt;
|
||||
GXInitTexObjWrapMode(obj, (GXTexWrapMode)ws, (GXTexWrapMode)wt);
|
||||
}, [=] {
|
||||
try {
|
||||
uint32_t word0 = Memory::Read32(oa + 0x00);
|
||||
word0 = (word0 & ~0xFu) | (ws & 0x3u) | ((wt & 0x3u) << 2);
|
||||
Memory::Write32(oa + 0x00, word0);
|
||||
} catch (...) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170b50, GX__InitTexObjWrapMode_80170b50, (uint32_t oa, uint32_t ws, uint32_t wt), (oa, ws, wt));
|
||||
|
||||
extern "C" void GX__InitTexObjTlut_80170b64(uint32_t oa, uint32_t tl) {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
GXTexObj* obj = GetHostTexObj(oa);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.tlut = tl;
|
||||
GXInitTexObjTlut(obj, tl);
|
||||
// Keep guest GXTexObj coherent for later GXLoadTexObj from memory.
|
||||
try {
|
||||
Memory::Write32(oa + 0x18, tl);
|
||||
} catch (...) {
|
||||
}
|
||||
InitTexObjSplit(oa, [=] {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
GXTexObj* obj = GetHostTexObj(oa);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.tlut = tl;
|
||||
GXInitTexObjTlut(obj, tl);
|
||||
}, [=] {
|
||||
// Keep guest GXTexObj coherent for later GXLoadTexObj from memory.
|
||||
try {
|
||||
Memory::Write32(oa + 0x18, tl);
|
||||
} catch (...) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170b64, GX__InitTexObjTlut_80170b64, (uint32_t oa, uint32_t tl), (oa, tl));
|
||||
|
||||
// GXInitTexObjFilter - sets mag/min filter modes
|
||||
extern "C" void GX__InitTexObjFilter_80170b6c(uint32_t oa, uint32_t minFilter, uint32_t magFilter) {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.minFilter = minFilter;
|
||||
meta.magFilter = magFilter;
|
||||
|
||||
try {
|
||||
uint32_t word0 = Memory::Read32(oa + 0x00);
|
||||
// bit 4: magFilter - set if magFilter == GX_LINEAR (1), clear otherwise
|
||||
// Original code used cntlzw to check: cntlzw(magFilter-1) >> 1 & 0x10
|
||||
// This sets bit 4 when magFilter-1 is 0 (i.e., magFilter == 1)
|
||||
uint32_t magBit = (magFilter == 1) ? 0x10 : 0;
|
||||
word0 = (word0 & 0xFFFFFFEF) | magBit;
|
||||
// bits [7:5]: minFilter from LUT
|
||||
uint32_t minBits = (kGxToHwMinFilter[minFilter & 7] & 7) << 5;
|
||||
word0 = (word0 & 0xFFFFFF0F) | magBit | minBits;
|
||||
Memory::Write32(oa + 0x00, word0);
|
||||
} catch (...) {}
|
||||
|
||||
// Update host texture object if it exists
|
||||
if (GXTexObj* obj = TryGetHostTexObj(oa)) {
|
||||
ApplyHostTexObjLod(obj, meta, (GXTexFilter)minFilter, (GXTexFilter)magFilter, meta.lodBias,
|
||||
(GXAnisotropy)meta.maxAniso);
|
||||
}
|
||||
InitTexObjSplit(oa, [=] {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.minFilter = minFilter;
|
||||
meta.magFilter = magFilter;
|
||||
// Update host texture object if it exists
|
||||
if (GXTexObj* obj = TryGetHostTexObj(oa)) {
|
||||
ApplyHostTexObjLod(obj, meta, (GXTexFilter)minFilter, (GXTexFilter)magFilter, meta.lodBias,
|
||||
(GXAnisotropy)meta.maxAniso);
|
||||
}
|
||||
}, [=] {
|
||||
try {
|
||||
uint32_t word0 = Memory::Read32(oa + 0x00);
|
||||
// bit 4: magFilter - set if magFilter == GX_LINEAR (1), clear otherwise
|
||||
// Original code used cntlzw to check: cntlzw(magFilter-1) >> 1 & 0x10
|
||||
// This sets bit 4 when magFilter-1 is 0 (i.e., magFilter == 1)
|
||||
uint32_t magBit = (magFilter == 1) ? 0x10 : 0;
|
||||
word0 = (word0 & 0xFFFFFFEF) | magBit;
|
||||
// bits [7:5]: minFilter from LUT
|
||||
uint32_t minBits = (kGxToHwMinFilter[minFilter & 7] & 7) << 5;
|
||||
word0 = (word0 & 0xFFFFFF0F) | magBit | minBits;
|
||||
Memory::Write32(oa + 0x00, word0);
|
||||
} catch (...) {}
|
||||
});
|
||||
}
|
||||
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170b6c, GX__InitTexObjFilter_80170b6c, (uint32_t oa, uint32_t minFilter, uint32_t magFilter), (oa, minFilter, magFilter));
|
||||
|
||||
// GXInitTexObjLODBias - sets LOD bias value
|
||||
extern "C" void GX__InitTexObjLODBias_80170b94(uint32_t oa, float bias) {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
|
||||
// Clamp bias to [-4.0, 3.99...] range as per SDK
|
||||
constexpr float kMinBias = -4.0f;
|
||||
constexpr float kMaxBias = 3.99f; // Actually ~3.990000009536743
|
||||
float clampedBias = bias;
|
||||
if (clampedBias < kMinBias) clampedBias = kMinBias;
|
||||
if (clampedBias >= 4.0f) clampedBias = kMaxBias;
|
||||
|
||||
meta.lodBias = clampedBias;
|
||||
|
||||
// Update guest GXTexObj word0 with bias (scale by 32, store in bits [16:9])
|
||||
try {
|
||||
uint32_t word0 = Memory::Read32(oa + 0x00);
|
||||
int8_t biasScaled = static_cast<int8_t>(clampedBias * 32.0f);
|
||||
word0 = (word0 & 0xFFFE01FF) | ((static_cast<uint32_t>(static_cast<uint8_t>(biasScaled)) & 0xFF) << 9);
|
||||
Memory::Write32(oa + 0x00, word0);
|
||||
} catch (...) {}
|
||||
|
||||
// Update host texture object if it exists
|
||||
if (GXTexObj* obj = TryGetHostTexObj(oa)) {
|
||||
ApplyHostTexObjLod(obj, meta, (GXTexFilter)meta.minFilter, (GXTexFilter)meta.magFilter,
|
||||
clampedBias, (GXAnisotropy)meta.maxAniso);
|
||||
}
|
||||
|
||||
InitTexObjSplit(oa, [=] {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.lodBias = clampedBias;
|
||||
// Update host texture object if it exists
|
||||
if (GXTexObj* obj = TryGetHostTexObj(oa)) {
|
||||
ApplyHostTexObjLod(obj, meta, (GXTexFilter)meta.minFilter, (GXTexFilter)meta.magFilter,
|
||||
clampedBias, (GXAnisotropy)meta.maxAniso);
|
||||
}
|
||||
}, [=] {
|
||||
// Update guest GXTexObj word0 with bias (scale by 32, store in bits [16:9])
|
||||
try {
|
||||
uint32_t word0 = Memory::Read32(oa + 0x00);
|
||||
int8_t biasScaled = static_cast<int8_t>(clampedBias * 32.0f);
|
||||
word0 = (word0 & 0xFFFE01FF) | ((static_cast<uint32_t>(static_cast<uint8_t>(biasScaled)) & 0xFF) << 9);
|
||||
Memory::Write32(oa + 0x00, word0);
|
||||
} catch (...) {}
|
||||
});
|
||||
}
|
||||
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170b94, GX__InitTexObjLODBias_80170b94, (uint32_t oa, float bias), (oa, bias));
|
||||
|
||||
extern "C" void GX__InitTexObjUserData_80170be8(uint32_t oa, uint32_t userData) {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.userData = userData;
|
||||
|
||||
// The RVL SDK stores this opaque guest pointer verbatim at GXTexObj + 0x10.
|
||||
WriteGuest32(oa + 0x10, userData, "GXInitTexObjUserData");
|
||||
|
||||
// Aurora keeps an expanded host-side object, so mirror the pointer when
|
||||
// that representation has already been constructed.
|
||||
if (GXTexObj* obj = TryGetHostTexObj(oa)) {
|
||||
GXInitTexObjUserData(obj, GuestToHostPtr(userData));
|
||||
}
|
||||
InitTexObjSplit(oa, [=] {
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
TexObjMeta& meta = GetTexObjMeta(oa);
|
||||
meta.userData = userData;
|
||||
// Aurora keeps an expanded host-side object, so mirror the pointer when
|
||||
// that representation has already been constructed.
|
||||
if (GXTexObj* obj = TryGetHostTexObj(oa)) {
|
||||
GXInitTexObjUserData(obj, GuestToHostPtr(userData));
|
||||
}
|
||||
}, [=] {
|
||||
// The RVL SDK stores this opaque guest pointer verbatim at GXTexObj + 0x10.
|
||||
WriteGuest32(oa + 0x10, userData, "GXInitTexObjUserData");
|
||||
});
|
||||
}
|
||||
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170be8, GX__InitTexObjUserData_80170be8,
|
||||
(uint32_t oa, uint32_t userData), (oa, userData));
|
||||
|
||||
@@ -446,7 +493,7 @@ static void BindUnloadableTexturePlaceholder(uint32_t tid) {
|
||||
GXLoadTexObj(&s_placeholder, (GXTexMapID)tid);
|
||||
}
|
||||
|
||||
extern "C" void GX__LoadTexObj_80170f2c(uint32_t oa, uint32_t tid) {
|
||||
static void LoadTexObjImpl(uint32_t oa, uint32_t tid) {
|
||||
static uint32_t s_invalidMetaLogCount = 0;
|
||||
static uint32_t s_invalidTidLogCount = 0;
|
||||
static uint32_t s_invalidDimLogCount = 0;
|
||||
@@ -631,6 +678,27 @@ extern "C" void GX__LoadTexObj_80170f2c(uint32_t oa, uint32_t tid) {
|
||||
BindUnloadableTexturePlaceholder(tid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void GX__LoadTexObj_80170f2c(uint32_t oa, uint32_t tid) {
|
||||
if (GxWorker::ShouldDefer()) {
|
||||
// The CPU-side SDK reads the GXTexObj at call time; the texture data itself is read later.
|
||||
std::optional<TexObjBytes> bytes;
|
||||
try {
|
||||
bytes = CaptureTexObjBytes(oa);
|
||||
} catch (...) {
|
||||
}
|
||||
GxWorker::Submit([oa, tid, bytes] {
|
||||
if (bytes) {
|
||||
TexObjSnapshotScope snapshot(oa, *bytes);
|
||||
LoadTexObjImpl(oa, tid);
|
||||
} else {
|
||||
LoadTexObjImpl(oa, tid);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
LoadTexObjImpl(oa, tid);
|
||||
}
|
||||
try { uint32_t gd = Memory::Read32(kGXDataPtrAddr); if (gd) { Memory::Write32(gd + 0x5FCu, Memory::Read32(gd + 0x5FCu) | 1u); Memory::Write16(gd + 2, 0); } } catch (...) {}
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170f2c, GX__LoadTexObj_80170f2c, (uint32_t oa, uint32_t tid), (oa, tid));
|
||||
@@ -643,6 +711,7 @@ PPC_NATIVE_OVERRIDE_VOID(80170dc8, GX__LoadTexObjPreLoaded_80170dc8, (uint32_t o
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__GetTexObjAll_80170bf8(uint32_t oa, uint32_t dp, uint32_t wp, uint32_t hp, uint32_t fp, uint32_t wsp, uint32_t wtp, uint32_t mp) {
|
||||
GX_SYNC_INLINE();
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
TexObjMeta meta;
|
||||
if (!TryGetOrExtractTexObjMeta(oa, meta)) {
|
||||
@@ -657,6 +726,7 @@ extern "C" void GX__GetTexObjAll_80170bf8(uint32_t oa, uint32_t dp, uint32_t wp,
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170bf8, GX__GetTexObjAll_80170bf8, (uint32_t oa, uint32_t dp, uint32_t wp, uint32_t hp, uint32_t fp, uint32_t wsp, uint32_t wtp, uint32_t mp), (oa, dp, wp, hp, fp, wsp, wtp, mp));
|
||||
|
||||
extern "C" void GX__GetTexObjLODAll_80170cbc(uint32_t oa, uint32_t mifp, uint32_t mafp, uint32_t milp, uint32_t malp, uint32_t lbp, uint32_t bcp, uint32_t elp, uint32_t map) {
|
||||
GX_SYNC_INLINE();
|
||||
std::lock_guard<std::mutex> guard(g_texObjMutex);
|
||||
TexObjMeta meta;
|
||||
if (!TryGetOrExtractTexObjMeta(oa, meta)) {
|
||||
@@ -676,6 +746,7 @@ PPC_NATIVE_OVERRIDE_VOID(80170cbc, GX__GetTexObjLODAll_80170cbc, (uint32_t oa, u
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__InitTlutObj_80170f80(uint32_t oa, uint32_t da, uint32_t f, uint32_t e) {
|
||||
GX_DEFER_CALL(GX__InitTlutObj_80170f80, oa, da, f, e);
|
||||
std::lock_guard<std::mutex> guard(g_tlutObjMutex); GXTlutObj* obj = CreateHostTlutObj(oa); TlutObjMeta& meta = GetTlutObjMeta(oa);
|
||||
meta.dataAddr=CanonicalizeGxMainRamAddress(da); meta.format=f; meta.entries=(u16)e; meta.dirty=false;
|
||||
// Leave the object unconstructed on a bad descriptor. A later GXLoadTlut
|
||||
@@ -686,7 +757,12 @@ extern "C" void GX__InitTlutObj_80170f80(uint32_t oa, uint32_t da, uint32_t f, u
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170f80, GX__InitTlutObj_80170f80, (uint32_t oa, uint32_t da, uint32_t f, uint32_t e), (oa, da, f, e));
|
||||
|
||||
extern "C" void GX__LoadTlut_80170fa8(uint32_t oa, uint32_t tl) { std::lock_guard<std::mutex> guard(g_tlutObjMutex); if (tl >= kMaxTluts) { RT_LOGF(RT_TAG_GX, "GXLoadTlut: invalid TLUT index %u (oa=0x%08X)\n", tl, oa); return; } auto metaIt = g_TlutObjMeta.find(oa); if (metaIt != g_TlutObjMeta.end() && metaIt->second.dirty) { if (ValidateTlutData(oa, metaIt->second)) { GXTlutObj* rebuild = CreateHostTlutObj(oa); GXInitTlutObj(rebuild, GuestToHostPtr(metaIt->second.dataAddr), (GXTlutFmt)metaIt->second.format, metaIt->second.entries); MarkHostTlutObjConstructed(oa); } metaIt->second.dirty = false; } GXTlutObj* obj = GetHostTlutObj(oa); GXLoadTlut(obj, (GXTlut)tl); try { uint32_t gd = Memory::Read32(kGXDataPtrAddr); if (gd) Memory::Write16(gd + 2, 0); } catch (...) {} }
|
||||
static void LoadTlutImpl(uint32_t oa, uint32_t tl) { std::lock_guard<std::mutex> guard(g_tlutObjMutex); if (tl >= kMaxTluts) { RT_LOGF(RT_TAG_GX, "GXLoadTlut: invalid TLUT index %u (oa=0x%08X)\n", tl, oa); return; } auto metaIt = g_TlutObjMeta.find(oa); if (metaIt != g_TlutObjMeta.end() && metaIt->second.dirty) { if (ValidateTlutData(oa, metaIt->second)) { GXTlutObj* rebuild = CreateHostTlutObj(oa); GXInitTlutObj(rebuild, GuestToHostPtr(metaIt->second.dataAddr), (GXTlutFmt)metaIt->second.format, metaIt->second.entries); MarkHostTlutObjConstructed(oa); } metaIt->second.dirty = false; } GXTlutObj* obj = GetHostTlutObj(oa); GXLoadTlut(obj, (GXTlut)tl); }
|
||||
|
||||
extern "C" void GX__LoadTlut_80170fa8(uint32_t oa, uint32_t tl) {
|
||||
GxWorker::Run([oa, tl] { LoadTlutImpl(oa, tl); });
|
||||
try { uint32_t gd = Memory::Read32(kGXDataPtrAddr); if (gd) Memory::Write16(gd + 2, 0); } catch (...) {}
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80170fa8, GX__LoadTlut_80170fa8, (uint32_t oa, uint32_t tl), (oa, tl));
|
||||
|
||||
// ============================================================================
|
||||
@@ -694,6 +770,7 @@ PPC_NATIVE_OVERRIDE_VOID(80170fa8, GX__LoadTlut_80170fa8, (uint32_t oa, uint32_t
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__InvalidateTexAll_80171110() {
|
||||
GX_DEFER_CALL0(GX__InvalidateTexAll_80171110);
|
||||
// Real GX invalidates its internal texture cache here. Aurora forwards the
|
||||
// invalidate to the renderer, which keeps unchanged source uploads hot but
|
||||
// revalidates reused guest buffers before serving cached texture handles.
|
||||
@@ -701,8 +778,8 @@ extern "C" void GX__InvalidateTexAll_80171110() {
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171110, GX__InvalidateTexAll_80171110, (), ());
|
||||
|
||||
extern "C" void GX__SetTexCoordScaleManually_80171180(uint32_t c, uint32_t en, uint32_t ss, uint32_t ts) { GXSetTexCoordScaleManually((GXTexCoordID)c, (GXBool)en, (u16)ss, (u16)ts); try{ uint32_t gd=Memory::Read32(kGXDataPtrAddr); if(gd){ Memory::Write32(gd+0x5E4u, (Memory::Read32(gd+0x5E4u)&~(1u<<c))|((en&1u)<<c)); if(en){ uint32_t sa=gd+0x108u+c*4u, ta=gd+0x128u+c*4u; Memory::Write32(sa, (Memory::Read32(sa)&0xFFFF0000u)|((ss-1)&0xFFFFu)); Memory::Write32(ta, (Memory::Read32(ta)&0xFFFF0000u)|((ts-1)&0xFFFFu)); Memory::Write16(gd+2, 0); } } }catch(...){} }
|
||||
extern "C" void GX__SetTexCoordScaleManually_80171180(uint32_t c, uint32_t en, uint32_t ss, uint32_t ts) { GxWorker::Run([c, en, ss, ts] { GXSetTexCoordScaleManually((GXTexCoordID)c, (GXBool)en, (u16)ss, (u16)ts); }); try{ uint32_t gd=Memory::Read32(kGXDataPtrAddr); if(gd){ Memory::Write32(gd+0x5E4u, (Memory::Read32(gd+0x5E4u)&~(1u<<c))|((en&1u)<<c)); if(en){ uint32_t sa=gd+0x108u+c*4u, ta=gd+0x128u+c*4u; Memory::Write32(sa, (Memory::Read32(sa)&0xFFFF0000u)|((ss-1)&0xFFFFu)); Memory::Write32(ta, (Memory::Read32(ta)&0xFFFF0000u)|((ts-1)&0xFFFFu)); Memory::Write16(gd+2, 0); } } }catch(...){} }
|
||||
PPC_NATIVE_OVERRIDE_VOID(80171180, GX__SetTexCoordScaleManually_80171180, (uint32_t c, uint32_t en, uint32_t ss, uint32_t ts), (c, en, ss, ts));
|
||||
|
||||
extern "C" void GX__SetTexCoordBias_801711fc(uint32_t c, uint32_t se, uint32_t te) { GXSetTexCoordBias((GXTexCoordID)c, (GXBool)se, (GXBool)te); try{ uint32_t gd=Memory::Read32(kGXDataPtrAddr); if(gd){ uint32_t sa=gd+0x108u+c*4u, ta=gd+0x128u+c*4u; Memory::Write32(sa, (Memory::Read32(sa)&0xFFFEFFFFu)|((se&1u)<<16)); Memory::Write32(ta, (Memory::Read32(ta)&0xFFFEFFFFu)|((te&1u)<<16)); if(Memory::Read32(gd+0x5E4u)&(1u<<c)) Memory::Write16(gd+2, 0); } }catch(...){} }
|
||||
extern "C" void GX__SetTexCoordBias_801711fc(uint32_t c, uint32_t se, uint32_t te) { GxWorker::Run([c, se, te] { GXSetTexCoordBias((GXTexCoordID)c, (GXBool)se, (GXBool)te); }); try{ uint32_t gd=Memory::Read32(kGXDataPtrAddr); if(gd){ uint32_t sa=gd+0x108u+c*4u, ta=gd+0x128u+c*4u; Memory::Write32(sa, (Memory::Read32(sa)&0xFFFEFFFFu)|((se&1u)<<16)); Memory::Write32(ta, (Memory::Read32(ta)&0xFFFEFFFFu)|((te&1u)<<16)); if(Memory::Read32(gd+0x5E4u)&(1u<<c)) Memory::Write16(gd+2, 0); } }catch(...){} }
|
||||
PPC_NATIVE_OVERRIDE_VOID(801711fc, GX__SetTexCoordBias_801711fc, (uint32_t c, uint32_t se, uint32_t te), (c, se, te));
|
||||
|
||||
@@ -40,13 +40,13 @@ namespace {
|
||||
// transform double-transformed menu viewports and broke 640x480 overscan, so it was removed.
|
||||
extern "C" void GX__SetViewportJitter_80173378(float l, float t, float w, float h, float nz, float fz, uint32_t f) {
|
||||
g_viewportState[0]=l; g_viewportState[1]=t; g_viewportState[2]=w; g_viewportState[3]=h; g_viewportState[4]=nz; g_viewportState[5]=fz;
|
||||
GXSetViewportJitter(l, t, w, h, nz, fz, f);
|
||||
GxWorker::Run([=] { GXSetViewportJitter(l, t, w, h, nz, fz, f); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80173378, GX__SetViewportJitter_80173378, (float l, float t, float w, float h, float nz, float fz, uint32_t f), (l, t, w, h, nz, fz, f));
|
||||
|
||||
extern "C" void GX__SetViewport_801733b4(float l, float t, float w, float h, float nz, float fz) {
|
||||
g_viewportState[0]=l; g_viewportState[1]=t; g_viewportState[2]=w; g_viewportState[3]=h; g_viewportState[4]=nz; g_viewportState[5]=fz;
|
||||
GXSetViewport(l, t, w, h, nz, fz);
|
||||
GxWorker::Run([=] { GXSetViewport(l, t, w, h, nz, fz); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801733b4, GX__SetViewport_801733b4, (float l, float t, float w, float h, float nz, float fz), (l, t, w, h, nz, fz));
|
||||
|
||||
@@ -66,7 +66,7 @@ extern "C" void GX__GetViewportv_801733e0(uint32_t oa) {
|
||||
PPC_NATIVE_OVERRIDE_VOID(801733e0, GX__GetViewportv_801733e0, (uint32_t oa), (oa));
|
||||
|
||||
extern "C" void GX__SetZScaleOffset_80173400(float s, float o) {
|
||||
GXSetZScaleOffset(s, o);
|
||||
GxWorker::Run([s, o] { GXSetZScaleOffset(s, o); });
|
||||
try {
|
||||
const uint32_t gd = Memory::Read32(kGXDataPtrAddr);
|
||||
if (gd) {
|
||||
@@ -80,7 +80,7 @@ extern "C" void GX__SetZScaleOffset_80173400(float s, float o) {
|
||||
PPC_NATIVE_OVERRIDE_VOID(80173400, GX__SetZScaleOffset_80173400, (float s, float o), (s, o));
|
||||
|
||||
extern "C" void GX__SetScissorBoxOffset_801734e0(int32_t xo, int32_t yo) {
|
||||
GXSetScissorBoxOffset(xo, yo);
|
||||
GxWorker::Run([xo, yo] { GXSetScissorBoxOffset(xo, yo); });
|
||||
try {
|
||||
const uint32_t gd = Memory::Read32(kGXDataPtrAddr);
|
||||
if (gd) Memory::Write16(gd + 2, 0);
|
||||
@@ -104,7 +104,7 @@ extern "C" void GX__SetScissor_80173430(uint32_t l, uint32_t t, uint32_t w, uint
|
||||
// No viewport replay here: aurora recomputes viewport and scissor together
|
||||
// on every scissor change (set_logical_scissor -> apply_logical_render_state),
|
||||
// so re-issuing the current viewport would be duplicate work.
|
||||
GXSetScissor(l, t, w, h);
|
||||
GxWorker::Run([l, t, w, h] { GXSetScissor(l, t, w, h); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80173430, GX__SetScissor_80173430, (uint32_t l, uint32_t t, uint32_t w, uint32_t h), (l, t, w, h));
|
||||
|
||||
@@ -115,7 +115,7 @@ PPC_NATIVE_OVERRIDE_VOID(80173430, GX__SetScissor_80173430, (uint32_t l, uint32_
|
||||
extern "C" void GX__SetProjection_8017301c(uint32_t ma, uint32_t pt) {
|
||||
const uint32_t* raw=(const uint32_t*)GuestToHostPtr(ma, 64); float m[16];
|
||||
SwapBeF32ArrayToHost(raw, m, 16);
|
||||
GXSetProjection(m, (GXProjectionType)pt);
|
||||
GxWorker::Run([mtx = std::to_array(m), pt]() mutable { GXSetProjection(mtx.data(), (GXProjectionType)pt); });
|
||||
UpdateProjectionVectorFromMatrix(m, (GXProjectionType)pt);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8017301c, GX__SetProjection_8017301c, (uint32_t ma, uint32_t pt), (ma, pt));
|
||||
@@ -126,7 +126,7 @@ extern "C" void GX__SetProjectionv_80173080(uint32_t pa) {
|
||||
GXProjectionType pt=(v[0]!=0.f)?GX_ORTHOGRAPHIC:GX_PERSPECTIVE;
|
||||
float m[16]={0.f}; m[0]=v[1]; m[5]=v[3]; m[10]=v[5]; m[11]=v[6];
|
||||
if(pt==GX_PERSPECTIVE){ m[2]=v[2]; m[6]=v[4]; m[14]=-1.f; } else { m[3]=v[2]; m[7]=v[4]; m[15]=1.f; }
|
||||
GXSetProjection(m, pt);
|
||||
GxWorker::Run([mtx = std::to_array(m), pt]() mutable { GXSetProjection(mtx.data(), pt); });
|
||||
UpdateProjectionVectorFromProjV(v);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80173080, GX__SetProjectionv_80173080, (uint32_t pa), (pa));
|
||||
@@ -146,28 +146,51 @@ PPC_NATIVE_OVERRIDE_VOID(801730cc, GX__GetProjectionv_801730cc, (uint32_t pa), (
|
||||
extern "C" void GX__LoadPosMtxImm_8017310c(uint32_t ma, uint32_t id) {
|
||||
const uint32_t* raw=(const uint32_t*)GuestToHostPtr(ma); float m[12];
|
||||
SwapBeF32ArrayToHost(raw, m, 12);
|
||||
GXLoadPosMtxImm((float(*)[4])m, id);
|
||||
GxWorker::Run([mtx = std::to_array(m), id]() mutable { GXLoadPosMtxImm((float(*)[4])mtx.data(), id); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8017310c, GX__LoadPosMtxImm_8017310c, (uint32_t ma, uint32_t id), (ma, id));
|
||||
|
||||
// The matrix is read at load time, as Dolphin does, since the worker can run well behind the game.
|
||||
// Returns false only when the game thread doesn't know the array and the worker must resolve it.
|
||||
static bool IndexedMtxArray(uint32_t attr, uint32_t& base, uint32_t& stride) {
|
||||
if (GxWorker::ShouldDefer()) {
|
||||
return ApiVtxArray(attr, base, stride);
|
||||
}
|
||||
base = g_hleGxState.vtxArray[attr].base;
|
||||
stride = g_hleGxState.vtxArray[attr].stride;
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" void GX__LoadPosMtxIndx_8017315c(uint32_t mi, uint32_t id) {
|
||||
const auto& arr=g_hleGxState.vtxArray[GX_POS_MTX_ARRAY];
|
||||
if(arr.base==0||arr.stride==0) return;
|
||||
const uint32_t* raw=(const uint32_t*)GuestToHostPtr(arr.base+mi*arr.stride, 48);
|
||||
if(raw){ float m[12]; SwapBeF32ArrayToHost(raw,m,12); GXLoadPosMtxImm((float(*)[4])m,id); }
|
||||
uint32_t base = 0, stride = 0;
|
||||
if (!IndexedMtxArray(GX_POS_MTX_ARRAY, base, stride)) {
|
||||
GX_DEFER_CALL(GX__LoadPosMtxIndx_8017315c, mi, id);
|
||||
return;
|
||||
}
|
||||
if (base == 0 || stride == 0) return;
|
||||
const uint32_t* raw=(const uint32_t*)GuestToHostPtr(base+mi*stride, 48);
|
||||
if(raw){
|
||||
float m[12]; SwapBeF32ArrayToHost(raw,m,12);
|
||||
GxWorker::Run([mtx = std::to_array(m), id]() mutable { GXLoadPosMtxImm((float(*)[4])mtx.data(), id); });
|
||||
}
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8017315c, GX__LoadPosMtxIndx_8017315c, (uint32_t mi, uint32_t id), (mi, id));
|
||||
|
||||
extern "C" void GX__LoadNrmMtxImm_80173188(uint32_t ma, uint32_t id) {
|
||||
const uint32_t* raw=(const uint32_t*)GuestToHostPtr(ma, 48); float m[12];
|
||||
SwapBeF32ArrayToHost(raw, m, 12); GXLoadNrmMtxImm((float(*)[4])m, id);
|
||||
SwapBeF32ArrayToHost(raw, m, 12);
|
||||
GxWorker::Run([mtx = std::to_array(m), id]() mutable { GXLoadNrmMtxImm((float(*)[4])mtx.data(), id); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80173188, GX__LoadNrmMtxImm_80173188, (uint32_t ma, uint32_t id), (ma, id));
|
||||
|
||||
extern "C" void GX__LoadNrmMtxIndx3x3_801731e0(uint32_t mi, uint32_t id) {
|
||||
const auto& arr=g_hleGxState.vtxArray[GX_NRM_MTX_ARRAY];
|
||||
if(arr.base==0||arr.stride==0) return;
|
||||
const uint32_t* raw=(const uint32_t*)GuestToHostPtr(arr.base+mi*arr.stride, 36);
|
||||
uint32_t base = 0, stride = 0;
|
||||
if (!IndexedMtxArray(GX_NRM_MTX_ARRAY, base, stride)) {
|
||||
GX_DEFER_CALL(GX__LoadNrmMtxIndx3x3_801731e0, mi, id);
|
||||
return;
|
||||
}
|
||||
if (base == 0 || stride == 0) return;
|
||||
const uint32_t* raw=(const uint32_t*)GuestToHostPtr(base+mi*stride, 36);
|
||||
if(raw){
|
||||
float src[9]{};
|
||||
float m[12]{};
|
||||
@@ -177,7 +200,7 @@ extern "C" void GX__LoadNrmMtxIndx3x3_801731e0(uint32_t mi, uint32_t id) {
|
||||
m[row * 4 + col] = src[row * 3 + col];
|
||||
}
|
||||
}
|
||||
GXLoadNrmMtxImm((float(*)[4])m, id);
|
||||
GxWorker::Run([mtx = std::to_array(m), id]() mutable { GXLoadNrmMtxImm((float(*)[4])mtx.data(), id); });
|
||||
}
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(801731e0, GX__LoadNrmMtxIndx3x3_801731e0, (uint32_t mi, uint32_t id), (mi, id));
|
||||
@@ -185,9 +208,13 @@ PPC_NATIVE_OVERRIDE_VOID(801731e0, GX__LoadNrmMtxIndx3x3_801731e0, (uint32_t mi,
|
||||
extern "C" void GX__LoadTexMtxImm_80173234(uint32_t ma, uint32_t id, uint32_t t) {
|
||||
size_t c=(t==(uint32_t)GX_MTX3x4)?12:8;
|
||||
const uint32_t* raw=(const uint32_t*)GuestToHostPtr(ma,c*4); float l[12]={};
|
||||
SwapBeF32ArrayToHost(raw,l,c); GXLoadTexMtxImm(l, id, (GXTexMtxType)t);
|
||||
SwapBeF32ArrayToHost(raw,l,c);
|
||||
GxWorker::Run([mtx = std::to_array(l), id, t]() mutable { GXLoadTexMtxImm(mtx.data(), id, (GXTexMtxType)t); });
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80173234, GX__LoadTexMtxImm_80173234, (uint32_t ma, uint32_t id, uint32_t t), (ma, id, t));
|
||||
|
||||
extern "C" void GX__SetCurrentMtx_80173214(uint32_t id) { GXSetCurrentMtx(id); }
|
||||
extern "C" void GX__SetCurrentMtx_80173214(uint32_t id) {
|
||||
GX_DEFER_CALL(GX__SetCurrentMtx_80173214, id);
|
||||
GXSetCurrentMtx(id);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(80173214, GX__SetCurrentMtx_80173214, (uint32_t id), (id));
|
||||
|
||||
@@ -13,6 +13,9 @@ uint32_t CanonicalVtxAttr(uint32_t attr) {
|
||||
return attr == GX_VA_NBT ? GX_VA_NRM : attr;
|
||||
}
|
||||
|
||||
// Array bases as last set through GXSetArray on the game thread.
|
||||
std::array<std::array<uint32_t, 2>, 26> g_apiVtxArrays{};
|
||||
|
||||
// There is deliberately no indexed-aurora path here. The immediate begin path
|
||||
// streams vertex data incrementally, and aurora's indexed-array upload must
|
||||
// precede the draw carrying the max-index bounds, which cannot be known without
|
||||
@@ -27,11 +30,19 @@ void ApplyAuroraVtxStateForBegin(GXVtxFmt fmt) {
|
||||
}
|
||||
}
|
||||
|
||||
bool ApiVtxArray(uint32_t attr, uint32_t& base, uint32_t& stride) {
|
||||
if (attr >= 26) return false;
|
||||
base = g_apiVtxArrays[attr][0];
|
||||
stride = g_apiVtxArrays[attr][1];
|
||||
return base != 0 && stride != 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Vertex Descriptor
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__ClearVtxDesc_8016dc34() {
|
||||
GX_DEFER_CALL0(GX__ClearVtxDesc_8016dc34);
|
||||
bool changed = false;
|
||||
for(int i=0; i<26; ++i){
|
||||
changed |= g_hleGxState.vtxDesc[i] != GX_NONE;
|
||||
@@ -44,6 +55,7 @@ extern "C" void GX__ClearVtxDesc_8016dc34() {
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016dc34, GX__ClearVtxDesc_8016dc34, (), ());
|
||||
|
||||
extern "C" void GX__SetVtxDesc_8016d3a4(uint32_t a, uint32_t t) {
|
||||
GX_DEFER_CALL(GX__SetVtxDesc_8016d3a4, a, t);
|
||||
const uint32_t attr = CanonicalVtxAttr(a);
|
||||
if(attr>=26||attr==GX_VA_NULL) return;
|
||||
const GXAttrType oldType = g_hleGxState.vtxDesc[attr];
|
||||
@@ -59,16 +71,22 @@ PPC_NATIVE_OVERRIDE_VOID(8016d3a4, GX__SetVtxDesc_8016d3a4, (uint32_t a, uint32_
|
||||
|
||||
extern "C" void GX__SetVtxDescv_8016d608(uint32_t la) {
|
||||
if(!la) return; uint32_t p=la;
|
||||
std::vector<std::array<uint32_t, 2>> list;
|
||||
while(true){
|
||||
uint32_t a=Memory::Read32(p);
|
||||
if(a==0xFFu) break;
|
||||
GX__SetVtxDesc_8016d3a4(a, Memory::Read32(p+4));
|
||||
list.push_back({a, Memory::Read32(p+4)});
|
||||
p+=8;
|
||||
}
|
||||
GxWorker::Run([list = std::move(list)] {
|
||||
for (const auto& [a, t] : list) GX__SetVtxDesc_8016d3a4(a, t);
|
||||
});
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016d608, GX__SetVtxDescv_8016d608, (uint32_t la), (la));
|
||||
|
||||
// Display lists can change the vertex layout, so answer from the state the worker has applied.
|
||||
extern "C" void GX__GetVtxDesc_8016d9f0(uint32_t a, uint32_t tp) {
|
||||
GX_SYNC_INLINE();
|
||||
GXAttrType type = GX_NONE;
|
||||
const uint32_t attr = CanonicalVtxAttr(a);
|
||||
if (attr < GX_VA_MAX_ATTR && attr != GX_VA_NULL) {
|
||||
@@ -84,6 +102,7 @@ extern "C" void GX__GetVtxDescv_8016dba4(uint32_t la) {
|
||||
if (!la) {
|
||||
return;
|
||||
}
|
||||
GX_SYNC_INLINE();
|
||||
|
||||
uint32_t p = la;
|
||||
for (uint32_t a = GX_VA_PNMTXIDX; a <= GX_VA_TEX7; ++a) {
|
||||
@@ -103,6 +122,7 @@ PPC_NATIVE_OVERRIDE_VOID(8016dba4, GX__GetVtxDescv_8016dba4, (uint32_t la), (la)
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetVtxAttrFmt_8016dc68(uint32_t vf, uint32_t a, uint32_t c, uint32_t t, uint32_t fr) {
|
||||
GX_DEFER_CALL(GX__SetVtxAttrFmt_8016dc68, vf, a, c, t, fr);
|
||||
const uint32_t attr = CanonicalVtxAttr(a);
|
||||
if(vf<8&&attr<26){
|
||||
const VtxAttrFmt oldFmt = g_hleGxState.vtxAttrFmt[vf][attr];
|
||||
@@ -126,16 +146,21 @@ PPC_NATIVE_OVERRIDE_VOID(8016dc68, GX__SetVtxAttrFmt_8016dc68, (uint32_t vf, uin
|
||||
|
||||
extern "C" void GX__SetVtxAttrFmtv_8016de08(uint32_t vf, uint32_t la) {
|
||||
if(!la) return; uint32_t p=la;
|
||||
std::vector<std::array<uint32_t, 4>> list;
|
||||
while(true){
|
||||
uint32_t a=Memory::Read32(p);
|
||||
if(a==0xFFu) break;
|
||||
GX__SetVtxAttrFmt_8016dc68(vf, a, Memory::Read32(p+4), Memory::Read32(p+8), Memory::Read32(p+12)&0xFFu);
|
||||
list.push_back({a, Memory::Read32(p+4), Memory::Read32(p+8), Memory::Read32(p+12)&0xFFu});
|
||||
p+=16;
|
||||
}
|
||||
GxWorker::Run([vf, list = std::move(list)] {
|
||||
for (const auto& [a, c, t, fr] : list) GX__SetVtxAttrFmt_8016dc68(vf, a, c, t, fr);
|
||||
});
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016de08, GX__SetVtxAttrFmtv_8016de08, (uint32_t vf, uint32_t la), (vf, la));
|
||||
|
||||
extern "C" void GX__GetVtxAttrFmt_8016e04c(uint32_t vf, uint32_t a, uint32_t cp, uint32_t tp, uint32_t fp) {
|
||||
GX_SYNC_INLINE();
|
||||
VtxAttrFmt fmt{};
|
||||
const uint32_t attr = CanonicalVtxAttr(a);
|
||||
if (vf < 8 && attr < GX_VA_MAX_ATTR && attr != GX_VA_NULL) {
|
||||
@@ -157,6 +182,7 @@ extern "C" void GX__GetVtxAttrFmtv_8016e2b8(uint32_t vf, uint32_t la) {
|
||||
if (!la) {
|
||||
return;
|
||||
}
|
||||
GX_SYNC_INLINE();
|
||||
|
||||
uint32_t p = la;
|
||||
for (uint32_t a = GX_VA_POS; a <= GX_VA_TEX7; ++a) {
|
||||
@@ -179,6 +205,11 @@ PPC_NATIVE_OVERRIDE_VOID(8016e2b8, GX__GetVtxAttrFmtv_8016e2b8, (uint32_t vf, ui
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetArray_8016e32c(uint32_t a, uint32_t ba, uint32_t str) {
|
||||
if (!GxWorker::OnWorker()) {
|
||||
const uint32_t attr = CanonicalVtxAttr(a);
|
||||
if (attr < 26) g_apiVtxArrays[attr] = {ba, str};
|
||||
}
|
||||
GX_DEFER_CALL(GX__SetArray_8016e32c, a, ba, str);
|
||||
const uint32_t attr = CanonicalVtxAttr(a);
|
||||
if(attr<26){ g_hleGxState.vtxArray[attr].base=ba; g_hleGxState.vtxArray[attr].stride=str; }
|
||||
}
|
||||
@@ -194,11 +225,13 @@ PPC_NATIVE_OVERRIDE_VOID(8016e1c4, GX__SetArray_8016e1c4, (uint32_t a, uint32_t
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void GX__SetNumTexGens_8016e5a4(uint32_t n) {
|
||||
GX_DEFER_CALL(GX__SetNumTexGens_8016e5a4, n);
|
||||
GXSetNumTexGens((u8)n);
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016e5a4, GX__SetNumTexGens_8016e5a4, (uint32_t n), (n));
|
||||
|
||||
extern "C" void GX__SetTexCoordGen2_8016e37c(uint32_t dc, uint32_t f, uint32_t sp, uint32_t m, uint32_t n, uint32_t pm) {
|
||||
GX_DEFER_CALL(GX__SetTexCoordGen2_8016e37c, dc, f, sp, m, n, pm);
|
||||
if (sp >= static_cast<uint32_t>(GX_MAX_TEXGENSRC)) {
|
||||
uint32_t pc = 0;
|
||||
uint32_t lr = 0;
|
||||
@@ -215,6 +248,7 @@ extern "C" void GX__SetTexCoordGen2_8016e37c(uint32_t dc, uint32_t f, uint32_t s
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016e37c, GX__SetTexCoordGen2_8016e37c, (uint32_t dc, uint32_t f, uint32_t sp, uint32_t m, uint32_t n, uint32_t pm), (dc, f, sp, m, n, pm));
|
||||
|
||||
extern "C" void GX__EnableTexOffsets_8016f37c(uint32_t coord, uint32_t lineEnable, uint32_t pointEnable) {
|
||||
GX_DEFER_CALL(GX__EnableTexOffsets_8016f37c, coord, lineEnable, pointEnable);
|
||||
GXEnableTexOffsets(static_cast<GXTexCoordID>(coord),
|
||||
lineEnable ? GX_TRUE : GX_FALSE,
|
||||
pointEnable ? GX_TRUE : GX_FALSE);
|
||||
@@ -222,11 +256,13 @@ extern "C" void GX__EnableTexOffsets_8016f37c(uint32_t coord, uint32_t lineEnabl
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f37c, GX__EnableTexOffsets_8016f37c, (uint32_t coord, uint32_t lineEnable, uint32_t pointEnable), (coord, lineEnable, pointEnable));
|
||||
|
||||
extern "C" void GX__SetLineWidth_8016f314(uint32_t width, uint32_t texOffsets) {
|
||||
GX_DEFER_CALL(GX__SetLineWidth_8016f314, width, texOffsets);
|
||||
GXSetLineWidth(static_cast<u8>(width), static_cast<GXTexOffset>(texOffsets));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f314, GX__SetLineWidth_8016f314, (uint32_t width, uint32_t texOffsets), (width, texOffsets));
|
||||
|
||||
extern "C" void GX__SetPointSize_8016f348(uint32_t pointSize, uint32_t texOffsets) {
|
||||
GX_DEFER_CALL(GX__SetPointSize_8016f348, pointSize, texOffsets);
|
||||
GXSetPointSize(static_cast<u8>(pointSize), static_cast<GXTexOffset>(texOffsets));
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f348, GX__SetPointSize_8016f348, (uint32_t pointSize, uint32_t texOffsets), (pointSize, texOffsets));
|
||||
@@ -274,6 +310,8 @@ void ServiceDeferredTimingDuringGxWork() {
|
||||
}
|
||||
}
|
||||
|
||||
static void GxBeginImpl(uint32_t t, uint32_t vf, uint32_t nv);
|
||||
|
||||
extern "C" void GX__Begin_8016f0f0(uint32_t t, uint32_t vf, uint32_t nv) {
|
||||
if(IsDisplayListActive()){
|
||||
WriteDisplayListData((u8)(t|vf), 1);
|
||||
@@ -281,6 +319,11 @@ extern "C" void GX__Begin_8016f0f0(uint32_t t, uint32_t vf, uint32_t nv) {
|
||||
return;
|
||||
}
|
||||
ServiceDeferredTimingDuringGxWork();
|
||||
GX_DEFER_CALL(GxBeginImpl, t, vf, nv);
|
||||
GxBeginImpl(t, vf, nv);
|
||||
}
|
||||
|
||||
static void GxBeginImpl(uint32_t t, uint32_t vf, uint32_t nv) {
|
||||
ApplyAuroraVtxStateForBegin(static_cast<GXVtxFmt>(vf));
|
||||
g_hleGxState.currentVtxFmt=(GXVtxFmt)vf; g_hleGxState.currentPrim=(GXPrimitive)t;
|
||||
g_hleGxState.vertsRemaining=nv; g_hleGxState.inBegin=true; g_hleGxState.auroraBeginCalled=false;
|
||||
@@ -289,13 +332,19 @@ extern "C" void GX__Begin_8016f0f0(uint32_t t, uint32_t vf, uint32_t nv) {
|
||||
}
|
||||
PPC_NATIVE_OVERRIDE_VOID(8016f0f0, GX__Begin_8016f0f0, (uint32_t t, uint32_t vf, uint32_t nv), (t, vf, nv));
|
||||
|
||||
extern "C" void GX__End_80044b30() { g_hleGxState.inBegin=false; GXEnd(); }
|
||||
extern "C" void GX__End_80044b30() {
|
||||
GX_DEFER_CALL0(GX__End_80044b30); g_hleGxState.inBegin=false; GXEnd(); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(80044b30, GX__End_80044b30, (), ());
|
||||
|
||||
extern "C" void GX__End_80048c30() { GX__End_80044b30(); }
|
||||
PPC_NATIVE_OVERRIDE_VOID(80048c30, GX__End_80048c30, (), ());
|
||||
|
||||
extern "C" void GX__DrawSphere_80172a30(uint32_t numMajor, uint32_t numMinor) {
|
||||
if (GxWorker::ShouldDefer()) {
|
||||
EnsureAuroraFrameActive();
|
||||
GxWorker::Submit([=] { GX__DrawSphere_80172a30(numMajor, numMinor); });
|
||||
return;
|
||||
}
|
||||
constexpr uint32_t kAttrCount = 26;
|
||||
constexpr float kSphereRadius = 1.0f;
|
||||
constexpr float kPi = 3.14159265358979323846f;
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
#include "gx_worker.h"
|
||||
|
||||
#include "runtime_config.h"
|
||||
#include "runtime_log.h"
|
||||
|
||||
#include <dolphin/gx/GXAurora.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
#include <immintrin.h>
|
||||
#endif
|
||||
|
||||
void GxWorkerReplayFifoWrite(uint32_t value, uint32_t sizeBytes);
|
||||
void GxWorkerReplayFifoBurst(const uint8_t* data, uint32_t sizeBytes);
|
||||
|
||||
namespace GxWorker {
|
||||
namespace detail {
|
||||
std::atomic<bool> g_active{false};
|
||||
constinit thread_local bool t_onWorker = false;
|
||||
constinit thread_local int t_inlineDepth = 0;
|
||||
bool g_fifoPending = false;
|
||||
} // namespace detail
|
||||
|
||||
namespace {
|
||||
struct Header {
|
||||
void (*run)(void* payload);
|
||||
uint32_t size;
|
||||
uint32_t reserved;
|
||||
};
|
||||
constexpr size_t kHeaderSize = 16;
|
||||
static_assert(sizeof(Header) == kHeaderSize);
|
||||
|
||||
constexpr size_t kCapacity = size_t{8} << 20;
|
||||
// How long the worker keeps polling before it sleeps; recording arrives in bursts and each wake-up
|
||||
// costs the game thread a lock and notify.
|
||||
constexpr auto kIdleSpin = std::chrono::microseconds{50};
|
||||
constexpr size_t kFifoFlushBytes = 8 * 1024;
|
||||
constexpr uint8_t kFifoBurstTag = 0xB0;
|
||||
|
||||
uint8_t* g_ring = nullptr;
|
||||
uint64_t g_write = 0;
|
||||
std::atomic<uint64_t> g_head{0};
|
||||
std::atomic<uint64_t> g_tail{0};
|
||||
std::atomic<bool> g_consumerWaiting{false};
|
||||
std::atomic<bool> g_producerWaiting{false};
|
||||
std::mutex g_headMutex;
|
||||
std::condition_variable g_headCv;
|
||||
std::mutex g_tailMutex;
|
||||
std::condition_variable g_tailCv;
|
||||
void (*g_waitCallback)() = nullptr;
|
||||
std::atomic<bool> g_stop{false};
|
||||
std::thread* g_thread = nullptr;
|
||||
std::vector<uint8_t> g_fifoBytes;
|
||||
|
||||
constexpr size_t AlignUp(size_t value) { return (value + detail::kAlign - 1) & ~(detail::kAlign - 1); }
|
||||
|
||||
inline void CpuRelax() {
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
_mm_pause();
|
||||
#endif
|
||||
}
|
||||
|
||||
// libc++ on Windows truncates sub-millisecond condvar timeouts to zero (a spin), so the bounded
|
||||
// wait goes straight to the OS there.
|
||||
void WaitTailBriefly(std::unique_lock<std::mutex>& lock) {
|
||||
#if defined(_WIN32) && defined(_LIBCPP_VERSION)
|
||||
SleepConditionVariableSRW(reinterpret_cast<PCONDITION_VARIABLE>(g_tailCv.native_handle()),
|
||||
reinterpret_cast<PSRWLOCK>(lock.mutex()->native_handle()), 1, 0);
|
||||
#else
|
||||
g_tailCv.wait_for(lock, std::chrono::milliseconds{1});
|
||||
#endif
|
||||
}
|
||||
|
||||
void WaitForTail(uint64_t minTail, bool service) {
|
||||
for (int spin = 0; spin < 256 && g_tail.load(std::memory_order_acquire) < minTail; ++spin) {
|
||||
CpuRelax();
|
||||
}
|
||||
while (g_tail.load(std::memory_order_acquire) < minTail) {
|
||||
{
|
||||
std::unique_lock lock{g_tailMutex};
|
||||
g_producerWaiting.store(true, std::memory_order_seq_cst);
|
||||
if (g_tail.load(std::memory_order_seq_cst) < minTail) {
|
||||
WaitTailBriefly(lock);
|
||||
}
|
||||
g_producerWaiting.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
if (service && g_waitCallback != nullptr && g_tail.load(std::memory_order_acquire) < minTail) {
|
||||
g_waitCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WaitForSpace(size_t bytes) {
|
||||
if (g_write + bytes > kCapacity) {
|
||||
WaitForTail(g_write + bytes - kCapacity, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ReplayFifo(void* payload) {
|
||||
uint32_t size = 0;
|
||||
std::memcpy(&size, payload, sizeof(size));
|
||||
const uint8_t* data = static_cast<const uint8_t*>(payload) + sizeof(size);
|
||||
const uint8_t* end = data + size;
|
||||
while (data < end) {
|
||||
const uint8_t tag = *data++;
|
||||
if (tag == kFifoBurstTag) {
|
||||
uint32_t length = 0;
|
||||
std::memcpy(&length, data, sizeof(length));
|
||||
data += sizeof(length);
|
||||
try {
|
||||
GxWorkerReplayFifoBurst(data, length);
|
||||
} catch (...) {
|
||||
RT_LOGF(RT_TAG_GX, "GX worker FIFO burst failed\n");
|
||||
}
|
||||
data += length;
|
||||
} else {
|
||||
uint32_t value = 0;
|
||||
std::memcpy(&value, data, sizeof(value));
|
||||
data += sizeof(value);
|
||||
try {
|
||||
GxWorkerReplayFifoWrite(value, tag);
|
||||
} catch (...) {
|
||||
RT_LOGF(RT_TAG_GX, "GX worker FIFO write failed\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WorkerMain() {
|
||||
detail::t_onWorker = true;
|
||||
AuroraSetGxProducerThread();
|
||||
#if defined(_WIN32)
|
||||
SetThreadDescription(GetCurrentThread(), L"GX worker");
|
||||
#endif
|
||||
uint64_t tail = g_tail.load(std::memory_order_relaxed);
|
||||
for (;;) {
|
||||
uint64_t head = g_head.load(std::memory_order_acquire);
|
||||
if (head == tail) {
|
||||
const auto spinUntil = std::chrono::steady_clock::now() + kIdleSpin;
|
||||
while (head == tail) {
|
||||
for (int spin = 0; spin < 64 && head == tail; ++spin) {
|
||||
CpuRelax();
|
||||
head = g_head.load(std::memory_order_acquire);
|
||||
}
|
||||
if (head == tail && std::chrono::steady_clock::now() >= spinUntil) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (head == tail) {
|
||||
if (g_stop.load(std::memory_order_acquire)) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::unique_lock lock{g_headMutex};
|
||||
g_consumerWaiting.store(true, std::memory_order_seq_cst);
|
||||
while (g_head.load(std::memory_order_seq_cst) == tail && !g_stop.load(std::memory_order_acquire)) {
|
||||
g_headCv.wait(lock);
|
||||
}
|
||||
g_consumerWaiting.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
while (tail != head) {
|
||||
uint8_t* record = g_ring + tail % kCapacity;
|
||||
const Header* header = reinterpret_cast<const Header*>(record);
|
||||
if (header->run != nullptr) {
|
||||
try {
|
||||
header->run(record + kHeaderSize);
|
||||
} catch (const std::exception& error) {
|
||||
RT_LOGF(RT_TAG_GX, "GX worker command failed: %s\n", error.what());
|
||||
} catch (...) {
|
||||
RT_LOGF(RT_TAG_GX, "GX worker command failed\n");
|
||||
}
|
||||
}
|
||||
tail += header->size;
|
||||
g_tail.store(tail, std::memory_order_seq_cst);
|
||||
if (g_producerWaiting.load(std::memory_order_seq_cst)) {
|
||||
std::lock_guard lock{g_tailMutex};
|
||||
g_tailCv.notify_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace detail {
|
||||
void* Reserve(size_t payloadSize, void (*run)(void*)) {
|
||||
const size_t total = kHeaderSize + AlignUp(payloadSize);
|
||||
if (total > kCapacity) {
|
||||
RT_LOGF(RT_TAG_GX, "GX worker command of %zu bytes exceeds the ring\n", total);
|
||||
std::abort();
|
||||
}
|
||||
size_t offset = static_cast<size_t>(g_write % kCapacity);
|
||||
const size_t remaining = kCapacity - offset;
|
||||
if (total > remaining) {
|
||||
WaitForSpace(remaining + total);
|
||||
auto* wrap = reinterpret_cast<Header*>(g_ring + offset);
|
||||
wrap->run = nullptr;
|
||||
wrap->size = static_cast<uint32_t>(remaining);
|
||||
g_write += remaining;
|
||||
offset = 0;
|
||||
} else {
|
||||
WaitForSpace(total);
|
||||
}
|
||||
auto* header = reinterpret_cast<Header*>(g_ring + offset);
|
||||
header->run = run;
|
||||
header->size = static_cast<uint32_t>(total);
|
||||
g_write += total;
|
||||
return g_ring + offset + kHeaderSize;
|
||||
}
|
||||
|
||||
void Commit() {
|
||||
g_head.store(g_write, std::memory_order_seq_cst);
|
||||
if (g_consumerWaiting.load(std::memory_order_seq_cst)) {
|
||||
std::lock_guard lock{g_headMutex};
|
||||
g_headCv.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void FlushFifo() {
|
||||
g_fifoPending = false;
|
||||
if (g_fifoBytes.empty()) {
|
||||
return;
|
||||
}
|
||||
const uint32_t size = static_cast<uint32_t>(g_fifoBytes.size());
|
||||
void* payload = Reserve(sizeof(size) + size, &ReplayFifo);
|
||||
std::memcpy(payload, &size, sizeof(size));
|
||||
std::memcpy(static_cast<uint8_t*>(payload) + sizeof(size), g_fifoBytes.data(), size);
|
||||
g_fifoBytes.clear();
|
||||
Commit();
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
void FifoWrite(uint32_t value, uint32_t sizeBytes) {
|
||||
const size_t at = g_fifoBytes.size();
|
||||
g_fifoBytes.resize(at + 1 + sizeof(value));
|
||||
g_fifoBytes[at] = static_cast<uint8_t>(sizeBytes);
|
||||
std::memcpy(g_fifoBytes.data() + at + 1, &value, sizeof(value));
|
||||
detail::g_fifoPending = true;
|
||||
if (g_fifoBytes.size() >= kFifoFlushBytes) {
|
||||
detail::FlushFifo();
|
||||
}
|
||||
}
|
||||
|
||||
void FifoBurst(const uint8_t* data, uint32_t sizeBytes) {
|
||||
const size_t at = g_fifoBytes.size();
|
||||
g_fifoBytes.resize(at + 1 + sizeof(sizeBytes) + sizeBytes);
|
||||
g_fifoBytes[at] = kFifoBurstTag;
|
||||
std::memcpy(g_fifoBytes.data() + at + 1, &sizeBytes, sizeof(sizeBytes));
|
||||
std::memcpy(g_fifoBytes.data() + at + 1 + sizeof(sizeBytes), data, sizeBytes);
|
||||
detail::g_fifoPending = true;
|
||||
if (g_fifoBytes.size() >= kFifoFlushBytes) {
|
||||
detail::FlushFifo();
|
||||
}
|
||||
}
|
||||
|
||||
void Sync() {
|
||||
if (!Active() || detail::t_onWorker) {
|
||||
return;
|
||||
}
|
||||
// Guest callbacks serviced while waiting can record more work.
|
||||
do {
|
||||
if (detail::g_fifoPending) {
|
||||
detail::FlushFifo();
|
||||
}
|
||||
WaitForTail(g_write, true);
|
||||
} while (detail::g_fifoPending || g_tail.load(std::memory_order_acquire) != g_write);
|
||||
}
|
||||
|
||||
void SetWaitCallback(void (*callback)()) { g_waitCallback = callback; }
|
||||
|
||||
void BeginInline() {
|
||||
if (detail::t_inlineDepth == 0 && Active() && !detail::t_onWorker) {
|
||||
Sync();
|
||||
}
|
||||
++detail::t_inlineDepth;
|
||||
}
|
||||
|
||||
void EndInline() { --detail::t_inlineDepth; }
|
||||
|
||||
void Start() {
|
||||
if (Active()) {
|
||||
return;
|
||||
}
|
||||
const char* env = std::getenv("MKW_GX_WORKER");
|
||||
if (!RuntimeConfigFile::GxWorkerEnabled(true) || (env != nullptr && std::strcmp(env, "0") == 0)) {
|
||||
RT_LOGF(RT_TAG_GX, "GX worker disabled\n");
|
||||
return;
|
||||
}
|
||||
if (g_ring == nullptr) {
|
||||
g_ring = static_cast<uint8_t*>(::operator new(kCapacity, std::align_val_t{64}));
|
||||
}
|
||||
g_fifoBytes.reserve(kFifoFlushBytes + 4096);
|
||||
g_stop.store(false, std::memory_order_relaxed);
|
||||
g_thread = new std::thread(WorkerMain);
|
||||
detail::g_active.store(true, std::memory_order_release);
|
||||
RT_LOGF(RT_TAG_GX, "GX worker started\n");
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
if (!Active()) {
|
||||
return;
|
||||
}
|
||||
// Guest state may already be torn down; don't service it while draining.
|
||||
g_waitCallback = nullptr;
|
||||
Sync();
|
||||
detail::g_active.store(false, std::memory_order_release);
|
||||
g_stop.store(true, std::memory_order_release);
|
||||
Submit([] {});
|
||||
if (g_thread != nullptr) {
|
||||
g_thread->join();
|
||||
delete g_thread;
|
||||
g_thread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace GxWorker
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "memory.h"
|
||||
#include "hle/audio/ax_dsp.h"
|
||||
#include "gx_worker.h"
|
||||
#include "hle_stubs.h"
|
||||
#include "ppc_runtime.h"
|
||||
#include "runtime_log.h"
|
||||
@@ -124,6 +125,7 @@ extern "C" uint32_t OSResetSystem()
|
||||
std::fflush(stderr);
|
||||
// The AX mix worker holds resolved host pointers into the guest regions;
|
||||
// it must be stopped before those mappings are torn down.
|
||||
GxWorker::Stop();
|
||||
AxDspHle::ShutdownMixWorker();
|
||||
Memory::Reset();
|
||||
SetRuntimeExitCode(0);
|
||||
@@ -145,6 +147,7 @@ extern "C" uint32_t Exit_801AE58C(int status)
|
||||
ShowRuntimeFatalPopup("the game exited with an error status", details);
|
||||
MarkFatalErrorReported();
|
||||
}
|
||||
GxWorker::Stop();
|
||||
SetRuntimeExitCode(status);
|
||||
std::exit(status);
|
||||
return static_cast<uint32_t>(status);
|
||||
|
||||
@@ -577,7 +577,7 @@ void VI_HLE_PresentFrame(bool presentedXfb, bool paceToRetrace) {
|
||||
aurora_set_present_schedule(0, 0);
|
||||
}
|
||||
|
||||
aurora_end_frame();
|
||||
EndAuroraFrame();
|
||||
if (paceThisFrame) {
|
||||
PaceToRetraceBoundary(paceDeadline);
|
||||
std::lock_guard<std::mutex> lock(g_viMutex);
|
||||
@@ -751,7 +751,10 @@ extern "C" void VIConfigure_HLE_801b9f6c(CpuContext* ctx)
|
||||
renderMode.xfbHeight != 0 ? renderMode.xfbHeight : renderMode.efbHeight;
|
||||
}
|
||||
|
||||
::VIConfigure(&renderMode);
|
||||
{
|
||||
GX_SYNC_INLINE();
|
||||
::VIConfigure(&renderMode);
|
||||
}
|
||||
|
||||
ViSetR3(ctx, 0);
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
#include "wiimote_host.h"
|
||||
#include "discord_presence.h"
|
||||
#include "fiber_manager.h"
|
||||
#include "gx_worker.h"
|
||||
#include "hle_stubs.h"
|
||||
#include "runtime_config.h"
|
||||
#include "runtime_log.h"
|
||||
@@ -88,6 +89,9 @@ namespace {
|
||||
std::string FormatHostStackTrace(unsigned framesToSkip = 0);
|
||||
|
||||
void ServiceGuestTimingDuringAuroraFrameWait() {
|
||||
if (GxWorker::OnWorker()) {
|
||||
return;
|
||||
}
|
||||
// Aurora can block inside FIFO drains before control returns to GX HLE, for as long as a
|
||||
// whole display period. Keep VI retraces, alarms and audio moving at wall-clock cadence here
|
||||
// while still suppressing guest rescheduling and recursive Aurora work.
|
||||
@@ -1466,6 +1470,8 @@ int RuntimeMain(int argc, char** argv) {
|
||||
<< (forceAspect169 ? "16:9" : (configWidescreen ? "surface (dynamic EGG canvas)" : "4:3"))
|
||||
<< std::endl;
|
||||
g_auroraInitialized.store(true, std::memory_order_release);
|
||||
GxWorker::SetWaitCallback(ServiceGuestTimingDuringAuroraFrameWait);
|
||||
GxWorker::Start();
|
||||
|
||||
auto entry = ResolveEntry();
|
||||
InitializePersistentCpuContext();
|
||||
@@ -1494,6 +1500,7 @@ int RuntimeMain(int argc, char** argv) {
|
||||
Fiber::GuestFiberManager::Shutdown();
|
||||
WindowPlacementPersistence::Flush(true);
|
||||
Wiimote::Shutdown();
|
||||
GxWorker::Stop();
|
||||
aurora_shutdown();
|
||||
DiscordPresence::Shutdown();
|
||||
SetRuntimeExitCodeImpl(0);
|
||||
@@ -1513,6 +1520,7 @@ int RuntimeMain(int argc, char** argv) {
|
||||
Fiber::GuestFiberManager::Shutdown();
|
||||
WindowPlacementPersistence::Flush(true);
|
||||
Wiimote::Shutdown();
|
||||
GxWorker::Stop();
|
||||
aurora_shutdown();
|
||||
DiscordPresence::Shutdown();
|
||||
ShutdownProcessTranscript();
|
||||
@@ -1526,6 +1534,7 @@ int RuntimeMain(int argc, char** argv) {
|
||||
Fiber::GuestFiberManager::Shutdown();
|
||||
WindowPlacementPersistence::Flush(true);
|
||||
Wiimote::Shutdown();
|
||||
GxWorker::Stop();
|
||||
aurora_shutdown();
|
||||
DiscordPresence::Shutdown();
|
||||
ShutdownProcessTranscript();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "settings_overlay.h"
|
||||
#include "gx_worker.h"
|
||||
#include "audio_backend.h"
|
||||
#include "aurora_events.h"
|
||||
#include "controller_button_names.h"
|
||||
@@ -188,6 +189,7 @@ bool IsHighFrameRateMode() {
|
||||
}
|
||||
|
||||
void SetResolutionScale(float scale) {
|
||||
GX_SYNC_INLINE();
|
||||
g_resolutionScale = scale;
|
||||
VISetFrameBufferScale(scale);
|
||||
RuntimeConfigFile::SetResolutionMultiplier(scale);
|
||||
@@ -1134,6 +1136,7 @@ void DrawGraphicsSettings() {
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
if (frameInterpolationModeChanged) {
|
||||
GX_SYNC_INLINE();
|
||||
const uint32_t targetFps = kFrameInterpolationTargetFps[static_cast<size_t>(g_frameInterpolationMode)];
|
||||
aurora_set_frame_interpolation_fps(targetFps);
|
||||
RuntimeConfigFile::SetFrameInterpolationFps(targetFps);
|
||||
@@ -1177,10 +1180,12 @@ void DrawGraphicsSettings() {
|
||||
}
|
||||
#endif
|
||||
if (ImGui::Checkbox("Disable copy filter", &g_disableCopyFilter)) {
|
||||
GX_SYNC_INLINE();
|
||||
aurora_set_disable_copy_filter(g_disableCopyFilter);
|
||||
RuntimeConfigFile::SetDisableCopyFilter(g_disableCopyFilter);
|
||||
}
|
||||
if (ImGui::Checkbox("Skip draws while shaders compile", &g_skipUnreadyPipelines)) {
|
||||
GX_SYNC_INLINE();
|
||||
aurora_set_skip_unready_pipelines(g_skipUnreadyPipelines);
|
||||
RuntimeConfigFile::SetSkipUnreadyPipelines(g_skipUnreadyPipelines);
|
||||
}
|
||||
@@ -1573,9 +1578,7 @@ void ReleaseControllers() noexcept {
|
||||
}
|
||||
|
||||
void Draw() noexcept {
|
||||
// Wait for the frame worker's DONE phase: it has replayed the previous frame's ImGui draw lists
|
||||
// and started the next ImGui frame, so all overlay callers can now safely issue ImGui commands.
|
||||
aurora_wait_for_frame_worker();
|
||||
// ImGui is only touched on this thread; the render worker replays a frozen copy.
|
||||
ApplyConfiguredMappings();
|
||||
PersistDisplayModeIfChanged();
|
||||
UpdateCursorAutoHide();
|
||||
|
||||
Reference in New Issue
Block a user