179 lines
6.4 KiB
Zig
179 lines
6.4 KiB
Zig
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,
|
|
|
|
rgba_buffer: [256 * 240]u32 = [_]u32{0xFF000000} ** (256 * 240),
|
|
|
|
audio_ring: [16384]i16 = [_]i16{0} ** 16384,
|
|
audio_ring_head: usize = 0,
|
|
audio_ring_tail: usize = 0,
|
|
|
|
event_queue: [64]platform.Event = undefined,
|
|
event_head: usize = 0,
|
|
event_tail: usize = 0,
|
|
polled_this_frame: bool = false,
|
|
|
|
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;
|
|
}
|
|
|
|
if (self.polled_this_frame) {
|
|
return null;
|
|
}
|
|
self.polled_this_frame = true;
|
|
self.event_head = 0;
|
|
self.event_tail = 0;
|
|
|
|
// 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_C, .key = .c },
|
|
.{ .c_key = c.KEY_V, .key = .v },
|
|
.{ .c_key = c.KEY_J, .key = .j },
|
|
.{ .c_key = c.KEY_K, .key = .k },
|
|
.{ .c_key = c.KEY_SPACE, .key = .space },
|
|
.{ .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_W, .key = .w },
|
|
.{ .c_key = c.KEY_S, .key = .s },
|
|
.{ .c_key = c.KEY_A, .key = .a },
|
|
.{ .c_key = c.KEY_D, .key = .d },
|
|
.{ .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)) {
|
|
const next = (self.event_head + 1) % self.event_queue.len;
|
|
if (next != self.event_tail) {
|
|
self.event_queue[self.event_head] = .{ .key_down = .{ .key = mapping.key } };
|
|
self.event_head = next;
|
|
}
|
|
}
|
|
if (c.IsKeyReleased(mapping.c_key)) {
|
|
const next = (self.event_head + 1) % self.event_queue.len;
|
|
if (next != self.event_tail) {
|
|
self.event_queue[self.event_head] = .{ .key_up = .{ .key = mapping.key } };
|
|
self.event_head = next;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Support mouse left click as Start/Action button
|
|
if (c.IsMouseButtonPressed(c.MOUSE_BUTTON_LEFT)) {
|
|
const next = (self.event_head + 1) % self.event_queue.len;
|
|
if (next != self.event_tail) {
|
|
self.event_queue[self.event_head] = .{ .key_down = .{ .key = .enter } };
|
|
self.event_head = next;
|
|
}
|
|
}
|
|
if (c.IsMouseButtonReleased(c.MOUSE_BUTTON_LEFT)) {
|
|
const next = (self.event_head + 1) % self.event_queue.len;
|
|
if (next != self.event_tail) {
|
|
self.event_queue[self.event_head] = .{ .key_up = .{ .key = .enter } };
|
|
self.event_head = next;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
pub fn renderVideo(self: *Driver, output: usize, frame: contract.VideoFrame) void {
|
|
_ = output;
|
|
if (frame.data.len >= 256 * 240 * 2) {
|
|
const raw_pixels = std.mem.bytesAsSlice(u16, frame.data);
|
|
for (raw_pixels[0..@min(raw_pixels.len, self.rgba_buffer.len)], 0..) |pixel, i| {
|
|
const r = @as(u32, (pixel >> 11) & 0x1F) * 255 / 31;
|
|
const g = @as(u32, (pixel >> 5) & 0x3F) * 255 / 63;
|
|
const b = @as(u32, pixel & 0x1F) * 255 / 31;
|
|
self.rgba_buffer[i] = r | (g << 8) | (b << 16) | (0xFF << 24);
|
|
}
|
|
c.UpdateTexture(self.texture, &self.rgba_buffer);
|
|
}
|
|
|
|
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);
|
|
|
|
self.polled_this_frame = false;
|
|
}
|
|
|
|
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 {}
|
|
};
|