Compare commits
22 Commits
8b4faca80f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6cacdc7180 | |||
| 56ac755a73 | |||
| c5ffa19e06 | |||
| 2d3c659b85 | |||
| 94894fadc6 | |||
| 0e02d9aaab | |||
| b4830326ff | |||
| ef93bbe084 | |||
| f71aaafe41 | |||
| 66192daf6c | |||
| 05b7a9014d | |||
| 493d7aeede | |||
| 9183e6850d | |||
| d54202bf8b | |||
| d097dcc2f5 | |||
| 203971c91a | |||
| 78b849b6ff | |||
| 557b90a39f | |||
| dd3158bcfc | |||
| 64cd373957 | |||
| 64a30b190c | |||
| f2c728ef44 |
29
.github/workflows/main.yml
vendored
29
.github/workflows/main.yml
vendored
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
paths:
|
||||
- "**.zig"
|
||||
- "dl_sdl2.ps1"
|
||||
branches:
|
||||
- main
|
||||
schedule:
|
||||
@@ -14,13 +15,17 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
# os: [ubuntu-latest, windows-latest]
|
||||
os: [ubuntu-latest, windows-latest] # TODO: Figure out Apple Silicon macOS
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
- uses: goto-bus-stop/setup-zig@v2
|
||||
with:
|
||||
version: master
|
||||
version: 0.13.0
|
||||
- run: |
|
||||
git config --global core.autocrlf false
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: prepare-linux
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
@@ -29,26 +34,19 @@ jobs:
|
||||
- name: prepare-windows
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
vcpkg integrate install
|
||||
vcpkg install sdl2:x64-windows
|
||||
git config --global core.autocrlf false
|
||||
.\dl_sdl2.ps1
|
||||
- name: prepare-macos
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
brew install sdl2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: build
|
||||
run: zig build -Doptimize=ReleaseSafe -Dcpu=baseline
|
||||
- name: prepare-executable
|
||||
run: |
|
||||
mv zig-out/lib/* zig-out/bin
|
||||
- name: upload
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: zba-${{matrix.os}}
|
||||
path: zig-out/bin
|
||||
path: zig-out
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -57,6 +55,5 @@ jobs:
|
||||
submodules: recursive
|
||||
- uses: goto-bus-stop/setup-zig@v2
|
||||
with:
|
||||
version: 0.11.0-dev.3395+1e7dcaa3a
|
||||
- run: zig fmt src/**/*.zig
|
||||
|
||||
version: 0.13.0
|
||||
- run: zig fmt --check {src,lib}/**/*.zig build.zig build.zig.zon
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
/.vscode
|
||||
/bin
|
||||
**/zig-cache
|
||||
**/.zig-cache
|
||||
**/zig-out
|
||||
/docs
|
||||
**/*.log
|
||||
|
||||
5
.gitmodules
vendored
5
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
||||
[submodule "lib/SDL.zig"]
|
||||
path = lib/SDL.zig
|
||||
url = https://github.com/MasterQ32/SDL.zig
|
||||
[submodule "lib/zgui"]
|
||||
path = lib/zgui
|
||||
url = https://git.musuka.dev/paoda/zgui
|
||||
url = https://github.com/paoda/SDL.zig
|
||||
|
||||
@@ -27,7 +27,7 @@ Finally it's worth noting that ZBA uses a TOML config file it'll store in your O
|
||||
|
||||
## Compiling
|
||||
|
||||
Most recently built on Zig [v0.11.0-dev.3395+1e7dcaa3a](https://github.com/ziglang/zig/tree/1e7dcaa3a)
|
||||
Most recently built on Zig [v0.11.0](https://github.com/ziglang/zig/tree/0.11.0)
|
||||
|
||||
### Dependencies
|
||||
|
||||
|
||||
66
build.zig
66
build.zig
@@ -1,15 +1,16 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const Sdk = @import("lib/SDL.zig/Sdk.zig");
|
||||
const zgui = @import("lib/zgui/build.zig");
|
||||
const sdl = @import("lib/SDL.zig/build.zig");
|
||||
|
||||
const SemVer = std.SemanticVersion;
|
||||
|
||||
const target_version = "0.13.0";
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
// Minimum Zig Version
|
||||
const min_ver = std.SemanticVersion.parse("0.11.0-dev.3395+1e7dcaa3a") catch return; // https://github.com/ziglang/zig/commit/34865d693
|
||||
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);
|
||||
const actual_version = builtin.zig_version;
|
||||
if (comptime actual_version.order(SemVer.parse(target_version) catch unreachable) != .eq) {
|
||||
@compileError("ZBA must be built with Zig v" ++ target_version ++ ".");
|
||||
}
|
||||
|
||||
const target = b.standardTargetOptions(.{});
|
||||
@@ -17,52 +18,45 @@ pub fn build(b: *std.Build) void {
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "zba",
|
||||
.root_source_file = .{ .path = "src/main.zig" },
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
exe.setMainPkgPath("."); // Necessary so that src/main.zig can embed example.toml
|
||||
const sdk = sdl.init(b, null, null);
|
||||
const zgui = b.dependency("zgui", .{ .shared = false, .with_implot = true, .backend = .sdl2_opengl3 });
|
||||
const imgui = zgui.artifact("imgui");
|
||||
|
||||
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.root_module.addImport("known_folders", b.dependency("known-folders", .{}).module("known-folders")); // https://github.com/ziglibs/known-folders
|
||||
exe.root_module.addImport("datetime", b.dependency("zig-datetime", .{}).module("zig-datetime")); // https://github.com/frmdstryr/zig-datetime
|
||||
exe.root_module.addImport("clap", b.dependency("zig-clap", .{}).module("clap")); // https://github.com/Hejsil/zig-clap
|
||||
exe.root_module.addImport("zba-util", b.dependency("zba-util", .{}).module("zba-util")); // https://git.musuka.dev/paoda/zba-util
|
||||
exe.root_module.addImport("tomlz", b.dependency("tomlz", .{}).module("tomlz")); // https://github.com/mattyhall/tomlz
|
||||
exe.root_module.addImport("arm32", b.dependency("arm32", .{}).module("arm32")); // https://git.musuka.dev/paoda/arm32
|
||||
exe.root_module.addImport("gdbstub", b.dependency("zba-gdbstub", .{}).module("zba-gdbstub")); // https://git.musuka.dev/paoda/gdbstub
|
||||
exe.root_module.addImport("nfd", b.dependency("nfd", .{}).module("nfd")); // https://github.com/fabioarnold/nfd-zig
|
||||
exe.root_module.addImport("zgui", zgui.module("root")); // https://git.musuka.dev/paoda/zgui
|
||||
exe.root_module.addImport("sdl2", sdk.getNativeModule()); // https://github.com/MasterQ32/SDL.zig
|
||||
|
||||
// https://github.com/fabioarnold/nfd-zig
|
||||
const nfd_dep = b.dependency("nfd", .{ .target = target, .optimize = optimize });
|
||||
exe.linkLibrary(nfd_dep.artifact("nfd"));
|
||||
exe.addModule("nfd", nfd_dep.module("nfd"));
|
||||
exe.root_module.addAnonymousImport("bitfield", .{ .root_source_file = b.path("lib/bitfield.zig") }); // https://github.com/FlorenceOS/
|
||||
exe.root_module.addAnonymousImport("gl", .{ .root_source_file = b.path("lib/gl.zig") }); // https://github.com/MasterQ32/zig-opengl
|
||||
exe.root_module.addAnonymousImport("example.toml", .{ .root_source_file = b.path("example.toml") });
|
||||
|
||||
// https://github.com/MasterQ32/SDL.zig
|
||||
const sdk = Sdk.init(b, null);
|
||||
sdk.link(exe, .dynamic);
|
||||
exe.addModule("sdl2", sdk.getNativeModule());
|
||||
|
||||
// https://git.musuka.dev/paoda/zgui
|
||||
// .shared option should stay in sync with SDL.zig call above where true == .dynamic, and false == .static
|
||||
const zgui_pkg = zgui.package(b, target, optimize, .{ .options = .{ .backend = .sdl2_opengl3, .shared = true } });
|
||||
zgui_pkg.link(exe);
|
||||
|
||||
exe.addAnonymousModule("bitfield", .{ .source_file = .{ .path = "lib/bitfield.zig" } }); // https://github.com/FlorenceOS/
|
||||
exe.addAnonymousModule("gl", .{ .source_file = .{ .path = "lib/gl.zig" } }); // https://github.com/MasterQ32/zig-opengl
|
||||
sdk.link(exe, .dynamic, .SDL2);
|
||||
sdk.link(imgui, .dynamic, .SDL2);
|
||||
exe.linkLibrary(imgui);
|
||||
|
||||
b.installArtifact(exe);
|
||||
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
if (b.args) |args| run_cmd.addArgs(args);
|
||||
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
|
||||
const exe_tests = b.addTest(.{
|
||||
.root_source_file = .{ .path = "src/main.zig" },
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
@@ -1,38 +1,50 @@
|
||||
.{
|
||||
.name = "zba",
|
||||
.version = "0.1.0",
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"lib/bitfield.zig",
|
||||
"lib/gl.zig",
|
||||
"src",
|
||||
},
|
||||
.minimum_zig_version = "0.13.0",
|
||||
.dependencies = .{
|
||||
.nfd = .{
|
||||
.url = "https://github.com/fabioarnold/nfd-zig/archive/ae6df5e13f1013cee66929d934d34225b7bbb372.tar.gz",
|
||||
.hash = "122040d748f2fed10c4786077ef2b6edbe65ab15c741a6450befab5867e3f828eb24",
|
||||
.url = "git+https://github.com/paoda/nfd-zig#ad81729d33da30d5f4fd23718debec48245121ca",
|
||||
.hash = "1220a679380847513262c8c5c474d4a415f9ecc4921c8c6aefbdbdce66cf2aa19ceb",
|
||||
},
|
||||
.@"known-folders" = .{
|
||||
.url = "https://github.com/ziglibs/known-folders/archive/03b70c3179e404c0d03b5480ddda52d4e0c8b744.tar.gz",
|
||||
.hash = "1220480ebdabd57a5aa79889239c7f353a76a83a0185cced414589cf7bae29fcd272",
|
||||
.url = "git+https://github.com/ziglibs/known-folders#1cceeb70e77dec941a4178160ff6c8d05a74de6f",
|
||||
.hash = "12205f5e7505c96573f6fc5144592ec38942fb0a326d692f9cddc0c7dd38f9028f29",
|
||||
},
|
||||
.@"zig-datetime" = .{
|
||||
.url = "https://github.com/frmdstryr/zig-datetime/archive/ddecb4e508e99ad6ab1314378225413959d54756.tar.gz",
|
||||
.hash = "12202cbb909feb6b09164ac997307c6b1ab35cb05a846198cf41f7ec608d842c1761",
|
||||
.url = "git+https://github.com/frmdstryr/zig-datetime#70aebf28fb3e137cd84123a9349d157a74708721",
|
||||
.hash = "122077215ce36e125a490e59ec1748ffd4f6ba00d4d14f7308978e5360711d72d77f",
|
||||
},
|
||||
.@"zig-clap" = .{
|
||||
.url = "https://github.com/Hejsil/zig-clap/archive/bdb5853b678d68f342ec65b04a6785af522ca6c9.tar.gz",
|
||||
.hash = "12202af04ec78191f2018458a7be29f54e0d9118f7688e7a226857acf754d68b8473",
|
||||
},
|
||||
.@"zba-gdbstub" = .{
|
||||
.url = "https://git.musuka.dev/paoda/zba-gdbstub/archive/dc159b4aebfa4a6dbafcd0ad0006ee9fe53a8bad.tar.gz",
|
||||
.hash = "122021a32734d559087b912c72be759709d7f57758d747b2ae0176e88a7a3bf39ad6",
|
||||
.url = "git+https://github.com/Hejsil/zig-clap#c0193e9247335a6c1688b946325060289405de2a",
|
||||
.hash = "12207ee987ce045596cb992cfb15b0d6d9456e50d4721c3061c69dabc2962053644d",
|
||||
},
|
||||
.@"zba-util" = .{
|
||||
.url = "https://git.musuka.dev/paoda/zba-util/archive/322c798e384a0d24cc84ffcfa2e4a3ca807798a0.tar.gz",
|
||||
.hash = "12209ce0e729460b997706e47a53a32f1842672cd120189e612f4871731780a30ed0",
|
||||
.url = "git+https://git.musuka.dev/paoda/zba-util#bf0e744047ce1ec90172dbcc0c72bfcc29a063e3",
|
||||
.hash = "1220d044ecfbeacc3b3cebeff131d587e24167d61435a3cb96dffd4d4521bb06aed0",
|
||||
},
|
||||
.@"zba-gdbstub" = .{
|
||||
.url = "git+https://git.musuka.dev/paoda/zba-gdbstub#9a50607d5f48293f950a4e823344f2bc24582a5a",
|
||||
.hash = "1220ac267744ed2a735f03c4620d7c6210fbd36d7bfb2b376ddc3436faebadee0f61",
|
||||
},
|
||||
.tomlz = .{
|
||||
.url = "https://github.com/mattyhall/tomlz/archive/1a6f112183fa04ec88b2cab2a2e71cb1f8a40dd1.tar.gz",
|
||||
.hash = "122098fcc1a1699303f7207d7ca3528750a972d927be2942eef4cd454ee52036650d",
|
||||
.url = "git+https://github.com/paoda/tomlz#9a16dd53927ef2012478b6494bafb4475e44f4c9",
|
||||
.hash = "12204f922cab84980e36b5c058d354ec0ee169bda401c8e0e80a463580349b476569",
|
||||
},
|
||||
.arm32 = .{
|
||||
.url = "https://git.musuka.dev/paoda/arm32/archive/6c81608c5914888858abea56514707712d64272a.tar.gz",
|
||||
.hash = "1220b305f4ecba1918479e60dbe31c99f7763a0ce3883e7dcf30d24e0dc11f44a9c9",
|
||||
.url = "git+https://git.musuka.dev/paoda/arm32#814d081ea0983bc48841a6baad7158c157b17ad6",
|
||||
.hash = "12203c3dacf3a7aa7aee5fc5763dd7b40399bd1c34d1483330b6bd5a76bffef22d82",
|
||||
},
|
||||
.zgui = .{
|
||||
.url = "git+https://git.musuka.dev/paoda/zgui#7f8d05101e96c64314d7926c80ee157dcb89da4e",
|
||||
.hash = "1220bd81a1c7734892b1d4233ed047710487787873c85dd5fc76d1764a331ed2ff43",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
36
dl_sdl2.ps1
Normal file
36
dl_sdl2.ps1
Normal file
@@ -0,0 +1,36 @@
|
||||
$SDL2Version = "2.30.0"
|
||||
$ArchiveFile = ".\SDL2-devel-mingw.zip"
|
||||
$Json = @"
|
||||
{
|
||||
"x86_64-windows-gnu": {
|
||||
"include": ".build_config\\SDL2\\include",
|
||||
"libs": ".build_config\\SDL2\\lib",
|
||||
"bin": ".build_config\\SDL2\\bin"
|
||||
}
|
||||
}
|
||||
"@
|
||||
|
||||
New-Item -Force -ItemType Directory -Path .\.build_config
|
||||
Set-Location -Path .build_config -PassThru
|
||||
|
||||
if (!(Test-Path -PathType Leaf $ArchiveFile)) {
|
||||
Invoke-WebRequest "https://github.com/libsdl-org/SDL/releases/download/release-$SDL2Version/SDL2-devel-$SDL2Version-mingw.zip" -OutFile $ArchiveFile
|
||||
}
|
||||
|
||||
Expand-Archive $ArchiveFile
|
||||
|
||||
if (Test-Path -PathType Container .\SDL2) {
|
||||
Remove-Item -Recurse .\SDL2
|
||||
}
|
||||
|
||||
New-Item -Force -ItemType Directory -Path .\SDL2
|
||||
Get-ChildItem -Path ".\SDL2-devel-mingw\SDL2-$SDL2Version\x86_64-w64-mingw32" | Move-Item -Destination .\SDL2
|
||||
|
||||
# #include <SDL.h>
|
||||
Move-Item -Force -Path .\SDL2\include\SDL2\* -Destination .\SDL2\include
|
||||
Remove-Item -Force .\SDL2\include\SDL2
|
||||
|
||||
New-Item -Force .\sdl.json -Value $Json
|
||||
|
||||
Remove-Item -Recurse .\SDL2-devel-mingw
|
||||
Set-Location -Path .. -PassThru
|
||||
Submodule lib/SDL.zig updated: 4d565b5422...fac81ec499
1
lib/zgui
1
lib/zgui
Submodule lib/zgui deleted from 25fa193b1f
@@ -18,7 +18,7 @@ pub fn read(self: *const Self, comptime T: type, nr30: io.WaveSelect, addr: u32)
|
||||
const base = if (!nr30.bank.read()) @as(u32, 0x10) else 0; // Read from the Opposite Bank in Use
|
||||
|
||||
const i = base + addr - 0x0400_0090;
|
||||
return std.mem.readIntSliceLittle(T, self.buf[i..][0..@sizeOf(T)]);
|
||||
return std.mem.readInt(T, self.buf[i..][0..@sizeOf(T)], .little);
|
||||
}
|
||||
|
||||
pub fn write(self: *Self, comptime T: type, nr30: io.WaveSelect, addr: u32, value: T) void {
|
||||
@@ -26,7 +26,7 @@ pub fn write(self: *Self, comptime T: type, nr30: io.WaveSelect, addr: u32, valu
|
||||
const base = if (!nr30.bank.read()) @as(u32, 0x10) else 0; // Write to the Opposite Bank in Use
|
||||
|
||||
const i = base + addr - 0x0400_0090;
|
||||
std.mem.writeIntSliceLittle(T, self.buf[i..][0..@sizeOf(T)], value);
|
||||
std.mem.writeInt(T, self.buf[i..][0..@sizeOf(T)], value, .little);
|
||||
}
|
||||
|
||||
pub fn init(sched: *Scheduler) Self {
|
||||
|
||||
@@ -51,7 +51,7 @@ fn _read(self: *const Self, comptime T: type, addr: u32) T {
|
||||
const buf = self.buf orelse std.debug.panic("[BIOS] ZBA tried to read {} from 0x{X:0>8} but not BIOS was present", .{ T, addr });
|
||||
|
||||
return switch (T) {
|
||||
u32, u16, u8 => std.mem.readIntSliceLittle(T, buf[addr..][0..@sizeOf(T)]),
|
||||
u32, u16, u8 => std.mem.readInt(T, buf[addr..][0..@sizeOf(T)], .little),
|
||||
else => @compileError("BIOS: Unsupported read width"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ pub fn read(self: *const Self, comptime T: type, address: usize) T {
|
||||
const addr = address & 0x3FFFF;
|
||||
|
||||
return switch (T) {
|
||||
u32, u16, u8 => std.mem.readIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)]),
|
||||
u32, u16, u8 => std.mem.readInt(T, self.buf[addr..][0..@sizeOf(T)], .little),
|
||||
else => @compileError("EWRAM: Unsupported read width"),
|
||||
};
|
||||
}
|
||||
@@ -20,7 +20,7 @@ pub fn write(self: *const Self, comptime T: type, address: usize, value: T) void
|
||||
const addr = address & 0x3FFFF;
|
||||
|
||||
return switch (T) {
|
||||
u32, u16, u8 => std.mem.writeIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)], value),
|
||||
u32, u16, u8 => std.mem.writeInt(T, self.buf[addr..][0..@sizeOf(T)], value, .little),
|
||||
else => @compileError("EWRAM: Unsupported write width"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ pub fn read(self: *const Self, comptime T: type, address: usize) T {
|
||||
const addr = address & 0x7FFF;
|
||||
|
||||
return switch (T) {
|
||||
u32, u16, u8 => std.mem.readIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)]),
|
||||
u32, u16, u8 => std.mem.readInt(T, self.buf[addr..][0..@sizeOf(T)], .little),
|
||||
else => @compileError("IWRAM: Unsupported read width"),
|
||||
};
|
||||
}
|
||||
@@ -20,7 +20,7 @@ pub fn write(self: *const Self, comptime T: type, address: usize, value: T) void
|
||||
const addr = address & 0x7FFF;
|
||||
|
||||
return switch (T) {
|
||||
u32, u16, u8 => std.mem.writeIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)], value),
|
||||
u32, u16, u8 => std.mem.writeInt(T, self.buf[addr..][0..@sizeOf(T)], value, .little),
|
||||
else => @compileError("IWRAM: Unsupported write width"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ pub const Eeprom = struct {
|
||||
.Large => {
|
||||
if (self.writer.len() == 14) {
|
||||
const addr: u10 = @intCast(self.writer.finish());
|
||||
const value = std.mem.readIntSliceLittle(u64, buf[@as(u13, addr) * 8 ..][0..8]);
|
||||
const value = std.mem.readInt(u64, buf[@as(u13, addr) * 8 ..][0..8], .little);
|
||||
|
||||
self.reader.configure(value);
|
||||
self.state = .RequestEnd;
|
||||
@@ -131,7 +131,7 @@ pub const Eeprom = struct {
|
||||
if (self.writer.len() == 6) {
|
||||
// FIXME: Duplicated code from above
|
||||
const addr: u6 = @intCast(self.writer.finish());
|
||||
const value = std.mem.readIntSliceLittle(u64, buf[@as(u13, addr) * 8 ..][0..8]);
|
||||
const value = std.mem.readInt(u64, buf[@as(u13, addr) * 8 ..][0..8], .little);
|
||||
|
||||
self.reader.configure(value);
|
||||
self.state = .RequestEnd;
|
||||
@@ -159,7 +159,7 @@ pub const Eeprom = struct {
|
||||
},
|
||||
.WriteTransfer => {
|
||||
if (self.writer.len() == 64) {
|
||||
std.mem.writeIntSliceLittle(u64, buf[self.addr * 8 ..][0..8], self.writer.finish());
|
||||
std.mem.writeInt(u64, buf[self.addr * 8 ..][0..8], self.writer.finish(), .little);
|
||||
self.state = .RequestEnd;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -96,7 +96,7 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) ?T {
|
||||
0x0400_0128 => util.io.read.todo(log, "Read {} from SIOCNT", .{T}),
|
||||
|
||||
// Keypad Input
|
||||
0x0400_0130 => bus.io.keyinput.load(.Monotonic),
|
||||
0x0400_0130 => bus.io.keyinput.load(.monotonic),
|
||||
|
||||
// Serial Communication 2
|
||||
0x0400_0134 => util.io.read.todo(log, "Read {} from RCNT", .{T}),
|
||||
@@ -382,7 +382,7 @@ pub const KeyInput = extern union {
|
||||
|
||||
const AtomicKeyInput = struct {
|
||||
const Self = @This();
|
||||
const Ordering = std.atomic.Ordering;
|
||||
const AtomicOrder = std.builtin.AtomicOrder;
|
||||
|
||||
inner: KeyInput,
|
||||
|
||||
@@ -390,18 +390,18 @@ const AtomicKeyInput = struct {
|
||||
return .{ .inner = value };
|
||||
}
|
||||
|
||||
pub inline fn load(self: *const Self, comptime ordering: Ordering) u16 {
|
||||
pub inline fn load(self: *const Self, comptime ordering: AtomicOrder) u16 {
|
||||
return switch (ordering) {
|
||||
.AcqRel, .Release => @compileError("not supported for atomic loads"),
|
||||
.acq_rel, .release => @compileError("not supported for atomic loads"),
|
||||
else => @atomicLoad(u16, &self.inner.raw, ordering),
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn fetchOr(self: *Self, value: u16, comptime ordering: Ordering) void {
|
||||
pub inline fn fetchOr(self: *Self, value: u16, comptime ordering: AtomicOrder) void {
|
||||
_ = @atomicRmw(u16, &self.inner.raw, .Or, value, ordering);
|
||||
}
|
||||
|
||||
pub inline fn fetchAnd(self: *Self, value: u16, comptime ordering: Ordering) void {
|
||||
pub inline fn fetchAnd(self: *Self, value: u16, comptime ordering: AtomicOrder) void {
|
||||
_ = @atomicRmw(u16, &self.inner.raw, .And, value, ordering);
|
||||
}
|
||||
};
|
||||
|
||||
125
src/core/emu.zig
125
src/core/emu.zig
@@ -6,15 +6,39 @@ const Scheduler = @import("scheduler.zig").Scheduler;
|
||||
const Arm7tdmi = @import("arm32").Arm7tdmi;
|
||||
const Bus = @import("Bus.zig");
|
||||
const Tracker = @import("../util.zig").FpsTracker;
|
||||
const Channel = @import("zba-util").Channel(Message, 0x100);
|
||||
|
||||
pub const Message = enum { Pause, Resume, Quit };
|
||||
const Channel = @import("../util.zig").Queue;
|
||||
|
||||
const stepDmaTransfer = @import("cpu_util.zig").stepDmaTransfer;
|
||||
const isHalted = @import("cpu_util.zig").isHalted;
|
||||
|
||||
const Timer = std.time.Timer;
|
||||
|
||||
pub const Synchro = struct {
|
||||
const AtomicBool = std.atomic.Value(bool);
|
||||
|
||||
// FIXME: This Enum ends up being really LARGE!!!
|
||||
pub const Message = union(enum) {
|
||||
rom_path: [std.fs.MAX_PATH_BYTES]u8,
|
||||
bios_path: [std.fs.MAX_PATH_BYTES]u8,
|
||||
restart: void,
|
||||
};
|
||||
|
||||
paused: AtomicBool = AtomicBool.init(true), // FIXME: can ui_busy and paused be the same?
|
||||
should_quit: AtomicBool = AtomicBool.init(false),
|
||||
|
||||
ch: Channel(Message),
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) !@This() {
|
||||
const msg_buf = try allocator.alloc(Message, 1);
|
||||
return .{ .ch = Channel(Message).init(msg_buf) };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
|
||||
allocator.free(self.ch.inner.buf);
|
||||
self.* = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/// 4 Cycles in 1 dot
|
||||
const cycles_per_dot = 4;
|
||||
|
||||
@@ -41,18 +65,18 @@ const RunKind = enum {
|
||||
LimitedFPS,
|
||||
};
|
||||
|
||||
pub fn run(cpu: *Arm7tdmi, scheduler: *Scheduler, tracker: *Tracker, rx: Channel.Receiver) 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", .{});
|
||||
|
||||
if (config.config().guest.video_sync) {
|
||||
inner(.LimitedFPS, audio_sync, cpu, scheduler, tracker, rx);
|
||||
inner(.LimitedFPS, audio_sync, cpu, scheduler, tracker, sync);
|
||||
} else {
|
||||
inner(.UnlimitedFPS, audio_sync, cpu, scheduler, tracker, rx);
|
||||
inner(.UnlimitedFPS, audio_sync, cpu, scheduler, tracker, sync);
|
||||
}
|
||||
}
|
||||
|
||||
fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *Scheduler, tracker: ?*Tracker, rx: Channel.Receiver) 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", .{});
|
||||
@@ -60,19 +84,15 @@ fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *S
|
||||
|
||||
const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr));
|
||||
|
||||
var paused: bool = false;
|
||||
// FIXME: audioSync accesses emulator state without any guarantees
|
||||
|
||||
switch (kind) {
|
||||
.Unlimited, .UnlimitedFPS => {
|
||||
log.info("Emulation w/out video sync", .{});
|
||||
|
||||
while (true) {
|
||||
if (rx.recv()) |m| switch (m) {
|
||||
.Quit => break,
|
||||
.Resume, .Pause => paused = m == .Pause,
|
||||
};
|
||||
|
||||
if (paused) continue;
|
||||
while (!sync.should_quit.load(.monotonic)) {
|
||||
handleChannel(cpu, &sync.ch);
|
||||
if (sync.paused.load(.monotonic)) continue;
|
||||
|
||||
runFrame(scheduler, cpu);
|
||||
audioSync(audio_sync, bus_ptr.apu.stream, &bus_ptr.apu.is_buffer_full);
|
||||
@@ -85,13 +105,9 @@ fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *S
|
||||
var timer = Timer.start() catch @panic("failed to initalize std.timer.Timer");
|
||||
var wake_time: u64 = frame_period;
|
||||
|
||||
while (true) {
|
||||
if (rx.recv()) |m| switch (m) {
|
||||
.Quit => break,
|
||||
.Resume, .Pause => paused = m == .Pause,
|
||||
};
|
||||
|
||||
if (paused) continue;
|
||||
while (!sync.should_quit.load(.monotonic)) {
|
||||
handleChannel(cpu, &sync.ch);
|
||||
if (sync.paused.load(.monotonic)) continue;
|
||||
|
||||
runFrame(scheduler, cpu);
|
||||
const new_wake_time = videoSync(&timer, wake_time);
|
||||
@@ -111,6 +127,22 @@ fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *S
|
||||
}
|
||||
}
|
||||
|
||||
inline fn handleChannel(cpu: *Arm7tdmi, channel: *Channel(Synchro.Message)) void {
|
||||
const message = channel.pop() orelse return;
|
||||
|
||||
switch (message) {
|
||||
.rom_path => |path_buf| {
|
||||
const path = std.mem.sliceTo(&path_buf, 0);
|
||||
replaceGamepak(cpu, path) catch |e| log.err("failed to replace GamePak: {}", .{e});
|
||||
},
|
||||
.bios_path => |path_buf| {
|
||||
const path = std.mem.sliceTo(&path_buf, 0);
|
||||
replaceBios(cpu, path) catch |e| log.err("failed to replace BIOS: {}", .{e});
|
||||
},
|
||||
.restart => reset(cpu),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void {
|
||||
const frame_end = sched.tick + cycles_per_frame;
|
||||
|
||||
@@ -197,6 +229,49 @@ pub const EmuThing = struct {
|
||||
const Interface = @import("gdbstub").Emulator;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
pub const target =
|
||||
\\<target version="1.0">
|
||||
\\ <architecture>armv4t</architecture>
|
||||
\\ <feature name="org.gnu.gdb.arm.core">
|
||||
\\ <reg name="r0" bitsize="32" type="uint32"/>
|
||||
\\ <reg name="r1" bitsize="32" type="uint32"/>
|
||||
\\ <reg name="r2" bitsize="32" type="uint32"/>
|
||||
\\ <reg name="r3" bitsize="32" type="uint32"/>
|
||||
\\ <reg name="r4" bitsize="32" type="uint32"/>
|
||||
\\ <reg name="r5" bitsize="32" type="uint32"/>
|
||||
\\ <reg name="r6" bitsize="32" type="uint32"/>
|
||||
\\ <reg name="r7" bitsize="32" type="uint32"/>
|
||||
\\ <reg name="r8" bitsize="32" type="uint32"/>
|
||||
\\ <reg name="r9" bitsize="32" type="uint32"/>
|
||||
\\ <reg name="r10" bitsize="32" type="uint32"/>
|
||||
\\ <reg name="r11" bitsize="32" type="uint32"/>
|
||||
\\ <reg name="r12" bitsize="32" type="uint32"/>
|
||||
\\ <reg name="sp" bitsize="32" type="data_ptr"/>
|
||||
\\ <reg name="lr" bitsize="32"/>
|
||||
\\ <reg name="pc" bitsize="32" type="code_ptr"/>
|
||||
\\
|
||||
\\ <reg name="cpsr" bitsize="32" regnum="25"/>
|
||||
\\ </feature>
|
||||
\\</target>
|
||||
;
|
||||
|
||||
// Game Pak SRAM isn't included
|
||||
// TODO: Can i be more specific here?
|
||||
pub const map =
|
||||
\\ <memory-map version="1.0">
|
||||
\\ <memory type="rom" start="0x00000000" length="0x00004000"/>
|
||||
\\ <memory type="ram" start="0x02000000" length="0x00040000"/>
|
||||
\\ <memory type="ram" start="0x03000000" length="0x00008000"/>
|
||||
\\ <memory type="ram" start="0x04000000" length="0x00000400"/>
|
||||
\\ <memory type="ram" start="0x05000000" length="0x00000400"/>
|
||||
\\ <memory type="ram" start="0x06000000" length="0x00018000"/>
|
||||
\\ <memory type="ram" start="0x07000000" length="0x00000400"/>
|
||||
\\ <memory type="rom" start="0x08000000" length="0x02000000"/>
|
||||
\\ <memory type="rom" start="0x0A000000" length="0x02000000"/>
|
||||
\\ <memory type="rom" start="0x0C000000" length="0x02000000"/>
|
||||
\\ </memory-map>
|
||||
;
|
||||
|
||||
cpu: *Arm7tdmi,
|
||||
scheduler: *Scheduler,
|
||||
|
||||
@@ -248,21 +323,21 @@ pub const EmuThing = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub fn reset(cpu: *Arm7tdmi) void {
|
||||
fn reset(cpu: *Arm7tdmi) void {
|
||||
// @breakpoint();
|
||||
cpu.sched.reset(); // Yes this is order sensitive, see the PPU reset for why
|
||||
cpu.bus.reset();
|
||||
cpu.reset();
|
||||
}
|
||||
|
||||
pub fn replaceGamepak(cpu: *Arm7tdmi, file_path: []const u8) !void {
|
||||
fn replaceGamepak(cpu: *Arm7tdmi, file_path: []const u8) !void {
|
||||
const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr));
|
||||
|
||||
try bus_ptr.replaceGamepak(file_path);
|
||||
reset(cpu);
|
||||
}
|
||||
|
||||
pub fn replaceBios(cpu: *Arm7tdmi, file_path: []const u8) !void {
|
||||
fn replaceBios(cpu: *Arm7tdmi, file_path: []const u8) !void {
|
||||
const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr));
|
||||
|
||||
const allocator = bus_ptr.bios.allocator;
|
||||
|
||||
@@ -12,7 +12,7 @@ pub fn read(self: *const Self, comptime T: type, address: usize) T {
|
||||
const addr = address & 0x3FF;
|
||||
|
||||
return switch (T) {
|
||||
u32, u16, u8 => std.mem.readIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)]),
|
||||
u32, u16, u8 => std.mem.readInt(T, self.buf[addr..][0..@sizeOf(T)], .little),
|
||||
else => @compileError("OAM: Unsupported read width"),
|
||||
};
|
||||
}
|
||||
@@ -21,7 +21,7 @@ pub fn write(self: *Self, comptime T: type, address: usize, value: T) void {
|
||||
const addr = address & 0x3FF;
|
||||
|
||||
switch (T) {
|
||||
u32, u16 => std.mem.writeIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)], value),
|
||||
u32, u16 => std.mem.writeInt(T, self.buf[addr..][0..@sizeOf(T)], value, .little),
|
||||
u8 => return, // 8-bit writes are explicitly ignored
|
||||
else => @compileError("OAM: Unsupported write width"),
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ pub fn read(self: *const Self, comptime T: type, address: usize) T {
|
||||
const addr = address & 0x3FF;
|
||||
|
||||
return switch (T) {
|
||||
u32, u16, u8 => std.mem.readIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)]),
|
||||
u32, u16, u8 => std.mem.readInt(T, self.buf[addr..][0..@sizeOf(T)], .little),
|
||||
else => @compileError("PALRAM: Unsupported read width"),
|
||||
};
|
||||
}
|
||||
@@ -21,10 +21,10 @@ pub fn write(self: *Self, comptime T: type, address: usize, value: T) void {
|
||||
const addr = address & 0x3FF;
|
||||
|
||||
switch (T) {
|
||||
u32, u16 => std.mem.writeIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)], value),
|
||||
u32, u16 => std.mem.writeInt(T, self.buf[addr..][0..@sizeOf(T)], value, .little),
|
||||
u8 => {
|
||||
const align_addr = addr & ~@as(u32, 1); // Aligned to Halfword boundary
|
||||
std.mem.writeIntSliceLittle(u16, self.buf[align_addr..][0..@sizeOf(u16)], @as(u16, value) * 0x101);
|
||||
std.mem.writeInt(u16, self.buf[align_addr..][0..@sizeOf(u16)], @as(u16, value) * 0x101, .little);
|
||||
},
|
||||
else => @compileError("PALRAM: Unsupported write width"),
|
||||
}
|
||||
@@ -47,5 +47,5 @@ pub fn deinit(self: *Self) void {
|
||||
}
|
||||
|
||||
pub inline fn backdrop(self: *const Self) u16 {
|
||||
return std.mem.readIntNative(u16, self.buf[0..2]);
|
||||
return std.mem.readInt(u16, self.buf[0..2], .little);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ pub fn read(self: *const Self, comptime T: type, address: usize) T {
|
||||
const addr = Self.mirror(address);
|
||||
|
||||
return switch (T) {
|
||||
u32, u16, u8 => std.mem.readIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)]),
|
||||
u32, u16, u8 => std.mem.readInt(T, self.buf[addr..][0..@sizeOf(T)], .little),
|
||||
else => @compileError("VRAM: Unsupported read width"),
|
||||
};
|
||||
}
|
||||
@@ -23,7 +23,7 @@ pub fn write(self: *Self, comptime T: type, dispcnt: io.DisplayControl, address:
|
||||
const idx = Self.mirror(address);
|
||||
|
||||
switch (T) {
|
||||
u32, u16 => std.mem.writeIntSliceLittle(T, self.buf[idx..][0..@sizeOf(T)], value),
|
||||
u32, u16 => std.mem.writeInt(T, self.buf[idx..][0..@sizeOf(T)], value, .little),
|
||||
u8 => {
|
||||
// Ignore write if it falls within the boundaries of OBJ VRAM
|
||||
switch (mode) {
|
||||
@@ -32,7 +32,7 @@ pub fn write(self: *Self, comptime T: type, dispcnt: io.DisplayControl, address:
|
||||
}
|
||||
|
||||
const align_idx = idx & ~@as(u32, 1); // Aligned to a halfword boundary
|
||||
std.mem.writeIntSliceLittle(u16, self.buf[align_idx..][0..@sizeOf(u16)], @as(u16, value) * 0x101);
|
||||
std.mem.writeInt(u16, self.buf[align_idx..][0..@sizeOf(u16)], @as(u16, value) * 0x101, .little);
|
||||
},
|
||||
else => @compileError("VRAM: Unsupported write width"),
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ pub const Scheduler = struct {
|
||||
// invalidates the slice we're iterating over
|
||||
_ = self.queue.removeIndex(i);
|
||||
|
||||
log.debug("Removed {?}@{}", .{ event.kind, event.tick });
|
||||
// log.debug("Removed {?}@{}", .{ event.kind, event.tick });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ const Gui = @import("platform.zig").Gui;
|
||||
const Arm7tdmi = @import("arm32").Arm7tdmi;
|
||||
const Scheduler = @import("core/scheduler.zig").Scheduler;
|
||||
const Bus = @import("core/Bus.zig");
|
||||
const Synchro = @import("core/emu.zig").Synchro;
|
||||
|
||||
const RingBuffer = @import("zba-util").RingBuffer;
|
||||
const Dimensions = @import("platform.zig").Dimensions;
|
||||
@@ -70,11 +71,13 @@ pub const State = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub fn draw(state: *State, win_dim: Dimensions, tex_id: GLuint, cpu: *Arm7tdmi) bool {
|
||||
pub fn draw(state: *State, sync: *Synchro, dim: Dimensions, cpu: *const Arm7tdmi, tex_id: GLuint) bool {
|
||||
const scn_scale = config.config().host.win_scale;
|
||||
const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr));
|
||||
|
||||
zgui.backend.newFrame(@floatFromInt(win_dim.width), @floatFromInt(win_dim.height));
|
||||
zgui.backend.newFrame(@floatFromInt(dim.width), @floatFromInt(dim.height));
|
||||
|
||||
state.title = handleTitle(&bus_ptr.pak.title);
|
||||
|
||||
{
|
||||
_ = zgui.beginMainMenuBar();
|
||||
@@ -87,42 +90,59 @@ pub fn draw(state: *State, win_dim: Dimensions, tex_id: GLuint, cpu: *Arm7tdmi)
|
||||
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});
|
||||
const file_path = tmp: {
|
||||
const path_opt = nfd.openFileDialog("gba", null) catch |e| {
|
||||
log.err("file dialog failed to open: {}", .{e});
|
||||
break :blk;
|
||||
};
|
||||
|
||||
const file_path = maybe_path orelse {
|
||||
break :tmp path_opt orelse {
|
||||
log.warn("did not receive a file path", .{});
|
||||
break :blk;
|
||||
};
|
||||
};
|
||||
defer nfd.freePath(file_path);
|
||||
|
||||
log.info("user chose: \"{s}\"", .{file_path});
|
||||
emu.replaceGamepak(cpu, file_path) catch |e| {
|
||||
log.err("failed to replace GamePak: {}", .{e});
|
||||
|
||||
const message = tmp: {
|
||||
var msg: Synchro.Message = .{ .rom_path = undefined };
|
||||
@memcpy(msg.rom_path[0..file_path.len], file_path);
|
||||
break :tmp msg;
|
||||
};
|
||||
|
||||
sync.ch.push(message) catch |e| {
|
||||
log.err("failed to send file path to emu thread: {}", .{e});
|
||||
break :blk;
|
||||
};
|
||||
|
||||
state.title = handleTitle(&bus_ptr.pak.title);
|
||||
state.emulation = .{ .Transition = .Active };
|
||||
}
|
||||
|
||||
if (zgui.menuItem("Load BIOS", .{})) blk: {
|
||||
const maybe_path = nfd.openFileDialog("bin", null) catch |e| {
|
||||
log.err("failed to open file dialog: {}", .{e});
|
||||
const file_path = tmp: {
|
||||
const path_opt = nfd.openFileDialog("bin", null) catch |e| {
|
||||
log.err("file dialog failed to open: {}", .{e});
|
||||
break :blk;
|
||||
};
|
||||
|
||||
const file_path = maybe_path orelse {
|
||||
break :tmp path_opt orelse {
|
||||
log.warn("did not receive a file path", .{});
|
||||
break :blk;
|
||||
};
|
||||
};
|
||||
defer nfd.freePath(file_path);
|
||||
|
||||
log.info("user chose: \"{s}\"", .{file_path});
|
||||
emu.replaceBios(cpu, file_path) catch |e| {
|
||||
log.err("failed to replace BIOS: {}", .{e});
|
||||
|
||||
const message = tmp: {
|
||||
var msg: Synchro.Message = .{ .bios_path = undefined };
|
||||
@memcpy(msg.bios_path[0..file_path.len], file_path);
|
||||
break :tmp msg;
|
||||
};
|
||||
|
||||
sync.ch.push(message) catch |e| {
|
||||
log.err("failed to send file path to emu thread: {}", .{e});
|
||||
break :blk;
|
||||
};
|
||||
}
|
||||
@@ -149,7 +169,7 @@ pub fn draw(state: *State, win_dim: Dimensions, tex_id: GLuint, cpu: *Arm7tdmi)
|
||||
}
|
||||
|
||||
if (zgui.menuItem("Restart", .{}))
|
||||
emu.reset(cpu);
|
||||
sync.ch.push(.restart) catch |e| log.err("failed to send restart req to emu thread: {}", .{e});
|
||||
}
|
||||
|
||||
if (zgui.beginMenu("Stats", true)) {
|
||||
@@ -175,7 +195,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?
|
||||
@@ -324,9 +344,9 @@ pub fn draw(state: *State, win_dim: Dimensions, tex_id: GLuint, cpu: *Arm7tdmi)
|
||||
const Event = std.meta.Child(@TypeOf(sched_ptr.queue.items));
|
||||
|
||||
var items: [20]Event = undefined;
|
||||
const len = sched_ptr.queue.len;
|
||||
const len = @min(sched_ptr.queue.items.len, items.len);
|
||||
|
||||
@memcpy(&items, sched_ptr.queue.items);
|
||||
@memcpy(items[0..len], sched_ptr.queue.items[0..len]);
|
||||
std.mem.sort(Event, items[0..len], {}, widgets.eventDesc(Event));
|
||||
|
||||
for (items[0..len]) |event| {
|
||||
@@ -345,9 +365,9 @@ pub fn draw(state: *State, win_dim: Dimensions, tex_id: GLuint, cpu: *Arm7tdmi)
|
||||
widgets.paletteGrid(.Object, cpu);
|
||||
}
|
||||
|
||||
{
|
||||
zgui.showDemoWindow(null);
|
||||
}
|
||||
// {
|
||||
// zgui.showDemoWindow(null);
|
||||
// }
|
||||
|
||||
return true; // request redraw
|
||||
}
|
||||
|
||||
75
src/main.zig
75
src/main.zig
@@ -6,7 +6,7 @@ const clap = @import("clap");
|
||||
const config = @import("config.zig");
|
||||
const emu = @import("core/emu.zig");
|
||||
|
||||
const Channel = @import("zba-util").Channel(emu.Message, 0x100);
|
||||
const Synchro = @import("core/emu.zig").Synchro;
|
||||
const Gui = @import("platform.zig").Gui;
|
||||
const Bus = @import("core/Bus.zig");
|
||||
const Scheduler = @import("core/scheduler.zig").Scheduler;
|
||||
@@ -61,7 +61,8 @@ pub fn main() void {
|
||||
defer allocator.free(config_path);
|
||||
|
||||
// Parse CLI
|
||||
const result = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{}) catch |e| exitln("failed to parse cli: {}", .{e});
|
||||
|
||||
const result = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{ .allocator = allocator }) catch |e| exitln("failed to parse cli: {}", .{e});
|
||||
defer result.deinit();
|
||||
|
||||
// TODO: Move config file to XDG Config directory?
|
||||
@@ -70,8 +71,24 @@ pub fn main() void {
|
||||
|
||||
config.load(allocator, cfg_file_path) catch |e| exitln("failed to load config file: {}", .{e});
|
||||
|
||||
const paths = handleArguments(allocator, data_path, &result) catch |e| exitln("failed to handle cli arguments: {}", .{e});
|
||||
defer if (paths.save) |path| allocator.free(path);
|
||||
var paths = handleArguments(allocator, data_path, &result) catch |e| exitln("failed to handle cli arguments: {}", .{e});
|
||||
defer paths.deinit(allocator);
|
||||
|
||||
// if paths.bios is null, then we want to see if it's in the data directory
|
||||
if (paths.bios == null) blk: {
|
||||
const bios_path = std.mem.join(allocator, "/", &.{ data_path, "zba", "gba_bios.bin" }) catch |e| exitln("failed to allocate backup bios dir path: {}", .{e});
|
||||
defer allocator.free(bios_path);
|
||||
|
||||
_ = std.fs.cwd().statFile(bios_path) catch |e| switch (e) {
|
||||
error.FileNotFound => { // ZBA will crash on attempt to read BIOS but that's fine
|
||||
log.err("file located at {s} was not found", .{bios_path});
|
||||
break :blk;
|
||||
},
|
||||
else => exitln("error when checking \"{s}\": {}", .{ bios_path, e }),
|
||||
};
|
||||
|
||||
paths.bios = allocator.dupe(u8, bios_path) catch |e| exitln("failed to duplicate path to bios: {}", .{e});
|
||||
}
|
||||
|
||||
const log_file = switch (config.config().debug.cpu_trace) {
|
||||
true => std.fs.cwd().createFile("zba.log", .{}) catch |e| exitln("failed to create trace log file: {}", .{e}),
|
||||
@@ -85,8 +102,8 @@ pub fn main() void {
|
||||
|
||||
var bus: Bus = undefined;
|
||||
|
||||
var ischeduler = IScheduler.init(&scheduler);
|
||||
var ibus = IBus.init(&bus);
|
||||
const ischeduler = IScheduler.init(&scheduler);
|
||||
const ibus = IBus.init(&bus);
|
||||
|
||||
var cpu = Arm7tdmi.init(ischeduler, ibus);
|
||||
|
||||
@@ -103,10 +120,8 @@ 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 quit = std.atomic.Atomic(bool).init(false);
|
||||
|
||||
var ch = Channel.init(allocator) catch |e| exitln("failed to initialize ui -> emu thread message channel: {}", .{e});
|
||||
defer ch.deinit(allocator);
|
||||
var sync = Synchro.init(allocator) catch |e| exitln("failed to allocate sync types: {}", .{e});
|
||||
defer sync.deinit(allocator);
|
||||
|
||||
if (result.args.gdb != 0) {
|
||||
const Server = @import("gdbstub").Server;
|
||||
@@ -118,46 +133,48 @@ pub fn main() void {
|
||||
|
||||
log.info("Ready to connect", .{});
|
||||
|
||||
var server = Server.init(emulator) catch |e| exitln("failed to init gdb server: {}", .{e});
|
||||
var server = Server.init(
|
||||
emulator,
|
||||
.{ .memory_map = EmuThing.map, .target = EmuThing.target },
|
||||
) catch |e| exitln("failed to init gdb server: {}", .{e});
|
||||
defer server.deinit(allocator);
|
||||
|
||||
log.info("Starting GDB Server Thread", .{});
|
||||
|
||||
const thread = std.Thread.spawn(.{}, Server.run, .{ &server, allocator, &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(.Debug, .{
|
||||
gui.run(.{
|
||||
.cpu = &cpu,
|
||||
.scheduler = &scheduler,
|
||||
.ch = ch.tx,
|
||||
.sync = &sync,
|
||||
}) catch |e| exitln("main thread panicked: {}", .{e});
|
||||
} else {
|
||||
var tracker = FpsTracker.init();
|
||||
|
||||
// emu should start paused if there's no ROM to run
|
||||
if (paths.rom == null)
|
||||
ch.tx.send(.Pause);
|
||||
|
||||
const thread = std.Thread.spawn(.{}, emu.run, .{ &cpu, &scheduler, &tracker, ch.rx }) 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(.Standard, .{
|
||||
gui.run(.{
|
||||
.cpu = &cpu,
|
||||
.scheduler = &scheduler,
|
||||
.ch = ch.tx,
|
||||
.tracker = &tracker,
|
||||
.sync = &sync,
|
||||
}) catch |e| exitln("main thread panicked: {}", .{e});
|
||||
}
|
||||
}
|
||||
|
||||
fn handleArguments(allocator: Allocator, data_path: []const u8, result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) !FilePaths {
|
||||
const rom_path = romPath(result);
|
||||
log.info("ROM path: {?s}", .{rom_path});
|
||||
const rom_path = try romPath(allocator, result);
|
||||
errdefer if (rom_path) |path| allocator.free(path);
|
||||
|
||||
const bios_path = result.args.bios;
|
||||
if (bios_path) |path| log.info("BIOS path: {s}", .{path}) else log.warn("No BIOS provided", .{});
|
||||
const bios_path: ?[]const u8 = if (result.args.bios) |path| try allocator.dupe(u8, path) else null;
|
||||
errdefer if (bios_path) |path| allocator.free(path);
|
||||
|
||||
const save_path = try std.fs.path.join(allocator, &[_][]const u8{ data_path, "zba", "save" });
|
||||
|
||||
log.info("ROM path: {?s}", .{rom_path});
|
||||
log.info("BIOS path: {?s}", .{bios_path});
|
||||
log.info("Save path: {s}", .{save_path});
|
||||
|
||||
return .{
|
||||
@@ -180,7 +197,7 @@ fn configFilePath(allocator: Allocator, config_path: []const u8) ![]const u8 {
|
||||
const config_file = std.fs.createFileAbsolute(path, .{}) catch |err| exitln("failed to create \"{s}\": {}", .{ path, err });
|
||||
defer config_file.close();
|
||||
|
||||
try config_file.writeAll(@embedFile("../example.toml"));
|
||||
try config_file.writeAll(@embedFile("example.toml"));
|
||||
};
|
||||
|
||||
return path;
|
||||
@@ -201,10 +218,10 @@ fn ensureConfigDirExists(config_path: []const u8) !void {
|
||||
try dir.makePath("zba");
|
||||
}
|
||||
|
||||
fn romPath(result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) ?[]const u8 {
|
||||
fn romPath(allocator: Allocator, result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) !?[]const u8 {
|
||||
return switch (result.positionals.len) {
|
||||
0 => null,
|
||||
1 => result.positionals[0],
|
||||
1 => try allocator.dupe(u8, result.positionals[0]),
|
||||
else => exitln("ZBA received too many positional arguments.", .{}),
|
||||
};
|
||||
}
|
||||
@@ -213,5 +230,5 @@ fn exitln(comptime format: []const u8, args: anytype) noreturn {
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
stderr.print(format, args) catch {}; // Just exit already...
|
||||
stderr.writeByte('\n') catch {};
|
||||
std.os.exit(1);
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
158
src/platform.zig
158
src/platform.zig
@@ -12,7 +12,7 @@ const Arm7tdmi = @import("arm32").Arm7tdmi;
|
||||
const Bus = @import("core/Bus.zig");
|
||||
const Scheduler = @import("core/scheduler.zig").Scheduler;
|
||||
const FpsTracker = @import("util.zig").FpsTracker;
|
||||
const Channel = @import("zba-util").Channel(emu.Message, 0x100);
|
||||
const Synchro = @import("core/emu.zig").Synchro;
|
||||
const KeyInput = @import("core/bus/io.zig").KeyInput;
|
||||
|
||||
const gba_width = @import("core/ppu.zig").width;
|
||||
@@ -95,33 +95,30 @@ pub const Gui = struct {
|
||||
}
|
||||
|
||||
const RunOptions = struct {
|
||||
ch: Channel.Sender,
|
||||
sync: *Synchro,
|
||||
tracker: ?*FpsTracker = null,
|
||||
cpu: *Arm7tdmi,
|
||||
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 ch = opt.ch;
|
||||
const sync = opt.sync;
|
||||
|
||||
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;
|
||||
@@ -129,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) {
|
||||
@@ -156,7 +153,7 @@ pub const Gui = struct {
|
||||
else => {},
|
||||
}
|
||||
|
||||
bus_ptr.io.keyinput.fetchAnd(~keyinput.raw, .Monotonic);
|
||||
bus_ptr.io.keyinput.fetchAnd(~keyinput.raw, .monotonic);
|
||||
},
|
||||
SDL.SDL_KEYUP => {
|
||||
// TODO: Make use of compare_and_xor?
|
||||
@@ -177,7 +174,7 @@ pub const Gui = struct {
|
||||
else => {},
|
||||
}
|
||||
|
||||
bus_ptr.io.keyinput.fetchOr(keyinput.raw, .Monotonic);
|
||||
bus_ptr.io.keyinput.fetchOr(keyinput.raw, .monotonic);
|
||||
},
|
||||
SDL.SDL_WINDOWEVENT => {
|
||||
if (event.window.event == SDL.SDL_WINDOWEVENT_RESIZED) {
|
||||
@@ -196,51 +193,20 @@ pub const Gui = struct {
|
||||
switch (self.state.emulation) {
|
||||
.Transition => |inner| switch (inner) {
|
||||
.Active => {
|
||||
ch.send(.Resume);
|
||||
sync.paused.store(false, .monotonic);
|
||||
if (!config.config().host.mute) SDL.SDL_PauseAudioDevice(self.audio.device, 0);
|
||||
|
||||
self.state.emulation = .Active;
|
||||
},
|
||||
.Inactive => {
|
||||
// Assert that double pausing is impossible
|
||||
|
||||
SDL.SDL_PauseAudioDevice(self.audio.device, 1);
|
||||
ch.send(.Pause);
|
||||
sync.paused.store(true, .monotonic);
|
||||
|
||||
self.state.emulation = .Inactive;
|
||||
},
|
||||
},
|
||||
.Active => {
|
||||
const is_std = mode == .Standard;
|
||||
|
||||
if (is_std) ch.send(.Pause);
|
||||
defer if (is_std) ch.send(.Resume);
|
||||
|
||||
// switch (mode) {
|
||||
// .Standard => blk: {
|
||||
// const limit = 15; // TODO: What should this be?
|
||||
|
||||
// // TODO: learn more about std.atomic.spinLoopHint();
|
||||
// for (0..limit) |_| {
|
||||
// const message = channel.gui.pop() orelse continue;
|
||||
|
||||
// switch (message) {
|
||||
// .Paused => break :blk,
|
||||
// .Quit => unreachable,
|
||||
// }
|
||||
// }
|
||||
|
||||
// log.info("timed out waiting for emu thread to pause (limit: {})", .{limit});
|
||||
// break :skip_draw;
|
||||
// },
|
||||
// .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...
|
||||
// }
|
||||
// },
|
||||
// }
|
||||
|
||||
// Add FPS count to the histogram
|
||||
if (tracker) |t| self.state.fps_hist.push(t.value()) catch {};
|
||||
|
||||
@@ -249,9 +215,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)
|
||||
@@ -260,9 +225,9 @@ pub const Gui = struct {
|
||||
SDL.SDL_LockAudioDevice(self.audio.device);
|
||||
defer SDL.SDL_UnlockAudioDevice(self.audio.device);
|
||||
|
||||
zgui_redraw = imgui.draw(&self.state, win_dim, out_tex, cpu);
|
||||
zgui_redraw = imgui.draw(&self.state, sync, win_dim, cpu, out_tex);
|
||||
},
|
||||
.Inactive => zgui_redraw = imgui.draw(&self.state, win_dim, out_tex, cpu),
|
||||
.Inactive => zgui_redraw = imgui.draw(&self.state, sync, win_dim, cpu, out_tex),
|
||||
}
|
||||
|
||||
if (zgui_redraw) {
|
||||
@@ -278,7 +243,7 @@ pub const Gui = struct {
|
||||
SDL.SDL_GL_SwapWindow(self.window);
|
||||
}
|
||||
|
||||
ch.send(.Quit);
|
||||
sync.should_quit.store(true, .monotonic);
|
||||
}
|
||||
|
||||
fn glGetProcAddress(ctx: SDL.SDL_GLContext, proc: [:0]const u8) ?*anyopaque {
|
||||
@@ -330,44 +295,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");
|
||||
|
||||
@@ -387,50 +332,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);
|
||||
|
||||
@@ -445,7 +362,7 @@ const opengl_impl = struct {
|
||||
return tex_id;
|
||||
}
|
||||
|
||||
fn createOutputTexture() GLuint {
|
||||
fn outTex() GLuint {
|
||||
var tex_id: GLuint = undefined;
|
||||
gl.genTextures(1, &tex_id);
|
||||
|
||||
@@ -460,7 +377,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);
|
||||
|
||||
@@ -468,9 +385,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;
|
||||
@@ -479,8 +394,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;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#version 330 core
|
||||
out vec4 frag_color;
|
||||
|
||||
in vec3 color;
|
||||
in vec2 uv;
|
||||
|
||||
uniform sampler2D screen;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
44
src/util.zig
44
src/util.zig
@@ -11,24 +11,24 @@ pub const FpsTracker = struct {
|
||||
const Self = @This();
|
||||
|
||||
fps: u32,
|
||||
count: std.atomic.Atomic(u32),
|
||||
count: std.atomic.Value(u32),
|
||||
timer: std.time.Timer,
|
||||
|
||||
pub fn init() Self {
|
||||
return .{
|
||||
.fps = 0,
|
||||
.count = std.atomic.Atomic(u32).init(0),
|
||||
.count = std.atomic.Value(u32).init(0),
|
||||
.timer = std.time.Timer.start() catch unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn tick(self: *Self) void {
|
||||
_ = self.count.fetchAdd(1, .Monotonic);
|
||||
_ = self.count.fetchAdd(1, .monotonic);
|
||||
}
|
||||
|
||||
pub fn value(self: *Self) u32 {
|
||||
if (self.timer.read() >= std.time.ns_per_s) {
|
||||
self.fps = self.count.swap(0, .Monotonic);
|
||||
self.fps = self.count.swap(0, .monotonic);
|
||||
self.timer.reset();
|
||||
}
|
||||
|
||||
@@ -52,7 +52,13 @@ pub fn escape(title: [12]u8) [12]u8 {
|
||||
pub const FilePaths = struct {
|
||||
rom: ?[]const u8,
|
||||
bios: ?[]const u8,
|
||||
save: ?[]const u8,
|
||||
save: []const u8,
|
||||
|
||||
pub fn deinit(self: @This(), allocator: Allocator) void {
|
||||
if (self.rom) |path| allocator.free(path);
|
||||
if (self.bios) |path| allocator.free(path);
|
||||
allocator.free(self.save);
|
||||
}
|
||||
};
|
||||
|
||||
pub const io = struct {
|
||||
@@ -289,3 +295,31 @@ pub const FrameBuffer = struct {
|
||||
return self.layers[if (dev == .Emulator) self.current else ~self.current];
|
||||
}
|
||||
};
|
||||
|
||||
const RingBuffer = @import("zba-util").RingBuffer;
|
||||
|
||||
// TODO: Lock Free Queue?
|
||||
pub fn Queue(comptime T: type) type {
|
||||
return struct {
|
||||
inner: RingBuffer(T),
|
||||
mtx: std.Thread.Mutex = .{},
|
||||
|
||||
pub fn init(buf: []T) @This() {
|
||||
return .{ .inner = RingBuffer(T).init(buf) };
|
||||
}
|
||||
|
||||
pub fn push(self: *@This(), value: T) !void {
|
||||
self.mtx.lock();
|
||||
defer self.mtx.unlock();
|
||||
|
||||
try self.inner.push(value);
|
||||
}
|
||||
|
||||
pub fn pop(self: *@This()) ?T {
|
||||
self.mtx.lock();
|
||||
defer self.mtx.unlock();
|
||||
|
||||
return self.inner.pop();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user