Skip to content
56 changes: 50 additions & 6 deletions patches/graphics_patches.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ static u32 g_dbg_world_perspective_calls = 0;
static u32 g_dbg_depth_clear_calls = 0;
static u32 g_dbg_world_depth_failsafe_calls = 0;

// End of the world state at the start of the translucent object list, so the dome reflection replay
// (ship_window_patches.c) can run the list without the projection reload.
Gfx* gXluStateBarrierEnd = NULL;

void setup_intro_perspective_638558(Gfx** dl);

static inline void write_main_viewport_expand(s16 viewport_width, s16 viewport_height) {
Expand Down Expand Up @@ -257,6 +261,35 @@ static inline void patch_world_chunk_clip_ratio_dl(void) {
}
}

// The TV screen model (Something Fishy monitors) draws its static as a white quad with prim alpha 0x80
// and G_AC_DITHER, in 2-cycle mode with G_CC_PASS2 / G_CC_PRIMITIVE. The alpha compare uses the first
// cycle's alpha, which there is COMBINED: on hardware that still holds the previous pixel's output (~0x80),
// so about half the pixels pass at random. RT64 starts COMBINED at 0, every pixel fails and the screen
// stays black. Use PRIMITIVE in the first cycle as well so the dither compares against the prim alpha.
#define TV_SCREEN_DL_SEGMENT_OFFSET 0x00017AA0u
#define TV_SCREEN_STATIC_COMBINE_INDEX 27

static inline void patch_tv_screen_static_dl(void) {
Gfx* cmd;
Gfx original;
Gfx fixed;

if (gSegment1Base == NULL) {
return;
}

cmd = &((Gfx*)((u8*)gSegment1Base + TV_SCREEN_DL_SEGMENT_OFFSET))[TV_SCREEN_STATIC_COMBINE_INDEX];
gDPSetCombineMode(&original, G_CC_PASS2, G_CC_PRIMITIVE);
gDPSetCombineMode(&fixed, G_CC_PRIMITIVE, G_CC_PRIMITIVE);

// Only touch it while segment 1 holds the TV model (the previous command is its dithered render mode).
if ((cmd->words.w0 == original.words.w0) && (cmd->words.w1 == original.words.w1) &&
((cmd[-1].words.w0 & 0xFF000000u) == 0xB9000000u) && ((cmd[-1].words.w1 & 0x3u) == 0x3u)) {
cmd->words.w0 = fixed.words.w0;
cmd->words.w1 = fixed.words.w1;
}
}

