We doing global variables now

This commit is contained in:
duck
2025-07-07 21:11:55 +05:00
parent b2be9e3708
commit 7e162221ef
14 changed files with 521 additions and 1445 deletions

View File

@@ -1,284 +0,0 @@
const std = @import("std");
const sdl = @import("sdl");
const math = @import("math.zig");
const Time = @import("time.zig");
const Transform = @import("graphics/transform.zig");
const Controller = @import("graph/controller.zig");
const Graphics = @import("graphics.zig");
const Game = @import("game.zig");
const CUBE_MESH_DATA = [_]f32{
-0.5, 0.5, -0.5, 0.0, 0.0,
0.5, 0.5, -0.5, 0.0, 0.0,
-0.5, -0.5, -0.5, 0.0, 0.0,
0.5, -0.5, -0.5, 0.0, 0.0,
-0.5, -0.5, -0.5, 0.0, 0.0,
0.5, 0.5, -0.5, 0.0, 0.0,
0.5, 0.5, -0.5, 0.0, 0.0,
0.5, 0.5, 0.5, 0.0, 0.0,
0.5, -0.5, -0.5, 0.0, 0.0,
0.5, -0.5, 0.5, 0.0, 0.0,
0.5, -0.5, -0.5, 0.0, 0.0,
0.5, 0.5, 0.5, 0.0, 0.0,
0.5, 0.5, 0.5, 0.0, 0.0,
-0.5, 0.5, 0.5, 0.0, 0.0,
0.5, -0.5, 0.5, 0.0, 0.0,
-0.5, -0.5, 0.5, 0.0, 0.0,
0.5, -0.5, 0.5, 0.0, 0.0,
-0.5, 0.5, 0.5, 0.0, 0.0,
-0.5, 0.5, 0.5, 0.0, 0.0,
-0.5, 0.5, -0.5, 0.0, 0.0,
-0.5, -0.5, 0.5, 0.0, 0.0,
-0.5, -0.5, -0.5, 0.0, 0.0,
-0.5, -0.5, 0.5, 0.0, 0.0,
-0.5, 0.5, -0.5, 0.0, 0.0,
-0.5, 0.5, 0.5, 0.0, 0.0,
0.5, 0.5, 0.5, 0.0, 0.0,
-0.5, 0.5, -0.5, 0.0, 0.0,
0.5, 0.5, -0.5, 0.0, 0.0,
-0.5, 0.5, -0.5, 0.0, 0.0,
0.5, 0.5, 0.5, 0.0, 0.0,
-0.5, -0.5, -0.5, 0.0, 0.0,
0.5, -0.5, -0.5, 0.0, 0.0,
-0.5, -0.5, 0.5, 0.0, 0.0,
0.5, -0.5, 0.5, 0.0, 0.0,
-0.5, -0.5, 0.5, 0.0, 0.0,
0.5, -0.5, -0.5, 0.0, 0.0,
};
const PLANE_MESH_DATA = [_]f32{
-0.5, -0.5, 0, 0.0, 1.0,
0.5, 0.5, 0, 1.0, 0.0,
-0.5, 0.5, 0, 0.0, 0.0,
0.5, 0.5, 0, 1.0, 0.0,
-0.5, -0.5, 0, 0.0, 1.0,
0.5, -0.5, 0, 1.0, 1.0,
};
// const TEXTURE_DATA = [_]u8{
// 255, 0, 0, 255,
// 0, 255, 0, 255,
// 0, 0, 255, 255,
// 0, 0, 0, 255,
// };
const TEXTURE_DATA = [_]u8{
255, 0, 0, 255,
0, 255, 0, 255,
0, 0, 255, 255,
0, 0, 0, 255,
};
pub const WorldTime = struct {
time: Time,
last_time: Time,
view_unresolved: f32,
view_timescale: f32,
};
pub const PlayerState = union(enum) {
idle,
moving: struct {
from_position: @Vector(3, f32),
from_time: Time,
to_time: Time,
},
};
pub const Player = struct {
mesh: Graphics.Mesh,
texture: Graphics.Texture,
transform: Transform,
position: @Vector(2, i32),
state: PlayerState,
move_units: f32,
idle_timescale: f32,
moving_timescale: f32,
};
pub const Environment = struct {
mesh: Graphics.Mesh,
texture: Graphics.Texture,
transform: Transform,
};
pub fn init(controller: *Controller, graphics: *Graphics) !void {
controller.addResource(Player{
.mesh = try graphics.loadMesh(@ptrCast(&CUBE_MESH_DATA)),
.texture = try graphics.loadTexture(2, 2, @ptrCast(&TEXTURE_DATA)),
.transform = .{},
.position = .{ 0, 0 },
.state = .idle,
.move_units = 0.125,
.moving_timescale = 1.0,
.idle_timescale = 0.0625,
});
controller.addResource(Environment{
.mesh = try graphics.loadMesh(@ptrCast(&PLANE_MESH_DATA)),
.texture = try graphics.loadTexture(2, 2, @ptrCast(&TEXTURE_DATA)),
.transform = .{
.position = .{ 0, 0, -1 },
.scale = @splat(5),
},
});
controller.addResource(WorldTime{
.time = Time.ZERO,
.last_time = Time.ZERO,
.view_unresolved = 0.0,
.view_timescale = 0.0625,
});
}
pub fn deinit() void {}
pub fn updateReal(
real_time: *Game.Time,
world_time: *WorldTime,
controller: *Controller,
) void {
world_time.view_unresolved += real_time.delta;
controller.queue(.{
updateWorld,
updatePlayerTransform,
updateCamera,
Controller.Option.ordered,
});
}
pub fn updateWorld(
world_time: *WorldTime,
controller: *Controller,
player: *Player,
) void {
if (world_time.view_unresolved <= 0) return;
var real_delta = world_time.view_unresolved;
var world_delta = Time.durationFromUnits(real_delta * world_time.view_timescale);
switch (player.state) {
.moving => |move| {
if (move.to_time.clock > world_time.time.clock) {
world_delta = @min(world_delta, move.to_time.clock - world_time.time.clock);
real_delta = Time.unitsFromDuration(world_delta) / world_time.view_timescale;
}
},
.idle => {},
}
if (world_delta == 0) return;
world_time.last_time = world_time.time;
world_time.time.clock += world_delta;
world_time.view_unresolved -= real_delta;
controller.queue(.{
updatePlayer,
updateWorld,
Controller.Option.ordered,
});
}
pub fn updatePlayer(
player: *Player,
keyboard: *Game.Keyboard,
world_time: *WorldTime,
) void {
switch (player.state) {
.idle => {},
.moving => |move| {
if (world_time.time.past(move.to_time)) {
player.state = .idle;
world_time.view_timescale = player.idle_timescale;
} else return;
},
}
var delta: @Vector(2, i32) = .{ 0, 0 };
if (keyboard.keys.is_pressed(sdl.SCANCODE_W)) {
delta[1] += 1;
}
if (keyboard.keys.is_pressed(sdl.SCANCODE_S)) {
delta[1] -= 1;
}
if (keyboard.keys.is_pressed(sdl.SCANCODE_D)) {
delta[0] += 1;
}
if (keyboard.keys.is_pressed(sdl.SCANCODE_A)) {
delta[0] -= 1;
}
if (delta[0] != 0 or delta[1] != 0) {
updatePlayerTransform(player, world_time);
player.state = .{ .moving = .{
.from_position = player.transform.position,
.from_time = world_time.time,
.to_time = world_time.time.offset(player.move_units * math.lengthInt(delta)),
} };
player.position[0] += delta[0];
player.position[1] += delta[1];
world_time.view_timescale = player.moving_timescale;
}
}
pub fn updateCamera(
graphics: *Graphics,
player: *Player,
real_time: *Game.Time,
) void {
graphics.camera.transform.position = math.lerpTimeLn(
graphics.camera.transform.position,
player.transform.position + @Vector(3, f32){ 0.0, -2.0, 5.0 },
real_time.delta,
-25,
);
const ORIGIN_DIR = @Vector(3, f32){ 0.0, 0.0, -1.0 };
const INIT_ROTATION = Transform.rotationByAxis(.{ 1.0, 0.0, 0.0 }, std.math.pi * 0.5);
const ROTATED_DIR = Transform.rotateVector(ORIGIN_DIR, INIT_ROTATION);
const target_rotation = Transform.combineRotations(
INIT_ROTATION,
Transform.rotationToward(
ROTATED_DIR,
player.transform.position - graphics.camera.transform.position,
.{ .normalize_to = true },
),
);
graphics.camera.transform.rotation = Transform.normalizeRotation(math.slerpTimeLn(
graphics.camera.transform.rotation,
target_rotation,
real_time.delta,
-2,
));
}
pub fn updatePlayerTransform(player: *Player, world_time: *WorldTime) void {
switch (player.state) {
.idle => player.transform.position = .{ @floatFromInt(player.position[0]), @floatFromInt(player.position[1]), 0.0 },
.moving => |move| {
const to_position = @Vector(3, f32){ @floatFromInt(player.position[0]), @floatFromInt(player.position[1]), 0.0 };
player.transform.position = math.lerp(
move.from_position,
to_position,
world_time.time.progress(move.from_time, move.to_time),
);
},
}
}
pub fn draw(
player: *Player,
env: *Environment,
graphics: *Graphics,
world_time: *WorldTime,
) !void {
env.transform.rotation = Transform.combineRotations(
env.transform.rotation,
Transform.rotationByAxis(.{ 0, 0, 1 }, world_time.time.unitsSince(world_time.last_time) * std.math.pi),
);
try graphics.drawMesh(env.mesh, env.texture, env.transform);
try graphics.drawMesh(env.mesh, env.texture, Transform{
.position = .{ 0, 0, -0.5 },
.scale = @splat(5),
});
try graphics.drawMesh(player.mesh, player.texture, player.transform);
}

118
src/entity.zig Normal file
View File

