Compare commits
16 Commits
6cacdc7180
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e43c4a2cb9 | |||
| 276b928ee2 | |||
| de711a37c2 | |||
| 38fabc92c5 | |||
| f4c8bd9099 | |||
| 242060e35d | |||
| 497a62c16d | |||
| 08f56ef4e8 | |||
| 53bb41e151 | |||
| 5b2bd0b6b6 | |||
| 10308e2dd0 | |||
| af47c00749 | |||
| 32fb30e954 | |||
| c3d687fe6a | |||
| 5841ecea46 | |||
| bd02f625a5 |
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
|||||||
[submodule "lib/SDL.zig"]
|
|
||||||
path = lib/SDL.zig
|
|
||||||
url = https://github.com/paoda/SDL.zig
|
|
||||||
30
README.md
30
README.md
@@ -27,35 +27,9 @@ Finally it's worth noting that ZBA uses a TOML config file it'll store in your O
|
|||||||
|
|
||||||
## Compiling
|
## Compiling
|
||||||
|
|
||||||
Most recently built on Zig [v0.11.0](https://github.com/ziglang/zig/tree/0.11.0)
|
Most recently built on Zig [v0.15.1](https://github.com/ziglang/zig/tree/0.15.1)
|
||||||
|
|
||||||
### Dependencies
|
Run `zig build -Doptimize=ReleaseSafe`. The executable will be under `zig-out/bin` and the shared libraries (if enabled) under `zig-out/lib`. If working with shared libraries on windows, be sure to add all artifacts to the same directory. On Unix, you'll want to make use of `LD_PRELOAD`.
|
||||||
|
|
||||||
Dependency | Source
|
|
||||||
--- | ---
|
|
||||||
known-folders | <https://github.com/ziglibs/known-folders>
|
|
||||||
nfd-zig | <https://github.com/fabioarnold/nfd-zig>
|
|
||||||
SDL.zig | <https://github.com/MasterQ32/SDL.zig>
|
|
||||||
tomlz | <https://github.com/mattyhall/tomlz>
|
|
||||||
zba-gdbstub | <https://github.com/paoda/zba-gdbstub>
|
|
||||||
zba-util | <https://git.musuka.dev/paoda/zba-util>
|
|
||||||
zgui | <https://github.com/michal-z/zig-gamedev/tree/main/libs/zgui>
|
|
||||||
zig-clap | <https://github.com/Hejsil/zig-clap>
|
|
||||||
zig-datetime | <https://github.com/frmdstryr/zig-datetime>
|
|
||||||
`bitfield.zig` | [https://github.com/FlorenceOS/Florence](https://github.com/FlorenceOS/Florence/blob/aaa5a9e568/lib/util/bitfields.zig)
|
|
||||||
`gl.zig` | <https://github.com/MasterQ32/zig-opengl>
|
|
||||||
|
|
||||||
Use `git submodule update --init` from the project root to pull the git relevant git submodules
|
|
||||||
|
|
||||||
Be sure to provide SDL2 using:
|
|
||||||
|
|
||||||
- Linux: Your distro's package manager
|
|
||||||
- macOS: ¯\\\_(ツ)_/¯ (try [this formula](https://formulae.brew.sh/formula/sdl2)?)
|
|
||||||
- Windows: [`vcpkg`](https://github.com/Microsoft/vcpkg) (install `sdl2:x64-windows`)
|
|
||||||
|
|
||||||
`SDL.zig` will provide a helpful compile error if the zig compiler is unable to find SDL2.
|
|
||||||
|
|
||||||
Once you've got all the dependencies, execute `zig build -Doptimize=ReleaseSafe`. The executable will be under `zig-out/bin` and the shared libraries (if enabled) under `zig-out/lib`. If working with shared libraries on windows, be sure to add all artifacts to the same directory. On Unix, you'll want to make use of `LD_PRELOAD`.
|
|
||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
|
|
||||||
|
|||||||
179
build.zig
179
build.zig
@@ -1,66 +1,159 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
|
||||||
|
|
||||||
const sdl = @import("lib/SDL.zig/build.zig");
|
|
||||||
|
|
||||||
const SemVer = std.SemanticVersion;
|
|
||||||
|
|
||||||
const target_version = "0.13.0";
|
|
||||||
|
|
||||||
|
// Although this function looks imperative, it does not perform the build
|
||||||
|
// directly and instead it mutates the build graph (`b`) that will be then
|
||||||
|
// executed by an external runner. The functions in `std.Build` implement a DSL
|
||||||
|
// for defining build steps and express dependencies between them, allowing the
|
||||||
|
// build runner to parallelize the build automatically (and the cache system to
|
||||||
|
// know when a step doesn't need to be re-run).
|
||||||
pub fn build(b: *std.Build) void {
|
pub fn build(b: *std.Build) void {
|
||||||
const actual_version = builtin.zig_version;
|
// Standard target options allow the person running `zig build` to choose
|
||||||
if (comptime actual_version.order(SemVer.parse(target_version) catch unreachable) != .eq) {
|
// what target to build for. Here we do not override the defaults, which
|
||||||
@compileError("ZBA must be built with Zig v" ++ target_version ++ ".");
|
// means any target is allowed, and the default is native. Other options
|
||||||
}
|
// for restricting supported target set are available.
|
||||||
|
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
|
// Standard optimization options allow the person running `zig build` to select
|
||||||
|
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
|
||||||
|
// set a preferred release mode, allowing the user to decide how to optimize.
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
// It's also possible to define more custom flags to toggle optional features
|
||||||
|
// of this build script using `b.option()`. All defined flags (including
|
||||||
|
// target and optimize options) will be listed when running `zig build --help`
|
||||||
|
// in this directory.
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
// This creates a module, which represents a collection of source files alongside
|
||||||
.name = "zba",
|
// some compilation options, such as optimization mode and linked system libraries.
|
||||||
|
// Zig modules are the preferred way of making Zig code available to consumers.
|
||||||
|
// addModule defines a module that we intend to make available for importing
|
||||||
|
// to our consumers. We must give it a name because a Zig package can expose
|
||||||
|
// multiple modules and consumers will need to be able to specify which
|
||||||
|
// module they want to access.
|
||||||
|
const exe_mod = b.addModule("zba", .{
|
||||||
|
// The root source file is the "entry point" of this module. Users of
|
||||||
|
// this module will only be able to access public declarations contained
|
||||||
|
// in this file, which means that if you have declarations that you
|
||||||
|
// intend to expose to consumers that were defined in other files part
|
||||||
|
// of this module, you will have to make sure to re-export them from
|
||||||
|
// the root file.
|
||||||
.root_source_file = b.path("src/main.zig"),
|
.root_source_file = b.path("src/main.zig"),
|
||||||
|
// Later on we'll use this module as the root module of a test executable
|
||||||
|
// which requires us to specify a target.
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
const sdk = sdl.init(b, null, null);
|
// Here we define an executable. An executable needs to have a root module
|
||||||
const zgui = b.dependency("zgui", .{ .shared = false, .with_implot = true, .backend = .sdl2_opengl3 });
|
// which needs to expose a `main` function. While we could add a main function
|
||||||
const imgui = zgui.artifact("imgui");
|
// to the module defined above, it's sometimes preferable to split business
|
||||||
|
// business logic and the CLI into two separate modules.
|
||||||
|
//
|
||||||
|
// If your goal is to create a Zig library for others to use, consider if
|
||||||
|
// it might benefit from also exposing a CLI tool. A parser library for a
|
||||||
|
// data serialization format could also bundle a CLI syntax checker, for example.
|
||||||
|
//
|
||||||
|
// If instead your goal is to create an executable, consider if users might
|
||||||
|
// be interested in also being able to embed the core functionality of your
|
||||||
|
// program in their own executable in order to avoid the overhead involved in
|
||||||
|
// subprocessing your CLI tool.
|
||||||
|
//
|
||||||
|
// If neither case applies to you, feel free to delete the declaration you
|
||||||
|
// don't need and to put everything under a single module.
|
||||||
|
const exe = b.addExecutable(.{ .name = "zba", .root_module = exe_mod });
|
||||||
|
|
||||||
exe.root_module.addImport("known_folders", b.dependency("known-folders", .{}).module("known-folders")); // https://github.com/ziglibs/known-folders
|
const zgui = b.dependency("zgui", .{ .shared = false, .with_implot = true, .backend = .sdl3_opengl3 });
|
||||||
exe.root_module.addImport("datetime", b.dependency("zig-datetime", .{}).module("zig-datetime")); // https://github.com/frmdstryr/zig-datetime
|
const sdl = b.dependency("sdl", .{ .target = target, .optimize = optimize, .preferred_linkage = .static });
|
||||||
exe.root_module.addImport("clap", b.dependency("zig-clap", .{}).module("clap")); // https://github.com/Hejsil/zig-clap
|
const gl = @import("zigglgen").generateBindingsModule(b, .{ .api = .gl, .version = .@"3.3", .profile = .core });
|
||||||
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
|
|
||||||
|
|
||||||
exe.root_module.addAnonymousImport("bitfield", .{ .root_source_file = b.path("lib/bitfield.zig") }); // https://github.com/FlorenceOS/
|
const sdl_lib = sdl.artifact("SDL3");
|
||||||
exe.root_module.addAnonymousImport("gl", .{ .root_source_file = b.path("lib/gl.zig") }); // https://github.com/MasterQ32/zig-opengl
|
const zgui_lib = zgui.artifact("imgui");
|
||||||
exe.root_module.addAnonymousImport("example.toml", .{ .root_source_file = b.path("example.toml") });
|
|
||||||
|
|
||||||
sdk.link(exe, .dynamic, .SDL2);
|
zgui_lib.linkLibrary(sdl_lib);
|
||||||
sdk.link(imgui, .dynamic, .SDL2);
|
|
||||||
exe.linkLibrary(imgui);
|
|
||||||
|
|
||||||
|
exe_mod.linkLibrary(sdl_lib);
|
||||||
|
exe_mod.linkLibrary(zgui_lib);
|
||||||
|
|
||||||
|
exe_mod.addImport("gl", gl);
|
||||||
|
exe_mod.addImport("known_folders", b.dependency("known_folders", .{}).module("known-folders"));
|
||||||
|
exe_mod.addImport("datetime", b.dependency("datetime", .{}).module("datetime"));
|
||||||
|
exe_mod.addImport("clap", b.dependency("clap", .{}).module("clap"));
|
||||||
|
exe_mod.addImport("zba_util", b.dependency("zba_util", .{}).module("zba_util"));
|
||||||
|
exe_mod.addImport("arm32", b.dependency("arm32", .{}).module("arm32"));
|
||||||
|
exe_mod.addImport("gdbstub", b.dependency("zba_gdbstub", .{}).module("zba_gdbstub"));
|
||||||
|
exe_mod.addImport("nfd", b.dependency("nfdzig", .{}).module("nfd"));
|
||||||
|
exe_mod.addImport("zgui", zgui.module("root"));
|
||||||
|
exe_mod.addImport("bitjuggle", b.dependency("bitjuggle", .{}).module("bitjuggle"));
|
||||||
|
exe_mod.addImport("toml", b.dependency("toml", .{}).module("toml"));
|
||||||
|
|
||||||
|
exe_mod.addAnonymousImport("example.toml", .{ .root_source_file = b.path("example.toml") });
|
||||||
|
|
||||||
|
// This declares intent for the executable to be installed into the
|
||||||
|
// install prefix when running `zig build` (i.e. when executing the default
|
||||||
|
// step). By default the install prefix is `zig-out/` but can be overridden
|
||||||
|
// by passing `--prefix` or `-p`.
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
|
|
||||||
const run_cmd = b.addRunArtifact(exe);
|
// This creates a top level step. Top level steps have a name and can be
|
||||||
run_cmd.step.dependOn(b.getInstallStep());
|
// invoked by name when running `zig build` (e.g. `zig build run`).
|
||||||
if (b.args) |args| run_cmd.addArgs(args);
|
// This will evaluate the `run` step rather than the default step.
|
||||||
|
// For a top level step to actually do something, it must depend on other
|
||||||
|
// steps (e.g. a Run step, as we will see in a moment).
|
||||||
const run_step = b.step("run", "Run the app");
|
const run_step = b.step("run", "Run the app");
|
||||||
|
|
||||||
|
// This creates a RunArtifact step in the build graph. A RunArtifact step
|
||||||
|
// invokes an executable compiled by Zig. Steps will only be executed by the
|
||||||
|
// runner if invoked directly by the user (in the case of top level steps)
|
||||||
|
// or if another step depends on it, so it's up to you to define when and
|
||||||
|
// how this Run step will be executed. In our case we want to run it when
|
||||||
|
// the user runs `zig build run`, so we create a dependency link.
|
||||||
|
const run_cmd = b.addRunArtifact(exe);
|
||||||
run_step.dependOn(&run_cmd.step);
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
|
||||||
const exe_tests = b.addTest(.{
|
// By making the run step depend on the default step, it will be run from the
|
||||||
.root_source_file = b.path("src/main.zig"),
|
// installation directory rather than directly from within the cache directory.
|
||||||
.target = target,
|
run_cmd.step.dependOn(b.getInstallStep());
|
||||||
.optimize = optimize,
|
|
||||||
|
// This allows the user to pass arguments to the application in the build
|
||||||
|
// command itself, like this: `zig build run -- arg1 arg2 etc`
|
||||||
|
if (b.args) |args| {
|
||||||
|
run_cmd.addArgs(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates an executable that will run `test` blocks from the provided module.
|
||||||
|
// Here `mod` needs to define a target, which is why earlier we made sure to
|
||||||
|
// set the releative field.
|
||||||
|
const mod_tests = b.addTest(.{
|
||||||
|
.root_module = exe_mod,
|
||||||
});
|
});
|
||||||
|
|
||||||
const test_step = b.step("test", "Run unit tests");
|
// A run step that will run the test executable.
|
||||||
test_step.dependOn(&exe_tests.step);
|
const run_mod_tests = b.addRunArtifact(mod_tests);
|
||||||
|
|
||||||
|
// Creates an executable that will run `test` blocks from the executable's
|
||||||
|
// root module. Note that test executables only test one module at a time,
|
||||||
|
// hence why we have to create two separate ones.
|
||||||
|
const exe_tests = b.addTest(.{
|
||||||
|
.root_module = exe.root_module,
|
||||||
|
});
|
||||||
|
|
||||||
|
// A run step that will run the second test executable.
|
||||||
|
const run_exe_tests = b.addRunArtifact(exe_tests);
|
||||||
|
|
||||||
|
// A top level step for running all tests. dependOn can be called multiple
|
||||||
|
// times and since the two run steps do not depend on one another, this will
|
||||||
|
// make the two of them run in parallel.
|
||||||
|
const test_step = b.step("test", "Run tests");
|
||||||
|
test_step.dependOn(&run_mod_tests.step);
|
||||||
|
test_step.dependOn(&run_exe_tests.step);
|
||||||
|
|
||||||
|
// Just like flags, top level steps are also listed in the `--help` menu.
|
||||||
|
//
|
||||||
|
// The Zig build system is entirely implemented in userland, which means
|
||||||
|
// that it cannot hook into private compiler APIs. All compilation work
|
||||||
|
// orchestrated by the build system will result in other Zig compiler
|
||||||
|
// subcommands being invoked with the right flags defined. You can observe
|
||||||
|
// these invocations when one fails (or you pass a flag to increase
|
||||||
|
// verbosity) to validate assumptions and diagnose problems.
|
||||||
|
//
|
||||||
|
// Lastly, the Zig build system is relatively simple and self-contained,
|
||||||
|
// and reading its source code will allow you to master it.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.{
|
.{
|
||||||
.name = "zba",
|
.name = .zba,
|
||||||
.version = "0.1.0",
|
.version = "0.1.0",
|
||||||
.paths = .{
|
.paths = .{
|
||||||
"build.zig",
|
"build.zig",
|
||||||
@@ -8,43 +8,56 @@
|
|||||||
"lib/gl.zig",
|
"lib/gl.zig",
|
||||||
"src",
|
"src",
|
||||||
},
|
},
|
||||||
.minimum_zig_version = "0.13.0",
|
.minimum_zig_version = "0.15.1",
|
||||||
|
.fingerprint = 0xcb596c7fbdb20efc,
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.nfd = .{
|
.known_folders = .{
|
||||||
.url = "git+https://github.com/paoda/nfd-zig#ad81729d33da30d5f4fd23718debec48245121ca",
|
.url = "git+https://github.com/ziglibs/known-folders.git#ab5cf5feb936fa3b72c95d3ad0c0c67791937ba1",
|
||||||
.hash = "1220a679380847513262c8c5c474d4a415f9ecc4921c8c6aefbdbdce66cf2aa19ceb",
|
.hash = "known_folders-0.0.0-Fy-PJtTTAADUOhGKM0sxzG4eMkNQxRvx9e5dfHVyaeA3",
|
||||||
},
|
},
|
||||||
.@"known-folders" = .{
|
.nfdzig = .{
|
||||||
.url = "git+https://github.com/ziglibs/known-folders#1cceeb70e77dec941a4178160ff6c8d05a74de6f",
|
.url = "git+https://github.com/paoda/nfd-zig#0ad2a0c092ffba0c98613d619b82100c991f5ad6",
|
||||||
.hash = "12205f5e7505c96573f6fc5144592ec38942fb0a326d692f9cddc0c7dd38f9028f29",
|
.hash = "nfdzig-0.1.0-11fxvN6IBgD5rvvfjrw1wPqibMsbUJ-h2ZcGR6FOEvrm",
|
||||||
},
|
},
|
||||||
.@"zig-datetime" = .{
|
.datetime = .{
|
||||||
.url = "git+https://github.com/frmdstryr/zig-datetime#70aebf28fb3e137cd84123a9349d157a74708721",
|
.url = "git+https://github.com/frmdstryr/zig-datetime#3a39a21e6e34dcb0ade0ff828d0914d40ba535f3",
|
||||||
.hash = "122077215ce36e125a490e59ec1748ffd4f6ba00d4d14f7308978e5360711d72d77f",
|
.hash = "datetime-0.8.0-cJNXzP_YAQBxQ5hkNNP6ScnG5XsqciJmeP5RVV4xwCBA",
|
||||||
},
|
},
|
||||||
.@"zig-clap" = .{
|
.clap = .{
|
||||||
.url = "git+https://github.com/Hejsil/zig-clap#c0193e9247335a6c1688b946325060289405de2a",
|
.url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.11.0.tar.gz",
|
||||||
.hash = "12207ee987ce045596cb992cfb15b0d6d9456e50d4721c3061c69dabc2962053644d",
|
.hash = "clap-0.11.0-oBajB-HnAQDPCKYzwF7rO3qDFwRcD39Q0DALlTSz5H7e",
|
||||||
},
|
},
|
||||||
.@"zba-util" = .{
|
.sdl = .{
|
||||||
.url = "git+https://git.musuka.dev/paoda/zba-util#bf0e744047ce1ec90172dbcc0c72bfcc29a063e3",
|
.url = "git+https://github.com/castholm/SDL.git#b1913e7c31ad72ecfd3ab04aeac387027754cfaf",
|
||||||
.hash = "1220d044ecfbeacc3b3cebeff131d587e24167d61435a3cb96dffd4d4521bb06aed0",
|
.hash = "sdl-0.3.0+3.2.22-7uIn9Pg3fwGG2IyIOPxxOSVe-75nUng9clt7tXGFLzMr",
|
||||||
},
|
},
|
||||||
.@"zba-gdbstub" = .{
|
.zigglgen = .{
|
||||||
.url = "git+https://git.musuka.dev/paoda/zba-gdbstub#9a50607d5f48293f950a4e823344f2bc24582a5a",
|
.url = "git+https://github.com/castholm/zigglgen.git#a1e969b3e35818785fab0373124f50463793b28a",
|
||||||
.hash = "1220ac267744ed2a735f03c4620d7c6210fbd36d7bfb2b376ddc3436faebadee0f61",
|
.hash = "zigglgen-0.4.0-bmyqLQGMLwA0EPVmSm-Nc6Olb84zBzvwMjguxwKYLf1S",
|
||||||
},
|
},
|
||||||
.tomlz = .{
|
.bitjuggle = .{
|
||||||
.url = "git+https://github.com/paoda/tomlz#9a16dd53927ef2012478b6494bafb4475e44f4c9",
|
.url = "git+https://github.com/leecannon/zig-bitjuggle#80111f4f8c672aaea94a8a189ae2a7c8bbaf883f",
|
||||||
.hash = "12204f922cab84980e36b5c058d354ec0ee169bda401c8e0e80a463580349b476569",
|
.hash = "bitjuggle-2.0.0-SJdU76dvAAARompHEhqKDiwZ4FE4FZ8eHvPvmz5JUOS0",
|
||||||
},
|
|
||||||
.arm32 = .{
|
|
||||||
.url = "git+https://git.musuka.dev/paoda/arm32#814d081ea0983bc48841a6baad7158c157b17ad6",
|
|
||||||
.hash = "12203c3dacf3a7aa7aee5fc5763dd7b40399bd1c34d1483330b6bd5a76bffef22d82",
|
|
||||||
},
|
},
|
||||||
.zgui = .{
|
.zgui = .{
|
||||||
.url = "git+https://git.musuka.dev/paoda/zgui#7f8d05101e96c64314d7926c80ee157dcb89da4e",
|
.url = "git+https://github.com/zig-gamedev/zgui#7fa8081c208885b85e3fdfc043cd9d9cb9559123",
|
||||||
.hash = "1220bd81a1c7734892b1d4233ed047710487787873c85dd5fc76d1764a331ed2ff43",
|
.hash = "zgui-0.6.0-dev--L6sZL7tbQAPRLYrcQAVx0V49tPHAXNxclZ-v8IP4wLr",
|
||||||
|
},
|
||||||
|
.toml = .{
|
||||||
|
.url = "git+https://github.com/sam701/zig-toml?ref=zig-0.15#475b03c630c802f8b6bd3e239d8fc2279b4fadb8",
|
||||||
|
.hash = "toml-0.3.0-bV14BfV7AQD8DkuQI7skP8ekQTaBYKTO0MY_35Cw_EXo",
|
||||||
|
},
|
||||||
|
.zba_util = .{
|
||||||
|
.url = "git+https://git.musuka.dev/paoda/zba-util#d75962ac9bdf9b6f0d37e37d5097ea9bb9c16779",
|
||||||
|
.hash = "zba_util-0.0.0-SK3QiakqAABPCct0WbLsuHFfmxwzH99FmEVznCBKzdmR",
|
||||||
|
},
|
||||||
|
.arm32 = .{
|
||||||
|
.url = "git+https://git.musuka.dev/paoda/arm32#c9360e1e239300d8c80bcbc3a7494f015a82c4e1",
|
||||||
|
.hash = "arm32-0.0.0--_LW1VwTAgAbqwOzVfjnkClkwBrB8IZ1LSz8N4EoA3U3",
|
||||||
|
},
|
||||||
|
.zba_gdbstub = .{
|
||||||
|
.url = "git+https://git.musuka.dev/paoda/zba-gdbstub#7b77964e145ac874424a190ecd390596601a40d1",
|
||||||
|
.hash = "zba_gdbstub-0.0.0-KDf2GNiPAAAR5QuteF1MsN3XGElNvHLlRJ7O4PNFvTmK",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
36
dl_sdl2.ps1
36
dl_sdl2.ps1
@@ -1,36 +0,0 @@
|
|||||||
$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 deleted from fac81ec499
146
lib/bitfield.zig
146
lib/bitfield.zig
@@ -1,146 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
fn PtrCastPreserveCV(comptime T: type, comptime PtrToT: type, comptime NewT: type) type {
|
|
||||||
return switch (PtrToT) {
|
|
||||||
*T => *NewT,
|
|
||||||
*const T => *const NewT,
|
|
||||||
*volatile T => *volatile NewT,
|
|
||||||
*const volatile T => *const volatile NewT,
|
|
||||||
|
|
||||||
else => @compileError("wtf you doing"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn BitType(comptime FieldType: type, comptime ValueType: type, comptime shamt: usize) type {
|
|
||||||
const self_bit: FieldType = (1 << shamt);
|
|
||||||
|
|
||||||
return extern struct {
|
|
||||||
bits: Bitfield(FieldType, shamt, 1),
|
|
||||||
|
|
||||||
pub fn set(self: anytype) void {
|
|
||||||
self.bits.field().* |= self_bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unset(self: anytype) void {
|
|
||||||
self.bits.field().* &= ~self_bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read(self: anytype) ValueType {
|
|
||||||
return @bitCast(@as(u1, @truncate(self.bits.field().* >> shamt)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since these are mostly used with MMIO, I want to avoid
|
|
||||||
// reading the memory just to write it again, also races
|
|
||||||
pub fn write(self: anytype, val: ValueType) void {
|
|
||||||
if (@as(bool, @bitCast(val))) {
|
|
||||||
self.set();
|
|
||||||
} else {
|
|
||||||
self.unset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Original Bit Constructor
|
|
||||||
// pub fn Bit(comptime FieldType: type, comptime shamt: usize) type {
|
|
||||||
// return BitType(FieldType, u1, shamt);
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn Bit(comptime FieldType: type, comptime shamt: usize) type {
|
|
||||||
return BitType(FieldType, bool, shamt);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn Boolean(comptime FieldType: type, comptime shamt: usize) type {
|
|
||||||
return BitType(FieldType, bool, shamt);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn Bitfield(comptime FieldType: type, comptime shamt: usize, comptime num_bits: usize) type {
|
|
||||||
if (shamt + num_bits > @bitSizeOf(FieldType)) {
|
|
||||||
@compileError("bitfield doesn't fit");
|
|
||||||
}
|
|
||||||
|
|
||||||
const self_mask: FieldType = ((1 << num_bits) - 1) << shamt;
|
|
||||||
|
|
||||||
const ValueType = std.meta.Int(.unsigned, num_bits);
|
|
||||||
|
|
||||||
return extern struct {
|
|
||||||
dummy: FieldType,
|
|
||||||
|
|
||||||
fn field(self: anytype) PtrCastPreserveCV(@This(), @TypeOf(self), FieldType) {
|
|
||||||
return @ptrCast(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write(self: anytype, val: ValueType) void {
|
|
||||||
self.field().* &= ~self_mask;
|
|
||||||
self.field().* |= @as(FieldType, @intCast(val)) << shamt;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read(self: anytype) ValueType {
|
|
||||||
const val: FieldType = self.field().*;
|
|
||||||
return @intCast((val & self_mask) >> shamt);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test "bit" {
|
|
||||||
const S = extern union {
|
|
||||||
low: Bit(u32, 0),
|
|
||||||
high: Bit(u32, 1),
|
|
||||||
val: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
std.testing.expect(@sizeOf(S) == 4);
|
|
||||||
std.testing.expect(@bitSizeOf(S) == 32);
|
|
||||||
|
|
||||||
var s: S = .{ .val = 1 };
|
|
||||||
|
|
||||||
std.testing.expect(s.low.read() == 1);
|
|
||||||
std.testing.expect(s.high.read() == 0);
|
|
||||||
|
|
||||||
s.low.write(0);
|
|
||||||
s.high.write(1);
|
|
||||||
|
|
||||||
std.testing.expect(s.val == 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "boolean" {
|
|
||||||
const S = extern union {
|
|
||||||
low: Boolean(u32, 0),
|
|
||||||
high: Boolean(u32, 1),
|
|
||||||
val: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
std.testing.expect(@sizeOf(S) == 4);
|
|
||||||
std.testing.expect(@bitSizeOf(S) == 32);
|
|
||||||
|
|
||||||
var s: S = .{ .val = 2 };
|
|
||||||
|
|
||||||
std.testing.expect(s.low.read() == false);
|
|
||||||
std.testing.expect(s.high.read() == true);
|
|
||||||
|
|
||||||
s.low.write(true);
|
|
||||||
s.high.write(false);
|
|
||||||
|
|
||||||
std.testing.expect(s.val == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "bitfield" {
|
|
||||||
const S = extern union {
|
|
||||||
low: Bitfield(u32, 0, 16),
|
|
||||||
high: Bitfield(u32, 16, 16),
|
|
||||||
val: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
std.testing.expect(@sizeOf(S) == 4);
|
|
||||||
std.testing.expect(@bitSizeOf(S) == 32);
|
|
||||||
|
|
||||||
var s: S = .{ .val = 0x13376969 };
|
|
||||||
|
|
||||||
std.testing.expect(s.low.read() == 0x6969);
|
|
||||||
std.testing.expect(s.high.read() == 0x1337);
|
|
||||||
|
|
||||||
s.low.write(0x1337);
|
|
||||||
s.high.write(0x6969);
|
|
||||||
|
|
||||||
std.testing.expect(s.val == 0x69691337);
|
|
||||||
}
|
|
||||||
5053
lib/gl.zig
5053
lib/gl.zig
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const tomlz = @import("tomlz");
|
const toml = @import("toml");
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
@@ -7,7 +7,6 @@ const log = std.log.scoped(.Config);
|
|||||||
var state: Config = .{};
|
var state: Config = .{};
|
||||||
|
|
||||||
const Config = struct {
|
const Config = struct {
|
||||||
// FIXME: tomlz expects these to be case sensitive
|
|
||||||
host: Host = .{},
|
host: Host = .{},
|
||||||
guest: Guest = .{},
|
guest: Guest = .{},
|
||||||
debug: Debug = .{},
|
debug: Debug = .{},
|
||||||
@@ -50,14 +49,30 @@ pub fn config() *const Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Reads a config file and then loads it into the global state
|
/// Reads a config file and then loads it into the global state
|
||||||
pub fn load(allocator: Allocator, file_path: []const u8) !void {
|
pub fn load(allocator: Allocator, config_path: []const u8) !void {
|
||||||
var config_file = try std.fs.cwd().openFile(file_path, .{});
|
var dir = try std.fs.openDirAbsolute(config_path, .{});
|
||||||
defer config_file.close();
|
defer dir.close();
|
||||||
|
|
||||||
log.info("loaded from {s}", .{file_path});
|
const sub_path = "zba" ++ std.fs.path.sep_str ++ "config.toml";
|
||||||
|
|
||||||
const contents = try config_file.readToEndAlloc(allocator, try config_file.getEndPos());
|
var file = try dir.openFile(sub_path, .{});
|
||||||
|
defer file.close();
|
||||||
|
|
||||||
|
{
|
||||||
|
const path = try std.fs.path.join(allocator, &.{ config_path, sub_path });
|
||||||
|
defer allocator.free(path);
|
||||||
|
|
||||||
|
log.info("loaded from {s}", .{path});
|
||||||
|
}
|
||||||
|
|
||||||
|
const contents = try file.readToEndAlloc(allocator, try file.getEndPos());
|
||||||
defer allocator.free(contents);
|
defer allocator.free(contents);
|
||||||
|
|
||||||
state = try tomlz.parser.decode(Config, allocator, contents);
|
var parser = toml.Parser(Config).init(allocator);
|
||||||
|
defer parser.deinit();
|
||||||
|
|
||||||
|
const parsed = try parser.parseString(contents);
|
||||||
|
defer parsed.deinit();
|
||||||
|
|
||||||
|
state = parsed.value; // FIXME: should copy the struct to state
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ const TimerTuple = @import("bus/timer.zig").TimerTuple;
|
|||||||
const Scheduler = @import("scheduler.zig").Scheduler;
|
const Scheduler = @import("scheduler.zig").Scheduler;
|
||||||
const FilePaths = @import("../util.zig").FilePaths;
|
const FilePaths = @import("../util.zig").FilePaths;
|
||||||
|
|
||||||
const io = @import("bus/io.zig");
|
const _io = @import("bus/io.zig");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const log = std.log.scoped(.Bus);
|
const log = std.log.scoped(.Bus);
|
||||||
|
|
||||||
const createDmaTuple = @import("bus/dma.zig").create;
|
const createDmaTuple = @import("bus/dma.zig").create;
|
||||||
const createTimerTuple = @import("bus/timer.zig").create;
|
const createTimerTuple = @import("bus/timer.zig").create;
|
||||||
const rotr = @import("zba-util").rotr;
|
const rotr = @import("zba_util").rotr;
|
||||||
|
|
||||||
const timings: [2][0x10]u8 = [_][0x10]u8{
|
const timings: [2][0x10]u8 = [_][0x10]u8{
|
||||||
// BIOS, Unused, EWRAM, IWRAM, I/0, PALRAM, VRAM, OAM, ROM0, ROM0, ROM1, ROM1, ROM2, ROM2, SRAM, Unused
|
// BIOS, Unused, EWRAM, IWRAM, I/0, PALRAM, VRAM, OAM, ROM0, ROM0, ROM1, ROM1, ROM2, ROM2, SRAM, Unused
|
||||||
@@ -81,7 +81,7 @@ pub fn init(self: *Self, allocator: Allocator, sched: *Scheduler, cpu: *Arm7tdmi
|
|||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.fillReadTable(read_table);
|
self.fillReadTable(@ptrCast(read_table));
|
||||||
|
|
||||||
// Internal Display Memory behaves differently on 8-bit reads
|
// Internal Display Memory behaves differently on 8-bit reads
|
||||||
self.fillWriteTable(u32, write_tables[0]);
|
self.fillWriteTable(u32, write_tables[0]);
|
||||||
@@ -135,12 +135,10 @@ pub fn replaceGamepak(self: *Self, file_path: []const u8) !void {
|
|||||||
|
|
||||||
self.pak = try GamePak.init(self.allocator, self.cpu, file_path, save_path);
|
self.pak = try GamePak.init(self.allocator, self.cpu, file_path, save_path);
|
||||||
|
|
||||||
const read_ptr: *[table_len]?*const anyopaque = @constCast(self.read_table);
|
// SAFETY: TODO: why do we know this is safe?
|
||||||
const write_ptrs: [2]*[table_len]?*anyopaque = .{ @constCast(self.write_tables[0]), @constCast(self.write_tables[1]) };
|
self.fillReadTable(@ptrCast(@constCast(self.read_table)));
|
||||||
|
self.fillWriteTable(u32, @constCast(self.write_tables[0]));
|
||||||
self.fillReadTable(read_ptr);
|
self.fillWriteTable(u8, @constCast(self.write_tables[1]));
|
||||||
self.fillWriteTable(u32, write_ptrs[0]);
|
|
||||||
self.fillWriteTable(u8, write_ptrs[1]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fillReadTable(self: *Self, table: *[table_len]?*const anyopaque) void {
|
fn fillReadTable(self: *Self, table: *[table_len]?*const anyopaque) void {
|
||||||
@@ -169,7 +167,7 @@ fn fillReadTable(self: *Self, table: *[table_len]?*const anyopaque) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fillWriteTable(self: *Self, comptime T: type, table: *[table_len]?*const anyopaque) void {
|
fn fillWriteTable(self: *Self, comptime T: type, table: *[table_len]?*anyopaque) void {
|
||||||
comptime std.debug.assert(T == u32 or T == u16 or T == u8);
|
comptime std.debug.assert(T == u32 or T == u16 or T == u8);
|
||||||
const vramMirror = @import("ppu/Vram.zig").mirror;
|
const vramMirror = @import("ppu/Vram.zig").mirror;
|
||||||
|
|
||||||
@@ -240,11 +238,11 @@ fn fillReadTableExternal(self: *Self, addr: u32) ?*anyopaque {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn readIo(self: *const Self, comptime T: type, address: u32) T {
|
fn readIo(self: *const Self, comptime T: type, address: u32) T {
|
||||||
return io.read(self, T, address) orelse self.openBus(T, address);
|
return _io.read(self, T, address) orelse self.openBus(T, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn openBus(self: *const Self, comptime T: type, address: u32) T {
|
fn openBus(self: *const Self, comptime T: type, address: u32) T {
|
||||||
@setCold(true);
|
@branchHint(.cold);
|
||||||
const r15 = self.cpu.r[15];
|
const r15 = self.cpu.r[15];
|
||||||
|
|
||||||
const word = blk: {
|
const word = blk: {
|
||||||
@@ -305,7 +303,7 @@ fn openBus(self: *const Self, comptime T: type, address: u32) T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(self: *Self, comptime T: type, unaligned_address: u32) T {
|
pub fn read(self: *Self, comptime T: type, unaligned_address: u32) T {
|
||||||
const bits = @typeInfo(std.math.IntFittingRange(0, page_size - 1)).Int.bits;
|
const bits = @typeInfo(std.math.IntFittingRange(0, page_size - 1)).int.bits;
|
||||||
const page = unaligned_address >> bits;
|
const page = unaligned_address >> bits;
|
||||||
const offset = unaligned_address & (page_size - 1);
|
const offset = unaligned_address & (page_size - 1);
|
||||||
|
|
||||||
@@ -328,7 +326,7 @@ pub fn read(self: *Self, comptime T: type, unaligned_address: u32) T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn dbgRead(self: *const Self, comptime T: type, unaligned_address: u32) T {
|
pub fn dbgRead(self: *const Self, comptime T: type, unaligned_address: u32) T {
|
||||||
const bits = @typeInfo(std.math.IntFittingRange(0, page_size - 1)).Int.bits;
|
const bits = @typeInfo(std.math.IntFittingRange(0, page_size - 1)).int.bits;
|
||||||
const page = unaligned_address >> bits;
|
const page = unaligned_address >> bits;
|
||||||
const offset = unaligned_address & (page_size - 1);
|
const offset = unaligned_address & (page_size - 1);
|
||||||
|
|
||||||
@@ -348,7 +346,7 @@ pub fn dbgRead(self: *const Self, comptime T: type, unaligned_address: u32) T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn slowRead(self: *Self, comptime T: type, unaligned_address: u32) T {
|
fn slowRead(self: *Self, comptime T: type, unaligned_address: u32) T {
|
||||||
@setCold(true);
|
@branchHint(.cold);
|
||||||
|
|
||||||
const page: u8 = @truncate(unaligned_address >> 24);
|
const page: u8 = @truncate(unaligned_address >> 24);
|
||||||
const address = forceAlign(T, unaligned_address);
|
const address = forceAlign(T, unaligned_address);
|
||||||
@@ -419,7 +417,7 @@ fn readBackup(self: *const Self, comptime T: type, unaligned_address: u32) T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(self: *Self, comptime T: type, unaligned_address: u32, value: T) void {
|
pub fn write(self: *Self, comptime T: type, unaligned_address: u32, value: T) void {
|
||||||
const bits = @typeInfo(std.math.IntFittingRange(0, page_size - 1)).Int.bits;
|
const bits = @typeInfo(std.math.IntFittingRange(0, page_size - 1)).int.bits;
|
||||||
const page = unaligned_address >> bits;
|
const page = unaligned_address >> bits;
|
||||||
const offset = unaligned_address & (page_size - 1);
|
const offset = unaligned_address & (page_size - 1);
|
||||||
|
|
||||||
@@ -446,7 +444,7 @@ pub fn write(self: *Self, comptime T: type, unaligned_address: u32, value: T) vo
|
|||||||
|
|
||||||
/// Mostly Identical to `Bus.write`, slowmeme is handled by `Bus.dbgSlowWrite`
|
/// Mostly Identical to `Bus.write`, slowmeme is handled by `Bus.dbgSlowWrite`
|
||||||
pub fn dbgWrite(self: *Self, comptime T: type, unaligned_address: u32, value: T) void {
|
pub fn dbgWrite(self: *Self, comptime T: type, unaligned_address: u32, value: T) void {
|
||||||
const bits = @typeInfo(std.math.IntFittingRange(0, page_size - 1)).Int.bits;
|
const bits = @typeInfo(std.math.IntFittingRange(0, page_size - 1)).int.bits;
|
||||||
const page = unaligned_address >> bits;
|
const page = unaligned_address >> bits;
|
||||||
const offset = unaligned_address & (page_size - 1);
|
const offset = unaligned_address & (page_size - 1);
|
||||||
|
|
||||||
@@ -469,7 +467,7 @@ pub fn dbgWrite(self: *Self, comptime T: type, unaligned_address: u32, value: T)
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn slowWrite(self: *Self, comptime T: type, unaligned_address: u32, value: T) void {
|
fn slowWrite(self: *Self, comptime T: type, unaligned_address: u32, value: T) void {
|
||||||
@setCold(true);
|
@branchHint(.cold);
|
||||||
|
|
||||||
const page: u8 = @truncate(unaligned_address >> 24);
|
const page: u8 = @truncate(unaligned_address >> 24);
|
||||||
const address = forceAlign(T, unaligned_address);
|
const address = forceAlign(T, unaligned_address);
|
||||||
@@ -479,7 +477,7 @@ fn slowWrite(self: *Self, comptime T: type, unaligned_address: u32, value: T) vo
|
|||||||
0x00 => self.bios.write(T, address, value),
|
0x00 => self.bios.write(T, address, value),
|
||||||
0x02 => unreachable, // completely handled by fastmem
|
0x02 => unreachable, // completely handled by fastmem
|
||||||
0x03 => unreachable, // completely handled by fastmem
|
0x03 => unreachable, // completely handled by fastmem
|
||||||
0x04 => io.write(self, T, address, value),
|
0x04 => _io.write(self, T, address, value),
|
||||||
|
|
||||||
// Internal Display Memory
|
// Internal Display Memory
|
||||||
0x05 => self.ppu.palette.write(T, address, value),
|
0x05 => self.ppu.palette.write(T, address, value),
|
||||||
@@ -494,7 +492,7 @@ fn slowWrite(self: *Self, comptime T: type, unaligned_address: u32, value: T) vo
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn dbgSlowWrite(self: *Self, comptime T: type, unaligned_address: u32, value: T) void {
|
fn dbgSlowWrite(self: *Self, comptime T: type, unaligned_address: u32, value: T) void {
|
||||||
@setCold(true);
|
@branchHint(.cold);
|
||||||
|
|
||||||
const page: u8 = @truncate(unaligned_address >> 24);
|
const page: u8 = @truncate(unaligned_address >> 24);
|
||||||
const address = forceAlign(T, unaligned_address);
|
const address = forceAlign(T, unaligned_address);
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const SDL = @import("sdl2");
|
|
||||||
const io = @import("bus/io.zig");
|
const io = @import("bus/io.zig");
|
||||||
const util = @import("../util.zig");
|
const util = @import("../util.zig");
|
||||||
|
const c = @import("../lib.zig").c;
|
||||||
|
|
||||||
|
const LinearFifo = @import("../lib/fifo.zig").LinearFifo;
|
||||||
const Arm7tdmi = @import("arm32").Arm7tdmi;
|
const Arm7tdmi = @import("arm32").Arm7tdmi;
|
||||||
const Bus = @import("Bus.zig");
|
const Bus = @import("Bus.zig");
|
||||||
const Scheduler = @import("scheduler.zig").Scheduler;
|
const Scheduler = @import("scheduler.zig").Scheduler;
|
||||||
@@ -11,7 +12,7 @@ const Tone = @import("apu/Tone.zig");
|
|||||||
const Wave = @import("apu/Wave.zig");
|
const Wave = @import("apu/Wave.zig");
|
||||||
const Noise = @import("apu/Noise.zig");
|
const Noise = @import("apu/Noise.zig");
|
||||||
|
|
||||||
const SoundFifo = std.fifo.LinearFifo(u8, .{ .Static = 0x20 });
|
const SoundFifo = LinearFifo(u8, .{ .static = 0x20 });
|
||||||
|
|
||||||
const getHalf = util.getHalf;
|
const getHalf = util.getHalf;
|
||||||
const setHalf = util.setHalf;
|
const setHalf = util.setHalf;
|
||||||
@@ -246,14 +247,14 @@ pub const Apu = struct {
|
|||||||
|
|
||||||
sampling_cycle: u2,
|
sampling_cycle: u2,
|
||||||
|
|
||||||
stream: *SDL.SDL_AudioStream,
|
// NB: This AudioStream is owned by platform.zig
|
||||||
|
stream: ?*c.SDL_AudioStream = null,
|
||||||
|
|
||||||
sched: *Scheduler,
|
sched: *Scheduler,
|
||||||
|
|
||||||
fs: FrameSequencer,
|
fs: FrameSequencer,
|
||||||
capacitor: f32,
|
capacitor: f32,
|
||||||
|
|
||||||
is_buffer_full: bool,
|
|
||||||
|
|
||||||
pub const Tick = enum { Length, Envelope, Sweep };
|
pub const Tick = enum { Length, Envelope, Sweep };
|
||||||
|
|
||||||
pub fn init(sched: *Scheduler) Self {
|
pub fn init(sched: *Scheduler) Self {
|
||||||
@@ -271,12 +272,10 @@ pub const Apu = struct {
|
|||||||
.bias = .{ .raw = 0x0200 },
|
.bias = .{ .raw = 0x0200 },
|
||||||
|
|
||||||
.sampling_cycle = 0b00,
|
.sampling_cycle = 0b00,
|
||||||
.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_U16, 2, 1 << 15, host_format, 2, host_rate).?,
|
|
||||||
.sched = sched,
|
.sched = sched,
|
||||||
|
|
||||||
.capacitor = 0,
|
.capacitor = 0,
|
||||||
.fs = FrameSequencer.init(),
|
.fs = FrameSequencer.init(),
|
||||||
.is_buffer_full = false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Self.initEvents(apu.sched, apu.interval());
|
Self.initEvents(apu.sched, apu.interval());
|
||||||
@@ -397,11 +396,6 @@ pub const Apu = struct {
|
|||||||
pub fn sampleAudio(self: *Self, late: u64) void {
|
pub fn sampleAudio(self: *Self, late: u64) void {
|
||||||
self.sched.push(.SampleAudio, self.interval() -| late);
|
self.sched.push(.SampleAudio, self.interval() -| late);
|
||||||
|
|
||||||
// Whether the APU is busy or not is determined by the main loop in emu.zig
|
|
||||||
// This should only ever be true (because this side of the emu is single threaded)
|
|
||||||
// When audio sync is disaabled
|
|
||||||
if (self.is_buffer_full) return;
|
|
||||||
|
|
||||||
var left: i16 = 0;
|
var left: i16 = 0;
|
||||||
var right: i16 = 0;
|
var right: i16 = 0;
|
||||||
|
|
||||||
@@ -459,21 +453,24 @@ pub const Apu = struct {
|
|||||||
|
|
||||||
if (self.sampling_cycle != self.bias.sampling_cycle.read()) self.replaceSDLResampler();
|
if (self.sampling_cycle != self.bias.sampling_cycle.read()) self.replaceSDLResampler();
|
||||||
|
|
||||||
_ = SDL.SDL_AudioStreamPut(self.stream, &[2]u16{ ext_left, ext_right }, 2 * @sizeOf(u16));
|
const ret = c.SDL_PutAudioStreamData(self.stream, &[2]i16{ @bitCast(ext_left ^ 0x8000), @bitCast(ext_right ^ 0x8000) }, 2 * @sizeOf(i16));
|
||||||
|
if (!ret) std.debug.panic("failed to append to sample queue. SDL Error: {s}", .{c.SDL_GetError()});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replaceSDLResampler(self: *Self) void {
|
fn replaceSDLResampler(self: *Self) void {
|
||||||
@setCold(true);
|
|
||||||
const sample_rate = Self.sampleRate(self.bias.sampling_cycle.read());
|
const sample_rate = Self.sampleRate(self.bias.sampling_cycle.read());
|
||||||
log.info("Sample Rate changed from {}Hz to {}Hz", .{ Self.sampleRate(self.sampling_cycle), sample_rate });
|
log.info("Sample Rate changed from {}Hz to {}Hz", .{ Self.sampleRate(self.sampling_cycle), sample_rate });
|
||||||
|
|
||||||
// Sampling Cycle (Sample Rate) changed, Craete a new SDL Audio Resampler
|
|
||||||
// FIXME: Replace SDL's Audio Resampler with either a custom or more reliable one
|
|
||||||
const old_stream = self.stream;
|
|
||||||
defer SDL.SDL_FreeAudioStream(old_stream);
|
|
||||||
|
|
||||||
self.sampling_cycle = self.bias.sampling_cycle.read();
|
self.sampling_cycle = self.bias.sampling_cycle.read();
|
||||||
self.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_U16, 2, @intCast(sample_rate), host_format, 2, host_rate).?;
|
|
||||||
|
const desired: c.SDL_AudioSpec = .{
|
||||||
|
.channels = 2,
|
||||||
|
.format = c.SDL_AUDIO_S16LE,
|
||||||
|
.freq = @intCast(Self.sampleRate(self.sampling_cycle)),
|
||||||
|
};
|
||||||
|
|
||||||
|
const ret = c.SDL_SetAudioStreamFormat(self.stream, &desired, null);
|
||||||
|
if (!ret) std.debug.panic("failed to change sample rate. SDL Error: {s}", .{c.SDL_GetError()});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn interval(self: *const Self) u64 {
|
fn interval(self: *const Self) u64 {
|
||||||
@@ -569,7 +566,7 @@ pub fn DmaSound(comptime kind: DmaSoundKind) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn enable(self: *Self) void {
|
fn enable(self: *Self) void {
|
||||||
@setCold(true);
|
@branchHint(.cold);
|
||||||
self.enabled = true;
|
self.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const std = @import("std");
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const log = std.log.scoped(.Bios);
|
const log = std.log.scoped(.Bios);
|
||||||
|
|
||||||
const rotr = @import("zba-util").rotr;
|
const rotr = @import("zba_util").rotr;
|
||||||
const forceAlign = @import("../Bus.zig").forceAlign;
|
const forceAlign = @import("../Bus.zig").forceAlign;
|
||||||
|
|
||||||
/// Size of the BIOS in bytes
|
/// Size of the BIOS in bytes
|
||||||
@@ -57,7 +57,7 @@ fn _read(self: *const Self, comptime T: type, addr: u32) T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(_: *Self, comptime T: type, addr: u32, value: T) void {
|
pub fn write(_: *Self, comptime T: type, addr: u32, value: T) void {
|
||||||
@setCold(true);
|
@branchHint(.cold);
|
||||||
log.debug("Tried to write {} 0x{X:} to 0x{X:0>8} ", .{ T, value, addr });
|
log.debug("Tried to write {} 0x{X:} to 0x{X:0>8} ", .{ T, value, addr });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const setHalf = util.setHalf;
|
|||||||
const setQuart = util.setQuart;
|
const setQuart = util.setQuart;
|
||||||
const handleInterrupt = @import("../cpu_util.zig").handleInterrupt;
|
const handleInterrupt = @import("../cpu_util.zig").handleInterrupt;
|
||||||
|
|
||||||
const rotr = @import("zba-util").rotr;
|
const rotr = @import("zba_util").rotr;
|
||||||
|
|
||||||
pub fn create() DmaTuple {
|
pub fn create() DmaTuple {
|
||||||
return .{ DmaController(0).init(), DmaController(1).init(), DmaController(2).init(), DmaController(3).init() };
|
return .{ DmaController(0).init(), DmaController(1).init(), DmaController(2).init(), DmaController(3).init() };
|
||||||
@@ -286,17 +286,17 @@ fn DmaController(comptime id: u2) type {
|
|||||||
if (self._word_count == 0) {
|
if (self._word_count == 0) {
|
||||||
if (self.cnt.irq.read()) {
|
if (self.cnt.irq.read()) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
0 => bus_ptr.io.irq.dma0.set(),
|
0 => bus_ptr.io.irq.dma0.write(true),
|
||||||
1 => bus_ptr.io.irq.dma1.set(),
|
1 => bus_ptr.io.irq.dma1.write(true),
|
||||||
2 => bus_ptr.io.irq.dma2.set(),
|
2 => bus_ptr.io.irq.dma2.write(true),
|
||||||
3 => bus_ptr.io.irq.dma3.set(),
|
3 => bus_ptr.io.irq.dma3.write(true),
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInterrupt(cpu);
|
handleInterrupt(cpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're not repeating, Fire the IRQs and disable the DMA
|
// If we're not repeating, Fire the IRQs and disable the DMA
|
||||||
if (!self.cnt.repeat.read()) self.cnt.enabled.unset();
|
if (!self.cnt.repeat.read()) self.cnt.enabled.write(false);
|
||||||
|
|
||||||
// We want to disable our internal enabled flag regardless of repeat
|
// We want to disable our internal enabled flag regardless of repeat
|
||||||
// because we only want to step A DMA that repeats during it's specific
|
// because we only want to step A DMA that repeats during it's specific
|
||||||
@@ -338,7 +338,7 @@ fn DmaController(comptime id: u2) type {
|
|||||||
|
|
||||||
// FIXME: Safe to just assume whatever DAD is set to is the FIFO Address?
|
// FIXME: Safe to just assume whatever DAD is set to is the FIFO Address?
|
||||||
// self.dad_latch = fifo_addr;
|
// self.dad_latch = fifo_addr;
|
||||||
self.cnt.repeat.set();
|
self.cnt.repeat.write(true);
|
||||||
self._word_count = 4;
|
self._word_count = 4;
|
||||||
self.in_progress = true;
|
self.in_progress = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Bit = @import("bitfield").Bit;
|
const Bit = @import("bitjuggle").Boolean;
|
||||||
const DateTime = @import("datetime").datetime.Datetime;
|
const DateTime = @import("datetime").datetime.Datetime;
|
||||||
|
|
||||||
const Arm7tdmi = @import("arm32").Arm7tdmi;
|
const Arm7tdmi = @import("arm32").Arm7tdmi;
|
||||||
@@ -408,7 +408,7 @@ pub const Clock = struct {
|
|||||||
// TODO: Confirm that this is the right behaviour
|
// TODO: Confirm that this is the right behaviour
|
||||||
log.debug("Force GamePak IRQ", .{});
|
log.debug("Force GamePak IRQ", .{});
|
||||||
|
|
||||||
bus_ptr.io.irq.game_pak.set();
|
bus_ptr.io.irq.game_pak.write(true);
|
||||||
handleInterrupt(self.cpu);
|
handleInterrupt(self.cpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ const apu = @import("../apu.zig");
|
|||||||
const ppu = @import("../ppu.zig");
|
const ppu = @import("../ppu.zig");
|
||||||
const util = @import("../../util.zig");
|
const util = @import("../../util.zig");
|
||||||
|
|
||||||
const Bit = @import("bitfield").Bit;
|
const Bit = @import("bitjuggle").Boolean;
|
||||||
const Bitfield = @import("bitfield").Bitfield;
|
const Bitfield = @import("bitjuggle").Bitfield;
|
||||||
const Bus = @import("../Bus.zig");
|
const Bus = @import("../Bus.zig");
|
||||||
|
|
||||||
const getHalf = util.getHalf;
|
const getHalf = util.getHalf;
|
||||||
|
|||||||
@@ -200,10 +200,10 @@ fn Timer(comptime id: u2) type {
|
|||||||
|
|
||||||
if (self.cnt.irq.read()) {
|
if (self.cnt.irq.read()) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
0 => io.irq.tim0.set(),
|
0 => io.irq.tim0.write(true),
|
||||||
1 => io.irq.tim1.set(),
|
1 => io.irq.tim1.write(true),
|
||||||
2 => io.irq.tim2.set(),
|
2 => io.irq.tim2.write(true),
|
||||||
3 => io.irq.tim3.set(),
|
3 => io.irq.tim3.write(true),
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInterrupt(cpu);
|
handleInterrupt(cpu);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const SDL = @import("sdl2");
|
|
||||||
const config = @import("../config.zig");
|
const config = @import("../config.zig");
|
||||||
|
const c = @import("../lib.zig").c;
|
||||||
|
|
||||||
const Scheduler = @import("scheduler.zig").Scheduler;
|
const Scheduler = @import("scheduler.zig").Scheduler;
|
||||||
const Arm7tdmi = @import("arm32").Arm7tdmi;
|
const Arm7tdmi = @import("arm32").Arm7tdmi;
|
||||||
@@ -18,8 +18,8 @@ pub const Synchro = struct {
|
|||||||
|
|
||||||
// FIXME: This Enum ends up being really LARGE!!!
|
// FIXME: This Enum ends up being really LARGE!!!
|
||||||
pub const Message = union(enum) {
|
pub const Message = union(enum) {
|
||||||
rom_path: [std.fs.MAX_PATH_BYTES]u8,
|
rom_path: [std.fs.max_path_bytes]u8,
|
||||||
bios_path: [std.fs.MAX_PATH_BYTES]u8,
|
bios_path: [std.fs.max_path_bytes]u8,
|
||||||
restart: void,
|
restart: void,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *S
|
|||||||
if (sync.paused.load(.monotonic)) continue;
|
if (sync.paused.load(.monotonic)) continue;
|
||||||
|
|
||||||
runFrame(scheduler, cpu);
|
runFrame(scheduler, cpu);
|
||||||
audioSync(audio_sync, bus_ptr.apu.stream, &bus_ptr.apu.is_buffer_full);
|
if (audio_sync) audioSync(bus_ptr.apu.stream);
|
||||||
|
|
||||||
if (kind == .UnlimitedFPS) tracker.?.tick();
|
if (kind == .UnlimitedFPS) tracker.?.tick();
|
||||||
}
|
}
|
||||||
@@ -117,10 +117,13 @@ fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *S
|
|||||||
// the amount of time needed for audio to catch up rather than
|
// the amount of time needed for audio to catch up rather than
|
||||||
// our expected wake-up time
|
// our expected wake-up time
|
||||||
|
|
||||||
audioSync(audio_sync, bus_ptr.apu.stream, &bus_ptr.apu.is_buffer_full);
|
if (audio_sync) {
|
||||||
if (!audio_sync) spinLoop(&timer, wake_time);
|
audioSync(bus_ptr.apu.stream);
|
||||||
wake_time = new_wake_time;
|
} else {
|
||||||
|
spinLoop(&timer, wake_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
wake_time = new_wake_time;
|
||||||
if (kind == .LimitedFPS) tracker.?.tick();
|
if (kind == .LimitedFPS) tracker.?.tick();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -160,26 +163,25 @@ pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn audioSync(audio_sync: bool, stream: *SDL.SDL_AudioStream, is_buffer_full: *bool) void {
|
fn audioSync(stream: ?*c.SDL_AudioStream) void {
|
||||||
comptime std.debug.assert(@import("../platform.zig").sample_format == SDL.AUDIO_U16);
|
if (stream == null) {
|
||||||
const sample_size = 2 * @sizeOf(u16);
|
@branchHint(.cold);
|
||||||
const max_buf_size: c_int = 0x400;
|
return log.err("audio sync failed. no SDL_AudioStream to sync to", .{});
|
||||||
|
|
||||||
// Determine whether the APU is busy right at this moment
|
|
||||||
var still_full: bool = SDL.SDL_AudioStreamAvailable(stream) > sample_size * if (is_buffer_full.*) max_buf_size >> 1 else max_buf_size;
|
|
||||||
defer is_buffer_full.* = still_full; // Update APU Busy status right before exiting scope
|
|
||||||
|
|
||||||
// If Busy is false, there's no need to sync here
|
|
||||||
if (!still_full) return;
|
|
||||||
|
|
||||||
// TODO: Refactor!!!!
|
|
||||||
// while (SDL.SDL_AudioStreamAvailable(stream) > sample_size * max_buf_size >> 1)
|
|
||||||
// std.atomic.spinLoopHint();
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
still_full = SDL.SDL_AudioStreamAvailable(stream) > sample_size * max_buf_size >> 1;
|
|
||||||
if (!audio_sync or !still_full) break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sample_size = 2 * @sizeOf(u16);
|
||||||
|
const max_sample_delay: c_int = 0x800;
|
||||||
|
var is_behind = true;
|
||||||
|
|
||||||
|
while (is_behind) {
|
||||||
|
const bytes = c.SDL_GetAudioStreamQueued(stream);
|
||||||
|
if (bytes == -1) std.debug.panic("failed to query amount of queued bytes in audio stream: SDL Error {s}", .{c.SDL_GetError()});
|
||||||
|
|
||||||
|
is_behind = bytes > max_sample_delay * sample_size;
|
||||||
|
std.atomic.spinLoopHint();
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME(paoda, 2025-11-12): There was an is_buffer_full thing going on here that I'm sure was really important for some niche reason
|
||||||
}
|
}
|
||||||
|
|
||||||
fn videoSync(timer: *Timer, wake_time: u64) u64 {
|
fn videoSync(timer: *Timer, wake_time: u64) u64 {
|
||||||
@@ -208,7 +210,7 @@ fn sleep(timer: *Timer, wake_time: u64) ?u64 {
|
|||||||
const times = sleep_for / step;
|
const times = sleep_for / step;
|
||||||
|
|
||||||
for (0..times) |_| {
|
for (0..times) |_| {
|
||||||
std.time.sleep(step);
|
std.Thread.sleep(step);
|
||||||
|
|
||||||
// Upon wakeup, check to see if this particular sleep was longer than expected
|
// Upon wakeup, check to see if this particular sleep was longer than expected
|
||||||
// if so we should exit early, but probably not skip a whole frame period
|
// if so we should exit early, but probably not skip a whole frame period
|
||||||
@@ -279,8 +281,8 @@ pub const EmuThing = struct {
|
|||||||
return .{ .cpu = cpu, .scheduler = scheduler };
|
return .{ .cpu = cpu, .scheduler = scheduler };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn interface(self: *Self, allocator: Allocator) Interface {
|
pub fn interface(self: *Self) Interface {
|
||||||
return Interface.init(allocator, self);
|
return Interface.init(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(self: *const Self, addr: u32) u8 {
|
pub fn read(self: *const Self, addr: u32) u8 {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ const std = @import("std");
|
|||||||
const io = @import("bus/io.zig");
|
const io = @import("bus/io.zig");
|
||||||
const util = @import("../util.zig");
|
const util = @import("../util.zig");
|
||||||
|
|
||||||
const Bit = @import("bitfield").Bit;
|
const Bit = @import("bitjuggle").Boolean;
|
||||||
const Bitfield = @import("bitfield").Bitfield;
|
const Bitfield = @import("bitjuggle").Bitfield;
|
||||||
const dma = @import("bus/dma.zig");
|
const dma = @import("bus/dma.zig");
|
||||||
|
|
||||||
const Oam = @import("ppu/Oam.zig");
|
const Oam = @import("ppu/Oam.zig");
|
||||||
@@ -1007,7 +1007,7 @@ pub const Ppu = struct {
|
|||||||
|
|
||||||
// Transitioning to a Hblank
|
// Transitioning to a Hblank
|
||||||
if (self.dispstat.hblank_irq.read()) {
|
if (self.dispstat.hblank_irq.read()) {
|
||||||
bus_ptr.io.irq.hblank.set();
|
bus_ptr.io.irq.hblank.write(true);
|
||||||
handleInterrupt(cpu);
|
handleInterrupt(cpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1015,7 +1015,7 @@ pub const Ppu = struct {
|
|||||||
if (!self.dispstat.vblank.read())
|
if (!self.dispstat.vblank.read())
|
||||||
dma.onBlanking(bus_ptr, .HBlank);
|
dma.onBlanking(bus_ptr, .HBlank);
|
||||||
|
|
||||||
self.dispstat.hblank.set();
|
self.dispstat.hblank.write(true);
|
||||||
self.sched.push(.HBlank, 68 * 4 -| late);
|
self.sched.push(.HBlank, 68 * 4 -| late);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1027,14 +1027,14 @@ pub const Ppu = struct {
|
|||||||
const scanline = (old_scanline + 1) % 228;
|
const scanline = (old_scanline + 1) % 228;
|
||||||
|
|
||||||
self.vcount.scanline.write(scanline);
|
self.vcount.scanline.write(scanline);
|
||||||
self.dispstat.hblank.unset();
|
self.dispstat.hblank.write(false);
|
||||||
|
|
||||||
// Perform Vc == VcT check
|
// Perform Vc == VcT check
|
||||||
const coincidence = scanline == self.dispstat.vcount_trigger.read();
|
const coincidence = scanline == self.dispstat.vcount_trigger.read();
|
||||||
self.dispstat.coincidence.write(coincidence);
|
self.dispstat.coincidence.write(coincidence);
|
||||||
|
|
||||||
if (coincidence and self.dispstat.vcount_irq.read()) {
|
if (coincidence and self.dispstat.vcount_irq.read()) {
|
||||||
bus_ptr.io.irq.coincidence.set();
|
bus_ptr.io.irq.coincidence.write(true);
|
||||||
handleInterrupt(cpu);
|
handleInterrupt(cpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1046,10 +1046,10 @@ pub const Ppu = struct {
|
|||||||
if (scanline == 160) {
|
if (scanline == 160) {
|
||||||
self.framebuf.swap(); // Swap FrameBuffers
|
self.framebuf.swap(); // Swap FrameBuffers
|
||||||
|
|
||||||
self.dispstat.vblank.set();
|
self.dispstat.vblank.write(true);
|
||||||
|
|
||||||
if (self.dispstat.vblank_irq.read()) {
|
if (self.dispstat.vblank_irq.read()) {
|
||||||
bus_ptr.io.irq.vblank.set();
|
bus_ptr.io.irq.vblank.write(true);
|
||||||
handleInterrupt(cpu);
|
handleInterrupt(cpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1060,7 +1060,7 @@ pub const Ppu = struct {
|
|||||||
dma.onBlanking(bus_ptr, .VBlank);
|
dma.onBlanking(bus_ptr, .VBlank);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scanline == 227) self.dispstat.vblank.unset();
|
if (scanline == 227) self.dispstat.vblank.write(false);
|
||||||
self.sched.push(.VBlank, 240 * 4 -| late);
|
self.sched.push(.VBlank, 240 * 4 -| late);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
165
src/imgui.zig
165
src/imgui.zig
@@ -15,11 +15,11 @@ const Scheduler = @import("core/scheduler.zig").Scheduler;
|
|||||||
const Bus = @import("core/Bus.zig");
|
const Bus = @import("core/Bus.zig");
|
||||||
const Synchro = @import("core/emu.zig").Synchro;
|
const Synchro = @import("core/emu.zig").Synchro;
|
||||||
|
|
||||||
const RingBuffer = @import("zba-util").RingBuffer;
|
const RingBuffer = @import("zba_util").RingBuffer;
|
||||||
const Dimensions = @import("platform.zig").Dimensions;
|
const Dimensions = @import("platform.zig").Dimensions;
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const GLuint = gl.GLuint;
|
const GLuint = gl.uint;
|
||||||
|
|
||||||
const gba_width = @import("core/ppu.zig").width;
|
const gba_width = @import("core/ppu.zig").width;
|
||||||
const gba_height = @import("core/ppu.zig").height;
|
const gba_height = @import("core/ppu.zig").height;
|
||||||
@@ -75,7 +75,7 @@ pub fn draw(state: *State, sync: *Synchro, dim: Dimensions, cpu: *const Arm7tdmi
|
|||||||
const scn_scale = config.config().host.win_scale;
|
const scn_scale = config.config().host.win_scale;
|
||||||
const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr));
|
const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr));
|
||||||
|
|
||||||
zgui.backend.newFrame(@floatFromInt(dim.width), @floatFromInt(dim.height));
|
zgui.backend.newFrame(dim.width, dim.height);
|
||||||
|
|
||||||
state.title = handleTitle(&bus_ptr.pak.title);
|
state.title = handleTitle(&bus_ptr.pak.title);
|
||||||
|
|
||||||
@@ -195,7 +195,8 @@ pub fn draw(state: *State, sync: *Synchro, dim: Dimensions, cpu: *const Arm7tdmi
|
|||||||
_ = zgui.begin(window_title, .{ .flags = .{ .no_resize = true, .always_auto_resize = true } });
|
_ = zgui.begin(window_title, .{ .flags = .{ .no_resize = true, .always_auto_resize = true } });
|
||||||
defer zgui.end();
|
defer zgui.end();
|
||||||
|
|
||||||
zgui.image(@ptrFromInt(tex_id), .{ .w = w, .h = h });
|
// FIXME(paoda): go look at some documentation here
|
||||||
|
zgui.image(.{ .tex_data = null, .tex_id = @enumFromInt(tex_id) }, .{ .w = w, .h = h });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Any other steps to respect the copyright of the libraries I use?
|
// TODO: Any other steps to respect the copyright of the libraries I use?
|
||||||
@@ -203,44 +204,84 @@ pub fn draw(state: *State, sync: *Synchro, dim: Dimensions, cpu: *const Arm7tdmi
|
|||||||
_ = zgui.begin("Dependencies", .{ .popen = &state.win_stat.show_deps });
|
_ = zgui.begin("Dependencies", .{ .popen = &state.win_stat.show_deps });
|
||||||
defer zgui.end();
|
defer zgui.end();
|
||||||
|
|
||||||
zgui.bulletText("known-folders by ziglibs", .{});
|
zgui.textLinkOpenURL("known-folders", "https://github.com/ziglibs/known-folders");
|
||||||
zgui.bulletText("nfd-zig by Fabio Arnold", .{});
|
zgui.sameLine(.{});
|
||||||
|
zgui.text("by ziglibs", .{});
|
||||||
|
|
||||||
|
zgui.textLinkOpenURL("nfd-zig", "https://github.com/fabioarnold/nfd-zig");
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
zgui.text("ported by Fabio Arnold", .{});
|
||||||
{
|
{
|
||||||
zgui.indent(.{});
|
zgui.indent(.{});
|
||||||
defer zgui.unindent(.{});
|
defer zgui.unindent(.{});
|
||||||
|
|
||||||
zgui.bulletText("nativefiledialog by Michael Labbe", .{});
|
zgui.textLinkOpenURL("nativefiledialog", "https://github.com/mlabbe/nativefiledialog");
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
zgui.text("by Michael Labbe", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
zgui.bulletText("SDL.zig by Felix Queißner", .{});
|
zgui.textLinkOpenURL("SDL", "https://github.com/castholm/SDL");
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
zgui.text("ported by Carl Åstholm", .{});
|
||||||
{
|
{
|
||||||
zgui.indent(.{});
|
zgui.indent(.{});
|
||||||
defer zgui.unindent(.{});
|
defer zgui.unindent(.{});
|
||||||
|
|
||||||
zgui.bulletText("SDL by Sam Lantinga", .{});
|
zgui.textLinkOpenURL("SDL", "by Sam Lantinga");
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
zgui.text("by Sam Lantinga", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
zgui.bulletText("tomlz by Matthew Hall", .{});
|
zgui.textLinkOpenURL("zig-toml", "https://github.com/sam701/zig-toml");
|
||||||
zgui.bulletText("zba-gdbstub by Rekai Musuka", .{});
|
zgui.sameLine(.{});
|
||||||
zgui.bulletText("zba-util by Rekai Musuka", .{});
|
zgui.text("by Alexei Samokvalov", .{});
|
||||||
zgui.bulletText("zgui by Michal Ziulek", .{});
|
|
||||||
|
zgui.textLinkOpenURL("zba-gdbstub", "https://git.musuka.dev/paoda/zba-gdbstub");
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
zgui.text("by Rekai Musuka", .{});
|
||||||
|
|
||||||
|
zgui.textLinkOpenURL("zba-util", "https://git.musuka.dev/paoda/zba-util");
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
zgui.text("by Rekai Musuka", .{});
|
||||||
|
|
||||||
|
zgui.textLinkOpenURL("arm32", "https://git.musuka.dev/paoda/arm32");
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
zgui.text("by Rekai Musuka", .{});
|
||||||
|
|
||||||
|
zgui.textLinkOpenURL("zgui", "https://github.com/zig-gamedev/zgui");
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
zgui.text("ported by Michal Ziulek et al.", .{});
|
||||||
{
|
{
|
||||||
zgui.indent(.{});
|
zgui.indent(.{});
|
||||||
defer zgui.unindent(.{});
|
defer zgui.unindent(.{});
|
||||||
|
|
||||||
zgui.bulletText("DearImGui by Omar Cornut", .{});
|
zgui.textLinkOpenURL("Dear ImGui", "https://github.com/ocornut/imgui");
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
zgui.text("by Omar Cornut", .{});
|
||||||
}
|
}
|
||||||
zgui.bulletText("zig-clap by Jimmi Holst Christensen", .{});
|
|
||||||
zgui.bulletText("zig-datetime by Jairus Martin", .{});
|
|
||||||
|
|
||||||
zgui.newLine();
|
zgui.textLinkOpenURL("zig-clap", "https://github.com/Hejsil/zig-clap");
|
||||||
zgui.bulletText("bitfield.zig by Hannes Bredberg and FlorenceOS contributors", .{});
|
zgui.sameLine(.{});
|
||||||
zgui.bulletText("zig-opengl by Felix Queißner", .{});
|
zgui.text("by Jimmi Holst Christensen", .{});
|
||||||
|
|
||||||
|
zgui.textLinkOpenURL("zig-datetime", "https://github.com/frmdstryr/zig-datetime");
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
zgui.text("by Jairus Martin", .{});
|
||||||
|
|
||||||
|
zgui.textLinkOpenURL("zig-bitjuggle", "https://github.com/leecannon/zig-bitjuggle");
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
zgui.text("by Lee Cannon + Hannes Bredberg et al.", .{});
|
||||||
|
|
||||||
|
zgui.textLinkOpenURL("zigglgen", "https://github.com/castholm/zigglgen");
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
zgui.text("ported by Carl Åstholm", .{});
|
||||||
{
|
{
|
||||||
zgui.indent(.{});
|
zgui.indent(.{});
|
||||||
defer zgui.unindent(.{});
|
defer zgui.unindent(.{});
|
||||||
|
|
||||||
zgui.bulletText("OpenGL-Registry by The Khronos Group", .{});
|
zgui.textLinkOpenURL("OpenGL-Registry", "https://github.com/KhronosGroup/OpenGL-Registry");
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
zgui.text("by The Khronos Group", .{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,38 +313,21 @@ pub fn draw(state: *State, sync: *Synchro, dim: Dimensions, cpu: *const Arm7tdmi
|
|||||||
_ = zgui.begin("Performance", .{ .popen = &state.win_stat.show_perf });
|
_ = zgui.begin("Performance", .{ .popen = &state.win_stat.show_perf });
|
||||||
defer zgui.end();
|
defer zgui.end();
|
||||||
|
|
||||||
const tmp = blk: {
|
var values: [histogram_len]u32 = undefined;
|
||||||
var buf: [histogram_len]u32 = undefined;
|
const len = state.fps_hist.copy(&values);
|
||||||
const len = state.fps_hist.copy(&buf);
|
|
||||||
|
|
||||||
break :blk .{ buf, len };
|
|
||||||
};
|
|
||||||
const values = tmp[0];
|
|
||||||
const len = tmp[1];
|
|
||||||
|
|
||||||
|
// FIXME(paoda): okay but why
|
||||||
if (len == values.len) _ = state.fps_hist.pop();
|
if (len == values.len) _ = state.fps_hist.pop();
|
||||||
|
|
||||||
const sorted = blk: {
|
const y_max: f64 = 2 * if (len != 0) @as(f64, @floatFromInt(std.mem.max(u32, &values))) else emu.frame_rate;
|
||||||
var buf: @TypeOf(values) = undefined;
|
|
||||||
|
|
||||||
@memcpy(buf[0..len], values[0..len]);
|
|
||||||
std.mem.sort(u32, buf[0..len], {}, std.sort.asc(u32));
|
|
||||||
|
|
||||||
break :blk buf;
|
|
||||||
};
|
|
||||||
|
|
||||||
const y_max: f64 = 2 * if (len != 0) @as(f64, @floatFromInt(sorted[len - 1])) else emu.frame_rate;
|
|
||||||
const x_max: f64 = @floatFromInt(values.len);
|
const x_max: f64 = @floatFromInt(values.len);
|
||||||
|
|
||||||
const y_args = .{ .flags = .{ .no_grid_lines = true } };
|
|
||||||
const x_args = .{ .flags = .{ .no_grid_lines = true, .no_tick_labels = true, .no_tick_marks = true } };
|
|
||||||
|
|
||||||
if (zgui.plot.beginPlot("Emulation FPS", .{ .w = 0.0, .flags = .{ .no_title = true, .no_frame = true } })) {
|
if (zgui.plot.beginPlot("Emulation FPS", .{ .w = 0.0, .flags = .{ .no_title = true, .no_frame = true } })) {
|
||||||
defer zgui.plot.endPlot();
|
defer zgui.plot.endPlot();
|
||||||
|
|
||||||
zgui.plot.setupLegend(.{ .north = true, .east = true }, .{});
|
zgui.plot.setupLegend(.{ .north = true, .east = true }, .{});
|
||||||
zgui.plot.setupAxis(.x1, x_args);
|
zgui.plot.setupAxis(.x1, .{ .flags = .{ .no_grid_lines = true, .no_tick_labels = true, .no_tick_marks = true } });
|
||||||
zgui.plot.setupAxis(.y1, y_args);
|
zgui.plot.setupAxis(.y1, .{ .label = "FPS", .flags = .{ .no_grid_lines = true } });
|
||||||
zgui.plot.setupAxisLimits(.y1, .{ .min = 0.0, .max = y_max, .cond = .always });
|
zgui.plot.setupAxisLimits(.y1, .{ .min = 0.0, .max = y_max, .cond = .always });
|
||||||
zgui.plot.setupAxisLimits(.x1, .{ .min = 0.0, .max = x_max, .cond = .always });
|
zgui.plot.setupAxisLimits(.x1, .{ .min = 0.0, .max = x_max, .cond = .always });
|
||||||
zgui.plot.setupFinish();
|
zgui.plot.setupFinish();
|
||||||
@@ -311,24 +335,22 @@ pub fn draw(state: *State, sync: *Synchro, dim: Dimensions, cpu: *const Arm7tdmi
|
|||||||
zgui.plot.plotLineValues("FPS", u32, .{ .v = values[0..len] });
|
zgui.plot.plotLineValues("FPS", u32, .{ .v = values[0..len] });
|
||||||
}
|
}
|
||||||
|
|
||||||
const stats: struct { u32, u32, u32 } = blk: {
|
// the following metrics require a sorted array
|
||||||
if (len == 0) break :blk .{ 0, 0, 0 };
|
std.mem.sort(u32, values[0..len], {}, std.sort.asc(u32));
|
||||||
|
|
||||||
const average: u32 = average: {
|
const avg: u32 = avg: {
|
||||||
var sum: u32 = 0;
|
var sum: u32 = 0;
|
||||||
for (sorted[0..len]) |value| sum += value;
|
for (values[0..len]) |value| sum += value;
|
||||||
|
|
||||||
break :average @intCast(sum / len);
|
break :avg @intCast(sum / len);
|
||||||
};
|
|
||||||
const median = sorted[len / 2];
|
|
||||||
const low = sorted[len / 100]; // 1% Low
|
|
||||||
|
|
||||||
break :blk .{ average, median, low };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
zgui.text("Average: {:0>3} fps", .{stats[0]});
|
const median = if (len == 0) 0 else values[len / 2];
|
||||||
zgui.text(" Median: {:0>3} fps", .{stats[1]});
|
const low = if (len == 0) 0 else values[len / 100]; // 1% Low
|
||||||
zgui.text(" 1% Low: {:0>3} fps", .{stats[2]});
|
|
||||||
|
zgui.text("Average: {:0>3} fps", .{avg});
|
||||||
|
zgui.text(" Median: {:0>3} fps", .{median});
|
||||||
|
zgui.text(" 1% Low: {:0>3} fps", .{low});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.win_stat.show_schedule) {
|
if (state.win_stat.show_schedule) {
|
||||||
@@ -337,11 +359,18 @@ pub fn draw(state: *State, sync: *Synchro, dim: Dimensions, cpu: *const Arm7tdmi
|
|||||||
|
|
||||||
const scheduler = cpu.sched;
|
const scheduler = cpu.sched;
|
||||||
|
|
||||||
zgui.text("tick: {X:0>16}", .{scheduler.now()});
|
const tick = scheduler.now();
|
||||||
|
|
||||||
|
// >> 24 becuase the ARM7TDMI runs at 2^24 Hz
|
||||||
|
const elapsed_str = std.mem.sliceTo(&printTime(tick >> 24), 0);
|
||||||
|
|
||||||
|
zgui.text("tick: 0x{X:0>16}", .{tick});
|
||||||
|
zgui.sameLine(.{});
|
||||||
|
zgui.text(" {s}s", .{elapsed_str});
|
||||||
zgui.separator();
|
zgui.separator();
|
||||||
|
|
||||||
const sched_ptr: *Scheduler = @ptrCast(@alignCast(cpu.sched.ptr));
|
const sched_ptr: *Scheduler = @ptrCast(@alignCast(scheduler.ptr));
|
||||||
const Event = std.meta.Child(@TypeOf(sched_ptr.queue.items));
|
const Event = @typeInfo(@TypeOf(sched_ptr.queue.items)).pointer.child;
|
||||||
|
|
||||||
var items: [20]Event = undefined;
|
var items: [20]Event = undefined;
|
||||||
const len = @min(sched_ptr.queue.items.len, items.len);
|
const len = @min(sched_ptr.queue.items.len, items.len);
|
||||||
@@ -350,7 +379,7 @@ pub fn draw(state: *State, sync: *Synchro, dim: Dimensions, cpu: *const Arm7tdmi
|
|||||||
std.mem.sort(Event, items[0..len], {}, widgets.eventDesc(Event));
|
std.mem.sort(Event, items[0..len], {}, widgets.eventDesc(Event));
|
||||||
|
|
||||||
for (items[0..len]) |event| {
|
for (items[0..len]) |event| {
|
||||||
zgui.text("{X:0>16} | {?}", .{ event.tick, event.kind });
|
zgui.text("0x{X:0>16} | {t}", .{ event.tick, event.kind });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -509,3 +538,17 @@ fn handleTitle(title_opt: ?*const [12]u8) [12:0]u8 {
|
|||||||
|
|
||||||
return title.* ++ [_:0]u8{};
|
return title.* ++ [_:0]u8{};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn printTime(seconds: u64) [0x20]u8 {
|
||||||
|
var str = [_]u8{0x0} ** 0x20;
|
||||||
|
var writer = std.Io.Writer.fixed(&str);
|
||||||
|
|
||||||
|
const hr = seconds / 3600;
|
||||||
|
const min = (seconds % 3600) / 60;
|
||||||
|
const sec = seconds % 60;
|
||||||
|
|
||||||
|
// longest string is "5124095576030431:00:15" which is 22 bytes
|
||||||
|
writer.print("{:0>2}:{:0>2}:{:0>2}", .{ hr, min, sec }) catch unreachable;
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|||||||
28
src/lib.zig
Normal file
28
src/lib.zig
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
pub const c = @cImport({
|
||||||
|
@cDefine("SDL_DISABLE_OLD_NAMES", {});
|
||||||
|
@cDefine("SDL_MAIN_HANDLED", {});
|
||||||
|
|
||||||
|
@cInclude("SDL3/SDL.h");
|
||||||
|
@cInclude("SDL3/SDL_main.h");
|
||||||
|
});
|
||||||
|
|
||||||
|
// https://github.com/castholm/zig-examples/blob/77a829c85b5ddbad673026d504626015db4093ac/opengl-sdl/main.zig#L200-L219
|
||||||
|
pub inline fn errify(value: anytype) error{sdl_error}!switch (@typeInfo(@TypeOf(value))) {
|
||||||
|
.bool => void,
|
||||||
|
.pointer, .optional => @TypeOf(value.?),
|
||||||
|
.int => |info| switch (info.signedness) {
|
||||||
|
.signed => @TypeOf(@max(0, value)),
|
||||||
|
.unsigned => @TypeOf(value),
|
||||||
|
},
|
||||||
|
else => @compileError("unerrifiable type: " ++ @typeName(@TypeOf(value))),
|
||||||
|
} {
|
||||||
|
return switch (@typeInfo(@TypeOf(value))) {
|
||||||
|
.bool => if (!value) error.sdl_error,
|
||||||
|
.pointer, .optional => value orelse error.sdl_error,
|
||||||
|
.int => |info| switch (info.signedness) {
|
||||||
|
.signed => if (value >= 0) @max(0, value) else error.sdl_error,
|
||||||
|
.unsigned => if (value != 0) value else error.sdl_error,
|
||||||
|
},
|
||||||
|
else => comptime unreachable,
|
||||||
|
};
|
||||||
|
}
|
||||||
548
src/lib/fifo.zig
Normal file
548
src/lib/fifo.zig
Normal file
@@ -0,0 +1,548 @@
|
|||||||
|
// FIFO of fixed size items
|
||||||
|
// Usually used for e.g. byte buffers
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const math = std.math;
|
||||||
|
const mem = std.mem;
|
||||||
|
const Allocator = mem.Allocator;
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
pub const LinearFifoBufferType = union(enum) {
|
||||||
|
/// The buffer is internal to the fifo; it is of the specified size.
|
||||||
|
static: usize,
|
||||||
|
|
||||||
|
/// The buffer is passed as a slice to the initialiser.
|
||||||
|
slice,
|
||||||
|
|
||||||
|
/// The buffer is managed dynamically using a `mem.Allocator`.
|
||||||
|
dynamic,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn LinearFifo(
|
||||||
|
comptime T: type,
|
||||||
|
comptime buffer_type: LinearFifoBufferType,
|
||||||
|
) type {
|
||||||
|
const autoalign = false;
|
||||||
|
|
||||||
|
const powers_of_two = switch (buffer_type) {
|
||||||
|
.static => std.math.isPowerOfTwo(buffer_type.static),
|
||||||
|
.slice => false, // Any size slice could be passed in
|
||||||
|
.dynamic => true, // This could be configurable in future
|
||||||
|
};
|
||||||
|
|
||||||
|
return struct {
|
||||||
|
allocator: if (buffer_type == .dynamic) Allocator else void,
|
||||||
|
buf: if (buffer_type == .static) [buffer_type.static]T else []T,
|
||||||
|
head: usize,
|
||||||
|
count: usize,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
pub const Reader = std.io.Reader(*Self, error{}, readFn);
|
||||||
|
pub const Writer = std.io.Writer(*Self, error{OutOfMemory}, appendWrite);
|
||||||
|
|
||||||
|
// Type of Self argument for slice operations.
|
||||||
|
// If buffer is inline (Static) then we need to ensure we haven't
|
||||||
|
// returned a slice into a copy on the stack
|
||||||
|
const SliceSelfArg = if (buffer_type == .static) *Self else Self;
|
||||||
|
|
||||||
|
pub const init = switch (buffer_type) {
|
||||||
|
.static => initStatic,
|
||||||
|
.slice => initSlice,
|
||||||
|
.dynamic => initDynamic,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn initStatic() Self {
|
||||||
|
comptime assert(buffer_type == .static);
|
||||||
|
return .{
|
||||||
|
.allocator = {},
|
||||||
|
.buf = undefined,
|
||||||
|
.head = 0,
|
||||||
|
.count = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initSlice(buf: []T) Self {
|
||||||
|
comptime assert(buffer_type == .slice);
|
||||||
|
return .{
|
||||||
|
.allocator = {},
|
||||||
|
.buf = buf,
|
||||||
|
.head = 0,
|
||||||
|
.count = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initDynamic(allocator: Allocator) Self {
|
||||||
|
comptime assert(buffer_type == .dynamic);
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.buf = &.{},
|
||||||
|
.head = 0,
|
||||||
|
.count = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Self) void {
|
||||||
|
if (buffer_type == .dynamic) self.allocator.free(self.buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn realign(self: *Self) void {
|
||||||
|
if (self.buf.len - self.head >= self.count) {
|
||||||
|
mem.copyForwards(T, self.buf[0..self.count], self.buf[self.head..][0..self.count]);
|
||||||
|
self.head = 0;
|
||||||
|
} else {
|
||||||
|
var tmp: [4096 / 2 / @sizeOf(T)]T = undefined;
|
||||||
|
|
||||||
|
while (self.head != 0) {
|
||||||
|
const n = @min(self.head, tmp.len);
|
||||||
|
const m = self.buf.len - n;
|
||||||
|
@memcpy(tmp[0..n], self.buf[0..n]);
|
||||||
|
mem.copyForwards(T, self.buf[0..m], self.buf[n..][0..m]);
|
||||||
|
@memcpy(self.buf[m..][0..n], tmp[0..n]);
|
||||||
|
self.head -= n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{ // set unused area to undefined
|
||||||
|
const unused = mem.sliceAsBytes(self.buf[self.count..]);
|
||||||
|
@memset(unused, undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reduce allocated capacity to `size`.
|
||||||
|
pub fn shrink(self: *Self, size: usize) void {
|
||||||
|
assert(size >= self.count);
|
||||||
|
if (buffer_type == .dynamic) {
|
||||||
|
self.realign();
|
||||||
|
self.buf = self.allocator.realloc(self.buf, size) catch |e| switch (e) {
|
||||||
|
error.OutOfMemory => return, // no problem, capacity is still correct then.
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure that the buffer can fit at least `size` items
|
||||||
|
pub fn ensureTotalCapacity(self: *Self, size: usize) !void {
|
||||||
|
if (self.buf.len >= size) return;
|
||||||
|
if (buffer_type == .dynamic) {
|
||||||
|
self.realign();
|
||||||
|
const new_size = if (powers_of_two) math.ceilPowerOfTwo(usize, size) catch return error.OutOfMemory else size;
|
||||||
|
self.buf = try self.allocator.realloc(self.buf, new_size);
|
||||||
|
} else {
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes sure at least `size` items are unused
|
||||||
|
pub fn ensureUnusedCapacity(self: *Self, size: usize) error{OutOfMemory}!void {
|
||||||
|
if (self.writableLength() >= size) return;
|
||||||
|
|
||||||
|
return try self.ensureTotalCapacity(math.add(usize, self.count, size) catch return error.OutOfMemory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns number of items currently in fifo
|
||||||
|
pub fn readableLength(self: Self) usize {
|
||||||
|
return self.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a writable slice from the 'read' end of the fifo
|
||||||
|
fn readableSliceMut(self: SliceSelfArg, offset: usize) []T {
|
||||||
|
if (offset > self.count) return &[_]T{};
|
||||||
|
|
||||||
|
var start = self.head + offset;
|
||||||
|
if (start >= self.buf.len) {
|
||||||
|
start -= self.buf.len;
|
||||||
|
return self.buf[start .. start + (self.count - offset)];
|
||||||
|
} else {
|
||||||
|
const end = @min(self.head + self.count, self.buf.len);
|
||||||
|
return self.buf[start..end];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a readable slice from `offset`
|
||||||
|
pub fn readableSlice(self: SliceSelfArg, offset: usize) []const T {
|
||||||
|
return self.readableSliceMut(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn readableSliceOfLen(self: *Self, len: usize) []const T {
|
||||||
|
assert(len <= self.count);
|
||||||
|
const buf = self.readableSlice(0);
|
||||||
|
if (buf.len >= len) {
|
||||||
|
return buf[0..len];
|
||||||
|
} else {
|
||||||
|
self.realign();
|
||||||
|
return self.readableSlice(0)[0..len];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Discard first `count` items in the fifo
|
||||||
|
pub fn discard(self: *Self, count: usize) void {
|
||||||
|
assert(count <= self.count);
|
||||||
|
{ // set old range to undefined. Note: may be wrapped around
|
||||||
|
const slice = self.readableSliceMut(0);
|
||||||
|
if (slice.len >= count) {
|
||||||
|
const unused = mem.sliceAsBytes(slice[0..count]);
|
||||||
|
@memset(unused, undefined);
|
||||||
|
} else {
|
||||||
|
const unused = mem.sliceAsBytes(slice[0..]);
|
||||||
|
@memset(unused, undefined);
|
||||||
|
const unused2 = mem.sliceAsBytes(self.readableSliceMut(slice.len)[0 .. count - slice.len]);
|
||||||
|
@memset(unused2, undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (autoalign and self.count == count) {
|
||||||
|
self.head = 0;
|
||||||
|
self.count = 0;
|
||||||
|
} else {
|
||||||
|
var head = self.head + count;
|
||||||
|
if (powers_of_two) {
|
||||||
|
// Note it is safe to do a wrapping subtract as
|
||||||
|
// bitwise & with all 1s is a noop
|
||||||
|
head &= self.buf.len -% 1;
|
||||||
|
} else {
|
||||||
|
head %= self.buf.len;
|
||||||
|
}
|
||||||
|
self.head = head;
|
||||||
|
self.count -= count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the next item from the fifo
|
||||||
|
pub fn readItem(self: *Self) ?T {
|
||||||
|
if (self.count == 0) return null;
|
||||||
|
|
||||||
|
const c = self.buf[self.head];
|
||||||
|
self.discard(1);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read data from the fifo into `dst`, returns number of items copied.
|
||||||
|
pub fn read(self: *Self, dst: []T) usize {
|
||||||
|
var dst_left = dst;
|
||||||
|
|
||||||
|
while (dst_left.len > 0) {
|
||||||
|
const slice = self.readableSlice(0);
|
||||||
|
if (slice.len == 0) break;
|
||||||
|
const n = @min(slice.len, dst_left.len);
|
||||||
|
@memcpy(dst_left[0..n], slice[0..n]);
|
||||||
|
self.discard(n);
|
||||||
|
dst_left = dst_left[n..];
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst.len - dst_left.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as `read` except it returns an error union
|
||||||
|
/// The purpose of this function existing is to match `std.io.Reader` API.
|
||||||
|
fn readFn(self: *Self, dest: []u8) error{}!usize {
|
||||||
|
return self.read(dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reader(self: *Self) Reader {
|
||||||
|
return .{ .context = self };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns number of items available in fifo
|
||||||
|
pub fn writableLength(self: Self) usize {
|
||||||
|
return self.buf.len - self.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the first section of writable buffer.
|
||||||
|
/// Note that this may be of length 0
|
||||||
|
pub fn writableSlice(self: SliceSelfArg, offset: usize) []T {
|
||||||
|
if (offset > self.buf.len) return &[_]T{};
|
||||||
|
|
||||||
|
const tail = self.head + offset + self.count;
|
||||||
|
if (tail < self.buf.len) {
|
||||||
|
return self.buf[tail..];
|
||||||
|
} else {
|
||||||
|
return self.buf[tail - self.buf.len ..][0 .. self.writableLength() - offset];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a writable buffer of at least `size` items, allocating memory as needed.
|
||||||
|
/// Use `fifo.update` once you've written data to it.
|
||||||
|
pub fn writableWithSize(self: *Self, size: usize) ![]T {
|
||||||
|
try self.ensureUnusedCapacity(size);
|
||||||
|
|
||||||
|
// try to avoid realigning buffer
|
||||||
|
var slice = self.writableSlice(0);
|
||||||
|
if (slice.len < size) {
|
||||||
|
self.realign();
|
||||||
|
slice = self.writableSlice(0);
|
||||||
|
}
|
||||||
|
return slice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the tail location of the buffer (usually follows use of writable/writableWithSize)
|
||||||
|
pub fn update(self: *Self, count: usize) void {
|
||||||
|
assert(self.count + count <= self.buf.len);
|
||||||
|
self.count += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends the data in `src` to the fifo.
|
||||||
|
/// You must have ensured there is enough space.
|
||||||
|
pub fn writeAssumeCapacity(self: *Self, src: []const T) void {
|
||||||
|
assert(self.writableLength() >= src.len);
|
||||||
|
|
||||||
|
var src_left = src;
|
||||||
|
while (src_left.len > 0) {
|
||||||
|
const writable_slice = self.writableSlice(0);
|
||||||
|
assert(writable_slice.len != 0);
|
||||||
|
const n = @min(writable_slice.len, src_left.len);
|
||||||
|
@memcpy(writable_slice[0..n], src_left[0..n]);
|
||||||
|
self.update(n);
|
||||||
|
src_left = src_left[n..];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a single item to the fifo
|
||||||
|
pub fn writeItem(self: *Self, item: T) !void {
|
||||||
|
try self.ensureUnusedCapacity(1);
|
||||||
|
return self.writeItemAssumeCapacity(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeItemAssumeCapacity(self: *Self, item: T) void {
|
||||||
|
var tail = self.head + self.count;
|
||||||
|
if (powers_of_two) {
|
||||||
|
tail &= self.buf.len - 1;
|
||||||
|
} else {
|
||||||
|
tail %= self.buf.len;
|
||||||
|
}
|
||||||
|
self.buf[tail] = item;
|
||||||
|
self.update(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends the data in `src` to the fifo.
|
||||||
|
/// Allocates more memory as necessary
|
||||||
|
pub fn write(self: *Self, src: []const T) !void {
|
||||||
|
try self.ensureUnusedCapacity(src.len);
|
||||||
|
|
||||||
|
return self.writeAssumeCapacity(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as `write` except it returns the number of bytes written, which is always the same
|
||||||
|
/// as `bytes.len`. The purpose of this function existing is to match `std.io.Writer` API.
|
||||||
|
fn appendWrite(self: *Self, bytes: []const u8) error{OutOfMemory}!usize {
|
||||||
|
try self.write(bytes);
|
||||||
|
return bytes.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writer(self: *Self) Writer {
|
||||||
|
return .{ .context = self };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make `count` items available before the current read location
|
||||||
|
fn rewind(self: *Self, count: usize) void {
|
||||||
|
assert(self.writableLength() >= count);
|
||||||
|
|
||||||
|
var head = self.head + (self.buf.len - count);
|
||||||
|
if (powers_of_two) {
|
||||||
|
head &= self.buf.len - 1;
|
||||||
|
} else {
|
||||||
|
head %= self.buf.len;
|
||||||
|
}
|
||||||
|
self.head = head;
|
||||||
|
self.count += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Place data back into the read stream
|
||||||
|
pub fn unget(self: *Self, src: []const T) !void {
|
||||||
|
try self.ensureUnusedCapacity(src.len);
|
||||||
|
|
||||||
|
self.rewind(src.len);
|
||||||
|
|
||||||
|
const slice = self.readableSliceMut(0);
|
||||||
|
if (src.len < slice.len) {
|
||||||
|
@memcpy(slice[0..src.len], src);
|
||||||
|
} else {
|
||||||
|
@memcpy(slice, src[0..slice.len]);
|
||||||
|
const slice2 = self.readableSliceMut(slice.len);
|
||||||
|
@memcpy(slice2[0 .. src.len - slice.len], src[slice.len..]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the item at `offset`.
|
||||||
|
/// Asserts offset is within bounds.
|
||||||
|
pub fn peekItem(self: Self, offset: usize) T {
|
||||||
|
assert(offset < self.count);
|
||||||
|
|
||||||
|
var index = self.head + offset;
|
||||||
|
if (powers_of_two) {
|
||||||
|
index &= self.buf.len - 1;
|
||||||
|
} else {
|
||||||
|
index %= self.buf.len;
|
||||||
|
}
|
||||||
|
return self.buf[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pump data from a reader into a writer.
|
||||||
|
/// Stops when reader returns 0 bytes (EOF).
|
||||||
|
/// Buffer size must be set before calling; a buffer length of 0 is invalid.
|
||||||
|
pub fn pump(self: *Self, src_reader: anytype, dest_writer: anytype) !void {
|
||||||
|
assert(self.buf.len > 0);
|
||||||
|
while (true) {
|
||||||
|
if (self.writableLength() > 0) {
|
||||||
|
const n = try src_reader.read(self.writableSlice(0));
|
||||||
|
if (n == 0) break; // EOF
|
||||||
|
self.update(n);
|
||||||
|
}
|
||||||
|
self.discard(try dest_writer.write(self.readableSlice(0)));
|
||||||
|
}
|
||||||
|
// flush remaining data
|
||||||
|
while (self.readableLength() > 0) {
|
||||||
|
self.discard(try dest_writer.write(self.readableSlice(0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toOwnedSlice(self: *Self) Allocator.Error![]T {
|
||||||
|
if (self.head != 0) self.realign();
|
||||||
|
assert(self.head == 0);
|
||||||
|
assert(self.count <= self.buf.len);
|
||||||
|
const allocator = self.allocator;
|
||||||
|
if (allocator.resize(self.buf, self.count)) {
|
||||||
|
const result = self.buf[0..self.count];
|
||||||
|
self.* = Self.init(allocator);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const new_memory = try allocator.dupe(T, self.buf[0..self.count]);
|
||||||
|
allocator.free(self.buf);
|
||||||
|
self.* = Self.init(allocator);
|
||||||
|
return new_memory;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "LinearFifo(u8, .Dynamic) discard(0) from empty buffer should not error on overflow" {
|
||||||
|
var fifo = LinearFifo(u8, .dynamic).init(testing.allocator);
|
||||||
|
defer fifo.deinit();
|
||||||
|
|
||||||
|
// If overflow is not explicitly allowed this will crash in debug / safe mode
|
||||||
|
fifo.discard(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "LinearFifo(u8, .Dynamic)" {
|
||||||
|
var fifo = LinearFifo(u8, .dynamic).init(testing.allocator);
|
||||||
|
defer fifo.deinit();
|
||||||
|
|
||||||
|
try fifo.write("HELLO");
|
||||||
|
try testing.expectEqual(@as(usize, 5), fifo.readableLength());
|
||||||
|
try testing.expectEqualSlices(u8, "HELLO", fifo.readableSlice(0));
|
||||||
|
|
||||||
|
{
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < 5) : (i += 1) {
|
||||||
|
try fifo.write(&[_]u8{fifo.peekItem(i)});
|
||||||
|
}
|
||||||
|
try testing.expectEqual(@as(usize, 10), fifo.readableLength());
|
||||||
|
try testing.expectEqualSlices(u8, "HELLOHELLO", fifo.readableSlice(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
try testing.expectEqual(@as(u8, 'H'), fifo.readItem().?);
|
||||||
|
try testing.expectEqual(@as(u8, 'E'), fifo.readItem().?);
|
||||||
|
try testing.expectEqual(@as(u8, 'L'), fifo.readItem().?);
|
||||||
|
try testing.expectEqual(@as(u8, 'L'), fifo.readItem().?);
|
||||||
|
try testing.expectEqual(@as(u8, 'O'), fifo.readItem().?);
|
||||||
|
}
|
||||||
|
try testing.expectEqual(@as(usize, 5), fifo.readableLength());
|
||||||
|
|
||||||
|
{ // Writes that wrap around
|
||||||
|
try testing.expectEqual(@as(usize, 11), fifo.writableLength());
|
||||||
|
try testing.expectEqual(@as(usize, 6), fifo.writableSlice(0).len);
|
||||||
|
fifo.writeAssumeCapacity("6<chars<11");
|
||||||
|
try testing.expectEqualSlices(u8, "HELLO6<char", fifo.readableSlice(0));
|
||||||
|
try testing.expectEqualSlices(u8, "s<11", fifo.readableSlice(11));
|
||||||
|
try testing.expectEqualSlices(u8, "11", fifo.readableSlice(13));
|
||||||
|
try testing.expectEqualSlices(u8, "", fifo.readableSlice(15));
|
||||||
|
fifo.discard(11);
|
||||||
|
try testing.expectEqualSlices(u8, "s<11", fifo.readableSlice(0));
|
||||||
|
fifo.discard(4);
|
||||||
|
try testing.expectEqual(@as(usize, 0), fifo.readableLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const buf = try fifo.writableWithSize(12);
|
||||||
|
try testing.expectEqual(@as(usize, 12), buf.len);
|
||||||
|
var i: u8 = 0;
|
||||||
|
while (i < 10) : (i += 1) {
|
||||||
|
buf[i] = i + 'a';
|
||||||
|
}
|
||||||
|
fifo.update(10);
|
||||||
|
try testing.expectEqualSlices(u8, "abcdefghij", fifo.readableSlice(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
try fifo.unget("prependedstring");
|
||||||
|
var result: [30]u8 = undefined;
|
||||||
|
try testing.expectEqualSlices(u8, "prependedstringabcdefghij", result[0..fifo.read(&result)]);
|
||||||
|
try fifo.unget("b");
|
||||||
|
try fifo.unget("a");
|
||||||
|
try testing.expectEqualSlices(u8, "ab", result[0..fifo.read(&result)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fifo.shrink(0);
|
||||||
|
|
||||||
|
{
|
||||||
|
try fifo.writer().print("{s}, {s}!", .{ "Hello", "World" });
|
||||||
|
var result: [30]u8 = undefined;
|
||||||
|
try testing.expectEqualSlices(u8, "Hello, World!", result[0..fifo.read(&result)]);
|
||||||
|
try testing.expectEqual(@as(usize, 0), fifo.readableLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
try fifo.writer().writeAll("This is a test");
|
||||||
|
var result: [30]u8 = undefined;
|
||||||
|
try testing.expectEqualSlices(u8, "This", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?);
|
||||||
|
try testing.expectEqualSlices(u8, "is", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?);
|
||||||
|
try testing.expectEqualSlices(u8, "a", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?);
|
||||||
|
try testing.expectEqualSlices(u8, "test", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
try fifo.ensureTotalCapacity(1);
|
||||||
|
var in_fbs = std.io.fixedBufferStream("pump test");
|
||||||
|
var out_buf: [50]u8 = undefined;
|
||||||
|
var out_fbs = std.io.fixedBufferStream(&out_buf);
|
||||||
|
try fifo.pump(in_fbs.reader(), out_fbs.writer());
|
||||||
|
try testing.expectEqualSlices(u8, in_fbs.buffer, out_fbs.getWritten());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test LinearFifo {
|
||||||
|
inline for ([_]type{ u1, u8, u16, u64 }) |T| {
|
||||||
|
inline for ([_]LinearFifoBufferType{ LinearFifoBufferType{ .static = 32 }, .slice, .dynamic }) |bt| {
|
||||||
|
const FifoType = LinearFifo(T, bt);
|
||||||
|
var buf: if (bt == .slice) [32]T else void = undefined;
|
||||||
|
var fifo = switch (bt) {
|
||||||
|
.static => FifoType.init(),
|
||||||
|
.slice => FifoType.init(buf[0..]),
|
||||||
|
.dynamic => FifoType.init(testing.allocator),
|
||||||
|
};
|
||||||
|
defer fifo.deinit();
|
||||||
|
|
||||||
|
try fifo.write(&[_]T{ 0, 1, 1, 0, 1 });
|
||||||
|
try testing.expectEqual(@as(usize, 5), fifo.readableLength());
|
||||||
|
|
||||||
|
{
|
||||||
|
try testing.expectEqual(@as(T, 0), fifo.readItem().?);
|
||||||
|
try testing.expectEqual(@as(T, 1), fifo.readItem().?);
|
||||||
|
try testing.expectEqual(@as(T, 1), fifo.readItem().?);
|
||||||
|
try testing.expectEqual(@as(T, 0), fifo.readItem().?);
|
||||||
|
try testing.expectEqual(@as(T, 1), fifo.readItem().?);
|
||||||
|
try testing.expectEqual(@as(usize, 0), fifo.readableLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
try fifo.writeItem(1);
|
||||||
|
try fifo.writeItem(1);
|
||||||
|
try fifo.writeItem(1);
|
||||||
|
try testing.expectEqual(@as(usize, 3), fifo.readableLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var readBuf: [3]T = undefined;
|
||||||
|
const n = fifo.read(&readBuf);
|
||||||
|
try testing.expectEqual(@as(usize, 3), n); // NOTE: It should be the number of items.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
119
src/main.zig
119
src/main.zig
@@ -31,7 +31,7 @@ const params = clap.parseParamsComptime(
|
|||||||
\\
|
\\
|
||||||
);
|
);
|
||||||
|
|
||||||
pub fn main() void {
|
pub fn main() !void {
|
||||||
// Main Allocator for ZBA
|
// Main Allocator for ZBA
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
defer std.debug.assert(gpa.deinit() == .ok);
|
defer std.debug.assert(gpa.deinit() == .ok);
|
||||||
@@ -40,10 +40,8 @@ pub fn main() void {
|
|||||||
|
|
||||||
// Determine the Data Directory (stores saves)
|
// Determine the Data Directory (stores saves)
|
||||||
const data_path = blk: {
|
const data_path = blk: {
|
||||||
const result = known_folders.getPath(allocator, .data);
|
const path = (try known_folders.getPath(allocator, .data)) orelse return error.unknown_data_folder;
|
||||||
const option = result catch |e| exitln("interrupted while determining the data folder: {}", .{e});
|
try makePath(path, "zba" ++ std.fs.path.sep_str ++ "save");
|
||||||
const path = option orelse exitln("no valid data folder found", .{});
|
|
||||||
ensureDataDirsExist(path) catch |e| exitln("failed to create folders under \"{s}\": {}", .{ path, e });
|
|
||||||
|
|
||||||
break :blk path;
|
break :blk path;
|
||||||
};
|
};
|
||||||
@@ -51,47 +49,41 @@ pub fn main() void {
|
|||||||
|
|
||||||
// Determine the Config Directory
|
// Determine the Config Directory
|
||||||
const config_path = blk: {
|
const config_path = blk: {
|
||||||
const result = known_folders.getPath(allocator, .roaming_configuration);
|
const path = (try known_folders.getPath(allocator, .roaming_configuration)) orelse return error.unknown_config_folder;
|
||||||
const option = result catch |e| exitln("interreupted while determining the config folder: {}", .{e});
|
try makePath(path, "zba");
|
||||||
const path = option orelse exitln("no valid config folder found", .{});
|
|
||||||
ensureConfigDirExists(path) catch |e| exitln("failed to create required folder \"{s}\": {}", .{ path, e });
|
|
||||||
|
|
||||||
break :blk path;
|
break :blk path;
|
||||||
};
|
};
|
||||||
defer allocator.free(config_path);
|
defer allocator.free(config_path);
|
||||||
|
|
||||||
// Parse CLI
|
// Parse CLI
|
||||||
|
const result = try clap.parse(clap.Help, ¶ms, clap.parsers.default, .{ .allocator = allocator });
|
||||||
const result = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{ .allocator = allocator }) catch |e| exitln("failed to parse cli: {}", .{e});
|
|
||||||
defer result.deinit();
|
defer result.deinit();
|
||||||
|
|
||||||
// TODO: Move config file to XDG Config directory?
|
// TODO: Move config file to XDG Config directory?
|
||||||
const cfg_file_path = configFilePath(allocator, config_path) catch |e| exitln("failed to ready config file for access: {}", .{e});
|
try makeConfigFilePath(config_path);
|
||||||
defer allocator.free(cfg_file_path);
|
try config.load(allocator, config_path);
|
||||||
|
|
||||||
config.load(allocator, cfg_file_path) catch |e| exitln("failed to load config file: {}", .{e});
|
var paths = try handleArguments(allocator, data_path, &result);
|
||||||
|
|
||||||
var paths = handleArguments(allocator, data_path, &result) catch |e| exitln("failed to handle cli arguments: {}", .{e});
|
|
||||||
defer paths.deinit(allocator);
|
defer paths.deinit(allocator);
|
||||||
|
|
||||||
// if paths.bios is null, then we want to see if it's in the data directory
|
// if paths.bios is null, then we want to see if it's in the data directory
|
||||||
if (paths.bios == null) blk: {
|
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});
|
const bios_path = try std.mem.join(allocator, "/", &.{ data_path, "zba", "gba_bios.bin" }); // FIXME: std.fs.path_sep or something
|
||||||
defer allocator.free(bios_path);
|
defer allocator.free(bios_path);
|
||||||
|
|
||||||
_ = std.fs.cwd().statFile(bios_path) catch |e| switch (e) {
|
_ = std.fs.cwd().statFile(bios_path) catch |e| {
|
||||||
error.FileNotFound => { // ZBA will crash on attempt to read BIOS but that's fine
|
if (e != std.fs.Dir.StatFileError.FileNotFound) return e;
|
||||||
log.err("file located at {s} was not found", .{bios_path});
|
|
||||||
break :blk;
|
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});
|
paths.bios = try allocator.dupe(u8, bios_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
const log_file = switch (config.config().debug.cpu_trace) {
|
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}),
|
true => try std.fs.cwd().createFile("zba.log", .{}),
|
||||||
false => null,
|
false => null,
|
||||||
};
|
};
|
||||||
defer if (log_file) |file| file.close();
|
defer if (log_file) |file| file.close();
|
||||||
@@ -107,7 +99,7 @@ pub fn main() void {
|
|||||||
|
|
||||||
var cpu = Arm7tdmi.init(ischeduler, ibus);
|
var cpu = Arm7tdmi.init(ischeduler, ibus);
|
||||||
|
|
||||||
bus.init(allocator, &scheduler, &cpu, paths) catch |e| exitln("failed to init zba bus: {}", .{e});
|
try bus.init(allocator, &scheduler, &cpu, paths);
|
||||||
defer bus.deinit();
|
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 != 0 or paths.bios == null) {
|
||||||
@@ -117,10 +109,10 @@ pub fn main() void {
|
|||||||
const title_ptr = if (paths.rom != null) &bus.pak.title else null;
|
const title_ptr = if (paths.rom != null) &bus.pak.title else null;
|
||||||
|
|
||||||
// TODO: Just copy the title instead of grabbing a pointer to it
|
// TODO: Just copy the title instead of grabbing a pointer to it
|
||||||
var gui = Gui.init(allocator, &bus.apu, title_ptr) catch |e| exitln("failed to init gui: {}", .{e});
|
var gui = try Gui.init(allocator, &bus.apu, title_ptr);
|
||||||
defer gui.deinit();
|
defer gui.deinit();
|
||||||
|
|
||||||
var sync = Synchro.init(allocator) catch |e| exitln("failed to allocate sync types: {}", .{e});
|
var sync = try Synchro.init(allocator);
|
||||||
defer sync.deinit(allocator);
|
defer sync.deinit(allocator);
|
||||||
|
|
||||||
if (result.args.gdb != 0) {
|
if (result.args.gdb != 0) {
|
||||||
@@ -128,39 +120,27 @@ pub fn main() void {
|
|||||||
const EmuThing = @import("core/emu.zig").EmuThing;
|
const EmuThing = @import("core/emu.zig").EmuThing;
|
||||||
|
|
||||||
var wrapper = EmuThing.init(&cpu, &scheduler);
|
var wrapper = EmuThing.init(&cpu, &scheduler);
|
||||||
var emulator = wrapper.interface(allocator);
|
var emulator = wrapper.interface();
|
||||||
defer emulator.deinit();
|
defer emulator.deinit(allocator);
|
||||||
|
|
||||||
log.info("Ready to connect", .{});
|
log.info("Ready to connect", .{});
|
||||||
|
|
||||||
var server = Server.init(
|
var server = try Server.init(emulator, .{ .memory_map = EmuThing.map, .target = EmuThing.target });
|
||||||
emulator,
|
|
||||||
.{ .memory_map = EmuThing.map, .target = EmuThing.target },
|
|
||||||
) catch |e| exitln("failed to init gdb server: {}", .{e});
|
|
||||||
defer server.deinit(allocator);
|
defer server.deinit(allocator);
|
||||||
|
|
||||||
log.info("Starting GDB Server Thread", .{});
|
log.info("Starting GDB Server Thread", .{});
|
||||||
|
|
||||||
const thread = std.Thread.spawn(.{}, Server.run, .{ &server, allocator, &sync.should_quit }) catch |e| exitln("gdb server thread crashed: {}", .{e});
|
const thread = try std.Thread.spawn(.{}, Server.run, .{ &server, allocator, &sync.should_quit });
|
||||||
defer thread.join();
|
defer thread.join();
|
||||||
|
|
||||||
gui.run(.{
|
try gui.run(.{ .cpu = &cpu, .scheduler = &scheduler, .sync = &sync });
|
||||||
.cpu = &cpu,
|
|
||||||
.scheduler = &scheduler,
|
|
||||||
.sync = &sync,
|
|
||||||
}) catch |e| exitln("main thread panicked: {}", .{e});
|
|
||||||
} else {
|
} else {
|
||||||
var tracker = FpsTracker.init();
|
var tracker = FpsTracker.init();
|
||||||
|
|
||||||
const thread = std.Thread.spawn(.{}, emu.run, .{ &cpu, &scheduler, &tracker, &sync }) catch |e| exitln("emu thread panicked: {}", .{e});
|
const thread = try std.Thread.spawn(.{}, emu.run, .{ &cpu, &scheduler, &tracker, &sync });
|
||||||
defer thread.join();
|
defer thread.join();
|
||||||
|
|
||||||
gui.run(.{
|
try gui.run(.{ .cpu = &cpu, .scheduler = &scheduler, .tracker = &tracker, .sync = &sync });
|
||||||
.cpu = &cpu,
|
|
||||||
.scheduler = &scheduler,
|
|
||||||
.tracker = &tracker,
|
|
||||||
.sync = &sync,
|
|
||||||
}) catch |e| exitln("main thread panicked: {}", .{e});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,51 +164,36 @@ fn handleArguments(allocator: Allocator, data_path: []const u8, result: *const c
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn configFilePath(allocator: Allocator, config_path: []const u8) ![]const u8 {
|
fn makeConfigFilePath(config_path: []const u8) !void {
|
||||||
const path = try std.fs.path.join(allocator, &[_][]const u8{ config_path, "zba", "config.toml" });
|
var dir = try std.fs.openDirAbsolute(config_path, .{});
|
||||||
errdefer allocator.free(path);
|
defer dir.close();
|
||||||
|
|
||||||
|
const sub_path = "zba" ++ std.fs.path.sep_str ++ "config.toml";
|
||||||
|
|
||||||
// We try to create the file exclusively, meaning that we err out if the file already exists.
|
// We try to create the file exclusively, meaning that we err out if the file already exists.
|
||||||
// All we care about is a file being there so we can just ignore that error in particular and
|
// All we care about is a file being there so we can just ignore that error in particular and
|
||||||
// continue down the happy pathj
|
// continue down the happy pathj
|
||||||
std.fs.accessAbsolute(path, .{}) catch |e| {
|
dir.access(sub_path, .{}) catch |e| {
|
||||||
if (e != error.FileNotFound) return e;
|
if (e != std.fs.Dir.AccessError.FileNotFound) return e;
|
||||||
|
|
||||||
const config_file = std.fs.createFileAbsolute(path, .{}) catch |err| exitln("failed to create \"{s}\": {}", .{ path, err });
|
const config_file = try dir.createFile(sub_path, .{});
|
||||||
defer config_file.close();
|
defer config_file.close();
|
||||||
|
|
||||||
try config_file.writeAll(@embedFile("example.toml"));
|
try config_file.writeAll(@embedFile("example.toml"));
|
||||||
};
|
};
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensureDataDirsExist(data_path: []const u8) !void {
|
|
||||||
var dir = try std.fs.openDirAbsolute(data_path, .{});
|
|
||||||
defer dir.close();
|
|
||||||
|
|
||||||
// Will recursively create directories
|
|
||||||
try dir.makePath("zba" ++ std.fs.path.sep_str ++ "save");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensureConfigDirExists(config_path: []const u8) !void {
|
|
||||||
var dir = try std.fs.openDirAbsolute(config_path, .{});
|
|
||||||
defer dir.close();
|
|
||||||
|
|
||||||
try dir.makePath("zba");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn romPath(allocator: Allocator, 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) {
|
return switch (result.positionals.len) {
|
||||||
0 => null,
|
0 => null,
|
||||||
1 => try allocator.dupe(u8, result.positionals[0]),
|
1 => if (result.positionals[0]) |path| try allocator.dupe(u8, path) else null,
|
||||||
else => exitln("ZBA received too many positional arguments.", .{}),
|
else => error.too_many_positional_arguments,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exitln(comptime format: []const u8, args: anytype) noreturn {
|
fn makePath(path: []const u8, sub_path: []const u8) !void {
|
||||||
const stderr = std.io.getStdErr().writer();
|
var dir = try std.fs.openDirAbsolute(path, .{});
|
||||||
stderr.print(format, args) catch {}; // Just exit already...
|
defer dir.close();
|
||||||
stderr.writeByte('\n') catch {};
|
|
||||||
std.process.exit(1);
|
try dir.makePath(sub_path);
|
||||||
}
|
}
|
||||||
|
|||||||
348
src/platform.zig
348
src/platform.zig
@@ -1,7 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const SDL = @import("sdl2");
|
|
||||||
const gl = @import("gl");
|
const gl = @import("gl");
|
||||||
const zgui = @import("zgui");
|
const zgui = @import("zgui");
|
||||||
|
const c = @import("lib.zig").c;
|
||||||
|
|
||||||
const emu = @import("core/emu.zig");
|
const emu = @import("core/emu.zig");
|
||||||
const config = @import("config.zig");
|
const config = @import("config.zig");
|
||||||
@@ -18,24 +18,27 @@ const KeyInput = @import("core/bus/io.zig").KeyInput;
|
|||||||
const gba_width = @import("core/ppu.zig").width;
|
const gba_width = @import("core/ppu.zig").width;
|
||||||
const gba_height = @import("core/ppu.zig").height;
|
const gba_height = @import("core/ppu.zig").height;
|
||||||
|
|
||||||
const GLuint = gl.GLuint;
|
const GLsizei = gl.sizei;
|
||||||
const GLsizei = gl.GLsizei;
|
const SDL_GLContext = *c.SDL_GLContextState;
|
||||||
const SDL_GLContext = *anyopaque;
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
pub const Dimensions = struct { width: u32, height: u32 };
|
pub const Dimensions = struct { width: u32, height: u32 };
|
||||||
const default_dim: Dimensions = .{ .width = 1280, .height = 720 };
|
const default_dim: Dimensions = .{ .width = 1280, .height = 720 };
|
||||||
|
|
||||||
pub const sample_rate = 1 << 15;
|
pub const sample_rate = 1 << 15;
|
||||||
pub const sample_format = SDL.AUDIO_U16;
|
// pub const sample_format = SDL.AUDIO_U16;
|
||||||
|
|
||||||
const window_title = "ZBA";
|
const window_title = "ZBA";
|
||||||
|
|
||||||
|
const errify = @import("lib.zig").errify;
|
||||||
|
|
||||||
|
var gl_procs: gl.ProcTable = undefined;
|
||||||
|
|
||||||
pub const Gui = struct {
|
pub const Gui = struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
const log = std.log.scoped(.Gui);
|
const log = std.log.scoped(.Gui);
|
||||||
|
|
||||||
window: *SDL.SDL_Window,
|
window: *c.SDL_Window,
|
||||||
ctx: SDL_GLContext,
|
ctx: SDL_GLContext,
|
||||||
audio: Audio,
|
audio: Audio,
|
||||||
|
|
||||||
@@ -43,36 +46,44 @@ pub const Gui = struct {
|
|||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, apu: *Apu, title_opt: ?*const [12]u8) !Self {
|
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();
|
errdefer |err| if (err == error.sdl_error) log.err("SDL Error: {s}", .{c.SDL_GetError()});
|
||||||
if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_PROFILE_MASK, SDL.SDL_GL_CONTEXT_PROFILE_CORE) < 0) panic();
|
|
||||||
if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_MAJOR_VERSION, 3) < 0) panic();
|
|
||||||
if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_MAJOR_VERSION, 3) < 0) panic();
|
|
||||||
|
|
||||||
const window = SDL.SDL_CreateWindow(
|
c.SDL_SetMainReady();
|
||||||
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,
|
|
||||||
) orelse panic();
|
|
||||||
|
|
||||||
const ctx = SDL.SDL_GL_CreateContext(window) orelse panic();
|
try errify(c.SDL_Init(c.SDL_INIT_VIDEO | c.SDL_INIT_AUDIO | c.SDL_INIT_EVENTS));
|
||||||
if (SDL.SDL_GL_MakeCurrent(window, ctx) < 0) panic();
|
|
||||||
|
|
||||||
gl.load(ctx, Self.glGetProcAddress) catch {};
|
try errify(c.SDL_SetAppMetadata(window_title, "0.1.0", "moe.paoda.zba"));
|
||||||
if (SDL.SDL_GL_SetSwapInterval(@intFromBool(config.config().host.vsync)) < 0) panic();
|
try errify(c.SDL_GL_SetAttribute(c.SDL_GL_CONTEXT_MAJOR_VERSION, gl.info.version_major));
|
||||||
|
try errify(c.SDL_GL_SetAttribute(c.SDL_GL_CONTEXT_MINOR_VERSION, gl.info.version_minor));
|
||||||
|
try errify(c.SDL_GL_SetAttribute(c.SDL_GL_CONTEXT_PROFILE_MASK, c.SDL_GL_CONTEXT_PROFILE_CORE));
|
||||||
|
try errify(c.SDL_GL_SetAttribute(c.SDL_GL_CONTEXT_FLAGS, c.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG));
|
||||||
|
|
||||||
|
const window: *c.SDL_Window = try errify(c.SDL_CreateWindow(window_title, default_dim.width, default_dim.height, c.SDL_WINDOW_OPENGL | c.SDL_WINDOW_RESIZABLE));
|
||||||
|
errdefer c.SDL_DestroyWindow(window);
|
||||||
|
|
||||||
|
const gl_ctx = try errify(c.SDL_GL_CreateContext(window));
|
||||||
|
errdefer errify(c.SDL_GL_DestroyContext(gl_ctx)) catch {};
|
||||||
|
|
||||||
|
try errify(c.SDL_GL_MakeCurrent(window, gl_ctx));
|
||||||
|
errdefer errify(c.SDL_GL_MakeCurrent(window, null)) catch {};
|
||||||
|
|
||||||
|
if (!gl_procs.init(c.SDL_GL_GetProcAddress)) return error.gl_init_failed;
|
||||||
|
|
||||||
|
gl.makeProcTableCurrent(&gl_procs);
|
||||||
|
errdefer gl.makeProcTableCurrent(null);
|
||||||
|
|
||||||
|
try errify(c.SDL_GL_SetSwapInterval(@intFromBool(config.config().host.vsync)));
|
||||||
|
|
||||||
zgui.init(allocator);
|
zgui.init(allocator);
|
||||||
zgui.plot.init();
|
zgui.plot.init();
|
||||||
zgui.backend.init(window, ctx, "#version 330 core");
|
zgui.backend.init(window, gl_ctx);
|
||||||
|
|
||||||
// zgui.io.setIniFilename(null);
|
// zgui.io.setIniFilename(null);
|
||||||
|
|
||||||
return Self{
|
return Self{
|
||||||
.window = window,
|
.window = window,
|
||||||
.ctx = ctx,
|
.ctx = gl_ctx,
|
||||||
.audio = Audio.init(apu),
|
.audio = try Audio.init(apu),
|
||||||
|
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.state = try imgui.State.init(allocator, title_opt),
|
.state = try imgui.State.init(allocator, title_opt),
|
||||||
@@ -87,9 +98,9 @@ pub const Gui = struct {
|
|||||||
zgui.plot.deinit();
|
zgui.plot.deinit();
|
||||||
zgui.deinit();
|
zgui.deinit();
|
||||||
|
|
||||||
SDL.SDL_GL_DeleteContext(self.ctx);
|
errify(c.SDL_GL_DestroyContext(self.ctx)) catch {};
|
||||||
SDL.SDL_DestroyWindow(self.window);
|
c.SDL_DestroyWindow(self.window);
|
||||||
SDL.SDL_Quit();
|
c.SDL_Quit();
|
||||||
|
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
@@ -109,17 +120,19 @@ pub const Gui = struct {
|
|||||||
const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr));
|
const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr));
|
||||||
|
|
||||||
const vao_id = opengl_impl.vao();
|
const vao_id = opengl_impl.vao();
|
||||||
defer gl.deleteVertexArrays(1, &[_]GLuint{vao_id});
|
defer gl.DeleteVertexArrays(1, vao_id[0..]);
|
||||||
|
|
||||||
const emu_tex = opengl_impl.screenTex(bus_ptr.ppu.framebuf.get(.Renderer));
|
const emu_tex = opengl_impl.screenTex(bus_ptr.ppu.framebuf.get(.Renderer));
|
||||||
const out_tex = opengl_impl.outTex();
|
defer gl.DeleteTextures(1, emu_tex[0..]);
|
||||||
defer gl.deleteTextures(2, &[_]GLuint{ emu_tex, out_tex });
|
|
||||||
|
|
||||||
const fbo_id = try opengl_impl.frameBuffer(out_tex);
|
const out_tex = opengl_impl.outTex();
|
||||||
defer gl.deleteFramebuffers(1, &fbo_id);
|
defer gl.DeleteTextures(1, out_tex[0..]);
|
||||||
|
|
||||||
|
const fbo_id = try opengl_impl.frameBuffer(out_tex[0]);
|
||||||
|
defer gl.DeleteFramebuffers(1, fbo_id[0..]);
|
||||||
|
|
||||||
const prog_id = try opengl_impl.program(); // Dynamic Shaders?
|
const prog_id = try opengl_impl.program(); // Dynamic Shaders?
|
||||||
defer gl.deleteProgram(prog_id);
|
defer gl.DeleteProgram(prog_id);
|
||||||
|
|
||||||
var win_dim: Dimensions = default_dim;
|
var win_dim: Dimensions = default_dim;
|
||||||
|
|
||||||
@@ -128,61 +141,59 @@ pub const Gui = struct {
|
|||||||
// should exit, in which case we should also handle this
|
// should exit, in which case we should also handle this
|
||||||
if (self.state.should_quit or sync.should_quit.load(.monotonic)) break :emu_loop;
|
if (self.state.should_quit or sync.should_quit.load(.monotonic)) break :emu_loop;
|
||||||
|
|
||||||
var event: SDL.SDL_Event = undefined;
|
var event: c.SDL_Event = undefined;
|
||||||
while (SDL.SDL_PollEvent(&event) != 0) {
|
|
||||||
|
while (c.SDL_PollEvent(&event)) {
|
||||||
_ = zgui.backend.processEvent(&event);
|
_ = zgui.backend.processEvent(&event);
|
||||||
|
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
SDL.SDL_QUIT => break :emu_loop,
|
c.SDL_EVENT_QUIT => break :emu_loop,
|
||||||
SDL.SDL_KEYDOWN => {
|
c.SDL_EVENT_KEY_DOWN => {
|
||||||
// TODO: Make use of compare_and_xor?
|
// TODO: Make use of compare_and_xor?
|
||||||
const key_code = event.key.keysym.sym;
|
|
||||||
var keyinput: KeyInput = .{ .raw = 0x0000 };
|
var keyinput: KeyInput = .{ .raw = 0x0000 };
|
||||||
|
|
||||||
switch (key_code) {
|
switch (event.key.scancode) {
|
||||||
SDL.SDLK_UP => keyinput.up.set(),
|
c.SDL_SCANCODE_UP => keyinput.up.write(true),
|
||||||
SDL.SDLK_DOWN => keyinput.down.set(),
|
c.SDL_SCANCODE_DOWN => keyinput.down.write(true),
|
||||||
SDL.SDLK_LEFT => keyinput.left.set(),
|
c.SDL_SCANCODE_LEFT => keyinput.left.write(true),
|
||||||
SDL.SDLK_RIGHT => keyinput.right.set(),
|
c.SDL_SCANCODE_RIGHT => keyinput.right.write(true),
|
||||||
SDL.SDLK_x => keyinput.a.set(),
|
c.SDL_SCANCODE_X => keyinput.a.write(true),
|
||||||
SDL.SDLK_z => keyinput.b.set(),
|
c.SDL_SCANCODE_Z => keyinput.b.write(true),
|
||||||
SDL.SDLK_a => keyinput.shoulder_l.set(),
|
c.SDL_SCANCODE_A => keyinput.shoulder_l.write(true),
|
||||||
SDL.SDLK_s => keyinput.shoulder_r.set(),
|
c.SDL_SCANCODE_S => keyinput.shoulder_r.write(true),
|
||||||
SDL.SDLK_RETURN => keyinput.start.set(),
|
c.SDL_SCANCODE_RETURN => keyinput.start.write(true),
|
||||||
SDL.SDLK_RSHIFT => keyinput.select.set(),
|
c.SDL_SCANCODE_RSHIFT => keyinput.select.write(true),
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
bus_ptr.io.keyinput.fetchAnd(~keyinput.raw, .monotonic);
|
bus_ptr.io.keyinput.fetchAnd(~keyinput.raw, .monotonic);
|
||||||
},
|
},
|
||||||
SDL.SDL_KEYUP => {
|
c.SDL_EVENT_KEY_UP => {
|
||||||
|
// FIXME(paoda): merge with above?
|
||||||
// TODO: Make use of compare_and_xor?
|
// TODO: Make use of compare_and_xor?
|
||||||
const key_code = event.key.keysym.sym;
|
|
||||||
var keyinput: KeyInput = .{ .raw = 0x0000 };
|
var keyinput: KeyInput = .{ .raw = 0x0000 };
|
||||||
|
|
||||||
switch (key_code) {
|
switch (event.key.scancode) {
|
||||||
SDL.SDLK_UP => keyinput.up.set(),
|
c.SDL_SCANCODE_UP => keyinput.up.write(true),
|
||||||
SDL.SDLK_DOWN => keyinput.down.set(),
|
c.SDL_SCANCODE_DOWN => keyinput.down.write(true),
|
||||||
SDL.SDLK_LEFT => keyinput.left.set(),
|
c.SDL_SCANCODE_LEFT => keyinput.left.write(true),
|
||||||
SDL.SDLK_RIGHT => keyinput.right.set(),
|
c.SDL_SCANCODE_RIGHT => keyinput.right.write(true),
|
||||||
SDL.SDLK_x => keyinput.a.set(),
|
c.SDL_SCANCODE_X => keyinput.a.write(true),
|
||||||
SDL.SDLK_z => keyinput.b.set(),
|
c.SDL_SCANCODE_Z => keyinput.b.write(true),
|
||||||
SDL.SDLK_a => keyinput.shoulder_l.set(),
|
c.SDL_SCANCODE_A => keyinput.shoulder_l.write(true),
|
||||||
SDL.SDLK_s => keyinput.shoulder_r.set(),
|
c.SDL_SCANCODE_S => keyinput.shoulder_r.write(true),
|
||||||
SDL.SDLK_RETURN => keyinput.start.set(),
|
c.SDL_SCANCODE_RETURN => keyinput.start.write(true),
|
||||||
SDL.SDLK_RSHIFT => keyinput.select.set(),
|
c.SDL_SCANCODE_RSHIFT => keyinput.select.write(true),
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
bus_ptr.io.keyinput.fetchOr(keyinput.raw, .monotonic);
|
bus_ptr.io.keyinput.fetchOr(keyinput.raw, .monotonic);
|
||||||
},
|
},
|
||||||
SDL.SDL_WINDOWEVENT => {
|
c.SDL_EVENT_WINDOW_RESIZED => {
|
||||||
if (event.window.event == SDL.SDL_WINDOWEVENT_RESIZED) {
|
log.debug("window resized to: {}x{}", .{ event.window.data1, event.window.data2 });
|
||||||
log.debug("window resized to: {}x{}", .{ event.window.data1, event.window.data2 });
|
|
||||||
|
|
||||||
win_dim.width = @intCast(event.window.data1);
|
win_dim.width = @intCast(event.window.data1);
|
||||||
win_dim.height = @intCast(event.window.data2);
|
win_dim.height = @intCast(event.window.data2);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
@@ -191,16 +202,16 @@ pub const Gui = struct {
|
|||||||
var zgui_redraw: bool = false;
|
var zgui_redraw: bool = false;
|
||||||
|
|
||||||
switch (self.state.emulation) {
|
switch (self.state.emulation) {
|
||||||
.Transition => |inner| switch (inner) {
|
.Transition => |target| switch (target) {
|
||||||
.Active => {
|
.Active => {
|
||||||
sync.paused.store(false, .monotonic);
|
sync.paused.store(false, .monotonic);
|
||||||
if (!config.config().host.mute) SDL.SDL_PauseAudioDevice(self.audio.device, 0);
|
if (!config.config().host.mute) try errify(c.SDL_ResumeAudioStreamDevice(self.audio.stream));
|
||||||
|
|
||||||
self.state.emulation = .Active;
|
self.state.emulation = .Active;
|
||||||
},
|
},
|
||||||
.Inactive => {
|
.Inactive => {
|
||||||
// Assert that double pausing is impossible
|
// Assert that double pausing is impossible
|
||||||
SDL.SDL_PauseAudioDevice(self.audio.device, 1);
|
if (!config.config().host.mute) try errify(c.SDL_PauseAudioStreamDevice(self.audio.stream));
|
||||||
sync.paused.store(true, .monotonic);
|
sync.paused.store(true, .monotonic);
|
||||||
|
|
||||||
self.state.emulation = .Inactive;
|
self.state.emulation = .Inactive;
|
||||||
@@ -212,182 +223,163 @@ pub const Gui = struct {
|
|||||||
|
|
||||||
// Draw GBA Screen to Texture
|
// Draw GBA Screen to Texture
|
||||||
{
|
{
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo_id);
|
gl.BindFramebuffer(gl.FRAMEBUFFER, fbo_id[0]);
|
||||||
defer gl.bindFramebuffer(gl.FRAMEBUFFER, 0);
|
defer gl.BindFramebuffer(gl.FRAMEBUFFER, 0);
|
||||||
|
|
||||||
gl.viewport(0, 0, gba_width, gba_height);
|
gl.Viewport(0, 0, gba_width, gba_height);
|
||||||
opengl_impl.drawScreen(emu_tex, prog_id, vao_id, bus_ptr.ppu.framebuf.get(.Renderer));
|
opengl_impl.drawScreen(emu_tex[0], prog_id, vao_id[0], bus_ptr.ppu.framebuf.get(.Renderer));
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: We only really care about locking the audio device (and therefore writing silence)
|
// FIXME: We only really care about locking the audio device (and therefore writing silence)
|
||||||
// since if nfd-zig is used the emu may be paused for way too long. Perhaps we should try and limit
|
// since if nfd-zig is used the emu may be paused for way too long. Perhaps we should try and limit
|
||||||
// spurious calls to SDL_LockAudioDevice?
|
// spurious calls to SDL_LockAudioDevice?
|
||||||
SDL.SDL_LockAudioDevice(self.audio.device);
|
|
||||||
defer SDL.SDL_UnlockAudioDevice(self.audio.device);
|
|
||||||
|
|
||||||
zgui_redraw = imgui.draw(&self.state, sync, win_dim, cpu, out_tex);
|
try errify(c.SDL_LockAudioStream(self.audio.stream));
|
||||||
|
defer errify(c.SDL_UnlockAudioStream(self.audio.stream)) catch std.debug.panic("SDL Error: {s}", .{c.SDL_GetError()});
|
||||||
|
|
||||||
|
zgui_redraw = imgui.draw(&self.state, sync, win_dim, cpu, out_tex[0]);
|
||||||
},
|
},
|
||||||
.Inactive => zgui_redraw = imgui.draw(&self.state, sync, win_dim, cpu, out_tex),
|
.Inactive => zgui_redraw = imgui.draw(&self.state, sync, win_dim, cpu, out_tex[0]),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zgui_redraw) {
|
if (zgui_redraw) {
|
||||||
// Background Colour
|
// Background Colour
|
||||||
const size = zgui.io.getDisplaySize();
|
const size = zgui.io.getDisplaySize();
|
||||||
gl.viewport(0, 0, @intFromFloat(size[0]), @intFromFloat(size[1]));
|
gl.Viewport(0, 0, @intFromFloat(size[0]), @intFromFloat(size[1]));
|
||||||
gl.clearColor(0, 0, 0, 1.0);
|
gl.ClearColor(0, 0, 0, 1.0);
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
gl.Clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
zgui.backend.draw();
|
zgui.backend.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL.SDL_GL_SwapWindow(self.window);
|
try errify(c.SDL_GL_SwapWindow(self.window));
|
||||||
}
|
}
|
||||||
|
|
||||||
sync.should_quit.store(true, .monotonic);
|
sync.should_quit.store(true, .monotonic);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn glGetProcAddress(ctx: SDL.SDL_GLContext, proc: [:0]const u8) ?*anyopaque {
|
|
||||||
_ = ctx;
|
|
||||||
return SDL.SDL_GL_GetProcAddress(proc.ptr);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Audio = struct {
|
pub const Audio = struct {
|
||||||
const Self = @This();
|
const log = std.log.scoped(.platform_audio);
|
||||||
const log = std.log.scoped(.PlatformAudio);
|
|
||||||
|
|
||||||
device: SDL.SDL_AudioDeviceID,
|
stream: *c.SDL_AudioStream,
|
||||||
|
|
||||||
fn init(apu: *Apu) Self {
|
fn init(apu: *Apu) !@This() {
|
||||||
var have: SDL.SDL_AudioSpec = undefined;
|
const desired: c.SDL_AudioSpec = .{
|
||||||
var want: SDL.SDL_AudioSpec = std.mem.zeroes(SDL.SDL_AudioSpec);
|
.freq = sample_rate,
|
||||||
want.freq = sample_rate;
|
.format = c.SDL_AUDIO_S16LE,
|
||||||
want.format = sample_format;
|
.channels = 2,
|
||||||
want.channels = 2;
|
};
|
||||||
want.samples = 0x100;
|
|
||||||
want.callback = Self.callback;
|
|
||||||
want.userdata = apu;
|
|
||||||
|
|
||||||
std.debug.assert(sample_format == SDL.AUDIO_U16);
|
log.info("Host Sample Rate: {}Hz, Host Format: SDL_AUDIO_S16LE", .{sample_rate});
|
||||||
log.info("Host Sample Rate: {}Hz, Host Format: SDL.AUDIO_U16", .{sample_rate});
|
|
||||||
|
|
||||||
const device = SDL.SDL_OpenAudioDevice(null, 0, &want, &have, 0);
|
const stream = try errify(c.SDL_OpenAudioDeviceStream(c.SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &desired, null, null));
|
||||||
if (device == 0) panic();
|
errdefer c.SDL_DestroyAudioStream(stream);
|
||||||
|
|
||||||
return .{ .device = device };
|
apu.stream = stream;
|
||||||
|
return .{ .stream = stream };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinit(self: *Self) void {
|
fn deinit(self: *@This()) void {
|
||||||
SDL.SDL_CloseAudioDevice(self.device);
|
c.SDL_DestroyAudioStream(self.stream);
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export fn callback(userdata: ?*anyopaque, stream: [*c]u8, len: c_int) void {
|
|
||||||
const apu: *Apu = @ptrCast(@alignCast(userdata));
|
|
||||||
|
|
||||||
_ = SDL.SDL_AudioStreamGet(apu.stream, stream, len);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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 {
|
const opengl_impl = struct {
|
||||||
fn drawScreen(tex_id: GLuint, prog_id: GLuint, vao_id: GLuint, buf: []const u8) void {
|
fn drawScreen(tex_id: gl.uint, prog_id: gl.uint, vao_id: gl.uint, buf: []const u8) void {
|
||||||
gl.bindTexture(gl.TEXTURE_2D, tex_id);
|
gl.BindTexture(gl.TEXTURE_2D, tex_id);
|
||||||
defer gl.bindTexture(gl.TEXTURE_2D, 0);
|
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);
|
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
|
// Bind VAO
|
||||||
gl.bindVertexArray(vao_id);
|
gl.BindVertexArray(vao_id);
|
||||||
defer gl.bindVertexArray(0);
|
defer gl.BindVertexArray(0);
|
||||||
|
|
||||||
// Use compiled frag + vertex shader
|
// Use compiled frag + vertex shader
|
||||||
gl.useProgram(prog_id);
|
gl.UseProgram(prog_id);
|
||||||
defer gl.useProgram(0);
|
defer gl.UseProgram(0);
|
||||||
|
|
||||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 3);
|
gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn program() !GLuint {
|
fn program() !gl.uint {
|
||||||
const vert_shader = @embedFile("shader/pixelbuf.vert");
|
const vert_shader: [1][*]const u8 = .{@embedFile("shader/pixelbuf.vert")};
|
||||||
const frag_shader = @embedFile("shader/pixelbuf.frag");
|
const frag_shader: [1][*]const u8 = .{@embedFile("shader/pixelbuf.frag")};
|
||||||
|
|
||||||
const vs = gl.createShader(gl.VERTEX_SHADER);
|
const vs = gl.CreateShader(gl.VERTEX_SHADER);
|
||||||
defer gl.deleteShader(vs);
|
defer gl.DeleteShader(vs);
|
||||||
|
|
||||||
gl.shaderSource(vs, 1, &[_][*c]const u8{vert_shader}, 0);
|
gl.ShaderSource(vs, 1, vert_shader[0..], null);
|
||||||
gl.compileShader(vs);
|
gl.CompileShader(vs);
|
||||||
|
|
||||||
if (!shader.didCompile(vs)) return error.VertexCompileError;
|
if (!shader.didCompile(vs)) return error.VertexCompileError;
|
||||||
|
|
||||||
const fs = gl.createShader(gl.FRAGMENT_SHADER);
|
const fs = gl.CreateShader(gl.FRAGMENT_SHADER);
|
||||||
defer gl.deleteShader(fs);
|
defer gl.DeleteShader(fs);
|
||||||
|
|
||||||
gl.shaderSource(fs, 1, &[_][*c]const u8{frag_shader}, 0);
|
gl.ShaderSource(fs, 1, frag_shader[0..], null);
|
||||||
gl.compileShader(fs);
|
gl.CompileShader(fs);
|
||||||
|
|
||||||
if (!shader.didCompile(fs)) return error.FragmentCompileError;
|
if (!shader.didCompile(fs)) return error.FragmentCompileError;
|
||||||
|
|
||||||
const prog = gl.createProgram();
|
const prog = gl.CreateProgram();
|
||||||
gl.attachShader(prog, vs);
|
gl.AttachShader(prog, vs);
|
||||||
gl.attachShader(prog, fs);
|
gl.AttachShader(prog, fs);
|
||||||
gl.linkProgram(prog);
|
gl.LinkProgram(prog);
|
||||||
|
|
||||||
return prog;
|
return prog;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vao() GLuint {
|
fn vao() [1]gl.uint {
|
||||||
var vao_id: GLuint = undefined;
|
var vao_id: [1]gl.uint = undefined;
|
||||||
gl.genVertexArrays(1, &vao_id);
|
gl.GenVertexArrays(1, vao_id[0..]);
|
||||||
|
|
||||||
return vao_id;
|
return vao_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn screenTex(buf: []const u8) GLuint {
|
fn screenTex(buf: []const u8) [1]gl.uint {
|
||||||
var tex_id: GLuint = undefined;
|
var tex_id: [1]gl.uint = undefined;
|
||||||
gl.genTextures(1, &tex_id);
|
gl.GenTextures(1, tex_id[0..]);
|
||||||
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, tex_id);
|
gl.BindTexture(gl.TEXTURE_2D, tex_id[0]);
|
||||||
defer gl.bindTexture(gl.TEXTURE_2D, 0);
|
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_MIN_FILTER, gl.NEAREST);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_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);
|
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;
|
return tex_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn outTex() GLuint {
|
fn outTex() [1]gl.uint {
|
||||||
var tex_id: GLuint = undefined;
|
var tex_id: [1]gl.uint = undefined;
|
||||||
gl.genTextures(1, &tex_id);
|
gl.GenTextures(1, tex_id[0..]);
|
||||||
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, tex_id);
|
gl.BindTexture(gl.TEXTURE_2D, tex_id[0]);
|
||||||
defer gl.bindTexture(gl.TEXTURE_2D, 0);
|
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_MIN_FILTER, gl.NEAREST);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_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);
|
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;
|
return tex_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn frameBuffer(tex_id: GLuint) !GLuint {
|
fn frameBuffer(tex_id: gl.uint) ![1]gl.uint {
|
||||||
var fbo_id: GLuint = undefined;
|
var fbo_id: [1]gl.uint = undefined;
|
||||||
gl.genFramebuffers(1, &fbo_id);
|
gl.GenFramebuffers(1, fbo_id[0..]);
|
||||||
|
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo_id);
|
gl.BindFramebuffer(gl.FRAMEBUFFER, fbo_id[0]);
|
||||||
defer gl.bindFramebuffer(gl.FRAMEBUFFER, 0);
|
defer gl.BindFramebuffer(gl.FRAMEBUFFER, 0);
|
||||||
|
|
||||||
gl.framebufferTexture(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, tex_id, 0);
|
gl.FramebufferTexture(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, tex_id, 0);
|
||||||
gl.drawBuffers(1, &@as(GLuint, gl.COLOR_ATTACHMENT0));
|
gl.DrawBuffers(1, &.{gl.COLOR_ATTACHMENT0});
|
||||||
|
|
||||||
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE)
|
if (gl.CheckFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE)
|
||||||
return error.FrameBufferObejctInitFailed;
|
return error.FrameBufferObejctInitFailed;
|
||||||
|
|
||||||
return fbo_id;
|
return fbo_id;
|
||||||
@@ -396,20 +388,20 @@ const opengl_impl = struct {
|
|||||||
const shader = struct {
|
const shader = struct {
|
||||||
const log = std.log.scoped(.shader);
|
const log = std.log.scoped(.shader);
|
||||||
|
|
||||||
fn didCompile(id: gl.GLuint) bool {
|
fn didCompile(id: gl.uint) bool {
|
||||||
var success: gl.GLint = undefined;
|
var success: [1]gl.int = undefined;
|
||||||
gl.getShaderiv(id, gl.COMPILE_STATUS, &success);
|
gl.GetShaderiv(id, gl.COMPILE_STATUS, success[0..]);
|
||||||
|
|
||||||
if (success == 0) err(id);
|
if (success[0] == 0) err(id);
|
||||||
|
|
||||||
return success == 1;
|
return success[0] == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn err(id: gl.GLuint) void {
|
fn err(id: gl.uint) void {
|
||||||
const buf_len = 512;
|
const buf_len = 512;
|
||||||
var error_msg: [buf_len]u8 = undefined;
|
var error_msg: [buf_len]u8 = undefined;
|
||||||
|
|
||||||
gl.getShaderInfoLog(id, buf_len, 0, &error_msg);
|
gl.GetShaderInfoLog(id, buf_len, null, &error_msg);
|
||||||
log.err("{s}", .{std.mem.sliceTo(&error_msg, 0)});
|
log.err("{s}", .{std.mem.sliceTo(&error_msg, 0)});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
12
src/util.zig
12
src/util.zig
@@ -69,7 +69,7 @@ pub const io = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn undef(comptime T: type, comptime log: anytype, comptime format: []const u8, args: anytype) ?T {
|
pub fn undef(comptime T: type, comptime log: anytype, comptime format: []const u8, args: anytype) ?T {
|
||||||
@setCold(true);
|
@branchHint(.cold);
|
||||||
|
|
||||||
const unhandled_io = config.config().debug.unhandled_io;
|
const unhandled_io = config.config().debug.unhandled_io;
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ pub const io = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn err(comptime T: type, comptime log: anytype, comptime format: []const u8, args: anytype) ?T {
|
pub fn err(comptime T: type, comptime log: anytype, comptime format: []const u8, args: anytype) ?T {
|
||||||
@setCold(true);
|
@branchHint(.cold);
|
||||||
|
|
||||||
log.err(format, args);
|
log.err(format, args);
|
||||||
return null;
|
return null;
|
||||||
@@ -245,10 +245,10 @@ pub inline fn setHalf(comptime T: type, left: T, addr: u8, right: HalfInt(T)) T
|
|||||||
/// The Integer type which corresponds to T with exactly half the amount of bits
|
/// The Integer type which corresponds to T with exactly half the amount of bits
|
||||||
fn HalfInt(comptime T: type) type {
|
fn HalfInt(comptime T: type) type {
|
||||||
const type_info = @typeInfo(T);
|
const type_info = @typeInfo(T);
|
||||||
comptime std.debug.assert(type_info == .Int); // Type must be an integer
|
comptime std.debug.assert(type_info == .int); // Type must be an integer
|
||||||
comptime std.debug.assert(type_info.Int.bits % 2 == 0); // Type must have an even amount of bits
|
comptime std.debug.assert(type_info.int.bits % 2 == 0); // Type must have an even amount of bits
|
||||||
|
|
||||||
return std.meta.Int(type_info.Int.signedness, type_info.Int.bits >> 1);
|
return std.meta.Int(type_info.int.signedness, type_info.int.bits >> 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Double Buffering Implementation
|
/// Double Buffering Implementation
|
||||||
@@ -296,7 +296,7 @@ pub const FrameBuffer = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const RingBuffer = @import("zba-util").RingBuffer;
|
const RingBuffer = @import("zba_util").RingBuffer;
|
||||||
|
|
||||||
// TODO: Lock Free Queue?
|
// TODO: Lock Free Queue?
|
||||||
pub fn Queue(comptime T: type) type {
|
pub fn Queue(comptime T: type) type {
|
||||||
|
|||||||
Reference in New Issue
Block a user