diff --git a/patches/graphics_patches.c b/patches/graphics_patches.c index f98d8ab..d0b9d0d 100644 --- a/patches/graphics_patches.c +++ b/patches/graphics_patches.c @@ -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) { @@ -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; @@ -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)); @@ -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++; @@ -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( @@ -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); @@ -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(); } @@ -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)) { diff --git a/patches/lod_patches.c b/patches/lod_patches.c index 1e07f0b..3d8dd63 100644 --- a/patches/lod_patches.c +++ b/patches/lod_patches.c @@ -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, @@ -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) { @@ -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( @@ -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) { @@ -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)); } diff --git a/patches/modelview_stack_patches.c b/patches/modelview_stack_patches.c index d5d1236..bd43659 100644 --- a/patches/modelview_stack_patches.c +++ b/patches/modelview_stack_patches.c @@ -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); @@ -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); diff --git a/patches/overlay_context_patches.c b/patches/overlay_context_patches.c index c6203c9..ead901b 100644 --- a/patches/overlay_context_patches.c +++ b/patches/overlay_context_patches.c @@ -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); diff --git a/patches/ship_window_patches.c b/patches/ship_window_patches.c index 015533f..d9ff9a6 100644 --- a/patches/ship_window_patches.c +++ b/patches/ship_window_patches.c @@ -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(); + } } diff --git a/patches/sssv_patch_symbols.h b/patches/sssv_patch_symbols.h index 373d61f..b77f783 100644 --- a/patches/sssv_patch_symbols.h +++ b/patches/sssv_patch_symbols.h @@ -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; diff --git a/patches/sssv_patch_types.h b/patches/sssv_patch_types.h index f4bee54..0e562b9 100644 --- a/patches/sssv_patch_types.h +++ b/patches/sssv_patch_types.h @@ -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