Compare commits

...

4 Commits

10 changed files with 63 additions and 145 deletions

3
.gitmodules vendored
View File

@ -4,3 +4,6 @@
[submodule "lib/zgui"]
path = lib/zgui
url = https://git.musuka.dev/paoda/zgui
[submodule "lib/zba-gdbstub"]
path = lib/zba-gdbstub
url = https://git.musuka.dev/paoda/zba-gdbstub

View File

@ -3,6 +3,7 @@ const builtin = @import("builtin");
const Sdk = @import("lib/SDL.zig/Sdk.zig");
const zgui = @import("lib/zgui/build.zig");
const gdbstub = @import("lib/zba-gdbstub/build.zig");
pub fn build(b: *std.Build) void {
// Minimum Zig Version
@ -26,11 +27,12 @@ pub fn build(b: *std.Build) void {
exe.addModule("known_folders", b.dependency("known-folders", .{}).module("known-folders")); // https://github.com/ziglibs/known-folders
exe.addModule("datetime", b.dependency("zig-datetime", .{}).module("zig-datetime")); // https://github.com/frmdstryr/zig-datetime
exe.addModule("clap", b.dependency("zig-clap", .{}).module("clap")); // https://github.com/Hejsil/zig-clap
exe.addModule("gdbstub", b.dependency("zba-gdbstub", .{}).module("gdbstub")); // https://git.musuka.dev/paoda/zba-gdbstub
exe.addModule("zba-util", b.dependency("zba-util", .{}).module("zba-util")); // https://git.musuka.dev/paoda/zba-util
exe.addModule("tomlz", b.dependency("tomlz", .{}).module("tomlz")); // https://github.com/mattyhall/tomlz
exe.addModule("arm32", b.dependency("arm32", .{}).module("arm32")); // https://git.musuka.dev/paoda/arm32
exe.addModule("gdbstub", gdbstub.module(b)); // https://git.musuka.dev/paoda/gdbstub
// https://github.com/fabioarnold/nfd-zig
const nfd_dep = b.dependency("nfd", .{ .target = target, .optimize = optimize });
exe.linkLibrary(nfd_dep.artifact("nfd"));

View File

@ -18,10 +18,6 @@
.url = "https://github.com/Hejsil/zig-clap/archive/f49b94700e0761b7514abdca0e4f0e7f3f938a93.tar.gz",
.hash = "1220f48518ce22882e102255ed3bcdb7aeeb4891f50b2cdd3bd74b5b2e24d3149ba2",
},
.@"zba-gdbstub" = .{
.url = "https://git.musuka.dev/paoda/zba-gdbstub/archive/d3655e8f61a4943edfce40629dd2a82f9c7bf21c.tar.gz",
.hash = "122076f8e22bb55b555c9812d72d908419eda1bf07b6bb7a1f6d9ecb2bb2a49c56d0",
},
.@"zba-util" = .{
.url = "https://git.musuka.dev/paoda/zba-util/archive/322c798e384a0d24cc84ffcfa2e4a3ca807798a0.tar.gz",
.hash = "12209ce0e729460b997706e47a53a32f1842672cd120189e612f4871731780a30ed0",

1
lib/zba-gdbstub Submodule

@ -0,0 +1 @@
Subproject commit 479319e7cad78c3fd38b6865c56e9fe9e78d495c

View File

@ -17,13 +17,10 @@ const Timer = std.time.Timer;
pub const Synchro = struct {
const AtomicBool = std.atomic.Atomic(bool);
// UI -> Emulator
ui_busy: *AtomicBool,
paused: *AtomicBool, // FIXME: can ui_busy and paused be the same?
should_quit: *AtomicBool,
paused: AtomicBool = AtomicBool.init(true), // FIXME: can ui_busy and paused be the same?
should_quit: AtomicBool = AtomicBool.init(false),
// Emulator -> UI
did_pause: *AtomicBool,
emu_access: std.Thread.Mutex = .{},
};
/// 4 Cycles in 1 dot
@ -52,7 +49,7 @@ const RunKind = enum {
LimitedFPS,
};
pub fn run(cpu: *Arm7tdmi, scheduler: *Scheduler, tracker: *Tracker, sync: Synchro) void {
pub fn run(cpu: *Arm7tdmi, scheduler: *Scheduler, tracker: *Tracker, sync: *Synchro) void {
const audio_sync = config.config().guest.audio_sync and !config.config().host.mute;
if (audio_sync) log.info("Audio sync enabled", .{});
@ -63,7 +60,7 @@ pub fn run(cpu: *Arm7tdmi, scheduler: *Scheduler, tracker: *Tracker, sync: Synch
}
}
fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *Scheduler, tracker: ?*Tracker, sync: Synchro) void {
fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *Scheduler, tracker: ?*Tracker, sync: *Synchro) void {
if (kind == .UnlimitedFPS or kind == .LimitedFPS) {
std.debug.assert(tracker != null);
log.info("FPS tracking enabled", .{});
@ -71,18 +68,16 @@ fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *S
const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr));
// FIXME: audioSync accesses emulator state without any guarantees
switch (kind) {
.Unlimited, .UnlimitedFPS => {
log.info("Emulation w/out video sync", .{});
while (!sync.should_quit.load(.Monotonic)) {
if (sync.ui_busy.load(.Monotonic) or sync.paused.load(.Monotonic)) {
sync.did_pause.store(true, .Monotonic);
if (sync.paused.load(.Monotonic)) continue;
continue;
}
runFrame(scheduler, cpu);
runFrame(sync, scheduler, cpu);
audioSync(audio_sync, bus_ptr.apu.stream, &bus_ptr.apu.is_buffer_full);
if (kind == .UnlimitedFPS) tracker.?.tick();
@ -94,13 +89,9 @@ fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *S
var wake_time: u64 = frame_period;
while (!sync.should_quit.load(.Monotonic)) {
if (sync.ui_busy.load(.Monotonic) or sync.paused.load(.Monotonic)) {
sync.did_pause.store(true, .Release);
if (sync.paused.load(.Monotonic)) continue;
continue;
}
runFrame(scheduler, cpu);
runFrame(sync, scheduler, cpu);
const new_wake_time = videoSync(&timer, wake_time);
// Spin to make up the difference of OS scheduler innacuracies
@ -118,7 +109,10 @@ fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *S
}
}
pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void {
pub fn runFrame(sync: *Synchro, sched: *Scheduler, cpu: *Arm7tdmi) void {
sync.emu_access.lock();
defer sync.emu_access.unlock();
const frame_end = sched.tick + cycles_per_frame;
while (sched.tick < frame_end) {

View File

@ -175,7 +175,7 @@ pub fn draw(state: *State, win_dim: Dimensions, tex_id: GLuint, cpu: *Arm7tdmi)
_ = zgui.begin(window_title, .{ .flags = .{ .no_resize = true, .always_auto_resize = true } });
defer zgui.end();
zgui.image(@ptrFromInt(tex_id), .{ .w = w, .h = h, .uv0 = .{ 0, 1 }, .uv1 = .{ 1, 0 } });
zgui.image(@ptrFromInt(tex_id), .{ .w = w, .h = h });
}
// TODO: Any other steps to respect the copyright of the libraries I use?

View File

@ -119,16 +119,7 @@ pub fn main() void {
var gui = Gui.init(allocator, &bus.apu, title_ptr) catch |e| exitln("failed to init gui: {}", .{e});
defer gui.deinit();
var sync: Synchro = blk: {
const AtomicBool = std.atomic.Atomic(bool);
var ui_busy = AtomicBool.init(false);
var paused = AtomicBool.init(true);
var should_quit = AtomicBool.init(false);
var did_pause = AtomicBool.init(false);
break :blk .{ .ui_busy = &ui_busy, .paused = &paused, .should_quit = &should_quit, .did_pause = &did_pause };
};
var sync: Synchro = .{};
if (result.args.gdb != 0) {
const Server = @import("gdbstub").Server;
@ -145,25 +136,25 @@ pub fn main() void {
log.info("Starting GDB Server Thread", .{});
const thread = std.Thread.spawn(.{}, Server.run, .{ &server, allocator, sync.should_quit }) catch |e| exitln("gdb server thread crashed: {}", .{e});
const thread = std.Thread.spawn(.{}, Server.run, .{ &server, allocator, &sync.should_quit }) catch |e| exitln("gdb server thread crashed: {}", .{e});
defer thread.join();
gui.run(.{
.cpu = &cpu,
.scheduler = &scheduler,
.sync = sync,
.sync = &sync,
}) catch |e| exitln("main thread panicked: {}", .{e});
} else {
var tracker = FpsTracker.init();
const thread = std.Thread.spawn(.{}, emu.run, .{ &cpu, &scheduler, &tracker, sync }) catch |e| exitln("emu thread panicked: {}", .{e});
const thread = std.Thread.spawn(.{}, emu.run, .{ &cpu, &scheduler, &tracker, &sync }) catch |e| exitln("emu thread panicked: {}", .{e});
defer thread.join();
gui.run(.{
.cpu = &cpu,
.scheduler = &scheduler,
.tracker = &tracker,
.sync = sync,
.sync = &sync,
}) catch |e| exitln("main thread panicked: {}", .{e});
}
}

View File

@ -95,7 +95,7 @@ pub const Gui = struct {
}
const RunOptions = struct {
sync: Synchro,
sync: *Synchro,
tracker: ?*FpsTracker = null,
cpu: *Arm7tdmi,
scheduler: *Scheduler,
@ -108,18 +108,17 @@ pub const Gui = struct {
const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr));
const objects = opengl_impl.createObjects();
defer gl.deleteBuffers(3, @as(*const [3]GLuint, &.{ objects.vao, objects.vbo, objects.ebo }));
const vao_id = opengl_impl.vao();
defer gl.deleteVertexArrays(1, &[_]GLuint{vao_id});
const emu_tex = opengl_impl.createScreenTexture(bus_ptr.ppu.framebuf.get(.Renderer));
const out_tex = opengl_impl.createOutputTexture();
const emu_tex = opengl_impl.screenTex(bus_ptr.ppu.framebuf.get(.Renderer));
const out_tex = opengl_impl.outTex();
defer gl.deleteTextures(2, &[_]GLuint{ emu_tex, out_tex });
const fbo_id = try opengl_impl.createFrameBuffer(out_tex);
const fbo_id = try opengl_impl.frameBuffer(out_tex);
defer gl.deleteFramebuffers(1, &fbo_id);
// TODO: Support dynamically switching shaders?
const prog_id = try opengl_impl.compileShaders();
const prog_id = try opengl_impl.program(); // Dynamic Shaders?
defer gl.deleteProgram(prog_id);
var win_dim: Dimensions = default_dim;
@ -127,7 +126,7 @@ pub const Gui = struct {
emu_loop: while (true) {
// Outside of `SDL.SDL_QUIT` below, the DearImgui UI might signal that the program
// should exit, in which case we should also handle this
if (self.state.should_quit) break :emu_loop;
if (self.state.should_quit or sync.should_quit.load(.Monotonic)) break :emu_loop;
var event: SDL.SDL_Event = undefined;
while (SDL.SDL_PollEvent(&event) != 0) {
@ -207,21 +206,9 @@ pub const Gui = struct {
self.state.emulation = .Inactive;
},
},
.Active => blk: {
sync.ui_busy.store(true, .Monotonic);
defer sync.ui_busy.store(false, .Monotonic);
// spin until we know the emu is paused :)
const timeout = 0x100000;
wait_loop: for (0..timeout) |i| {
const ret = sync.did_pause.compareAndSwap(true, false, .Acquire, .Monotonic);
if (ret == null) break :wait_loop;
if (i == timeout - 1) break :blk;
}
// while (sync.did_pause.compareAndSwap(true, false, .Acquire, .Acquire) != null) std.atomic.spinLoopHint();
.Active => {
sync.emu_access.lock();
defer sync.emu_access.unlock();
// Add FPS count to the histogram
if (tracker) |t| self.state.fps_hist.push(t.value()) catch {};
@ -231,9 +218,8 @@ pub const Gui = struct {
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo_id);
defer gl.bindFramebuffer(gl.FRAMEBUFFER, 0);
const buf = bus_ptr.ppu.framebuf.get(.Renderer);
gl.viewport(0, 0, gba_width, gba_height);
opengl_impl.drawScreenTexture(emu_tex, prog_id, objects, buf);
opengl_impl.drawScreen(emu_tex, prog_id, vao_id, bus_ptr.ppu.framebuf.get(.Renderer));
}
// FIXME: We only really care about locking the audio device (and therefore writing silence)
@ -312,44 +298,24 @@ fn panic() noreturn {
}
const opengl_impl = struct {
// zig fmt: off
const vertices: [32]f32 = [_]f32{
// Positions // Colours // Texture Coords
1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, // Top Right
1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, // Bottom Right
-1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, // Bottom Left
-1.0, -1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, // Top Left
};
const indices: [6]u32 = [_]u32{
0, 1, 3, // First Triangle
1, 2, 3, // Second Triangle
};
// zig fmt: on
const Objects = struct { vao: GLuint, vbo: GLuint, ebo: GLuint };
fn drawScreenTexture(tex_id: GLuint, prog_id: GLuint, ids: Objects, buf: []const u8) void {
fn drawScreen(tex_id: GLuint, prog_id: GLuint, vao_id: GLuint, buf: []const u8) void {
gl.bindTexture(gl.TEXTURE_2D, tex_id);
defer gl.bindTexture(gl.TEXTURE_2D, 0);
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gba_width, gba_height, gl.RGBA, gl.UNSIGNED_INT_8_8_8_8, buf.ptr);
// Bind VAO, EBO. VBO not bound
gl.bindVertexArray(ids.vao); // VAO
// Bind VAO
gl.bindVertexArray(vao_id);
defer gl.bindVertexArray(0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ids.ebo); // EBO
defer gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0);
// Use compiled frag + vertex shader
gl.useProgram(prog_id);
defer gl.useProgram(0);
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, null);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 3);
}
fn compileShaders() !GLuint {
fn program() !GLuint {
const vert_shader = @embedFile("shader/pixelbuf.vert");
const frag_shader = @embedFile("shader/pixelbuf.frag");
@ -369,50 +335,22 @@ const opengl_impl = struct {
if (!shader.didCompile(fs)) return error.FragmentCompileError;
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
const prog = gl.createProgram();
gl.attachShader(prog, vs);
gl.attachShader(prog, fs);
gl.linkProgram(prog);
return program;
return prog;
}
// Returns the VAO ID since it's used in run()
fn createObjects() Objects {
fn vao() GLuint {
var vao_id: GLuint = undefined;
var vbo_id: GLuint = undefined;
var ebo_id: GLuint = undefined;
gl.genVertexArrays(1, &vao_id);
gl.genBuffers(1, &vbo_id);
gl.genBuffers(1, &ebo_id);
gl.bindVertexArray(vao_id);
defer gl.bindVertexArray(0);
gl.bindBuffer(gl.ARRAY_BUFFER, vbo_id);
defer gl.bindBuffer(gl.ARRAY_BUFFER, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo_id);
defer gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0);
gl.bufferData(gl.ARRAY_BUFFER, @sizeOf(@TypeOf(vertices)), &vertices, gl.STATIC_DRAW);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, @sizeOf(@TypeOf(indices)), &indices, gl.STATIC_DRAW);
// Position
gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), null); // lmao
gl.enableVertexAttribArray(0);
// Colour
gl.vertexAttribPointer(1, 3, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), @ptrFromInt((3 * @sizeOf(f32))));
gl.enableVertexAttribArray(1);
// Texture Coord
gl.vertexAttribPointer(2, 2, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), @ptrFromInt((6 * @sizeOf(f32))));
gl.enableVertexAttribArray(2);
return .{ .vao = vao_id, .vbo = vbo_id, .ebo = ebo_id };
return vao_id;
}
fn createScreenTexture(buf: []const u8) GLuint {
fn screenTex(buf: []const u8) GLuint {
var tex_id: GLuint = undefined;
gl.genTextures(1, &tex_id);
@ -427,7 +365,7 @@ const opengl_impl = struct {
return tex_id;
}
fn createOutputTexture() GLuint {
fn outTex() GLuint {
var tex_id: GLuint = undefined;
gl.genTextures(1, &tex_id);
@ -442,7 +380,7 @@ const opengl_impl = struct {
return tex_id;
}
fn createFrameBuffer(tex_id: GLuint) !GLuint {
fn frameBuffer(tex_id: GLuint) !GLuint {
var fbo_id: GLuint = undefined;
gl.genFramebuffers(1, &fbo_id);
@ -450,9 +388,7 @@ const opengl_impl = struct {
defer gl.bindFramebuffer(gl.FRAMEBUFFER, 0);
gl.framebufferTexture(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, tex_id, 0);
const draw_buffers: [1]GLuint = .{gl.COLOR_ATTACHMENT0};
gl.drawBuffers(1, &draw_buffers);
gl.drawBuffers(1, &@as(GLuint, gl.COLOR_ATTACHMENT0));
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE)
return error.FrameBufferObejctInitFailed;
@ -461,8 +397,7 @@ const opengl_impl = struct {
}
const shader = struct {
const Kind = enum { vertex, fragment };
const log = std.log.scoped(.Shader);
const log = std.log.scoped(.shader);
fn didCompile(id: gl.GLuint) bool {
var success: gl.GLint = undefined;

View File

@ -1,7 +1,6 @@
#version 330 core
out vec4 frag_color;
in vec3 color;
in vec2 uv;
uniform sampler2D screen;

View File

@ -1,13 +1,10 @@
#version 330 core
layout (location = 0) in vec3 pos;
layout (location = 1) in vec3 in_color;
layout (location = 2) in vec2 in_uv;
out vec3 color;
out vec2 uv;
const vec2 pos[3] = vec2[3](vec2(-1.0f, -1.0f), vec2(-1.0f, 3.0f), vec2(3.0f, -1.0f));
const vec2 uvs[3] = vec2[3](vec2( 0.0f, 0.0f), vec2( 0.0f, 2.0f), vec2(2.0f, 0.0f));
void main() {
color = in_color;
uv = in_uv;
gl_Position = vec4(pos, 1.0);
}
uv = uvs[gl_VertexID];
gl_Position = vec4(pos[gl_VertexID], 0.0, 1.0);
}