@@ -0,0 +1,118 @@
const std = @import("std");
const sdl = @import("sdl");
const Game = @import("game.zig");
const Graphics = @import("graphics.zig");
const Time = @import("time.zig");
const World = @import("world.zig");
const math = @import("math.zig");
position: @Vector(2, i32),
player: bool = false,
enemy: bool = false,
controller: Controller = .{},
next_update: Time = Time.ZERO,
const Controller = struct {
const Action = union(enum) {
move: @Vector(2, i32),
};
wanted_action: ?Action = null,
move_units: f32 = 0.125,
};
const Self = @This();
pub fn update(self: *Self) void {
if (!World.time.past(self.next_update)) return;
if (self.player) self.updatePlayer();
if (self.enemy) self.updateEnemy();
self.updateController();
}
pub fn updatePlayer(self: *Self) void {
var delta: @Vector(2, i32) = .{ 0, 0 };
if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_UP)) {
delta[1] += 1;
}
if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_DOWN)) {
delta[1] -= 1;
}
if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_RIGHT)) {
delta[0] += 1;
}
if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_LEFT)) {
delta[0] -= 1;
}
if (@reduce(.Or, delta != @Vector(2, i32){ 0, 0 }))
self.controller.wanted_action = .{ .move = delta }
else
self.controller.wanted_action = null;
}
fn updateEnemy(self: *Self) void {
if (World.getPlayer()) |player| {
var delta = player.position - self.position;
if (@reduce(.And, @abs(delta) <= @Vector(2, i64){ 1, 1 })) {
self.controller.wanted_action = null;
} else {
delta[0] = @max(-1, @min(1, delta[0]));
delta[1] = @max(-1, @min(1, delta[1]));
self.controller.wanted_action = .{ .move = delta };
}
}
}
fn updateController(self: *Self) void {
if (self.controller.wanted_action) |action| {
switch (action) {
.move => |delta| {
const target = self.position + delta;
if (World.isFree(target)) {
self.next_update = World.time.offset(self.controller.move_units * math.lengthInt(delta));
self.position[0] += delta[0];
self.position[1] += delta[1];
}
},
}
}
}
pub fn draw(self: *Self, delta: f32) void {
const transform = Graphics.Transform{
.position = .{
@floatFromInt(self.position[0]),
@floatFromInt(self.position[1]),
0.5,
},
};
Graphics.drawMesh(World.cube_mesh, World.texture, transform);
if (!self.player) return;
Graphics.camera.transform.position = math.lerpTimeLn(
Graphics.camera.transform.position,
transform.position + @Vector(3, f32){ 0.0, -2.0, 5.0 },
delta,
-25,
);
const ORIGIN_DIR = @Vector(3, f32){ 0.0, 0.0, -1.0 };
const INIT_ROTATION = Graphics.Transform.rotationByAxis(.{ 1.0, 0.0, 0.0 }, std.math.pi * 0.5);
const ROTATED_DIR = Graphics.Transform.rotateVector(ORIGIN_DIR, INIT_ROTATION);
const target_rotation = Graphics.Transform.combineRotations(
INIT_ROTATION,
Graphics.Transform.rotationToward(
ROTATED_DIR,
transform.position - Graphics.camera.transform.position,
.{ .normalize_to = true },
),
);
Graphics.camera.transform.rotation = Graphics.Transform.normalizeRotation(math.slerpTimeLn(
Graphics.camera.transform.rotation,
target_rotation,
delta,
-2,
));
}

16
src/error.zig Normal file
View File

@@ -0,0 +1,16 @@
const std = @import("std");
const libsdl = @import("sdl");
pub fn sdl() noreturn {
std.debug.panic("SDL Error:\n{s}\n", .{libsdl.GetError()});
}
pub fn oom() noreturn {
std.debug.panic("Out of memory!\n", .{});
}
const FileError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError;
pub fn file(err: FileError, path: []const u8) noreturn {
std.debug.panic("Error while reading \"{s}\": {any}", .{ path, err });
}

View File

