mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-04 22:00:08 +02:00
Merge upstream/main into openxr-work (reverse-Z fix adapted for VR)
Brings in patchzyy/Wiicompiled main: os_sleep parked-thread fix (#195), HTTPS Retro WFC payload (#198), macOS build guide (#177), and the reverse-Z depth fix (#134). Conflicts were in aurora-main/lib/gfx/common.cpp and lib/gx/shader.cpp, both from #134, which lands squarely on the VR stereo replay path. #134 makes UseReversedZ genuinely reversed: the near/far correction now applies exactly once, inside effective_projection(), instead of being applied there AND per-vertex in the shader (the double application had been cancelling out, so "reversed" Z silently behaved like forward Z). Three pieces of the VR path were built against that old behaviour and would have broken silently, so they are adapted here: - shader.cpp exact-screen-depth parked the virtual screen at -0.5*w specifically so the shader's following negation would land it at +0.5*w. With that negation gone it now writes +0.5*w directly; keeping the minus sign would park the screen at NDC -0.5, outside the clip volume, discarding every 2D/HUD draw. - stereo_replay.hpp backend_ndc_depth_row re-applied the correction to the projection it was handed. That projection is effective_projection() output, which now already carries it, so the function is a pass-through of the Z row and no longer depends on the reversed-Z setting; the dead bool parameter is dropped. Re-applying it would invert the virtual screen's depth ordering, so 2D layers meant to sit on top would lose the depth test to the ones behind them. - shader_info.cpp stages the host depth window for that exact-depth path. It now uses the same reversed-Z remap as upstream's new SetViewport code, since frag_depth is written directly and has to reproduce the window the fixed viewport transform would have applied. Restricted depth windows (how the game forces an element in front of everything) are exactly the 2D draws the virtual screen carries. The SetViewport resolution keeps upstream's remap but retains the ordering/clamp guard our version had: for any ordered guest range the result is identical to upstream, and it avoids handing WebGPU minDepth > maxDepth for the swapped pair MKW is known to emit. The VR eye replay reuses these recorded values, so the guard covers that path too. Test updates: - stereo_replay_test now asserts the composed Z row against the staged projection's own Z row rather than against the helper's output, so it actually catches a re-introduced double correction (verified: it fails when the old negation is put back; the previous self-consistent form passed). - gx_fifo_test's clearDepthValue expectation followed #134's deliberate clear_depth_value() inversion, expressed through UseReversedZ rather than hardcoded. Upstream changed the behaviour without updating this test, so it fails on upstream/main as-is. Verified: aurora suite 247 passed with the same 2 failures that already fail on the pre-merge branch (IndexedPaletteHistoryKeepsAbsoluteVertexSlots, PacksOneUniformWhenBothHalvesNeedInitialValue - both pre-existing, unrelated to depth); shader.cpp and common.cpp compile clean; translator suite 577 passed. Not yet validated on-device in VR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
c8eaa52727
22 files changed
+635
-71
No files matched your search
@@ -3,3 +3,4 @@
|
||||
|
||||
# Patch files must stay LF: git apply matches context bytes against LF upstream sources
|
||||
*.patch -text
|
||||
translator/tests/Translator.Tests/TestAssets/**/*.bin binary
|
||||
@@ -1,6 +1,6 @@
|
||||
# Fails the release build when a fact duplicated across the repo stops agreeing with the copy
|
||||
# that owns it (recomp.yml). Scripts read pinned facts through Get-MkwProjectPins, but three
|
||||
# consumers can't read YAML (the C++ runtime header, the C# constants, hand-written lists on
|
||||
# consumers can't read YAML (the C++ runtime header, the C# constants, shell scripts, and hand-written lists on
|
||||
# both sides of the C#/PowerShell boundary), so those are checked here instead.
|
||||
[CmdletBinding()]
|
||||
param([string]$RepositoryRoot)
|
||||
@@ -58,6 +58,12 @@ $hostUri = Get-CapturedValue $retroWfcPayload 'CurrentRetroWfcPayloadUri\s*=\s*"
|
||||
if ($hostUri -cne $pins.RetroWfcPayloadUri) {
|
||||
Add-Failure "InputValidation.CurrentRetroWfcPayloadUri is '$hostUri' but recomp.yml pins '$($pins.RetroWfcPayloadUri)'."
|
||||
}
|
||||
$macosSetup = Read-SourceFile (Join-Path $launcher 'macos\setup.command') 'macOS setup.command'
|
||||
$macosUri = Get-CapturedValue $macosSetup "'([^']*/api/wfc/payload\?g=RMCPD00)'" `
|
||||
'The macOS Retro-WFC endpoint'
|
||||
if ($macosUri -cne $pins.RetroWfcPayloadUri) {
|
||||
Add-Failure "macOS setup.command downloads '$macosUri' but recomp.yml pins '$($pins.RetroWfcPayloadUri)'."
|
||||
}
|
||||
|
||||
# --- The game identity: the manifest carries it, but the host also compiles a fallback for a
|
||||
# --- manifest that predates the field, and that fallback decides which disc is accepted.
|
||||
|
||||
@@ -24,7 +24,7 @@ public static class RetroWfcPayload
|
||||
private static readonly TimeSpan RetroWfcDownloadTimeout = TimeSpan.FromSeconds(30);
|
||||
private static readonly TimeSpan RetroWfcRetryDelay = TimeSpan.FromSeconds(1);
|
||||
|
||||
public const string CurrentRetroWfcPayloadUri = "http://nas.play.rwfc.net/payload?g=RMCPD00";
|
||||
public const string CurrentRetroWfcPayloadUri = "https://rwfc.net/api/wfc/payload?g=RMCPD00";
|
||||
private static readonly string RetroWfcOfflinePayloadFile =
|
||||
Path.Combine("binary", "payload.RMCPD00.bin");
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ if [[ -n "$retro_dir" ]]; then
|
||||
trap 'rm -rf "$payload_stage"' EXIT
|
||||
/usr/bin/curl --fail --silent --show-error --connect-timeout 10 --max-time 30 \
|
||||
--retry 1 --output "$temporary_payload" \
|
||||
'http://nas.play.rwfc.net/payload?g=RMCPD00' || fail 'could not download the Retro-WFC payload needed for online play'
|
||||
'https://rwfc.net/api/wfc/payload?g=RMCPD00' || fail 'could not download the Retro-WFC payload needed for online play'
|
||||
"$translator" validate-retro-wfc-payload --directory "$payload_stage" || \
|
||||
fail 'downloaded Retro-WFC payload failed signature validation'
|
||||
mkdir -p "$retro_wfc_dir/binary"
|
||||
|
||||
@@ -233,5 +233,6 @@ EOF
|
||||
rm -rf "$test_dir"
|
||||
trap - EXIT
|
||||
|
||||
rm -rf "$toolchain_dir"
|
||||
mv "$work" "$toolchain_dir"
|
||||
echo "prepare-portable-tools.sh: toolchain ready at $toolchain_dir ($(du -sh "$toolchain_dir" | cut -f1))"
|
||||
@@ -167,7 +167,9 @@ The default test suite needs no binaries and no host C++ compiler, so you can ha
|
||||
translator without any game data around.
|
||||
|
||||
For everything beyond that, feeding in your own `main.dol`/`StaticR.rel`, running the
|
||||
translation, generating the manifest and build graph, and compiling. see [`translator/README.md`](translator/README.md).
|
||||
translation, generating the manifest and build graph, and compiling, see [`translator/README.md`](translator/README.md).
|
||||
|
||||
For a step-by-step guide on compiling both WiiCompiled and Retro Rewind from source on macOS (Apple Silicon), see the [macOS Build Guide](docs/building-macos.md).
|
||||
|
||||
## FAQ
|
||||
|
||||
|
||||
@@ -170,7 +170,12 @@ struct RenderPass {
|
||||
Range resolveUniformRange;
|
||||
std::array<u32, 3> resolveCopyFilterCoefficients{0, 64, 0};
|
||||
Vec4<float> clearColorValue{0.f, 0.f, 0.f, 0.f};
|
||||
float clearDepthValue = 1.f;
|
||||
// 1.f is the forward-Z "farthest" clear value; under UseReversedZ farthest is 0.f instead (see
|
||||
// gx::clear_depth_value(), which the main render pass explicitly overrides this default with -
|
||||
// any OTHER pass that keeps this default, e.g. an offscreen render-to-texture pass composited
|
||||
// later, needs the same reversed-Z-aware value or its depth buffer starts "already nearest",
|
||||
// failing every subsequent depth test and making whatever's drawn into it vanish).
|
||||
float clearDepthValue = gx::UseReversedZ ? 0.f : 1.f;
|
||||
CommandList commands;
|
||||
bool clearColor = true;
|
||||
bool clearDepth = true;
|
||||
@@ -835,7 +840,9 @@ void begin_offscreen(uint32_t width, uint32_t height) {
|
||||
.targetSize = {width, height, 1},
|
||||
.msaaSamples = 1,
|
||||
.clearColorValue = {0.f, 0.f, 0.f, 0.f},
|
||||
.clearDepthValue = 1.f,
|
||||
// See the RenderPass::clearDepthValue default's comment: this offscreen pass gets its own
|
||||
// depth buffer, and the farthest clear value is 0.f, not 1.f, under UseReversedZ.
|
||||
.clearDepthValue = gx::UseReversedZ ? 0.f : 1.f,
|
||||
.clearColor = true,
|
||||
.clearDepth = true,
|
||||
};
|
||||
@@ -1412,7 +1419,7 @@ static bool prepare_stereo_replay_uniforms(const StereoReplayFrame& stereoFrame)
|
||||
static_cast<float>(displayRegion.x), static_cast<float>(displayRegion.y),
|
||||
static_cast<float>(displayRegion.width), static_cast<float>(displayRegion.height));
|
||||
const auto projection = stereo_replay::compose_hud_screen_projection(
|
||||
eye.projection, eye.viewFromCenter, hudScreen, gameProjection, gx::UseReversedZ, ndcRemap);
|
||||
eye.projection, eye.viewFromCenter, hudScreen, gameProjection, ndcRemap);
|
||||
std::memcpy(uniform.data() + layout.projectionOffset, &projection, sizeof(projection));
|
||||
}
|
||||
|
||||
@@ -1883,10 +1890,27 @@ static void render_pass_impl(const wgpu::RenderPassEncoder& pass, const std::vec
|
||||
switch (cmd.type) {
|
||||
case CommandType::SetViewport: {
|
||||
const auto& vp = cmd.data.setViewport;
|
||||
// WebGPU requires 0 <= minDepth <= maxDepth <= 1, and the guest's (near, far) order is already
|
||||
// reproduced in clip space. Passing the raw swapped pair diverged per backend in release builds.
|
||||
const float minDepth = std::clamp(std::min(vp.znear, vp.zfar), 0.0f, 1.0f);
|
||||
const float maxDepth = std::clamp(std::max(vp.znear, vp.zfar), 0.0f, 1.0f);
|
||||
// WebGPU requires 0 <= minDepth <= maxDepth <= 1. vp.znear/vp.zfar are in GX's own distance
|
||||
// terms (0 = near); under UseReversedZ the host depth-buffer storage direction is flipped
|
||||
// (near = 1, far = 0), so this range has to be remapped through 1-x the same way the
|
||||
// projection matrix, depth compare function, and clear value all are - a plain min/max clamp
|
||||
// (the previous code here) maps a *restricted* range (e.g. a viewport deliberately narrowed
|
||||
// to force something to draw "in front of everything") to the wrong end of the buffer: what
|
||||
// should land near the near-storage-extreme (1.0) instead lands near the far-storage-extreme
|
||||
// (0.0), so anything else drawn afterward at its true depth wins the compare test and the
|
||||
// "in front" geometry silently vanishes. A full [0,1] viewport is unaffected either way,
|
||||
// which is why this only broke specific elements, not the whole scene. Matches upstream
|
||||
// aurora's apply_viewport (lib/gfx/encoding.cpp).
|
||||
//
|
||||
// The remapped pair is then ordered and clamped before it reaches WebGPU. For any ordered
|
||||
// guest range this is a no-op (znear <= zfar implies 1-zfar <= 1-znear), so upstream's fix is
|
||||
// reproduced exactly; it only guards the swapped pair MKW is known to emit, which the raw
|
||||
// remap would hand to SetViewport as minDepth > maxDepth and fail validation. The VR eye
|
||||
// replay below reuses these recorded values, so the guard covers that path too.
|
||||
const float remappedNear = gx::UseReversedZ ? 1.0f - vp.zfar : vp.znear;
|
||||
const float remappedFar = gx::UseReversedZ ? 1.0f - vp.znear : vp.zfar;
|
||||
const float minDepth = std::clamp(std::min(remappedNear, remappedFar), 0.0f, 1.0f);
|
||||
const float maxDepth = std::clamp(std::max(remappedNear, remappedFar), 0.0f, 1.0f);
|
||||
recordedViewport = {
|
||||
.left = (vp.left - static_cast<float>(sourceRegionLeft)) * scaleX,
|
||||
.top = (vp.top - static_cast<float>(sourceRegionTop)) * scaleY,
|
||||
|
||||
@@ -92,7 +92,7 @@ struct Params {
|
||||
|
||||
constexpr std::string_view ReversedZBody = R"(
|
||||
fn gx_z24(depth: f32) -> u32 {
|
||||
return min(u32(clamp(depth, 0.0, 1.0) * 16777216.0), 0x00ffffffu);
|
||||
return min(u32(clamp(1.0 - depth, 0.0, 1.0) * 16777215.0 + 0.5), 0x00ffffffu);
|
||||
}
|
||||
)"sv;
|
||||
|
||||
|
||||
@@ -107,17 +107,24 @@ inline bool is_orthographic_projection(const Mat4x4<float>& projection) noexcept
|
||||
return projection.m3[0] == 0.0f && projection.m3[1] == 0.0f && projection.m3[2] == 0.0f && projection.m3[3] == 1.0f;
|
||||
}
|
||||
|
||||
// The stored GX projection has not yet passed through Aurora's final clip-depth
|
||||
// conversion. Turn its Z row into the 0..1 backend NDC value that the original
|
||||
// orthographic draw would have produced. The virtual-screen shader captures
|
||||
// this row before replacing raster depth with a stable midrange value.
|
||||
inline Vec4<float> backend_ndc_depth_row(const Mat4x4<float>& projection, bool reversedDepth) noexcept {
|
||||
Vec4<float> row{};
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
row[i] = reversedDepth ? -projection.m2[i] : projection.m2[i] + projection.m3[i];
|
||||
}
|
||||
return row;
|
||||
}
|
||||
// The Z row that reproduces the backend NDC depth the original orthographic draw
|
||||
// would have produced. The virtual-screen shader captures it before replacing
|
||||
// raster depth with a stable midrange value.
|
||||
//
|
||||
// This is a straight pass-through of the stored Z row, and deliberately does not
|
||||
// depend on the reversed-Z setting. The projection reaching here is the one staged
|
||||
// into the draw's own uniform, i.e. effective_projection()'s output, which since
|
||||
// the reverse-Z fix carries the near/far depth correction already applied - exactly
|
||||
// once, in the matrix - and the vertex shader now adds nothing on top of it. So
|
||||
// dot(v, projection.m2) IS the depth the unmodified draw would have written.
|
||||
//
|
||||
// It previously re-applied a correction here (negating the row under reversed Z, or
|
||||
// folding m3 in under forward Z). That was correct only while the vertex shader
|
||||
// still applied its own redundant per-vertex correction for this one to cancel
|
||||
// against. With that per-vertex step gone, any correction here is a double
|
||||
// application: it would invert the virtual screen's depth ordering, so the 2D
|
||||
// layers meant to sit on top would lose the depth test to the ones behind them.
|
||||
inline Vec4<float> backend_ndc_depth_row(const Mat4x4<float>& projection) noexcept { return projection.m2; }
|
||||
|
||||
// Replaces an orthographic draw's projection so its 2D output lands on the
|
||||
// fixed virtual screen instead of being stretched across the whole eye.
|
||||
@@ -142,7 +149,7 @@ inline Vec4<float> backend_ndc_depth_row(const Mat4x4<float>& projection, bool r
|
||||
// equal-depth 2D layers deterministic under head rotation and translation.
|
||||
inline Mat4x4<float> compose_hud_screen_projection(const Mat4x4<float>& eyeFrustum, const Mat3x4<float>& viewFromCenter,
|
||||
const HudScreen& screen, const Mat4x4<float>& gameProjection,
|
||||
bool reversedDepth, const HudNdcRemap& ndcRemap = {}) noexcept {
|
||||
const HudNdcRemap& ndcRemap = {}) noexcept {
|
||||
const Mat4x4<float> frameProjection = remap_hud_ndc(gameProjection, ndcRemap);
|
||||
// The screen point's three coordinates, each as a functional of (mv_pos, 1).
|
||||
Mat3x4<float> screenPoint{};
|
||||
@@ -166,7 +173,7 @@ inline Mat4x4<float> compose_hud_screen_projection(const Mat4x4<float>& eyeFrust
|
||||
dst[3] += view[3];
|
||||
}
|
||||
|
||||
const Vec4<float> exactDepthRow = backend_ndc_depth_row(gameProjection, reversedDepth);
|
||||
const Vec4<float> exactDepthRow = backend_ndc_depth_row(gameProjection);
|
||||
Mat4x4<float> out{};
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
out.m0[i] = eyeFrustum.m0[0] * eyePoint.m0[i] + eyeFrustum.m0[2] * eyePoint.m2[i];
|
||||
|
||||
@@ -137,7 +137,7 @@ fn gx_z24_at_coord(unclamped_coord: vec2i) -> u32 {
|
||||
let tex_size = vec2i(textureDimensions(src));
|
||||
let coord = clamp(unclamped_coord, vec2i(0), tex_size - vec2i(1));
|
||||
let depth = textureLoad(src, coord, 0);
|
||||
return min(u32(clamp(depth, 0.0, 1.0) * 16777216.0), 0x00ffffffu);
|
||||
return min(u32(clamp(1.0 - depth, 0.0, 1.0) * 16777215.0 + 0.5), 0x00ffffffu);
|
||||
}
|
||||
)"s
|
||||
: R"(
|
||||
|
||||
@@ -1416,23 +1416,32 @@ static inline GXBlendFactor remove_dst_alpha_usage(GXBlendFactor fac) {
|
||||
}
|
||||
}
|
||||
|
||||
// GX_LEQUAL etc. describe "pass if this pixel is closer than/equal to what's stored" in GX's own
|
||||
// distance terms, independent of how that distance is encoded as a host depth value. Under
|
||||
// UseReversedZ the encoding is flipped (near=1, far=0), so "closer" now corresponds to a *larger*
|
||||
// stored value, not a smaller one - the ordered compare functions (LESS/LEQUAL/GREATER/GEQUAL)
|
||||
// must invert to match, or the depth test silently runs backwards (verified directly: this was
|
||||
// the actual cause of a bug report after the projection/shader half of the reverse-Z fix
|
||||
// eliminated the double-negation that used to accidentally keep the unreversed comparisons
|
||||
// correct - LEQUAL now needs GreaterEqual, not LessEqual, once the encoding it's testing against
|
||||
// is genuinely reversed). Matches upstream aurora's to_compare_function exactly.
|
||||
static inline wgpu::CompareFunction to_compare_function(GXCompare func) {
|
||||
switch (func) {
|
||||
DEFAULT_FATAL("invalid depth fn {}", underlying(func));
|
||||
case GX_NEVER:
|
||||
return wgpu::CompareFunction::Never;
|
||||
case GX_LESS:
|
||||
return wgpu::CompareFunction::Less;
|
||||
return UseReversedZ ? wgpu::CompareFunction::Greater : wgpu::CompareFunction::Less;
|
||||
case GX_EQUAL:
|
||||
return wgpu::CompareFunction::Equal;
|
||||
case GX_LEQUAL:
|
||||
return wgpu::CompareFunction::LessEqual;
|
||||
return UseReversedZ ? wgpu::CompareFunction::GreaterEqual : wgpu::CompareFunction::LessEqual;
|
||||
case GX_GREATER:
|
||||
return wgpu::CompareFunction::Greater;
|
||||
return UseReversedZ ? wgpu::CompareFunction::Less : wgpu::CompareFunction::Greater;
|
||||
case GX_NEQUAL:
|
||||
return wgpu::CompareFunction::NotEqual;
|
||||
case GX_GEQUAL:
|
||||
return wgpu::CompareFunction::GreaterEqual;
|
||||
return UseReversedZ ? wgpu::CompareFunction::LessEqual : wgpu::CompareFunction::GreaterEqual;
|
||||
case GX_ALWAYS:
|
||||
return wgpu::CompareFunction::Always;
|
||||
}
|
||||
|
||||
@@ -487,7 +487,14 @@ const gfx::TextureBind& get_texture(GXTexMapID id) noexcept;
|
||||
void resolve_sampled_textures(const ShaderInfo& info) noexcept;
|
||||
|
||||
inline float clear_depth_value() {
|
||||
return std::min(static_cast<float>(g_gxState.clearDepth) / 16777216.f, 16777215.f / 16777216.f);
|
||||
// g_gxState.clearDepth is in GX's own distance terms (0 = near, larger = farther), independent of
|
||||
// how UseReversedZ encodes that as a host depth value - it must be re-mapped the same way the
|
||||
// projection matrix and depth compare function are, or the buffer clears to the wrong extreme
|
||||
// (verified directly: matches upstream aurora's clear_depth_value, which does this same inversion
|
||||
// and was the second missing piece alongside to_compare_function's compare-op inversion).
|
||||
const float normalizedDepth =
|
||||
std::min(static_cast<float>(g_gxState.clearDepth) / 16777216.f, 16777215.f / 16777216.f);
|
||||
return UseReversedZ ? (1.f - normalizedDepth) : normalizedDepth;
|
||||
}
|
||||
|
||||
inline bool render_target_has_alpha(GXPixelFmt pixelFmt) noexcept { return pixelFmt == GX_PF_RGBA6_Z24; }
|
||||
|
||||
@@ -990,19 +990,27 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept {
|
||||
"\n let clip_base = select(clip_a, clip_b, use_b);"
|
||||
"\n out.pos = vec4f(clip_base.xy + offset_ndc * clip_base.w, clip_base.zw);";
|
||||
}
|
||||
// The near/far depth correction used to be applied here per-vertex (out.pos.z = -out.pos.z for
|
||||
// reversed, or += out.pos.w for forward), redundantly on top of the same correction already
|
||||
// folded into ubuf.proj by effective_projection() (shader_info.cpp) - applying it twice canceled
|
||||
// out for the common case (any draw where effective_projection() decides to flip), silently
|
||||
// making "reversed" Z behave identically to forward Z. It is now applied exactly once, in the
|
||||
// projection matrix alone (matching upstream aurora commit 1dde08fa: "Move depth correction to
|
||||
// projection matrix"), so nothing needs to happen to out.pos.z here.
|
||||
if (config.exactScreenDepth) {
|
||||
vtxOutAttrs += fmt::format("\n @location({}) @interpolate(flat) exact_screen_depth: f32,", vtxOutIdx++);
|
||||
// Virtual-screen composition stores the original backend-convention NDC
|
||||
// depth in the otherwise replaceable projection Z row. Capture it before
|
||||
// parking raster depth at midrange; this avoids growing every GX uniform.
|
||||
//
|
||||
// The parked value is written directly as +0.5 * w now that the per-vertex
|
||||
// depth correction above is gone. It used to be -0.5 * w specifically so the
|
||||
// `out.pos.z = -out.pos.z` that followed would land it at +0.5 * w; with
|
||||
// that negation removed, keeping the minus sign would park the virtual
|
||||
// screen at NDC -0.5, outside the clip volume, and discard every 2D draw.
|
||||
vtxXfrAttrsPre +=
|
||||
"\n out.exact_screen_depth = clamp(out.pos.z, 0.0, 1.0);"
|
||||
"\n out.pos.z = -0.5 * out.pos.w;";
|
||||
}
|
||||
if constexpr (UseReversedZ) {
|
||||
vtxXfrAttrsPre += "\n out.pos.z = -out.pos.z;";
|
||||
} else {
|
||||
vtxXfrAttrsPre += "\n out.pos.z += out.pos.w;";
|
||||
"\n out.pos.z = 0.5 * out.pos.w;";
|
||||
}
|
||||
// GX rasterizes at a 7/12 pixel center when antialiasing is disabled, while WebGPU rasterizes at 1/2.
|
||||
vtxXfrAttrsPre +=
|
||||
@@ -1469,8 +1477,18 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept {
|
||||
textureDependency.texMapId, uvIn);
|
||||
}
|
||||
|
||||
std::string fogDepthExpr = UseReversedZ ? "in.pos.z" : "(1.0 - in.pos.z)";
|
||||
std::string fogZCoordExpr = fmt::format("u32(round(clamp({}, 0.0, 1.0) * 16777216.0))", fogDepthExpr);
|
||||
|
||||
// in.pos.z is the host NDC z (forward: 0=near/1=far; reversed: 1=near/0=far post-fix), but this
|
||||
// expression needs to produce GX's own native distance term (always 0=near/1=far, matching how
|
||||
// g_gxState.clearDepth/clear_depth_value() are interpreted before their own UseReversedZ
|
||||
// inversion) - forward already matches directly; reversed needs the same 1-x flip everything
|
||||
// else reversed-Z-aware uses. This was backwards (verified directly against upstream aurora's
|
||||
// identical expression in build_shader_source), which fed both fog density and the GX_ZT_ADD
|
||||
// z-texture path the wrong distance value.
|
||||
std::string fogDepthExpr = UseReversedZ ? "(1.0 - in.pos.z)" : "in.pos.z";
|
||||
std::string fogZCoordExpr =
|
||||
fmt::format("u32(round(clamp({}, 0.0, 1.0) * 16777216.0))", fogDepthExpr);
|
||||
|
||||
if (usesZTextureDepth) {
|
||||
const u32 zTexBias = config.zTexture & 0x00FFFFFFu;
|
||||
const u32 zTexFmt = (config.zTexture >> 24) & 0x3u;
|
||||
@@ -1501,7 +1519,7 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept {
|
||||
fragmentFn += fmt::format(
|
||||
"\n let oldZ = u32(round(clamp({0}, 0.0, 1.0) * 16777216.0));"
|
||||
"\n ztexCoord = (ztexCoord + oldZ) & 0x00ffffffu;",
|
||||
UseReversedZ ? "in.pos.z" : "(1.0 - in.pos.z)");
|
||||
UseReversedZ ? "(1.0 - in.pos.z)" : "in.pos.z");
|
||||
}
|
||||
fragmentFn += "\n let ztexDepth = f32(ztexCoord) / 16777216.0;";
|
||||
fogZCoordExpr = "ztexCoord";
|
||||
@@ -1645,12 +1663,22 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept {
|
||||
|
||||
if (config.exactScreenDepth) {
|
||||
// Fragment depth is already in window coordinates. Reapply the recorded
|
||||
// viewport's clamped depth window exactly as the fixed pipeline did.
|
||||
// viewport's clamped depth window exactly as the fixed pipeline did. This
|
||||
// value never passed through the per-vertex depth correction upstream
|
||||
// removed, so it needs no reversed-Z adjustment of its own - the recorded
|
||||
// range it is rebuilt from is already reversed-Z-aware (see the SetViewport
|
||||
// remap in gfx/common.cpp).
|
||||
fragmentFn +=
|
||||
"\n let fragDepth = ubuf.exact_screen_depth_range.x + "
|
||||
"in.exact_screen_depth * ubuf.exact_screen_depth_range.y;";
|
||||
} else {
|
||||
fragmentFn += fmt::format("\n let fragDepth = {}ztexDepth;", UseReversedZ ? "" : "1.0 - ");
|
||||
// ztexDepth is in GX's native distance terms (0=near/1=far, see fogDepthExpr's comment
|
||||
// above), but frag_depth must be written in the same host NDC-z convention in.pos.z itself
|
||||
// uses - forward matches directly (no change), reversed needs the same 1-x flip. This was
|
||||
// backwards the same way fogDepthExpr was (verified by the same derivation, since aurora
|
||||
// upstream has no directly equivalent line here to cross-check against - this
|
||||
// z-texture-depth-output path appears to be specific to this fork).
|
||||
fragmentFn += fmt::format("\n let fragDepth = {}ztexDepth;", UseReversedZ ? "1.0 - " : "");
|
||||
}
|
||||
fragmentReturnType = "FragmentOutput";
|
||||
fragmentReturn =
|
||||
|
||||
@@ -559,14 +559,22 @@ constexpr u16 kEfbHeight = 528;
|
||||
constexpr size_t kStagedUniformBytes = 96 + sizeof(Mat4x4<float>) + sizeof(Mat3x4<float>) * (MaxPostexMtx + MaxPnMtx);
|
||||
|
||||
// The host viewport always receives the normalized GX depth window (render_pass_impl clamps to minDepth <= maxDepth).
|
||||
//
|
||||
// Folds the near/far depth correction the vertex shader used to apply per-vertex directly into the
|
||||
// projection matrix instead (matching upstream aurora commit 1dde08fa, "Move depth correction to
|
||||
// projection matrix") - valid because the correction is a linear combination of the z/w rows, so
|
||||
// applying it once here to the row is equivalent to applying it once per-vertex to the dot product,
|
||||
// and it must be applied exactly once: doing it here AND in the shader (the previous bug) canceled
|
||||
// the negation out for `flip`, silently making "reversed" Z behave identically to forward Z.
|
||||
// `flip` decides which of the two single-application forms this draw needs: true bakes in the
|
||||
// reversed-Z inversion (z' = -z), false bakes in the forward-Z near/far combination (z' = z + w) -
|
||||
// exactly one always applies, never both, and never neither.
|
||||
static Mat4x4<float> effective_projection() noexcept {
|
||||
const auto& vp = g_gxState.renderViewport;
|
||||
const bool flip = (vp.znear <= vp.zfar) == UseReversedZ;
|
||||
Mat4x4<float> proj = g_gxState.proj;
|
||||
if (flip) {
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
proj.m2.m[i] = -(proj.m2.m[i] + proj.m3.m[i]);
|
||||
}
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
proj.m2.m[i] = flip ? -proj.m2.m[i] : (proj.m2.m[i] + proj.m3.m[i]);
|
||||
}
|
||||
return proj;
|
||||
}
|
||||
@@ -597,8 +605,17 @@ UniformRanges build_uniform(const ShaderInfo& info, u32 vtxStart, const BindGrou
|
||||
|
||||
const Mat4x4<float> effectiveProj = effective_projection();
|
||||
const auto& viewport = g_gxState.renderViewport;
|
||||
const float depthNear = std::clamp(std::min(viewport.znear, viewport.zfar), 0.0f, 1.0f);
|
||||
const float depthFar = std::clamp(std::max(viewport.znear, viewport.zfar), 0.0f, 1.0f);
|
||||
// The host depth window this draw's viewport applies. Must stay byte-for-byte the same mapping as
|
||||
// the SetViewport remap in gfx/common.cpp, because the exact-screen-depth path writes frag_depth
|
||||
// directly and has to reproduce the window the fixed viewport transform would have applied. Under
|
||||
// UseReversedZ the guest's GX-distance (near, far) pair is remapped through 1-x, then ordered and
|
||||
// clamped for the [0,1] host range. A full guest window lands on [0,1] either way; a *restricted*
|
||||
// one (how the game forces an element to draw in front of everything) is the case that differs,
|
||||
// and those restricted windows are 2D/HUD draws - exactly the ones the VR virtual screen carries.
|
||||
const float remappedNear = UseReversedZ ? 1.0f - viewport.zfar : viewport.znear;
|
||||
const float remappedFar = UseReversedZ ? 1.0f - viewport.znear : viewport.zfar;
|
||||
const float depthNear = std::clamp(std::min(remappedNear, remappedFar), 0.0f, 1.0f);
|
||||
const float depthFar = std::clamp(std::max(remappedNear, remappedFar), 0.0f, 1.0f);
|
||||
|
||||
stage_u32(vtxStart);
|
||||
// With a compacted position region the live matrix is uploaded to slot 0, so every `postex_mtx[in_pnmtxidx]` /
|
||||
|
||||
@@ -4192,7 +4192,14 @@ TEST_F(GXFifoTest, CopyTexClearTruePassesScratchRectAndUpdateMasksToResolve) {
|
||||
EXPECT_NEAR(resolve.clearColorValue.y(), 128.f / 255.f, 1.f / 255.f);
|
||||
EXPECT_NEAR(resolve.clearColorValue.z(), 192.f / 255.f, 1.f / 255.f);
|
||||
EXPECT_NEAR(resolve.clearColorValue.w(), 32.f / 255.f, 1.f / 255.f);
|
||||
EXPECT_NEAR(resolve.clearDepthValue, 0x123456 / 16777216.f, 1.f / 16777216.f);
|
||||
// clear_depth_value() maps the guest's GX-distance clear depth into the host depth-buffer
|
||||
// convention, so under reversed Z the stored value is the 1-x mirror of the guest's normalized
|
||||
// depth. Expressed through UseReversedZ rather than hardcoded, so this expectation follows the
|
||||
// convention instead of pinning one side of it.
|
||||
const float expectedNormalizedClearDepth = 0x123456 / 16777216.f;
|
||||
EXPECT_NEAR(resolve.clearDepthValue,
|
||||
aurora::gx::UseReversedZ ? 1.f - expectedNormalizedClearDepth : expectedNormalizedClearDepth,
|
||||
1.f / 16777216.f);
|
||||
EXPECT_EQ(resolve.resolveFormat, GX_TF_RGBA8);
|
||||
EXPECT_FALSE(resolve.halfScale);
|
||||
EXPECT_FALSE(resolve.forceOpaqueAlpha);
|
||||
|
||||
@@ -107,8 +107,7 @@ TEST(StereoReplayTest, HudScreenProjectionMatchesTheChainItComposes) {
|
||||
viewFromCenter.m2 = {-s, 0.0f, c, 7.0f};
|
||||
|
||||
const HudScreen screen{.halfWidth = 600.0f, .halfHeight = 337.5f, .distance = 1000.0f};
|
||||
const auto composed = compose_hud_screen_projection(eyeFrustum, viewFromCenter, screen, game, true);
|
||||
const auto exactDepth = backend_ndc_depth_row(game, true);
|
||||
const auto composed = compose_hud_screen_projection(eyeFrustum, viewFromCenter, screen, game);
|
||||
|
||||
for (const auto& v : kVertices) {
|
||||
// The same chain, one step at a time: game NDC, a point on the screen
|
||||
@@ -124,7 +123,12 @@ TEST(StereoReplayTest, HudScreenProjectionMatchesTheChainItComposes) {
|
||||
EXPECT_NEAR(dot4(composed.m1, v), eyeFrustum.m1[1] * eyeY + eyeFrustum.m1[2] * eyeZ, 1e-2f);
|
||||
const float clipW = -eyeZ;
|
||||
EXPECT_NEAR(dot4(composed.m3, v), clipW, 1e-2f);
|
||||
EXPECT_NEAR(dot4(composed.m2, v), dot4(exactDepth, v), 1e-6f);
|
||||
// The virtual screen must carry the depth the unmodified draw would have
|
||||
// written. Since the reverse-Z fix moved the near/far correction wholly into
|
||||
// the projection, that is the staged Z row applied directly - no further
|
||||
// inversion. Comparing against `game.m2` rather than the helper's own output
|
||||
// is what makes this catch a re-introduced double correction.
|
||||
EXPECT_NEAR(dot4(composed.m2, v), dot4(game.m2, v), 1e-6f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,16 +146,19 @@ TEST(StereoReplayTest, HudScreenParksRasterDepthAtMidrangeUnderHeadMotion) {
|
||||
moved.m2 = {-s, 0.0f, c, 13.0f};
|
||||
|
||||
const HudScreen screen{.halfWidth = 600.0f, .halfHeight = 337.5f, .distance = 1000.0f};
|
||||
const auto composed = compose_hud_screen_projection(eyeFrustum, moved, screen, game, true);
|
||||
const auto composed = compose_hud_screen_projection(eyeFrustum, moved, screen, game);
|
||||
|
||||
// The exact-depth shader captures composed Z, then parks clip Z at -0.5W.
|
||||
// Aurora's following reversed-depth conversion negates that to +0.5W, so
|
||||
// The exact-depth shader captures composed Z, then parks clip Z at +0.5W, so
|
||||
// rasterization stays stable even though W varies across the rotated screen.
|
||||
// It writes +0.5W directly: the reverse-Z fix removed the per-vertex depth
|
||||
// negation that used to follow, which is why the shader no longer pre-negates
|
||||
// to -0.5W. What this test pins is the invariant that survived that change -
|
||||
// the parked value must land at NDC 0.5 for every vertex, whatever W does.
|
||||
for (const auto& v : kVertices) {
|
||||
const float w = dot4(composed.m3, v);
|
||||
ASSERT_GT(w, 0.0f);
|
||||
const float parkedClipZ = -0.5f * w;
|
||||
EXPECT_NEAR(-parkedClipZ / w, 0.5f, 1e-5f);
|
||||
const float parkedClipZ = 0.5f * w;
|
||||
EXPECT_NEAR(parkedClipZ / w, 0.5f, 1e-5f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
# Building WiiCompiled and Retro Rewind on macOS
|
||||
|
||||
This guide covers building **WiiCompiled** (base game) and **Retro Rewind** from source on macOS for Apple Silicon (`arm64`). Follow these instructions to compile the native executables directly.
|
||||
|
||||
> [!NOTE]
|
||||
> If you only want to build the base game (**WiiCompiled**), look for sections marked **`(Skip if only building WiiCompiled)`** to bypass Retro Rewind and online payload steps.
|
||||
|
||||
---
|
||||
|
||||
## 1. Prerequisites
|
||||
|
||||
### System Requirements
|
||||
- **Hardware**: Apple Silicon Mac (M1/M2/M3/M4)
|
||||
- **Operating System**: macOS 14 (Sonoma) or later
|
||||
- **Xcode Command Line Tools**:
|
||||
```bash
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
### Toolchain Dependencies
|
||||
Install the required tools using [Homebrew](https://brew.sh):
|
||||
```bash
|
||||
brew install cmake ninja
|
||||
brew install --cask dotnet-sdk@8
|
||||
```
|
||||
|
||||
Verify that Clang, CMake, Ninja, and the .NET 8 runtime are available:
|
||||
```bash
|
||||
clang --version
|
||||
cmake --version
|
||||
ninja --version
|
||||
dotnet --list-runtimes # Must list Microsoft.NETCore.App 8.x
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Required Game and Mod Assets
|
||||
|
||||
Due to legal requirements, no proprietary Nintendo assets or code are included in this repository. You must provide your own legally dumped game files.
|
||||
|
||||
1. **Mario Kart Wii PAL (`RMCP01`) Disc Image** *(Required)*:
|
||||
- Supported formats: `.iso`, `.wbfs`, `.ciso`, `.rvz`, `.gcm`, `.gcz`.
|
||||
2. **nodtool** *(Required for disc extraction)*:
|
||||
- Download the macOS Apple Silicon binary of [nodtool](https://github.com/encounter/nod/releases):
|
||||
```bash
|
||||
curl -fsSL "https://github.com/encounter/nod/releases/download/v2.0.0-alpha.10/nodtool-macos-arm64" -o nodtool
|
||||
chmod +x nodtool
|
||||
```
|
||||
3. **Retro Rewind Distribution** *(Skip if only building WiiCompiled)*:
|
||||
- Download the [Retro Rewind](https://wiki.tockdom.com/wiki/Retro_Rewind) release package. You will need the `RetroRewind6` folder (which contains `Binaries/Code.pul`).
|
||||
4. **Retro-WFC Payload** *(Skip if only building WiiCompiled or building offline)*:
|
||||
- Required for online multiplayer on Retro Rewind. Downloaded during setup from `http://nas.play.rwfc.net/payload?g=RMCPD00`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Step 1: Extract Disc Assets
|
||||
|
||||
Extract your clean PAL `RMCP01` disc into the `Assets/` directory of the repository:
|
||||
|
||||
```bash
|
||||
# Using nodtool directly into a temporary scratch directory
|
||||
mkdir -p /tmp/mkw-extract
|
||||
./nodtool extract /path/to/RMCP01.iso /tmp/mkw-extract
|
||||
|
||||
# Copy extracted assets into the repository Assets directory
|
||||
rm -rf Assets/DATA/files Assets/DATA/sys
|
||||
mkdir -p Assets/DATA
|
||||
cp /tmp/mkw-extract/*/sys/main.dol Assets/main.dol
|
||||
cp /tmp/mkw-extract/*/files/rel/StaticR.rel Assets/StaticR.rel
|
||||
cp -R /tmp/mkw-extract/*/files Assets/DATA/files
|
||||
cp -R /tmp/mkw-extract/*/sys Assets/DATA/sys
|
||||
|
||||
# Clean up temporary files
|
||||
rm -rf /tmp/mkw-extract
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> Alternatively, you can use the repository's helper script:
|
||||
> ```bash
|
||||
> Launcher/macos/extract-disc.command --game /path/to/RMCP01.iso --assets-dir Assets --nodtool ./nodtool
|
||||
> ```
|
||||
|
||||
### Verify Extracted Asset Hashes
|
||||
Confirm that the extracted files match the expected clean PAL revision:
|
||||
```bash
|
||||
shasum -a 256 Assets/main.dol Assets/StaticR.rel
|
||||
```
|
||||
- `Assets/main.dol`: `80d18895b39c63bd80f457398bfcbb91b7d16ac116a41a88967e954080155b05`
|
||||
- `Assets/StaticR.rel`: `16d9d146112541fefea701ecb5bc1a496f9d50e4a752fbb5b6778e7c6399f67d`
|
||||
|
||||
---
|
||||
|
||||
## 4. Step 2: Build the Translator CLI
|
||||
|
||||
Compile the static recompiler CLI:
|
||||
|
||||
```bash
|
||||
dotnet build translator/src/Translator.Cli/Translator.Cli.csproj -c Release
|
||||
```
|
||||
|
||||
Define a shell function to invoke the translator (ensuring paths with spaces are handled safely):
|
||||
```bash
|
||||
translator() {
|
||||
dotnet "$(pwd)/translator/src/Translator.Cli/bin/Release/net8.0/Translator.Cli.dll" "$@"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Step 3: Translation
|
||||
|
||||
### A. Translate Base Game Functions
|
||||
```bash
|
||||
mkdir -p generated/functions build/base
|
||||
|
||||
translator translate-recursive 0x8000629c \
|
||||
--project projects/mkwii/recomp.yml \
|
||||
--outdir generated/functions \
|
||||
--output-metadata generated/base_translation_output.json \
|
||||
--production-source-bundle generated/base_translation_sources.bin \
|
||||
--no-function-files \
|
||||
--prune-stale \
|
||||
--threads $(sysctl -n hw.ncpu)
|
||||
```
|
||||
|
||||
### B. Emit Base Manifest
|
||||
```bash
|
||||
translator emit-base-manifest \
|
||||
--project projects/mkwii/recomp.yml \
|
||||
--out build/base \
|
||||
--functions-dir generated/functions \
|
||||
--translation-output-metadata generated/base_translation_output.json \
|
||||
--region P
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### C. Stage and Translate Retro Rewind *(Skip this step if you only want to build WiiCompiled)*
|
||||
|
||||
1. Stage `Code.pul`:
|
||||
```bash
|
||||
RETRO_DIR="/path/to/RetroRewind6"
|
||||
mkdir -p PulsarPacks/completed/RetroRewind/RetroRewind6/Binaries
|
||||
cp "$RETRO_DIR/Binaries/Code.pul" PulsarPacks/completed/RetroRewind/RetroRewind6/Binaries/Code.pul
|
||||
```
|
||||
|
||||
2. **Retro-WFC Payload Setup (for Online Multiplayer)**:
|
||||
Online play in Retro Rewind requires the shared Retro-WFC payload. Download and validate it:
|
||||
```bash
|
||||
mkdir -p build/retro-wfc/binary
|
||||
curl -fsSL --retry 3 "https://nas.play.rwfc.net/payload?g=RMCPD00" \
|
||||
-o build/retro-wfc/binary/payload.RMCPD00.bin
|
||||
|
||||
# Validate payload signature and integrity
|
||||
translator validate-retro-wfc-payload --directory build/retro-wfc
|
||||
```
|
||||
|
||||
3. Run Retro Rewind translation:
|
||||
```bash
|
||||
mkdir -p build/mods/retro_rewind_full_cpp
|
||||
|
||||
translator translate-mod \
|
||||
--project projects/mkwii/recomp.yml \
|
||||
--profile retro-rewind \
|
||||
--base-manifest build/base/mkwii_base_manifest.json \
|
||||
--base-translation-output-metadata generated/base_translation_output.json \
|
||||
--code-pul "$RETRO_DIR/Binaries/Code.pul" \
|
||||
--mod-root "$RETRO_DIR" \
|
||||
--mod-name "Retro Rewind" \
|
||||
--region P \
|
||||
--out build/mods/retro_rewind_full_cpp \
|
||||
--prefer-cached-inputs \
|
||||
--emit-cpp \
|
||||
--threads $(sysctl -n hw.ncpu) \
|
||||
--retro-wfc-payload build/retro-wfc/binary/payload.RMCPD00.bin
|
||||
```
|
||||
> [!TIP]
|
||||
> If you do not want online play or do not have an internet connection, replace `--retro-wfc-payload ...` with `--skip-retro-wfc`.
|
||||
|
||||
---
|
||||
|
||||
### D. Generate Data Initialization and Build Shards
|
||||
|
||||
First, generate the embedded game data initializer:
|
||||
```bash
|
||||
translator generate-data-init --project projects/mkwii/recomp.yml
|
||||
```
|
||||
|
||||
Next, generate the CMake build shards using **one** of the following options:
|
||||
|
||||
#### Option 1: Base Game Only (WiiCompiled)
|
||||
```bash
|
||||
mkdir -p generated/build_shards
|
||||
translator emit-build-shards \
|
||||
--project projects/mkwii/recomp.yml \
|
||||
--base-metadata generated/base_translation_output.json \
|
||||
--base-functions-dir generated/functions \
|
||||
--native-source-dir runtime/src \
|
||||
--out generated/build_shards
|
||||
```
|
||||
|
||||
#### Option 2: Base Game + Retro Rewind
|
||||
```bash
|
||||
mkdir -p generated/build_shards
|
||||
translator emit-build-shards \
|
||||
--project projects/mkwii/recomp.yml \
|
||||
--base-metadata generated/base_translation_output.json \
|
||||
--base-functions-dir generated/functions \
|
||||
--native-source-dir runtime/src \
|
||||
--out generated/build_shards \
|
||||
--resolved-profile build/mods/retro_rewind_full_cpp/resolved_dispatch_profile.json \
|
||||
--retro-cpp-dir build/mods/retro_rewind_full_cpp/cpp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Step 4: Configure and Compile with CMake & Ninja
|
||||
|
||||
Configure the native C++ build targeting Apple Silicon:
|
||||
|
||||
```bash
|
||||
cmake -S runtime -B build-macos -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DAURORA_SDL3_PROVIDER=vendor
|
||||
```
|
||||
|
||||
Compile the desired target:
|
||||
|
||||
```bash
|
||||
# To build WiiCompiled only:
|
||||
cmake --build build-macos --target WiiCompiled --parallel $(sysctl -n hw.ncpu)
|
||||
|
||||
# OR to build both WiiCompiled and Retro Rewind:
|
||||
cmake --build build-macos --target WiiCompiled RetroRewind --parallel $(sysctl -n hw.ncpu)
|
||||
```
|
||||
|
||||
Once compilation completes, the executables are ready in your build directory:
|
||||
- `build-macos/WiiCompiled`
|
||||
- `build-macos/RetroRewind` (if built)
|
||||
|
||||
During the build, CMake automatically copies the required runtime assets into `build-macos/`:
|
||||
- `build-macos/dsp_coef.bin`
|
||||
- `build-macos/initial_pipeline_cache.db`
|
||||
- `build-macos/wii_bootstrap/`
|
||||
|
||||
---
|
||||
|
||||
## 7. Step 5: Running Executables from the Build Folder
|
||||
|
||||
### Configure `Config.toml`
|
||||
The runtime reads configuration from `~/Library/Application Support/WiiCompiled/Config.toml`.
|
||||
|
||||
Create the directory and configuration file:
|
||||
|
||||
```bash
|
||||
mkdir -p "$HOME/Library/Application Support/WiiCompiled"
|
||||
```
|
||||
|
||||
#### For Base Game Only (WiiCompiled):
|
||||
```toml
|
||||
# ~/Library/Application Support/WiiCompiled/Config.toml
|
||||
[video]
|
||||
widescreen = true
|
||||
resolution_multiplier = 1.0
|
||||
graphics_api = "metal"
|
||||
|
||||
[paths]
|
||||
dvd_root = "/absolute/path/to/Wiicompiled/Assets/DATA"
|
||||
```
|
||||
|
||||
#### For Base Game and Retro Rewind:
|
||||
```toml
|
||||
# ~/Library/Application Support/WiiCompiled/Config.toml
|
||||
[video]
|
||||
widescreen = true
|
||||
resolution_multiplier = 1.0
|
||||
graphics_api = "metal"
|
||||
|
||||
[paths]
|
||||
dvd_root = "/absolute/path/to/Wiicompiled/Assets/DATA"
|
||||
retro_rewind_root = "/path/to/RetroRewind6"
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Ensure `dvd_root` points to the directory containing `files` and `sys/fst.bin`.
|
||||
|
||||
### Launching the Game
|
||||
Run the compiled binaries directly from your terminal or by double clicking:
|
||||
|
||||
```bash
|
||||
# Run base WiiCompiled
|
||||
./build-macos/WiiCompiled
|
||||
|
||||
# Run Retro Rewind
|
||||
./build-macos/RetroRewind
|
||||
```
|
||||
|
||||
|
||||
|
||||
Press **F10** in-game at any time to open the configuration bar (controls, resolution, display settings, audio).
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference: Automated Helper Script
|
||||
|
||||
The repository provides a script (`Launcher/local-build-macos.command`) that handles extraction, translation, and compilation in a single command.
|
||||
|
||||
### Building Base Game Only:
|
||||
```bash
|
||||
Launcher/local-build-macos.command \
|
||||
--profile base \
|
||||
--output-dir build-macos/Products \
|
||||
--game /path/to/RMCP01.iso \
|
||||
--nodtool ./nodtool
|
||||
```
|
||||
|
||||
### Building Both (with Online Retro-WFC Payload):
|
||||
```bash
|
||||
# 1. Download Retro-WFC payload into a staging directory:
|
||||
mkdir -p build/retro-wfc/binary
|
||||
curl -fsSL --retry 3 "http://nas.play.rwfc.net/payload?g=RMCPD00" \
|
||||
-o build/retro-wfc/binary/payload.RMCPD00.bin
|
||||
|
||||
# 2. Run the automated build with the payload directory:
|
||||
Launcher/local-build-macos.command \
|
||||
--profile both \
|
||||
--output-dir build-macos/Products \
|
||||
--base-output-dir build-macos/Products \
|
||||
--game /path/to/RMCP01.iso \
|
||||
--nodtool ./nodtool \
|
||||
--retro-rewind-package-dir /path/to/RetroRewind6 \
|
||||
--retro-wfc-offline-dir build/retro-wfc
|
||||
```
|
||||
|
||||
### Building Both (Offline, Skipping Payload):
|
||||
```bash
|
||||
Launcher/local-build-macos.command \
|
||||
--profile both \
|
||||
--output-dir build-macos/Products \
|
||||
--base-output-dir build-macos/Products \
|
||||
--game /path/to/RMCP01.iso \
|
||||
--nodtool ./nodtool \
|
||||
--retro-rewind-package-dir /path/to/RetroRewind6 \
|
||||
--skip-retro-wfc-payload
|
||||
```
|
||||
|
||||
When finished, the compiled executables reside in `native-build-macos/` and the bundled `.app` packages are placed in `build-macos/Products/`.
|
||||
@@ -71,7 +71,7 @@ profiles:
|
||||
module_link_base: 0x803992E0
|
||||
output: build/mods/retro_rewind_full_cpp
|
||||
enable_retro_wfc: true
|
||||
retro_wfc_payload: http://nas.play.rwfc.net/payload?g=RMCPD00
|
||||
retro_wfc_payload: https://rwfc.net/api/wfc/payload?g=RMCPD00
|
||||
retro_wfc_legacy_bootstrap_hook: 0x800ED6E8
|
||||
riivolution:
|
||||
xml: xml/RetroRewind6.xml
|
||||
|
||||
@@ -78,22 +78,38 @@ bool ProcessSleepTimers(CpuContext* cpu)
|
||||
{
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
std::vector<SleepTimerEntry> dueTimers;
|
||||
// Pop and process ONE due timer at a time, straight from the shared table. Resuming a
|
||||
// sleeper re-enters the scheduler (OSResumeThread -> SelectThread) and can switch fibers
|
||||
// away from this call. Timers that had already been popped into a private list would then
|
||||
// sit on the suspended fiber's stack with their threads parked and no entry in the table:
|
||||
// exactly the "park-shaped with no pending wake timer" strand the reconciler below heals
|
||||
// 100ms late, followed by a "sleep-timer stale" drop when this fiber finally resumes.
|
||||
// Leaving unprocessed timers in the table keeps them visible to every other pump (idle
|
||||
// loop, other threads' SelectThread) while this one is switched away.
|
||||
bool processedAny = false;
|
||||
constexpr size_t kMaxTimersPerCall = 64;
|
||||
size_t processedCount = 0;
|
||||
const auto now = Clock::now();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(gSleepTimerMutex);
|
||||
auto it = gSleepTimers.begin();
|
||||
while (it != gSleepTimers.end()) {
|
||||
if (it->deadline > now) {
|
||||
++it;
|
||||
continue;
|
||||
while (processedCount < kMaxTimersPerCall) {
|
||||
SleepTimerEntry timer{0, {}};
|
||||
bool found = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(gSleepTimerMutex);
|
||||
for (auto it = gSleepTimers.begin(); it != gSleepTimers.end(); ++it) {
|
||||
if (it->deadline <= now) {
|
||||
timer = *it;
|
||||
gSleepTimers.erase(it);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
dueTimers.push_back(*it);
|
||||
it = gSleepTimers.erase(it);
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
break;
|
||||
}
|
||||
++processedCount;
|
||||
processedAny = true;
|
||||
|
||||
for (const SleepTimerEntry& timer : dueTimers) {
|
||||
const uint32_t threadPtr = timer.threadPtr;
|
||||
if (threadPtr == 0 ||
|
||||
!Memory::Contains(threadPtr + kThreadSuspendOffset, sizeof(uint32_t))) {
|
||||
@@ -219,7 +235,7 @@ bool ProcessSleepTimers(CpuContext* cpu)
|
||||
}
|
||||
}
|
||||
|
||||
return !dueTimers.empty();
|
||||
return processedAny;
|
||||
}
|
||||
} // namespace OsHleInternal
|
||||
|
||||
|
||||
@@ -35,6 +35,39 @@ public class RetroWfcPayloadLoweringTests
|
||||
Assert.Equal("moduleFunction", pointer.TargetKind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProductionPayloadValidatesAndTranslatesEverySupportedPatch()
|
||||
{
|
||||
var payloadRoot = Path.Combine(
|
||||
AppContext.BaseDirectory,
|
||||
"TestAssets",
|
||||
"RetroWfcPayload");
|
||||
WiiCompiled.Setup.Common.RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(payloadRoot);
|
||||
var payloadPath = Path.Combine(
|
||||
payloadRoot,
|
||||
"binary",
|
||||
"payload.RMCPD00.bin");
|
||||
var payload = File.ReadAllBytes(payloadPath);
|
||||
|
||||
var result = RetroWfcPayload.Parse(
|
||||
payload,
|
||||
ProductionPayloadManifest(),
|
||||
0x81800000u,
|
||||
0x00200000u,
|
||||
"TestAssets/RetroWfcPayload/binary/payload.RMCPD00.bin");
|
||||
|
||||
Assert.Equal("RMCPD00", result.Summary.Game);
|
||||
Assert.Equal(payload.Length, result.Summary.PayloadImageSize);
|
||||
Assert.True(result.LoweringPlan.IsPlannable);
|
||||
Assert.Empty(result.LoweringPlan.Issues);
|
||||
Assert.NotEmpty(result.LoweringPlan.StaticBytePatches);
|
||||
Assert.NotEmpty(result.LoweringPlan.ExecutableHooks);
|
||||
Assert.NotEmpty(result.LoweringPlan.StaticPointers);
|
||||
Assert.All(result.LoweringPlan.ExecutableHooks, hook => Assert.NotNull(hook.TargetAddress));
|
||||
Assert.All(result.LoweringPlan.StaticPointers, pointer => Assert.NotNull(pointer.TargetAddress));
|
||||
Assert.NotEmpty(result.Summary.InitializationCallbacks);
|
||||
}
|
||||
|
||||
private static BaseManifest TestManifest() =>
|
||||
new(
|
||||
"test",
|
||||
@@ -52,6 +85,51 @@ public class RetroWfcPayloadLoweringTests
|
||||
],
|
||||
"ranges.json");
|
||||
|
||||
// The payload parser needs the base image's address classes and containing
|
||||
// function ranges to prove every patch can be lowered. A single synthetic
|
||||
// executable and writable ranges are sufficient here: the assertions above
|
||||
// test the real production payload without checking proprietary game bytes
|
||||
// into CI. The split also proves pointer patches lower as data writes.
|
||||
private static BaseManifest ProductionPayloadManifest() =>
|
||||
new(
|
||||
"test",
|
||||
1,
|
||||
"RMCP01",
|
||||
"P",
|
||||
"",
|
||||
0,
|
||||
[
|
||||
new BaseSectionMetadata(
|
||||
".synthetic-text",
|
||||
"synthetic.dol",
|
||||
0x80000000u,
|
||||
0x80800000u,
|
||||
true,
|
||||
false,
|
||||
"synthetic_text.bin",
|
||||
0),
|
||||
new BaseSectionMetadata(
|
||||
".synthetic-data",
|
||||
"synthetic.dol",
|
||||
0x80800000u,
|
||||
0x81000000u,
|
||||
false,
|
||||
true,
|
||||
"synthetic_data.bin",
|
||||
0)
|
||||
],
|
||||
[
|
||||
new BaseFunctionRangeMetadata(
|
||||
0x80000000u,
|
||||
0x80800000u,
|
||||
"synthetic_base",
|
||||
".synthetic-text",
|
||||
0,
|
||||
"test",
|
||||
["Executable"])
|
||||
],
|
||||
"ranges.json");
|
||||
|
||||
private static byte[] BuildSharedPayloadFixture()
|
||||
{
|
||||
var payload = new byte[0x240];
|
||||
|
||||
BIN
Binary file not shown.
@@ -26,6 +26,11 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Translator.Core\Translator.Core.csproj" />
|
||||
<ProjectReference Include="..\..\src\Translator.Cli\Translator.Cli.csproj" />
|
||||
<ProjectReference Include="..\..\..\Launcher\WiiCompiled.Setup.Common\WiiCompiled.Setup.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="TestAssets\RetroWfcPayload\binary\payload.RMCPD00.bin" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
|
||||
Reference in new issue
Block a user