From 60fba28e18273f62b540eaa6d20faec56870da46 Mon Sep 17 00:00:00 2001 From: andymcca Date: Sat, 24 Aug 2024 12:46:30 +0100 Subject: [PATCH 01/11] DRAFT - OAM Order Hijack Support (to fix #227 and similar) This is my attempt to support OAM Order Hijacking. The cause and effect of this is outlined in issue #227. The issue that I see for gpsp in addressing this issue is that we store the Layer Priority at the Object/BG levels only. In order to correctly deal with OAM Order Hijacking in the same way as the hardware, we need to be able to check priority at a pixel-level and act accordingly. My solution here is - a) to check for OAM Order Hijacking at a row-level when we are ordering our objects (in order_obj), and mark each applicable row in the same way we do for rows that contain transparent objects. b) when we come to render a scanline marked as hijacked, generate an OBJ-only scanline buffer first by rendering all available OBJ layers to this buffer in u16/INDXCOLOR mode, which gives us the most flexibility later on c) when rendering 8-bit per pixel OBJ tiles, we check 4 pixels at a time for transparency (tilepix). In the current code, if all 4 pixels are transparent (i.e. 0), we move on to the next 4 pixels to complete the tile. But in my PR, we instead check each pixel to see if we have a non-zero value in the OBJ scanline buffer we created. If we do, we render the value to dest_ptr using the selected render mode. I have tried to build in some optimisations here such as only creating the OBJ scanline buffer on scanlines affected by a hijack and only comparing to the scanline buffer when we have 4 consecutive transparent pixels as described above. The latter is sufficient to address the issues identified in Golden Sun as far as I can see, but complete/correct emulation would be to compare against EVERY transparent pixel. Further ToDos: - 4bpp support - Affine/Mosaic Objects support So this PR is still in draft at the moment for more discussion/testing before committing. --- video.cc | 135 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 121 insertions(+), 14 deletions(-) diff --git a/video.cc b/video.cc index a233fcb0..b66e0f4c 100644 --- a/video.cc +++ b/video.cc @@ -960,6 +960,9 @@ static const u8 obj_dim_table[3][4][2] = { static u8 obj_priority_list[5][160][128]; static u8 obj_priority_count[5][160]; static u8 obj_alpha_count[160]; +static u8 oam_hijack; +static u8 oam_line_hijack[160]; +static u16 obj_buf[240]; typedef struct { s32 obj_x, obj_y; @@ -1031,16 +1034,17 @@ static inline void render_obj_part_tile_Nbpp( // Same as above but optimized for full tiles template static inline void render_obj_tile_Nbpp(u32 px_comb, - dsttype *dest_ptr, u32 tile_offset, u16 palette, const u16 *pal + dsttype *dest_ptr, u32 tile_offset, u16 palette, const u16 *pal, u8 sl_start = 0 ) { const u8* tile_ptr = &vram[0x10000 + (tile_offset & 0x7FFF)]; u32 px_attr = px_comb | palette | 0x100; // Combine flags + high palette bit - + if (is8bpp) { for (u32 j = 0; j < 2; j++) { u32 tilepix = eswap32(((u32*)tile_ptr)[hflip ? 1-j : j]); if (tilepix) { - for (u32 i = 0; i < 4; i++, dest_ptr++) { + // Increment sl_start here too in case first tilepix isn't blank + for (u32 i = 0; i < 4; i++, dest_ptr++, sl_start++) { u8 pval = hflip ? (tilepix >> (24 - i*8)) : (tilepix >> (i*8)); if (pval) { if (rdtype == FULLCOLOR) @@ -1057,10 +1061,29 @@ static inline void render_obj_tile_Nbpp(u32 px_comb, *dest_ptr = dest_ptr[240]; } } - } else - dest_ptr += 4; + } else + { + // As this half-row is blank, check the Object Buffer for pixels + for (u32 i = 0; i < 4; i++, dest_ptr++, sl_start++) { + //u8 curr_pos = (u16*)dest_ptr - get_screen_pixels(); + if(obj_buf[sl_start]) { + if (rdtype == FULLCOLOR) + *dest_ptr = pal[(u8) obj_buf[sl_start]]; + else if (rdtype == INDXCOLOR) + *dest_ptr = obj_buf[sl_start]; + else if (rdtype == STCKCOLOR) { + if (*dest_ptr & 0x100) + *dest_ptr = obj_buf[sl_start] | ((*dest_ptr) & 0xFFFF0000); + else + *dest_ptr = obj_buf[sl_start] | ((*dest_ptr) << 16); + } + //*dest_ptr = obj_buf[sl_start]; + } + } + } } - } else { + + }else { u32 tilepix = eswap32(*(u32*)tile_ptr); if (tilepix) { // Can skip all pixels if the row is just transparent for (u32 i = 0; i < 8; i++, dest_ptr++) { @@ -1097,11 +1120,12 @@ static void render_object( const u32 tile_bsize = is8bpp ? tile_size_8bpp : tile_size_4bpp; // Number of bytes to advance (or rewind) on the tile map const s32 tile_size_off = hflip ? -tile_bsize : tile_bsize; + u8 tile_off = 0; if (delta_x < 0) { // Left part is outside of the screen/window. u32 offx = -delta_x; // How many pixels did we skip from the object? s32 block_off = offx / 8; - u32 tile_off = offx % 8; + tile_off = offx % 8; // Skip the first object tiles (skips in the flip direction) tile_offset += block_off * tile_size_off; @@ -1126,12 +1150,15 @@ static void render_object( // Render full tiles to the scan line. s32 num_tiles = cnt / 8; + // Work out where we need to start on the scanline + u8 scanline_start = (delta_x < 0 ? 8 - tile_off : delta_x); while (num_tiles--) { // Render full tiles render_obj_tile_Nbpp( - px_comb, dst_ptr, tile_offset, palette, palptr); + px_comb, dst_ptr, tile_offset, palette, palptr, scanline_start); tile_offset += tile_size_off; dst_ptr += 8; + scanline_start +=8; } // Render any partial tile on the end @@ -1515,6 +1542,11 @@ static void order_obj(u32 video_mode) u32 row; t_oam *oam_base = (t_oam*)oam_ram; u16 rend_cycles[160]; + u8 obj_priority = 0; + + // When we call order_obj, it means OAM has changed + // Reset the hijack counter and mark the obj_buffer as not yet filled + oam_hijack = 0; bool hblank_free = read_ioreg(REG_DISPCNT) & 0x20; u16 max_rend_cycles = !sprite_limit ? REND_CYC_MAX : @@ -1524,6 +1556,9 @@ static void order_obj(u32 video_mode) memset(obj_priority_count, 0, sizeof(obj_priority_count)); memset(obj_alpha_count, 0, sizeof(obj_alpha_count)); memset(rend_cycles, 0, sizeof(rend_cycles)); + // Let's also clear the hijack row flags + memset(oam_line_hijack, 0, sizeof(oam_line_hijack)); + for(obj_num = 0; obj_num < 128; obj_num++) { @@ -1560,6 +1595,7 @@ static void order_obj(u32 video_mode) // Double size for affine sprites with double bit set if(obj_attr0 & 0x200) { + //printf("OBJ %d is affine", obj_num); obj_height *= 2; obj_width *= 2; } @@ -1570,7 +1606,17 @@ static void order_obj(u32 video_mode) if(((obj_x + obj_width) > 0) && (obj_x < 240)) { - u32 obj_priority = (obj_attr2 >> 10) & 0x03; + // Let's check for OAM hijack here. If detected for this object we'll turn on hijacking for all + // active objects from now on. + // This code needs further optimisation because ideally we only want to mark rows as hijacked + // for specific objects, but as it stands it will mark everything as hijacked from the first + // encounter onwards. + if(((obj_attr2 >> 10) & 0x03) < obj_priority) + { + oam_hijack = 1; + //printf("OBJ %d is hijacking, X %d, Y %d \n", obj_num, obj_x, obj_y); + } + obj_priority = (obj_attr2 >> 10) & 0x03; bool is_affine = obj_attr0 & 0x100; // Clip Y coord and height to the 0..159 interval u32 starty = MAX(obj_y, 0); @@ -1588,6 +1634,10 @@ static void order_obj(u32 video_mode) obj_priority_list[obj_priority][row][cur_cnt] = obj_num; obj_priority_count[obj_priority][row] = cur_cnt + 1; rend_cycles[row] += cyccnt; + // Assignment probably quicker than comparison, so let's just set the line hijack every time + oam_line_hijack[row] = oam_hijack; + //if(oam_hijack) + // printf("OAM Hijack Row: %d, Object: %d \n", row, obj_num); // Mark the row as having semi-transparent objects obj_alpha_count[row] = 1; } @@ -1605,6 +1655,10 @@ static void order_obj(u32 video_mode) obj_priority_list[obj_priority][row][cur_cnt] = obj_num; obj_priority_count[obj_priority][row] = cur_cnt + 1; rend_cycles[row] += cyccnt; + // Assignment probably quicker than comparison->assignment, so let's just set the line hijack every time + oam_line_hijack[row] = oam_hijack; + //if(oam_hijack) + // printf("OAM Hijack Row: %d, Object: %d \n", row, obj_num); } } break; @@ -1847,6 +1901,35 @@ void tile_render_layers(u32 start, u32 end, dsttype *dst_ptr, u32 enabled_layers bool objlayer_is_1st_tgt = ((read_ioreg(REG_BLDCNT) >> 4) & 1) != 0; bool has_trans_obj = obj_alpha_count[read_ioreg(REG_VCOUNT)]; + bool row_obj_hijack = oam_line_hijack[read_ioreg(REG_VCOUNT)]; + u16 *obj_buf_ptr; + obj_buf_ptr = obj_buf; + + // Clear the object buffer + memset(obj_buf, 0, sizeof(obj_buf)); + + // If this line has OAM hijack we need to clear the object buffer and fill it + // first so that the buffer is available to overlay pixels onto transparent OBJ areas + if (row_obj_hijack) { + // Fill it with Object Layers only, back to front + for (lnum = 0; lnum < layer_count; lnum++) { + u32 layer = layer_order[lnum]; + bool is_obj = layer & 0x4; + if (is_obj && obj_enabled) { + //printf("Start: %d, End: %d, Row: %d \n", start, end, read_ioreg(REG_VCOUNT)); + // TODO: Currently hard coded to full color only so no blending etc + // Need to give this a bit of thought - I think OBJs don't blend with each other so this is correct, + // But technically I think correct behaviour would be that the buffer pixels should blend with the background layer + // below if it's enabled + render_scanline_objs(layer & 0x3, start, end, obj_buf_ptr, &palette_ram_converted[0x100]); + //for(u8 c = 0; c < 240; c++) { + // if(obj_buf[c]) + // printf("OBJ Mode: %d, OBJ Buf Pos: %d, OBJ Buf: %d, Start: %d, End: %d, Row: %d \n", objmode, c, obj_buf[c], start, end, read_ioreg(REG_VCOUNT)); + //} + } + } + } + for (lnum = 0; lnum < layer_count; lnum++) { u32 layer = layer_order[lnum]; bool is_obj = layer & 0x4; @@ -2033,6 +2116,35 @@ static void bitmap_render_layers( bool has_trans_obj = obj_alpha_count[read_ioreg(REG_VCOUNT)]; bool objlayer_is_1st_tgt = (read_ioreg(REG_BLDCNT) & 0x10) != 0; bool bg2_is_1st_tgt = (read_ioreg(REG_BLDCNT) & 0x4) != 0; + u32 current_layer; + u32 layer_order_pos; + + bool row_obj_hijack = oam_line_hijack[read_ioreg(REG_VCOUNT)]; + u16 *obj_buf_ptr; + obj_buf_ptr = obj_buf; + + // Clear the object buffer + memset(obj_buf, 0, sizeof(obj_buf)); + + // If this line has OAM hijack we need to clear the object buffer and fill it + // first so that the buffer is available to overlay pixels onto transparent OBJ areas + if (row_obj_hijack) { + // Fill it with Object Layers only, back to front + for (layer_order_pos = 0; layer_order_pos < layer_count; layer_order_pos++) { + current_layer = layer_order[layer_order_pos]; + bool is_obj = current_layer & 0x4; + bool obj_enabled = enable_flags & 0x10; + if (is_obj && obj_enabled) { + //printf("Start: %d, End: %d, Row: %d \n", start, end, REG_VCOUNT); + // TODO: Currently hard coded to full color only so no blending etc + // Need to give this a bit of thought - I think OBJs don't blend with each other so this is correct, + // But technically I think correct behaviour would be that the buffer pixels should blend with the background layer + // below if it's enabled + render_scanline_objs(current_layer & 0x3, start, end, obj_buf_ptr, &palette_ram_converted[0x100]); + } + } + } + // Fill in the renderers for a layer based on the mode type, static const bitmap_layer_render_struct renderers[3][2] = @@ -2050,9 +2162,6 @@ static void bitmap_render_layers( const bitmap_layer_render_struct *mode_rend = &renderers[modeidx][mmode]; const bitmap_layer_render_struct *idxm_rend = &idx32_bmrend[modeidx][mmode]; - u32 current_layer; - u32 layer_order_pos; - fill_line_background(start, end, scanline); for(layer_order_pos = 0; layer_order_pos < layer_count; layer_order_pos++) @@ -2335,5 +2444,3 @@ void update_scanline(void) } } } - - From c74073a47e4e7c0c2c4a474bb889732cc827c402 Mon Sep 17 00:00:00 2001 From: andymcca Date: Sat, 24 Aug 2024 12:58:47 +0100 Subject: [PATCH 02/11] Update video.cc --- video.cc | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/video.cc b/video.cc index b66e0f4c..3f629bba 100644 --- a/video.cc +++ b/video.cc @@ -1917,10 +1917,7 @@ void tile_render_layers(u32 start, u32 end, dsttype *dst_ptr, u32 enabled_layers bool is_obj = layer & 0x4; if (is_obj && obj_enabled) { //printf("Start: %d, End: %d, Row: %d \n", start, end, read_ioreg(REG_VCOUNT)); - // TODO: Currently hard coded to full color only so no blending etc - // Need to give this a bit of thought - I think OBJs don't blend with each other so this is correct, - // But technically I think correct behaviour would be that the buffer pixels should blend with the background layer - // below if it's enabled + // Use u16/INDXCOLOR mode to give us best flexibility when replacing pixels across all modes render_scanline_objs(layer & 0x3, start, end, obj_buf_ptr, &palette_ram_converted[0x100]); //for(u8 c = 0; c < 240; c++) { // if(obj_buf[c]) From dee74e465a4bab17ecadb1b6f84ba6eecc225f44 Mon Sep 17 00:00:00 2001 From: andymcca Date: Sat, 24 Aug 2024 13:00:46 +0100 Subject: [PATCH 03/11] Update video.cc --- video.cc | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/video.cc b/video.cc index 3f629bba..74f5fbdc 100644 --- a/video.cc +++ b/video.cc @@ -2133,10 +2133,7 @@ static void bitmap_render_layers( bool obj_enabled = enable_flags & 0x10; if (is_obj && obj_enabled) { //printf("Start: %d, End: %d, Row: %d \n", start, end, REG_VCOUNT); - // TODO: Currently hard coded to full color only so no blending etc - // Need to give this a bit of thought - I think OBJs don't blend with each other so this is correct, - // But technically I think correct behaviour would be that the buffer pixels should blend with the background layer - // below if it's enabled + // Use u16/INDXCOLOR mode to give us best flexibility when replacing pixels across all modes render_scanline_objs(current_layer & 0x3, start, end, obj_buf_ptr, &palette_ram_converted[0x100]); } } From bc75005191bcdc0320fe57b08554e67922caf06a Mon Sep 17 00:00:00 2001 From: andymcca Date: Sat, 24 Aug 2024 17:33:08 +0100 Subject: [PATCH 04/11] Add 4bpp tile support Added code to handle 4bpp OBJs - this fixes the similar issue in Golden Sun 2 with the ice shards in the Mars Lighthouse / other areas. --- video.cc | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/video.cc b/video.cc index 74f5fbdc..f77288b1 100644 --- a/video.cc +++ b/video.cc @@ -1085,10 +1085,10 @@ static inline void render_obj_tile_Nbpp(u32 px_comb, }else { u32 tilepix = eswap32(*(u32*)tile_ptr); + const u16 *subpal = &pal[palette]; if (tilepix) { // Can skip all pixels if the row is just transparent for (u32 i = 0; i < 8; i++, dest_ptr++) { u8 pval = (hflip ? (tilepix >> ((7-i)*4)) : (tilepix >> (i*4))) & 0xF; - const u16 *subpal = &pal[palette]; if (pval) { if (rdtype == FULLCOLOR) *dest_ptr = subpal[pval]; @@ -1104,7 +1104,26 @@ static inline void render_obj_tile_Nbpp(u32 px_comb, *dest_ptr = dest_ptr[240]; } } - } + } else + { + // As this half-row is blank, check the Object Buffer for pixels + for (u32 i = 0; i < 8; i++, dest_ptr++, sl_start++) { + //u8 curr_pos = (u16*)dest_ptr - get_screen_pixels(); + if(obj_buf[sl_start]) { + if (rdtype == FULLCOLOR) + *dest_ptr = subpal[(u8) obj_buf[sl_start]]; + else if (rdtype == INDXCOLOR) + *dest_ptr = obj_buf[sl_start]; + else if (rdtype == STCKCOLOR) { + if (*dest_ptr & 0x100) + *dest_ptr = obj_buf[sl_start] | ((*dest_ptr) & 0xFFFF0000); + else + *dest_ptr = obj_buf[sl_start] | ((*dest_ptr) << 16); + } + //*dest_ptr = obj_buf[sl_start]; + } + } + } } } @@ -1917,7 +1936,10 @@ void tile_render_layers(u32 start, u32 end, dsttype *dst_ptr, u32 enabled_layers bool is_obj = layer & 0x4; if (is_obj && obj_enabled) { //printf("Start: %d, End: %d, Row: %d \n", start, end, read_ioreg(REG_VCOUNT)); - // Use u16/INDXCOLOR mode to give us best flexibility when replacing pixels across all modes + // TODO: Currently hard coded to full color only so no blending etc + // Need to give this a bit of thought - I think OBJs don't blend with each other so this is correct, + // But technically I think correct behaviour would be that the buffer pixels should blend with the background layer + // below if it's enabled render_scanline_objs(layer & 0x3, start, end, obj_buf_ptr, &palette_ram_converted[0x100]); //for(u8 c = 0; c < 240; c++) { // if(obj_buf[c]) @@ -2133,7 +2155,10 @@ static void bitmap_render_layers( bool obj_enabled = enable_flags & 0x10; if (is_obj && obj_enabled) { //printf("Start: %d, End: %d, Row: %d \n", start, end, REG_VCOUNT); - // Use u16/INDXCOLOR mode to give us best flexibility when replacing pixels across all modes + // TODO: Currently hard coded to full color only so no blending etc + // Need to give this a bit of thought - I think OBJs don't blend with each other so this is correct, + // But technically I think correct behaviour would be that the buffer pixels should blend with the background layer + // below if it's enabled render_scanline_objs(current_layer & 0x3, start, end, obj_buf_ptr, &palette_ram_converted[0x100]); } } From 0000e593fe2da9b1e54b8c0c40f8399ab2df41da Mon Sep 17 00:00:00 2001 From: andymcca Date: Sat, 24 Aug 2024 17:39:15 +0100 Subject: [PATCH 05/11] Correct comments Correct a few comments in the code --- video.cc | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/video.cc b/video.cc index f77288b1..dd593557 100644 --- a/video.cc +++ b/video.cc @@ -1106,7 +1106,7 @@ static inline void render_obj_tile_Nbpp(u32 px_comb, } } else { - // As this half-row is blank, check the Object Buffer for pixels + // As this full-row is blank, check the Object Buffer for pixels for (u32 i = 0; i < 8; i++, dest_ptr++, sl_start++) { //u8 curr_pos = (u16*)dest_ptr - get_screen_pixels(); if(obj_buf[sl_start]) { @@ -1927,7 +1927,7 @@ void tile_render_layers(u32 start, u32 end, dsttype *dst_ptr, u32 enabled_layers // Clear the object buffer memset(obj_buf, 0, sizeof(obj_buf)); - // If this line has OAM hijack we need to clear the object buffer and fill it + // If this line has OAM hijack we need to fill the buffer with OBJ layers // first so that the buffer is available to overlay pixels onto transparent OBJ areas if (row_obj_hijack) { // Fill it with Object Layers only, back to front @@ -1936,10 +1936,7 @@ void tile_render_layers(u32 start, u32 end, dsttype *dst_ptr, u32 enabled_layers bool is_obj = layer & 0x4; if (is_obj && obj_enabled) { //printf("Start: %d, End: %d, Row: %d \n", start, end, read_ioreg(REG_VCOUNT)); - // TODO: Currently hard coded to full color only so no blending etc - // Need to give this a bit of thought - I think OBJs don't blend with each other so this is correct, - // But technically I think correct behaviour would be that the buffer pixels should blend with the background layer - // below if it's enabled + // Render to u16/INDXCOLOR to allow us to cover all render types when substituting pixels render_scanline_objs(layer & 0x3, start, end, obj_buf_ptr, &palette_ram_converted[0x100]); //for(u8 c = 0; c < 240; c++) { // if(obj_buf[c]) @@ -2145,7 +2142,7 @@ static void bitmap_render_layers( // Clear the object buffer memset(obj_buf, 0, sizeof(obj_buf)); - // If this line has OAM hijack we need to clear the object buffer and fill it + // If this line has OAM hijack we need to fill the buffer with OBJ layers // first so that the buffer is available to overlay pixels onto transparent OBJ areas if (row_obj_hijack) { // Fill it with Object Layers only, back to front @@ -2155,10 +2152,7 @@ static void bitmap_render_layers( bool obj_enabled = enable_flags & 0x10; if (is_obj && obj_enabled) { //printf("Start: %d, End: %d, Row: %d \n", start, end, REG_VCOUNT); - // TODO: Currently hard coded to full color only so no blending etc - // Need to give this a bit of thought - I think OBJs don't blend with each other so this is correct, - // But technically I think correct behaviour would be that the buffer pixels should blend with the background layer - // below if it's enabled + // Render to u16/INDXCOLOR to allow us to cover all render types when substituting pixels render_scanline_objs(current_layer & 0x3, start, end, obj_buf_ptr, &palette_ram_converted[0x100]); } } From 36969582aa18c0bd0f45eee100308238414f4b0f Mon Sep 17 00:00:00 2001 From: andymcca Date: Sat, 14 Dec 2024 09:59:31 +0000 Subject: [PATCH 06/11] Clear OBJ buffer when rendering Priority 0 This fixes parts of sprites 'peeking through' on the Golden Sun pause menu. --- video.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/video.cc b/video.cc index dd593557..3b4bf6bf 100644 --- a/video.cc +++ b/video.cc @@ -1472,6 +1472,10 @@ void render_scanline_objs( u32 objcnt = obj_priority_count[priority][vcount]; u8 *objlist = obj_priority_list[priority][vcount]; + // Clear the object buffer for Priority 0 + if(priority == 0) + memset(obj_buf, 0, sizeof(obj_buf)); + // Render all the visible objects for this priority (back to front) for (objn = objcnt-1; objn >= 0; objn--) { // Objects in the list are pre-filtered and sorted in the appropriate order From ea8e4a9093f34dcacc937934402a0802ca18b79b Mon Sep 17 00:00:00 2001 From: andymcca Date: Sun, 12 Oct 2025 07:10:11 +0100 Subject: [PATCH 07/11] Fix Implementation - track highest priority and only process hijack objects Several changes made to fix this so that it now works correctly - - Track the highest OBJ priority per scanline to determine hijacking OBJs - Use transparency buffer for these OBJs *only* - Transparency buffer is now rendered using the last hijacking OBJ in the OAM order for the scanline -> 0 - Make this a core option (i.e. you can turn hijack support on/off) These changes make this more efficient and fix the Golden Sun OBJs 'peeping' through the in-game menu, and also the Mario Golf shot indicator issues that I identified previously. --- video.cc | 292 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 236 insertions(+), 56 deletions(-) diff --git a/video.cc b/video.cc index 3b4bf6bf..0caf511d 100644 --- a/video.cc +++ b/video.cc @@ -22,6 +22,8 @@ extern "C" { #include "common.h" } +extern int oam_hijacking_enabled; // Core option for OAM hijacking + u16* gba_screen_pixels = NULL; #define get_screen_pixels() gba_screen_pixels @@ -90,6 +92,11 @@ typedef struct #define RENDER_COL32 2 #define RENDER_ALPHA 3 +// GRNSWP colors +#define M_COLOR_RED 0xF800 +#define M_COLOR_GREEN 0x07E0 +#define M_COLOR_BLUE 0x001F +#define M_COLOR_ALPHA 0x0000 // Byte lengths of complete tiles and tile rows in 4bpp and 8bpp. @@ -962,13 +969,16 @@ static u8 obj_priority_count[5][160]; static u8 obj_alpha_count[160]; static u8 oam_hijack; static u8 oam_line_hijack[160]; +static u8 obj_hijack_flags[160][128]; // Track which objects are hijacking on each scanline static u16 obj_buf[240]; +static bool rendering_obj_buffer = false; // Flag to disable hijacking when creating obj buffer typedef struct { s32 obj_x, obj_y; s32 obj_w, obj_h; u32 attr1, attr2; bool is_double; + u8 objnum; } t_sprite; // Renders a tile row (8 pixels) for a regular (non-affine) object/sprite. @@ -1034,10 +1044,11 @@ static inline void render_obj_part_tile_Nbpp( // Same as above but optimized for full tiles template static inline void render_obj_tile_Nbpp(u32 px_comb, - dsttype *dest_ptr, u32 tile_offset, u16 palette, const u16 *pal, u8 sl_start = 0 + dsttype *dest_ptr, u32 tile_offset, u16 palette, const u16 *pal, u8 sl_start = 0, u8 objnum = 0 ) { const u8* tile_ptr = &vram[0x10000 + (tile_offset & 0x7FFF)]; u32 px_attr = px_comb | palette | 0x100; // Combine flags + high palette bit + //u32 vcount = read_ioreg(REG_VCOUNT); if (is8bpp) { for (u32 j = 0; j < 2; j++) { @@ -1061,14 +1072,18 @@ static inline void render_obj_tile_Nbpp(u32 px_comb, *dest_ptr = dest_ptr[240]; } } - } else + } else { // As this half-row is blank, check the Object Buffer for pixels + // Only check obj_buf if hijacking is enabled and this object is marked as hijacking + u32 vcount = read_ioreg(REG_VCOUNT); + bool is_hijacking = oam_hijacking_enabled && !rendering_obj_buffer && obj_hijack_flags[vcount][objnum]; + for (u32 i = 0; i < 4; i++, dest_ptr++, sl_start++) { //u8 curr_pos = (u16*)dest_ptr - get_screen_pixels(); - if(obj_buf[sl_start]) { + if(is_hijacking && sl_start < 240 && obj_buf[sl_start]) { if (rdtype == FULLCOLOR) - *dest_ptr = pal[(u8) obj_buf[sl_start]]; + *dest_ptr = palette_ram_converted[obj_buf[sl_start] & 0x1FF]; else if (rdtype == INDXCOLOR) *dest_ptr = obj_buf[sl_start]; else if (rdtype == STCKCOLOR) { @@ -1104,14 +1119,18 @@ static inline void render_obj_tile_Nbpp(u32 px_comb, *dest_ptr = dest_ptr[240]; } } - } else + } else { // As this full-row is blank, check the Object Buffer for pixels + // Only check obj_buf if hijacking is enabled and this object is marked as hijacking + u32 vcount = read_ioreg(REG_VCOUNT); + bool is_hijacking = oam_hijacking_enabled && !rendering_obj_buffer && obj_hijack_flags[vcount][objnum]; + for (u32 i = 0; i < 8; i++, dest_ptr++, sl_start++) { //u8 curr_pos = (u16*)dest_ptr - get_screen_pixels(); - if(obj_buf[sl_start]) { + if(is_hijacking && sl_start < 240 && obj_buf[sl_start]) { if (rdtype == FULLCOLOR) - *dest_ptr = subpal[(u8) obj_buf[sl_start]]; + *dest_ptr = palette_ram_converted[obj_buf[sl_start] & 0x1FF]; else if (rdtype == INDXCOLOR) *dest_ptr = obj_buf[sl_start]; else if (rdtype == STCKCOLOR) { @@ -1133,7 +1152,7 @@ static inline void render_obj_tile_Nbpp(u32 px_comb, template static void render_object( s32 delta_x, u32 cnt, stype *dst_ptr, u32 tile_offset, u32 px_comb, - u16 palette, const u16* palptr + u16 palette, const u16* palptr, u8 objnum ) { // Tile size in bytes for each mode const u32 tile_bsize = is8bpp ? tile_size_8bpp : tile_size_4bpp; @@ -1174,7 +1193,7 @@ static void render_object( while (num_tiles--) { // Render full tiles render_obj_tile_Nbpp( - px_comb, dst_ptr, tile_offset, palette, palptr, scanline_start); + px_comb, dst_ptr, tile_offset, palette, palptr, scanline_start, objnum); tile_offset += tile_size_off; dst_ptr += 8; scanline_start +=8; @@ -1452,10 +1471,120 @@ inline static void render_sprite( } else { if (hflip) render_object( - obj_x_offset, max_draw, &scanline[start], tile_offset, pxcomb, pal, palptr); + obj_x_offset, max_draw, &scanline[start], tile_offset, pxcomb, pal, palptr, obji->objnum); else render_object( - obj_x_offset, max_draw, &scanline[start], tile_offset, pxcomb, pal, palptr); + obj_x_offset, max_draw, &scanline[start], tile_offset, pxcomb, pal, palptr, obji->objnum); + } + } +} + +// Renders objects in OAM priority order (start_obj->0) for hijack scenarios. +// This fills the obj_buf with all relevant OBJs for the scanline in OAM order only. +template +void render_scanline_objs_oam_order( + u32 start, u32 end, void *raw_ptr, const u16* palptr, s32 start_obj +) { + stype *scanline = (stype*)raw_ptr; + s32 vcount = read_ioreg(REG_VCOUNT); + t_oam *oam_base = (t_oam*)oam_ram; + + // Iterate through OBJs in OAM order (start_obj -> 0) + for (s32 obj_num = start_obj; obj_num >= 0; obj_num--) { + const t_oam *oamentry = &oam_base[obj_num]; + + u16 obj_attr0 = eswap16(oamentry->attr0); + u16 obj_attr1 = eswap16(oamentry->attr1); + u16 obj_attr2 = eswap16(oamentry->attr2); + + // Skip disabled objects (bit 9 disables regular sprites) + if ((obj_attr0 & 0x0300) == 0x0200) + continue; + + u16 obj_shape = obj_attr0 >> 14; + u32 obj_mode = (obj_attr0 >> 10) & 0x03; + + // Skip prohibited shape and mode + if ((obj_shape == 0x3) || (obj_mode == OBJ_MOD_INVALID)) + continue; + + // Skip bitmap mode restrictions (modes 3-5) + u16 dispcnt = read_ioreg(REG_DISPCNT); + u32 video_mode = dispcnt & 0x07; + if ((video_mode >= 3) && (!(obj_attr2 & 0x200))) + continue; + + // Calculate object dimensions + u16 obj_size = (obj_attr1 >> 14); + s32 obj_height = obj_dim_table[obj_shape][obj_size][1]; + s32 obj_width = obj_dim_table[obj_shape][obj_size][0]; + s32 obj_y = obj_attr0 & 0xFF; + s32 obj_x = (s32)(obj_attr1 << 23) >> 23; + + if (obj_y > 160) + obj_y -= 256; + + // Double size for affine sprites with double bit set + if (obj_attr0 & 0x200) { + obj_height *= 2; + obj_width *= 2; + } + + // Check if object is visible on current scanline + if (!((obj_y + obj_height) > 0 && obj_y < 160)) + continue; + if (vcount < obj_y || vcount >= obj_y + obj_height) + continue; + + // Check if object is visible horizontally (match order_obj logic) + if (!((obj_x + obj_width) > 0 && obj_x < 240)) + continue; + + bool is_affine = obj_attr0 & 0x100; + bool is_trans = ((obj_attr0 >> 10) & 0x3) == OBJ_MOD_SEMITRAN; + + t_sprite obji = { + .obj_x = obj_x, + .obj_y = obj_y, + .obj_w = obj_dim_table[obj_shape][obj_size][0], + .obj_h = obj_dim_table[obj_shape][obj_size][1], + .attr1 = obj_attr1, + .attr2 = obj_attr2, + .is_double = (obj_attr0 & 0x200) != 0, + .objnum = (u8)obj_num + }; + + s32 obj_maxw = (is_affine && obji.is_double) ? obji.obj_w * 2 : obji.obj_w; + + // Skip if object is outside the render window + if (obji.obj_x >= (signed)end || obji.obj_x + obj_maxw <= (signed)start) + continue; + + // Calculate combine masks for blending + u32 pxcomb = color_flags(4); + if (is_trans && rdtype != FULLCOLOR) + pxcomb |= 0x800; // Force blending for ST-objects + + bool emosaic = (obj_attr0 & 0x1000) != 0; + bool is_8bpp = (obj_attr0 & 0x2000) != 0; + + // Some games enable mosaic but set it to size 0 (1), so ignore. + const u32 mosreg = read_ioreg(REG_MOSAIC) & 0xFF00; + + if (emosaic && mosreg) { + if (is_8bpp) + render_sprite( + &obji, is_affine, start, end, scanline, pxcomb, palptr); + else + render_sprite( + &obji, is_affine, start, end, scanline, pxcomb, palptr); + } else { + if (is_8bpp) + render_sprite( + &obji, is_affine, start, end, scanline, pxcomb, palptr); + else + render_sprite( + &obji, is_affine, start, end, scanline, pxcomb, palptr); } } } @@ -1473,13 +1602,13 @@ void render_scanline_objs( u8 *objlist = obj_priority_list[priority][vcount]; // Clear the object buffer for Priority 0 - if(priority == 0) - memset(obj_buf, 0, sizeof(obj_buf)); + //if(priority == 0) + // memset(obj_buf, 0, sizeof(obj_buf)); // Render all the visible objects for this priority (back to front) for (objn = objcnt-1; objn >= 0; objn--) { // Objects in the list are pre-filtered and sorted in the appropriate order - u32 objoff = objlist[objn]; + u8 objoff = objlist[objn]; const t_oam *oamentry = &((t_oam*)oam_ram)[objoff]; u16 obj_attr0 = eswap16(oamentry->attr0); @@ -1497,6 +1626,7 @@ void render_scanline_objs( .attr1 = obj_attr1, .attr2 = eswap16(oamentry->attr2), .is_double = (obj_attr0 & 0x200) != 0, + .objnum = objoff }; s32 obj_maxw = (is_affine && obji.is_double) ? obji.obj_w * 2 : obji.obj_w; @@ -1551,6 +1681,17 @@ void render_scanline_objs( render_sprite( &obji, is_affine, start, end, scanline, pxcomb, palptr); } + + // Clear the hijack buffer for this row now that we've rendered the first hijack OBJ encountered + // This should fix issues with transparency on higher priority OBJs in the order + //if((oam_line_hijack[vcount] == objoff) && objoff > 0) { + //printf("OBJ %d has caused the OBJ Buffer to clear, clearing %d bytes \n", objoff, sizeof(obj_buf)); + // oam_line_hijack[vcount] = 0; + // Clear the object buffer + // memset(obj_buf, 0, sizeof(obj_buf)); + + //} + } } @@ -1565,7 +1706,7 @@ static void order_obj(u32 video_mode) u32 row; t_oam *oam_base = (t_oam*)oam_ram; u16 rend_cycles[160]; - u8 obj_priority = 0; + u8 highest_priority = 0; // When we call order_obj, it means OAM has changed // Reset the hijack counter and mark the obj_buffer as not yet filled @@ -1581,6 +1722,8 @@ static void order_obj(u32 video_mode) memset(rend_cycles, 0, sizeof(rend_cycles)); // Let's also clear the hijack row flags memset(oam_line_hijack, 0, sizeof(oam_line_hijack)); + // Clear the hijack flags for all objects on all scanlines + memset(obj_hijack_flags, 0, sizeof(obj_hijack_flags)); for(obj_num = 0; obj_num < 128; obj_num++) @@ -1631,15 +1774,25 @@ static void order_obj(u32 video_mode) { // Let's check for OAM hijack here. If detected for this object we'll turn on hijacking for all // active objects from now on. - // This code needs further optimisation because ideally we only want to mark rows as hijacked - // for specific objects, but as it stands it will mark everything as hijacked from the first - // encounter onwards. - if(((obj_attr2 >> 10) & 0x03) < obj_priority) + // We only set the row as hijacked if the OBJ is visible on-screen and is hijacking + // It will mark the row as hijacked and set the value to the OBJ number. + u32 obj_priority = (obj_attr2 >> 10) & 0x03; + + // Check if this specific object is hijacking the priority order + // Don't mark wrap-around objects as hijacking to avoid out-of-bounds issues + if(oam_hijacking_enabled && (obj_priority < highest_priority) && obj_num != 4) { - oam_hijack = 1; + // This object is hijacking - set oam_hijack to this object's number + oam_hijack = obj_num; //printf("OBJ %d is hijacking, X %d, Y %d \n", obj_num, obj_x, obj_y); } - obj_priority = (obj_attr2 >> 10) & 0x03; + else + { + // This object is not hijacking - reset oam_hijack and update the priority baseline + oam_hijack = 0; + highest_priority = obj_priority; + } + bool is_affine = obj_attr0 & 0x100; // Clip Y coord and height to the 0..159 interval u32 starty = MAX(obj_y, 0); @@ -1657,8 +1810,12 @@ static void order_obj(u32 video_mode) obj_priority_list[obj_priority][row][cur_cnt] = obj_num; obj_priority_count[obj_priority][row] = cur_cnt + 1; rend_cycles[row] += cyccnt; - // Assignment probably quicker than comparison, so let's just set the line hijack every time - oam_line_hijack[row] = oam_hijack; + // Set the hijack row if oam_hijack is active + // Mark this object as hijacking if oam_hijack is active + if(oam_hijacking_enabled && oam_hijack > 0) { + oam_line_hijack[row] = oam_hijack; + obj_hijack_flags[row][obj_num] = 1; + } //if(oam_hijack) // printf("OAM Hijack Row: %d, Object: %d \n", row, obj_num); // Mark the row as having semi-transparent objects @@ -1678,8 +1835,12 @@ static void order_obj(u32 video_mode) obj_priority_list[obj_priority][row][cur_cnt] = obj_num; obj_priority_count[obj_priority][row] = cur_cnt + 1; rend_cycles[row] += cyccnt; - // Assignment probably quicker than comparison->assignment, so let's just set the line hijack every time - oam_line_hijack[row] = oam_hijack; + // Set the hijack row if oam_hijack is active + // Mark this object as hijacking if oam_hijack is active + if(oam_hijacking_enabled && oam_hijack > 0) { + oam_line_hijack[row] = oam_hijack; + obj_hijack_flags[row][obj_num] = 1; + } //if(oam_hijack) // printf("OAM Hijack Row: %d, Object: %d \n", row, obj_num); } @@ -1924,7 +2085,7 @@ void tile_render_layers(u32 start, u32 end, dsttype *dst_ptr, u32 enabled_layers bool objlayer_is_1st_tgt = ((read_ioreg(REG_BLDCNT) >> 4) & 1) != 0; bool has_trans_obj = obj_alpha_count[read_ioreg(REG_VCOUNT)]; - bool row_obj_hijack = oam_line_hijack[read_ioreg(REG_VCOUNT)]; + u8 row_obj_hijack = oam_line_hijack[read_ioreg(REG_VCOUNT)]; u16 *obj_buf_ptr; obj_buf_ptr = obj_buf; @@ -1933,20 +2094,20 @@ void tile_render_layers(u32 start, u32 end, dsttype *dst_ptr, u32 enabled_layers // If this line has OAM hijack we need to fill the buffer with OBJ layers // first so that the buffer is available to overlay pixels onto transparent OBJ areas - if (row_obj_hijack) { - // Fill it with Object Layers only, back to front - for (lnum = 0; lnum < layer_count; lnum++) { - u32 layer = layer_order[lnum]; - bool is_obj = layer & 0x4; - if (is_obj && obj_enabled) { - //printf("Start: %d, End: %d, Row: %d \n", start, end, read_ioreg(REG_VCOUNT)); - // Render to u16/INDXCOLOR to allow us to cover all render types when substituting pixels - render_scanline_objs(layer & 0x3, start, end, obj_buf_ptr, &palette_ram_converted[0x100]); - //for(u8 c = 0; c < 240; c++) { - // if(obj_buf[c]) - // printf("OBJ Mode: %d, OBJ Buf Pos: %d, OBJ Buf: %d, Start: %d, End: %d, Row: %d \n", objmode, c, obj_buf[c], start, end, read_ioreg(REG_VCOUNT)); - //} - } + if (oam_hijacking_enabled && row_obj_hijack) { + // Fill it with Object Layers in OAM priority order (row_obj_hijack->0), ignoring BG priority + if (obj_enabled) { + //printf("Start: %d, End: %d, Row: %d \n", start, end, read_ioreg(REG_VCOUNT)); + // Disable hijacking behavior when creating the obj buffer + rendering_obj_buffer = true; + // Render to u16/INDXCOLOR to allow us to cover all render types when substituting pixels + render_scanline_objs_oam_order(start, end, obj_buf_ptr, &palette_ram_converted[0x100], row_obj_hijack); + // Re-enable hijacking behavior for normal rendering + rendering_obj_buffer = false; + //for(u8 c = 0; c < 240; c++) { + // if(obj_buf[c]) + // printf("OBJ Mode: %d, OBJ Buf Pos: %d, OBJ Buf: %d, Start: %d, End: %d, Row: %d \n", objmode, c, obj_buf[c], start, end, read_ioreg(REG_VCOUNT)); + //} } } @@ -2139,7 +2300,7 @@ static void bitmap_render_layers( u32 current_layer; u32 layer_order_pos; - bool row_obj_hijack = oam_line_hijack[read_ioreg(REG_VCOUNT)]; + u8 row_obj_hijack = oam_line_hijack[read_ioreg(REG_VCOUNT)]; u16 *obj_buf_ptr; obj_buf_ptr = obj_buf; @@ -2148,21 +2309,20 @@ static void bitmap_render_layers( // If this line has OAM hijack we need to fill the buffer with OBJ layers // first so that the buffer is available to overlay pixels onto transparent OBJ areas - if (row_obj_hijack) { - // Fill it with Object Layers only, back to front - for (layer_order_pos = 0; layer_order_pos < layer_count; layer_order_pos++) { - current_layer = layer_order[layer_order_pos]; - bool is_obj = current_layer & 0x4; - bool obj_enabled = enable_flags & 0x10; - if (is_obj && obj_enabled) { - //printf("Start: %d, End: %d, Row: %d \n", start, end, REG_VCOUNT); - // Render to u16/INDXCOLOR to allow us to cover all render types when substituting pixels - render_scanline_objs(current_layer & 0x3, start, end, obj_buf_ptr, &palette_ram_converted[0x100]); - } + if (oam_hijacking_enabled && row_obj_hijack) { + // Fill it with Object Layers in OAM priority order (row_obj_hijack->0), ignoring BG priority + bool obj_enabled = enable_flags & 0x10; + if (obj_enabled) { + //printf("Start: %d, End: %d, Row: %d \n", start, end, REG_VCOUNT); + // Disable hijacking behavior when creating the obj buffer + rendering_obj_buffer = true; + // Render to dsttype/FULLCOLOR to allow us to cover all render types when substituting pixels + render_scanline_objs_oam_order(start, end, obj_buf_ptr, &palette_ram_converted[0x100], row_obj_hijack); + // Re-enable hijacking behavior for normal rendering + rendering_obj_buffer = false; } } - - + // Fill in the renderers for a layer based on the mode type, static const bitmap_layer_render_struct renderers[3][2] = { @@ -2178,7 +2338,7 @@ static void bitmap_render_layers( unsigned modeidx = (dispcnt & 0x07) - 3; const bitmap_layer_render_struct *mode_rend = &renderers[modeidx][mmode]; const bitmap_layer_render_struct *idxm_rend = &idx32_bmrend[modeidx][mmode]; - + fill_line_background(start, end, scanline); for(layer_order_pos = 0; layer_order_pos < layer_count; layer_order_pos++) @@ -2330,12 +2490,12 @@ static void render_window_n_pass(u16 *scanline, u32 start, u32 end) // Enable bits for stuff inside the window (and outside) u32 winin = read_ioreg(REG_WININ); u32 wndn_enable = (winin >> (8 * winnum)) & 0x3F; - // If the window is defined upside down, the areas are inverted. if (goodwin) { // Render [start, win_l) range (which is outside the window) if (win_l != start) outfn(scanline, start, win_l); + // Render the actual window0 pixels render_scanline_conditional(win_l, win_r, scanline, wndn_enable); // Render the [win_l, end] range (outside) @@ -2412,6 +2572,7 @@ void update_scanline(void) { u32 pitch = get_screen_pitch(); u16 dispcnt = read_ioreg(REG_DISPCNT); + u16 grnswp = read_ioreg(REG_GRNSWP); u32 vcount = read_ioreg(REG_VCOUNT); u16 *screen_offset = get_screen_pixels() + (vcount * pitch); u32 video_mode = dispcnt & 0x07; @@ -2435,6 +2596,24 @@ void update_scanline(void) else render_scanline_window(screen_offset); + // Check for Undocumented Green Swap screen mode + if (grnswp) { + u16 swapbuffer = 0; + // Apply Green Swap to scanline in place for speed + for (u8 x = 0; x < pitch; x += 4) { + swapbuffer = screen_offset[x]; + screen_offset[x] = screen_offset[x] & (M_COLOR_RED | M_COLOR_BLUE); + screen_offset[x] |= screen_offset[x + 1] & M_COLOR_GREEN; + screen_offset[x + 1] = screen_offset[x + 1] & (M_COLOR_RED | M_COLOR_BLUE); + screen_offset[x + 1] |= swapbuffer & M_COLOR_GREEN; + swapbuffer = screen_offset[x + 2]; + screen_offset[x + 2] = screen_offset[x + 2] & (M_COLOR_RED | M_COLOR_BLUE); + screen_offset[x + 2] |= screen_offset[x + 3] & M_COLOR_GREEN; + screen_offset[x + 3] = screen_offset[x + 3] & (M_COLOR_RED | M_COLOR_BLUE); + screen_offset[x + 3] |= swapbuffer & M_COLOR_GREEN; + } + } + // Mode 0 does not use any affine params at all. if (video_mode) { // Account for vertical mosaic effect, by correcting affine references. @@ -2461,3 +2640,4 @@ void update_scanline(void) } } } + From 9faf6f9a2d53ae5d5fb51b76095e8efcf9bb9294 Mon Sep 17 00:00:00 2001 From: andymcca Date: Sun, 12 Oct 2025 07:16:36 +0100 Subject: [PATCH 08/11] Add oam hijack core option Add a core option so that oam hijacking support can be turned on/off. --- libretro/libretro.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libretro/libretro.c b/libretro/libretro.c index 6afa60dd..e9da4046 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -91,6 +91,7 @@ struct retro_perf_callback perf_cb; int dynarec_enable; boot_mode selected_boot_mode = boot_game; int sprite_limit = 1; +int oam_hijacking_enabled = 0; // Default to disabled static int rtc_mode = FEAT_AUTODETECT; static int rumble_mode = FEAT_AUTODETECT; @@ -918,6 +919,17 @@ static void check_variables(bool started_from_load) sprite_limit = 0; } + var.key = "gpsp_oam_hijack"; + var.value = 0; + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + if (strcmp(var.value, "disabled") == 0) + oam_hijacking_enabled = 0; + else if (strcmp(var.value, "enabled") == 0) + oam_hijacking_enabled = 1; + } + var.key = "gpsp_frameskip"; var.value = 0; frameskip_type_prev = current_frameskip_type; From c2d05aab787c976d03927ee4b748a0810c89210d Mon Sep 17 00:00:00 2001 From: andymcca Date: Sun, 12 Oct 2025 07:23:32 +0100 Subject: [PATCH 09/11] Add core option This adds a core option to turn oam hijacking emulation on/off. --- libretro/libretro_core_options.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/libretro/libretro_core_options.h b/libretro/libretro_core_options.h index 098db767..bde23050 100644 --- a/libretro/libretro_core_options.h +++ b/libretro/libretro_core_options.h @@ -100,6 +100,17 @@ struct retro_core_option_definition option_defs_us[] = { }, "disabled" }, + { + "gpsp_oam_hijack", + "OAM Priority Hijacking", + "Support for OAM priority hijacking behavior where objects relative BG priority does not follow the normal OAM order priority. The GBA has a transparency bug in these situations which is used by some games (Golden Sun, Golden Sun 2, Zelda: Minish Cap, Mario Golf etc), this option emulates that bug.", + { + { "disabled", NULL }, + { "enabled", NULL }, + { NULL, NULL }, + }, + "disabled" + }, { "gpsp_rtc", "RTC support", From 418b6f8d29c0efb895d3c8a70a017c06de5ba9cc Mon Sep 17 00:00:00 2001 From: andymcca Date: Sun, 12 Oct 2025 07:31:27 +0100 Subject: [PATCH 10/11] Tidy up comments and Green Swap Remove commented out code and also Green Swap code (this is from a different PR) --- video.cc | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) diff --git a/video.cc b/video.cc index 0caf511d..65a38cf2 100644 --- a/video.cc +++ b/video.cc @@ -92,12 +92,6 @@ typedef struct #define RENDER_COL32 2 #define RENDER_ALPHA 3 -// GRNSWP colors -#define M_COLOR_RED 0xF800 -#define M_COLOR_GREEN 0x07E0 -#define M_COLOR_BLUE 0x001F -#define M_COLOR_ALPHA 0x0000 - // Byte lengths of complete tiles and tile rows in 4bpp and 8bpp. #define tile_width_4bpp 4 @@ -1092,7 +1086,6 @@ static inline void render_obj_tile_Nbpp(u32 px_comb, else *dest_ptr = obj_buf[sl_start] | ((*dest_ptr) << 16); } - //*dest_ptr = obj_buf[sl_start]; } } } @@ -1139,7 +1132,6 @@ static inline void render_obj_tile_Nbpp(u32 px_comb, else *dest_ptr = obj_buf[sl_start] | ((*dest_ptr) << 16); } - //*dest_ptr = obj_buf[sl_start]; } } } @@ -1601,10 +1593,6 @@ void render_scanline_objs( u32 objcnt = obj_priority_count[priority][vcount]; u8 *objlist = obj_priority_list[priority][vcount]; - // Clear the object buffer for Priority 0 - //if(priority == 0) - // memset(obj_buf, 0, sizeof(obj_buf)); - // Render all the visible objects for this priority (back to front) for (objn = objcnt-1; objn >= 0; objn--) { // Objects in the list are pre-filtered and sorted in the appropriate order @@ -1681,17 +1669,6 @@ void render_scanline_objs( render_sprite( &obji, is_affine, start, end, scanline, pxcomb, palptr); } - - // Clear the hijack buffer for this row now that we've rendered the first hijack OBJ encountered - // This should fix issues with transparency on higher priority OBJs in the order - //if((oam_line_hijack[vcount] == objoff) && objoff > 0) { - //printf("OBJ %d has caused the OBJ Buffer to clear, clearing %d bytes \n", objoff, sizeof(obj_buf)); - // oam_line_hijack[vcount] = 0; - // Clear the object buffer - // memset(obj_buf, 0, sizeof(obj_buf)); - - //} - } } @@ -2104,10 +2081,6 @@ void tile_render_layers(u32 start, u32 end, dsttype *dst_ptr, u32 enabled_layers render_scanline_objs_oam_order(start, end, obj_buf_ptr, &palette_ram_converted[0x100], row_obj_hijack); // Re-enable hijacking behavior for normal rendering rendering_obj_buffer = false; - //for(u8 c = 0; c < 240; c++) { - // if(obj_buf[c]) - // printf("OBJ Mode: %d, OBJ Buf Pos: %d, OBJ Buf: %d, Start: %d, End: %d, Row: %d \n", objmode, c, obj_buf[c], start, end, read_ioreg(REG_VCOUNT)); - //} } } @@ -2572,7 +2545,6 @@ void update_scanline(void) { u32 pitch = get_screen_pitch(); u16 dispcnt = read_ioreg(REG_DISPCNT); - u16 grnswp = read_ioreg(REG_GRNSWP); u32 vcount = read_ioreg(REG_VCOUNT); u16 *screen_offset = get_screen_pixels() + (vcount * pitch); u32 video_mode = dispcnt & 0x07; @@ -2595,24 +2567,6 @@ void update_scanline(void) memset(screen_offset, 0xff, 240*sizeof(u16)); else render_scanline_window(screen_offset); - - // Check for Undocumented Green Swap screen mode - if (grnswp) { - u16 swapbuffer = 0; - // Apply Green Swap to scanline in place for speed - for (u8 x = 0; x < pitch; x += 4) { - swapbuffer = screen_offset[x]; - screen_offset[x] = screen_offset[x] & (M_COLOR_RED | M_COLOR_BLUE); - screen_offset[x] |= screen_offset[x + 1] & M_COLOR_GREEN; - screen_offset[x + 1] = screen_offset[x + 1] & (M_COLOR_RED | M_COLOR_BLUE); - screen_offset[x + 1] |= swapbuffer & M_COLOR_GREEN; - swapbuffer = screen_offset[x + 2]; - screen_offset[x + 2] = screen_offset[x + 2] & (M_COLOR_RED | M_COLOR_BLUE); - screen_offset[x + 2] |= screen_offset[x + 3] & M_COLOR_GREEN; - screen_offset[x + 3] = screen_offset[x + 3] & (M_COLOR_RED | M_COLOR_BLUE); - screen_offset[x + 3] |= swapbuffer & M_COLOR_GREEN; - } - } // Mode 0 does not use any affine params at all. if (video_mode) { From d8db010cb7e7e54a196146758998c0e30d5d05cf Mon Sep 17 00:00:00 2001 From: andymcca Date: Sun, 12 Oct 2025 23:51:15 +0100 Subject: [PATCH 11/11] Remove left-over debugging code Was hard-excluding obj_num 4 in all instances. --- video.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/video.cc b/video.cc index 65a38cf2..99e735cb 100644 --- a/video.cc +++ b/video.cc @@ -1757,7 +1757,7 @@ static void order_obj(u32 video_mode) // Check if this specific object is hijacking the priority order // Don't mark wrap-around objects as hijacking to avoid out-of-bounds issues - if(oam_hijacking_enabled && (obj_priority < highest_priority) && obj_num != 4) + if(oam_hijacking_enabled && (obj_priority < highest_priority)) { // This object is hijacking - set oam_hijack to this object's number oam_hijack = obj_num;