Skip to content

Commit b8abe16

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

File tree

1 file changed

+94
-51
lines changed

1 file changed

+94
-51
lines changed

src/ppu.zig

Lines changed: 94 additions & 51 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.
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

Comments
 (0)