Compare commits

..

No commits in common. "ce2271100b85d3cb9a34ef40c46dbf05d491f03d" and "2dc3864dca3cfca40a4ec17d1c29aa41456dc3d4" have entirely different histories.

4 changed files with 59 additions and 107 deletions

View File

@ -180,8 +180,8 @@ pub const Apu = struct {
left += bias; left += bias;
right += bias; right += bias;
const tmp_left = std.math.clamp(@bitCast(u16, left), std.math.minInt(u11), std.math.maxInt(u11)); const tmp_left = std.math.clamp(@intCast(u16, left), std.math.minInt(u11), std.math.maxInt(u11));
const tmp_right = std.math.clamp(@bitCast(u16, right), std.math.minInt(u11), std.math.maxInt(u11)); const tmp_right = std.math.clamp(@intCast(u16, right), std.math.minInt(u11), std.math.maxInt(u11));
// Extend to 16-bit signed audio samples // Extend to 16-bit signed audio samples
const final_left = (tmp_left << 5) | (tmp_left >> 6); const final_left = (tmp_left << 5) | (tmp_left >> 6);
@ -199,7 +199,10 @@ pub const Apu = struct {
self.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_U16, 2, @intCast(c_int, self.sampleRate()), SDL.AUDIO_U16, 2, host_sample_rate) orelse unreachable; self.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_U16, 2, @intCast(c_int, self.sampleRate()), SDL.AUDIO_U16, 2, host_sample_rate) orelse unreachable;
} }
_ = SDL.SDL_AudioStreamPut(self.stream, &[2]u16{ final_left, final_right }, 2 * @sizeOf(u16)); // If we add 0x4040 to each sample the DC Offset is removed
// note: found this through guess and check
// FIXME: Pretty sure this is a dirty hack and should be fixed
_ = SDL.SDL_AudioStreamPut(self.stream, &[2]u16{ final_left + 0x4040, final_right + 0x4040 }, 2 * @sizeOf(u16));
self.sched.push(.SampleAudio, self.sampleTicks() -| late); self.sched.push(.SampleAudio, self.sampleTicks() -| late);
} }

View File

