Skip to content

Commit 2aa9213

Browse files
committed
feat(ppu): add 10 sprite per line limit and fix priority
1 parent ed72aee commit 2aa9213

File tree

1 file changed

+73
-49
lines changed

1 file changed

+73
-49
lines changed

src/ppu.zig

Lines changed: 73 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)