@@ -1,155 +1,106 @@
const std = @import("std");
const builtin = @import("builtin");
const sdl = @import("sdl");
const err = @import("error.zig");
const Mouse = @import("mouse.zig");
const Keyboard = @import("keyboard.zig");
const debug_scene = @import("debug_scene.zig");
const Graph = @import("graph.zig");
const Graphics = @import("graphics.zig");
pub const Graphics = @import("graphics.zig");
pub const World = @import("world.zig");
// TODO:
// - Do something about deallocating `Resource`s when `Graph` fails
pub const RunInfo = struct { running: bool };
pub const Mouse = @import("mouse.zig");
pub const Keyboard = @import("keyboard.zig");
pub const Time = struct {
const Time = struct {
delta: f32,
now: sdl.Time,
};
alloc: std.mem.Allocator,
graph: Graph,
var alloc: std.mem.Allocator = undefined;
const Self = @This();
pub fn init(alloc: std.mem.Allocator) GameError!Self {
var graph = try Graph.init(alloc);
errdefer graph.deinit();
var running: bool = false;
var time: Time = .{ .delta = 0, .now = 0 };
pub var keyboard: Keyboard = .{};
pub var mouse: Mouse = .{};
const graphics = try Graphics.create();
var controller = try graph.getController();
controller.addResource(graphics);
controller.addResource(Mouse{
.buttons = .{},
.x = 0.0,
.y = 0.0,
.dx = 0.0,
.dy = 0.0,
});
controller.addResource(Keyboard{});
controller.addResource(Time{
.delta = 0.0,
.now = 0,
});
controller.queue(debug_scene.init);
try graph.freeController(controller);
defer graph.reset();
try graph.runAllSystems();
return Self{
.alloc = alloc,
.graph = graph,
};
const Game = @This();
pub fn init(game_alloc: std.mem.Allocator) void {
Game.alloc = game_alloc;
Game.running = false;
Game.time = Time{ .now = 0, .delta = 0 };
Game.keyboard = .{};
Game.mouse = .{ .x = 0, .y = 0, .dx = 0, .dy = 0 };
Graphics.create();
World.initDebug();
}
pub fn run(self: *Self) GameError!void {
{
var controller = try self.graph.getController();
controller.addResource(RunInfo{ .running = true });
try self.graph.freeController(controller);
}
while (true) {
if (!self.graph.getResource(RunInfo).?.running) {
break;
}
pub fn run() void {
Game.running = true;
while (Game.running) {
var current_time: sdl.Time = undefined;
if (sdl.GetCurrentTime(&current_time)) {
const time = self.graph.getResource(Time).?;
if (time.now != 0) {
time.delta = @as(f32, @floatFromInt(current_time - time.now)) * 0.000000001;
if (Game.time.now != 0) {
Game.time.delta = @as(f32, @floatFromInt(current_time - Game.time.now)) * 0.000000001;
}
time.now = current_time;
} else return error.SdlError;
Game.time.now = current_time;
} else err.sdl();
var controller = try self.graph.getController();
controller.queue(.{
processEvents,
debug_scene.updateReal,
beginDraw,
endDraw,
Graph.Controller.Option.ordered,
});
try self.graph.freeController(controller);
defer self.graph.reset();
try self.graph.runAllSystems();
Game.processEvents();
World.updateReal(Game.time.delta);
if (Game.beginDraw()) {
World.draw(Game.time.delta);
Game.endDraw();
}
}
}
fn beginDraw(graphics: *Graphics, controller: *Graph.Controller) GameError!void {
if (try graphics.beginDraw()) {
controller.queue(debug_scene.draw);
}
fn beginDraw() bool {
return Graphics.beginDraw();
}
fn endDraw(graphics: *Graphics) GameError!void {
try graphics.endDraw();
fn endDraw() void {
Graphics.endDraw();
}
fn clean(graphics: *Graphics) !void {
graphics.destroy();
// TODO: Also remove the resource
}
fn processEvents(
graphics: *Graphics,
run_info: *RunInfo,
mouse: *Mouse,
keyboard: *Keyboard,
) GameError!void {
mouse.dx = 0.0;
mouse.dy = 0.0;
keyboard.keys.reset();
fn processEvents() void {
Game.mouse.dx = 0.0;
Game.mouse.dy = 0.0;
Game.keyboard.keys.reset();
sdl.PumpEvents();
while (true) {
var buffer: [16]sdl.Event = undefined;
const count: usize = @intCast(sdl.PeepEvents(&buffer, buffer.len, sdl.GETEVENT, sdl.EVENT_FIRST, sdl.EVENT_LAST));
if (count == -1) return GameError.SdlError;
if (count == -1) err.sdl();
for (buffer[0..count]) |event| {
switch (event.type) {
sdl.EVENT_QUIT => {
run_info.running = false;
Game.running = false;
},
sdl.EVENT_WINDOW_RESIZED => {
if (event.window.windowID != sdl.GetWindowID(graphics.window)) continue;
graphics.resize(@intCast(event.window.data1), @intCast(event.window.data2));
if (event.window.windowID != Graphics.windowId()) continue;
Graphics.resize(@intCast(event.window.data1), @intCast(event.window.data2));
},
sdl.EVENT_MOUSE_MOTION => {
if (event.motion.windowID != sdl.GetWindowID(graphics.window)) continue;
mouse.x = event.motion.x;
mouse.y = event.motion.y;
mouse.dx += event.motion.xrel;
mouse.dy += event.motion.yrel;
if (event.motion.windowID != Graphics.windowId()) continue;
Game.mouse.x = event.motion.x;
Game.mouse.y = event.motion.y;
Game.mouse.dx += event.motion.xrel;
Game.mouse.dy += event.motion.yrel;
},
sdl.EVENT_KEY_DOWN => {
if (event.key.windowID != sdl.GetWindowID(graphics.window)) continue;
keyboard.keys.press(event.key.scancode);
if (event.key.windowID != Graphics.windowId()) continue;
Game.keyboard.keys.press(event.key.scancode);
},
sdl.EVENT_KEY_UP => {
if (event.key.windowID != sdl.GetWindowID(graphics.window)) continue;
keyboard.keys.release(event.key.scancode);
if (event.key.windowID != Graphics.windowId()) continue;
Game.keyboard.keys.release(event.key.scancode);
},
sdl.EVENT_MOUSE_BUTTON_DOWN => {
if (event.button.windowID != sdl.GetWindowID(graphics.window)) continue;
mouse.buttons.press(event.button.button);
if (event.button.windowID != Graphics.windowId()) continue;
Game.mouse.buttons.press(event.button.button);
},
sdl.EVENT_MOUSE_BUTTON_UP => {
if (event.button.windowID != sdl.GetWindowID(graphics.window)) continue;
mouse.buttons.release(event.button.button);
if (event.button.windowID != Graphics.windowId()) continue;
Game.mouse.buttons.release(event.button.button);
},
else => {},
}
@@ -161,25 +112,8 @@ fn processEvents(
sdl.FlushEvents(sdl.EVENT_FIRST, sdl.EVENT_LAST);
}
pub fn deinit(self: *Self) void {
var controller = self.graph.getController() catch unreachable;
controller.queue(.{
debug_scene.deinit,
clean,
Graph.Controller.Option.ordered,
});
self.graph.freeController(controller) catch unreachable;
self.graph.runAllSystems() catch unreachable;
self.graph.deinit();
pub fn deinit() void {
World.deinit();
Graphics.destroy();
sdl.Quit();
}
pub const GameError = error{
SdlError,
OSError,
OutOfMemory,
MissingResource,
SystemDeadlock,
};

View File

@@ -1,377 +0,0 @@
const std = @import("std");
const utils = @import("graph/utils.zig");
const Resource = @import("graph/resource.zig");
const System = @import("graph/system.zig");
// TODO:
// - Use arena allocator?
// - Resolve missing resource problem
// - Organize a better way to execute single commands on graph
// - Handle system errors
// - Removing of resources
pub const Controller = @import("graph/controller.zig");
const MAX_SYSTEM_REQUESTS = 8;
const DEFAULT_SYSTEM_CAPACITY = 16;
const DEFAULT_CONTROLLERS = 2;
const DEFAULT_DUDS_PER_CONTROLLER = 32;
const ResourceMap = std.AutoArrayHashMapUnmanaged(utils.Hash, Resource);
const SystemQueue = std.ArrayListUnmanaged(System);
const Controllers = std.ArrayListUnmanaged(Controller);
const Duds = std.ArrayListUnmanaged(System.Dud);
/// Assumed to be thread-safe
alloc: std.mem.Allocator,
resources: ResourceMap,
system_queue: SystemQueue,
controllers: Controllers,
duds: Duds,
const Self = @This();
pub fn init(alloc: std.mem.Allocator) !Self {
var resources = try ResourceMap.init(alloc, &.{}, &.{});
errdefer resources.deinit(alloc);
var system_queue = try SystemQueue.initCapacity(alloc, DEFAULT_SYSTEM_CAPACITY);
errdefer system_queue.deinit(alloc);
var controllers = try Controllers.initCapacity(alloc, DEFAULT_CONTROLLERS);
errdefer controllers.deinit(alloc);
errdefer for (controllers.items) |*controller| {
controller.deinit();
};
var duds = try Duds.initCapacity(alloc, DEFAULT_CONTROLLERS * DEFAULT_DUDS_PER_CONTROLLER);
errdefer duds.deinit(alloc);
for (0..DEFAULT_CONTROLLERS * DEFAULT_DUDS_PER_CONTROLLER) |_| {
duds.appendAssumeCapacity(.{});
}
for (0..DEFAULT_CONTROLLERS) |i| {
var controller = try Controller.create(alloc);
controller.setDuds(@intCast(DEFAULT_DUDS_PER_CONTROLLER * i), @intCast(DEFAULT_DUDS_PER_CONTROLLER * (i + 1)));
controllers.appendAssumeCapacity(controller);
}
return .{
.alloc = alloc,
.resources = resources,
.system_queue = system_queue,
.controllers = controllers,
.duds = duds,
};
}
pub fn deinit(self: *Self) void {
var resource_iter = self.resources.iterator();
while (resource_iter.next()) |entry| {
entry.value_ptr.deinit(self.alloc);
}
self.resources.clearAndFree(self.alloc);
self.resources.deinit(self.alloc);
for (self.system_queue.items) |system| {
self.alloc.free(system.requested_types);
}
self.system_queue.deinit(self.alloc);
for (self.controllers.items) |*controller| {
controller.deinit();
}
self.controllers.deinit(self.alloc);
self.duds.deinit(self.alloc);
}
/// Clear all internal data in preparation for the next run cycle
/// Does not clear any `Resource`s
pub fn reset(self: *Self) void {
// Controller cleanup
for (self.controllers.items, 0..) |*controller, i| {
for (controller.commands()) |*command| {
switch (command.*) {
.add_resource => |*resource| resource.deinit(controller.alloc),
.queue_system => {},
}
}
controller.clear();
controller.setDuds(
i * self.duds.items.len / self.controllers.items.len,
(i + 1) * self.duds.items.len / self.controllers.items.len,
);
}
self.system_queue.clearRetainingCapacity();
// Duds cleanup
for (self.duds.items) |*dud| {
dud.required_count = 0;
}
}
fn enqueueSystem(self: *Self, system: System) !void {
try self.system_queue.append(self.alloc, system);
}
pub fn runAllSystems(self: *Self) GraphError!void {
while (self.system_queue.items.len > 0) {
var swap_with = self.system_queue.items.len - 1;
while (true) {
const system = &self.system_queue.items[self.system_queue.items.len - 1];
if (system.requires_dud) |dud_id| {
if (self.duds.items[dud_id].required_count == 0) {
break;
}
} else break;
if (swap_with > 0) {
swap_with -= 1;
std.mem.swap(
System,
&self.system_queue.items[self.system_queue.items.len - 1],
&self.system_queue.items[swap_with],
);
} else {
return GraphError.SystemDeadlock;
}
}
const next_system = self.system_queue.pop().?;
self.runSystem(next_system) catch |err| {
std.debug.print("System run error: {} while running {s}\n", .{ err, next_system.label });
return err;
};
}
}
/// Does not deallocate the system
fn runSystem(self: *Self, system: System) GraphError!void {
if (system.requires_dud) |dud_id| {
std.debug.assert(self.duds.items[dud_id].required_count == 0);
}
var buffer: [MAX_SYSTEM_REQUESTS]*anyopaque = undefined;
var controller: ?Controller = null;
errdefer if (controller) |*c| c.deinit();
var buffer_len: usize = 0;
for (system.requested_types) |request| {
switch (request) {
.resource => |resource| {
buffer[buffer_len] = self.getAnyopaqueResource(resource) orelse return GraphError.MissingResource;
},
.controller => {
controller = try self.getController();
controller.?.submit_dud = system.submit_dud;
buffer[buffer_len] = @ptrCast(&controller.?);
},
}
buffer_len += 1;
}
system.function_runner(buffer[0..buffer_len]);
if (system.submit_dud) |dud_id| {
self.duds.items[dud_id].required_count -= 1;
}
if (controller) |c| {
defer controller = null;
try self.freeController(c);
}
}
fn applyCommands(self: *Self, commands: []const Controller.Command) !void {
for (commands) |command| {
switch (command) {
.add_resource => |r| try self.addResource(r),
.queue_system => |s| {
if (s.submit_dud) |submit_id| self.duds.items[submit_id].required_count += 1;
try self.enqueueSystem(s);
},
}
}
}
pub fn getController(self: *Self) !Controller {
if (self.controllers.pop()) |c| {
return c;
}
const next_dud_id = self.duds.items.len;
for (try self.duds.addManyAsSlice(self.alloc, DEFAULT_DUDS_PER_CONTROLLER)) |*dud| {
dud.required_count = 0;
}
errdefer self.duds.shrinkRetainingCapacity(self.duds.items.len - DEFAULT_DUDS_PER_CONTROLLER);
var controller = try Controller.create(self.alloc);
controller.setDuds(@intCast(next_dud_id), @intCast(next_dud_id + DEFAULT_DUDS_PER_CONTROLLER));
return controller;
}
/// Evaluates and clears the controller (even if errors out)
pub fn freeController(self: *Self, controller: Controller) !void {
var c = controller;
try self.applyCommands(c.commands());
c.clear();
try self.controllers.append(self.alloc, c);
// TODO: Handle controller error state
}
pub inline fn getResource(self: *Self, comptime resource: type) ?*resource {
utils.validateResource(resource);
if (getAnyopaqueResource(self, utils.hashType(resource))) |ptr| {
return @alignCast(@ptrCast(ptr));
}
return null;
}
fn getAnyopaqueResource(self: *Self, resource_hash: utils.Hash) ?*anyopaque {
if (self.resources.get(resource_hash)) |resource| {
return resource.pointer;
}
return null;
}
/// Discards any previous resource data, resource is assumed to be allocated with `self.alloc`
pub inline fn addResource(self: *Self, resource: Resource) !void {
var previous = try self.resources.fetchPut(self.alloc, resource.hash, resource);
if (previous) |*p| {
p.value.deinit(self.alloc);
}
}
const GraphError = error{
MissingResource,
OutOfMemory,
SystemDeadlock,
};
test "simple graph smoke test" {
const Graph = @This();
const TestResource = struct {
number: u32,
fn addOne(rsc: *@This()) void {
rsc.number += 1;
}
fn addTen(rsc: *@This()) void {
rsc.number += 10;
}
fn addThousand(rsc: *@This()) void {
rsc.number += 1000;
}
fn subThousand(rsc: *@This()) void {
rsc.number -= 1000;
}
fn addEleven(cmd: *Controller) void {
cmd.queue(addTen);
cmd.queue(addOne);
cmd.queue(.{
.{
addThousand,
addThousand,
addThousand,
},
.{
subThousand,
subThousand,
subThousand,
},
Controller.Option.ordered,
});
}
};
var graph = try Graph.init(std.testing.allocator);
defer graph.deinit();
var controller = try graph.getController();
controller.addResource(TestResource{ .number = 100 });
controller.queue(TestResource.addOne);
controller.queue(TestResource.addOne);
controller.queue(TestResource.addTen);
controller.queue(TestResource.addEleven);
try graph.freeController(controller);
try graph.runAllSystems();
const result = graph.getResource(TestResource);
try std.testing.expectEqual(result.?.number, 123);
}
test "complex queue graph smoke test" {
const Graph = @This();
const TestResource = struct {
const Rsc = @This();
data1: isize,
data2: isize,
fn queueManySystems(cmd: *Controller) void {
cmd.queue(.{
.{
addTen,
addTen,
addTen,
addTen,
subTwenty,
},
// `data1` = 20
// `data2` = 5
.{
mulTen,
mulTen,
mulTwo,
mulTwo,
},
// `data1` = 8000
// `data2` = 9
.{
subTwenty,
},
// `data1` = 7980
// `data2` = 10
Controller.Option.ordered,
});
}
fn addTen(rsc: *Rsc) void {
rsc.data1 += 10;
rsc.data2 += 1;
}
fn subTwenty(rsc: *Rsc) void {
rsc.data1 -= 20;
rsc.data2 += 1;
}
fn mulTen(rsc: *Rsc) void {
rsc.data1 *= 10;
rsc.data2 += 1;
}
fn mulTwo(rsc: *Rsc) void {
rsc.data1 *= 2;
rsc.data2 += 1;
}
};
var graph = try Graph.init(std.testing.allocator);
defer graph.deinit();
var controller = try graph.getController();
controller.addResource(TestResource{ .data1 = 0, .data2 = 0 });
controller.queue(TestResource.queueManySystems);
try graph.freeController(controller);
try graph.runAllSystems();
const result = graph.getResource(TestResource).?;
try std.testing.expectEqual(7980, result.data1);
try std.testing.expectEqual(10, result.data2);
}

View File

@@ -1,200 +0,0 @@
const std = @import("std");
const utils = @import("utils.zig");
const System = @import("system.zig");
const Resource = @import("resource.zig");
const Controller = @This();
pub const Option = utils.SystemSetOption;
const DEFAULT_CONTROLLER_CAPACITY = 8;
alloc: std.mem.Allocator,
command_buffer: std.ArrayListUnmanaged(Command),
error_state: ErrorState,
dud_range: struct { System.Dud.Id, System.Dud.Id },
submit_dud: ?System.Dud.Id,
pub const Command = union(enum) {
add_resource: Resource,
queue_system: System,
};
pub const ErrorState = union(enum) {
ok: void,
recoverable: []const u8,
unrecoverable: void,
};
pub fn create(alloc: std.mem.Allocator) !Controller {
return .{
.alloc = alloc,
.command_buffer = try std.ArrayListUnmanaged(Command).initCapacity(alloc, DEFAULT_CONTROLLER_CAPACITY),
.error_state = .ok,
.dud_range = .{ 0, 0 },
.submit_dud = null,
};
}
/// Returns command queue, caller is responsible for freeing it's data
/// Call `clean()` afterwards, to clear the command queue
pub fn commands(self: *Controller) []Command {
return self.command_buffer.items;
}
pub fn setDuds(self: *Controller, start_id: System.Dud.Id, end_id: System.Dud.Id) void {
self.dud_range = .{ start_id, end_id };
}
fn acquireDud(self: *Controller) ?System.Dud.Id {
if (self.dud_range[0] == self.dud_range[1]) return null;
defer self.dud_range[0] += 1;
return self.dud_range[0];
}
/// Clears the command buffer for the next use (does not deallocate it's contents)
pub fn clear(self: *Controller) void {
self.command_buffer.clearRetainingCapacity();
switch (self.error_state) {
.ok, .unrecoverable => {},
.recoverable => |msg| self.alloc.free(msg),
}
self.error_state = .ok;
self.submit_dud = null;
}
/// Adds resource to the global storage, discarding any previously existing data
pub inline fn addResource(self: *Controller, resource: anytype) void {
utils.validateResource(@TypeOf(resource));
self.addAnyopaqueResource(
@ptrCast(&resource),
utils.hashType(@TypeOf(resource)),
@sizeOf(@TypeOf(resource)),
@alignOf(@TypeOf(resource)),
) catch |err| self.fail(err);
}
/// Queues a multitude of functions to be executed either in parallel or in ordered manner
/// `system_set` can be either a `System`-like function or a tuple which may contain other system sets
///
/// Optional tuple fields that control the execution behavior of functions:
///
/// `ordered` - ensures that all systems specified in the tuple are executed in provided order
pub fn queue(self: *Controller, comptime system_set: anytype) void {
utils.validateSystemSet(system_set);
self.queueInternal(system_set) catch |err| self.fail(err);
}
fn queueInternal(self: *Controller, comptime system_set: anytype) !void {
const prev_count = self.command_buffer.items.len;
const command_buffer = try self.command_buffer.addManyAsSlice(self.alloc, utils.countSystems(system_set));
errdefer self.command_buffer.shrinkRetainingCapacity(prev_count);
const commands_created = try self.createQueueCommands(utils.SystemSet.fromAny(system_set), command_buffer, null, self.submit_dud);
std.debug.assert(commands_created == command_buffer.len);
}
fn createQueueCommands(
self: *Controller,
comptime system_set: utils.SystemSet,
command_buffer: []Command,
requires_dud: ?System.Dud.Id,
submit_dud: ?System.Dud.Id,
) !usize {
switch (system_set) {
.single => |single| {
command_buffer[0] = .{ .queue_system = .{
.function_runner = single.runner,
.requested_types = single.requests,
.requires_dud = requires_dud,
.submit_dud = submit_dud,
.label = single.label,
} };
return 1;
},
.set => |set| {
var queued_total: usize = 0;
var prev_dud = requires_dud;
var next_dud = submit_dud;
if (set.ordered) {
next_dud = requires_dud;
}
var queued_sets: usize = 0;
const total_sets: usize = set.subsets.len;
inline for (set.subsets) |subset| {
if (set.ordered) {
prev_dud = next_dud;
if (queued_sets == total_sets - 1) {
next_dud = submit_dud;
} else {
// TODO: Soft fail
next_dud = self.acquireDud().?;
}
}
queued_total += try self.createQueueCommands(
subset,
command_buffer[queued_total..],
prev_dud,
next_dud,
);
queued_sets += 1;
}
return queued_total;
},
}
}
/// `previous_output` is expected to be aligned accordingly
fn addAnyopaqueResource(
self: *Controller,
resource: *const anyopaque,
hash: utils.Hash,
size: usize,
align_to: u29,
) !void {
// TODO: Review this shady function
const resource_buffer = try self.alloc.alloc(u8, size + align_to - 1);
errdefer self.alloc.free(resource_buffer);
const align_offset = std.mem.alignPointerOffset(
@as([*]u8, @ptrCast(resource_buffer)),
align_to,
) orelse unreachable;
@memcpy(
resource_buffer[align_offset..size],
@as([*]const u8, @ptrCast(resource))[0..size],
);
try self.command_buffer.append(self.alloc, .{ .add_resource = .{
.pointer = @ptrCast(resource_buffer[align_offset..]),
.buffer = resource_buffer,
.alignment = align_to,
.hash = hash,
} });
}
const ControllerError = std.mem.Allocator.Error;
fn fail(self: *Controller, err: ControllerError) void {
if (self.error_state == .unrecoverable) return;
if (self.error_state == .recoverable) self.alloc.free(self.error_state.recoverable);
switch (err) {
error.OutOfMemory => self.error_state = .unrecoverable,
}
}
pub fn deinit(self: *Controller) void {
for (self.command_buffer.items) |*command| {
switch (command.*) {
.add_resource => |*resource| resource.deinit(self.alloc),
.queue_system => {},
}
}
self.clear();
self.command_buffer.deinit(self.alloc);
}

View File

@@ -1,14 +0,0 @@
const std = @import("std");
const utils = @import("utils.zig");
const Resource = @This();
/// Resource data
pointer: *anyopaque,
/// Pointer to the memory allocted for this resource
buffer: []u8,
alignment: u29,
hash: utils.Hash,
pub fn deinit(self: *Resource, alloc: std.mem.Allocator) void {
alloc.free(self.buffer);
}

View File

@@ -1,15 +0,0 @@
const std = @import("std");
const utils = @import("utils.zig");
const Controller = @import("controller.zig");
function_runner: *const fn ([]const *anyopaque) void,
requested_types: []const utils.SystemRequest,
requires_dud: ?Dud.Id,
submit_dud: ?Dud.Id,
label: []const u8,
pub const Dud = struct {
pub const Id = usize;
required_count: u16 = 0,
};

View File

@@ -1,204 +0,0 @@
const std = @import("std");
const Resource = @import("resource.zig");
const Controller = @import("controller.zig");
const System = @import("system.zig");
pub const Hash = u32;
const HashAlgorithm = std.crypto.hash.blake2.Blake2s(@bitSizeOf(Hash));
pub const SystemSetOption = enum {
ordered,
};
pub const SystemRequest = union(enum) {
resource: Hash,
controller: void,
};
pub const SystemSet = union(enum) {
single: struct {
runner: *const fn ([]const *anyopaque) void,
requests: []const SystemRequest,
label: []const u8,
},
set: struct {
subsets: []const SystemSet,
ordered: bool,
},
pub fn fromAny(comptime any: anytype) SystemSet {
return comptime switch (@typeInfo(@TypeOf(any))) {
.@"struct" => fromStruct(any),
.@"fn" => fromFunction(any),
else => @compileError("System set must be either a tuple or a function, got " ++ @typeName(@TypeOf(any))),
};
}
pub fn fromStruct(comptime set: anytype) SystemSet {
return comptime blk: {
var subset_count = 0;
for (@typeInfo(@TypeOf(set)).@"struct".fields) |field| {
const info = @typeInfo(field.type);
if (info == .@"fn" or info == .@"struct") {
subset_count += 1;
}
}
var subsets: [subset_count]SystemSet = undefined;
var ordered = false;
var i = 0;
for (@typeInfo(@TypeOf(set)).@"struct".fields) |field| {
const info = @typeInfo(field.type);
if (info == .@"fn" or info == .@"struct") {
subsets[i] = SystemSet.fromAny(@field(set, field.name));
i += 1;
continue;
}
if (field.type == SystemSetOption) {
switch (@field(set, field.name)) {
SystemSetOption.ordered => ordered = true,
}
i += 1;
continue;
}
@compileError("System set contains extraneous elements");
}
const subsets_const = subsets;
break :blk SystemSet{ .set = .{
.subsets = &subsets_const,
.ordered = ordered,
} };
};
}
pub fn fromFunction(comptime function: anytype) SystemSet {
return comptime SystemSet{ .single = .{
.runner = generateRunner(function),
.requests = generateRequests(function),
.label = @typeName(@TypeOf(function)),
} };
}
};
pub inline fn hashType(comptime h_type: type) Hash {
return hashString(@typeName(h_type));
}
pub fn hashString(comptime name: []const u8) Hash {
@setEvalBranchQuota(100000);
var output: [@divExact(@bitSizeOf(Hash), 8)]u8 = undefined;
HashAlgorithm.hash(name, &output, .{});
return std.mem.readInt(
Hash,
output[0..],
@import("builtin").cpu.arch.endian(),
);
}
pub fn validateResource(comptime resource_type: type) void {
switch (@typeInfo(resource_type)) {
.@"struct", .@"enum", .@"union" => return,
else => @compileError("Invalid resource type \"" ++ @typeName(resource_type) ++ "\""),
}
}
// TODO: Make validators print helpful errors so I don't have to check reference trace all the time
pub fn validateSystem(comptime system: anytype) void {
const info = @typeInfo(@TypeOf(system));
if (info != .@"fn") @compileError("System can only be a function, got " ++ @typeName(system));
if (@typeInfo(info.@"fn".return_type.?) != .void and
@typeInfo(info.@"fn".return_type.?) != .error_union) @compileError("Systems are not allowed to return any value (" ++ @typeName(info.@"fn".return_type.?) ++ " returned)");
if (info.@"fn".is_var_args) @compileError("System cannot be variadic");
if (info.@"fn".is_generic) @compileError("System cannot be generic");
comptime {
var controller_requests: usize = 0;
for (info.@"fn".params) |param| {
if (@typeInfo(param.type.?) != .pointer) @compileError("Systems can only have pointer parameters");
switch (@typeInfo(param.type.?).pointer.child) {
Controller => {
controller_requests += 1;
},
else => |t| validateResource(t),
}
}
if (controller_requests > 1) @compileError("A system cannot request controller more than once");
}
}
pub fn validateSystemSet(comptime system_set: anytype) void {
comptime {
@setEvalBranchQuota(1000);
switch (@typeInfo(@TypeOf(system_set))) {
.@"fn" => validateSystem(system_set),
.@"struct" => |struct_info| {
for (struct_info.fields) |field_info| {
switch (@typeInfo(field_info.type)) {
.@"fn", .@"struct" => validateSystemSet(@field(system_set, field_info.name)),
else => {
if (field_info.type == SystemSetOption) continue;
@compileError("Invalid field \"" ++
field_info.name ++
"\" of type `" ++
@typeName(field_info.type) ++
"` in system set");
},
}
}
},
else => {
@compileError("System set must be either a function or a tuple (got `" ++ @typeName(@TypeOf(system_set)) ++ "`)");
},
}
}
}
pub fn generateRunner(comptime system: anytype) fn ([]const *anyopaque) void {
const RunnerImpl = struct {
fn runner(resources: []const *anyopaque) void {
var args: std.meta.ArgsTuple(@TypeOf(system)) = undefined;
inline for (0..@typeInfo(@TypeOf(system)).@"fn".params.len) |index| {
args[index] = @alignCast(@ptrCast(resources[index]));
}
switch (@typeInfo(@typeInfo(@TypeOf(system)).@"fn".return_type.?)) {
.void => @call(.always_inline, system, args),
.error_union => @call(.always_inline, system, args) catch |err| {
std.debug.print("System error: {s}\n", .{@errorName(err)});
},
else => unreachable,
}
}
};
return RunnerImpl.runner;
}
pub fn generateRequests(comptime function: anytype) []const SystemRequest {
return comptime blk: {
var requests: [@typeInfo(@TypeOf(function)).@"fn".params.len]SystemRequest = undefined;
for (0.., @typeInfo(@TypeOf(function)).@"fn".params) |i, param| {
switch (@typeInfo(param.type.?).pointer.child) {
Controller => requests[i] = .controller,
else => |resource_type| requests[i] = .{ .resource = hashType(resource_type) },
}
}
const requests_const = requests;
break :blk &requests_const;
};
}
pub fn countSystems(comptime tuple: anytype) usize {
return comptime blk: {
var total: usize = 0;
switch (@typeInfo(@TypeOf(tuple))) {
.@"fn" => total += 1,
.@"struct" => |struct_info| {
for (struct_info.fields) |field| {
switch (@typeInfo(field.type)) {
.@"fn", .@"struct" => total += countSystems(@field(tuple, field.name)),
else => {},
}
}
},
else => {},
}
break :blk total;
};
}

View File

@@ -1,7 +1,7 @@
const std = @import("std");
const sdl = @import("sdl");
const err = @import("error.zig");
const presets = @import("graphics/presets.zig");
const GameError = @import("game.zig").GameError;
pub const Transform = @import("graphics/transform.zig");
pub const Camera = @import("graphics/camera.zig");
@@ -16,74 +16,67 @@ pub const Texture = struct {
sampler: *sdl.GPUSampler,
};
window: *sdl.Window,
renderer: *sdl.Renderer,
device: *sdl.GPUDevice,
var window: *sdl.Window = undefined;
var renderer: *sdl.Renderer = undefined;
var device: *sdl.GPUDevice = undefined;
/// Only available while drawing
command_buffer: ?*sdl.GPUCommandBuffer,
render_pass: ?*sdl.GPURenderPass,
var command_buffer: ?*sdl.GPUCommandBuffer = null;
var render_pass: ?*sdl.GPURenderPass = null;
shader_vert: *sdl.GPUShader,
shader_frag: *sdl.GPUShader,
var shader_vert: *sdl.GPUShader = undefined;
var shader_frag: *sdl.GPUShader = undefined;
vertex_buffer: *sdl.GPUBuffer,
vertex_buffer_capacity: usize,
vertex_buffer_used: usize,
var vertex_buffer: *sdl.GPUBuffer = undefined;
var vertex_buffer_capacity: usize = undefined;
var vertex_buffer_used: usize = undefined;
transfer_buffer: *sdl.GPUTransferBuffer,
transfer_buffer_capacity: usize,
var transfer_buffer: *sdl.GPUTransferBuffer = undefined;
var transfer_buffer_capacity: usize = undefined;
depth_texture: *sdl.GPUTexture,
msaa_resolve: *sdl.GPUTexture,
pipeline: *sdl.GPUGraphicsPipeline,
var depth_texture: *sdl.GPUTexture = undefined;
var msaa_resolve: *sdl.GPUTexture = undefined;
var pipeline: *sdl.GPUGraphicsPipeline = undefined;
window_size: [2]u32,
var window_size: [2]u32 = undefined;
camera: Camera,
pub var camera: Camera = undefined;
to_resize: ?[2]u32 = null,
var to_resize: ?[2]u32 = null;
const VERTEX_BUFFER_DEFAULT_CAPACITY = 1024;
const VERTEX_BUFFER_GROWTH_MULTIPLIER = 2;
const TRANSFER_BUFFER_DEFAULT_CAPACITY = 1024;
const BYTES_PER_VERTEX = 5 * 4;
const Self = @This();
pub fn create() GameError!Self {
const Graphics = @This();
pub fn create() void {
// Init
if (!sdl.Init(sdl.INIT_VIDEO | sdl.INIT_EVENTS)) return GameError.SdlError;
if (!sdl.Init(sdl.INIT_VIDEO | sdl.INIT_EVENTS)) err.sdl();
// Window and Renderer
var renderer: ?*sdl.Renderer = null;
var window: ?*sdl.Window = null;
if (!sdl.CreateWindowAndRenderer(
"",
1600,
900,
sdl.WINDOW_VULKAN | sdl.WINDOW_RESIZABLE,
&window,
&renderer,
)) return GameError.SdlError;
errdefer sdl.DestroyRenderer(renderer);
errdefer sdl.DestroyWindow(window);
@ptrCast(&Graphics.window),
@ptrCast(&Graphics.renderer),
)) err.sdl();
Graphics.window_size = .{ 1600, 900 };
if (!sdl.SetRenderVSync(renderer, sdl.RENDERER_VSYNC_ADAPTIVE)) return GameError.SdlError;
if (!sdl.SetRenderVSync(renderer, sdl.RENDERER_VSYNC_ADAPTIVE)) err.sdl();
// Device
const device = sdl.CreateGPUDevice(
Graphics.device = sdl.CreateGPUDevice(
sdl.GPU_SHADERFORMAT_SPIRV,
@import("builtin").mode == .Debug,
null,
) orelse return GameError.SdlError;
errdefer sdl.DestroyGPUDevice(device);
) orelse err.sdl();
// Claim
if (!sdl.ClaimWindowForGPUDevice(device, window)) return GameError.SdlError;
errdefer sdl.ReleaseWindowFromGPUDevice(device, window);
if (!sdl.ClaimWindowForGPUDevice(Graphics.device, Graphics.window)) err.sdl();
const shader_vert = try loadShader(
device,
Graphics.shader_vert = loadShader(
"data/shaders/basic.vert",
.{
.entrypoint = "main",
@@ -92,10 +85,8 @@ pub fn create() GameError!Self {
.num_uniform_buffers = 2,
},
);
errdefer sdl.ReleaseGPUShader(device, shader_vert);
const shader_frag = try loadShader(
device,
Graphics.shader_frag = loadShader(
"data/shaders/basic.frag",
.{
.entrypoint = "main",
@@ -104,37 +95,34 @@ pub fn create() GameError!Self {
.num_samplers = 1,
},
);
errdefer sdl.ReleaseGPUShader(device, shader_frag);
const vertex_buffer = sdl.CreateGPUBuffer(device, &.{
Graphics.vertex_buffer = sdl.CreateGPUBuffer(Graphics.device, &.{
.usage = sdl.GPU_BUFFERUSAGE_VERTEX,
.size = VERTEX_BUFFER_DEFAULT_CAPACITY,
}) orelse return GameError.SdlError;
errdefer sdl.ReleaseGPUBuffer(device, vertex_buffer);
}) orelse err.sdl();
Graphics.vertex_buffer_capacity = VERTEX_BUFFER_DEFAULT_CAPACITY;
Graphics.vertex_buffer_used = 0;
const transfer_buffer = sdl.CreateGPUTransferBuffer(device, &.{
Graphics.transfer_buffer = sdl.CreateGPUTransferBuffer(Graphics.device, &.{
.size = TRANSFER_BUFFER_DEFAULT_CAPACITY,
.usage = sdl.GPU_TRANSFERBUFFERUSAGE_UPLOAD | sdl.GPU_TRANSFERBUFFERUSAGE_DOWNLOAD,
}) orelse return GameError.SdlError;
errdefer sdl.ReleaseGPUTransferBuffer(device, transfer_buffer);
}) orelse err.sdl();
Graphics.transfer_buffer_capacity = TRANSFER_BUFFER_DEFAULT_CAPACITY;
const target_format = sdl.GetGPUSwapchainTextureFormat(device, window);
if (target_format == sdl.GPU_TEXTUREFORMAT_INVALID) return GameError.SdlError;
const target_format = sdl.GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window);
if (target_format == sdl.GPU_TEXTUREFORMAT_INVALID) err.sdl();
// TODO: Clean
var window_width: c_int = 1;
var window_height: c_int = 1;
if (!sdl.GetWindowSizeInPixels(window, &window_width, &window_height)) return GameError.SdlError;
if (!sdl.GetWindowSizeInPixels(Graphics.window, &window_width, &window_height)) err.sdl();
const depth_texture = try createDepthTexture(device, @intCast(window_width), @intCast(window_height));
errdefer sdl.ReleaseGPUTexture(device, depth_texture);
Graphics.depth_texture = createDepthTexture(@intCast(window_width), @intCast(window_height));
Graphics.msaa_resolve = createTexture(@intCast(window_width), @intCast(window_height), target_format);
const msaa_resolve = try createTexture(device, @intCast(window_width), @intCast(window_height), target_format);
errdefer sdl.ReleaseGPUTexture(device, msaa_resolve);
const pipeline = sdl.CreateGPUGraphicsPipeline(device, &.{
.vertex_shader = shader_vert,
.fragment_shader = shader_frag,
Graphics.pipeline = sdl.CreateGPUGraphicsPipeline(Graphics.device, &.{
.vertex_shader = Graphics.shader_vert,
.fragment_shader = Graphics.shader_frag,
.vertex_input_state = .{
.vertex_buffer_descriptions = &.{
.slot = 0,
@@ -173,68 +161,42 @@ pub fn create() GameError!Self {
.num_color_targets = 1,
.has_depth_stencil_target = true,
},
}) orelse return GameError.SdlError;
errdefer sdl.ReleaseGPUGraphicsPipeline(pipeline);
}) orelse err.sdl();
return .{
.window = window.?,
.renderer = renderer.?,
.device = device,
.command_buffer = null,
.render_pass = null,
.shader_vert = shader_vert,
.shader_frag = shader_frag,
.vertex_buffer = vertex_buffer,
.vertex_buffer_capacity = VERTEX_BUFFER_DEFAULT_CAPACITY,
.vertex_buffer_used = 0,
.transfer_buffer = transfer_buffer,
.transfer_buffer_capacity = TRANSFER_BUFFER_DEFAULT_CAPACITY,
.depth_texture = depth_texture,
.msaa_resolve = msaa_resolve,
.pipeline = pipeline,
.window_size = .{ 1600, 900 },
.camera = Camera{
.transform = .{},
.near = 1.0,
.far = 1024.0,
.lens = 1.5,
.aspect = 16.0 / 9.0,
},
Graphics.camera = Camera{
.transform = .{},
.near = 1.0,
.far = 1024.0,
.lens = 1.5,
.aspect = 16.0 / 9.0,
};
}
pub fn destroy(self: *Self) void {
sdl.ReleaseWindowFromGPUDevice(self.device, self.window);
sdl.DestroyRenderer(self.renderer);
sdl.DestroyWindow(self.window);
pub fn destroy() void {
sdl.ReleaseWindowFromGPUDevice(Graphics.device, Graphics.window);
sdl.DestroyRenderer(Graphics.renderer);
sdl.DestroyWindow(Graphics.window);
sdl.ReleaseGPUGraphicsPipeline(self.device, self.pipeline);
sdl.ReleaseGPUTexture(self.device, self.msaa_resolve);
sdl.ReleaseGPUTexture(self.device, self.depth_texture);
sdl.ReleaseGPUBuffer(self.device, self.vertex_buffer);
sdl.ReleaseGPUTransferBuffer(self.device, self.transfer_buffer);
sdl.ReleaseGPUGraphicsPipeline(Graphics.device, Graphics.pipeline);
sdl.ReleaseGPUTexture(Graphics.device, Graphics.msaa_resolve);
sdl.ReleaseGPUTexture(Graphics.device, Graphics.depth_texture);
sdl.ReleaseGPUBuffer(Graphics.device, Graphics.vertex_buffer);
sdl.ReleaseGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer);
sdl.ReleaseGPUShader(self.device, self.shader_vert);
sdl.ReleaseGPUShader(self.device, self.shader_frag);
sdl.ReleaseGPUShader(Graphics.device, Graphics.shader_vert);
sdl.ReleaseGPUShader(Graphics.device, Graphics.shader_frag);
if (self.command_buffer != null) {
_ = sdl.CancelGPUCommandBuffer(self.command_buffer);
self.command_buffer = null;
if (Graphics.command_buffer != null) {
_ = sdl.CancelGPUCommandBuffer(Graphics.command_buffer);
Graphics.command_buffer = null;
}
sdl.DestroyGPUDevice(self.device);
sdl.DestroyGPUDevice(Graphics.device);
}
pub fn loadTexture(self: *Self, width: u32, height: u32, texture_bytes: []const u8) GameError!Texture {
const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(self.device, self.window);
pub fn loadTexture(width: u32, height: u32, texture_bytes: []const u8) Texture {
const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window);
const texture = sdl.CreateGPUTexture(self.device, &sdl.GPUTextureCreateInfo{
const texture = sdl.CreateGPUTexture(Graphics.device, &sdl.GPUTextureCreateInfo{
.format = target_format,
.layer_count_or_depth = 1,
.width = width,
@@ -242,25 +204,22 @@ pub fn loadTexture(self: *Self, width: u32, height: u32, texture_bytes: []const
.num_levels = 1,
.sample_count = sdl.GPU_SAMPLECOUNT_1,
.usage = sdl.GPU_TEXTUREUSAGE_SAMPLER,
}) orelse return GameError.SdlError;
errdefer sdl.ReleaseGPUTexture(self.device, texture);
}) orelse err.sdl();
const command_buffer = sdl.AcquireGPUCommandBuffer(self.device) orelse return GameError.SdlError;
const temp_command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl();
{
errdefer _ = sdl.CancelGPUCommandBuffer(command_buffer);
const copy_pass = sdl.BeginGPUCopyPass(command_buffer) orelse return GameError.SdlError;
const copy_pass = sdl.BeginGPUCopyPass(temp_command_buffer) orelse err.sdl();
defer sdl.EndGPUCopyPass(copy_pass);
const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(self.device, self.transfer_buffer, false) orelse return GameError.SdlError);
const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer, false) orelse err.sdl());
@memcpy(map, texture_bytes);
sdl.UnmapGPUTransferBuffer(self.device, self.transfer_buffer);
sdl.UnmapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer);
sdl.UploadToGPUTexture(copy_pass, &sdl.GPUTextureTransferInfo{
.offset = 0,
.pixels_per_row = width,
.rows_per_layer = height,
.transfer_buffer = self.transfer_buffer,
.transfer_buffer = Graphics.transfer_buffer,
}, &sdl.GPUTextureRegion{
.texture = texture,
.mip_level = 0,
@@ -273,15 +232,15 @@ pub fn loadTexture(self: *Self, width: u32, height: u32, texture_bytes: []const
.d = 1,
}, false);
}
if (!sdl.SubmitGPUCommandBuffer(command_buffer)) return GameError.SdlError;
if (!sdl.SubmitGPUCommandBuffer(temp_command_buffer)) err.sdl();
const sampler = sdl.CreateGPUSampler(self.device, &sdl.GPUSamplerCreateInfo{
const sampler = sdl.CreateGPUSampler(Graphics.device, &sdl.GPUSamplerCreateInfo{
.address_mode_u = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
.address_mode_v = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
.address_mode_w = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
.mag_filter = sdl.GPU_FILTER_NEAREST,
.min_filter = sdl.GPU_FILTER_LINEAR,
}) orelse return GameError.SdlError;
}) orelse err.sdl();
return Texture{
.texture = texture,
@@ -289,44 +248,47 @@ pub fn loadTexture(self: *Self, width: u32, height: u32, texture_bytes: []const
};
}
pub fn loadMesh(self: *Self, mesh_bytes: []const u8) GameError!Mesh {
std.debug.assert(mesh_bytes.len < self.transfer_buffer_capacity);
pub fn unloadTexture(texture: Texture) void {
sdl.ReleaseGPUSampler(Graphics.device, texture.sampler);
sdl.ReleaseGPUTexture(Graphics.device, texture.texture);
}
pub fn loadMesh(mesh_bytes: []const u8) Mesh {
std.debug.assert(mesh_bytes.len < Graphics.transfer_buffer_capacity);
var size_mult: usize = 1;
while (self.vertex_buffer_used + mesh_bytes.len > self.vertex_buffer_capacity) {
while (Graphics.vertex_buffer_used + mesh_bytes.len > Graphics.vertex_buffer_capacity * size_mult) {
size_mult *= VERTEX_BUFFER_GROWTH_MULTIPLIER;
}
if (size_mult > 1) {
try self.growVertexBuffer(self.vertex_buffer_capacity * size_mult);
Graphics.growVertexBuffer(Graphics.vertex_buffer_capacity * size_mult);
}
const map = sdl.MapGPUTransferBuffer(self.device, self.transfer_buffer, false) orelse return GameError.SdlError;
const map = sdl.MapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer, false) orelse err.sdl();
@memcpy(@as([*]u8, @ptrCast(map)), mesh_bytes);
sdl.UnmapGPUTransferBuffer(self.device, self.transfer_buffer);
sdl.UnmapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer);
const command_buffer = sdl.AcquireGPUCommandBuffer(self.device) orelse return GameError.SdlError;
const temp_command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl();
const fence = blk: {
errdefer _ = sdl.CancelGPUCommandBuffer(command_buffer);
const copy_pass = sdl.BeginGPUCopyPass(command_buffer) orelse return GameError.SdlError;
const copy_pass = sdl.BeginGPUCopyPass(temp_command_buffer) orelse err.sdl();
sdl.UploadToGPUBuffer(copy_pass, &.{
.transfer_buffer = self.transfer_buffer,
.transfer_buffer = Graphics.transfer_buffer,
.offset = 0,
}, &.{
.buffer = self.vertex_buffer,
.offset = @intCast(self.vertex_buffer_used),
.buffer = Graphics.vertex_buffer,
.offset = @intCast(Graphics.vertex_buffer_used),
.size = @intCast(mesh_bytes.len),
}, false);
sdl.EndGPUCopyPass(copy_pass);
break :blk sdl.SubmitGPUCommandBufferAndAcquireFence(command_buffer) orelse return GameError.SdlError;
break :blk sdl.SubmitGPUCommandBufferAndAcquireFence(temp_command_buffer) orelse err.sdl();
};
defer sdl.ReleaseGPUFence(self.device, fence);
defer sdl.ReleaseGPUFence(Graphics.device, fence);
if (!sdl.WaitForGPUFences(self.device, true, &fence, 1)) return GameError.SdlError;
if (!sdl.WaitForGPUFences(Graphics.device, true, &fence, 1)) err.sdl();
const vertex_start = self.vertex_buffer_used;
self.vertex_buffer_used += mesh_bytes.len;
const vertex_start = Graphics.vertex_buffer_used;
Graphics.vertex_buffer_used += mesh_bytes.len;
return Mesh{
.vertex_start = vertex_start / BYTES_PER_VERTEX,
@@ -334,38 +296,34 @@ pub fn loadMesh(self: *Self, mesh_bytes: []const u8) GameError!Mesh {
};
}
pub fn unloadMesh(self: *Self, mesh: Mesh) void {
pub fn unloadMesh(mesh: Mesh) void {
// TODO: free some memory
_ = self;
_ = &mesh;
}
fn growVertexBuffer(self: *Self, new_size: usize) GameError!void {
const new_buffer = sdl.CreateGPUBuffer(self.device, &.{
fn growVertexBuffer(new_size: usize) void {
const new_buffer = sdl.CreateGPUBuffer(Graphics.device, &.{
.size = @intCast(new_size),
.usage = sdl.GPU_BUFFERUSAGE_VERTEX,
}) orelse return GameError.SdlError;
errdefer sdl.ReleaseGPUBuffer(self.device, new_buffer);
}) orelse err.sdl();
const command_buffer = sdl.AcquireGPUCommandBuffer(self.device) orelse return GameError.SdlError;
const temp_command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl();
const fence = blk: {
errdefer _ = sdl.CancelGPUCommandBuffer(command_buffer);
const copy_pass = sdl.BeginGPUCopyPass(command_buffer);
const copy_pass = sdl.BeginGPUCopyPass(temp_command_buffer);
var copied: usize = 0;
while (copied < self.vertex_buffer_used) {
const to_transer = @min(self.vertex_buffer_used - copied, self.transfer_buffer_capacity);
while (copied < Graphics.vertex_buffer_used) {
const to_transer = @min(Graphics.vertex_buffer_used - copied, Graphics.transfer_buffer_capacity);
sdl.DownloadFromGPUBuffer(copy_pass, &.{
.buffer = self.vertex_buffer,
.buffer = Graphics.vertex_buffer,
.offset = @intCast(copied),
.size = @intCast(to_transer),
}, &.{
.transfer_buffer = self.transfer_buffer,
.transfer_buffer = Graphics.transfer_buffer,
.offset = 0,
});
sdl.UploadToGPUBuffer(copy_pass, &.{
.transfer_buffer = self.transfer_buffer,
.transfer_buffer = Graphics.transfer_buffer,
.offset = 0,
}, &.{
.buffer = new_buffer,
@@ -376,36 +334,36 @@ fn growVertexBuffer(self: *Self, new_size: usize) GameError!void {
}
sdl.EndGPUCopyPass(copy_pass);
break :blk sdl.SubmitGPUCommandBufferAndAcquireFence(command_buffer) orelse return GameError.SdlError;
break :blk sdl.SubmitGPUCommandBufferAndAcquireFence(temp_command_buffer) orelse err.sdl();
};
defer sdl.ReleaseGPUFence(self.device, fence);
defer sdl.ReleaseGPUFence(Graphics.device, fence);
if (!sdl.WaitForGPUFences(self.device, true, &fence, 1)) return GameError.SdlError;
if (!sdl.WaitForGPUFences(Graphics.device, true, &fence, 1)) err.sdl();
sdl.ReleaseGPUBuffer(self.device, self.vertex_buffer);
self.vertex_buffer = new_buffer;
self.vertex_buffer_capacity = new_size;
sdl.ReleaseGPUBuffer(Graphics.device, Graphics.vertex_buffer);
Graphics.vertex_buffer = new_buffer;
Graphics.vertex_buffer_capacity = new_size;
}
/// If window is minimized returns `false`, `render_pass` remains null
/// Otherwise `command_buffer` and `render_pass` are both set
pub fn beginDraw(self: *Self) GameError!bool {
self.command_buffer = sdl.AcquireGPUCommandBuffer(self.device) orelse return GameError.SdlError;
if (self.to_resize) |new_size| {
try self.resetTextures(new_size[0], new_size[1]);
self.camera.aspect = @as(f32, @floatFromInt(new_size[0])) / @as(f32, @floatFromInt(new_size[1]));
self.window_size = new_size;
self.to_resize = null;
pub fn beginDraw() bool {
Graphics.command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl();
if (Graphics.to_resize) |new_size| {
Graphics.resetTextures(new_size[0], new_size[1]);
Graphics.camera.aspect = @as(f32, @floatFromInt(new_size[0])) / @as(f32, @floatFromInt(new_size[1]));
Graphics.window_size = new_size;
Graphics.to_resize = null;
}
var render_target: ?*sdl.GPUTexture = null;
var width: u32 = 0;
var height: u32 = 0;
if (!sdl.WaitAndAcquireGPUSwapchainTexture(self.command_buffer, self.window, &render_target, &width, &height)) return GameError.SdlError;
if (!sdl.WaitAndAcquireGPUSwapchainTexture(Graphics.command_buffer, Graphics.window, &render_target, &width, &height)) err.sdl();
// Hidden
if (render_target == null) return false;
self.render_pass = sdl.BeginGPURenderPass(self.command_buffer, &.{
Graphics.render_pass = sdl.BeginGPURenderPass(Graphics.command_buffer, &.{
.clear_color = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 },
.cycle = false,
.load_op = sdl.GPU_LOADOP_CLEAR,
@@ -413,57 +371,57 @@ pub fn beginDraw(self: *Self) GameError!bool {
// .store_op = sdl.GPU_STOREOP_STORE,
.resolve_texture = render_target,
.mip_level = 0,
.texture = self.msaa_resolve,
.texture = Graphics.msaa_resolve,
}, 1, &.{
.clear_depth = 1.0,
.load_op = sdl.GPU_LOADOP_CLEAR,
.store_op = sdl.GPU_STOREOP_DONT_CARE,
.stencil_load_op = sdl.GPU_STOREOP_DONT_CARE,
.stencil_store_op = sdl.GPU_STOREOP_DONT_CARE,
.texture = self.depth_texture,
}) orelse return GameError.SdlError;
.texture = Graphics.depth_texture,
}) orelse err.sdl();
sdl.BindGPUGraphicsPipeline(self.render_pass, self.pipeline);
sdl.BindGPUVertexBuffers(self.render_pass, 0, &.{ .offset = 0, .buffer = self.vertex_buffer }, 1);
sdl.PushGPUVertexUniformData(self.command_buffer, 0, &self.camera.matrix(), 16 * 4);
sdl.BindGPUGraphicsPipeline(Graphics.render_pass, Graphics.pipeline);
sdl.BindGPUVertexBuffers(Graphics.render_pass, 0, &.{ .offset = 0, .buffer = Graphics.vertex_buffer }, 1);
sdl.PushGPUVertexUniformData(Graphics.command_buffer, 0, &Graphics.camera.matrix(), 16 * 4);
return true;
}
pub fn drawMesh(self: *Self, mesh: Mesh, texture: Texture, transform: Transform) GameError!void {
if (self.render_pass == null) return;
pub fn drawMesh(mesh: Mesh, texture: Texture, transform: Transform) void {
if (Graphics.render_pass == null) return;
sdl.PushGPUVertexUniformData(self.command_buffer, 1, &transform.matrix(), 16 * 4);
sdl.BindGPUFragmentSamplers(self.render_pass, 0, &sdl.GPUTextureSamplerBinding{
sdl.PushGPUVertexUniformData(Graphics.command_buffer, 1, &transform.matrix(), 16 * 4);
sdl.BindGPUFragmentSamplers(Graphics.render_pass, 0, &sdl.GPUTextureSamplerBinding{
.texture = texture.texture,
.sampler = texture.sampler,
}, 1);
sdl.DrawGPUPrimitives(self.render_pass, @intCast(mesh.vertex_count), 1, @intCast(mesh.vertex_start), 0);
sdl.DrawGPUPrimitives(Graphics.render_pass, @intCast(mesh.vertex_count), 1, @intCast(mesh.vertex_start), 0);
}
pub fn endDraw(self: *Self) GameError!void {
defer self.command_buffer = null;
defer self.render_pass = null;
if (self.render_pass) |render_pass| {
sdl.EndGPURenderPass(render_pass);
pub fn endDraw() void {
defer Graphics.command_buffer = null;
defer Graphics.render_pass = null;
if (Graphics.render_pass) |pass| {
sdl.EndGPURenderPass(pass);
}
if (!sdl.SubmitGPUCommandBuffer(self.command_buffer)) return GameError.SdlError;
if (!sdl.SubmitGPUCommandBuffer(Graphics.command_buffer)) err.sdl();
}
fn loadShader(device: *sdl.GPUDevice, path: []const u8, info: sdl.GPUShaderCreateInfo) GameError!*sdl.GPUShader {
const file = std.fs.cwd().openFile(path, .{}) catch return GameError.OSError;
fn loadShader(path: []const u8, info: sdl.GPUShaderCreateInfo) *sdl.GPUShader {
const file = std.fs.cwd().openFile(path, .{}) catch |e| err.file(e, path);
defer file.close();
const code = file.readToEndAllocOptions(std.heap.c_allocator, 1024 * 1024 * 1024, null, .@"1", 0) catch return GameError.OSError;
const code = file.readToEndAllocOptions(std.heap.c_allocator, std.math.maxInt(usize), null, .@"1", 0) catch |e| err.file(e, path);
defer std.heap.c_allocator.free(code);
var updated_info = info;
updated_info.code = code;
updated_info.code_size = code.len;
return sdl.CreateGPUShader(device, &updated_info) orelse return GameError.SdlError;
return sdl.CreateGPUShader(device, &updated_info) orelse err.sdl();
}
fn createDepthTexture(device: *sdl.GPUDevice, width: u32, height: u32) GameError!*sdl.GPUTexture {
fn createDepthTexture(width: u32, height: u32) *sdl.GPUTexture {
return sdl.CreateGPUTexture(device, &.{
.format = sdl.GPU_TEXTUREFORMAT_D16_UNORM,
.layer_count_or_depth = 1,
@@ -472,10 +430,10 @@ fn createDepthTexture(device: *sdl.GPUDevice, width: u32, height: u32) GameError
.num_levels = 1,
.sample_count = sdl.GPU_SAMPLECOUNT_4,
.usage = sdl.GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET,
}) orelse return GameError.SdlError;
}) orelse err.sdl();
}
fn createTexture(device: *sdl.GPUDevice, width: u32, height: u32, format: c_uint) GameError!*sdl.GPUTexture {
fn createTexture(width: u32, height: u32, format: c_uint) *sdl.GPUTexture {
return sdl.CreateGPUTexture(device, &.{
.format = format,
.layer_count_or_depth = 1,
@@ -484,19 +442,23 @@ fn createTexture(device: *sdl.GPUDevice, width: u32, height: u32, format: c_uint
.num_levels = 1,
.sample_count = sdl.GPU_SAMPLECOUNT_4,
.usage = sdl.GPU_TEXTUREUSAGE_COLOR_TARGET,
}) orelse return GameError.SdlError;
}) orelse err.sdl();
}
fn resetTextures(self: *Self, width: u32, height: u32) GameError!void {
sdl.ReleaseGPUTexture(self.device, self.depth_texture);
self.depth_texture = try createDepthTexture(self.device, width, height);
fn resetTextures(width: u32, height: u32) void {
sdl.ReleaseGPUTexture(Graphics.device, Graphics.depth_texture);
Graphics.depth_texture = createDepthTexture(width, height);
const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(self.device, self.window);
const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window);
sdl.ReleaseGPUTexture(self.device, self.msaa_resolve);
self.msaa_resolve = try createTexture(self.device, width, height, target_format);
sdl.ReleaseGPUTexture(Graphics.device, Graphics.msaa_resolve);
Graphics.msaa_resolve = createTexture(width, height, target_format);
}
pub fn resize(self: *Self, width: u32, height: u32) void {
self.to_resize = .{ width, height };
pub fn resize(width: u32, height: u32) void {
Graphics.to_resize = .{ width, height };
}
pub fn windowId() sdl.WindowID {
return sdl.GetWindowID(Graphics.window);
}

View File

@@ -3,7 +3,7 @@ const builtin = @import("builtin");
const sdl = @import("sdl");
const Game = @import("game.zig");
pub fn runGame() !void {
pub fn main() void {
var allocator = switch (builtin.mode) {
.ReleaseSafe, .Debug => std.heap.DebugAllocator(.{}).init,
.ReleaseFast => std.heap.smp_allocator,
@@ -14,25 +14,13 @@ pub fn runGame() !void {
else => {},
};
var game = try Game.init(
Game.init(
switch (builtin.mode) {
.ReleaseSafe, .Debug => allocator.allocator(),
.ReleaseFast => allocator,
.ReleaseSmall => allocator,
},
);
defer game.deinit();
try game.run();
}
pub fn main() !void {
runGame() catch |err| {
switch (err) {
error.SdlError => {
std.debug.print("SDL Error:\n---\n{s}\n---\n", .{sdl.SDL_GetError()});
},
else => unreachable,
}
return err;
};
defer Game.deinit();
Game.run();
}

View File

@@ -1,9 +1,9 @@
const sdl = @import("sdl");
const key_store = @import("data/keystore.zig");
buttons: key_store.KeyStore(@TypeOf(sdl.BUTTON_LEFT), 4, 0),
buttons: key_store.KeyStore(@TypeOf(sdl.BUTTON_LEFT), 4, 0) = .{},
x: f32,
y: f32,
dx: f32,
dy: f32,
x: f32 = 0,
y: f32 = 0,
dx: f32 = 0,
dy: f32 = 0,

View File

@@ -41,3 +41,11 @@ pub fn unitsFromDuration(duration: TimeType) f32 {
pub fn durationFromUnits(units: f32) TimeType {
return @intFromFloat(@as(f32, @floatFromInt(TIME_UNIT)) * units);
}
pub fn earliest(a: Time, b: Time) Time {
return .{ .clock = @min(a.clock, b.clock) };
}
pub fn plus(time: Time, ticks: TimeType) Time {
return .{ .clock = time.clock + ticks };
}

144
src/world.zig Normal file
View File

@@ -0,0 +1,144 @@
const Graphics = @import("graphics.zig");
const Entity = @import("entity.zig");
const Time = @import("time.zig");
pub var time: Time = undefined;
var next_stop: Time = undefined;
var entities: [16]?Entity = undefined;
pub var plane_mesh: Graphics.Mesh = undefined;
pub var cube_mesh: Graphics.Mesh = undefined;
pub var texture: Graphics.Texture = undefined;
const World = @This();
pub fn initDebug() void {
entities = .{null} ** 16;
entities[0] = .{
.position = .{ 0, 0 },
.player = true,
};
entities[1] = .{
.position = .{ 2, 0 },
.enemy = true,
.controller = .{
.move_units = 0.25,
},
};
entities[2] = .{
.position = .{ 3, 0 },
.enemy = true,
.controller = .{
.move_units = 0.25,
},
};
time = Time.ZERO;
World.plane_mesh = Graphics.loadMesh(@ptrCast(&PLANE_MESH_DATA));
World.cube_mesh = Graphics.loadMesh(@ptrCast(&CUBE_MESH_DATA));
World.texture = Graphics.loadTexture(2, 2, @ptrCast(&TEXTURE_DATA));
}
pub fn deinit() void {
Graphics.unloadMesh(World.plane_mesh);
Graphics.unloadMesh(World.cube_mesh);
Graphics.unloadTexture(World.texture);
}
pub fn updateReal(delta: f32) void {
const update_until = World.time.plus(Time.durationFromUnits(delta));
while (!World.time.past(update_until)) {
const current = Time.earliest(World.next_stop, update_until);
defer World.time = current;
for (&World.entities) |*entity| {
if (entity.*) |*e| e.update();
}
}
}
pub fn draw(delta: f32) void {
Graphics.drawMesh(World.plane_mesh, World.texture, .{ .scale = @splat(5) });
for (&World.entities) |*entity| {
if (entity.*) |*e| e.draw(delta);
}
}
pub fn requestUpdate(at: Time) void {
World.next_stop = Time.earliest(at, World.next_stop);
}
pub fn entityAt(position: @Vector(2, i32)) ?*Entity {
for (&World.entities) |*maybe_entity| {
if (maybe_entity.*) |*entity| {
if (@reduce(.And, entity.position == position))
return entity;
}
}
return null;
}
pub fn isFree(position: @Vector(2, i32)) bool {
return World.entityAt(position) == null;
}
pub fn getPlayer() ?*Entity {
for (&World.entities) |*maybe_entity| {
if (maybe_entity.*) |*entity| {
if (entity.player)
return entity;
}
}
return null;
}
const CUBE_MESH_DATA = [_]f32{
-0.5, 0.5, -0.5, 0.0, 0.0,
0.5, 0.5, -0.5, 0.0, 0.0,
-0.5, -0.5, -0.5, 0.0, 0.0,
0.5, -0.5, -0.5, 0.0, 0.0,
-0.5, -0.5, -0.5, 0.0, 0.0,
0.5, 0.5, -0.5, 0.0, 0.0,
0.5, 0.5, -0.5, 0.0, 0.0,
0.5, 0.5, 0.5, 0.0, 0.0,
0.5, -0.5, -0.5, 0.0, 0.0,
0.5, -0.5, 0.5, 0.0, 0.0,
0.5, -0.5, -0.5, 0.0, 0.0,
0.5, 0.5, 0.5, 0.0, 0.0,
0.5, 0.5, 0.5, 0.0, 0.0,
-0.5, 0.5, 0.5, 0.0, 0.0,
0.5, -0.5, 0.5, 0.0, 0.0,
-0.5, -0.5, 0.5, 0.0, 0.0,
0.5, -0.5, 0.5, 0.0, 0.0,
-0.5, 0.5, 0.5, 0.0, 0.0,
-0.5, 0.5, 0.5, 0.0, 0.0,
-0.5, 0.5, -0.5, 0.0, 0.0,
-0.5, -0.5, 0.5, 0.0, 0.0,
-0.5, -0.5, -0.5, 0.0, 0.0,
-0.5, -0.5, 0.5, 0.0, 0.0,
-0.5, 0.5, -0.5, 0.0, 0.0,
-0.5, 0.5, 0.5, 0.0, 0.0,
0.5, 0.5, 0.5, 0.0, 0.0,
-0.5, 0.5, -0.5, 0.0, 0.0,
0.5, 0.5, -0.5, 0.0, 0.0,
-0.5, 0.5, -0.5, 0.0, 0.0,
0.5, 0.5, 0.5, 0.0, 0.0,
-0.5, -0.5, -0.5, 0.0, 0.0,
0.5, -0.5, -0.5, 0.0, 0.0,
-0.5, -0.5, 0.5, 0.0, 0.0,
0.5, -0.5, 0.5, 0.0, 0.0,
-0.5, -0.5, 0.5, 0.0, 0.0,
0.5, -0.5, -0.5, 0.0, 0.0,
};
const PLANE_MESH_DATA = [_]f32{
-0.5, -0.5, 0, 0.0, 1.0,
0.5, 0.5, 0, 1.0, 0.0,
-0.5, 0.5, 0, 0.0, 0.0,
0.5, 0.5, 0, 1.0, 0.0,
-0.5, -0.5, 0, 0.0, 1.0,
0.5, -0.5, 0, 1.0, 1.0,
};
const TEXTURE_DATA = [_]u8{
255, 64, 64, 255,
64, 255, 64, 255,
64, 64, 255, 255,
64, 64, 64, 255,
};