diff --git a/src/emu.zig b/src/emu.zig index 7ec4841..664a349 100644 --- a/src/emu.zig +++ b/src/emu.zig @@ -3,7 +3,7 @@ const std = @import("std"); const Bus = @import("Bus.zig"); const Scheduler = @import("scheduler.zig").Scheduler; const Arm7tdmi = @import("cpu.zig").Arm7tdmi; -const FpsAverage = @import("util.zig").FpsAverage; +const EmulatorFps = @import("util.zig").EmulatorFps; const Timer = std.time.Timer; const Thread = std.Thread; @@ -32,12 +32,12 @@ const RunKind = enum { LimitedBusy, }; -pub fn run(kind: RunKind, quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi) void { +pub fn run(kind: RunKind, quit: *Atomic(bool), fps: *EmulatorFps, sched: *Scheduler, cpu: *Arm7tdmi) void { switch (kind) { - .Unlimited => runUnsync(quit, sched, cpu), - .Limited => runSync(quit, sched, cpu), - .UnlimitedFPS => runUnsyncFps(quit, fps, sched, cpu), - .LimitedFPS => runSyncFps(quit, fps, sched, cpu), + .Unlimited => runUnsynchronized(quit, sched, cpu, null), + .Limited => runSynchronized(quit, sched, cpu, null), + .UnlimitedFPS => runUnsynchronized(quit, sched, cpu, fps), + .LimitedFPS => runSynchronized(quit, sched, cpu, fps), .LimitedBusy => runBusyLoop(quit, sched, cpu), } } @@ -56,57 +56,46 @@ pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void { } } -pub fn runUnsync(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi) void { - log.info("Start unsynchronized emu thread", .{}); - while (!quit.load(.Unordered)) runFrame(sched, cpu); +pub fn runUnsynchronized(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, fps: ?*EmulatorFps) void { + if (fps) |tracker| { + log.info("Start unsynchronized emu thread w/ fps tracking", .{}); + + while (!quit.load(.SeqCst)) { + runFrame(sched, cpu); + tracker.completeFrame(); + } + } else { + log.info("Start unsynchronized emu thread", .{}); + while (!quit.load(.SeqCst)) runFrame(sched, cpu); + } } -pub fn runSync(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi) void { - log.info("Start synchronized emu thread", .{}); +pub fn runSynchronized(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, fps: ?*EmulatorFps) void { var timer = Timer.start() catch unreachable; var wake_time: u64 = frame_period; - while (!quit.load(.Unordered)) { - runFrame(sched, cpu); + if (fps) |tracker| { + log.info("Start synchronized emu thread w/ fps tracking", .{}); - // Put the Thread to Sleep + Backup Spin Loop - // This saves on resource usage when frame limiting - sleep(&timer, &wake_time); - - // Update to the new wake time - wake_time += frame_period; + while (!quit.load(.SeqCst)) { + runSyncCore(sched, cpu, &timer, &wake_time); + tracker.completeFrame(); + } + } else { + log.info("Start synchronized emu thread", .{}); + while (!quit.load(.SeqCst)) runSyncCore(sched, cpu, &timer, &wake_time); } } -pub fn runUnsyncFps(quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi) void { - log.info("Start unsynchronized emu thread w/ fps tracking", .{}); - var fps_timer = Timer.start() catch unreachable; +inline fn runSyncCore(sched: *Scheduler, cpu: *Arm7tdmi, timer: *Timer, wake_time: *u64) void { + runFrame(sched, cpu); - while (!quit.load(.Unordered)) { - runFrame(sched, cpu); - fps.add(fps_timer.lap()); - } -} + // Put the Thread to Sleep + Backup Spin Loop + // This saves on resource usage when frame limiting + sleep(timer, wake_time); -pub fn runSyncFps(quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi) void { - log.info("Start synchronized emu thread w/ fps tracking", .{}); - var timer = Timer.start() catch unreachable; - var fps_timer = Timer.start() catch unreachable; - var wake_time: u64 = frame_period; - - while (!quit.load(.Unordered)) { - runFrame(sched, cpu); - - // Put the Thread to Sleep + Backup Spin Loop - // This saves on resource usage when frame limiting - sleep(&timer, &wake_time); - - // Determine FPS - fps.add(fps_timer.lap()); - - // Update to the new wake time - wake_time += frame_period; - } + // Update to the new wake time + wake_time.* += frame_period; } pub fn runBusyLoop(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi) void { @@ -114,7 +103,7 @@ pub fn runBusyLoop(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi) void var timer = Timer.start() catch unreachable; var wake_time: u64 = frame_period; - while (!quit.load(.Unordered)) { + while (!quit.load(.SeqCst)) { runFrame(sched, cpu); spinLoop(&timer, wake_time); diff --git a/src/main.zig b/src/main.zig index 89657e4..1102d92 100644 --- a/src/main.zig +++ b/src/main.zig @@ -9,7 +9,7 @@ const Bus = @import("Bus.zig"); const Apu = @import("apu.zig").Apu; const Arm7tdmi = @import("cpu.zig").Arm7tdmi; const Scheduler = @import("scheduler.zig").Scheduler; -const FpsAverage = @import("util.zig").FpsAverage; +const EmulatorFps = @import("util.zig").EmulatorFps; const Timer = std.time.Timer; const Thread = std.Thread; @@ -99,10 +99,10 @@ pub fn main() anyerror!void { // Init Atomics var quit = Atomic(bool).init(false); - var emu_rate = FpsAverage.init(); + var emu_rate = EmulatorFps.init(); // Create Emulator Thread - const emu_thread = try Thread.spawn(.{}, emu.run, .{ .UnlimitedFPS, &quit, &emu_rate, &scheduler, &cpu }); + const emu_thread = try Thread.spawn(.{}, emu.run, .{ .LimitedFPS, &quit, &emu_rate, &scheduler, &cpu }); defer emu_thread.join(); var title_buf: [0x20]u8 = std.mem.zeroes([0x20]u8); @@ -123,8 +123,6 @@ pub fn main() anyerror!void { emu_loop: while (true) { var event: SDL.SDL_Event = undefined; while (SDL.SDL_PollEvent(&event) != 0) { - // Pause Emulation Thread during Input Writing - switch (event.type) { SDL.SDL_QUIT => break :emu_loop, SDL.SDL_KEYDOWN => { @@ -168,18 +166,17 @@ pub fn main() anyerror!void { } } - // FIXME: Is it OK just to copy the Emulator's Frame Buffer to SDL? + // Emulator has an internal Double Buffer const buf_ptr = cpu.bus.ppu.framebuf.get(.Renderer).ptr; _ = SDL.SDL_UpdateTexture(texture, null, buf_ptr, framebuf_pitch); _ = SDL.SDL_RenderCopy(renderer, texture, null, null); SDL.SDL_RenderPresent(renderer); - const actual = emu_rate.calc(); - const dyn_title = std.fmt.bufPrint(&dyn_title_buf, "{s} [Emu: {d:.1}fps, {d:0>6.2}%] ", .{ window_title, actual, actual * 100 / expected_rate }) catch unreachable; + const dyn_title = std.fmt.bufPrint(&dyn_title_buf, "{s} [Emu: {}fps] ", .{ window_title, emu_rate.value() }) catch unreachable; SDL.SDL_SetWindowTitle(window, dyn_title.ptr); } - quit.store(true, .Unordered); // Terminate Emulator Thread + quit.store(true, .SeqCst); // Terminate Emulator Thread } const CliError = error{ diff --git a/src/util.zig b/src/util.zig index 1c41edd..742e3e0 100644 --- a/src/util.zig +++ b/src/util.zig @@ -17,30 +17,36 @@ pub inline fn rotr(comptime T: type, x: T, r: anytype) T { return x >> ar | x << (1 +% ~ar); } -pub const FpsAverage = struct { +pub const EmulatorFps = struct { const Self = @This(); - total: u64, - sample_count: u64, + fps: u32, + count: std.atomic.Atomic(u32), + timer: std.time.Timer, pub fn init() Self { - return .{ .total = 0, .sample_count = 1 }; + return .{ + .fps = 0, + .count = std.atomic.Atomic(u32).init(0), + .timer = std.time.Timer.start() catch unreachable, + }; } - pub fn add(self: *Self, sample: u64) void { - if (self.sample_count == 600) return self.reset(sample); - - self.total += sample; - self.sample_count += 1; + // TODO: Rename + pub fn completeFrame(self: *Self) void { + _ = self.count.fetchAdd(1, .Monotonic); } - pub fn calc(self: *const Self) f64 { - return @intToFloat(f64, std.time.ns_per_s) / (@intToFloat(f64, self.total) / @intToFloat(f64, self.sample_count)); - } + pub fn value(self: *Self) u32 { + const expected = @intToFloat(f64, std.time.ns_per_s); + const actual = @intToFloat(f64, self.timer.read()); - fn reset(self: *Self, sample: u64) void { - self.total = sample; - self.sample_count = 1; + if (actual >= expected) { + self.fps = self.count.swap(0, .SeqCst); + self.timer.reset(); + } + + return self.fps; } };