Add a GUI to ZBA #7
|
@ -0,0 +1,298 @@
|
||||||
|
//! Namespace for dealing with ZBA's immediate-mode GUI
|
||||||
|
//! Currently, ZBA uses zgui from https://github.com/michal-z/zig-gamedev
|
||||||
|
//! which provides Zig bindings for https://github.com/ocornut/imgui under the hood
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const zgui = @import("zgui");
|
||||||
|
const gl = @import("gl");
|
||||||
|
const nfd = @import("nfd");
|
||||||
|
const config = @import("config.zig");
|
||||||
|
const emu = @import("core/emu.zig");
|
||||||
|
|
||||||
|
const Gui = @import("platform.zig").Gui;
|
||||||
|
const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
|
||||||
|
const RingBuffer = @import("util.zig").RingBuffer;
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const GLuint = gl.GLuint;
|
||||||
|
|
||||||
|
const gba_width = @import("core/ppu.zig").width;
|
||||||
|
const gba_height = @import("core/ppu.zig").height;
|
||||||
|
|
||||||
|
const log = std.log.scoped(.Imgui);
|
||||||
|
|
||||||
|
// TODO: Document how I decided on this value (I forgot 😅)
|
||||||
|
const histogram_len = 0x400;
|
||||||
|
|
||||||
|
/// Immediate-Mode GUI State
|
||||||
|
pub const State = struct {
|
||||||
|
title: [:0]const u8,
|
||||||
|
|
||||||
|
fps_hist: RingBuffer(u32),
|
||||||
|
should_quit: bool = false,
|
||||||
|
|
||||||
|
pub fn init(allocator: Allocator, title: [12]u8) !@This() {
|
||||||
|
const history = try allocator.alloc(u32, histogram_len);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.title = try allocator.dupeZ(u8, &title),
|
||||||
|
.fps_hist = RingBuffer(u32).init(history),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This(), allocator: Allocator) void {
|
||||||
|
allocator.free(self.title);
|
||||||
|
self.fps_hist.deinit(allocator);
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn draw(state: *State, tex_id: GLuint, cpu: *const Arm7tdmi) void {
|
||||||
|
const win_scale = config.config().host.win_scale;
|
||||||
|
|
||||||
|
{
|
||||||
|
_ = zgui.beginMainMenuBar();
|
||||||
|
defer zgui.endMainMenuBar();
|
||||||
|
|
||||||
|
if (zgui.beginMenu("File", true)) {
|
||||||
|
defer zgui.endMenu();
|
||||||
|
|
||||||
|
if (zgui.menuItem("Quit", .{})) state.should_quit = true;
|
||||||
|
if (zgui.menuItem("Insert ROM", .{})) blk: {
|
||||||
|
const maybe_path = nfd.openFileDialog("gba", null) catch |e| {
|
||||||
|
log.err("failed to open file dialog: {?}", .{e});
|
||||||
|
break :blk;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (maybe_path) |file_path| {
|
||||||
|
defer nfd.freePath(file_path);
|
||||||
|
log.info("user chose: \"{s}\"", .{file_path});
|
||||||
|
|
||||||
|
// emu.loadRom(cpu, file_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zgui.beginMenu("Emulation", true)) {
|
||||||
|
defer zgui.endMenu();
|
||||||
|
|
||||||
|
if (zgui.menuItem("Restart", .{})) log.warn("TODO: Restart Emulator", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const w = @intToFloat(f32, gba_width * win_scale);
|
||||||
|
const h = @intToFloat(f32, gba_height * win_scale);
|
||||||
|
|
||||||
|
_ = zgui.begin(state.title, .{ .flags = .{ .no_resize = true, .always_auto_resize = true } });
|
||||||
|
defer zgui.end();
|
||||||
|
|
||||||
|
zgui.image(@intToPtr(*anyopaque, tex_id), .{ .w = w, .h = h, .uv0 = .{ 0, 1 }, .uv1 = .{ 1, 0 } });
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
_ = zgui.begin("Information", .{});
|
||||||
|
defer zgui.end();
|
||||||
|
|
||||||
|
for (0..8) |i| {
|
||||||
|
zgui.text("R{}: 0x{X:0>8}", .{ i, cpu.r[i] });
|
||||||
|
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
|
||||||
|
const padding = if (8 + i < 10) " " else "";
|
||||||
|
zgui.text("{s}R{}: 0x{X:0>8}", .{ padding, 8 + i, cpu.r[8 + i] });
|
||||||
|
}
|
||||||
|
|
||||||
|
zgui.separator();
|
||||||
|
|
||||||
|
widgets.psr("CPSR", cpu.cpsr);
|
||||||
|
widgets.psr("SPSR", cpu.spsr);
|
||||||
|
|
||||||
|
zgui.separator();
|
||||||
|
|
||||||
|
widgets.interrupts(" IE", cpu.bus.io.ie);
|
||||||
|
widgets.interrupts("IRQ", cpu.bus.io.irq);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
_ = zgui.begin("Performance", .{});
|
||||||
|
defer zgui.end();
|
||||||
|
|
||||||
|
const tmp = blk: {
|
||||||
|
var buf: [0x400]u32 = undefined;
|
||||||
|
const len = state.fps_hist.copy(&buf);
|
||||||
|
|
||||||
|
break :blk .{ buf, len };
|
||||||
|
};
|
||||||
|
const values = tmp[0];
|
||||||
|
const len = tmp[1];
|
||||||
|
|
||||||
|
if (len == values.len) _ = state.fps_hist.pop();
|
||||||
|
|
||||||
|
const sorted = blk: {
|
||||||
|
var buf: @TypeOf(values) = undefined;
|
||||||
|
|
||||||
|
std.mem.copy(u32, buf[0..len], values[0..len]);
|
||||||
|
std.sort.sort(u32, buf[0..len], {}, std.sort.asc(u32));
|
||||||
|
|
||||||
|
break :blk buf;
|
||||||
|
};
|
||||||
|
|
||||||
|
const y_max = 2 * if (len != 0) @intToFloat(f64, sorted[len - 1]) else emu.frame_rate;
|
||||||
|
const x_max = @intToFloat(f64, values.len);
|
||||||
|
|
||||||
|
const y_args = .{ .flags = .{ .no_grid_lines = true } };
|
||||||
|
const x_args = .{ .flags = .{ .no_grid_lines = true, .no_tick_labels = true, .no_tick_marks = true } };
|
||||||
|
|
||||||
|
if (zgui.plot.beginPlot("Emulation FPS", .{ .w = 0.0, .flags = .{ .no_title = true, .no_frame = true } })) {
|
||||||
|
defer zgui.plot.endPlot();
|
||||||
|
|
||||||
|
zgui.plot.setupLegend(.{ .north = true, .east = true }, .{});
|
||||||
|
zgui.plot.setupAxis(.x1, x_args);
|
||||||
|
zgui.plot.setupAxis(.y1, y_args);
|
||||||
|
zgui.plot.setupAxisLimits(.y1, .{ .min = 0.0, .max = y_max, .cond = .always });
|
||||||
|
zgui.plot.setupAxisLimits(.x1, .{ .min = 0.0, .max = x_max, .cond = .always });
|
||||||
|
zgui.plot.setupFinish();
|
||||||
|
|
||||||
|
zgui.plot.plotLineValues("FPS", u32, .{ .v = values[0..len] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const stats: struct { u32, u32, u32 } = blk: {
|
||||||
|
if (len == 0) break :blk .{ 0, 0, 0 };
|
||||||
|
|
||||||
|
const average = average: {
|
||||||
|
var sum: u32 = 0;
|
||||||
|
for (sorted[0..len]) |value| sum += value;
|
||||||
|
|
||||||
|
break :average @intCast(u32, sum / len);
|
||||||
|
};
|
||||||
|
const median = sorted[len / 2];
|
||||||
|
const low = sorted[len / 100]; // 1% Low
|
||||||
|
|
||||||
|
break :blk .{ average, median, low };
|
||||||
|
};
|
||||||
|
|
||||||
|
zgui.text("Average: {:0>3} fps", .{stats[0]});
|
||||||
|
zgui.text(" Median: {:0>3} fps", .{stats[1]});
|
||||||
|
zgui.text(" 1% Low: {:0>3} fps", .{stats[2]});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
_ = zgui.begin("Scheduler", .{});
|
||||||
|
defer zgui.end();
|
||||||
|
|
||||||
|
const scheduler = cpu.sched;
|
||||||
|
|
||||||
|
zgui.text("tick: {X:0>16}", .{scheduler.tick});
|
||||||
|
zgui.separator();
|
||||||
|
|
||||||
|
const Event = std.meta.Child(@TypeOf(scheduler.queue.items));
|
||||||
|
|
||||||
|
var items: [20]Event = undefined;
|
||||||
|
const len = scheduler.queue.len;
|
||||||
|
|
||||||
|
std.mem.copy(Event, &items, scheduler.queue.items);
|
||||||
|
std.sort.sort(Event, items[0..len], {}, widgets.eventDesc(Event));
|
||||||
|
|
||||||
|
for (items[0..len]) |event| {
|
||||||
|
zgui.text("{X:0>16} | {?}", .{ event.tick, event.kind });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// {
|
||||||
|
// zgui.showDemoWindow(null);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
const widgets = struct {
|
||||||
|
fn interrupts(comptime label: []const u8, int: anytype) void {
|
||||||
|
const h = 15.0;
|
||||||
|
const w = 9.0 * 2 + 3.5;
|
||||||
|
const ww = 9.0 * 3;
|
||||||
|
|
||||||
|
{
|
||||||
|
zgui.text(label ++ ":", .{});
|
||||||
|
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
_ = zgui.selectable("VBL", .{ .w = w, .h = h, .selected = int.vblank.read() });
|
||||||
|
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
_ = zgui.selectable("HBL", .{ .w = w, .h = h, .selected = int.hblank.read() });
|
||||||
|
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
_ = zgui.selectable("VCT", .{ .w = w, .h = h, .selected = int.coincidence.read() });
|
||||||
|
|
||||||
|
{
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
_ = zgui.selectable("TIM0", .{ .w = ww, .h = h, .selected = int.tim0.read() });
|
||||||
|
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
_ = zgui.selectable("TIM1", .{ .w = ww, .h = h, .selected = int.tim1.read() });
|
||||||
|
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
_ = zgui.selectable("TIM2", .{ .w = ww, .h = h, .selected = int.tim2.read() });
|
||||||
|
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
_ = zgui.selectable("TIM3", .{ .w = ww, .h = h, .selected = int.tim3.read() });
|
||||||
|
}
|
||||||
|
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
_ = zgui.selectable("SRL", .{ .w = w, .h = h, .selected = int.serial.read() });
|
||||||
|
|
||||||
|
{
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
_ = zgui.selectable("DMA0", .{ .w = ww, .h = h, .selected = int.dma0.read() });
|
||||||
|
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
_ = zgui.selectable("DMA1", .{ .w = ww, .h = h, .selected = int.dma1.read() });
|
||||||
|
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
_ = zgui.selectable("DMA2", .{ .w = ww, .h = h, .selected = int.dma2.read() });
|
||||||
|
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
_ = zgui.selectable("DMA3", .{ .w = ww, .h = h, .selected = int.dma3.read() });
|
||||||
|
}
|
||||||
|
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
_ = zgui.selectable("KPD", .{ .w = w, .h = h, .selected = int.keypad.read() });
|
||||||
|
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
_ = zgui.selectable("GPK", .{ .w = w, .h = h, .selected = int.game_pak.read() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn psr(comptime label: []const u8, register: anytype) void {
|
||||||
|
const Mode = @import("core/cpu.zig").Mode;
|
||||||
|
|
||||||
|
const maybe_mode = std.meta.intToEnum(Mode, register.mode.read()) catch null;
|
||||||
|
const mode = if (maybe_mode) |mode| mode.toString() else "???";
|
||||||
|
const w = 9.0;
|
||||||
|
const h = 15.0;
|
||||||
|
|
||||||
|
zgui.text(label ++ ": 0x{X:0>8}", .{register.raw});
|
||||||
|
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
_ = zgui.selectable("N", .{ .w = w, .h = h, .selected = register.n.read() });
|
||||||
|
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
_ = zgui.selectable("Z", .{ .w = w, .h = h, .selected = register.z.read() });
|
||||||
|
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
_ = zgui.selectable("C", .{ .w = w, .h = h, .selected = register.c.read() });
|
||||||
|
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
_ = zgui.selectable("V", .{ .w = w, .h = h, .selected = register.v.read() });
|
||||||
|
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
zgui.text("{s}", .{mode});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eventDesc(comptime T: type) fn (void, T, T) bool {
|
||||||
|
return struct {
|
||||||
|
fn inner(_: void, left: T, right: T) bool {
|
||||||
|
return left.tick > right.tick;
|
||||||
|
}
|
||||||
|
}.inner;
|
||||||
|
}
|
||||||
|
};
|
279
src/platform.zig
279
src/platform.zig
|
@ -2,10 +2,10 @@ const std = @import("std");
|
||||||
const SDL = @import("sdl2");
|
const SDL = @import("sdl2");
|
||||||
const gl = @import("gl");
|
const gl = @import("gl");
|
||||||
const zgui = @import("zgui");
|
const zgui = @import("zgui");
|
||||||
const nfd = @import("nfd");
|
|
||||||
|
|
||||||
const emu = @import("core/emu.zig");
|
const emu = @import("core/emu.zig");
|
||||||
const config = @import("config.zig");
|
const config = @import("config.zig");
|
||||||
|
const imgui = @import("imgui.zig");
|
||||||
|
|
||||||
const Apu = @import("core/apu.zig").Apu;
|
const Apu = @import("core/apu.zig").Apu;
|
||||||
const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
|
const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
|
||||||
|
@ -27,27 +27,12 @@ const height = 720;
|
||||||
pub const sample_rate = 1 << 15;
|
pub const sample_rate = 1 << 15;
|
||||||
pub const sample_format = SDL.AUDIO_U16;
|
pub const sample_format = SDL.AUDIO_U16;
|
||||||
|
|
||||||
const default_title = "ZBA";
|
const window_title = "ZBA";
|
||||||
|
|
||||||
pub const Gui = struct {
|
pub const Gui = struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
const log = std.log.scoped(.Gui);
|
const log = std.log.scoped(.Gui);
|
||||||
|
|
||||||
const State = struct {
|
|
||||||
fps_hist: RingBuffer(u32),
|
|
||||||
should_quit: bool = false,
|
|
||||||
|
|
||||||
pub fn init(allocator: Allocator) !@This() {
|
|
||||||
const history = try allocator.alloc(u32, 0x400);
|
|
||||||
return .{ .fps_hist = RingBuffer(u32).init(history) };
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deinit(self: *@This(), allocator: Allocator) void {
|
|
||||||
self.fps_hist.deinit(allocator);
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// zig fmt: off
|
// zig fmt: off
|
||||||
const vertices: [32]f32 = [_]f32{
|
const vertices: [32]f32 = [_]f32{
|
||||||
// Positions // Colours // Texture Coords
|
// Positions // Colours // Texture Coords
|
||||||
|
@ -65,10 +50,9 @@ pub const Gui = struct {
|
||||||
|
|
||||||
window: *SDL.SDL_Window,
|
window: *SDL.SDL_Window,
|
||||||
ctx: SDL_GLContext,
|
ctx: SDL_GLContext,
|
||||||
title: [:0]const u8,
|
|
||||||
audio: Audio,
|
audio: Audio,
|
||||||
|
|
||||||
state: State,
|
state: imgui.State,
|
||||||
|
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
program_id: gl.GLuint,
|
program_id: gl.GLuint,
|
||||||
|
@ -80,7 +64,7 @@ pub const Gui = struct {
|
||||||
if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_MAJOR_VERSION, 3) < 0) panic();
|
if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_MAJOR_VERSION, 3) < 0) panic();
|
||||||
|
|
||||||
const window = SDL.SDL_CreateWindow(
|
const window = SDL.SDL_CreateWindow(
|
||||||
default_title,
|
window_title,
|
||||||
SDL.SDL_WINDOWPOS_CENTERED,
|
SDL.SDL_WINDOWPOS_CENTERED,
|
||||||
SDL.SDL_WINDOWPOS_CENTERED,
|
SDL.SDL_WINDOWPOS_CENTERED,
|
||||||
width,
|
width,
|
||||||
|
@ -102,13 +86,12 @@ pub const Gui = struct {
|
||||||
|
|
||||||
return Self{
|
return Self{
|
||||||
.window = window,
|
.window = window,
|
||||||
.title = try allocator.dupeZ(u8, &title),
|
|
||||||
.ctx = ctx,
|
.ctx = ctx,
|
||||||
.program_id = try compileShaders(),
|
.program_id = try compileShaders(),
|
||||||
.audio = Audio.init(apu),
|
.audio = Audio.init(apu),
|
||||||
|
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.state = try State.init(allocator),
|
.state = try imgui.State.init(allocator, title),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +108,6 @@ pub const Gui = struct {
|
||||||
SDL.SDL_DestroyWindow(self.window);
|
SDL.SDL_DestroyWindow(self.window);
|
||||||
SDL.SDL_Quit();
|
SDL.SDL_Quit();
|
||||||
|
|
||||||
self.allocator.free(self.title);
|
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,163 +242,6 @@ pub const Gui = struct {
|
||||||
return fbo_id;
|
return fbo_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(self: *Self, tex_id: GLuint, cpu: *const Arm7tdmi) void {
|
|
||||||
const win_scale = config.config().host.win_scale;
|
|
||||||
|
|
||||||
{
|
|
||||||
_ = zgui.beginMainMenuBar();
|
|
||||||
defer zgui.endMainMenuBar();
|
|
||||||
|
|
||||||
if (zgui.beginMenu("File", true)) {
|
|
||||||
defer zgui.endMenu();
|
|
||||||
|
|
||||||
if (zgui.menuItem("Quit", .{})) self.state.should_quit = true;
|
|
||||||
if (zgui.menuItem("Insert ROM", .{})) blk: {
|
|
||||||
const maybe_path = nfd.openFileDialog("gba", null) catch |e| {
|
|
||||||
log.err("failed to open file dialog: {?}", .{e});
|
|
||||||
break :blk;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (maybe_path) |file_path| {
|
|
||||||
defer nfd.freePath(file_path);
|
|
||||||
log.info("user chose: \"{s}\"", .{file_path});
|
|
||||||
|
|
||||||
// emu.loadRom(cpu, file_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (zgui.beginMenu("Emulation", true)) {
|
|
||||||
defer zgui.endMenu();
|
|
||||||
|
|
||||||
if (zgui.menuItem("Restart", .{})) log.warn("TODO: Restart Emulator", .{});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const w = @intToFloat(f32, gba_width * win_scale);
|
|
||||||
const h = @intToFloat(f32, gba_height * win_scale);
|
|
||||||
|
|
||||||
_ = zgui.begin(self.title, .{ .flags = .{ .no_resize = true, .always_auto_resize = true } });
|
|
||||||
defer zgui.end();
|
|
||||||
|
|
||||||
zgui.image(@intToPtr(*anyopaque, tex_id), .{ .w = w, .h = h, .uv0 = .{ 0, 1 }, .uv1 = .{ 1, 0 } });
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
_ = zgui.begin("Information", .{});
|
|
||||||
defer zgui.end();
|
|
||||||
|
|
||||||
for (0..8) |i| {
|
|
||||||
zgui.text("R{}: 0x{X:0>8}", .{ i, cpu.r[i] });
|
|
||||||
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
|
|
||||||
const prefix = if (8 + i < 10) " " else "";
|
|
||||||
zgui.text("{s}R{}: 0x{X:0>8}", .{ prefix, 8 + i, cpu.r[8 + i] });
|
|
||||||
}
|
|
||||||
|
|
||||||
zgui.separator();
|
|
||||||
|
|
||||||
widgets.psr("CPSR", cpu.cpsr);
|
|
||||||
widgets.psr("SPSR", cpu.spsr);
|
|
||||||
|
|
||||||
zgui.separator();
|
|
||||||
|
|
||||||
widgets.interrupts(" IE", cpu.bus.io.ie); // space is for padding
|
|
||||||
widgets.interrupts("IRQ", cpu.bus.io.irq);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
_ = zgui.begin("Performance", .{});
|
|
||||||
defer zgui.end();
|
|
||||||
|
|
||||||
const tmp = blk: {
|
|
||||||
var buf: [0x400]u32 = undefined;
|
|
||||||
const len = self.state.fps_hist.copy(&buf);
|
|
||||||
|
|
||||||
break :blk .{ buf, len };
|
|
||||||
};
|
|
||||||
const values = tmp[0];
|
|
||||||
const len = tmp[1];
|
|
||||||
|
|
||||||
if (len == values.len) _ = self.state.fps_hist.pop();
|
|
||||||
|
|
||||||
const sorted = blk: {
|
|
||||||
var buf: @TypeOf(values) = undefined;
|
|
||||||
|
|
||||||
std.mem.copy(u32, buf[0..len], values[0..len]);
|
|
||||||
std.sort.sort(u32, buf[0..len], {}, std.sort.asc(u32));
|
|
||||||
|
|
||||||
break :blk buf;
|
|
||||||
};
|
|
||||||
|
|
||||||
const y_max = 2 * if (len != 0) @intToFloat(f64, sorted[len - 1]) else emu.frame_rate;
|
|
||||||
const x_max = @intToFloat(f64, values.len);
|
|
||||||
|
|
||||||
const y_args = .{ .flags = .{ .no_grid_lines = true } };
|
|
||||||
const x_args = .{ .flags = .{ .no_grid_lines = true, .no_tick_labels = true, .no_tick_marks = true } };
|
|
||||||
|
|
||||||
if (zgui.plot.beginPlot("Emulation FPS", .{ .w = 0.0, .flags = .{ .no_title = true, .no_frame = true } })) {
|
|
||||||
defer zgui.plot.endPlot();
|
|
||||||
|
|
||||||
zgui.plot.setupLegend(.{ .north = true, .east = true }, .{});
|
|
||||||
zgui.plot.setupAxis(.x1, x_args);
|
|
||||||
zgui.plot.setupAxis(.y1, y_args);
|
|
||||||
zgui.plot.setupAxisLimits(.y1, .{ .min = 0.0, .max = y_max, .cond = .always });
|
|
||||||
zgui.plot.setupAxisLimits(.x1, .{ .min = 0.0, .max = x_max, .cond = .always });
|
|
||||||
zgui.plot.setupFinish();
|
|
||||||
|
|
||||||
zgui.plot.plotLineValues("FPS", u32, .{ .v = values[0..len] });
|
|
||||||
}
|
|
||||||
|
|
||||||
const stats: struct { u32, u32, u32 } = blk: {
|
|
||||||
if (len == 0) break :blk .{ 0, 0, 0 };
|
|
||||||
|
|
||||||
const average = average: {
|
|
||||||
var sum: u32 = 0;
|
|
||||||
for (sorted[0..len]) |value| sum += value;
|
|
||||||
|
|
||||||
break :average @intCast(u32, sum / len);
|
|
||||||
};
|
|
||||||
const median = sorted[len / 2];
|
|
||||||
const low = sorted[len / 100]; // 1% Low
|
|
||||||
|
|
||||||
break :blk .{ average, median, low };
|
|
||||||
};
|
|
||||||
|
|
||||||
zgui.text("Average: {:0>3} fps", .{stats[0]});
|
|
||||||
zgui.text(" Median: {:0>3} fps", .{stats[1]});
|
|
||||||
zgui.text(" 1% Low: {:0>3} fps", .{stats[2]});
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
_ = zgui.begin("Scheduler", .{});
|
|
||||||
defer zgui.end();
|
|
||||||
|
|
||||||
const scheduler = cpu.sched;
|
|
||||||
|
|
||||||
zgui.text("tick: {X:0>16}", .{scheduler.tick});
|
|
||||||
zgui.separator();
|
|
||||||
|
|
||||||
const Event = std.meta.Child(@TypeOf(scheduler.queue.items));
|
|
||||||
|
|
||||||
var items: [20]Event = undefined;
|
|
||||||
const len = scheduler.queue.len;
|
|
||||||
|
|
||||||
std.mem.copy(Event, &items, scheduler.queue.items);
|
|
||||||
std.sort.sort(Event, items[0..len], {}, widgets.eventDesc(Event));
|
|
||||||
|
|
||||||
for (items[0..len]) |event| {
|
|
||||||
zgui.text("{X:0>16} | {?}", .{ event.tick, event.kind });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// {
|
|
||||||
// zgui.showDemoWindow(null);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
const RunOptions = struct {
|
const RunOptions = struct {
|
||||||
quit: *std.atomic.Atomic(bool),
|
quit: *std.atomic.Atomic(bool),
|
||||||
pause: ?*std.atomic.Atomic(bool) = null,
|
pause: ?*std.atomic.Atomic(bool) = null,
|
||||||
|
@ -529,7 +354,7 @@ pub const Gui = struct {
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
zgui.backend.newFrame(width, height);
|
zgui.backend.newFrame(width, height);
|
||||||
self.draw(out_tex, cpu);
|
imgui.draw(&self.state, out_tex, cpu);
|
||||||
zgui.backend.draw();
|
zgui.backend.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -614,95 +439,3 @@ fn panic() noreturn {
|
||||||
const str = @as(?[*:0]const u8, SDL.SDL_GetError()) orelse "unknown error";
|
const str = @as(?[*:0]const u8, SDL.SDL_GetError()) orelse "unknown error";
|
||||||
@panic(std.mem.sliceTo(str, 0));
|
@panic(std.mem.sliceTo(str, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
const widgets = struct {
|
|
||||||
fn interrupts(comptime label: []const u8, int: anytype) void {
|
|
||||||
const h = 15.0;
|
|
||||||
const w = 9.0 * 2 + 3.5;
|
|
||||||
const ww = 9.0 * 3;
|
|
||||||
|
|
||||||
{
|
|
||||||
zgui.text(label ++ ":", .{});
|
|
||||||
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
_ = zgui.selectable("VBL", .{ .w = w, .h = h, .selected = int.vblank.read() });
|
|
||||||
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
_ = zgui.selectable("HBL", .{ .w = w, .h = h, .selected = int.hblank.read() });
|
|
||||||
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
_ = zgui.selectable("VCT", .{ .w = w, .h = h, .selected = int.coincidence.read() });
|
|
||||||
|
|
||||||
{
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
_ = zgui.selectable("TIM0", .{ .w = ww, .h = h, .selected = int.tim0.read() });
|
|
||||||
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
_ = zgui.selectable("TIM1", .{ .w = ww, .h = h, .selected = int.tim1.read() });
|
|
||||||
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
_ = zgui.selectable("TIM2", .{ .w = ww, .h = h, .selected = int.tim2.read() });
|
|
||||||
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
_ = zgui.selectable("TIM3", .{ .w = ww, .h = h, .selected = int.tim3.read() });
|
|
||||||
}
|
|
||||||
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
_ = zgui.selectable("SRL", .{ .w = w, .h = h, .selected = int.serial.read() });
|
|
||||||
|
|
||||||
{
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
_ = zgui.selectable("DMA0", .{ .w = ww, .h = h, .selected = int.dma0.read() });
|
|
||||||
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
_ = zgui.selectable("DMA1", .{ .w = ww, .h = h, .selected = int.dma1.read() });
|
|
||||||
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
_ = zgui.selectable("DMA2", .{ .w = ww, .h = h, .selected = int.dma2.read() });
|
|
||||||
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
_ = zgui.selectable("DMA3", .{ .w = ww, .h = h, .selected = int.dma3.read() });
|
|
||||||
}
|
|
||||||
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
_ = zgui.selectable("KPD", .{ .w = w, .h = h, .selected = int.keypad.read() });
|
|
||||||
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
_ = zgui.selectable("GPK", .{ .w = w, .h = h, .selected = int.game_pak.read() });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn psr(comptime label: []const u8, register: anytype) void {
|
|
||||||
const Mode = @import("core/cpu.zig").Mode;
|
|
||||||
|
|
||||||
const maybe_mode = std.meta.intToEnum(Mode, register.mode.read()) catch null;
|
|
||||||
const mode = if (maybe_mode) |mode| mode.toString() else "???";
|
|
||||||
const w = 9.0;
|
|
||||||
const h = 15.0;
|
|
||||||
|
|
||||||
zgui.text(label ++ ": 0x{X:0>8}", .{register.raw});
|
|
||||||
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
_ = zgui.selectable("N", .{ .w = w, .h = h, .selected = register.n.read() });
|
|
||||||
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
_ = zgui.selectable("Z", .{ .w = w, .h = h, .selected = register.z.read() });
|
|
||||||
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
_ = zgui.selectable("C", .{ .w = w, .h = h, .selected = register.c.read() });
|
|
||||||
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
_ = zgui.selectable("V", .{ .w = w, .h = h, .selected = register.v.read() });
|
|
||||||
|
|
||||||
zgui.sameLine(.{});
|
|
||||||
zgui.text("{s}", .{mode});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eventDesc(comptime T: type) fn (void, T, T) bool {
|
|
||||||
return struct {
|
|
||||||
fn inner(_: void, left: T, right: T) bool {
|
|
||||||
return left.tick > right.tick;
|
|
||||||
}
|
|
||||||
}.inner;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
Loading…
Reference in New Issue