From 8cdac4e2e7940bbdad04932047eca68040014f75 Mon Sep 17 00:00:00 2001 From: Peter Li Date: Mon, 14 Apr 2025 23:58:08 -0700 Subject: [PATCH] added gltf mesh loader back --- engine/core/src/engine.zig | 79 +++--- engine/core/src/panickers.zig | 12 +- engine/platform/src/windowing.zig | 5 +- engine/rend/build.zig | 1 + engine/rend/build.zig.zon | 2 +- engine/rend/src/meshes/gltfLoader.zig | 333 ++++++++++++++++++++++++++ engine/rend/src/meshes/meshPool.zig | 0 engine/rend/src/rend.zig | 4 +- engine/rend/tests/tests.zig | 4 + lib/sdl3/test.json | 1 + lib/sdl3/test.msl | 0 lib/zgltf/src/main.zig | 2 +- projects/sampleGame/main.zig | 8 +- 13 files changed, 396 insertions(+), 55 deletions(-) create mode 100644 engine/rend/src/meshes/gltfLoader.zig create mode 100644 engine/rend/src/meshes/meshPool.zig create mode 100644 lib/sdl3/test.json create mode 100644 lib/sdl3/test.msl diff --git a/engine/core/src/engine.zig b/engine/core/src/engine.zig index 51e2d8e..ce3bdae 100644 --- a/engine/core/src/engine.zig +++ b/engine/core/src/engine.zig @@ -277,43 +277,7 @@ pub const Engine = struct { tracy.SetThreadName("Systems Thread"); - var exitSignaled: bool = false; - - if (ctx.engine.platformSetupFunc) |setupFunc| { - setupFunc(ctx.engine.platformCtx) catch unreachable; - } - - if (ctx.engine.rendererSetupFunc) |setupFunc| { - setupFunc(ctx.engine.rendererCtx) catch unreachable; - } - - while (true) { - ctx.engine.tick() catch unreachable; - - if (!exitSignaled and ctx.engine.exitSignal.load(.seq_cst)) { - exitSignaled = true; - core.engine_logs("Processing exit signals"); - - for (ctx.engine.exitListeners.items) |ref| { - ref.vtable.exitSignal_func.?(ref.ptr) catch unreachable; - } - } - - if (exitSignaled) { - var readyToExit: bool = true; - for (ctx.engine.exitListeners.items) |pending| { - if (!pending.vtable.readyToExit_func.?(pending.ptr)) { - readyToExit = false; - } - } - - if (readyToExit) { - break; - } - } - } - - ctx.engine.exitConfirmed.store(true, .seq_cst); + _ = ctx; } }; @@ -338,11 +302,46 @@ pub const Engine = struct { } fn mainLoop(self: *@This()) !void { - while (!self.exitConfirmed.load(.acquire)) { + var exitSignaled: bool = false; + + if (self.platformSetupFunc) |setupFunc| { + setupFunc(self.platformCtx) catch unreachable; + } + + if (self.rendererSetupFunc) |setupFunc| { + setupFunc(self.rendererCtx) catch unreachable; + } + + while (true) { + self.tick() catch unreachable; self.jobManager.bump(); - std.time.sleep(1000 * 1000); // 1ms delay between polling functions, effectively limits input to 1khz. - // (there is a noticable power consumption draw on laptops and battery based systems if this is unlimited) try self.nfdRuntime.processMessages(); + + if (!exitSignaled and self.exitSignal.load(.seq_cst)) { + exitSignaled = true; + core.engine_logs("Processing exit signals"); + + for (self.exitListeners.items) |ref| { + ref.vtable.exitSignal_func.?(ref.ptr) catch unreachable; + } + } + + if (exitSignaled) { + var readyToExit: bool = true; + core.engine_logs("checking everything is ready to exit"); + for (self.exitListeners.items) |pending| { + core.engine_log("checking everything is ready to exit {any}", .{pending}); + if (!pending.vtable.readyToExit_func.?(pending.ptr)) { + readyToExit = false; + } + } + + if (readyToExit) { + core.engine_logs("exiting"); + self.exitConfirmed.store(true, .seq_cst); + break; + } + } } } diff --git a/engine/core/src/panickers.zig b/engine/core/src/panickers.zig index 096b895..f3d2c29 100644 --- a/engine/core/src/panickers.zig +++ b/engine/core/src/panickers.zig @@ -81,7 +81,7 @@ pub fn dumpStackPointerAddr(prefix: []const u8) void { std.debug.print("{} sp = 0x{x}\n", .{ prefix, sp }); } -fn dumpSegfaultInfoPosix(sig: i32, addr: usize, ctx_ptr: ?*const anyopaque) void { +fn dumpSegfaultInfoPosix(sig: i32, addr: usize, ctx_ptr: ?*anyopaque) void { const stderr = std.io.getStdErr().writer(); _ = switch (sig) { std.posix.SIG.SEGV => stderr.print("Segmentation fault at address 0x{x}\n", .{addr}), @@ -97,14 +97,14 @@ fn dumpSegfaultInfoPosix(sig: i32, addr: usize, ctx_ptr: ?*const anyopaque) void .arm, .aarch64, => { - const ctx: *const std.c.ucontext_t = @ptrCast(@alignCast(ctx_ptr)); + const ctx: *std.c.ucontext_t = @ptrCast(@alignCast(ctx_ptr)); std.debug.dumpStackTraceFromBase(ctx); }, else => {}, } } -fn handleSegfaultPosix(sig: i32, info: *const std.posix.siginfo_t, ctx_ptr: ?*const anyopaque) callconv(.C) noreturn { +fn handleSegfaultPosix(sig: i32, info: *const std.posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.C) noreturn { core.engine_logs("PANIC!!"); core.forceFlush(); resetSegfaultHandler(); @@ -160,7 +160,7 @@ fn resetSegfaultHandler() void { .flags = 0, }; // To avoid a double-panic, do nothing if an error happens here. - std.debug.updateSegfaultHandler(&act) catch {}; + std.debug.updateSegfaultHandler(&act); } // my custom version of segfault handler. @@ -177,7 +177,5 @@ pub fn attachSegfaultHandler() void { .flags = (std.posix.SA.SIGINFO | std.posix.SA.RESTART | std.posix.SA.RESETHAND), }; - std.debug.updateSegfaultHandler(&act) catch { - @panic("unable to install segfault handler, maybe adjust have_segfault_handling_support in std/debug.zig"); - }; + std.debug.updateSegfaultHandler(&act); } diff --git a/engine/platform/src/windowing.zig b/engine/platform/src/windowing.zig index bbe1dbc..3aedecf 100644 --- a/engine/platform/src/windowing.zig +++ b/engine/platform/src/windowing.zig @@ -108,10 +108,7 @@ pub const PlatformInstance = struct { pub fn setupWindow(opaqueSelf: *anyopaque) core.EngineDataEventError!void { const self: *@This() = @alignCast(@ptrCast(opaqueSelf)); - sdl3.init(.{ - .video = true, - .gamepad = true, - }) catch return error.BadInit; + sdl3.init(.{ .video = true, .gamepad = true }) catch return error.BadInit; self.window = sdl3.c.SDL_CreateWindow(self.windowName, self.windowExtent.x, self.windowExtent.y, 0).?; // resizeable } diff --git a/engine/rend/build.zig b/engine/rend/build.zig index 5af8941..e4aedbd 100644 --- a/engine/rend/build.zig +++ b/engine/rend/build.zig @@ -7,6 +7,7 @@ const dependencyList = [_][]const u8{ "platform", "shaderTypes", "objLoader", + "zgltf", "ozz", }; diff --git a/engine/rend/build.zig.zon b/engine/rend/build.zig.zon index 50e2fdb..102ade7 100644 --- a/engine/rend/build.zig.zon +++ b/engine/rend/build.zig.zon @@ -9,7 +9,7 @@ .sdl3 = .{ .path = "../../lib/sdl3" }, .ozz = .{ .path = "../../lib/ozz" }, .shaderTypes = .{ .path = "../../lib/sdl3/shaderTypes" }, - .cgltf = .{ .path = "../../lib/cgltf" }, + .zgltf = .{ .path = "../../lib/zgltf" }, .objLoader = .{ .path = "../../lib/objLoader" }, }, .paths = .{ diff --git a/engine/rend/src/meshes/gltfLoader.zig b/engine/rend/src/meshes/gltfLoader.zig new file mode 100644 index 0000000..b41b5a0 --- /dev/null +++ b/engine/rend/src/meshes/gltfLoader.zig @@ -0,0 +1,333 @@ +pub fn loadIndexedMeshForPoolingGltf(allocator: std.mem.Allocator, meshName: core.Name, skeletonName: ?core.Name, path: []const u8) !MeshUpdate { + const file = try core.fs().loadFile(path); + defer core.fs().unmap(file); + + const gcAllocator = allocator; + + // const allocator = gMeshPoolBuffer.allocator; + + var parser = zgltf.init(allocator); + defer parser.deinit(); + + const ext = core.getFileExtension(path); + if (std.mem.eql(u8, ".gltf", ext)) { + try parser.parse(@alignCast(file.bytes[0 .. file.bytes.len - 1])); + } else { + try parser.parse(@alignCast(file.bytes)); + } + + // std.debug.print("\n", .{}); + // parser.debugPrint(); + + if (parser.data.meshes.items.len > 1) { + return error.OnlyOneMeshPerGltfImplemented; + } + + if (parser.data.skins.items.len > 1) { + return error.TooManySkins; + } + + var binaryFile: ?core.packer.PackerBytesMapping = null; + var binaryBytes: []const u8 = undefined; + + if (std.mem.eql(u8, ext, ".glb")) { + binaryBytes = parser.glb_binary.?; + } else { + const binaryPath = try std.fmt.allocPrint(allocator, "{s}bin", .{path[0 .. path.len - 4]}); + defer allocator.free(binaryPath); + core.engine_log("{s}", .{binaryPath}); + binaryFile = try core.fs().loadFile(binaryPath); + binaryBytes = binaryFile.?.bytes; + } + + defer if (binaryFile) |f| core.fs().unmap(f); + + const m = parser.data.meshes.items[0]; + core.engine_log("mesh name {s} number of primitives = {d}", .{ m.name, m.primitives.items.len }); + + var positions = std.ArrayList(f32).init(allocator); + defer positions.deinit(); + + var texcoords = std.ArrayList(f32).init(allocator); + defer texcoords.deinit(); + + var normals = std.ArrayList(f32).init(allocator); + defer normals.deinit(); + + var joints = std.ArrayList(u16).init(allocator); + defer joints.deinit(); + // add a different joint format one, todo- i need to fix up zgltf + + var useJoints8: bool = false; + var joints8 = std.ArrayList(u8).init(allocator); + defer joints8.deinit(); + + var weights = std.ArrayList(f32).init(allocator); + defer weights.deinit(); + + var weightCount: usize = 4; + + if (m.primitives.items.len > 1) { + @panic("sorry, havent implemented support for multiple primitives yet, would require more work on the way i handle materials"); + } + + var indexList = std.ArrayList(u32).init(allocator); + for (m.primitives.items) |primitive| { + if (primitive.indices) |indices| { + const accessor = parser.data.accessors.items[indices]; + // core.engine_log("index accessor info: {any}", .{accessor}); + + if (accessor.component_type == .unsigned_short) { + var temp = std.ArrayList(u16).init(allocator); + defer temp.deinit(); + parser.getDataFromBufferView(u16, &temp, accessor, @alignCast(binaryBytes)); + for (temp.items) |t| { + try indexList.append(@intCast(t)); + } + } else if (accessor.component_type == .unsigned_integer) { + parser.getDataFromBufferView(u32, &indexList, accessor, @alignCast(binaryBytes)); + } + } + + for (primitive.attributes.items) |attribute| { + // core.engine_log("attribute: {any}", .{attribute}); + + switch (attribute) { + .position => |x| { + const accessor = parser.data.accessors.items[x]; + // core.engine_log("accessor info: {any}", .{accessor}); + + parser.getDataFromBufferView(f32, &positions, accessor, @alignCast(binaryBytes)); + // core.engine_log("positions loaded: {d}", .{positions.items.len}); + }, + .normal => |x| { + const accessor = parser.data.accessors.items[x]; + // core.engine_log("accessor info: {any}", .{accessor}); + + parser.getDataFromBufferView(f32, &normals, accessor, @alignCast(binaryBytes)); + // core.engine_log("normals loaded: {d}", .{normals.items.len}); + }, + .texcoord => |x| { + const accessor = parser.data.accessors.items[x]; + // core.engine_log("accessor info: {any}", .{accessor}); + + parser.getDataFromBufferView(f32, &texcoords, accessor, @alignCast(binaryBytes)); + // core.engine_log("texcoords loaded: {d}", .{texcoords.items.len}); + }, + .joints => |x| { + const accessor = parser.data.accessors.items[x]; + // core.engine_log("accessor info: {any} acecssor index {d}", .{ accessor, x }); + + if (accessor.component_type == .unsigned_byte) { + useJoints8 = true; + parser.getDataFromBufferView(u8, &joints8, accessor, @alignCast(binaryBytes)); + // core.engine_log("joints8 loaded: {d} - {d} {d} {d} {d}", .{ joints8.items.len, joints8.items[0], joints8.items[1], joints8.items[2], joints8.items[3] }); + } else { + parser.getDataFromBufferView(u16, &joints, accessor, @alignCast(binaryBytes)); + // core.engine_log("joints loaded: {d} - {d} {d} {d} {d}", .{ joints.items.len, joints.items[0], joints.items[1], joints.items[2], joints.items[3] }); + } + }, + .weights => |x| { + const accessor = parser.data.accessors.items[x]; + // core.engine_log("accessor info: {any}", .{accessor}); + + parser.getDataFromBufferView(f32, &weights, accessor, @alignCast(binaryBytes)); + + if (accessor.type == .vec3) { + weightCount = 3; + } + + // core.engine_log("weights loaded: {d} - {d} {d} {d} {d}", .{ weights.items.len, weights.items[0], weights.items[1], weights.items[2], weights.items[3] }); + }, + .tangent => |x| { + const accessor = parser.data.accessors.items[x]; + core.engine_log("accessor info: {any} NOT PARSED", .{accessor}); + }, + .color => |x| { + const accessor = parser.data.accessors.items[x]; + core.engine_log("accessor info: {any} NOT PARSED", .{accessor}); + }, + } + } + } + + if (parser.data.skins.items.len > 1) { + @panic("too many skins, not supported"); + } + + var jointNameList: std.ArrayList(JointNameEntry) = std.ArrayList(JointNameEntry).init(allocator); + + if (weights.items.len > 0) { + core.engine_log("skin found, building joint map", .{}); + if (parser.data.skins.items[0].skeleton) |skeletonIndex| { + for (parser.data.nodes.items[skeletonIndex..], 0..) |node, i| { + // core.engine_log("gltf: {s} -> {d} (skeleton index)", .{ node.name, i }); + // const gcAllocator = graphics.getContext().allocator; + try jointNameList.append(.{ .index = @intCast(i), .name = try gcAllocator.dupe(u8, node.name) }); + } + } else { + if (parser.data.skins.items[0].joints.items.len > 0) { + for (parser.data.skins.items[0].joints.items, 0..) |i, j| { + const node = parser.data.nodes.items[i]; + // core.engine_log("gltf: {s} -> {d} (joints map)", .{ node.name, j }); + // const gcAllocator = graphics.getContext().allocator; + try jointNameList.append(.{ .index = @intCast(j), .name = try gcAllocator.dupe(u8, node.name) }); + } + } else { + for (parser.data.nodes.items, 0..) |node, i| { + // core.engine_log("gltf: {s} -> {d} (fallback)", .{ node.name, i }); + // const gcAllocator = graphics.getContext().allocator; + try jointNameList.append(.{ .index = @intCast(i), .name = try gcAllocator.dupe(u8, node.name) }); + } + } + } + } + + var vertexList = std.ArrayList(MeshVertex).init(allocator); + + var i: usize = 0; + const vertexCount = positions.items.len / 3; + while (i < vertexCount) : (i += 1) { + const normalIndex = i * 3; + const positionIndex = i * 3; + const uvIndex = i * 2; + + const uv: core.Vector2f = if (uvIndex < texcoords.items.len) .{ + .x = texcoords.items[uvIndex], + .y = texcoords.items[uvIndex + 1], + } else core.Vector2f{}; + + const normal = if (normalIndex < normals.items.len) core.Vectorf{ + .x = normals.items[i], + .y = normals.items[i + 1], + .z = normals.items[i + 2], + } else core.Vectorf{}; + + try vertexList.append(.{ + .position = .{ + .x = positions.items[positionIndex], + .y = positions.items[positionIndex + 1], + .z = positions.items[positionIndex + 2], + }, + .normal = normal, + .color = .{}, + .uv = uv, + }); + + const jointsIndex = weightCount * i; + if (weightCount == 4) { + if (useJoints8) { + if (jointsIndex < joints8.items.len) { + vertexList.items[vertexList.items.len - 1].bones = .{ + @intCast(joints8.items[jointsIndex + 0]), + @intCast(joints8.items[jointsIndex + 1]), + @intCast(joints8.items[jointsIndex + 2]), + @intCast(joints8.items[jointsIndex + 3]), + }; + vertexList.items[vertexList.items.len - 1].weights = .{ + @intFromFloat(weights.items[jointsIndex + 0] * 255), + @intFromFloat(weights.items[jointsIndex + 1] * 255), + @intFromFloat(weights.items[jointsIndex + 2] * 255), + @intFromFloat(weights.items[jointsIndex + 3] * 255), + }; + } + } else { + if (jointsIndex < joints.items.len) { + vertexList.items[vertexList.items.len - 1].bones = .{ + @intCast(joints.items[jointsIndex + 0]), + @intCast(joints.items[jointsIndex + 1]), + @intCast(joints.items[jointsIndex + 2]), + @intCast(joints.items[jointsIndex + 3]), + }; + vertexList.items[vertexList.items.len - 1].weights = .{ + @intFromFloat(weights.items[jointsIndex + 0] * 255), + @intFromFloat(weights.items[jointsIndex + 1] * 255), + @intFromFloat(weights.items[jointsIndex + 2] * 255), + @intFromFloat(weights.items[jointsIndex + 3] * 255), + }; + } + } + } else { + return error.NotImplementedYet; + } + } + + if (indexList.items.len == 0) { + for (0..vertexList.items.len) |x| { + try indexList.append(@intCast(x)); + } + } + + const rv: MeshUpdate = .{ + .new = .{ + .vertices = try vertexList.toOwnedSlice(), + .indices = try indexList.toOwnedSlice(), + .jointNames = try jointNameList.toOwnedSlice(), + .skeletonName = skeletonName, + .name = meshName, + }, + }; + + core.graphics_log("[{s}] gltf loaded vertex count vertices={d} indices={d}", .{ path, rv.new.vertices.len, rv.new.indices.len }); + + // try gMeshPoolBuffer.updateRequests.pushLocked(rv); + return rv; +} + +pub const MeshPoolCreationSettings = struct { + vertexCount: u32 = 4_000_000, + indexCount: u32 = 16_000_000, +}; + +pub const MeshUpdate = union(enum(u8)) { + new: struct { + vertices: []MeshVertex, + indices: []u32, + jointNames: []JointNameEntry, + name: core.Name, + skeletonName: ?core.Name, + }, + + free: struct { + vertices: core.Span, + indices: core.Span, + name: core.Name, + }, + + pub fn deinit(self: @This(), allocator: std.mem.Allocator) void { + switch (self) { + .new => |new| { + allocator.free(new.vertices); + allocator.free(new.indices); + for (new.jointNames) |entry| { + entry.deinit(allocator); + } + allocator.free(new.jointNames); + }, + .free => {}, + } + } +}; + +pub const JointNameEntry = struct { + name: []u8 = undefined, + index: u32 = 0, + + pub fn deinit(self: @This(), allocator: std.mem.Allocator) void { + // const allocator = graphics.getContext().allocator; + allocator.free(self.name); + } +}; + +pub const MeshVertex = extern struct { + position: core.Vectorf = .{}, + normal: core.Vectorf = .{}, + color: core.colors.Color = .{}, + uv: core.Vector2f = .{}, + bones: [4]u8 = .{ 0, 0, 0, 0 }, + weights: [4]u8 = .{ 0, 0, 0, 0 }, +}; + +const core = @import("core"); +const std = @import("std"); +const zgltf = @import("zgltf"); diff --git a/engine/rend/src/meshes/meshPool.zig b/engine/rend/src/meshes/meshPool.zig new file mode 100644 index 0000000..e69de29 diff --git a/engine/rend/src/rend.zig b/engine/rend/src/rend.zig index 703431f..ad6d6a0 100644 --- a/engine/rend/src/rend.zig +++ b/engine/rend/src/rend.zig @@ -1,7 +1,9 @@ const std = @import("std"); -const core = @import("core"); +pub const core = @import("core"); const sgpu_renderer = @import("sgpu/renderer.zig"); +pub const gltfLoader = @import("meshes/gltfLoader.zig"); + // controls glfw and general windowing // graphics depends on this one diff --git a/engine/rend/tests/tests.zig b/engine/rend/tests/tests.zig index deb5330..d7bbf87 100644 --- a/engine/rend/tests/tests.zig +++ b/engine/rend/tests/tests.zig @@ -1 +1,5 @@ +const rend = @import("rend"); +const core = rend.core; +const std = @import("std"); + test "this does nothing" {} diff --git a/lib/sdl3/test.json b/lib/sdl3/test.json new file mode 100644 index 0000000..1270c53 --- /dev/null +++ b/lib/sdl3/test.json @@ -0,0 +1 @@ +{ "samplers": 0, "storage_textures": 0, "storage_buffers": 1, "uniform_buffers": 0 } diff --git a/lib/sdl3/test.msl b/lib/sdl3/test.msl new file mode 100644 index 0000000..e69de29 diff --git a/lib/zgltf/src/main.zig b/lib/zgltf/src/main.zig index ab7157d..2095c9f 100644 --- a/lib/zgltf/src/main.zig +++ b/lib/zgltf/src/main.zig @@ -1321,7 +1321,7 @@ fn parseIndex(component: json.Value) usize { // floating numbers. fn parseFloat(comptime T: type, component: json.Value) T { const type_info = @typeInfo(T); - if (type_info != .Float) { + if (type_info != .float) { panic( "Given type '{any}' is not a floating number.", .{type_info}, diff --git a/projects/sampleGame/main.zig b/projects/sampleGame/main.zig index b649d0b..cb3ef0a 100644 --- a/projects/sampleGame/main.zig +++ b/projects/sampleGame/main.zig @@ -11,7 +11,6 @@ pub fn init(allocator: std.mem.Allocator) !*@This() { } pub fn prepare_game(self: *@This()) !void { - _ = self; try core.fs().addContentPath("sampleGame"); try script.loadTypes("scripts"); try script.runScriptFile("scripts/prepare.lua"); @@ -20,6 +19,12 @@ pub fn prepare_game(self: *@This()) !void { exitInput.addKey(.escape, .keyDown); _ = exitInput.data.addListener(null, onExit); exitInput.activate(); + + const results = try rend.gltfLoader.loadIndexedMeshForPoolingGltf(self.allocator, core.MakeName("test"), null, "gltf-samples/Fox/glTF/Fox.gltf"); + defer results.deinit(self.allocator); + + core.engine_log("loaded fox, vertices: {d}", .{results.new.vertices.len}); + core.engine_log("loaded fox, indices: {d}", .{results.new.indices.len}); } pub fn onExit(ctx: ?*anyopaque, action: core.ActionEvent) void { @@ -50,4 +55,5 @@ pub fn main() anyerror!void { const std = @import("std"); const backlog = @import("Backlog"); const core = backlog.core; +const rend = backlog.rend; const script = core.script;