@ -184,12 +184,12 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
0x0400_001C => bus.ppu.setBgOffsets(3, value), 0x0400_001C => bus.ppu.setBgOffsets(3, value),
0x0400_0020 => bus.ppu.aff.bg[0].writePaPb(value), 0x0400_0020 => bus.ppu.aff.bg[0].writePaPb(value),
0x0400_0024 => bus.ppu.aff.bg[0].writePcPd(value), 0x0400_0024 => bus.ppu.aff.bg[0].writePcPd(value),
0x0400_0028 => bus.ppu.aff.bg[0].x = @bitCast(i32, value), 0x0400_0028 => bus.ppu.aff.bg[0].x.raw = value,
0x0400_002C => bus.ppu.aff.bg[0].y = @bitCast(i32, value), 0x0400_002C => bus.ppu.aff.bg[0].y.raw = value,
0x0400_0030 => bus.ppu.aff.bg[1].writePaPb(value), 0x0400_0030 => bus.ppu.aff.bg[1].writePaPb(value),
0x0400_0034 => bus.ppu.aff.bg[1].writePcPd(value), 0x0400_0034 => bus.ppu.aff.bg[1].writePcPd(value),
0x0400_0038 => bus.ppu.aff.bg[1].x = @bitCast(i32, value), 0x0400_0038 => bus.ppu.aff.bg[1].x.raw = value,
0x0400_003C => bus.ppu.aff.bg[1].y = @bitCast(i32, value), 0x0400_003C => bus.ppu.aff.bg[1].y.raw = value,
0x0400_0040 => log.debug("Wrote 0x{X:0>8} to WIN0H and WIN1H", .{value}), 0x0400_0040 => log.debug("Wrote 0x{X:0>8} to WIN0H and WIN1H", .{value}),
0x0400_0044 => log.debug("Wrote 0x{X:0>8} to WIN0V and WIN1V", .{value}), 0x0400_0044 => log.debug("Wrote 0x{X:0>8} to WIN0V and WIN1V", .{value}),
0x0400_0048 => log.debug("Wrote 0x{X:0>8} to WININ and WINOUT", .{value}), 0x0400_0048 => log.debug("Wrote 0x{X:0>8} to WININ and WINOUT", .{value}),
@ -270,22 +270,22 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
0x0400_001A => bus.ppu.bg[2].vofs.raw = value, 0x0400_001A => bus.ppu.bg[2].vofs.raw = value,
0x0400_001C => bus.ppu.bg[3].hofs.raw = value, 0x0400_001C => bus.ppu.bg[3].hofs.raw = value,
0x0400_001E => bus.ppu.bg[3].vofs.raw = value, 0x0400_001E => bus.ppu.bg[3].vofs.raw = value,
0x0400_0020 => bus.ppu.aff.bg[0].pa = @bitCast(i16, value), 0x0400_0020 => bus.ppu.aff.bg[0].pa.raw = value,
0x0400_0022 => bus.ppu.aff.bg[0].pb = @bitCast(i16, value), 0x0400_0022 => bus.ppu.aff.bg[0].pb.raw = value,
0x0400_0024 => bus.ppu.aff.bg[0].pc = @bitCast(i16, value), 0x0400_0024 => bus.ppu.aff.bg[0].pc.raw = value,
0x0400_0026 => bus.ppu.aff.bg[0].pd = @bitCast(i16, value), 0x0400_0026 => bus.ppu.aff.bg[0].pd.raw = value,
0x0400_0028 => bus.ppu.aff.bg[0].x = @bitCast(i32, @bitCast(u32, bus.ppu.aff.bg[0].x) & 0xFFFF_0000 | value), 0x0400_0028 => bus.ppu.aff.bg[0].x.raw = bus.ppu.aff.bg[0].x.raw & 0xFFFF_0000 | value,
0x0400_002A => bus.ppu.aff.bg[0].x = @bitCast(i32, @bitCast(u32, bus.ppu.aff.bg[0].x) & 0x0000_FFFF | (@as(u32, value) << 16)), 0x0400_002A => bus.ppu.aff.bg[0].x.raw = bus.ppu.aff.bg[0].x.raw & 0x0000_FFFF | (@as(u32, value) << 16),
0x0400_002C => bus.ppu.aff.bg[0].y = @bitCast(i32, @bitCast(u32, bus.ppu.aff.bg[0].y) & 0xFFFF_0000 | value), 0x0400_002C => bus.ppu.aff.bg[0].y.raw = bus.ppu.aff.bg[0].y.raw & 0xFFFF_0000 | value,
0x0400_002E => bus.ppu.aff.bg[0].y = @bitCast(i32, @bitCast(u32, bus.ppu.aff.bg[0].y) & 0x0000_FFFF | (@as(u32, value) << 16)), 0x0400_002E => bus.ppu.aff.bg[0].y.raw = bus.ppu.aff.bg[0].y.raw & 0x0000_FFFF | (@as(u32, value) << 16),
0x0400_0030 => bus.ppu.aff.bg[1].pa = @bitCast(i16, value), 0x0400_0030 => bus.ppu.aff.bg[1].pa.raw = value,
0x0400_0032 => bus.ppu.aff.bg[1].pb = @bitCast(i16, value), 0x0400_0032 => bus.ppu.aff.bg[1].pb.raw = value,
0x0400_0034 => bus.ppu.aff.bg[1].pc = @bitCast(i16, value), 0x0400_0034 => bus.ppu.aff.bg[1].pc.raw = value,
0x0400_0036 => bus.ppu.aff.bg[1].pd = @bitCast(i16, value), 0x0400_0036 => bus.ppu.aff.bg[1].pd.raw = value,
0x0400_0038 => bus.ppu.aff.bg[1].x = @bitCast(i32, @bitCast(u32, bus.ppu.aff.bg[1].x) & 0xFFFF_0000 | value), 0x0400_0038 => bus.ppu.aff.bg[1].x.raw = bus.ppu.aff.bg[1].x.raw & 0xFFFF_0000 | value,
0x0400_003A => bus.ppu.aff.bg[1].x = @bitCast(i32, @bitCast(u32, bus.ppu.aff.bg[1].x) & 0x0000_FFFF | (@as(u32, value) << 16)), 0x0400_003A => bus.ppu.aff.bg[1].x.raw = bus.ppu.aff.bg[1].x.raw & 0x0000_FFFF | (@as(u32, value) << 16),
0x0400_003C => bus.ppu.aff.bg[1].y = @bitCast(i32, @bitCast(u32, bus.ppu.aff.bg[1].y) & 0xFFFF_0000 | value), 0x0400_003C => bus.ppu.aff.bg[1].y.raw = bus.ppu.aff.bg[1].y.raw & 0xFFFF_0000 | value,
0x0400_003E => bus.ppu.aff.bg[1].y = @bitCast(i32, @bitCast(u32, bus.ppu.aff.bg[1].y) & 0x0000_FFFF | (@as(u32, value) << 16)), 0x0400_003E => bus.ppu.aff.bg[1].y.raw = bus.ppu.aff.bg[1].y.raw & 0x0000_FFFF | (@as(u32, value) << 16),
0x0400_0040 => log.debug("Wrote 0x{X:0>4} to WIN0H", .{value}), 0x0400_0040 => log.debug("Wrote 0x{X:0>4} to WIN0H", .{value}),
0x0400_0042 => log.debug("Wrote 0x{X:0>4} to WIN1H", .{value}), 0x0400_0042 => log.debug("Wrote 0x{X:0>4} to WIN1H", .{value}),
0x0400_0044 => log.debug("Wrote 0x{X:0>4} to WIN0V", .{value}), 0x0400_0044 => log.debug("Wrote 0x{X:0>4} to WIN0V", .{value}),
@ -394,8 +394,6 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
0x0400_0009 => bus.ppu.bg[0].cnt.raw = (@as(u16, value) << 8) | (bus.ppu.bg[0].cnt.raw & 0xFF), 0x0400_0009 => bus.ppu.bg[0].cnt.raw = (@as(u16, value) << 8) | (bus.ppu.bg[0].cnt.raw & 0xFF),
0x0400_000A => bus.ppu.bg[1].cnt.raw = (bus.ppu.bg[1].cnt.raw & 0xFF00) | value, 0x0400_000A => bus.ppu.bg[1].cnt.raw = (bus.ppu.bg[1].cnt.raw & 0xFF00) | value,
0x0400_000B => bus.ppu.bg[1].cnt.raw = (@as(u16, value) << 8) | (bus.ppu.bg[1].cnt.raw & 0xFF), 0x0400_000B => bus.ppu.bg[1].cnt.raw = (@as(u16, value) << 8) | (bus.ppu.bg[1].cnt.raw & 0xFF),
0x0400_0048 => log.debug("Wrote 0x{X:0>2} to WININ_L", .{value}),
0x0400_004A => log.debug("Wrote 0x{X:0>2} to WINOUT_L", .{value}),
0x0400_0054 => log.debug("Wrote 0x{X:0>2} to BLDY_L", .{value}), 0x0400_0054 => log.debug("Wrote 0x{X:0>2} to BLDY_L", .{value}),
// Sound // Sound
@ -563,6 +561,20 @@ pub const BackgroundOffset = extern union {
raw: u16, raw: u16,
}; };
pub const BackgroundRefPoint = extern union {
fractional: Bitfield(u32, 0, 8),
integer: Bitfield(u32, 8, 19),
sign: Bit(u32, 27),
raw: u32,
};
pub const BackgroundRotScaleParam = extern union {
fractional: Bitfield(u16, 0, 8),
integer: Bitfield(u16, 8, 7),
sign: Bit(u32, 15),
raw: u16,
};
/// Read / Write /// Read / Write
const InterruptRequest = extern union { const InterruptRequest = extern union {
vblank: Bit(u16, 0), vblank: Bit(u16, 0),

View File

@ -245,7 +245,7 @@ export fn audioCallback(userdata: ?*anyopaque, stream: [*c]u8, len: c_int) void
// If we don't write anything, play silence otherwise garbage will be played // If we don't write anything, play silence otherwise garbage will be played
// FIXME: I don't think this hack to remove DC Offset is acceptable :thinking: // FIXME: I don't think this hack to remove DC Offset is acceptable :thinking:
if (written == 0) std.mem.set(u8, stream[0..@intCast(usize, len)], 0x40); if (written == 0) std.mem.set(u8, stream[0..@intCast(usize, len)], 0x80);
} }
fn getSavePath(alloc: std.mem.Allocator) !?[]const u8 { fn getSavePath(alloc: std.mem.Allocator) !?[]const u8 {

View File

@ -201,53 +201,6 @@ pub const Ppu = struct {
if (pal_id != 0) self.scanline_buf[@bitCast(u9, x)] = self.palette.read(u16, 0x200 + pal_id * 2); if (pal_id != 0) self.scanline_buf[@bitCast(u9, x)] = self.palette.read(u16, 0x200 + pal_id * 2);
} }
fn drawAffineBackground(self: *Self, comptime n: u3) void {
comptime std.debug.assert(n == 2 or n == 3); // Only BG2 and BG3 can be affine
const char_base = @as(u32, 0x4000) * self.bg[n].cnt.char_base.read();
const screen_base = @as(u32, 0x800) * self.bg[n].cnt.screen_base.read();
const size: u2 = self.bg[n].cnt.size.read();
const tile_width = @as(i32, 0x10) << size;
const px_width = tile_width << 3;
const px_height = px_width;
var aff_x = self.aff.bg[n - 2].x_latch.?;
var aff_y = self.aff.bg[n - 2].y_latch.?;
var i: u32 = 0;
while (i < width) : (i += 1) {
var ix = aff_x >> 8;
var iy = aff_y >> 8;
aff_x += self.aff.bg[n - 2].pa;
aff_y += self.aff.bg[n - 2].pc;
if (self.scanline_buf[@as(usize, i)] != null) continue;
if (self.bg[n].cnt.display_overflow.read()) {
ix = if (ix > px_width) @rem(ix, px_width) else if (ix < 0) px_width + @rem(ix, px_width) else ix;
iy = if (iy > px_height) @rem(iy, px_height) else if (iy < 0) px_height + @rem(iy, px_height) else iy;
} else if (ix > px_width or iy > px_height or ix < 0 or iy < 0) continue;
const x = @bitCast(u32, ix);
const y = @bitCast(u32, iy);
const tile_id: u32 = self.vram.read(u8, screen_base + ((y / 8) * @bitCast(u32, tile_width) + (x / 8)));
const row = y & 7;
const col = x & 7;
const tile_addr = char_base + (tile_id * 0x40) + (row * 0x8) + col;
const pal_id: u16 = self.vram.buf[tile_addr];
if (pal_id != 0) self.scanline_buf[i] = self.palette.read(u16, pal_id * 2);
}
// Update BGxX and BGxY
self.aff.bg[n - 2].x_latch.? += self.aff.bg[n - 2].pb; // PB is added to BGxX
self.aff.bg[n - 2].y_latch.? += self.aff.bg[n - 2].pd; // PD is added to BGxY
}
fn drawBackround(self: *Self, comptime n: u3) void { fn drawBackround(self: *Self, comptime n: u3) void {
// A Tile in a charblock is a byte, while a Screen Entry is a halfword // A Tile in a charblock is a byte, while a Screen Entry is a halfword
const charblock_len: u32 = 0x4000; const charblock_len: u32 = 0x4000;
@ -352,7 +305,7 @@ pub const Ppu = struct {
self.drawSprites(@truncate(u2, layer)); self.drawSprites(@truncate(u2, layer));
if (layer == self.bg[0].cnt.priority.read() and bg_enable & 1 == 1) self.drawBackround(0); if (layer == self.bg[0].cnt.priority.read() and bg_enable & 1 == 1) self.drawBackround(0);
if (layer == self.bg[1].cnt.priority.read() and bg_enable >> 1 & 1 == 1) self.drawBackround(1); if (layer == self.bg[1].cnt.priority.read() and bg_enable >> 1 & 1 == 1) self.drawBackround(1);
if (layer == self.bg[2].cnt.priority.read() and bg_enable >> 2 & 1 == 1) self.drawAffineBackground(2); // TODO: Implement Affine BG2
} }
// Copy Drawn Scanline to Frame Buffer // Copy Drawn Scanline to Frame Buffer
@ -374,8 +327,7 @@ pub const Ppu = struct {
var layer: usize = 0; var layer: usize = 0;
while (layer < 4) : (layer += 1) { while (layer < 4) : (layer += 1) {
self.drawSprites(@truncate(u2, layer)); self.drawSprites(@truncate(u2, layer));
if (layer == self.bg[2].cnt.priority.read() and bg_enable >> 2 & 1 == 1) self.drawAffineBackground(2); // TODO: Implement Affine BG2, BG3
if (layer == self.bg[3].cnt.priority.read() and bg_enable >> 3 & 1 == 1) self.drawAffineBackground(3);
} }
// Copy Drawn Scanline to Frame Buffer // Copy Drawn Scanline to Frame Buffer
@ -506,9 +458,6 @@ pub const Ppu = struct {
cpu.handleInterrupt(); cpu.handleInterrupt();
} }
self.aff.bg[0].latchRefPoints();
self.aff.bg[1].latchRefPoints();
// See if Vblank DMA is present and not enabled // See if Vblank DMA is present and not enabled
pollBlankingDma(&cpu.bus, .VBlank); pollBlankingDma(&cpu.bus, .VBlank);
} }
@ -707,45 +656,33 @@ const AffineBackground = struct {
const AffineBackgroundRegisters = struct { const AffineBackgroundRegisters = struct {
const Self = @This(); const Self = @This();
x: i32, x: io.BackgroundRefPoint,
y: i32, y: io.BackgroundRefPoint,
pa: i16, pa: io.BackgroundRotScaleParam,
pb: i16, pb: io.BackgroundRotScaleParam,
pc: i16, pc: io.BackgroundRotScaleParam,
pd: i16, pd: io.BackgroundRotScaleParam,
x_latch: ?i32,
y_latch: ?i32,
fn init() Self { fn init() Self {
return .{ return .{
.x = 0, .x = .{ .raw = 0 },
.y = 0, .y = .{ .raw = 0 },
.pa = 0, .pa = .{ .raw = 0 },
.pb = 0, .pb = .{ .raw = 0 },
.pc = 0, .pc = .{ .raw = 0 },
.pd = 0, .pd = .{ .raw = 0 },
.x_latch = null,
.y_latch = null,
}; };
} }
pub fn writePaPb(self: *Self, value: u32) void { pub fn writePaPb(self: *Self, value: u32) void {
self.pa = @bitCast(i16, @truncate(u16, value)); self.pa.raw = @truncate(u16, value);
self.pb = @bitCast(i16, @truncate(u16, value >> 16)); self.pb.raw = @truncate(u16, value >> 16);
} }
pub fn writePcPd(self: *Self, value: u32) void { pub fn writePcPd(self: *Self, value: u32) void {
self.pc = @bitCast(i16, @truncate(u16, value)); self.pc.raw = @truncate(u16, value);
self.pd = @bitCast(i16, @truncate(u16, value >> 16)); self.pd.raw = @truncate(u16, value >> 16);
}
// Every Vblank BG?X/Y registers are latched
fn latchRefPoints(self: *Self) void {
self.x_latch = self.x;
self.y_latch = self.y;
} }
}; };