From b71479aeae7ccaba88f540c818cf6e1d2a8fc093 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Sun, 30 Nov 2025 01:15:44 -0500 Subject: [PATCH] Enhance jiggy puzzle framebuffer effect to use direct framebuffer sampling to work with high res and widescreen --- patches/transition_patches.c | 454 +++++++++++++++++++++++++++++++++++ 1 file changed, 454 insertions(+) create mode 100644 patches/transition_patches.c diff --git a/patches/transition_patches.c b/patches/transition_patches.c new file mode 100644 index 0000000..06399af --- /dev/null +++ b/patches/transition_patches.c @@ -0,0 +1,454 @@ +#include "patches.h" +#include "graphics.h" +#include "transform_ids.h" +#include "core1/core1.h" +#include "gc/gctransition.h" +#include "gc/gcbound.h" + +float gu_sqrtf(float val); +BKGfxList *model_getDisplayList(BKModelBin *arg0); +s32 getGameMode(void); +void scissorBox_setSmall(void); +s16 *func_8030C704(void); + +typedef enum { + TRANSITION_ID_1_BLACK_IN = 1, + TRANSITION_ID_2_BLACK_OUT, + TRANSITION_ID_3_CIRCLE_IN, + TRANSITION_ID_4_CIRCLE_OUT, + TRANSITION_ID_5_JIGGY_IN, + TRANSITION_ID_6_JIGGY_OUT, + TRANSITION_ID_7, + TRANSITION_ID_8_WHITE_IN, + TRANSITION_ID_9_WHITE_OUT, + TRANSITION_ID_A_WITCH_HEAD_OUT, + TRANSITION_ID_B_CIRCLE_IN_FAST, + TRANSITION_ID_C_CIRCLE_OUT_FAST, + TRANSITION_ID_D, + TRANSITION_ID_E_CIRCLE_IN_SLOW, + TRANSITION_ID_F_CIRCLE_OUT_SLOW, + TRANSITION_ID_10_FALLING_PIECES_IN, + TRANSITION_ID_11_FALLING_PIECES_OUT, + TRANSITION_ID_12_CIRCLE_IN_EXTRA_SLOW, + TRANSITION_ID_13_CIRCLE_OUT_EXTRA_SLOW, + TRANSITION_ID_14_CIRCLE_IN_SUPER_SLOW, + TRANSITION_ID_15_CIRCLE_OUT_SUPER_SLOW +} TransitionId; + +typedef enum { + TRANSITION_STATE_0_NONE, + TRANSITION_STATE_1_LOADING, + TRANSITION_STATE_2_BLACK_IN, + TRANSITION_STATE_3_BLACK_OUT, + TRANSITION_STATE_4_FADE_IN, + TRANSITION_STATE_5_FADE_OUT, + TRANSITION_STATE_6_LOADING_WHITE, + TRANSITION_STATE_7_WHITE_IN, + TRANSITION_STATE_8_WHITE_OUT +} TransitionState; + +typedef struct { + u8 uid; + u8 state; + u8 next_state; + // u8 pad3[1]; + f32 duration; + s32 model_index; + s32 anim_index; + f32 scale; +}TransitionInfo; + +extern struct{ + s32 unk0; //times drawn? + TransitionInfo *transistion_info; + u8 state; + //u8 pad9[3]; //not needed + void * model_ptr; //asset_ptr + f32 rotation; + f32 timer; + AnimCtrl *anctrl; + s32 substate; //times update called? +} s_current_transition; + +extern f32 D_8036C440; +extern f32 D_8036C444; + +// Dedicated framebuffer to hold the frame that was rendered as the jiggy transition texture. +u16 jiggy_transition_framebuffer[DEFAULT_FRAMEBUFFER_WIDTH * DEFAULT_FRAMEBUFFER_HEIGHT]; + +Gfx load_jiggy_transition_texture[] = { + // Load the entire framebuffer as a texture, as tmem limits can be bypassed in RT64 for framebuffer textures. + gsDPLoadTextureTile(jiggy_transition_framebuffer, G_IM_FMT_RGBA, G_IM_SIZ_16b, + DEFAULT_FRAMEBUFFER_WIDTH, DEFAULT_FRAMEBUFFER_HEIGHT, + 0, 0, + DEFAULT_FRAMEBUFFER_WIDTH - 1, DEFAULT_FRAMEBUFFER_HEIGHT - 1, + 0, + G_TX_CLAMP | G_TX_NOMIRROR, G_TX_CLAMP | G_TX_NOMIRROR, + G_TX_NOMASK, G_TX_NOMASK, + G_TX_NOLOD, G_TX_NOLOD + ), + gsSPEndDisplayList(), +}; + +const s32 fb_tiles_per_row = 10; +const s32 fb_tiles_per_column = 8; +const s32 fb_tile_size = 32; +const s32 fb_grid_width = fb_tile_size * fb_tiles_per_row; +const s32 fb_grid_height = fb_tile_size * fb_tiles_per_column; + +// Input vertex coordinates for the corners of the jiggy transition effect. +const float left_x_input = -701.0f; +const float top_y_input = 701.0f; +const float right_x_input = 701.0f; +const float bottom_y_input = -701.0f; + +// NDC output vertex coordinates for the corners of the jiggy transition effect, measured via renderdoc. +const float left_x_output = -1.0941545714285714285714285714286f; +const float top_y_output = 1.4791303428571428571428571428571f; +const float right_x_output = 1.101004f; +const float bottom_y_output = -1.4883895428571428571428571428571; + +const float output_per_input_x = (right_x_output - left_x_output) / (right_x_input - left_x_input); +const float output_per_input_y = (bottom_y_output - top_y_output) / (bottom_y_input - top_y_input); + +void set_vertex_texcoord(Vtx* v) { + // Calculate vertex texture coordinates based on the input coordinate. The untransformed positions of the jiggy piece vertices + // reflect where they end up in the combined picture already, so they can be used to calculate the correct texcoord without + // taking into account the animation. + + float output_x = output_per_input_x * v->v.ob[0]; + float output_y = output_per_input_y * v->v.ob[1]; + + // Convert from -1,1 to 0,1. Also flip the Y-axis. + float output_x_adjusted = output_x * 0.5f + 0.5f; + float output_y_adjusted = output_y * -0.5f + 0.5f; + + // Calculate texcoords based on the input texture size, accounting for a texcoord scale of 0.5. + float texcoord_s = output_x_adjusted * DEFAULT_FRAMEBUFFER_WIDTH * 2.0f; + float texcoord_t = output_y_adjusted * DEFAULT_FRAMEBUFFER_HEIGHT * 2.0f; + + // Convert the texcoords to fixed point with 5 bits of fractional precision. + v->v.tc[0] = (s32)(texcoord_s * 32.0f); + v->v.tc[1] = (s32)(texcoord_t * 32.0f); +} + +void patch_falling_jiggies_model(BKModelBin* model) { + // Validate that this is the unmodified falling jiggy transition model. + if (model->texture_list_offset_8 != 0x38) { + return; + } + if (model->gfx_list_offset_C != 0x28770) { + return; + } + if (model->vtx_list_offset_10 != 0x2EC40) { + return; + } + // Start and end segmented addresses of the framebuffer tile range. + const u32 fb_tiles_start = 0x02000000; + const u32 fb_tiles_end = 0x02028000; + const u32 fb_tile_bytes = 32 * 32 * sizeof(u16); + // // Texcoord offsets, multiplied by 2 to account for 0.5 texcoord scaling and converted to fixed point. + // const u32 fb_s_offset = (((fb_grid_width - DEFAULT_FRAMEBUFFER_WIDTH /*- 1*/) * 2 / 2)) << 5; + // const u32 fb_t_offset = (((fb_grid_height - DEFAULT_FRAMEBUFFER_HEIGHT /*- 1*/) * 2 / 2)) << 5; + + BKGfxList* gfx_list = model_getDisplayList(model); + + // Iterate until the end of the displaylist data, which lines up with the start of the vertex data. + Gfx* cur_gfx = &gfx_list->list[0]; + Gfx* end_gfx = (Gfx*)((char*)model + model->vtx_list_offset_10); + Vtx* model_verts = model_getVtxList(model)->vtx_18; + s32 prev_tile_col = -1; + s32 prev_tile_row = -1; + while (cur_gfx < end_gfx) { + // Check if this is a set texture image command. + if (cur_gfx->words.w0 >> 24 == G_SETTIMG) { + // Check if the texture pointer is a framebuffer tile. + if (cur_gfx->words.w1 >= fb_tiles_start && cur_gfx->words.w1 < fb_tiles_end) { + // Determine the row and column of the tile. + u32 tile_offset = cur_gfx->words.w1 - fb_tiles_start; + u32 tile_index = tile_offset / fb_tile_bytes; + prev_tile_row = tile_index / fb_tiles_per_row; + prev_tile_col = tile_index % fb_tiles_per_row; + + // Overwrite this command with a displaylist call to the new texture load. + gSPDisplayList(cur_gfx++, load_jiggy_transition_texture); + + // Overwrite the next 6 commands with noops. + gSPNoOp(cur_gfx++); + gSPNoOp(cur_gfx++); + gSPNoOp(cur_gfx++); + gSPNoOp(cur_gfx++); + gSPNoOp(cur_gfx++); + gSPNoOp(cur_gfx++); + + // Rewind the graphics pointer by 1 to account for the increment that happens at the end of the loop. + cur_gfx--; + } + } + // Patch vertices. + else if (cur_gfx->words.w0 >> 24 == G_VTX) { + // Get the loaded vertex count for this command. + u32 cur_vtx_count = (cur_gfx->words.w0 >> 10) & 0b111111; + + // Get the vertex address, converting from a segmented address if necessary. + Vtx* cur_vtx; + if (cur_gfx->words.w1 >> 24 == 0x01) { + cur_vtx = (Vtx*)(SEGMENT_OFFSET(cur_gfx->words.w1) + (char*)model_verts); + } + else { + // Not a segmented address (should never happen, but may help with compatibility for future mods). + cur_vtx = (Vtx*)cur_gfx->words.w1; + } + + for (u32 i = 0; i < cur_vtx_count; i++) { + // Recalculate texture coordinates based on the vertex coordinate. The untransformed vertices of the jiggy pieces + // have their final position already, so they can be used to calculate the correct texcoord. + + // All texcoords in the model can be written to without an issue, as any vertices that aren't used to display + // the framebuffer have texcoord generation enabled, so these edits will be ignored. + set_vertex_texcoord(&cur_vtx[i]); + } + } + cur_gfx++; + } +}; + +bool should_draw_to_transition_framebuffer() { + if (s_current_transition.transistion_info == NULL) { + return FALSE; + } + + if (s_current_transition.transistion_info->uid == TRANSITION_ID_10_FALLING_PIECES_IN) { + return s_current_transition.substate <= 2; + } + else { + return s_current_transition.substate == 2; + } +} + +// @recomp Patched to use the dedicated transition framebuffer if this is drawing a transition frame. +RECOMP_PATCH void scissorBox_SetForGameMode(Gfx **gdl, s32 framebuffer_idx) { + if(getGameMode() == GAME_MODE_8_BOTTLES_BONUS || getGameMode() == GAME_MODE_A_SNS_PICTURE) + { + scissorBox_setSmall(); + func_80253640(gdl, func_8030C704()); + } + else{ + scissorBox_setDefault(); + func_80253640(gdl, gFramebuffers[framebuffer_idx]); + // @recomp After clearing the main framebuffer, switch to and clear the dedicated transition framebuffer. + if (should_draw_to_transition_framebuffer()) { + func_80253640(gdl, jiggy_transition_framebuffer); + } + } +} + +// s_current_transition.transistion_info != NULL +// s_current_transition.transistion_info->model_index == ASSET_467_MODEL_TRANSITION_FALLING_JIGGIES +// s_current_transition.substate == 1 (or maybe < 2) + +// @recomp Patched to set a transform ID for transition models and the transition projection transform ID. +RECOMP_PATCH void gctransition_draw(Gfx **gdl, Mtx **mptr, Vtx **vptr){ + f32 vp_position[3]; + f32 vp_rotation[3]; + f32 percentage; + f32 sp58[3]; + f32 scale; + + s_current_transition.unk0++; + if(s_current_transition.state == 0) + return; + + viewport_backupState(); + if(s_current_transition.anctrl != NULL){ + vp_position[0] = 0.0f; + vp_position[1] = 0.0f; + vp_position[2] = 350.0f; + }else{ + vp_position[0] = 0.0f; + vp_position[1] = 0.0f; + vp_position[2] = 300.0f; + } + + vp_rotation[0] = 0.0f; + vp_rotation[1] = 0.0f; + vp_rotation[2] = 0.0f; + viewport_setNearAndFar(D_8036C440, D_8036C444); + viewport_setPosition_vec3f(vp_position); //viewport_getPosition_vec3f + viewport_setRotation_vec3f(vp_rotation); //viewport_getRotation_vec3f + viewport_update(); //camera_updateNormal + + // @recomp Set the perspective projection transform ID for the transition projection. + cur_perspective_projection_transform_id = PROJECTION_TRANSITION_TRANSFORM_ID; + + viewport_setRenderViewportAndPerspectiveMatrix(gdl, mptr); + + sp58[0] = 0.0f; + sp58[1] = 0.0f; + sp58[2] = 0.0f; + if(s_current_transition.anctrl != NULL){ + gDPSetTextureFilter((*gdl)++, G_TF_POINT); + gDPSetColorDither((*gdl)++, G_CD_DISABLE); + anctrl_drawSetup(s_current_transition.anctrl, sp58, 1); + modelRender_setDepthMode(MODEL_RENDER_DEPTH_FULL); + } + + // @recomp Calculate the ratio between the original aspect ratio and the current aspect ratio. + // TODO change this back to using the actual framebuffer size when RT64 uses that aspect ratio instead of adjusting to 4:3. + // float original_aspect = (float)DEFAULT_FRAMEBUFFER_WIDTH / DEFAULT_FRAMEBUFFER_HEIGHT; + float original_aspect = 4.0f / 3.0f; + float cur_aspect = recomp_get_target_aspect_ratio(original_aspect); + float aspect_ratio_ratio = cur_aspect / original_aspect; + + // @recomp Determine what scaling to use based on the transition type. + float transition_scale = 1.0f; + float transition_x_scale = 1.0f; + if (s_current_transition.transistion_info != NULL) { + // recomp_printf(" Transition uid: %d\n", s_current_transition.transistion_info->uid); + // recomp_printf(" Transition model index: %04X\n", s_current_transition.transistion_info->model_index); + switch (s_current_transition.transistion_info->uid) { + // Scale the jiggy framebuffer transition by the aspect ratio ratio as it just needs to cover the additional horizontal space. + // Only scale along the X axis. + case TRANSITION_ID_10_FALLING_PIECES_IN: + case TRANSITION_ID_11_FALLING_PIECES_OUT: + transition_x_scale = aspect_ratio_ratio; + break; + // Scale other transitions by slightly more to account for overscan removal. + default: + transition_scale = aspect_ratio_ratio + 0.1f; + } + + // Check if the transition model is the falling jiggies transition model, and patch it if it hasn't been patched yet. + if (s_current_transition.transistion_info->model_index == ASSET_467_MODEL_TRANSITION_FALLING_JIGGIES && s_current_transition.model_ptr != NULL) { + // Use pad0 to tell if the model has been patched. + BKModelBin* transition_model = s_current_transition.model_ptr; + if (transition_model->pad0[0] != 0xBA) { + patch_falling_jiggies_model(transition_model); + transition_model->pad0[0] = 0xBA; + } + } + } + else { + // recomp_printf(" No transition_info\n"); + transition_scale = aspect_ratio_ratio + 0.1f; + } + + //complex animation (from animation bin file) + if(s_current_transition.state == 1 || s_current_transition.state == 6){ + // @recomp Set the current transform ID to the transition model's. + cur_drawn_model_transform_id = TRANSITION_MODEL_TRANSFORM_ID_START; + + // recomp_printf(" A\n"); + + // @recomp Scale the transition. + set_additional_model_scale(transition_x_scale, 1.0f, 1.0f); + modelRender_draw(gdl, mptr, sp58, vp_rotation, transition_scale, 0, s_current_transition.model_ptr); + + // @recomp Clear the current transform ID. + cur_drawn_model_transform_id = 0; + if(s_current_transition.anctrl != NULL){ + gDPSetTextureFilter((*gdl)++, G_TF_BILERP); + gDPSetColorDither((*gdl)++, G_CD_MAGICSQ); + } + return; + } + + + percentage = s_current_transition.timer/(s_current_transition.transistion_info->duration); + if(s_current_transition.state == TRANSITION_STATE_4_FADE_IN){ + //rotate and zoom in + if(s_current_transition.transistion_info->uid == 0x10){ + scale = s_current_transition.transistion_info->scale; + + } + else{ + vp_rotation[2] = s_current_transition.rotation - 90.0f*percentage; + scale = percentage*s_current_transition.transistion_info->scale + 0.1; + } + // @recomp Set the current transform ID to the transition model's. + cur_drawn_model_transform_id = TRANSITION_MODEL_TRANSFORM_ID_START; + + // recomp_printf(" B\n"); + + // @recomp Scale the transition. + set_additional_model_scale(transition_x_scale, 1.0f, 1.0f); + modelRender_draw(gdl, mptr, sp58, vp_rotation, scale * transition_scale, 0, s_current_transition.model_ptr); + + // @recomp Clear the current transform ID. + cur_drawn_model_transform_id = 0; + } + else if(s_current_transition.state == TRANSITION_STATE_5_FADE_OUT){//L8030B9EC + switch (s_current_transition.transistion_info->uid) + { + default: + //rotate and zoom out + vp_rotation[2] = s_current_transition.rotation - 90.0f*percentage; + scale = (1.0f - percentage)*s_current_transition.transistion_info->scale + 0.1; + break; + case 0x11: + //static + scale = s_current_transition.transistion_info->scale; + break; + case 0xA: + //zoom out only + vp_rotation[2] = 0.0f; + scale = (1.0f - func_80257618(percentage))*s_current_transition.transistion_info->scale + 0.1; + break; + } + if(!(s_current_transition.substate < 3) || s_current_transition.transistion_info->uid != 0x11){ + // @recomp Set the current transform ID to the transition model's. + cur_drawn_model_transform_id = TRANSITION_MODEL_TRANSFORM_ID_START; + + // recomp_printf(" C\n"); + + // @recomp Scale the transition. + set_additional_model_scale(transition_x_scale, 1.0f, 1.0f); + modelRender_draw(gdl, mptr, sp58, vp_rotation, scale * transition_scale, 0, s_current_transition.model_ptr); + + // @recomp Clear the current transform ID. + cur_drawn_model_transform_id = 0; + } + else{ + modelRender_reset(); + } + + } + else if(s_current_transition.state == TRANSITION_STATE_2_BLACK_IN){//L8030BAF4 + //fade in black (i.e. get less black) + gcbound_reset(); + gcbound_alpha((1.0f - percentage)*255.0f + 0.5); + gcbound_color(0,0,0); + gcbound_draw(gdl); + } + else if(s_current_transition.state == TRANSITION_STATE_3_BLACK_OUT){//L8030BB6C + //fade out black (i.e. get more black) + gcbound_reset(); + gcbound_alpha(percentage*255.0f + 0.5); + gcbound_color(0,0,0); + gcbound_draw(gdl); + } + else if(s_current_transition.state == TRANSITION_STATE_7_WHITE_IN){//L8030BBD8 + //fade in white (i.e. get less white) + percentage = (percentage <= 0.5)? 1.0 : 1.0 - (percentage-0.5)/0.5; + gcbound_reset(); + gcbound_alpha(percentage*255.0f + 0.5); + gcbound_color(0xff,0xff,0xff); + gcbound_draw(gdl); + } + else if(s_current_transition.state == TRANSITION_STATE_8_WHITE_OUT){//L8030BC8C + //fade out white (i.e. get more white) + gcbound_reset(); + gcbound_alpha(percentage*255.0f + 0.5); + gcbound_color(0xff,0xff,0xff); + gcbound_draw(gdl); + }//L8030BD00 + else{ + + } + if(s_current_transition.anctrl != NULL){ + gDPSetTextureFilter((*gdl)++, G_TF_BILERP); + } + viewport_restoreState(); + viewport_setRenderViewportAndPerspectiveMatrix(gdl, mptr); + +} \ No newline at end of file