Compare commits

..

4 Commits

Author SHA1 Message Date
duck
640e7bed86 Fix some issues with asset unloading 2025-09-20 23:22:30 +05:00
duck
fc6ebb73b8 Use reversed infinite projection for camera 2025-09-20 19:03:36 +05:00
duck
c59d833542 Zig 0.14 => Zig 0.15, STB Image => SDL3 Image 2025-09-20 17:56:15 +05:00
duck
9933a956c2 Some .ignore file fixes 2025-09-20 01:36:26 +05:00
15 changed files with 126 additions and 8082 deletions

View File

@@ -1,5 +1,5 @@
/data/**.png /data/**/*.png
/data/**.bin /data/**/*.bin
/data/**.blend /data/**/*.blend
/data/**.blend1 /data/**/*.blend1
/.git /.git

View File

@@ -6,6 +6,10 @@ const SHADERS: []const []const u8 = &.{
"data/shaders/basic.vert", "data/shaders/basic.vert",
"data/shaders/basic.frag", "data/shaders/basic.frag",
}; };
const SDL_LIBS: []const []const u8 = &.{
"SDL3",
"SDL3_image",
};
pub fn build(b: *Build) void { pub fn build(b: *Build) void {
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
@@ -56,16 +60,16 @@ fn stepBuildClient(
) *Build.Step.Compile { ) *Build.Step.Compile {
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "tabletop_client", .name = "tabletop_client",
.root_source_file = b.path("src/client.zig"), .root_module = b.createModule(.{
.target = target, .root_source_file = b.path("src/client.zig"),
.optimize = optimize, .target = target,
.optimize = optimize,
}),
}); });
exe.root_module.addImport("sdl", sdl_module); exe.root_module.addImport("sdl", sdl_module);
exe.step.dependOn(sdl_step); exe.step.dependOn(sdl_step);
exe.addIncludePath(b.path("lib/clibs"));
return exe; return exe;
} }
@@ -76,9 +80,11 @@ fn stepBuildServer(
) *Build.Step.Compile { ) *Build.Step.Compile {
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "tabletop_server", .name = "tabletop_server",
.root_source_file = b.path("src/server.zig"), .root_module = b.createModule(.{
.target = target, .root_source_file = b.path("src/server.zig"),
.optimize = optimize, .target = target,
.optimize = optimize,
}),
}); });
return exe; return exe;
@@ -93,16 +99,16 @@ fn stepBuildOffline(
) *Build.Step.Compile { ) *Build.Step.Compile {
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "tabletop", .name = "tabletop",
.root_source_file = b.path("src/offline.zig"), .root_module = b.createModule(.{
.target = target, .root_source_file = b.path("src/offline.zig"),
.optimize = optimize, .target = target,
.optimize = optimize,
}),
}); });
exe.root_module.addImport("sdl", sdl_module); exe.root_module.addImport("sdl", sdl_module);
exe.step.dependOn(sdl_step); exe.step.dependOn(sdl_step);
exe.addIncludePath(b.path("lib/clibs"));
return exe; return exe;
} }
@@ -112,11 +118,15 @@ fn stepBuildSdlTranslator(
) *Build.Step.Compile { ) *Build.Step.Compile {
const sdl_translator = b.addExecutable(.{ const sdl_translator = b.addExecutable(.{
.name = "sdl_header_translator", .name = "sdl_header_translator",
.root_source_file = b.path("utils/sdl_translator.zig"), .root_module = b.createModule(.{
.target = target, .root_source_file = b.path("utils/sdl_translator.zig"),
.optimize = .Debug, .target = target,
.optimize = .Debug,
}),
}); });
sdl_translator.linkSystemLibrary("SDL3"); for (SDL_LIBS) |lib| {
sdl_translator.linkSystemLibrary(lib);
}
return sdl_translator; return sdl_translator;
} }
@@ -138,20 +148,25 @@ fn stepSdlModule(
target: Build.ResolvedTarget, target: Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode, optimize: std.builtin.OptimizeMode,
) struct { *Build.Module, *Build.Step } { ) struct { *Build.Module, *Build.Step } {
const sdl_module = b.addModule("sdl", .{ const translate_sdl_step, const sdl_rename = stepTranslateSdl(b, target);
.root_source_file = b.path("lib/sdl.zig"),
const translate_c = b.addTranslateC(.{
.root_source_file = b.path("lib/sdl.h"),
.link_libc = true, .link_libc = true,
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
sdl_module.linkSystemLibrary("SDL3", .{}); translate_c.addIncludePath(sdl_rename.dirname());
const translate_module = translate_c.createModule();
for (SDL_LIBS) |lib| {
translate_module.linkSystemLibrary(lib, .{ .needed = true });
}
const translate_step, const sdl_rename = stepTranslateSdl(b, target); translate_c.step.dependOn(translate_sdl_step);
sdl_module.addIncludePath(sdl_rename.dirname());
return .{ return .{
sdl_module, translate_module,
translate_step, &translate_c.step,
}; };
} }

View File

@@ -13,6 +13,6 @@ layout(set = 1, binding = 1) uniform Object{
void main() { void main() {
gl_Position = vec4(inCoord, 1.0) * object.transform * camera.transform; gl_Position = vec4(inCoord, 1.0) * object.transform * camera.transform;
gl_ClipDistance[0] = gl_Position.z; gl_ClipDistance[0] = gl_Position.w - gl_Position.z;
outUV = inUV; outUV = inUV;
} }

File diff suppressed because it is too large Load Diff

3
lib/sdl.h Normal file
View File

@@ -0,0 +1,3 @@
#include "SDL3/SDL.h"
#include "SDL3_image/SDL_image.h"
#include "sdl_rename.h"

View File

@@ -1,4 +0,0 @@
pub usingnamespace @cImport({
@cInclude("SDL3/SDL.h");
@cInclude("sdl_rename.h");
});

View File

@@ -56,6 +56,7 @@ pub const LoadError = error{
ParsingError, ParsingError,
SdlError, SdlError,
FileTooBig, FileTooBig,
UnsupportedAsset,
} || std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || std.json.ParseError(std.json.Scanner); } || std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || std.json.ParseError(std.json.Scanner);
pub const AssetType = enum { pub const AssetType = enum {
@@ -123,7 +124,7 @@ pub fn AssetContainer(comptime T: type) type {
self.last_state = self.asset_pointer.state; self.last_state = self.asset_pointer.state;
} }
if (self.last_state == .loaded) { if (self.last_state == .loaded) {
self.data_pointer = @alignCast(@ptrCast(self.asset_pointer.data)); self.data_pointer = @ptrCast(@alignCast(self.asset_pointer.data));
return self.data_pointer; return self.data_pointer;
} else return null; } else return null;
}, },
@@ -150,7 +151,7 @@ pub fn AssetContainer(comptime T: type) type {
self.last_state = self.asset_pointer.state; self.last_state = self.asset_pointer.state;
} }
if (self.last_state == .loaded) { if (self.last_state == .loaded) {
self.data_pointer = @alignCast(@ptrCast(self.asset_pointer.data)); self.data_pointer = @ptrCast(@alignCast(self.asset_pointer.data));
} }
continue :sw self.last_state; continue :sw self.last_state;
}, },
@@ -175,6 +176,7 @@ pub fn deinit() void {
if (worker.thread == null) continue; if (worker.thread == null) continue;
worker.thread.?.join(); worker.thread.?.join();
} }
updateFree();
var iter = Assets.asset_map.valueIterator(); var iter = Assets.asset_map.valueIterator();
while (iter.next()) |asset| { while (iter.next()) |asset| {
std.debug.assert(asset.*.counter == 0); std.debug.assert(asset.*.counter == 0);
@@ -203,22 +205,28 @@ pub fn update() void {
Assets.next_worker_update = 0; Assets.next_worker_update = 0;
} }
Assets.free_board_mutex.lock(); updateFree();
defer Assets.free_board_mutex.unlock(); }
if (Assets.free_board.items.len == 0) return;
fn updateFree() void {
// TODO: Delegate freeing to worker threads? // TODO: Delegate freeing to worker threads?
Assets.asset_map_mutex.lock(); Assets.asset_map_mutex.lock();
defer Assets.asset_map_mutex.unlock(); defer Assets.asset_map_mutex.unlock();
Assets.free_board_mutex.lock();
while (Assets.free_board.pop()) |request| { while (Assets.free_board.pop()) |request| {
if (@atomicLoad(usize, &request.counter, .monotonic) == 0) { if (@atomicLoad(usize, &request.counter, .monotonic) == 0) {
if (!Assets.asset_map.remove(.{ .type = request.type, .path = request.path })) continue; if (!Assets.asset_map.remove(.{ .type = request.type, .path = request.path })) continue;
if (request.state == .loaded) if (request.state == .loaded) {
Assets.free_board_mutex.unlock();
request.unload(Game.alloc); request.unload(Game.alloc);
Assets.free_board_mutex.lock();
}
Game.alloc.free(request.path); Game.alloc.free(request.path);
Game.alloc.destroy(request); Game.alloc.destroy(request);
} }
} }
Assets.free_board_mutex.unlock();
} }
pub fn load(comptime asset_type: AssetType, path: []const u8) AssetContainer(asset_type.getType()) { pub fn load(comptime asset_type: AssetType, path: []const u8) AssetContainer(asset_type.getType()) {
@@ -298,8 +306,8 @@ fn makeLoader(comptime T: type, comptime func: *const fn ([]const u8, std.mem.Al
fn makeUnloader(comptime T: type, comptime func: *const fn (T, std.mem.Allocator) void) *const fn (*AssetCell, std.mem.Allocator) void { fn makeUnloader(comptime T: type, comptime func: *const fn (T, std.mem.Allocator) void) *const fn (*AssetCell, std.mem.Allocator) void {
const Container = struct { const Container = struct {
pub fn unloader(cell: *AssetCell, alloc: std.mem.Allocator) void { pub fn unloader(cell: *AssetCell, alloc: std.mem.Allocator) void {
func(@as(*T, @alignCast(@ptrCast(cell.data))).*, alloc); func(@as(*T, @ptrCast(@alignCast(cell.data))).*, alloc);
alloc.destroy(@as(*T, @alignCast(@ptrCast(cell.data)))); alloc.destroy(@as(*T, @ptrCast(@alignCast(cell.data))));
} }
}; };
return Container.unloader; return Container.unloader;

View File

@@ -1,7 +1,6 @@
const std = @import("std"); const std = @import("std");
const sdl = @import("sdl"); const sdl = @import("sdl");
const err = @import("../error.zig"); const err = @import("../error.zig");
const c = @import("../c.zig");
const Assets = @import("../assets.zig"); const Assets = @import("../assets.zig");
const Graphics = @import("../graphics.zig"); const Graphics = @import("../graphics.zig");
@@ -14,26 +13,21 @@ pub fn load(path: []const u8, alloc: std.mem.Allocator) Assets.LoadError!@This()
defer Assets.free(file); defer Assets.free(file);
const data = (file.getSync() catch return error.DependencyError).bytes; const data = (file.getSync() catch return error.DependencyError).bytes;
var width: u32 = undefined; const image: *sdl.Surface = @ptrCast(sdl.IMG_Load_IO(sdl.IOFromConstMem(data.ptr, data.len), true) orelse return error.ParsingError);
var height: u32 = undefined; defer sdl.DestroySurface(image);
var channels: u32 = undefined; const format = image.format;
const image = c.stbi_load_from_memory(
@ptrCast(data), const width: u32 = @intCast(image.w);
@intCast(data.len), const height: u32 = @intCast(image.h);
@ptrCast(&width), const channels: u32 = 4;
@ptrCast(&height),
@ptrCast(&channels), const image_slice = @as([*]u8, @ptrCast(image.pixels))[0..@intCast(width * height * channels)];
4,
);
if (image == null) return error.ParsingError;
defer c.stbi_image_free(image);
const image_slice = image[0..@intCast(width * height * channels)];
if (width > 8192 or height > 8192) return error.FileTooBig; if (width > 8192 or height > 8192) return error.FileTooBig;
const target_format = sdl.GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; const target_format = sdl.GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
const bytes_per_pixel = 4; const bytes_per_pixel = 4;
const mip_level = if(std.math.isPowerOfTwo(width) and width == height) @as(u32, Graphics.MIP_LEVEL) else @as(u32, 1); const mip_level = if (std.math.isPowerOfTwo(width) and width == height) @as(u32, Graphics.MIP_LEVEL) else @as(u32, 1);
const texture = Graphics.createTexture( const texture = Graphics.createTexture(
width, width,
@@ -63,6 +57,43 @@ pub fn load(path: []const u8, alloc: std.mem.Allocator) Assets.LoadError!@This()
defer sdl.EndGPUCopyPass(copy_pass); defer sdl.EndGPUCopyPass(copy_pass);
const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(Graphics.device, transfer_buffer, false) orelse err.sdl()); const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(Graphics.device, transfer_buffer, false) orelse err.sdl());
var pixel: u32 = rows_uploaded * width * bytes_per_pixel;
var mapped: u32 = 0;
while (pixel < (rows_uploaded + rows_to_upload) * width * bytes_per_pixel) {
defer pixel += bytes_per_pixel;
defer mapped += bytes_per_pixel;
switch (format) {
// Convert to RGBA8888
sdl.PIXELFORMAT_ABGR8888 => {
map[mapped + 0] = image_slice[pixel + 3];
map[mapped + 1] = image_slice[pixel + 2];
map[mapped + 2] = image_slice[pixel + 1];
map[mapped + 3] = image_slice[pixel + 0];
},
sdl.PIXELFORMAT_ARGB8888 => {
map[mapped + 0] = image_slice[pixel + 1];
map[mapped + 1] = image_slice[pixel + 2];
map[mapped + 2] = image_slice[pixel + 3];
map[mapped + 3] = image_slice[pixel + 0];
},
sdl.PIXELFORMAT_RGBA8888 => {
map[mapped + 0] = image_slice[pixel + 0];
map[mapped + 1] = image_slice[pixel + 1];
map[mapped + 2] = image_slice[pixel + 2];
map[mapped + 3] = image_slice[pixel + 3];
},
sdl.PIXELFORMAT_BGRA8888 => {
map[mapped + 0] = image_slice[pixel + 2];
map[mapped + 1] = image_slice[pixel + 1];
map[mapped + 2] = image_slice[pixel + 0];
map[mapped + 3] = image_slice[pixel + 3];
},
else => {
sdl.UnmapGPUTransferBuffer(Graphics.device, transfer_buffer);
return error.UnsupportedAsset;
},
}
}
@memcpy(map, image_slice[(rows_uploaded * width * bytes_per_pixel)..((rows_uploaded + rows_to_upload) * width * bytes_per_pixel)]); @memcpy(map, image_slice[(rows_uploaded * width * bytes_per_pixel)..((rows_uploaded + rows_to_upload) * width * bytes_per_pixel)]);
sdl.UnmapGPUTransferBuffer(Graphics.device, transfer_buffer); sdl.UnmapGPUTransferBuffer(Graphics.device, transfer_buffer);

View File

@@ -1,8 +0,0 @@
pub usingnamespace @cImport({
@cDefine("STBI_NO_GIF", "1");
@cDefine("STBI_NO_JPEG", "1");
@cDefine("STBI_NO_HDR", "1");
@cDefine("STBI_NO_TGA", "1");
@cDefine("STB_IMAGE_IMPLEMENTATION", "1");
@cInclude("stb_image.h");
});

View File

@@ -14,7 +14,3 @@ const FileError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.Fil
pub fn file(err: FileError, path: []const u8) noreturn { pub fn file(err: FileError, path: []const u8) noreturn {
std.debug.panic("Error while reading \"{s}\": {any}", .{ path, err }); std.debug.panic("Error while reading \"{s}\": {any}", .{ path, err });
} }
pub fn stbi() noreturn {
std.debug.panic("STBI error!\n", .{});
}

View File

@@ -142,7 +142,6 @@ pub fn create() void {
Graphics.camera = Camera{ Graphics.camera = Camera{
.transform = .{}, .transform = .{},
.near = 1.0 / 16.0, .near = 1.0 / 16.0,
.far = 128.0,
.lens = 1.5, .lens = 1.5,
.aspect = 16.0 / 9.0, .aspect = 16.0 / 9.0,
.matrix = undefined, .matrix = undefined,
@@ -193,7 +192,7 @@ pub fn beginDraw() bool {
.mip_level = 0, .mip_level = 0,
.texture = Graphics.fsaa_target, .texture = Graphics.fsaa_target,
}, 1, &.{ }, 1, &.{
.clear_depth = 1.0, .clear_depth = 0.0,
.load_op = sdl.GPU_LOADOP_CLEAR, .load_op = sdl.GPU_LOADOP_CLEAR,
.store_op = sdl.GPU_STOREOP_DONT_CARE, .store_op = sdl.GPU_STOREOP_DONT_CARE,
.stencil_load_op = sdl.GPU_STOREOP_DONT_CARE, .stencil_load_op = sdl.GPU_STOREOP_DONT_CARE,
@@ -219,7 +218,7 @@ pub fn clearDepth() void {
.mip_level = 0, .mip_level = 0,
.texture = Graphics.fsaa_target, .texture = Graphics.fsaa_target,
}, 1, &.{ }, 1, &.{
.clear_depth = 1.0, .clear_depth = 0.0,
.load_op = sdl.GPU_LOADOP_CLEAR, .load_op = sdl.GPU_LOADOP_CLEAR,
.store_op = sdl.GPU_STOREOP_DONT_CARE, .store_op = sdl.GPU_STOREOP_DONT_CARE,
.stencil_load_op = sdl.GPU_STOREOP_DONT_CARE, .stencil_load_op = sdl.GPU_STOREOP_DONT_CARE,
@@ -282,7 +281,7 @@ fn loadShader(path: []const u8, info: sdl.GPUShaderCreateInfo) *sdl.GPUShader {
const file = std.fs.cwd().openFile(path, .{}) catch |e| err.file(e, path); const file = std.fs.cwd().openFile(path, .{}) catch |e| err.file(e, path);
defer file.close(); defer file.close();
const code = file.readToEndAllocOptions(std.heap.c_allocator, std.math.maxInt(usize), null, 1, 0) catch |e| err.file(e, path); 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); defer std.heap.c_allocator.free(code);
var updated_info = info; var updated_info = info;

View File

@@ -8,7 +8,6 @@ transform: Transform,
/// tangent of the half of the view angle (90 degress = 1 "lens") /// tangent of the half of the view angle (90 degress = 1 "lens")
lens: f32, lens: f32,
near: f32, near: f32,
far: f32,
/// width = height * aspect /// width = height * aspect
aspect: f32, aspect: f32,
@@ -19,14 +18,11 @@ pub fn computeMatrix(camera: *Camera) void {
const xx = 1.0 / (camera.lens * camera.aspect); const xx = 1.0 / (camera.lens * camera.aspect);
const yy = 1.0 / camera.lens; const yy = 1.0 / camera.lens;
const fnmod = 1.0 / (camera.far - camera.near);
const zz = camera.far * fnmod;
const wz = -camera.near * camera.far * fnmod;
const projection = @Vector(16, f32){ const projection = @Vector(16, f32){
xx, 0, 0, 0, xx, 0, 0, 0,
0, yy, 0, 0, 0, yy, 0, 0,
0, 0, -zz, wz, 0, 0, 0, camera.near,
0, 0, -1, 0, 0, 0, -1, 0,
}; };
camera.matrix = Transform.multiplyMatrix(projection, camera.transform.inverseMatrix()); camera.matrix = Transform.multiplyMatrix(projection, camera.transform.inverseMatrix());
} }
@@ -61,9 +57,9 @@ pub fn mouse_in_quad(camera: Camera, mouse: @Vector(2, f32), quad_transform: Tra
const hh = height * 0.5; const hh = height * 0.5;
const pi: [4]@Vector(2, f32) = .{ const pi: [4]@Vector(2, f32) = .{
.{ -hw, -hh }, .{ -hw, -hh },
.{ -hw, hh }, .{ -hw, hh },
.{ hw, hh }, .{ hw, hh },
.{ hw, -hh }, .{ hw, -hh },
}; };
var po: [4]@Vector(2, f32) = undefined; var po: [4]@Vector(2, f32) = undefined;
for (0..4) |i| { for (0..4) |i| {

View File

@@ -12,7 +12,7 @@ pub const BLEND_NORMAL = sdl.GPUColorTargetBlendState{
}; };
pub const DEPTH_ENABLED = sdl.GPUDepthStencilState{ pub const DEPTH_ENABLED = sdl.GPUDepthStencilState{
.compare_op = sdl.GPU_COMPAREOP_LESS, .compare_op = sdl.GPU_COMPAREOP_GREATER,
.enable_depth_test = true, .enable_depth_test = true,
.enable_depth_write = true, .enable_depth_write = true,
}; };

View File

@@ -459,7 +459,7 @@ pub fn draw() void {
); );
Graphics.drawObject(&World.cubemap, .{ Graphics.drawObject(&World.cubemap, .{
.scale = Graphics.camera.far, .scale = 1e20,
.position = Graphics.camera.transform.position, .position = Graphics.camera.transform.position,
}); });
Graphics.clearDepth(); Graphics.clearDepth();

View File

@@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const sdl = @cImport({ const sdl = @cImport({
@cInclude("SDL3/SDL.h"); @cInclude("SDL3/SDL.h");
@cInclude("SDL3_image/SDL_image.h");
}); });
const PREFIX = "SDL_"; const PREFIX = "SDL_";
@@ -23,12 +24,8 @@ pub fn main() !void {
try std.fs.cwd().createFile(output, .{}); try std.fs.cwd().createFile(output, .{});
defer file.close(); defer file.close();
const writer = file.writer(); var writer_buffer: [4096]u8 = undefined;
var writer = file.writer(&writer_buffer);
const stdout = std.io.getStdOut();
defer stdout.close();
const out = stdout.writer();
var renamed_count: usize = 0; var renamed_count: usize = 0;
for (@typeInfo(sdl).@"struct".decls) |decl| { for (@typeInfo(sdl).@"struct".decls) |decl| {
@@ -36,7 +33,7 @@ pub fn main() !void {
const new_name: []const u8 = decl.name[PREFIX.len..]; const new_name: []const u8 = decl.name[PREFIX.len..];
try writer.print( try writer.interface.print(
\\#define {1s} {0s} \\#define {1s} {0s}
\\ \\
, .{ decl.name, new_name }); , .{ decl.name, new_name });
@@ -45,5 +42,4 @@ pub fn main() !void {
if (renamed_count == 0) { if (renamed_count == 0) {
@panic("No SDL definitions renamed"); @panic("No SDL definitions renamed");
} }
try out.print("[SDL Translator] {} SDL definitions renamed\n", .{renamed_count});
} }