add raylib dependency and desktop platform driver with interactive play mode
This commit is contained in:
@@ -38,7 +38,6 @@ pub fn build(b: *std.Build) void {
|
|||||||
.root_source_file = b.path("src/platform/root.zig"),
|
.root_source_file = b.path("src/platform/root.zig"),
|
||||||
});
|
});
|
||||||
platform_mod.addImport("contract", contract_mod);
|
platform_mod.addImport("contract", contract_mod);
|
||||||
|
|
||||||
const frontend_mod = b.createModule(.{
|
const frontend_mod = b.createModule(.{
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
@@ -63,6 +62,23 @@ pub fn build(b: *std.Build) void {
|
|||||||
.root_module = exe_mod,
|
.root_module = exe_mod,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (b.lazyDependency("raylib", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
})) |raylib_dep| {
|
||||||
|
const raylib_lib = raylib_dep.artifact("raylib");
|
||||||
|
exe_mod.linkLibrary(raylib_lib);
|
||||||
|
|
||||||
|
const raylib_c = b.addTranslateC(.{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.root_source_file = raylib_dep.path("src/raylib.h"),
|
||||||
|
});
|
||||||
|
const craylib_mod = raylib_c.createModule();
|
||||||
|
platform_mod.addImport("c_raylib", craylib_mod);
|
||||||
|
exe_mod.addImport("c_raylib", craylib_mod);
|
||||||
|
}
|
||||||
|
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
|
|
||||||
// Test Step Configuration
|
// Test Step Configuration
|
||||||
|
|||||||
@@ -13,6 +13,10 @@
|
|||||||
.hash = "N-V-__8AAKmLHACkwIMCMRdqnsyLtL5hbQdi8cm3nGs8n-O7",
|
.hash = "N-V-__8AAKmLHACkwIMCMRdqnsyLtL5hbQdi8cm3nGs8n-O7",
|
||||||
.lazy = true,
|
.lazy = true,
|
||||||
},
|
},
|
||||||
|
.raylib = .{
|
||||||
|
.url = "https://github.com/raysan5/raylib/archive/refs/heads/master.tar.gz",
|
||||||
|
.hash = "raylib-6.0.0-whq8uIPTKwXybRNNu7BJNM6bxip5fYUgAh-bctTa7oHR",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
.paths = .{""},
|
.paths = .{""},
|
||||||
|
|||||||
+22
-3
@@ -29,13 +29,16 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!std.mem.eql(u8, command, "run")) {
|
const is_play = std.mem.eql(u8, command, "play");
|
||||||
|
const is_run = std.mem.eql(u8, command, "run");
|
||||||
|
|
||||||
|
if (!is_play and !is_run) {
|
||||||
std.debug.print("Unknown command '{s}'. Use '6soz --help' for usage.\n", .{command});
|
std.debug.print("Unknown command '{s}'. Use '6soz --help' for usage.\n", .{command});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args_count < 3) {
|
if (args_count < 3) {
|
||||||
std.debug.print("Error: missing ROM file path.\nUsage: 6soz run <path/to/rom.nes>\n", .{});
|
std.debug.print("Error: missing ROM file path.\nUsage: 6soz play <path/to/rom.nes> or 6soz run <path/to/rom.nes>\n", .{});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +105,7 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const frontend = @import("frontend");
|
const frontend = @import("frontend");
|
||||||
|
const platform = @import("platform");
|
||||||
|
|
||||||
const nes_bindings = frontend.Bindings{
|
const nes_bindings = frontend.Bindings{
|
||||||
.keys = &.{
|
.keys = &.{
|
||||||
@@ -121,6 +125,19 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
|
|
||||||
var quick_save_buffer: [64 * 1024]u8 = undefined;
|
var quick_save_buffer: [64 * 1024]u8 = undefined;
|
||||||
var app = frontend.App(Nes).init(&nes, nes_bindings, .{ .quick_state_buffer = &quick_save_buffer });
|
var app = frontend.App(Nes).init(&nes, nes_bindings, .{ .quick_state_buffer = &quick_save_buffer });
|
||||||
|
|
||||||
|
if (is_play) {
|
||||||
|
std.debug.print("[6soz] Launching Raylib Desktop Window...\n", .{});
|
||||||
|
var driver = platform.raylib.Driver.init("6soz NES Emulator Engine", 256, 240, 3);
|
||||||
|
defer driver.deinit();
|
||||||
|
|
||||||
|
while (app.state != .stopped and !driver.shouldClose()) {
|
||||||
|
while (driver.pollEvent()) |event| {
|
||||||
|
app.handleEvent(event);
|
||||||
|
}
|
||||||
|
app.tickFrame(&driver);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
var driver = frontend.HeadlessDriver.init();
|
var driver = frontend.HeadlessDriver.init();
|
||||||
|
|
||||||
std.debug.print("[6soz] Running NES emulator for {d} frames...\n", .{target_frames});
|
std.debug.print("[6soz] Running NES emulator for {d} frames...\n", .{target_frames});
|
||||||
@@ -140,6 +157,7 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
std.debug.print(" - Frames: {d}\n", .{target_frames});
|
std.debug.print(" - Frames: {d}\n", .{target_frames});
|
||||||
std.debug.print(" - Elapsed Time: {d:.3}s\n", .{elapsed_sec});
|
std.debug.print(" - Elapsed Time: {d:.3}s\n", .{elapsed_sec});
|
||||||
std.debug.print(" - Performance: {d:.1} FPS\n", .{fps});
|
std.debug.print(" - Performance: {d:.1} FPS\n", .{fps});
|
||||||
|
}
|
||||||
|
|
||||||
if (save_state_path) |path| {
|
if (save_state_path) |path| {
|
||||||
var state_buf: [64 * 1024]u8 = undefined;
|
var state_buf: [64 * 1024]u8 = undefined;
|
||||||
@@ -166,7 +184,8 @@ fn printUsage() void {
|
|||||||
std.debug.print(
|
std.debug.print(
|
||||||
\\6soz - 6502 / 6510 & NES Emulator Engine
|
\\6soz - 6502 / 6510 & NES Emulator Engine
|
||||||
\\Usage:
|
\\Usage:
|
||||||
\\ 6soz run <path/to/rom.nes> [options]
|
\\ 6soz play <path/to/rom.nes> Launch interactive Raylib desktop window
|
||||||
|
\\ 6soz run <path/to/rom.nes> [options] Run headless emulator benchmark
|
||||||
\\
|
\\
|
||||||
\\Options:
|
\\Options:
|
||||||
\\ --frames <N> Number of frames to execute (default: 60)
|
\\ --frames <N> Number of frames to execute (default: 60)
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const contract = @import("contract");
|
||||||
|
const platform = @import("root.zig");
|
||||||
|
const c = @import("c_raylib");
|
||||||
|
|
||||||
|
pub const Driver = struct {
|
||||||
|
texture: c.Texture2D,
|
||||||
|
audio_stream: c.AudioStream,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
scale: i32,
|
||||||
|
|
||||||
|
event_queue: [16]platform.Event = undefined,
|
||||||
|
event_head: usize = 0,
|
||||||
|
event_tail: usize = 0,
|
||||||
|
|
||||||
|
pub fn init(title: [*:0]const u8, width: i32, height: i32, scale: i32) Driver {
|
||||||
|
c.InitWindow(width * scale, height * scale, title);
|
||||||
|
c.SetTargetFPS(60);
|
||||||
|
|
||||||
|
c.InitAudioDevice();
|
||||||
|
c.SetAudioStreamBufferSizeDefault(1024);
|
||||||
|
|
||||||
|
const stream = c.LoadAudioStream(44100, 16, 1);
|
||||||
|
c.PlayAudioStream(stream);
|
||||||
|
|
||||||
|
const img = c.GenImageColor(width, height, c.BLACK);
|
||||||
|
defer c.UnloadImage(img);
|
||||||
|
|
||||||
|
const tex = c.LoadTextureFromImage(img);
|
||||||
|
c.SetTextureFilter(tex, c.TEXTURE_FILTER_POINT);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.texture = tex,
|
||||||
|
.audio_stream = stream,
|
||||||
|
.width = width,
|
||||||
|
.height = height,
|
||||||
|
.scale = scale,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Driver) void {
|
||||||
|
c.UnloadAudioStream(self.audio_stream);
|
||||||
|
c.CloseAudioDevice();
|
||||||
|
c.UnloadTexture(self.texture);
|
||||||
|
c.CloseWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shouldClose(_: *Driver) bool {
|
||||||
|
return c.WindowShouldClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pollEvent(self: *Driver) ?platform.Event {
|
||||||
|
if (self.event_head != self.event_tail) {
|
||||||
|
const ev = self.event_queue[self.event_tail];
|
||||||
|
self.event_tail = (self.event_tail + 1) % self.event_queue.len;
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll Raylib Key Events
|
||||||
|
const key_mappings = [_]struct { c_key: c_int, key: platform.Key }{
|
||||||
|
.{ .c_key = c.KEY_Z, .key = .z },
|
||||||
|
.{ .c_key = c.KEY_X, .key = .x },
|
||||||
|
.{ .c_key = c.KEY_RIGHT_SHIFT, .key = .right_shift },
|
||||||
|
.{ .c_key = c.KEY_LEFT_SHIFT, .key = .left_shift },
|
||||||
|
.{ .c_key = c.KEY_ENTER, .key = .enter },
|
||||||
|
.{ .c_key = c.KEY_UP, .key = .up },
|
||||||
|
.{ .c_key = c.KEY_DOWN, .key = .down },
|
||||||
|
.{ .c_key = c.KEY_LEFT, .key = .left },
|
||||||
|
.{ .c_key = c.KEY_RIGHT, .key = .right },
|
||||||
|
.{ .c_key = c.KEY_F5, .key = .f5 },
|
||||||
|
.{ .c_key = c.KEY_F8, .key = .f8 },
|
||||||
|
.{ .c_key = c.KEY_P, .key = .p },
|
||||||
|
};
|
||||||
|
|
||||||
|
for (key_mappings) |mapping| {
|
||||||
|
if (c.IsKeyPressed(mapping.c_key)) {
|
||||||
|
return .{ .key_down = .{ .key = mapping.key } };
|
||||||
|
}
|
||||||
|
if (c.IsKeyReleased(mapping.c_key)) {
|
||||||
|
return .{ .key_up = .{ .key = mapping.key } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn renderVideo(self: *Driver, output: usize, frame: contract.VideoFrame) void {
|
||||||
|
_ = output;
|
||||||
|
if (frame.data.len > 0) {
|
||||||
|
c.UpdateTexture(self.texture, frame.data.ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
c.BeginDrawing();
|
||||||
|
defer c.EndDrawing();
|
||||||
|
|
||||||
|
c.ClearBackground(c.BLACK);
|
||||||
|
const src = c.Rectangle{ .x = 0, .y = 0, .width = @floatFromInt(self.width), .height = @floatFromInt(self.height) };
|
||||||
|
const dest = c.Rectangle{
|
||||||
|
.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
.width = @floatFromInt(self.width * self.scale),
|
||||||
|
.height = @floatFromInt(self.height * self.scale),
|
||||||
|
};
|
||||||
|
c.DrawTexturePro(self.texture, src, dest, c.Vector2{ .x = 0, .y = 0 }, 0.0, c.WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn queueAudio(self: *Driver, output: usize, buffer: contract.AudioBuffer) void {
|
||||||
|
_ = output;
|
||||||
|
if (buffer.frames > 0 and c.IsAudioStreamProcessed(self.audio_stream)) {
|
||||||
|
c.UpdateAudioStream(self.audio_stream, buffer.data.ptr, @intCast(buffer.frames));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setDeviceFeedback(_: *Driver, _: usize, _: contract.DeviceFeedback) void {}
|
||||||
|
};
|
||||||
@@ -130,6 +130,8 @@ pub const Headless = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const raylib = @import("raylib.zig");
|
||||||
|
|
||||||
test "platform: headless has no events" {
|
test "platform: headless has no events" {
|
||||||
var platform_driver = Headless.init();
|
var platform_driver = Headless.init();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user