Fix rendering issues with held objects (#1221)
Some checks failed
Build coop / build-linux (push) Has been cancelled
Build coop / build-steamos (push) Has been cancelled
Build coop / build-windows-opengl (push) Has been cancelled
Build coop / build-windows-directx (push) Has been cancelled
Build coop / build-macos-arm (push) Has been cancelled
Build coop / build-macos-intel (push) Has been cancelled

Add a way to save and load a gfx state in display lists.
Its primary purpose is to save the gfx state before rendering a held object, then restore it later.
It fixes the issue where Mario had opaque legs with the vanish and metal caps when holding an object.
This commit is contained in:
PeachyPeach 2026-05-05 23:10:59 +02:00 committed by GitHub
parent b37b2bf29f
commit 07c229afdc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 298 additions and 18 deletions

View file

@ -118,7 +118,8 @@ include_constants = {
"include/PR/gbi_extension.h": [
"^G_VTX_EXT$",
"^G_PPARTTOCOLOR$",
"^G_SETENVRGB$"
"^G_SETENVRGB$",
"^G_STATE_EXT$",
],
}

View file

@ -3031,6 +3031,9 @@ G_SETENVRGB = 0xd1
--- @type integer
G_PPARTTOCOLOR = 0xd3
--- @type integer
G_STATE_EXT = 0x10
BACKGROUND_OCEAN_SKY = 0 --- @type SkyBackgroundParams
BACKGROUND_FLAMING_SKY = 1 --- @type SkyBackgroundParams
BACKGROUND_UNDERWATER_CITY = 2 --- @type SkyBackgroundParams

View file

@ -401,7 +401,7 @@ s64 DynOS_Gfx_ParseGfxConstants(const String& _Arg, bool* found) {
gfx_constant(EMBLEM);
gfx_constant(METAL);
// Extended
// Extended geometry modes
gfx_constant(G_LIGHT_MAP_EXT);
gfx_constant(G_LIGHTING_ENGINE_EXT);
gfx_constant(G_PACKED_NORMALS_EXT);
@ -409,12 +409,30 @@ s64 DynOS_Gfx_ParseGfxConstants(const String& _Arg, bool* found) {
gfx_constant(G_FRESNEL_COLOR_EXT);
gfx_constant(G_FRESNEL_ALPHA_EXT);
// Player part to color
gfx_constant(G_COL_PRIM);
gfx_constant(G_COL_ENV);
gfx_constant(G_CP_LIGHT);
gfx_constant(G_CP_AMBIENT);
// Gfx state
gfx_constant(G_STATE_GEOMETRY_MODE);
gfx_constant(G_STATE_COMBINE_MODE);
gfx_constant(G_STATE_OTHER_MODE_L);
gfx_constant(G_STATE_OTHER_MODE_H);
gfx_constant(G_STATE_OTHER_MODE);
gfx_constant(G_STATE_ENV_COLOR);
gfx_constant(G_STATE_PRIM_COLOR);
gfx_constant(G_STATE_FOG_COLOR);
gfx_constant(G_STATE_FILL_COLOR);
gfx_constant(G_STATE_FRESNEL);
gfx_constant(G_STATE_TEXTURES);
gfx_constant(G_STATE_LIGHTS);
gfx_constant(G_STATE_VIEWPORT);
gfx_constant(G_STATE_SCISSOR);
gfx_constant(G_STATE_Z_BUFFER);
gfx_constant(G_STATE_COLOR_IMAGE);
// Common values
gfx_constant(CALC_DXT(4,G_IM_SIZ_4b_BYTES));
gfx_constant(CALC_DXT(8,G_IM_SIZ_4b_BYTES));

View file

@ -1316,6 +1316,7 @@
- G_VTX_EXT
- G_SETENVRGB
- G_PPARTTOCOLOR
- G_STATE_EXT
[:arrow_up_small:](#)

View file

@ -9,7 +9,7 @@
// Please update the following table when implementing a new command.
//
// RSP -> 09 0a 0b 0c 0d 0e 0f
// 10 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f
// 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f
// 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f
// 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f
// 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f
@ -222,3 +222,55 @@
#define gsSPFresnel(scale, offset) \
gsMoveWd(G_MW_FX, G_MWO_FRESNEL, \
(_SHIFTL((scale), 16, 16) | _SHIFTL((offset), 0, 16)))
/////////////////
// G_STATE_EXT //
/////////////////
#define G_STATE_EXT 0x10
#define G_STATE_LOAD 0
#define G_STATE_SAVE 1
#define G_STATE_GEOMETRY_MODE (1 << 0)
#define G_STATE_COMBINE_MODE (1 << 1)
#define G_STATE_OTHER_MODE_L (1 << 2)
#define G_STATE_OTHER_MODE_H (1 << 3)
#define G_STATE_OTHER_MODE (G_STATE_OTHER_MODE_L | G_STATE_OTHER_MODE_H)
#define G_STATE_ENV_COLOR (1 << 4)
#define G_STATE_PRIM_COLOR (1 << 5)
#define G_STATE_FOG_COLOR (1 << 6)
#define G_STATE_FILL_COLOR (1 << 7)
#define G_STATE_FRESNEL (1 << 8)
#define G_STATE_TEXTURES (1 << 9)
#define G_STATE_LIGHTS (1 << 10)
#define G_STATE_VIEWPORT (1 << 11)
#define G_STATE_SCISSOR (1 << 12)
#define G_STATE_Z_BUFFER (1 << 13)
#define G_STATE_COLOR_IMAGE (1 << 14)
#define gSPLoadState(pkt, state) \
{ \
Gfx *_g = (Gfx *)(pkt); \
_g->words.w0 = _SHIFTL(G_STATE_EXT,24,8)|_SHIFTL(G_STATE_LOAD,16,8); \
_g->words.w1 = (u32)(state); \
}
#define gsSPLoadState(state) \
{{ \
(_SHIFTL(G_STATE_EXT,24,8)|_SHIFTL(G_STATE_LOAD,16,8)), \
(u32)(state) \
}}
#define gSPSaveState(pkt, state) \
{ \
Gfx *_g = (Gfx *)(pkt); \
_g->words.w0 = _SHIFTL(G_STATE_EXT,24,8)|_SHIFTL(G_STATE_SAVE,16,8); \
_g->words.w1 = (u32)(state); \
}
#define gsSPSaveState(state) \
{{ \
(_SHIFTL(G_STATE_EXT,24,8)|_SHIFTL(G_STATE_SAVE,16,8)), \
(u32)(state) \
}}

View file

@ -61,6 +61,8 @@ define_gfx_symbol(gsSPFresnel, 2, false, GFX_PARAM_TYPE_INT, GFX_PARAM_TYPE_INT)
define_gfx_symbol(gsDPSetColorImage, 4, false, GFX_PARAM_TYPE_INT, GFX_PARAM_TYPE_INT, GFX_PARAM_TYPE_INT, GFX_PARAM_TYPE_INT);
define_gfx_symbol(gsSPNoOp, 0, false);
define_gfx_symbol(gsSPMatrix, 2, false, GFX_PARAM_TYPE_PTR, GFX_PARAM_TYPE_INT);
define_gfx_symbol(gsSPLoadState, 1, false, GFX_PARAM_TYPE_INT);
define_gfx_symbol(gsSPSaveState, 1, false, GFX_PARAM_TYPE_INT);
define_gfx_symbol_manual(gsSPTexture, 5, false, GFX_PARAM_TYPE_INT, GFX_PARAM_TYPE_INT, GFX_PARAM_TYPE_INT, GFX_PARAM_TYPE_INT, GFX_PARAM_TYPE_INT);
define_gfx_symbol_manual(gsSPSetGeometryMode, 1, false, GFX_PARAM_TYPE_INT);

View file

@ -90,6 +90,34 @@ static Gfx obj_sanitize_gfx[] = {
gsSPEndDisplayList(),
};
static Gfx obj_load_gfx_state[] = {
gsSPLoadState(G_STATE_GEOMETRY_MODE
| G_STATE_COMBINE_MODE
| G_STATE_OTHER_MODE
| G_STATE_ENV_COLOR
| G_STATE_PRIM_COLOR
| G_STATE_FOG_COLOR
| G_STATE_FILL_COLOR
| G_STATE_FRESNEL
| G_STATE_TEXTURES
| G_STATE_LIGHTS),
gsSPEndDisplayList(),
};
static Gfx obj_save_gfx_state[] = {
gsSPSaveState(G_STATE_GEOMETRY_MODE
| G_STATE_COMBINE_MODE
| G_STATE_OTHER_MODE
| G_STATE_ENV_COLOR
| G_STATE_PRIM_COLOR
| G_STATE_FOG_COLOR
| G_STATE_FILL_COLOR
| G_STATE_FRESNEL
| G_STATE_TEXTURES
| G_STATE_LIGHTS),
gsSPEndDisplayList(),
};
/**
* Animation nodes have state in global variables, so this struct captures
* the animation state so a 'context switch' can be made when rendering the
@ -552,6 +580,16 @@ static void geo_append_display_list(void *displayList, s16 layer) {
}
}
static void geo_append_display_list_to_all_layers(void *displayList) {
geo_append_display_list(displayList, LAYER_OPAQUE);
geo_append_display_list(displayList, LAYER_OPAQUE_DECAL);
geo_append_display_list(displayList, LAYER_OPAQUE_INTER);
geo_append_display_list(displayList, LAYER_ALPHA);
geo_append_display_list(displayList, LAYER_TRANSPARENT);
geo_append_display_list(displayList, LAYER_TRANSPARENT_DECAL);
geo_append_display_list(displayList, LAYER_TRANSPARENT_INTER);
}
/**
* Process the master list node.
*/
@ -1436,13 +1474,15 @@ static s32 obj_is_in_view(struct GraphNodeObject *node, Mat4 matrix) {
}
static void geo_sanitize_object_gfx(void) {
geo_append_display_list(obj_sanitize_gfx, LAYER_OPAQUE);
geo_append_display_list(obj_sanitize_gfx, LAYER_OPAQUE_DECAL);
geo_append_display_list(obj_sanitize_gfx, LAYER_OPAQUE_INTER);
geo_append_display_list(obj_sanitize_gfx, LAYER_ALPHA);
geo_append_display_list(obj_sanitize_gfx, LAYER_TRANSPARENT);
geo_append_display_list(obj_sanitize_gfx, LAYER_TRANSPARENT_DECAL);
geo_append_display_list(obj_sanitize_gfx, LAYER_TRANSPARENT_INTER);
geo_append_display_list_to_all_layers(obj_sanitize_gfx);
}
static void geo_load_object_gfx_state(void) {
geo_append_display_list_to_all_layers(obj_load_gfx_state);
}
static void geo_save_object_gfx_state(void) {
geo_append_display_list_to_all_layers(obj_save_gfx_state);
}
static struct MarioBodyState *get_mario_body_state_from_mario_object(struct Object *marioObj) {
@ -1729,6 +1769,10 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) {
dynos_gfx_swap_animations(node->objNode);
}
// The held object is going to change the gfx state before
// the holder finishes rendering, so let's save the state now
geo_save_object_gfx_state();
geo_sanitize_object_gfx();
// While rendering the held object's geo tree, ensure "current object" globals
// refer to the held object, otherwise Lua geo callbacks can accidentally
@ -1745,13 +1789,13 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) {
gCurrAnimAttribute = gGeoTempState.attribute;
gCurAnim = gGeoTempState.anim;
gPrevAnimFrame = gGeoTempState.prevFrame;
// Force-restore matrix stack index to avoid any imbalance caused by
// held object geo trees (including Lua geo callbacks).
gMatStackIndex = savedMatStackIndex;
// Reset any render-state changes performed by the held object's geo tree
// so the holder's remaining body parts render correctly.
geo_sanitize_object_gfx();
// Restore the previously saved state before continuing
geo_load_object_gfx_state();
}
if (node->fnNode.node.children != NULL) {

View file

@ -26,6 +26,7 @@
#define MAX_TILES 8
#define MAX_TEXTURES 2
#define MAX_CACHED_TEXTURES 4096 // for preloading purposes
#define MAX_GFX_STATES 8
#define HASH_SHIFT 0
#define HASHMAP_LEN (MAX_CACHED_TEXTURES * 2)

View file

@ -48,7 +48,7 @@ static struct ColorCombiner color_combiner_pool[CC_MAX_SHADERS] = { 0 };
static uint8_t color_combiner_pool_size = 0;
static uint8_t color_combiner_pool_index = 0;
static struct RSP {
struct RSP {
ALIGNED16 Mat4 MP_matrix;
ALIGNED16 Mat4 P_matrix;
ALIGNED16 Mat4 modelview_matrix_stack[MAX_MATRIX_STACK_SIZE];
@ -70,9 +70,10 @@ static struct RSP {
Light_t current_lights[MAX_LIGHTS + 1];
struct GfxVertex loaded_vertices[MAX_VERTICES + 4];
} rsp;
};
static struct RSP rsp;
static struct RDP {
struct RDP {
const uint8_t *palette[2];
struct UnloadedTex texture_to_load;
struct TextureTile texture_tile[MAX_TILES];
@ -87,7 +88,15 @@ static struct RDP {
bool viewport_or_scissor_changed;
void *z_buf_address;
void *color_image_address;
} rdp;
};
static struct RDP rdp;
struct GfxState {
struct RSP rsp;
struct RDP rdp;
};
static struct GfxState gfx_states[MAX_GFX_STATES];
static size_t num_gfx_states;
static struct RenderingState {
bool depth_test;
@ -1997,6 +2006,7 @@ static void gfx_sp_reset(void) {
rsp.modelview_matrix_stack_size = 1;
rsp.current_num_lights = 2;
rsp.lights_changed = true;
num_gfx_states = 0;
}
void gfx_get_dimensions(uint32_t *width, uint32_t *height) {
@ -2245,6 +2255,150 @@ static void OPTIMIZE_O3 djui_gfx_sp_simple_tri1(uint8_t vtx1_idx, uint8_t vtx2_i
return;
}
static void gfx_sp_load_or_save_state(uint8_t cmd, uint32_t state) {
// Load state
if (cmd == G_STATE_LOAD) {
if (num_gfx_states == 0) {
return;
}
num_gfx_states--;
struct GfxState *gfx_state = &gfx_states[num_gfx_states];
if (state & G_STATE_GEOMETRY_MODE) {
rsp.geometry_mode = gfx_state->rsp.geometry_mode;
}
if (state & G_STATE_COMBINE_MODE) {
gfx_dp_set_combine_mode(
gfx_state->rdp.combine_mode.rgb1,
gfx_state->rdp.combine_mode.alpha1,
gfx_state->rdp.combine_mode.rgb2,
gfx_state->rdp.combine_mode.alpha2
);
}
if (state & G_STATE_OTHER_MODE_L) {
rdp.other_mode_l = gfx_state->rdp.other_mode_l;
}
if (state & G_STATE_OTHER_MODE_H) {
rdp.other_mode_h = gfx_state->rdp.other_mode_h;
}
if (state & G_STATE_ENV_COLOR) {
rdp.env_color = gfx_state->rdp.env_color;
}
if (state & G_STATE_PRIM_COLOR) {
rdp.prim_color = gfx_state->rdp.prim_color;
}
if (state & G_STATE_FOG_COLOR) {
rdp.fog_color = gfx_state->rdp.fog_color;
rsp.fog_mul = gfx_state->rsp.fog_mul;
rsp.fog_offset = gfx_state->rsp.fog_offset;
}
if (state & G_STATE_FILL_COLOR) {
rdp.fill_color = gfx_state->rdp.fill_color;
}
if (state & G_STATE_FRESNEL) {
rsp.fresnel_scale = gfx_state->rsp.fresnel_scale;
rsp.fresnel_offset = gfx_state->rsp.fresnel_offset;
}
if (state & G_STATE_TEXTURES) {
rsp.texture_scaling_factor = gfx_state->rsp.texture_scaling_factor;
rdp.texture_to_load = gfx_state->rdp.texture_to_load;
memcpy(rdp.palette, gfx_state->rdp.palette, sizeof(rdp.palette));
memcpy(rdp.texture_tile, gfx_state->rdp.texture_tile, sizeof(rdp.texture_tile));
memcpy(rdp.loaded_texture, gfx_state->rdp.loaded_texture, sizeof(rdp.loaded_texture));
for (s32 i = 0; i != ARRAY_COUNT(rdp.textures_changed); ++i) {
rdp.textures_changed[i] = true;
}
}
if (state & G_STATE_LIGHTS) {
rsp.current_num_lights = gfx_state->rsp.current_num_lights;
memcpy(rsp.current_lights, gfx_state->rsp.current_lights, sizeof(rsp.current_lights));
rsp.lights_changed = true;
}
if (state & G_STATE_VIEWPORT) {
rdp.viewport = gfx_state->rdp.viewport;
rdp.viewport_or_scissor_changed = true;
}
if (state & G_STATE_SCISSOR) {
rdp.scissor = gfx_state->rdp.scissor;
rdp.viewport_or_scissor_changed = true;
}
if (state & G_STATE_Z_BUFFER) {
rdp.z_buf_address = gfx_state->rdp.z_buf_address;
}
if (state & G_STATE_COLOR_IMAGE) {
rdp.color_image_address = gfx_state->rdp.color_image_address;
}
return;
}
// Save state
if (cmd == G_STATE_SAVE) {
if (num_gfx_states == MAX_GFX_STATES) {
return;
}
struct GfxState *gfx_state = &gfx_states[num_gfx_states];
num_gfx_states++;
if (state & G_STATE_GEOMETRY_MODE) {
gfx_state->rsp.geometry_mode = rsp.geometry_mode;
}
if (state & G_STATE_COMBINE_MODE) {
gfx_state->rdp.combine_mode = rdp.combine_mode;
}
if (state & G_STATE_OTHER_MODE_L) {
gfx_state->rdp.other_mode_l = rdp.other_mode_l;
}
if (state & G_STATE_OTHER_MODE_H) {
gfx_state->rdp.other_mode_h = rdp.other_mode_h;
}
if (state & G_STATE_ENV_COLOR) {
gfx_state->rdp.env_color = rdp.env_color;
}
if (state & G_STATE_PRIM_COLOR) {
gfx_state->rdp.prim_color = rdp.prim_color;
}
if (state & G_STATE_FOG_COLOR) {
gfx_state->rdp.fog_color = rdp.fog_color;
gfx_state->rsp.fog_mul = rsp.fog_mul;
gfx_state->rsp.fog_offset = rsp.fog_offset;
}
if (state & G_STATE_FILL_COLOR) {
gfx_state->rdp.fill_color = rdp.fill_color;
}
if (state & G_STATE_FRESNEL) {
gfx_state->rsp.fresnel_scale = rsp.fresnel_scale;
gfx_state->rsp.fresnel_offset = rsp.fresnel_offset;
}
if (state & G_STATE_TEXTURES) {
gfx_state->rsp.texture_scaling_factor = rsp.texture_scaling_factor;
gfx_state->rdp.texture_to_load = rdp.texture_to_load;
memcpy(gfx_state->rdp.palette, rdp.palette, sizeof(rdp.palette));
memcpy(gfx_state->rdp.texture_tile, rdp.texture_tile, sizeof(rdp.texture_tile));
memcpy(gfx_state->rdp.loaded_texture, rdp.loaded_texture, sizeof(rdp.loaded_texture));
}
if (state & G_STATE_LIGHTS) {
gfx_state->rsp.current_num_lights = rsp.current_num_lights;
memcpy(gfx_state->rsp.current_lights, rsp.current_lights, sizeof(rsp.current_lights));
}
if (state & G_STATE_VIEWPORT) {
gfx_state->rdp.viewport = rdp.viewport;
}
if (state & G_STATE_SCISSOR) {
gfx_state->rdp.scissor = rdp.scissor;
}
if (state & G_STATE_Z_BUFFER) {
gfx_state->rdp.z_buf_address = rdp.z_buf_address;
}
if (state & G_STATE_COLOR_IMAGE) {
gfx_state->rdp.color_image_address = rdp.color_image_address;
}
return;
}
}
void gfx_pc_precomp_shader(uint32_t rgb1, uint32_t alpha1, uint32_t rgb2, uint32_t alpha2, uint32_t flags) {
gfx_dp_set_combine_mode(rgb1, alpha1, rgb2, alpha2);
@ -2285,5 +2439,8 @@ void OPTIMIZE_O3 ext_gfx_run_dl(Gfx* cmd) {
case G_PPARTTOCOLOR:
gfx_sp_copy_playerpart_to_color(C0(16, 8), cmd->words.w1);
break;
case G_STATE_EXT:
gfx_sp_load_or_save_state(C0(16, 8), cmd->words.w1);
break;
}
}

View file

@ -1584,6 +1584,7 @@ char gSmluaConstants[] = ""
"G_VTX_EXT=0x11\n"
"G_SETENVRGB=0xd1\n"
"G_PPARTTOCOLOR=0xd3\n"
"G_STATE_EXT=0x10\n"
"BACKGROUND_OCEAN_SKY=0\n"
"BACKGROUND_FLAMING_SKY=1\n"
"BACKGROUND_UNDERWATER_CITY=2\n"