@@ -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.
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.
@@ -193,6 +198,17 @@ fn renderLine(gb: *gameboy.State) void {
193198 if (! gb .memory .io .lcdc .lcd_enable ) return ;
194199
195200 var rendered_window = false ;
201+ const Line = struct {
202+ pixel : Pixel ,
203+ kind : enum (u1 ) { background , object },
204+ is_transparent : bool ,
205+ };
206+ var line = [_ ]Line {.{
207+ .pixel = colors [gb .memory .io .bgp .id0 ],
208+ .kind = .background ,
209+ .is_transparent = true ,
210+ }} ** SCREEN_WIDTH ;
211+
196212 if (gb .memory .io .lcdc .bg_window_enable_priority ) {
197213 const data_area_start : memory.Addr = switch (gb .memory .io .lcdc .bg_window_tile_data_area ) {
198214 .signed = > memory .TILE_BLOCK2_START ,
@@ -252,8 +268,11 @@ fn renderLine(gb: *gameboy.State) void {
252268 2 = > gb .memory .io .bgp .id2 ,
253269 3 = > gb .memory .io .bgp .id3 ,
254270 };
255- gb .ppu .back_pixels [@as (u16 , gb .memory .io .ly ) * SCREEN_WIDTH + x_pixel_off ] =
256- colors [color_id ];
271+ line [x_pixel_off ] = .{
272+ .pixel = colors [color_id ],
273+ .kind = .background ,
274+ .is_transparent = color_id == 0 ,
275+ };
257276 }
258277 }
259278 if (rendered_window ) {
@@ -264,6 +283,14 @@ fn renderLine(gb: *gameboy.State) void {
264283 if (gb .memory .io .lcdc .obj_enable ) {
265284 var obj_addr : memory.Addr = memory .OAM_START ;
266285 const obj_size = @sizeOf (Object );
286+ const obj_height : u5 = switch (gb .memory .io .lcdc .obj_size ) {
287+ .bit8 = > 8 ,
288+ .bit16 = > 16 ,
289+ };
290+
291+ // draw at most 10 objects
292+ var visible_sprites : [10 ]Object = [_ ]Object {@bitCast (@as (u32 , 0 ))} ** 10 ;
293+ var visible_sprites_idx : u8 = 0 ;
267294
268295 while (obj_addr < memory .OAM_START + 0xa0 ) : (obj_addr += obj_size ) {
269296 // get the current oam object
@@ -272,61 +299,77 @@ fn renderLine(gb: *gameboy.State) void {
272299 gb .memory .oam [rel_addr .. rel_addr + obj_size ][0.. obj_size ].* ,
273300 );
274301
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
302+ if (object .x_pos != 0 and
303+ object .y_pos <= gb .memory .io .ly + 16 and
280304 gb .memory .io .ly + 16 < object .y_pos + obj_height )
281305 {
282- const palette = switch (object .flags .dmg_palette ) {
283- 0 = > gb .memory .io .obp0 ,
284- 1 = > gb .memory .io .obp1 ,
285- };
306+ visible_sprites [visible_sprites_idx ] = object ;
307+ visible_sprites_idx += 1 ;
286308
287- const tile_id = switch (gb .memory .io .lcdc .obj_size ) {
288- .bit8 = > object .tile_id ,
289- .bit16 = > object .tile_id & 0xfe ,
290- };
309+ if (visible_sprites_idx == visible_sprites .len ) {
310+ break ;
311+ }
312+ }
313+ }
291314
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- }
315+ // the object with the smallest x position wins, otherwise
316+ // it is decided by the order in the oam
317+ mem .sort (Object , & visible_sprites , {}, Object .lessThan );
318+
319+ for (visible_sprites ) | object | {
320+ const palette = switch (object .flags .dmg_palette ) {
321+ 0 = > gb .memory .io .obp0 ,
322+ 1 = > gb .memory .io .obp1 ,
323+ };
324+
325+ const tile_id = switch (gb .memory .io .lcdc .obj_size ) {
326+ .bit8 = > object .tile_id ,
327+ .bit16 = > object .tile_id & 0xfe ,
328+ };
329+
330+ for (0.. 8) | x_pixel_off | {
331+ const x_pixel : u8 = object .x_pos +% @as (u8 , @intCast (x_pixel_off )) -% 8 ;
332+
333+ if (x_pixel < SCREEN_WIDTH and
334+ ((line [x_pixel ].kind == .background and object .flags .priority == .above ) or line [x_pixel ].is_transparent ))
335+ {
336+ // always used unsigned addressing for objects
337+ var tile_addr = memory .TILE_BLOCK0_START + @as (u16 , tile_id ) * 16 ;
338+
339+ const y_pixel_off = gb .memory .io .ly + 16 - object .y_pos ;
340+ tile_addr += (if (object .flags .y_flip )
341+ obj_height - 1 - y_pixel_off
342+ else
343+ y_pixel_off ) * 2 ;
344+
345+ const tile_data1 = memory .read_vram (gb , tile_addr );
346+ const tile_data2 = memory .read_vram (gb , tile_addr + 1 );
347+
348+ const x_bit_num : u3 = if (object .flags .x_flip )
349+ @intCast (7 - x_pixel_off )
350+ else
351+ @intCast (x_pixel_off );
352+ const lo = tile_data1 & (@as (u8 , 0x80 ) >> x_bit_num ) != 0 ;
353+ const hi = tile_data2 & (@as (u8 , 0x80 ) >> x_bit_num ) != 0 ;
354+
355+ const palette_id = @as (u2 , @intFromBool (hi )) << 1 | @intFromBool (lo );
356+ const color_id = switch (palette_id ) {
357+ 0 = > continue ,
358+ 1 = > palette .id1 ,
359+ 2 = > palette .id2 ,
360+ 3 = > palette .id3 ,
361+ };
362+ line [x_pixel ] = .{
363+ .pixel = colors [color_id ],
364+ .kind = .object ,
365+ .is_transparent = color_id == 0 ,
366+ };
328367 }
329368 }
330369 }
331370 }
371+
372+ for (0.. SCREEN_WIDTH ) | x_pixel_off | {
373+ gb .ppu .back_pixels [@as (u16 , gb .memory .io .ly ) * SCREEN_WIDTH + x_pixel_off ] = line [x_pixel_off ].pixel ;
374+ }
332375}
0 commit comments