@@ -99,6 +99,11 @@ const Object = packed struct(u32) {
9999 /// Whether the object is above or below the background and window.
100100 priority : enum (u1 ) { above = 0 , below = 1 },
101101 },
102+
103+ /// We want the smaller x position to win so it comes later.
104+ fn lessThan (_ : void , lhs : @This (), rhs : @This ()) bool {
105+ return lhs .x_pos > rhs .x_pos ;
106+ }
102107};
103108
104109/// Same as the normal palette except 0 is unused since it's transparent.
@@ -264,6 +269,14 @@ fn renderLine(gb: *gameboy.State) void {
264269 if (gb .memory .io .lcdc .obj_enable ) {
265270 var obj_addr : memory.Addr = memory .OAM_START ;
266271 const obj_size = @sizeOf (Object );
272+ const obj_height : u5 = switch (gb .memory .io .lcdc .obj_size ) {
273+ .bit8 = > 8 ,
274+ .bit16 = > 16 ,
275+ };
276+
277+ // draw at most 10 objects
278+ var visible_sprites : [10 ]Object = [_ ]Object {@bitCast (@as (u32 , 0 ))} ** 10 ;
279+ var visible_sprites_idx : u8 = 0 ;
267280
268281 while (obj_addr < memory .OAM_START + 0xa0 ) : (obj_addr += obj_size ) {
269282 // get the current oam object
@@ -272,59 +285,70 @@ fn renderLine(gb: *gameboy.State) void {
272285 gb .memory .oam [rel_addr .. rel_addr + obj_size ][0.. obj_size ].* ,
273286 );
274287
275- const obj_height : u5 = switch (gb .memory .io .lcdc .obj_size ) {
276- .bit8 = > 8 ,
277- .bit16 = > 16 ,
278- };
279- if (object .y_pos <= gb .memory .io .ly + 16 and
288+ if (object .x_pos != 0 and
289+ object .y_pos <= gb .memory .io .ly + 16 and
280290 gb .memory .io .ly + 16 < object .y_pos + obj_height )
281291 {
282- const palette = switch (object .flags .dmg_palette ) {
283- 0 = > gb .memory .io .obp0 ,
284- 1 = > gb .memory .io .obp1 ,
285- };
292+ visible_sprites [visible_sprites_idx ] = object ;
293+ visible_sprites_idx += 1 ;
286294
287- const tile_id = switch (gb .memory .io .lcdc .obj_size ) {
288- .bit8 = > object .tile_id ,
289- .bit16 = > object .tile_id & 0xfe ,
290- };
295+ if (visible_sprites_idx == visible_sprites .len ) {
296+ break ;
297+ }
298+ }
299+ }
291300
292- for (0.. 8) | x_pixel_off | {
293- const x_pixel : u8 = object .x_pos +% @as (u8 , @intCast (x_pixel_off )) -% 8 ;
294-
295- if (0 <= x_pixel and x_pixel < SCREEN_WIDTH and
296- (object .flags .priority == .above or
297- gb .ppu .back_pixels [@as (u16 , gb .memory .io .ly ) * SCREEN_WIDTH + x_pixel ] == colors [gb .memory .io .bgp .id0 ]))
298- {
299- // always used unsigned addressing for objects
300- var tile_addr = memory .TILE_BLOCK0_START + @as (u16 , tile_id ) * 16 ;
301-
302- const y_pixel_off = gb .memory .io .ly + 16 - object .y_pos ;
303- tile_addr += (if (object .flags .y_flip )
304- obj_height - 1 - y_pixel_off
305- else
306- y_pixel_off ) * 2 ;
307-
308- const tile_data1 = memory .read_vram (gb , tile_addr );
309- const tile_data2 = memory .read_vram (gb , tile_addr + 1 );
310-
311- const x_bit_num : u3 = if (object .flags .x_flip )
312- @intCast (7 - x_pixel_off )
313- else
314- @intCast (x_pixel_off );
315- const lo = tile_data1 & (@as (u8 , 0x80 ) >> x_bit_num ) != 0 ;
316- const hi = tile_data2 & (@as (u8 , 0x80 ) >> x_bit_num ) != 0 ;
317-
318- const palette_id = @as (u2 , @intFromBool (hi )) << 1 | @intFromBool (lo );
319- const color_id = switch (palette_id ) {
320- 0 = > continue ,
321- 1 = > palette .id1 ,
322- 2 = > palette .id2 ,
323- 3 = > palette .id3 ,
324- };
325- gb .ppu .back_pixels [@as (u16 , gb .memory .io .ly ) * SCREEN_WIDTH + x_pixel ] =
326- colors [color_id ];
327- }
301+ // the object with the smallest x position wins, otherwise
302+ // it is decided by the order in the oam
303+ mem .reverse (Object , & visible_sprites );
304+ mem .sort (Object , & visible_sprites , {}, Object .lessThan );
305+
306+ for (visible_sprites ) | object | {
307+ const palette = switch (object .flags .dmg_palette ) {
308+ 0 = > gb .memory .io .obp0 ,
309+ 1 = > gb .memory .io .obp1 ,
310+ };
311+
312+ const tile_id = switch (gb .memory .io .lcdc .obj_size ) {
313+ .bit8 = > object .tile_id ,
314+ .bit16 = > object .tile_id & 0xfe ,
315+ };
316+
317+ for (0.. 8) | x_pixel_off | {
318+ const x_pixel : u8 = object .x_pos +% @as (u8 , @intCast (x_pixel_off )) -% 8 ;
319+
320+ if (0 <= x_pixel and x_pixel < SCREEN_WIDTH and
321+ (object .flags .priority == .above or
322+ gb .ppu .back_pixels [@as (u16 , gb .memory .io .ly ) * SCREEN_WIDTH + x_pixel ] == colors [gb .memory .io .bgp .id0 ]))
323+ {
324+ // always used unsigned addressing for objects
325+ var tile_addr = memory .TILE_BLOCK0_START + @as (u16 , tile_id ) * 16 ;
326+
327+ const y_pixel_off = gb .memory .io .ly + 16 - object .y_pos ;
328+ tile_addr += (if (object .flags .y_flip )
329+ obj_height - 1 - y_pixel_off
330+ else
331+ y_pixel_off ) * 2 ;
332+
333+ const tile_data1 = memory .read_vram (gb , tile_addr );
334+ const tile_data2 = memory .read_vram (gb , tile_addr + 1 );
335+
336+ const x_bit_num : u3 = if (object .flags .x_flip )
337+ @intCast (7 - x_pixel_off )
338+ else
339+ @intCast (x_pixel_off );
340+ const lo = tile_data1 & (@as (u8 , 0x80 ) >> x_bit_num ) != 0 ;
341+ const hi = tile_data2 & (@as (u8 , 0x80 ) >> x_bit_num ) != 0 ;
342+
343+ const palette_id = @as (u2 , @intFromBool (hi )) << 1 | @intFromBool (lo );
344+ const color_id = switch (palette_id ) {
345+ 0 = > continue ,
346+ 1 = > palette .id1 ,
347+ 2 = > palette .id2 ,
348+ 3 = > palette .id3 ,
349+ };
350+ gb .ppu .back_pixels [@as (u16 , gb .memory .io .ly ) * SCREEN_WIDTH + x_pixel ] =
351+ colors [color_id ];
328352 }
329353 }
330354 }
0 commit comments