static inline void append_sp_set_geometry_mode_cmd(Gfx** dl, u32 mode) {
(*dl)->words.w0 = 0xB7000000u;
(*dl)->words.w1 = mode;
Expand Down Expand Up @@ -346,6 +379,7 @@ RECOMP_PATCH void load_segments(Gfx **arg0, DisplayList *ddl) {
if (rc_world_projection_active()) {
patch_world_chunk_clip_ratio_dl();
}
patch_tv_screen_static_dl();

gSPSegment((*arg0)++, 0, 0);
gSPSegment((*arg0)++, 1, (const void*)(uintptr_t)osVirtualToPhysical(gSegment1Base));
Expand Down Expand Up @@ -477,7 +511,9 @@ RECOMP_PATCH void draw_rectangle(Gfx** dl, s16 x0, s16 y0, s16 x1, s16 y1, u8 r,
gDPFillRectangle((*dl)++, draw_x0, draw_y0, draw_x1, draw_y1);
}

gDPSetScissor((*dl)++, G_SC_NON_INTERLACE, 0, 0, screen_w, screen_h);
// The original restores the TV-safe frame scissor (8, 8, w - 8, h - 8) here; leaving it at
// full screen lets the 3D geometry drawn afterwards spill into the overscan border.
set_default_frame_scissor(dl);
gDPPipeSync((*dl)++);

g_dbg_draw_rectangle_calls++;
Expand Down Expand Up @@ -597,11 +633,9 @@ static inline void append_intro_black_border_mask(Gfx** dl) {
}

RECOMP_PATCH void draw_intro_logo_model(void) {
ScissorRect full_rect;

fill_full_scissor_rect(&full_rect);

gDPSetScissor(gMainDL++, G_SC_NON_INTERLACE, full_rect.ulx, full_rect.uly, full_rect.lrx, full_rect.lry);
// The original clips the logo ship to the TV-safe frame (8, 8, 312, 232); only widescreen
// may open it up to the full frame.
set_default_frame_scissor(&gMainDL);
gSPDisplayList(gMainDL++, gOpaqueLit3DRenderSetupDl);

guTranslate(
Expand Down Expand Up @@ -732,6 +766,10 @@ RECOMP_PATCH void render_intro_overlay_frame_63BF88(void) {
gScreenWidth = target_width;
gScreenHeight = SSSV_BASE_HEIGHT;
load_segments(&gMainDL, gDisplayListContext);

// Title and attract frames are grabbed from RDRAM for the transition into the game.
rc_ensure_rt64_extended_enabled(&gMainDL);
gEXSetRenderToRAM(gMainDL++, 1);
clear_depth_buffer(&gMainDL);
write_main_viewport_expand(gScreenWidth, gScreenHeight);

Expand Down Expand Up @@ -802,6 +840,11 @@ RECOMP_PATCH void setup_world_perspective_6AB090(DisplayList* arg0) {
rc_ensure_rt64_extended_enabled(&gLayer0DL);
rc_ensure_rt64_extended_enabled(&gAuxDL);

// Writing every rendered frame back to RDRAM costs a GPU readback that scales with the output
// resolution (2x: ~25 fps instead of 30 on weaker GPUs). In gameplay the game only reads a frame
// back while entering the pause menu, where it copies the previous frame into the menu thumbnail.
gEXSetRenderToRAM(gMainDL++, (gInitialisationState != 0) ? 1 : 0);

if (target_width > SSSV_BASE_WIDTH) {
append_world_depth_clear_failsafe();
}
Expand Down Expand Up @@ -830,6 +873,7 @@ RECOMP_PATCH void setup_world_perspective_6AB090(DisplayList* arg0) {
gSPMatrix(gXluDL++, &arg0->unk374D0, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gSPPerspNormalize(gXluDL++, gWorldPerspNorm);
#endif
gXluStateBarrierEnd = gXluDL;

g_dbg_world_perspective_calls++;
if (SHOULD_TRACE_PERIODIC(g_dbg_world_perspective_calls, 6, 240)) {
Expand Down
51 changes: 44 additions & 7 deletions patches/lod_patches.c
Original file line number Diff line number Diff line change
@@ -1,14 +1,43 @@
#include "sssv_patch_common.h"
#include "sssv_render_context.h"

static inline void apply_lod_overrides(s32* arg3) {

#if SSSV_PATCH_DISABLE_LOD || SSSV_PATCH_DISABLE_FAR_AWAY_CULL
// Forcing every object to VISIBILITY_VISIBLE changes more than rendering: the game also acts on the
// out-of-bounds/too-far results, and the camera oscillates (it never settles, so it shakes while the
// player moves). The far-away cull override therefore stays off by default; the LOD override only
// raises the detail level of objects the game itself classified as visible.
static inline void apply_far_away_cull_override(s32* arg3) {
#if SSSV_PATCH_DISABLE_FAR_AWAY_CULL
*arg3 = 0;
gLodDetailState = 0;
#endif
}

static inline s16 apply_lod_override(s16 visibility) {
#if SSSV_PATCH_DISABLE_LOD
if (visibility == VISIBILITY_VISIBLE) {
gLodDetailState = 0;
}
#endif
return visibility;
}

// Near a reflecting dome wall (gCameraVisibilityMask[6], see ship_window_patches.c) an object also counts as
// visible when its mirror image is, so its reflection is drawn even while the object itself is off screen.
static inline s32 mirror_image_visible(s32 xPos, s32 zPos, s32 yPos, s32 arg3, s8 arg8, u8 arg9, s32 allow_x_mirror) {
const s32 plane = (gCameraVisibilityMask[6] & 0xFFC) << 0x12;

if (arg9 != 0) {
return 0;
}
if (gCameraVisibilityMask[6] & 1) {
return classify_visibility_simple(xPos, zPos, plane - yPos, arg3, arg8) == 0;
}
if (allow_x_mirror && (gCameraVisibilityMask[6] & 2)) {
return classify_visibility_simple(plane - xPos, zPos, yPos, arg3, arg8) == 0;
}
return 0;
}

RECOMP_PATCH s16 classify_object_visibility_6FA0A0(
s32 xPos,
s32 zPos,
Expand All @@ -22,7 +51,7 @@ RECOMP_PATCH s16 classify_object_visibility_6FA0A0(
u8 arg9
) {
rc_set(RC_WORLD3D);
apply_lod_overrides(&arg3);
apply_far_away_cull_override(&arg3);

if (arg3 == 0) {
if (is_world_cell_loaded_6AB9E4(xPos >> 16, zPos >> 16, yPos >> 16) != 0) {
Expand All @@ -32,7 +61,11 @@ RECOMP_PATCH s16 classify_object_visibility_6FA0A0(
}
}

return classify_visibility_and_draw_fov_mask(xPos, zPos, yPos, arg3, fovImageIdx, arg5, arg6, arg7, arg8, arg9);
if (mirror_image_visible(xPos, zPos, yPos, arg3, arg8, arg9, 1)) {
return apply_lod_override(VISIBILITY_VISIBLE);
}

return apply_lod_override(classify_visibility_and_draw_fov_mask(xPos, zPos, yPos, arg3, fovImageIdx, arg5, arg6, arg7, arg8, arg9));
}

RECOMP_PATCH s16 classify_dynamic_visibility_6FA26C(
Expand All @@ -48,7 +81,7 @@ RECOMP_PATCH s16 classify_dynamic_visibility_6FA26C(
u8 arg9
) {
rc_set(RC_WORLD3D);
apply_lod_overrides(&arg3);
apply_far_away_cull_override(&arg3);

if (arg3 == 0) {
if (is_world_cell_loaded_6AB9E4(xPos >> 16, zPos >> 16, yPos >> 16) != 0) {
Expand All @@ -58,5 +91,9 @@ RECOMP_PATCH s16 classify_dynamic_visibility_6FA26C(
}
}

return classify_visibility_and_draw_fov_mask(xPos, zPos, yPos, arg3, fovImageIdx, arg5, arg6, arg7, arg8, arg9);
if (mirror_image_visible(xPos, zPos, yPos, arg3, arg8, arg9, 0)) {
return apply_lod_override(VISIBILITY_VISIBLE);
}

return apply_lod_override(classify_visibility_and_draw_fov_mask(xPos, zPos, yPos, arg3, fovImageIdx, arg5, arg6, arg7, arg8, arg9));
}
5 changes: 3 additions & 2 deletions patches/modelview_stack_patches.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ RECOMP_PATCH void render_rig_instance_xlu(s32 x, s32 y, s32 z, s16 pitch, s16 ya
gSPPopMatrix(gXluDL++, G_MTX_MODELVIEW);
}

append_joint_pose_transform(1, &gRigPoseStates[pose_bank][1]);
// The original pushes joint 1 a second time here, so part 9 gets the joint 1 offset twice:
// Evo's visor floats a head-height above him in the intro cockpit. The pop above already
// restored the joint 1 level, so push joint 3 from there.

if (pose_bank == 1) {
gDPSetPrimColor(gXluDL++, 0, 0, 0xEA, 0xE6, 0xFF, 0xFF);
Expand All @@ -122,7 +124,6 @@ RECOMP_PATCH void render_rig_instance_xlu(s32 x, s32 y, s32 z, s16 pitch, s16 ya
gSPDisplayList(gXluDL++, gRigPartDisplayLists[rig_variant][9]);
gSPPopMatrix(gXluDL++, G_MTX_MODELVIEW);
gSPPopMatrix(gXluDL++, G_MTX_MODELVIEW);
gSPPopMatrix(gXluDL++, G_MTX_MODELVIEW);

// Return to the caller's stack depth before the next translucent draw.
gSPPopMatrix(gXluDL++, G_MTX_MODELVIEW);
Expand Down
7 changes: 6 additions & 1 deletion patches/overlay_context_patches.c
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,12 @@ RECOMP_PATCH void render_terminal_background_frame(void) {

gScreenWidth = terminal_frame_width;
load_default_display_list(&gMainDL);
patch_clear_overlay_color(&gMainDL);
// The original only darkens the previous frame here (the translucent rectangle above), which
// leaves the trails behind the glyphs, DNA and animal. In widescreen a native-width copy of
// the stat text also stays visible that way, so the frame is only cleared there.
if (terminal_frame_width > SSSV_BASE_WIDTH) {
patch_clear_overlay_color(&gMainDL);
}
set_menu_text_color(0xFF, 0xFF, 0xFF, 0xFF);
select_font(0, FONT_COMIC_SANS_PATCH, 1, 0);
update_terminal_scene_lighting(0xFF);
Expand Down
97 changes: 95 additions & 2 deletions patches/ship_window_patches.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,99 @@
#include "sssv_patch_common.h"

// The original ship-window path re-rendered world geometry under a second
// projection. The recomp keeps the normal world pass and drops that replay.
// Reflections in the dome glass (e.g. the glass walls in Snow Joke). When the camera is near a reflecting
// wall, the game sets gCameraVisibilityMask[6] (bit 0: z mirror, bit 1: x mirror, bits 2-11: plane offset)
// and replays the frame's object display lists under a mirror matrix multiplied into the projection. This
// happens before the world geometry is drawn, so the mirrored objects only show through the translucent glass.

// The patch-side DisplayList type stops before these fields; offsets as in the decomp's DisplayList.
#define DL_OPAQUE_LIST(ctx) ((Gfx*)((u8*)(ctx) + 0x9600))
#define DL_XLU_LIST(ctx) ((Gfx*)((u8*)(ctx) + 0xBB80))
#define DL_XLU_LIST_LEN 1000
#define DL_MIRROR_MTX(ctx) ((Mtx*)((u8*)(ctx) + 0x37510))
#define DL_AMBIENT(ctx) ((u8*)(ctx) + 0x3B638) // lights.a: col[3], pad, colc[3], pad
#define DL_LIGHT0(ctx) ((u8*)(ctx) + 0x3B640) // lights.l[0]: col[3], pad, colc[3], pad, dir[3], pad
#define DL_MIRROR_AMBIENT(ctx) ((u8*)(ctx) + 0x3B6B0)
#define DL_MIRROR_LIGHT(ctx) ((u8*)(ctx) + 0x3B6B8)

// F3DEX gSPNumLights(1) and gSPLight(l, n).
static inline void sp_num_lights_1(Gfx* pkt) {
pkt->words.w0 = 0xBC000002u; // G_MOVEWORD, G_MW_NUMLIGHT
pkt->words.w1 = 0x80000040u; // NUML(1)
}

static inline void sp_light(Gfx* pkt, const void* light, u32 n) {
pkt->words.w0 = _SHIFTL(G_MOVEMEM, 24, 8) | _SHIFTL((n - 1) * 2 + 0x86, 16, 8) | _SHIFTL(16, 0, 16);
pkt->words.w1 = (u32)(uintptr_t)light;
}

extern void func_80127D30(Mtx* mtx, s16 zOffset); // build_ship_window_z_projection_matrix
extern void func_80127ED4(Mtx* mtx, s16 xOffset); // build_ship_window_x_projection_matrix

// Start of the translucent object list after the world state that setup_world_perspective_6AB090 puts in
// front of it. That state reloads the projection, which would drop the mirror matrix in the replay.
extern Gfx* gXluStateBarrierEnd;

static inline void load_world_modelview(void) {
gSPMatrix(gMainDL++, &gDisplayListContext->unk374D0, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
}

static inline void restore_world_projection(void) {
gSPMatrix(gMainDL++, &gDisplayListContext->unk37410, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION);
gSPMatrix(gMainDL++, &gDisplayListContext->unk37450, G_MTX_NOPUSH | G_MTX_MUL | G_MTX_PROJECTION);
gSPMatrix(gMainDL++, &gDisplayListContext->unk37490, G_MTX_NOPUSH | G_MTX_MUL | G_MTX_PROJECTION);
}

RECOMP_PATCH void render_ship_window_projection_replay(void) {
DisplayList* ctx = gDisplayListContext;
u8* ambient;
u8* light;
u8* mirror_ambient;
u8* mirror_light;
s32 i;

if (gCameraVisibilityMask[6] & 1) {
func_80127D30(DL_MIRROR_MTX(ctx), (gCameraVisibilityMask[6] & 0xFFC) << 1);

gSPMatrix(gMainDL++, DL_MIRROR_MTX(ctx), G_MTX_NOPUSH | G_MTX_MUL | G_MTX_PROJECTION);
if ((gXluStateBarrierEnd >= DL_XLU_LIST(ctx)) && (gXluStateBarrierEnd < DL_XLU_LIST(ctx) + DL_XLU_LIST_LEN)) {
load_world_modelview();
gSPDisplayList(gMainDL++, gXluStateBarrierEnd);
}
load_world_modelview();
gSPDisplayList(gMainDL++, DL_OPAQUE_LIST(ctx));
gSPPopMatrix(gMainDL++, G_MTX_MODELVIEW);

restore_world_projection();
} else if (gCameraVisibilityMask[6] & 2) {
func_80127ED4(DL_MIRROR_MTX(ctx), (gCameraVisibilityMask[6] & 0xFFC) << 1);

// The x mirror only replays opaque objects, lit with half the ambient and a quarter of the light.
ambient = DL_AMBIENT(ctx);
light = DL_LIGHT0(ctx);
mirror_ambient = DL_MIRROR_AMBIENT(ctx);
mirror_light = DL_MIRROR_LIGHT(ctx);
for (i = 0; i < 3; i++) {
mirror_ambient[i] = ambient[i] >> 1;
mirror_ambient[4 + i] = ambient[4 + i] >> 1;
mirror_light[i] = light[i] >> 2;
mirror_light[4 + i] = light[i] >> 2; // as in the original: colc from col
mirror_light[8 + i] = light[8 + i];
}

sp_num_lights_1(gMainDL++);
sp_light(gMainDL++, mirror_light, 1);
sp_light(gMainDL++, mirror_ambient, 2);

gSPMatrix(gMainDL++, DL_MIRROR_MTX(ctx), G_MTX_NOPUSH | G_MTX_MUL | G_MTX_PROJECTION);
load_world_modelview();
gSPDisplayList(gMainDL++, DL_OPAQUE_LIST(ctx));

sp_num_lights_1(gMainDL++);
sp_light(gMainDL++, light, 1);
sp_light(gMainDL++, ambient, 2);

gSPPopMatrix(gMainDL++, G_MTX_MODELVIEW);

restore_world_projection();
}
}
1 change: 1 addition & 0 deletions patches/sssv_patch_symbols.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ extern u8 gLodDetailState;
extern s16 gScreenWidth;
extern s16 gScreenHeight;
extern s8 gAttractModeState;
extern u8 gInitialisationState;
extern VpRaw gMainViewport;
extern Gfx* gMainDL;
extern Gfx* gOpaqueDL;
Expand Down
2 changes: 1 addition & 1 deletion patches/sssv_patch_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#define VISIBILITY_INVISIBLE 4

#define SSSV_PATCH_DISABLE_LOD 1
#define SSSV_PATCH_DISABLE_FAR_AWAY_CULL 1
#define SSSV_PATCH_DISABLE_FAR_AWAY_CULL 0
#define SSSV_PATCH_TRANSPARENT_SHIP_GLASS 0
#define SSSV_PATCH_DISABLE_PB_UFO_GLASS_OBJECT 0
#define SSSV_PATCH_SHIP_WINDOW_XLU_STATE_BARRIER 1
Expand Down
Loading