From b9f3f759729d8433a1957553e5b4dbfb50afebb6 Mon Sep 17 00:00:00 2001 From: Peter Li Date: Fri, 18 Apr 2025 10:49:49 -0700 Subject: [PATCH] Mesh pool storage + mesh asset loader interfaces --- build.zig | 1 - engine/assets/src/asset_references.zig | 19 ++-- engine/audio/src/audio.zig | 1 + engine/backlog.zig | 14 +-- engine/core/src/engine.zig | 18 +++- engine/core/src/engineObject.zig | 67 ++++++++++++-- engine/core/src/logging.zig | 4 +- engine/rend/build.zig | 1 + engine/rend/src/meshes/MeshComponent.zig | 33 +++++++ engine/rend/src/meshes/meshes.zig | 40 +++++++- engine/rend/src/meshes/objLoader.zig | 97 +++++++++++++++++++ engine/rend/src/rend.zig | 11 ++- engine/rend/src/sgpu/MeshAssetLoader.zig | 75 +++++++++++++++ engine/rend/src/sgpu/mesh-pool.zig | 113 ++++++++++++++++++++--- engine/rend/src/sgpu/renderer.zig | 57 ++++++++---- lib/objLoader/src/obj_loader.zig | 12 +-- lib/sdl3/src/sdl3.zig | 4 + lib/tracy/build.zig | 2 +- projects/sampleGame/main.zig | 28 ++++-- projects/sampleGame/scripts/prepare.lua | 2 +- 20 files changed, 521 insertions(+), 78 deletions(-) create mode 100644 engine/rend/src/meshes/MeshComponent.zig create mode 100644 engine/rend/src/meshes/objLoader.zig create mode 100644 engine/rend/src/sgpu/MeshAssetLoader.zig diff --git a/build.zig b/build.zig index 9c35304..c8f130e 100644 --- a/build.zig +++ b/build.zig @@ -1,4 +1,3 @@ -//! // MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=1 -- use this if you're getting that odd crash on mac b: *std.Build, nw_builder: *std.Build, diff --git a/engine/assets/src/asset_references.zig b/engine/assets/src/asset_references.zig index a3458d5..d7185a7 100644 --- a/engine/assets/src/asset_references.zig +++ b/engine/assets/src/asset_references.zig @@ -87,7 +87,7 @@ pub const AssetLoaderInterface = struct { assetType: []const u8, loadAsset: *const fn (*anyopaque, AssetRef, ?AssetPropertiesBag) AssetLoaderError!void, - destroy: *const fn (*anyopaque, std.mem.Allocator) void, + // destroy: *const fn (*anyopaque, std.mem.Allocator) void, discardAll: *const fn (*anyopaque) void, pub fn from(comptime assetType: []const u8, comptime TargetType: type) @This() { @@ -131,7 +131,6 @@ pub const AssetLoaderInterface = struct { .typeAlign = @alignOf(TargetType), .loadAsset = wrappedFuncs.loadAsset, .discardAll = wrappedFuncs.discardAll, - .destroy = wrappedFuncs.destroy, .assetType = assetType, }; @@ -151,10 +150,6 @@ pub const AssetLoaderRef = struct { pub fn discardAll(self: @This()) void { self.vtable.discardAll(self.target); } - - pub fn destroy(self: *@This(), allocator: std.mem.Allocator) void { - self.vtable.destroy(self.target, allocator); - } }; pub const AssetReferenceSys = struct { @@ -178,6 +173,7 @@ pub const AssetReferenceSys = struct { .target = loader, .size = @sizeOf(@TypeOf(loader)), }); + core.engine_log("asset loader for asset type ({s}) registered", .{assetType.utf8()}); } pub fn loadRef(self: *@This(), asset: AssetRef, propertiesBag: ?AssetPropertiesBag) !void { @@ -201,10 +197,13 @@ pub const AssetReferenceSys = struct { } pub fn deinit(self: *@This()) void { - var iter = self.loaders.valueIterator(); - while (iter.next()) |i| { - i.destroy(self.allocator); - } + // the loader system shall no longer + // control lifetimes for assset loaders + // each asset loader will be responsible for its own lifetime + // var iter = self.loaders.valueIterator(); + // while (iter.next()) |i| { + // i.destroy(self.allocator); + // } self.loaders.deinit(self.allocator); } }; diff --git a/engine/audio/src/audio.zig b/engine/audio/src/audio.zig index f0b571d..008aa7d 100644 --- a/engine/audio/src/audio.zig +++ b/engine/audio/src/audio.zig @@ -21,6 +21,7 @@ pub fn start_module(comptime programSpec: anytype, args: anytype, allocator: std } gSoundEngine = core.gEngine.createObject(NeonSoundEngine, .{ .can_tick = true }) catch unreachable; + gSoundLoader = allocator.create(soundEngine.SoundLoader) catch unreachable; gSoundLoader.* = soundEngine.SoundLoader.init(gSoundEngine); diff --git a/engine/backlog.zig b/engine/backlog.zig index 82908ce..bfe935c 100644 --- a/engine/backlog.zig +++ b/engine/backlog.zig @@ -37,10 +37,16 @@ var shutdownModuleNames: std.ArrayListUnmanaged([]const u8) = .{}; pub fn start_modules(comptime programSpec: anytype, maybeArgs: ?NwArgs, allocator: std.mem.Allocator) !void { const Backlog = @This(); + var z = core.tracy.ZoneN(@src(), "Starting all Modules"); + defer z.End(); + inline for (modulelist) |feature| { if (@hasDecl(Backlog, feature)) { const Struct = @field(Backlog, feature); if (comptime core.isModuleEnabled(Struct.Module, programSpec)) { + var z1 = core.tracy.ZoneN(@src(), @ptrCast("Initializing Module")); + defer z1.End(); + core.tracy.Message(Struct.Module.name); if (maybeArgs) |args| { try Struct.start_module(programSpec, args, allocator); } else { @@ -84,13 +90,7 @@ pub fn run_everything(comptime GameContext: type) !void { canTick = true; } - var gameContext = try core.createObject(GameContext, .{ .can_tick = canTick }); - - if (@hasDecl(GameContext, "prepare_game")) { - gameContext.prepare_game() catch @panic("Unable to run base level prepare script"); - } else if (@hasDecl(GameContext, "prepare")) { - gameContext.prepare() catch @panic("Unable to run base level prepare script"); - } + _ = try core.createObject(GameContext, .{ .can_tick = canTick }); try core.gEngine.run(); diff --git a/engine/core/src/engine.zig b/engine/core/src/engine.zig index ce3bdae..91a67a6 100644 --- a/engine/core/src/engine.zig +++ b/engine/core/src/engine.zig @@ -55,6 +55,8 @@ pub const Engine = struct { destroyListSimple: ArrayListUnmanaged(EngineObjectRef) = .{}, destroyListCore: ArrayListUnmanaged(EngineObjectRef) = .{}, + prepares: ArrayListUnmanaged(EngineObjectRef) = .{}, + lastEngineTime: f64, deltaTime: f64, // delta time for this frame from the previous frame frameNumber: u64, @@ -127,6 +129,7 @@ pub const Engine = struct { self.eventors.deinit(self.allocator); core.engine_logs("destroying tickables"); + self.prepares.deinit(self.allocator); self.tickables.deinit(self.allocator); self.preTickables.deinit(self.allocator); self.nfdRuntime.destroy(); @@ -169,6 +172,10 @@ pub const Engine = struct { try self.renderers.append(self.allocator, newObjectRef); } + if (@hasDecl(T, "prepare")) { + try self.prepares.append(self.allocator, newObjectRef); + } + if (@hasDecl(T, "preTick")) { try self.preTickables.append(self.allocator, newObjectRef); } @@ -308,8 +315,15 @@ pub const Engine = struct { setupFunc(self.platformCtx) catch unreachable; } - if (self.rendererSetupFunc) |setupFunc| { - setupFunc(self.rendererCtx) catch unreachable; + { + var z = core.tracy.Zone(@src()); + for (self.prepares.items) |prep| { + var z1 = core.tracy.ZoneN(@src(), "Preparing game"); + core.tracy.Message(prep.vtable.typeName); + prep.vtable.prepare_func.?(prep.ptr) catch unreachable; + z1.End(); + } + z.End(); } while (true) { diff --git a/engine/core/src/engineObject.zig b/engine/core/src/engineObject.zig index b236743..7c6405c 100644 --- a/engine/core/src/engineObject.zig +++ b/engine/core/src/engineObject.zig @@ -51,23 +51,59 @@ pub const EngineObjectVTable = struct { exitSignal_func: ?*const fn (*anyopaque) EngineDataEventError!void = null, readyToExit_func: ?*const fn (*anyopaque) bool = null, + prepare_func: ?*const fn (*anyopaque) EngineDataEventError!void = null, + pub fn from(comptime TargetType: type) EngineObjectVTable { - const wrappedInit = struct { - const funcFind: @TypeOf(@field(TargetType, "init")) = @field(TargetType, "init"); - - pub fn func(allocator: std.mem.Allocator) EngineDataEventError!*anyopaque { - const newObject = funcFind(allocator) catch return error.BadInit; - return @as(*anyopaque, @ptrCast(newObject)); - } - }; - var self = EngineObjectVTable{ .typeName = @typeName(TargetType), .typeSize = @sizeOf(TargetType), .typeAlign = @alignOf(TargetType), - .init_func = wrappedInit.func, + .init_func = undefined, }; + if (@hasDecl(TargetType, "init")) { + const wrappedInit = struct { + const funcFind: @TypeOf(@field(TargetType, "init")) = @field(TargetType, "init"); + + pub fn func(allocator: std.mem.Allocator) EngineDataEventError!*anyopaque { + const newObject = funcFind(allocator) catch return error.BadInit; + return @as(*anyopaque, @ptrCast(newObject)); + } + }; + + self.init_func = wrappedInit.func; + } + + if (@hasDecl(TargetType, "create")) { + const wrappedInit = struct { + const funcFind: @TypeOf(@field(TargetType, "create")) = @field(TargetType, "create"); + + pub fn func(allocator: std.mem.Allocator) EngineDataEventError!*anyopaque { + const newObject = funcFind(allocator) catch return error.BadInit; + return @as(*anyopaque, @ptrCast(newObject)); + } + }; + + self.init_func = wrappedInit.func; + } + + // function used for objects created before engine loop starts; + // this is a defferred startup + // + // will run normally during create if in systems thread + // + // or else will run + if (@hasDecl(TargetType, "prepare")) { + const wrap = struct { + pub fn func(pointer: *anyopaque) EngineDataEventError!void { + var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer))); + ptr.prepare() catch return error.BadInit; + } + }; + + self.prepare_func = wrap.func; + } + if (@hasDecl(TargetType, "postInit")) { const wrappedPostInit = struct { pub fn func(pointer: *anyopaque) EngineDataEventError!void { @@ -155,6 +191,17 @@ pub const EngineObjectVTable = struct { self.deinit_func = wrappedDeinit.func; } + if (@hasDecl(TargetType, "destroy")) { + const wrappedDeinit = struct { + pub fn func(pointer: *anyopaque) void { + var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer))); + ptr.destroy(); + } + }; + + self.deinit_func = wrappedDeinit.func; + } + return self; } }; diff --git a/engine/core/src/logging.zig b/engine/core/src/logging.zig index 9d63bf1..260785f 100644 --- a/engine/core/src/logging.zig +++ b/engine/core/src/logging.zig @@ -216,7 +216,9 @@ pub const LoggerSys = struct { if (builtin.is_test) { std.debug.print("{s}", .{self.flushBuffer.items}); } else { - try self.consoleFile.writer().writeAll(self.flushBuffer.items); + const writer = self.consoleFile.writer(); + try writer.writeAll(self.flushBuffer.items); + try self.consoleFile.writeAll(""); } self.flushBuffer.clearRetainingCapacity(); self.lock.unlock(); diff --git a/engine/rend/build.zig b/engine/rend/build.zig index a140fdb..d64c705 100644 --- a/engine/rend/build.zig +++ b/engine/rend/build.zig @@ -3,6 +3,7 @@ const sdl3 = @import("sdl3"); const dependencyList = [_][]const u8{ "core", + "assets", "sdl3", "platform", "shaderTypes", diff --git a/engine/rend/src/meshes/MeshComponent.zig b/engine/rend/src/meshes/MeshComponent.zig new file mode 100644 index 0000000..6778d31 --- /dev/null +++ b/engine/rend/src/meshes/MeshComponent.zig @@ -0,0 +1,33 @@ +mesh: ?meshes.IndexedMesh = null, +textureId: ?u32 = null, +visibility: bool = true, + +textureName: core.Name = core.NameInvalid, +meshName: core.Name = core.NameInvalid, + +pub var BaseContainer: *MeshSet = undefined; +pub const ComponentName = "Mesh"; + +pub const ScriptExports: []const []const u8 = &.{ + "setMesh", + "setTextureByName", +}; + +pub fn setMeshByName(self: *@This(), meshName: core.Name) void { + self.meshName = meshName; +} + +// script function +pub fn setMesh(self: *@This(), meshName: []const u8) void { + const name = core.MakeName(meshName); + self.mesh = rend.getMeshByName(core.MakeName(meshName)); + self.meshName = name; +} + +// animator: ?*Animator = null, todo.. implement animator +pub const MeshSet = core.SparseSet(@This()); + +const meshes = @import("meshes.zig"); +const core = @import("core"); +const std = @import("std"); +const rend = @import("../rend.zig"); diff --git a/engine/rend/src/meshes/meshes.zig b/engine/rend/src/meshes/meshes.zig index 9f2d412..6ef08ac 100644 --- a/engine/rend/src/meshes/meshes.zig +++ b/engine/rend/src/meshes/meshes.zig @@ -42,7 +42,6 @@ pub const JointNameEntry = struct { index: u32 = 0, pub fn deinit(self: @This(), allocator: std.mem.Allocator) void { - // const allocator = graphics.getContext().allocator; allocator.free(self.name); } }; @@ -54,6 +53,45 @@ pub const IndexedMesh = struct { jointRemap: ?[]u8, // this is NOT a string, they're joint indices.. which happen to be u8s }; +pub const MeshSourceType = enum { obj, gltf }; + +pub const LoadMeshSettings = struct { + path: []const u8, + sourceType: ?MeshSourceType = null, + skeletonName: ?core.Name, +}; + +pub fn loadIndexedMeshForPooling(meshName: core.Name, opt: LoadMeshSettings) !void { + var sourceType = MeshSourceType.gltf; + if (opt.sourceType) |st| { + sourceType = st; + } + + // todo.. check if we have a cooked version of that file, if so just load that instead. + + // otherwise, load the file + var update: MeshUpdate = undefined; + + switch (sourceType) { + .obj => { + update = try loadIndexedMeshForPoolingObj(rend.getAllocator(), meshName, opt.path); + }, + .gltf => { + update = try loadIndexedMeshForPoolingGltf(rend.getAllocator(), meshName, opt.skeletonName, opt.path); + }, + } + + try renderer.pushMeshUpdate(update); +} + +const loadIndexedMeshForPoolingGltf = gltfLoader.loadIndexedMeshForPoolingGltf; +const loadIndexedMeshForPoolingObj = objLoader.loadIndexedMeshForPoolingObj; + +const gltfLoader = @import("gltfLoader.zig"); +const objLoader = @import("objLoader.zig"); + const core = @import("core"); +const rend = @import("../rend.zig"); +const renderer = rend.renderer; const std = @import("std"); const zgltf = @import("zgltf"); diff --git a/engine/rend/src/meshes/objLoader.zig b/engine/rend/src/meshes/objLoader.zig new file mode 100644 index 0000000..2a516b7 --- /dev/null +++ b/engine/rend/src/meshes/objLoader.zig @@ -0,0 +1,97 @@ +const MeshVertexTransmute = extern struct { data: [@sizeOf(MeshVertex)]u8 }; + +pub fn loadIndexedMeshForPoolingObj(allocator: std.mem.Allocator, meshName: core.Name, path: []const u8) !MeshUpdate { + const file = try core.fs().loadFile(path); + defer core.fs().unmap(file); + + var Objs = try objLoader.loadObjBytes(file.bytes, allocator); + defer Objs.deinit(); + + var vertexMap = std.AutoHashMap(MeshVertexTransmute, u32).init(allocator); + defer vertexMap.deinit(); + var vertexList = std.ArrayList(MeshVertex).init(allocator); + var indexList = std.ArrayList(u32).init(allocator); + + const m: *objLoader.ObjMesh = &Objs.meshes.items[0]; + + // only thing i care about right now is normal and position + for (m.v_faces.items) |f| { + const face: objLoader.ObjFace = f; + + if (face.count == 3) { + for (0..face.count) |i| { + const p = m.v_positions.items[face.vertex[i] - 1]; + const n = m.v_normals.items[face.normal[i] - 1]; + const u = m.v_uvs.items[face.texture[i] - 1]; + const meshVertex: MeshVertex = .{ + .position = .{ .x = p.x, .y = p.y, .z = p.z }, + .normal = .{ .x = n.x, .y = n.y, .z = n.z }, + .color = .{ .r = n.x, .g = n.y, .b = n.z, .a = 1.0 }, + .uv = .{ .x = u.x, .y = 1 - u.y }, + }; + + var index: u32 = @intCast(vertexList.items.len); + + const transmute: MeshVertexTransmute = @bitCast(meshVertex); + if (vertexMap.get(transmute)) |cachedIndex| { + index = cachedIndex; + } else { + try vertexMap.put(transmute, index); + try vertexList.append(meshVertex); + } + try indexList.append(index); + } + } + + if (face.count == 4) { + const il: []const usize = &.{ 0, 1, 2, 2, 3, 0 }; + for (il) |i| { + const p = m.v_positions.items[face.vertex[i] - 1]; + const n = m.v_normals.items[face.normal[i] - 1]; + const u = m.v_uvs.items[face.texture[i] - 1]; + const meshVertex: MeshVertex = .{ + .position = .{ .x = p.x, .y = p.y, .z = p.z }, + .normal = .{ .x = n.x, .y = n.y, .z = n.z }, + .color = .{ .r = n.x, .g = n.y, .b = n.z, .a = 1.0 }, + .uv = .{ .x = u.x, .y = 1 - u.y }, + }; + + var index: u32 = @intCast(vertexList.items.len); + + const transmute: MeshVertexTransmute = @bitCast(meshVertex); + if (vertexMap.get(transmute)) |cachedIndex| { + index = cachedIndex; + } else { + try vertexMap.put(transmute, index); + try vertexList.append(meshVertex); + } + try indexList.append(index); + } + } + } + + var jointNames = std.ArrayList(JointNameEntry).init(allocator); + + const rv: MeshUpdate = .{ + .new = .{ + .vertices = try vertexList.toOwnedSlice(), + .indices = try indexList.toOwnedSlice(), + .jointNames = try jointNames.toOwnedSlice(), + .skeletonName = null, + .name = meshName, + }, + }; + + core.graphics_log("[{s}] vertex count vertices={d} indices={d}", .{ path, rv.new.vertices.len, rv.new.indices.len }); + + return rv; +} + +const meshes = @import("meshes.zig"); +const MeshUpdate = meshes.MeshUpdate; +const JointNameEntry = meshes.JointNameEntry; +const MeshVertex = meshes.MeshVertex; + +const core = @import("core"); +const std = @import("std"); +const objLoader = @import("objLoader"); diff --git a/engine/rend/src/rend.zig b/engine/rend/src/rend.zig index 25a65b6..3277069 100644 --- a/engine/rend/src/rend.zig +++ b/engine/rend/src/rend.zig @@ -17,6 +17,14 @@ pub const context = renderer.context; // context getter func pub const MeshPool = renderer.MeshPool; +pub const getMesh = renderer.getMesh; +pub const getMeshByName = renderer.getMeshByName; + +var rendAllocator: std.mem.Allocator = undefined; +pub fn getAllocator() std.mem.Allocator { + return rendAllocator; +} + // controls glfw and general windowing // graphics depends on this one pub const Module: core.ModuleDescription = .{ @@ -27,7 +35,8 @@ pub const Module: core.ModuleDescription = .{ pub fn start_module(comptime programSpec: anytype, args: anytype, allocator: std.mem.Allocator) !void { _ = args; _ = programSpec; - _ = allocator; + core.engine_log("starting up renderer interface", .{}); + rendAllocator = allocator; try renderer.start(); } diff --git a/engine/rend/src/sgpu/MeshAssetLoader.zig b/engine/rend/src/sgpu/MeshAssetLoader.zig new file mode 100644 index 0000000..74c9ea3 --- /dev/null +++ b/engine/rend/src/sgpu/MeshAssetLoader.zig @@ -0,0 +1,75 @@ +// @peterino2 + +allocator: std.mem.Allocator, + +pub var LoaderInterfaceVTable = assets.AssetLoaderInterface.from("Mesh", @This()); +pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This()); + +pub fn create(allocator: std.mem.Allocator) !*@This() { + const self = try allocator.create(@This()); + self.* = .{ + .allocator = allocator, + }; + + return self; +} + +pub fn discardAll(self: *@This()) void { + _ = self; +} + +pub fn destroy(self: *@This()) void { + self.allocator.destroy(self); +} + +// unfortunately this one is blocking +pub fn loadAsset(self: *@This(), assetRef: assets.AssetRef, propertiesBag: ?assets.AssetPropertiesBag) assets.AssetLoaderError!void { + _ = self; + + const sourceType = getSourceType(propertiesBag); + + core.engine_log("loading mesh asset {s} [{s}]", .{ propertiesBag.?.path, if (sourceType) |s| @tagName(s) else "default" }); + + meshes.loadIndexedMeshForPooling(assetRef.name, .{ + .path = propertiesBag.?.path, + .sourceType = getSourceType(propertiesBag), + .skeletonName = if (propertiesBag.?.skeletonName) |skName| core.MakeName(skName) else null, + }) catch return error.UnableToLoad; +} + +fn getSourceType(propertiesBag: ?assets.AssetPropertiesBag) ?meshes.MeshSourceType { + if (propertiesBag) |bag| { + if (bag.meshType) |meshType| { + if (std.mem.eql(u8, meshType, "obj")) { + return meshes.MeshSourceType.obj; + } + if (std.mem.eql(u8, meshType, "gltf")) { + return meshes.MeshSourceType.gltf; + } + } + + // try to deduce it by file name, if nothing is set. + const ext = core.getFileExtension(bag.path); + if (std.mem.eql(u8, ext, ".obj")) { + return meshes.MeshSourceType.obj; + } + + if (std.mem.eql(u8, ext, ".gltf")) { + return meshes.MeshSourceType.gltf; + } + + if (std.mem.eql(u8, ext, ".glb")) { + return meshes.MeshSourceType.gltf; + } + } + + return null; +} + +const assets = @import("assets"); +const core = @import("core"); +const std = @import("std"); + +const rend = @import("../rend.zig"); +const renderer = rend.renderer; +const meshes = rend.meshes; diff --git a/engine/rend/src/sgpu/mesh-pool.zig b/engine/rend/src/sgpu/mesh-pool.zig index b8dd5ed..f8c8ed5 100644 --- a/engine/rend/src/sgpu/mesh-pool.zig +++ b/engine/rend/src/sgpu/mesh-pool.zig @@ -12,14 +12,21 @@ pub const MeshPool = struct { device: *gpu.GPUDevice, + destroyList: std.ArrayList(*gpu.GPUTransferBuffer), + + installedMeshes: std.AutoHashMapUnmanaged(u32, meshes.IndexedMesh) = .{}, + pub fn create(device: *gpu.GPUDevice, allocator: std.mem.Allocator, settings: MeshPoolCreationSettings) !*@This() { const self = try allocator.create(@This()); + gMeshPool = self; + self.* = .{ .allocator = allocator, .indexSpans = try core.MergedSpans.init(allocator, settings.indexCount), .vertexSpans = try core.MergedSpans.init(allocator, settings.vertexCount), .meshUpdates = try core.RingQueue(rend.MeshUpdate).init(allocator, 128), + .destroyList = std.ArrayList(*gpu.GPUTransferBuffer).init(allocator), .device = device, }; @@ -35,24 +42,106 @@ pub const MeshPool = struct { .props = 0, }); + core.graphics_log("mesh pool created {d}k vertices, {d}k indices", .{ settings.vertexCount / 1000, settings.indexCount / 1000 }); + + try assets.gAssetSys.registerLoader(try core.createObject(MeshAssetLoader, .{})); + return self; } + pub fn onUploadCleanup(p: *anyopaque) void { + const self: *@This() = @ptrCast(@alignCast(p)); + + for (self.destroyList.items) |transferBuffer| { + self.device.releaseGPUTransferBuffer(transferBuffer); + } + + self.destroyList.clearRetainingCapacity(); + } + + pub fn onUploadInner(self: *@This(), copyPass: *gpu.GPUCopyPass) !void { + self.meshUpdates.lock(); + defer self.meshUpdates.unlock(); + + self.destroyList = std.ArrayList(*gpu.GPUTransferBuffer).init(self.allocator); + + while (self.meshUpdates.popFromUnlocked()) |u| { + switch (u) { + .new => |new| { + const indexSpan = try self.addTransfer(copyPass, self.indexBuffer, u32, &self.indexSpans, new.indices); + const vertexSpan = try self.addTransfer(copyPass, self.vertexBuffer, meshes.MeshVertex, &self.vertexSpans, new.vertices); + + var name = new.name; + try self.installedMeshes.put(self.allocator, name.handle(), .{ + .vertex = vertexSpan, + .index = indexSpan, + .name = new.name, + .jointRemap = null, // joint remaps... parse the installed skeletal meshes to generate this remap + }); + }, + .free => |free| { + var name = free.name; + if (!self.installedMeshes.remove(name.handle())) { + core.engine_log("unable to find handle from mesh pool.. {s}", .{name.utf8()}); + } + self.vertexSpans.removeSpan(free.vertices); + self.indexSpans.removeSpan(free.indices); + }, + } + + u.deinit(self.allocator); + } + } + pub fn onUpload(p: *anyopaque, copyPass: *gpu.GPUCopyPass) void { const self: *@This() = @ptrCast(@alignCast(p)); if (self.meshUpdates.count() <= 0) return; - _ = copyPass; + + self.onUploadInner(copyPass) catch unreachable; + } + + pub fn addTransfer( + self: *@This(), + copyPass: *gpu.GPUCopyPass, + destBuffer: *gpu.GPUBuffer, + comptime T: type, + mergedSpans: *core.MergedSpans, + uploadSlice: []const T, + ) !core.Span { + const newSpan = try mergedSpans.allocate(@intCast(uploadSlice.len)); + const upload = self.device.createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, .size = @sizeOf(T) * newSpan.size, .props = 0 }); + try self.destroyList.append(upload); + + var mappedSlice: []T = undefined; + mappedSlice.ptr = @ptrCast(@alignCast(self.device.mapGPUTransferBuffer(upload, false))); + defer self.device.unmapGPUTransferBuffer(upload); + mappedSlice.len = uploadSlice.len; + std.mem.copyForwards(T, mappedSlice, uploadSlice); + + //copyPass.uploadToGPUBuffer(source: [*c]const GPUTransferBufferLocation, destination: [*c]const GPUBufferRegion, cycle: bool) + copyPass.uploadToGPUBuffer( + &.{ .transfer_buffer = upload, .offset = 0 }, + &.{ .buffer = destBuffer, .offset = newSpan.start, .size = newSpan.size }, + false, + ); + + return newSpan; } // pushes the meshUpdate into the pool, either an insert or a free. pub fn pushMeshUpdate(self: *@This(), meshUpdate: rend.MeshUpdate) !void { - self.meshUpdates.pushLocked(meshUpdate); + try self.meshUpdates.pushLocked(meshUpdate); } pub fn destroy(p: *anyopaque) void { const self: *@This() = @ptrCast(@alignCast(p)); + self.device.releaseGPUBuffer(self.indexBuffer); + self.device.releaseGPUBuffer(self.vertexBuffer); + + self.installedMeshes.deinit(self.allocator); + self.destroyList.deinit(); self.indexSpans.deinit(); self.meshUpdates.deinit(); self.vertexSpans.deinit(); @@ -65,25 +154,27 @@ pub const MeshPool = struct { }; }; +/// === renderer interface implementation === var gMeshPool: *MeshPool = undefined; -pub fn getIndexedMesh(name: []const u8) ?rend.IndexedMesh { +pub fn getMesh(name: []const u8) ?rend.IndexedMesh { var n = core.MakeName(name); - return getIndexedMeshByName(&n); + return getMeshByName(&n); } -pub fn getIndexedMeshByName(name: *core.Name) ?rend.IndexedMesh { - // var name = _name; - // gMeshPoolBuffer.vertexMapLock.lock(); - // defer gMeshPoolBuffer.vertexMapLock.unlock(); - // return gMeshPoolBuffer.vertexMap.get(name.handle()); - _ = name; - return null; +pub fn getMeshByName(name: *core.Name) ?rend.IndexedMesh { + return gMeshPool.installedMeshes.get(name.handle()); } +pub fn pushMeshUpdate(meshUpdate: rend.MeshUpdate) !void { + try gMeshPool.pushMeshUpdate(meshUpdate); +} + +const MeshAssetLoader = @import("MeshAssetLoader.zig"); const sdl3 = @import("sdl3"); const gpu = sdl3.gpu; const rend = @import("../rend.zig"); const std = @import("std"); const core = @import("core"); +const assets = @import("assets"); const meshes = rend.meshes; diff --git a/engine/rend/src/sgpu/renderer.zig b/engine/rend/src/sgpu/renderer.zig index b47cbfc..bda8944 100644 --- a/engine/rend/src/sgpu/renderer.zig +++ b/engine/rend/src/sgpu/renderer.zig @@ -24,6 +24,7 @@ pub const Renderer = struct { meshPool: *MeshPool = undefined, uploads: std.ArrayListUnmanaged(struct { ptr: *anyopaque, func: *const fn (*anyopaque, *gpu.GPUCopyPass) void }) = .{}, + uploadCleanup: std.ArrayListUnmanaged(struct { ptr: *anyopaque, func: *const fn (*anyopaque) void }) = .{}, destroys: std.ArrayListUnmanaged(struct { ptr: *anyopaque, func: *const fn (*anyopaque) void }) = .{}, pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This()); @@ -35,18 +36,19 @@ pub const Renderer = struct { .allocator = allocator, }; - core.registerRendererSetup(self, engineSetup); return self; } - pub fn engineSetup(p: *anyopaque) core.EngineDataEventError!void { - @as(*@This(), @ptrCast(@alignCast(p))).startRenderer() catch return error.BadInit; + pub fn prepare(self: *@This()) core.EngineDataEventError!void { + core.engine_log("starting up renderer interface", .{}); + self.startRenderer() catch return error.BadInit; } pub fn registerRendererObject(self: *@This(), T: type) !void { const object = try T.create(self.device, self.allocator, .{}); try self.uploads.append(self.allocator, .{ .ptr = object, .func = T.onUpload }); + try self.uploadCleanup.append(self.allocator, .{ .ptr = object, .func = T.onUploadCleanup }); try self.destroys.append(self.allocator, .{ .ptr = object, .func = T.destroy }); } @@ -165,7 +167,8 @@ pub const Renderer = struct { return rv; } - pub fn frameUploads(self: *@This(), cmd: *gpu.GPUCommandBuffer) void { + pub fn frameUploads(self: *@This()) void { + const cmd = self.device.acquireGPUCommandBuffer(); const buffer = self.device.mapGPUTransferBuffer(self.colorBufferTransfer, true); var b: [*][4]f32 = @ptrCast(@alignCast(buffer)); @@ -187,18 +190,25 @@ pub const Renderer = struct { for (self.uploads.items) |upload| { upload.func(upload.ptr, copyPass); } + if (!cmd.submitGPUCommandBuffer()) { + core.graphics_log("submit gpu command buffer SDL ERROR: {s}", .{sdl3.getError()}); + } + + for (self.uploadCleanup.items) |uploadCleanup| { + uploadCleanup.func(uploadCleanup.ptr); + } } pub fn tick(self: *@This(), dt: f64) void { self.totalTime += dt; const cmd = self.device.acquireGPUCommandBuffer(); - self.frameUploads(cmd); + self.frameUploads(); - var swapchain_texture: *gpu.GPUTexture = undefined; - if (cmd.waitAndAcquireGPUSwapchainTexture(self.window, &swapchain_texture, null, null)) { + var swapchainTexture: *gpu.GPUTexture = undefined; + if (cmd.waitAndAcquireGPUSwapchainTexture(self.window, &swapchainTexture, null, null)) { var targetInfo: gpu.GPUColorTargetInfo = std.mem.zeroes(gpu.GPUColorTargetInfo); - targetInfo.texture = swapchain_texture; + targetInfo.texture = swapchainTexture; targetInfo.clear_color = .{ .r = 0.1, .g = 0.1, .b = 0.1, .a = 1.0 }; targetInfo.load_op = .loadopClear; targetInfo.store_op = .storeopStore; @@ -218,6 +228,7 @@ pub const Renderer = struct { for (self.destroys.items) |d| { d.func(d.ptr); } + self.uploadCleanup.deinit(self.allocator); self.uploads.deinit(self.allocator); self.destroys.deinit(self.allocator); self.allocator.destroy(self); @@ -227,6 +238,22 @@ pub const Renderer = struct { pub var gRenderer: *Renderer = undefined; pub var gAllocator: std.mem.Allocator = undefined; +const rend = @import("../rend.zig"); + +const std = @import("std"); +const core = @import("core"); +const platform = @import("platform"); +const sdl3 = @import("sdl3"); +const gpu = sdl3.gpu; +const SSBO_Scene = @import("sample.vert").Scene; +const meshes_vert = @import("meshes.vert"); +const MeshVertices = meshes_vert.Scene; +const MeshUniforms = meshes_vert.Uniforms; + +pub const mesh_pool = @import("mesh-pool.zig"); +pub const MeshPool = mesh_pool.MeshPool; + +// ====== renderer API ======= pub fn start() !void { gRenderer = try core.createObject(Renderer, .{ .can_tick = true, .isCore = true }); gAllocator = gRenderer.allocator; @@ -237,15 +264,7 @@ pub fn shutdown() void {} pub fn context() *Renderer { return gRenderer; } +pub const getMesh = mesh_pool.getMesh; +pub const getMeshByName = mesh_pool.getMeshByName; -const rend = @import("../rend.zig"); - -pub const mesh_pool = @import("mesh-pool.zig"); -pub const MeshPool = mesh_pool.MeshPool; - -const std = @import("std"); -const core = @import("core"); -const platform = @import("platform"); -const sdl3 = @import("sdl3"); -const gpu = sdl3.gpu; -const SSBO_Scene = @import("sample.vert").Scene; +pub const pushMeshUpdate = mesh_pool.pushMeshUpdate; diff --git a/lib/objLoader/src/obj_loader.zig b/lib/objLoader/src/obj_loader.zig index bbcbd5b..8c7ca23 100644 --- a/lib/objLoader/src/obj_loader.zig +++ b/lib/objLoader/src/obj_loader.zig @@ -189,7 +189,7 @@ pub fn toksIntoFace(toks: anytype) !ObjFace { while (toks.next()) |tok| { if (tok.len == 0) continue; - var face_desc = std.mem.tokenize(u8, tok, "/"); + var face_desc = std.mem.tokenizeAny(u8, tok, "/"); var ic: u32 = 0; // ic= inner_count if (count >= 4) { continue; @@ -232,7 +232,7 @@ fn parse_line(lineIn: []const u8, allocator: std.mem.Allocator) !LineParseResult line = line[1..line.len]; } - var tokens = std.mem.tokenize(u8, line, " "); + var tokens = std.mem.tokenizeAny(u8, line, " "); const first = tokens.next().?; if (line[0] == '#') { @@ -354,16 +354,16 @@ pub fn fileIntoLines(file_contents: []const u8) std.mem.SplitIterator(u8, .seque if (file_contents[index] == '\n') { if (index > 0) { if (file_contents[index - 1] == '\r') { - return std.mem.split(u8, file_contents, "\r\n"); + return std.mem.splitSequence(u8, file_contents, "\r\n"); } else { - return std.mem.split(u8, file_contents, "\n"); + return std.mem.splitSequence(u8, file_contents, "\n"); } } else { - return std.mem.split(u8, file_contents, "\n"); + return std.mem.splitSequence(u8, file_contents, "\n"); } } } - return std.mem.split(u8, file_contents, "\n"); + return std.mem.splitSequence(u8, file_contents, "\n"); } const ObjContents = struct { diff --git a/lib/sdl3/src/sdl3.zig b/lib/sdl3/src/sdl3.zig index 41ee5c1..3af95af 100644 --- a/lib/sdl3/src/sdl3.zig +++ b/lib/sdl3/src/sdl3.zig @@ -44,5 +44,9 @@ pub fn pollEvent() ?*Event { return null; } +pub fn getError() [*c]const u8 { + return c.SDL_GetError(); +} + pub const gpu = @import("gpu.zig"); pub const Scancode = @import("scancode.zig").Scancode; diff --git a/lib/tracy/build.zig b/lib/tracy/build.zig index 5b34ac1..70e9fd2 100644 --- a/lib/tracy/build.zig +++ b/lib/tracy/build.zig @@ -9,7 +9,7 @@ pub fn build(b: *std.Build) void { //const tracy_enabled = b.option(bool, "tracy", "Enables tracy integration") orelse false; - const tracy_enabled: bool = false; //if (b.graph.env_map.hash_map.get("WITH_TRACY") != null) true else false; + const tracy_enabled: bool = true; //if (b.graph.env_map.hash_map.get("WITH_TRACY") != null) true else false; // if (b.graph.env_map.hash_map.get("WITH_TRACY")) |with_tracy| { // tracy_enabled = with_tracy; // } diff --git a/projects/sampleGame/main.zig b/projects/sampleGame/main.zig index cb3ef0a..1955297 100644 --- a/projects/sampleGame/main.zig +++ b/projects/sampleGame/main.zig @@ -10,21 +10,34 @@ pub fn init(allocator: std.mem.Allocator) !*@This() { return self; } -pub fn prepare_game(self: *@This()) !void { +const assetReferences = [_]assets.AssetImportReference{ + assets.MakeImportRefOptions( + "Mesh", + "m_empire", + .{ .path = "meshes/lost_empire.obj" }, + ), + assets.MakeImportRefOptions( + "Mesh", + "m_fox", + .{ .path = "gltf-samples/Fox/glTF/Fox.gltf" }, + ), +}; + +pub fn prepare(self: *@This()) !void { + _ = self; + core.engine_log(">>>>>>> game prepare", .{}); + var z = core.tracy.ZoneN(@src(), "PREPARING GAME"); + defer z.End(); try core.fs().addContentPath("sampleGame"); try script.loadTypes("scripts"); try script.runScriptFile("scripts/prepare.lua"); + try assets.loadList(assetReferences); + const exitInput = try core.ActionBinding.create(core.MakeName("exit")); 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 { @@ -54,6 +67,7 @@ pub fn main() anyerror!void { const std = @import("std"); const backlog = @import("Backlog"); +const assets = backlog.assets; const core = backlog.core; const rend = backlog.rend; const script = core.script; diff --git a/projects/sampleGame/scripts/prepare.lua b/projects/sampleGame/scripts/prepare.lua index 665c637..8b13789 100644 --- a/projects/sampleGame/scripts/prepare.lua +++ b/projects/sampleGame/scripts/prepare.lua @@ -1 +1 @@ -print("hello world!") +