1 Commits

Author SHA1 Message Date
9844d657b0 fix: implement Ace Lightning (2002) obscure behaviour 2023-04-01 01:23:18 -05:00
21 changed files with 281 additions and 477 deletions

View File

@@ -8,7 +8,7 @@ const nfd = @import("lib/nfd-zig/build.zig");
pub fn build(b: *std.Build) void {
// Minimum Zig Version
const min_ver = std.SemanticVersion.parse("0.11.0-dev.2934+1b432072b") catch return; // https://github.com/ziglang/zig/commit/1b432072b
const min_ver = std.SemanticVersion.parse("0.11.0-dev.2168+322ace70f") catch return; // https://github.com/ziglang/zig/commit/322ace70f
if (builtin.zig_version.order(min_ver).compare(.lt)) {
std.log.err("{s}", .{b.fmt("Zig v{} does not meet the minimum version requirement. (Zig v{})", .{ builtin.zig_version, min_ver })});
std.os.exit(1);
@@ -65,9 +65,9 @@ pub fn build(b: *std.Build) void {
const zgui_pkg = zgui.package(b, target, optimize, .{ .options = .{ .backend = .sdl2_opengl3, .shared = true } });
zgui_pkg.link(exe);
b.installArtifact(exe);
exe.install();
const run_cmd = b.addRunArtifact(exe);
const run_cmd = exe.run();
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);

View File

@@ -27,7 +27,7 @@ pub fn write(self: *const Self, comptime T: type, address: usize, value: T) void
pub fn init(allocator: Allocator) !Self {
const buf = try allocator.alloc(u8, ewram_size);
@memset(buf, 0);
std.mem.set(u8, buf, 0);
return Self{
.buf = buf,
@@ -36,7 +36,7 @@ pub fn init(allocator: Allocator) !Self {
}
pub fn reset(self: *Self) void {
@memset(self.buf, 0);
std.mem.set(u8, self.buf, 0);
}
pub fn deinit(self: *Self) void {

View File

@@ -27,7 +27,7 @@ pub fn write(self: *const Self, comptime T: type, address: usize, value: T) void
pub fn init(allocator: Allocator) !Self {
const buf = try allocator.alloc(u8, iwram_size);
@memset(buf, 0);
std.mem.set(u8, buf, 0);
return Self{
.buf = buf,
@@ -36,7 +36,7 @@ pub fn init(allocator: Allocator) !Self {
}
pub fn reset(self: *Self) void {
@memset(self.buf, 0);
std.mem.set(u8, self.buf, 0);
}
pub fn deinit(self: *Self) void {

View File

@@ -110,7 +110,7 @@ pub const Backup = struct {
};
const buf = try allocator.alloc(u8, buf_size);
@memset(buf, 0xFF);
std.mem.set(u8, buf, 0xFF);
var backup = Self{
.buf = buf,
@@ -163,7 +163,7 @@ pub const Backup = struct {
switch (self.kind) {
.Sram, .Flash, .Flash1M => {
if (self.buf.len == file_buf.len) {
@memcpy(self.buf, file_buf);
std.mem.copy(u8, self.buf, file_buf);
return log.info("Loaded Save from {s}", .{file_path});
}
@@ -174,7 +174,7 @@ pub const Backup = struct {
self.eeprom.kind = if (file_buf.len == 0x200) .Small else .Large;
self.buf = try allocator.alloc(u8, file_buf.len);
@memcpy(self.buf, file_buf);
std.mem.copy(u8, self.buf, file_buf);
return log.info("Loaded Save from {s}", .{file_path});
}
@@ -205,6 +205,10 @@ pub const Backup = struct {
const file_path = try self.savePath(allocator, path);
defer allocator.free(file_path);
// FIXME: communicate edge case to the user?
if (std.mem.eql(u8, &self.title, "ACE LIGHTNIN"))
return;
switch (self.kind) {
.Sram, .Flash, .Flash1M, .Eeprom => {
const file = try std.fs.createFileAbsolute(file_path, .{});

View File

@@ -44,7 +44,7 @@ pub fn handleCommand(self: *Self, buf: []u8, byte: u8) void {
0xB0 => self.set_bank = true,
0x80 => self.prep_erase = true,
0x10 => {
@memset(buf, 0xFF);
std.mem.set(u8, buf, 0xFF);
self.prep_erase = false;
},
0xA0 => self.prep_write = true,
@@ -61,7 +61,7 @@ pub fn shouldEraseSector(self: *const Self, addr: usize, byte: u8) bool {
pub fn erase(self: *Self, buf: []u8, sector: usize) void {
const start = self.address() + (sector & 0xF000);
@memset(buf[start..][0..0x1000], 0xFF);
std.mem.set(u8, buf[start..][0..0x1000], 0xFF);
self.prep_erase = false;
self.state = .Ready;
}

View File

@@ -58,9 +58,7 @@ pub const Eeprom = struct {
log.err("Failed to resize EEPROM buf to {} bytes", .{len});
std.debug.panic("EEPROM entered irrecoverable state {}", .{e});
};
// FIXME: ptr to a slice?
@memset(buf.*, 0xFF);
std.mem.set(u8, buf.*, 0xFF);
}
}

View File

@@ -39,7 +39,7 @@ pub const arm = struct {
}
fn populate() [0x1000]InstrFn {
return comptime comptime_blk: {
comptime {
@setEvalBranchQuota(0xE000);
var table = [_]InstrFn{und} ** 0x1000;
@@ -106,8 +106,8 @@ pub const arm = struct {
};
}
break :comptime_blk table;
};
return table;
}
}
};
@@ -135,7 +135,7 @@ pub const thumb = struct {
}
fn populate() [0x400]InstrFn {
return comptime comptime_blk: {
comptime {
@setEvalBranchQuota(5025); // This is exact
var table = [_]InstrFn{und} ** 0x400;
@@ -228,8 +228,8 @@ pub const thumb = struct {
};
}
break :comptime_blk table;
};
return table;
}
}
};

View File

@@ -266,7 +266,7 @@ pub const Ppu = struct {
sched.push(.Draw, 240 * 4); // Add first PPU Event to Scheduler
const sprites = try allocator.create([128]?Sprite);
@memset(sprites, null);
std.mem.set(?Sprite, sprites, null);
return Self{
.vram = try Vram.init(allocator),
@@ -307,7 +307,7 @@ pub const Ppu = struct {
self.vcount = .{ .raw = 0x0000 };
self.scanline.reset();
@memset(self.scanline_sprites, null);
std.mem.set(?Sprite, self.scanline_sprites, null);
}
pub fn deinit(self: *Self) void {
@@ -744,7 +744,7 @@ pub const Ppu = struct {
// Reset Current Scanline Pixel Buffer and list of fetched sprites
// in prep for next scanline
self.scanline.reset();
@memset(self.scanline_sprites, null);
std.mem.set(?Sprite, self.scanline_sprites, null);
}
fn getBgr555(self: *Self, maybe_top: Scanline.Pixel, maybe_btm: Scanline.Pixel) u16 {
@@ -1554,7 +1554,7 @@ const Scanline = struct {
fn init(allocator: Allocator) !Self {
const buf = try allocator.alloc(Pixel, width * 2); // Top & Bottom Scanline
@memset(buf, .unset);
std.mem.set(Pixel, buf, .unset);
return .{
// Top & Bototm Layers
@@ -1565,7 +1565,7 @@ const Scanline = struct {
}
fn reset(self: *Self) void {
@memset(self.buf, .unset);
std.mem.set(Pixel, self.buf, .unset);
}
fn deinit(self: *Self) void {

View File

@@ -29,13 +29,13 @@ pub fn write(self: *Self, comptime T: type, address: usize, value: T) void {
pub fn init(allocator: Allocator) !Self {
const buf = try allocator.alloc(u8, buf_len);
@memset(buf, 0);
std.mem.set(u8, buf, 0);
return Self{ .buf = buf, .allocator = allocator };
}
pub fn reset(self: *Self) void {
@memset(self.buf, 0);
std.mem.set(u8, self.buf, 0);
}
pub fn deinit(self: *Self) void {

View File

@@ -32,13 +32,13 @@ pub fn write(self: *Self, comptime T: type, address: usize, value: T) void {
pub fn init(allocator: Allocator) !Self {
const buf = try allocator.alloc(u8, buf_len);
@memset(buf, 0);
std.mem.set(u8, buf, 0);
return Self{ .buf = buf, .allocator = allocator };
}
pub fn reset(self: *Self) void {
@memset(self.buf, 0);
std.mem.set(u8, self.buf, 0);
}
pub fn deinit(self: *Self) void {

View File

@@ -40,13 +40,13 @@ pub fn write(self: *Self, comptime T: type, dispcnt: io.DisplayControl, address:
pub fn init(allocator: Allocator) !Self {
const buf = try allocator.alloc(u8, buf_len);
@memset(buf, 0);
std.mem.set(u8, buf, 0);
return Self{ .buf = buf, .allocator = allocator };
}
pub fn reset(self: *Self) void {
@memset(self.buf, 0);
std.mem.set(u8, self.buf, 0);
}
pub fn deinit(self: *Self) void {

View File

@@ -30,34 +30,14 @@ pub const State = struct {
fps_hist: RingBuffer(u32),
should_quit: bool = false,
emulation: Emulation,
win_stat: WindowStatus = .{},
const WindowStatus = struct {
show_deps: bool = false,
show_regs: bool = false,
show_schedule: bool = false,
show_perf: bool = false,
show_palette: bool = false,
};
const Emulation = union(enum) {
Active,
Inactive,
Transition: enum { Active, Inactive },
};
/// if zba is initialized with a ROM already provided, this initializer should be called
/// with `title_opt` being non-null
pub fn init(allocator: Allocator, title_opt: ?*const [12]u8) !@This() {
const history = try allocator.alloc(u32, histogram_len);
return .{
.title = handleTitle(title_opt),
.emulation = if (title_opt == null) .Inactive else .Active,
.fps_hist = RingBuffer(u32).init(history),
};
const title: [12:0]u8 = if (title_opt) |t| t.* ++ [_:0]u8{} else "[No Title]\x00\x00".*;
return .{ .title = title, .fps_hist = RingBuffer(u32).init(history) };
}
pub fn deinit(self: *@This(), allocator: Allocator) void {
@@ -76,8 +56,7 @@ pub fn draw(state: *State, tex_id: GLuint, cpu: *Arm7tdmi) void {
if (zgui.beginMenu("File", true)) {
defer zgui.endMenu();
if (zgui.menuItem("Quit", .{}))
state.should_quit = true;
if (zgui.menuItem("Quit", .{})) state.should_quit = true;
if (zgui.menuItem("Insert ROM", .{})) blk: {
const maybe_path = nfd.openFileDialog("gba", null) catch |e| {
@@ -97,47 +76,16 @@ pub fn draw(state: *State, tex_id: GLuint, cpu: *Arm7tdmi) void {
break :blk;
};
state.title = handleTitle(&cpu.bus.pak.title);
state.emulation = .{ .Transition = .Active };
state.title = cpu.bus.pak.title ++ [_:0]u8{};
}
}
if (zgui.beginMenu("Emulation", true)) {
defer zgui.endMenu();
if (zgui.menuItem("Registers", .{ .selected = state.win_stat.show_regs }))
state.win_stat.show_regs = true;
if (zgui.menuItem("Palette", .{ .selected = state.win_stat.show_palette }))
state.win_stat.show_palette = true;
if (zgui.menuItem("Schedule", .{ .selected = state.win_stat.show_schedule }))
state.win_stat.show_schedule = true;
if (zgui.menuItem("Paused", .{ .selected = state.emulation == .Inactive })) {
state.emulation = switch (state.emulation) {
.Active => .{ .Transition = .Inactive },
.Inactive => .{ .Transition = .Active },
else => state.emulation,
};
}
if (zgui.menuItem("Restart", .{}))
if (zgui.menuItem("Restart", .{})) {
emu.reset(cpu);
}
if (zgui.beginMenu("Stats", true)) {
defer zgui.endMenu();
if (zgui.menuItem("Performance", .{ .selected = state.win_stat.show_perf }))
state.win_stat.show_perf = true;
}
if (zgui.beginMenu("Help", true)) {
defer zgui.endMenu();
if (zgui.menuItem("Dependencies", .{ .selected = state.win_stat.show_deps }))
state.win_stat.show_deps = true;
}
}
}
@@ -152,51 +100,8 @@ pub fn draw(state: *State, tex_id: GLuint, cpu: *Arm7tdmi) void {
zgui.image(@intToPtr(*anyopaque, tex_id), .{ .w = w, .h = h, .uv0 = .{ 0, 1 }, .uv1 = .{ 1, 0 } });
}
// TODO: Any other steps to respect the copyright of the libraries I use?
if (state.win_stat.show_deps) {
_ = zgui.begin("Dependencies", .{ .popen = &state.win_stat.show_deps });
defer zgui.end();
zgui.bulletText("SDL.zig by Felix Queißner", .{});
{
zgui.indent(.{});
defer zgui.unindent(.{});
zgui.bulletText("SDL by Sam Lantinga", .{});
}
zgui.bulletText("known-folders by ziglibs", .{});
zgui.bulletText("nfd-zig by Fabio Arnold", .{});
{
zgui.indent(.{});
defer zgui.unindent(.{});
zgui.bulletText("nativefiledialog by Michael Labbe", .{});
}
zgui.bulletText("zba-gdbstub by Rekai Musuka", .{});
zgui.bulletText("zba-util by Rekai Musuka", .{});
zgui.bulletText("zgui by Michal Ziulek", .{});
{
zgui.indent(.{});
defer zgui.unindent(.{});
zgui.bulletText("DearImGui by Omar Cornut", .{});
}
zgui.bulletText("zig-clap by Jimmi Holst Christensen", .{});
zgui.bulletText("zig-datetime by Jairus Martin", .{});
zgui.bulletText("zig-opengl by Felix Queißner", .{});
{
zgui.indent(.{});
defer zgui.unindent(.{});
zgui.bulletText("OpenGL-Registry by The Khronos Group", .{});
}
zgui.bulletText("zig-toml by Aeron Avery", .{});
zgui.bulletText("bitfield.zig by Hannes Bredberg and FlorenceOS contributors", .{});
}
if (state.win_stat.show_regs) {
_ = zgui.begin("Guest Registers", .{ .popen = &state.win_stat.show_regs });
{
_ = zgui.begin("Information", .{});
defer zgui.end();
for (0..8) |i| {
@@ -219,8 +124,8 @@ pub fn draw(state: *State, tex_id: GLuint, cpu: *Arm7tdmi) void {
widgets.interrupts("IRQ", cpu.bus.io.irq);
}
if (state.win_stat.show_perf) {
_ = zgui.begin("Performance", .{ .popen = &state.win_stat.show_perf });
{
_ = zgui.begin("Performance", .{});
defer zgui.end();
const tmp = blk: {
@@ -237,7 +142,7 @@ pub fn draw(state: *State, tex_id: GLuint, cpu: *Arm7tdmi) void {
const sorted = blk: {
var buf: @TypeOf(values) = undefined;
@memcpy(buf[0..len], values[0..len]);
std.mem.copy(u32, buf[0..len], values[0..len]);
std.sort.sort(u32, buf[0..len], {}, std.sort.asc(u32));
break :blk buf;
@@ -282,8 +187,8 @@ pub fn draw(state: *State, tex_id: GLuint, cpu: *Arm7tdmi) void {
zgui.text(" 1% Low: {:0>3} fps", .{stats[2]});
}
if (state.win_stat.show_schedule) {
_ = zgui.begin("Schedule", .{ .popen = &state.win_stat.show_schedule });
{
_ = zgui.begin("Scheduler", .{});
defer zgui.end();
const scheduler = cpu.sched;
@@ -296,7 +201,7 @@ pub fn draw(state: *State, tex_id: GLuint, cpu: *Arm7tdmi) void {
var items: [20]Event = undefined;
const len = scheduler.queue.len;
@memcpy(&items, scheduler.queue.items);
std.mem.copy(Event, &items, scheduler.queue.items);
std.sort.sort(Event, items[0..len], {}, widgets.eventDesc(Event));
for (items[0..len]) |event| {
@@ -304,59 +209,12 @@ pub fn draw(state: *State, tex_id: GLuint, cpu: *Arm7tdmi) void {
}
}
if (state.win_stat.show_palette) {
_ = zgui.begin("Palette", .{ .popen = &state.win_stat.show_palette });
defer zgui.end();
widgets.paletteGrid(.Background, cpu);
zgui.sameLine(.{ .spacing = 20.0 });
widgets.paletteGrid(.Object, cpu);
}
{
zgui.showDemoWindow(null);
}
// {
// zgui.showDemoWindow(null);
// }
}
const widgets = struct {
const PaletteKind = enum { Background, Object };
fn paletteGrid(comptime kind: PaletteKind, cpu: *const Arm7tdmi) void {
_ = zgui.beginGroup();
defer zgui.endGroup();
const address: u32 = switch (kind) {
.Background => 0x0500_0000,
.Object => 0x0500_0200,
};
for (0..0x100) |i| {
const offset = @truncate(u32, i);
const bgr555 = cpu.bus.dbgRead(u16, address + offset * @sizeOf(u16));
widgets.colourSquare(bgr555);
if ((i + 1) % 0x10 != 0) zgui.sameLine(.{});
}
zgui.text(@tagName(kind), .{});
}
fn colourSquare(bgr555: u16) void {
// FIXME: working with the packed struct enum is currently broken :pensive:
const ImguiColorEditFlags_NoInputs: u32 = 1 << 5;
const ImguiColorEditFlags_NoPicker: u32 = 1 << 2;
const flags = @bitCast(zgui.ColorEditFlags, ImguiColorEditFlags_NoInputs | ImguiColorEditFlags_NoPicker);
const b = @intToFloat(f32, bgr555 >> 10 & 0x1f);
const g = @intToFloat(f32, bgr555 >> 5 & 0x1F);
const r = @intToFloat(f32, bgr555 & 0x1F);
var col = [_]f32{ r / 31.0, g / 31.0, b / 31.0 };
_ = zgui.colorEdit3("", .{ .col = &col, .flags = flags });
}
fn interrupts(comptime label: []const u8, int: anytype) void {
const h = 15.0;
const w = 9.0 * 2 + 3.5;
@@ -447,13 +305,3 @@ const widgets = struct {
}.inner;
}
};
fn handleTitle(title_opt: ?*const [12]u8) [12:0]u8 {
if (title_opt == null) return "[N/A Title]\x00".*; // No ROM present
const title = title_opt.?;
// ROM Title is an empty string (ImGui hates these)
if (title[0] == '\x00') return "[No Title]\x00\x00".*;
return title.* ++ [_:0]u8{};
}

View File

@@ -31,7 +31,7 @@ const params = clap.parseParamsComptime(
pub fn main() void {
// Main Allocator for ZBA
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer std.debug.assert(gpa.deinit() == .ok);
defer std.debug.assert(!gpa.deinit());
const allocator = gpa.allocator();
@@ -86,7 +86,7 @@ pub fn main() void {
bus.init(allocator, &scheduler, &cpu, paths) catch |e| exitln("failed to init zba bus: {}", .{e});
defer bus.deinit();
if (config.config().guest.skip_bios or result.args.skip != 0 or paths.bios == null) {
if (config.config().guest.skip_bios or result.args.skip or paths.bios == null) {
cpu.fastBoot();
}
@@ -101,7 +101,7 @@ pub fn main() void {
var items: [0x100]u8 = undefined;
var channel = TwoWayChannel.init(&items);
if (result.args.gdb != 0) {
if (result.args.gdb) {
const Server = @import("gdbstub").Server;
const EmuThing = @import("core/emu.zig").EmuThing;
@@ -119,7 +119,7 @@ pub fn main() void {
const thread = std.Thread.spawn(.{}, Server.run, .{ &server, allocator, &quit }) catch |e| exitln("gdb server thread crashed: {}", .{e});
defer thread.join();
gui.run(.Debug, .{
gui.run(.{
.cpu = &cpu,
.scheduler = &scheduler,
.channel = &channel,
@@ -127,14 +127,10 @@ pub fn main() void {
} else {
var tracker = FpsTracker.init();
// emu should start paused if there's no ROM to run
if (paths.rom == null)
channel.emu.push(.Pause);
const thread = std.Thread.spawn(.{}, emu.run, .{ &cpu, &scheduler, &tracker, &channel }) catch |e| exitln("emu thread panicked: {}", .{e});
defer thread.join();
gui.run(.Standard, .{
gui.run(.{
.cpu = &cpu,
.scheduler = &scheduler,
.channel = &channel,

View File

@@ -21,8 +21,8 @@ const GLsizei = gl.GLsizei;
const SDL_GLContext = *anyopaque;
const Allocator = std.mem.Allocator;
const Dimensions = struct { width: u32, height: u32 };
const default_dim: Dimensions = .{ .width = 1280, .height = 720 };
const width = 1280;
const height = 720;
pub const sample_rate = 1 << 15;
pub const sample_format = SDL.AUDIO_U16;
@@ -33,12 +33,29 @@ pub const Gui = struct {
const Self = @This();
const log = std.log.scoped(.Gui);
// 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
window: *SDL.SDL_Window,
ctx: SDL_GLContext,
audio: Audio,
state: imgui.State,
allocator: Allocator,
program_id: gl.GLuint,
pub fn init(allocator: Allocator, apu: *Apu, title_opt: ?*const [12]u8) !Self {
if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_EVENTS | SDL.SDL_INIT_AUDIO) < 0) panic();
@@ -50,9 +67,9 @@ pub const Gui = struct {
window_title,
SDL.SDL_WINDOWPOS_CENTERED,
SDL.SDL_WINDOWPOS_CENTERED,
default_dim.width,
default_dim.height,
SDL.SDL_WINDOW_OPENGL | SDL.SDL_WINDOW_SHOWN | SDL.SDL_WINDOW_RESIZABLE,
width,
height,
SDL.SDL_WINDOW_OPENGL | SDL.SDL_WINDOW_SHOWN,
) orelse panic();
const ctx = SDL.SDL_GL_CreateContext(window) orelse panic();
@@ -70,6 +87,7 @@ pub const Gui = struct {
return Self{
.window = window,
.ctx = ctx,
.program_id = try compileShaders(),
.audio = Audio.init(apu),
.allocator = allocator,
@@ -85,6 +103,7 @@ pub const Gui = struct {
zgui.plot.deinit();
zgui.deinit();
gl.deleteProgram(self.program_id);
SDL.SDL_GL_DeleteContext(self.ctx);
SDL.SDL_DestroyWindow(self.window);
SDL.SDL_Quit();
@@ -92,6 +111,137 @@ pub const Gui = struct {
self.* = undefined;
}
fn drawGbaTexture(self: *const Self, obj_ids: struct { GLuint, GLuint, GLuint }, tex_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(obj_ids[0]); // VAO
defer gl.bindVertexArray(0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, obj_ids[2]); // EBO
defer gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0);
// Use compiled frag + vertex shader
gl.useProgram(self.program_id);
defer gl.useProgram(0);
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, null);
}
fn compileShaders() !GLuint {
const vert_shader = @embedFile("shader/pixelbuf.vert");
const frag_shader = @embedFile("shader/pixelbuf.frag");
const vs = gl.createShader(gl.VERTEX_SHADER);
defer gl.deleteShader(vs);
gl.shaderSource(vs, 1, &[_][*c]const u8{vert_shader}, 0);
gl.compileShader(vs);
if (!shader.didCompile(vs)) return error.VertexCompileError;
const fs = gl.createShader(gl.FRAGMENT_SHADER);
defer gl.deleteShader(fs);
gl.shaderSource(fs, 1, &[_][*c]const u8{frag_shader}, 0);
gl.compileShader(fs);
if (!shader.didCompile(fs)) return error.FragmentCompileError;
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
return program;
}
// Returns the VAO ID since it's used in run()
fn genBufferObjects() struct { GLuint, GLuint, 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), @intToPtr(?*anyopaque, (3 * @sizeOf(f32))));
gl.enableVertexAttribArray(1);
// Texture Coord
gl.vertexAttribPointer(2, 2, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), @intToPtr(?*anyopaque, (6 * @sizeOf(f32))));
gl.enableVertexAttribArray(2);
return .{ vao_id, vbo_id, ebo_id };
}
fn genGbaTexture(buf: []const u8) GLuint {
var tex_id: GLuint = undefined;
gl.genTextures(1, &tex_id);
gl.bindTexture(gl.TEXTURE_2D, tex_id);
defer gl.bindTexture(gl.TEXTURE_2D, 0);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gba_width, gba_height, 0, gl.RGBA, gl.UNSIGNED_INT_8_8_8_8, buf.ptr);
return tex_id;
}
fn genOutTexture() GLuint {
var tex_id: GLuint = undefined;
gl.genTextures(1, &tex_id);
gl.bindTexture(gl.TEXTURE_2D, tex_id);
defer gl.bindTexture(gl.TEXTURE_2D, 0);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gba_width, gba_height, 0, gl.RGBA, gl.UNSIGNED_INT_8_8_8_8, null);
return tex_id;
}
fn genFrameBufObject(tex_id: c_uint) !GLuint {
var fbo_id: GLuint = undefined;
gl.genFramebuffers(1, &fbo_id);
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo_id);
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);
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE)
return error.FrameBufferObejctInitFailed;
return fbo_id;
}
const RunOptions = struct {
channel: *TwoWayChannel,
tracker: ?*FpsTracker = null,
@@ -99,30 +249,29 @@ pub const Gui = struct {
scheduler: *Scheduler,
};
const RunMode = enum { Standard, Debug };
pub fn run(self: *Self, comptime mode: RunMode, opt: RunOptions) !void {
pub fn run(self: *Self, opt: RunOptions) !void {
const cpu = opt.cpu;
const tracker = opt.tracker;
const channel = opt.channel;
const objects = opengl_impl.createObjects();
defer gl.deleteBuffers(3, @as(*const [3]GLuint, &.{ objects.vao, objects.vbo, objects.ebo }));
const obj_ids = Self.genBufferObjects();
defer gl.deleteBuffers(3, @as(*const [3]c_uint, &obj_ids));
const emu_tex = opengl_impl.createScreenTexture(cpu.bus.ppu.framebuf.get(.Renderer));
const out_tex = opengl_impl.createOutputTexture();
defer gl.deleteTextures(2, &[_]GLuint{ emu_tex, out_tex });
const emu_tex = Self.genGbaTexture(cpu.bus.ppu.framebuf.get(.Renderer));
const out_tex = Self.genOutTexture();
defer gl.deleteTextures(2, &[_]c_uint{ emu_tex, out_tex });
const fbo_id = try opengl_impl.createFrameBuffer(out_tex);
const fbo_id = try Self.genFrameBufObject(out_tex);
defer gl.deleteFramebuffers(1, &fbo_id);
// TODO: Support dynamically switching shaders?
const prog_id = try opengl_impl.compileShaders();
defer gl.deleteProgram(prog_id);
var win_dim: Dimensions = default_dim;
emu_loop: while (true) {
// `quit` from RunOptions may be modified by the GDBSTUB thread,
// so we want to recognize that it may change to `true` and exit the GUI thread
if (channel.gui.pop()) |event| switch (event) {
.Quit => break :emu_loop,
.Paused => @panic("TODO: We want to peek (and then pop if it's .Quit), not always pop"),
};
// 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;
@@ -173,85 +322,44 @@ pub const Gui = struct {
cpu.bus.io.keyinput.store(keyinput.raw, .Monotonic);
},
SDL.SDL_WINDOWEVENT => {
if (event.window.event == SDL.SDL_WINDOWEVENT_RESIZED) {
log.debug("window resized to: {}x{}", .{ event.window.data1, event.window.data2 });
win_dim.width = @intCast(u32, event.window.data1);
win_dim.height = @intCast(u32, event.window.data2);
}
},
else => {},
}
}
zgui.backend.newFrame(@intToFloat(f32, win_dim.width), @intToFloat(f32, win_dim.height));
{
channel.emu.push(.Pause);
defer channel.emu.push(.Resume);
switch (self.state.emulation) {
.Transition => |inner| switch (inner) {
.Active => {
_ = channel.gui.pop();
// Spin Loop until we know that the emu is paused
wait: while (true) switch (channel.gui.pop() orelse continue) {
.Paused => break :wait,
else => |any| std.debug.panic("[Gui/Channel]: Unhandled Event: {}", .{any}),
};
channel.emu.push(.Resume);
self.state.emulation = .Active;
},
.Inactive => {
// Assert that double pausing is impossible
if (channel.gui.peek()) |value|
std.debug.assert(value != .Paused);
// Add FPS count to the histogram
if (tracker) |t| self.state.fps_hist.push(t.value()) catch {};
channel.emu.push(.Pause);
self.state.emulation = .Inactive;
},
},
.Active => {
const is_std = mode == .Standard;
// Draw GBA Screen to Texture
{
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo_id);
defer gl.bindFramebuffer(gl.FRAMEBUFFER, 0);
if (is_std) channel.emu.push(.Pause);
defer if (is_std) channel.emu.push(.Resume);
const buf = cpu.bus.ppu.framebuf.get(.Renderer);
gl.viewport(0, 0, gba_width, gba_height);
self.drawGbaTexture(obj_ids, emu_tex, buf);
}
switch (mode) {
.Standard => {
// TODO: add timeout
while (true) switch (channel.gui.pop() orelse continue) {
.Paused => break,
.Quit => unreachable, // only signaled in debug mode
};
},
.Debug => blk: {
switch (channel.gui.pop() orelse break :blk) {
.Paused => unreachable, // only in standard mode
.Quit => break :emu_loop, // FIXME: gdb side of emu is seriously out-of-date...
}
},
}
// Background Colour
const size = zgui.io.getDisplaySize();
gl.viewport(0, 0, @floatToInt(c_int, size[0]), @floatToInt(c_int, size[1]));
gl.clearColor(0, 0, 0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// Add FPS count to the histogram
if (tracker) |t| self.state.fps_hist.push(t.value()) catch {};
// Draw GBA Screen to Texture
{
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo_id);
defer gl.bindFramebuffer(gl.FRAMEBUFFER, 0);
const buf = cpu.bus.ppu.framebuf.get(.Renderer);
gl.viewport(0, 0, gba_width, gba_height);
opengl_impl.drawScreenTexture(emu_tex, prog_id, objects, buf);
}
imgui.draw(&self.state, out_tex, cpu);
},
.Inactive => imgui.draw(&self.state, out_tex, cpu),
zgui.backend.newFrame(width, height);
imgui.draw(&self.state, out_tex, cpu);
zgui.backend.draw();
}
// Background Colour
const size = zgui.io.getDisplaySize();
gl.viewport(0, 0, @floatToInt(GLsizei, size[0]), @floatToInt(GLsizei, size[1]));
gl.clearColor(0, 0, 0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
zgui.backend.draw();
SDL.SDL_GL_SwapWindow(self.window);
}
@@ -307,179 +415,29 @@ const Audio = struct {
}
};
const shader = struct {
const Kind = enum { vertex, fragment };
const log = std.log.scoped(.Shader);
fn didCompile(id: gl.GLuint) bool {
var success: gl.GLint = undefined;
gl.getShaderiv(id, gl.COMPILE_STATUS, &success);
if (success == 0) err(id);
return success == 1;
}
fn err(id: gl.GLuint) void {
const buf_len = 512;
var error_msg: [buf_len]u8 = undefined;
gl.getShaderInfoLog(id, buf_len, 0, &error_msg);
log.err("{s}", .{std.mem.sliceTo(&error_msg, 0)});
}
};
fn panic() noreturn {
const str = @as(?[*:0]const u8, SDL.SDL_GetError()) orelse "unknown error";
@panic(std.mem.sliceTo(str, 0));
}
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 {
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
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);
}
fn compileShaders() !GLuint {
const vert_shader = @embedFile("shader/pixelbuf.vert");
const frag_shader = @embedFile("shader/pixelbuf.frag");
const vs = gl.createShader(gl.VERTEX_SHADER);
defer gl.deleteShader(vs);
gl.shaderSource(vs, 1, &[_][*c]const u8{vert_shader}, 0);
gl.compileShader(vs);
if (!shader.didCompile(vs)) return error.VertexCompileError;
const fs = gl.createShader(gl.FRAGMENT_SHADER);
defer gl.deleteShader(fs);
gl.shaderSource(fs, 1, &[_][*c]const u8{frag_shader}, 0);
gl.compileShader(fs);
if (!shader.didCompile(fs)) return error.FragmentCompileError;
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
return program;
}
// Returns the VAO ID since it's used in run()
fn createObjects() Objects {
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), @intToPtr(?*anyopaque, (3 * @sizeOf(f32))));
gl.enableVertexAttribArray(1);
// Texture Coord
gl.vertexAttribPointer(2, 2, gl.FLOAT, gl.FALSE, 8 * @sizeOf(f32), @intToPtr(?*anyopaque, (6 * @sizeOf(f32))));
gl.enableVertexAttribArray(2);
return .{ .vao = vao_id, .vbo = vbo_id, .ebo = ebo_id };
}
fn createScreenTexture(buf: []const u8) GLuint {
var tex_id: GLuint = undefined;
gl.genTextures(1, &tex_id);
gl.bindTexture(gl.TEXTURE_2D, tex_id);
defer gl.bindTexture(gl.TEXTURE_2D, 0);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gba_width, gba_height, 0, gl.RGBA, gl.UNSIGNED_INT_8_8_8_8, buf.ptr);
return tex_id;
}
fn createOutputTexture() GLuint {
var tex_id: GLuint = undefined;
gl.genTextures(1, &tex_id);
gl.bindTexture(gl.TEXTURE_2D, tex_id);
defer gl.bindTexture(gl.TEXTURE_2D, 0);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gba_width, gba_height, 0, gl.RGBA, gl.UNSIGNED_INT_8_8_8_8, null);
return tex_id;
}
fn createFrameBuffer(tex_id: GLuint) !GLuint {
var fbo_id: GLuint = undefined;
gl.genFramebuffers(1, &fbo_id);
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo_id);
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);
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE)
return error.FrameBufferObejctInitFailed;
return fbo_id;
}
const shader = struct {
const Kind = enum { vertex, fragment };
const log = std.log.scoped(.Shader);
fn didCompile(id: gl.GLuint) bool {
var success: gl.GLint = undefined;
gl.getShaderiv(id, gl.COMPILE_STATUS, &success);
if (success == 0) err(id);
return success == 1;
}
fn err(id: gl.GLuint) void {
const buf_len = 512;
var error_msg: [buf_len]u8 = undefined;
gl.getShaderInfoLog(id, buf_len, 0, &error_msg);
log.err("{s}", .{std.mem.sliceTo(&error_msg, 0)});
}
};
};

View File

@@ -260,7 +260,7 @@ pub const FrameBuffer = struct {
pub fn init(allocator: Allocator, comptime len: comptime_int) !Self {
const buf = try allocator.alloc(u8, len * 2);
@memset(buf, 0);
std.mem.set(u8, buf, 0);
return .{
// Front and Back Framebuffers
@@ -272,7 +272,7 @@ pub const FrameBuffer = struct {
}
pub fn reset(self: *Self) void {
@memset(self.buf, 0);
std.mem.set(u8, self.buf, 0);
self.current = 0;
}