From 5f426c61de363df2ce45eedd63bb705eda955037 Mon Sep 17 00:00:00 2001 From: sergeypdev Date: Sat, 24 Feb 2024 23:57:31 +0400 Subject: [PATCH] First scene loaded, discovered an issue with ubo's, weird stuff in fullscreen --- assets/test_scene.mtl | 2 + assets/test_scene.obj | 303 +++++++++++++++++++++++++++++++++++++ build.zig | 23 ++- src/AssetManager.zig | 59 ++++++++ src/Render.zig | 81 ++++++---- src/assets/root.zig | 5 +- src/formats.zig | 113 +++++++++++++- src/game.zig | 95 +++++++----- src/gen/asset_manifest.zig | 1 + src/globals.zig | 110 +++++++++++--- tools/asset_compiler.zig | 141 ++++++++++++++--- 11 files changed, 803 insertions(+), 130 deletions(-) create mode 100644 assets/test_scene.mtl create mode 100644 assets/test_scene.obj diff --git a/assets/test_scene.mtl b/assets/test_scene.mtl new file mode 100644 index 0000000..763e0ab --- /dev/null +++ b/assets/test_scene.mtl @@ -0,0 +1,2 @@ +# Blender 4.0.2 MTL File: 'None' +# www.blender.org diff --git a/assets/test_scene.obj b/assets/test_scene.obj new file mode 100644 index 0000000..64f3f52 --- /dev/null +++ b/assets/test_scene.obj @@ -0,0 +1,303 @@ +# Blender 4.0.2 +# www.blender.org +mtllib test_scene.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vn -1.0000 -0.0000 -0.0000 +vn -0.0000 -0.0000 -1.0000 +vn 1.0000 -0.0000 -0.0000 +vn -0.0000 -0.0000 1.0000 +vn -0.0000 -1.0000 -0.0000 +vn -0.0000 1.0000 -0.0000 +vt 0.375000 0.000000 +vt 0.625000 0.000000 +vt 0.625000 0.250000 +vt 0.375000 0.250000 +vt 0.625000 0.500000 +vt 0.375000 0.500000 +vt 0.625000 0.750000 +vt 0.375000 0.750000 +vt 0.625000 1.000000 +vt 0.375000 1.000000 +vt 0.125000 0.500000 +vt 0.125000 0.750000 +vt 0.875000 0.500000 +vt 0.875000 0.750000 +s 0 +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/4/2 4/3/2 8/5/2 7/6/2 +f 7/6/3 8/5/3 6/7/3 5/8/3 +f 5/8/4 6/7/4 2/9/4 1/10/4 +f 3/11/5 7/6/5 5/8/5 1/12/5 +f 8/5/6 4/13/6 2/14/6 6/7/6 +o Cylinder +v -4.365820 -1.000000 -1.000000 +v -4.365820 1.000000 -1.000000 +v -4.170730 -1.000000 -0.980785 +v -4.170730 1.000000 -0.980785 +v -3.983137 -1.000000 -0.923880 +v -3.983137 1.000000 -0.923880 +v -3.810250 -1.000000 -0.831470 +v -3.810250 1.000000 -0.831470 +v -3.658714 -1.000000 -0.707107 +v -3.658714 1.000000 -0.707107 +v -3.534351 -1.000000 -0.555570 +v -3.534351 1.000000 -0.555570 +v -3.441941 -1.000000 -0.382683 +v -3.441941 1.000000 -0.382683 +v -3.385035 -1.000000 -0.195090 +v -3.385035 1.000000 -0.195090 +v -3.365820 -1.000000 0.000000 +v -3.365820 1.000000 0.000000 +v -3.385035 -1.000000 0.195090 +v -3.385035 1.000000 0.195090 +v -3.441941 -1.000000 0.382683 +v -3.441941 1.000000 0.382683 +v -3.534351 -1.000000 0.555570 +v -3.534351 1.000000 0.555570 +v -3.658714 -1.000000 0.707107 +v -3.658714 1.000000 0.707107 +v -3.810250 -1.000000 0.831470 +v -3.810250 1.000000 0.831470 +v -3.983137 -1.000000 0.923880 +v -3.983137 1.000000 0.923880 +v -4.170730 -1.000000 0.980785 +v -4.170730 1.000000 0.980785 +v -4.365820 -1.000000 1.000000 +v -4.365820 1.000000 1.000000 +v -4.560911 -1.000000 0.980785 +v -4.560911 1.000000 0.980785 +v -4.748504 -1.000000 0.923880 +v -4.748504 1.000000 0.923880 +v -4.921391 -1.000000 0.831470 +v -4.921391 1.000000 0.831470 +v -5.072927 -1.000000 0.707107 +v -5.072927 1.000000 0.707107 +v -5.197290 -1.000000 0.555570 +v -5.197290 1.000000 0.555570 +v -5.289700 -1.000000 0.382683 +v -5.289700 1.000000 0.382683 +v -5.346606 -1.000000 0.195090 +v -5.346606 1.000000 0.195090 +v -5.365820 -1.000000 0.000000 +v -5.365820 1.000000 0.000000 +v -5.346606 -1.000000 -0.195090 +v -5.346606 1.000000 -0.195090 +v -5.289700 -1.000000 -0.382683 +v -5.289700 1.000000 -0.382683 +v -5.197290 -1.000000 -0.555570 +v -5.197290 1.000000 -0.555570 +v -5.072927 -1.000000 -0.707107 +v -5.072927 1.000000 -0.707107 +v -4.921391 -1.000000 -0.831470 +v -4.921391 1.000000 -0.831470 +v -4.748504 -1.000000 -0.923880 +v -4.748504 1.000000 -0.923880 +v -4.560911 -1.000000 -0.980785 +v -4.560911 1.000000 -0.980785 +vn 0.0980 -0.0000 -0.9952 +vn 0.2903 -0.0000 -0.9569 +vn 0.4714 -0.0000 -0.8819 +vn 0.6344 -0.0000 -0.7730 +vn 0.7730 -0.0000 -0.6344 +vn 0.8819 -0.0000 -0.4714 +vn 0.9569 -0.0000 -0.2903 +vn 0.9952 -0.0000 -0.0980 +vn 0.9952 -0.0000 0.0980 +vn 0.9569 -0.0000 0.2903 +vn 0.8819 -0.0000 0.4714 +vn 0.7730 -0.0000 0.6344 +vn 0.6344 -0.0000 0.7730 +vn 0.4714 -0.0000 0.8819 +vn 0.2903 -0.0000 0.9569 +vn 0.0980 -0.0000 0.9952 +vn -0.0980 -0.0000 0.9952 +vn -0.2903 -0.0000 0.9569 +vn -0.4714 -0.0000 0.8819 +vn -0.6344 -0.0000 0.7730 +vn -0.7730 -0.0000 0.6344 +vn -0.8819 -0.0000 0.4714 +vn -0.9569 -0.0000 0.2903 +vn -0.9952 -0.0000 0.0980 +vn -0.9952 -0.0000 -0.0980 +vn -0.9569 -0.0000 -0.2903 +vn -0.8819 -0.0000 -0.4714 +vn -0.7730 -0.0000 -0.6344 +vn -0.6344 -0.0000 -0.7730 +vn -0.4714 -0.0000 -0.8819 +vn -0.0000 1.0000 -0.0000 +vn -0.2903 -0.0000 -0.9569 +vn -0.0980 -0.0000 -0.9952 +vn -0.0000 -1.0000 -0.0000 +vt 1.000000 0.500000 +vt 1.000000 1.000000 +vt 0.968750 1.000000 +vt 0.968750 0.500000 +vt 0.937500 1.000000 +vt 0.937500 0.500000 +vt 0.906250 1.000000 +vt 0.906250 0.500000 +vt 0.875000 1.000000 +vt 0.875000 0.500000 +vt 0.843750 1.000000 +vt 0.843750 0.500000 +vt 0.812500 1.000000 +vt 0.812500 0.500000 +vt 0.781250 1.000000 +vt 0.781250 0.500000 +vt 0.750000 1.000000 +vt 0.750000 0.500000 +vt 0.718750 1.000000 +vt 0.718750 0.500000 +vt 0.687500 1.000000 +vt 0.687500 0.500000 +vt 0.656250 1.000000 +vt 0.656250 0.500000 +vt 0.625000 1.000000 +vt 0.625000 0.500000 +vt 0.593750 1.000000 +vt 0.593750 0.500000 +vt 0.562500 1.000000 +vt 0.562500 0.500000 +vt 0.531250 1.000000 +vt 0.531250 0.500000 +vt 0.500000 1.000000 +vt 0.500000 0.500000 +vt 0.468750 1.000000 +vt 0.468750 0.500000 +vt 0.437500 1.000000 +vt 0.437500 0.500000 +vt 0.406250 1.000000 +vt 0.406250 0.500000 +vt 0.375000 1.000000 +vt 0.375000 0.500000 +vt 0.343750 1.000000 +vt 0.343750 0.500000 +vt 0.312500 1.000000 +vt 0.312500 0.500000 +vt 0.281250 1.000000 +vt 0.281250 0.500000 +vt 0.250000 1.000000 +vt 0.250000 0.500000 +vt 0.218750 1.000000 +vt 0.218750 0.500000 +vt 0.187500 1.000000 +vt 0.187500 0.500000 +vt 0.156250 1.000000 +vt 0.156250 0.500000 +vt 0.125000 1.000000 +vt 0.125000 0.500000 +vt 0.093750 1.000000 +vt 0.093750 0.500000 +vt 0.062500 1.000000 +vt 0.062500 0.500000 +vt 0.296822 0.485388 +vt 0.250000 0.490000 +vt 0.203178 0.485388 +vt 0.158156 0.471731 +vt 0.116663 0.449553 +vt 0.080294 0.419706 +vt 0.050447 0.383337 +vt 0.028269 0.341844 +vt 0.014612 0.296822 +vt 0.010000 0.250000 +vt 0.014612 0.203178 +vt 0.028269 0.158156 +vt 0.050447 0.116663 +vt 0.080294 0.080294 +vt 0.116663 0.050447 +vt 0.158156 0.028269 +vt 0.203178 0.014612 +vt 0.250000 0.010000 +vt 0.296822 0.014612 +vt 0.341844 0.028269 +vt 0.383337 0.050447 +vt 0.419706 0.080294 +vt 0.449553 0.116663 +vt 0.471731 0.158156 +vt 0.485388 0.203178 +vt 0.490000 0.250000 +vt 0.485388 0.296822 +vt 0.471731 0.341844 +vt 0.449553 0.383337 +vt 0.419706 0.419706 +vt 0.383337 0.449553 +vt 0.341844 0.471731 +vt 0.031250 1.000000 +vt 0.031250 0.500000 +vt 0.000000 1.000000 +vt 0.000000 0.500000 +vt 0.750000 0.490000 +vt 0.796822 0.485388 +vt 0.841844 0.471731 +vt 0.883337 0.449553 +vt 0.919706 0.419706 +vt 0.949553 0.383337 +vt 0.971731 0.341844 +vt 0.985388 0.296822 +vt 0.990000 0.250000 +vt 0.985388 0.203178 +vt 0.971731 0.158156 +vt 0.949553 0.116663 +vt 0.919706 0.080294 +vt 0.883337 0.050447 +vt 0.841844 0.028269 +vt 0.796822 0.014612 +vt 0.750000 0.010000 +vt 0.703178 0.014612 +vt 0.658156 0.028269 +vt 0.616663 0.050447 +vt 0.580294 0.080294 +vt 0.550447 0.116663 +vt 0.528269 0.158156 +vt 0.514612 0.203178 +vt 0.510000 0.250000 +vt 0.514612 0.296822 +vt 0.528269 0.341844 +vt 0.550447 0.383337 +vt 0.580294 0.419706 +vt 0.616663 0.449553 +vt 0.658156 0.471731 +vt 0.703178 0.485388 +s 0 +f 9/15/7 10/16/7 12/17/7 11/18/7 +f 11/18/8 12/17/8 14/19/8 13/20/8 +f 13/20/9 14/19/9 16/21/9 15/22/9 +f 15/22/10 16/21/10 18/23/10 17/24/10 +f 17/24/11 18/23/11 20/25/11 19/26/11 +f 19/26/12 20/25/12 22/27/12 21/28/12 +f 21/28/13 22/27/13 24/29/13 23/30/13 +f 23/30/14 24/29/14 26/31/14 25/32/14 +f 25/32/15 26/31/15 28/33/15 27/34/15 +f 27/34/16 28/33/16 30/35/16 29/36/16 +f 29/36/17 30/35/17 32/37/17 31/38/17 +f 31/38/18 32/37/18 34/39/18 33/40/18 +f 33/40/19 34/39/19 36/41/19 35/42/19 +f 35/42/20 36/41/20 38/43/20 37/44/20 +f 37/44/21 38/43/21 40/45/21 39/46/21 +f 39/46/22 40/45/22 42/47/22 41/48/22 +f 41/48/23 42/47/23 44/49/23 43/50/23 +f 43/50/24 44/49/24 46/51/24 45/52/24 +f 45/52/25 46/51/25 48/53/25 47/54/25 +f 47/54/26 48/53/26 50/55/26 49/56/26 +f 49/56/27 50/55/27 52/57/27 51/58/27 +f 51/58/28 52/57/28 54/59/28 53/60/28 +f 53/60/29 54/59/29 56/61/29 55/62/29 +f 55/62/30 56/61/30 58/63/30 57/64/30 +f 57/64/31 58/63/31 60/65/31 59/66/31 +f 59/66/32 60/65/32 62/67/32 61/68/32 +f 61/68/33 62/67/33 64/69/33 63/70/33 +f 63/70/34 64/69/34 66/71/34 65/72/34 +f 65/72/35 66/71/35 68/73/35 67/74/35 +f 67/74/36 68/73/36 70/75/36 69/76/36 +f 12/77/37 10/78/37 72/79/37 70/80/37 68/81/37 66/82/37 64/83/37 62/84/37 60/85/37 58/86/37 56/87/37 54/88/37 52/89/37 50/90/37 48/91/37 46/92/37 44/93/37 42/94/37 40/95/37 38/96/37 36/97/37 34/98/37 32/99/37 30/100/37 28/101/37 26/102/37 24/103/37 22/104/37 20/105/37 18/106/37 16/107/37 14/108/37 +f 69/76/38 70/75/38 72/109/38 71/110/38 +f 71/110/39 72/109/39 10/111/39 9/112/39 +f 9/113/40 11/114/40 13/115/40 15/116/40 17/117/40 19/118/40 21/119/40 23/120/40 25/121/40 27/122/40 29/123/40 31/124/40 33/125/40 35/126/40 37/127/40 39/128/40 41/129/40 43/130/40 45/131/40 47/132/40 49/133/40 51/134/40 53/135/40 55/136/40 57/137/40 59/138/40 61/139/40 63/140/40 65/141/40 67/142/40 69/143/40 71/144/40 diff --git a/build.zig b/build.zig index e4a6893..e1ec561 100644 --- a/build.zig +++ b/build.zig @@ -32,13 +32,11 @@ pub fn build(b: *Build) void { const assets_step = b.step("assets", "Build and install assets"); b.getInstallStep().dependOn(assets_step); - const assetc = buildAssetCompiler(b, buildOptimize); + const assetc = buildAssetCompiler(b, buildOptimize, assets_mod); const install_assetc_step = b.addInstallArtifact(assetc, .{ .dest_dir = .{ .override = .prefix } }); assets_step.dependOn(&install_assetc_step.step); - assetc.root_module.addImport("assets", assets_mod); - const gen_asset_manifest = buildAssets(b, &install_assetc_step.step, assets_step, assetc, "assets") catch |err| { std.log.err("Failed to build assets {}\n", .{err}); @panic("buildAssets"); @@ -205,15 +203,18 @@ fn buildAssets(b: *std.Build, install_assetc_step: *Step, step: *Step, assetc: * return asset_manifest_file; } -fn buildAssetCompiler(b: *Build, optimize: std.builtin.OptimizeMode) *Step.Compile { +fn buildAssetCompiler(b: *Build, optimize: std.builtin.OptimizeMode, assets_mod: *Build.Module) *Step.Compile { const assimp_dep = b.dependency("zig-assimp", .{ .target = b.host, .optimize = optimize, //.formats = @as([]const u8, "3DS,3MF,AC,AMF,ASE,Assbin,Assjson,Assxml,B3D,Blender,BVH,C4D,COB,Collada,CSM,DXF,FBX,glTF,glTF2,HMP,IFC,Irr,LWO,LWS,M3D,MD2,MD3,MD5,MDC,MDL,MMD,MS3D,NDO,NFF,Obj,OFF,Ogre,OpenGEX,Ply,Q3BSP,Q3D,Raw,SIB,SMD,Step,STEPParser,STL,Terragen,Unreal,X,X3D,XGL"), .formats = @as([]const u8, "Obj"), }); + const zalgebra_dep = b.dependency("zalgebra", .{}); const assimp_lib = assimp_dep.artifact("assimp"); + // HACK: fix in assimp + assimp_lib.defineCMacro("AI_CONFIG_FBX_USE_SKELETON_BONE_CONTAINER", "\"AI_CONFIG_FBX_USE_SKELETON_BONE_CONTAINER\""); const assetc = b.addExecutable(.{ .name = "assetc", @@ -223,13 +224,21 @@ fn buildAssetCompiler(b: *Build, optimize: std.builtin.OptimizeMode) *Step.Compi }); assetc.linkLibC(); - b.installFile("libs/ispc_texcomp/lib/ispc_texcomp.dll", "ispc_texcomp.dll"); - b.installFile("libs/ispc_texcomp/lib/ispc_texcomp.pdb", "ispc_texcomp.pdb"); + if (b.host.result.os.tag == .windows) { + b.installFile("libs/ispc_texcomp/lib/ispc_texcomp.dll", "ispc_texcomp.dll"); + b.installFile("libs/ispc_texcomp/lib/ispc_texcomp.pdb", "ispc_texcomp.pdb"); + } assetc.addLibraryPath(.{ .path = "libs/ispc_texcomp/lib" }); assetc.addIncludePath(.{ .path = "libs/ispc_texcomp/include" }); assetc.linkSystemLibrary("ispc_texcomp"); - assetc.root_module.addAnonymousImport("formats", .{ .root_source_file = .{ .path = "src/formats.zig" } }); + const zalgebra_mod = zalgebra_dep.module("zalgebra"); + const formats_mod = b.addModule("formats", .{ .root_source_file = .{ .path = "src/formats.zig" } }); + formats_mod.addImport("zalgebra", zalgebra_mod); + formats_mod.addImport("assets", assets_mod); + assetc.root_module.addImport("formats", formats_mod); + assetc.root_module.addImport("zalgebra", zalgebra_mod); + assetc.root_module.addImport("assets", assets_mod); assetc.linkLibrary(assimp_lib); assetc.linkLibC(); diff --git a/src/AssetManager.zig b/src/AssetManager.zig index dd98852..00974f5 100644 --- a/src/AssetManager.zig +++ b/src/AssetManager.zig @@ -122,6 +122,21 @@ pub fn resolveTexture(self: *AssetManager, handle: Handle.Texture) *const Loaded return self.loadTexture(handle.id); } +pub fn resolveScene(self: *AssetManager, handle: Handle.Scene) *const formats.Scene { + if (handle.id == 0) return &NullScene.scene; + + if (self.loaded_assets.getPtr(handle.id)) |asset| { + switch (asset.*) { + .scene => |*scene| { + return &scene.scene; + }, + else => unreachable, + } + } + + return &self.loadScene(handle.id).scene; +} + // TODO: proper watching pub fn watchChanges(self: *AssetManager) void { var iter = self.loaded_assets.iterator(); @@ -258,6 +273,11 @@ const NullTexture = LoadedTexture{ .handle = 0, }; +const NullScene = LoadedScene{ + .buf = "", + .scene = .{}, +}; + pub fn loadMesh(self: *AssetManager, id: AssetId) *const LoadedMesh { return self.loadMeshErr(id) catch |err| { std.log.err("Error: {} loading mesh at path: {s}", .{ err, asset_manifest.getPath(id) }); @@ -425,11 +445,41 @@ fn loadTextureErr(self: *AssetManager, id: AssetId) !*const LoadedTexture { return &self.loaded_assets.getPtr(id).?.texture; } +fn loadScene(self: *AssetManager, id: AssetId) *const LoadedScene { + return self.loadSceneErr(id) catch |err| { + std.log.err("Error: {} loading scene at path {s}\n", .{ err, asset_manifest.getPath(id) }); + + return &NullScene; + }; +} + +fn loadSceneErr(self: *AssetManager, id: AssetId) !*const LoadedScene { + const path = asset_manifest.getPath(id); + const data = try self.loadFile(self.allocator, path, TEXTURE_MAX_BYTES); + + const scene = try formats.Scene.fromBuffer(data.bytes); + + try self.loaded_assets.put( + self.allocator, + id, + .{ + .scene = LoadedScene{ + .buf = data.bytes, + .scene = scene, + }, + }, + ); + try self.modified_times.put(self.allocator, id, data.modified); + + return &self.loaded_assets.getPtr(id).?.scene; +} + const LoadedAsset = union(enum) { shader: LoadedShader, shaderProgram: LoadedShaderProgram, mesh: LoadedMesh, texture: LoadedTexture, + scene: LoadedScene, }; const LoadedShader = struct { @@ -454,6 +504,12 @@ const LoadedTexture = struct { handle: gl.GLuint64, }; +const LoadedScene = struct { + // Buffer that holds scene data + buf: []const u8, + scene: formats.Scene, +}; + pub const AABB = struct { min: Vec3 = Vec3.zero(), max: Vec3 = Vec3.zero(), @@ -607,6 +663,9 @@ fn unloadAssetWithDependees(self: *AssetManager, id: AssetId) void { gl.GL_ARB_bindless_texture.makeTextureHandleNonResidentARB(texture.handle); gl.deleteTextures(1, &texture.name); }, + .scene => |*scene| { + self.allocator.free(scene.buf); + }, } } _ = self.loaded_assets.remove(id); diff --git a/src/Render.zig b/src/Render.zig index 5fa061a..963c1f8 100644 --- a/src/Render.zig +++ b/src/Render.zig @@ -4,6 +4,7 @@ const c = @import("sdl.zig"); const AssetManager = @import("AssetManager.zig"); const a = @import("asset_manifest"); const globals = @import("globals.zig"); +pub const Material = @import("formats.zig").Material; const za = @import("zalgebra"); const Vec2 = za.Vec2; @@ -27,9 +28,10 @@ mesh_vao: gl.GLuint = 0, tripple_buffer_index: usize = MAX_FRAMES_QUEUED - 1, gl_fences: [MAX_FRAMES_QUEUED]?gl.GLsync = [_]?gl.GLsync{null} ** MAX_FRAMES_QUEUED, camera_ubo: gl.GLuint = 0, -camera_matrices: []CameraMatrices = &.{}, +camera_matrices: []u8 = &.{}, point_lights_ubo: gl.GLuint = 0, -point_lights: []PointLightArray = &.{}, +point_lights: []u8 = &.{}, +ubo_align: usize = 0, pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetman: *AssetManager) Render { var render = Render{ @@ -38,6 +40,13 @@ pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetm .assetman = assetman, }; + var buffer_align_int: gl.GLint = 0; + gl.getIntegerv(gl.UNIFORM_BUFFER_OFFSET_ALIGNMENT, &buffer_align_int); + + if (buffer_align_int == 0) @panic("Failed to query GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT"); + + render.ubo_align = @intCast(buffer_align_int); + // MESH VAO var vao: gl.GLuint = 0; gl.createVertexArrays(1, &vao); @@ -72,17 +81,23 @@ pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetm gl.createBuffers(1, &render.camera_ubo); std.debug.assert(render.camera_ubo != 0); + const buf_size = render.uboAlignedSizeOf(CameraMatrices) * MAX_FRAMES_QUEUED; gl.namedBufferStorage( render.camera_ubo, - @sizeOf(CameraMatrices) * MAX_FRAMES_QUEUED, + @intCast(buf_size), null, PERSISTENT_BUFFER_FLAGS, ); - const camera_matrices_c: [*c]CameraMatrices = @alignCast(@ptrCast(gl.mapNamedBufferRange(render.camera_ubo, 0, @sizeOf(CameraMatrices) * MAX_FRAMES_QUEUED, PERSISTENT_BUFFER_FLAGS) orelse { + const camera_matrices_c: [*]u8 = @ptrCast(gl.mapNamedBufferRange( + render.camera_ubo, + 0, + @intCast(buf_size), + PERSISTENT_BUFFER_FLAGS, + ) orelse { checkGLError(); @panic("bind camera_ubo"); - })); - render.camera_matrices = camera_matrices_c[0..MAX_FRAMES_QUEUED]; + }); + render.camera_matrices = camera_matrices_c[0..buf_size]; } // Point lights ubo @@ -90,22 +105,23 @@ pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetm gl.createBuffers(1, &render.point_lights_ubo); std.debug.assert(render.camera_ubo != 0); + const buf_size = render.uboAlignedSizeOf(PointLightArray) * MAX_FRAMES_QUEUED; gl.namedBufferStorage( render.point_lights_ubo, - @sizeOf(PointLightArray) * MAX_FRAMES_QUEUED, + @intCast(buf_size), null, PERSISTENT_BUFFER_FLAGS, ); - const point_lights_c: [*c]PointLightArray = @alignCast(@ptrCast(gl.mapNamedBufferRange( + const point_lights_c: [*]u8 = @ptrCast(gl.mapNamedBufferRange( render.point_lights_ubo, 0, - @sizeOf(PointLightArray) * MAX_FRAMES_QUEUED, + @intCast(buf_size), PERSISTENT_BUFFER_FLAGS, ) orelse { checkGLError(); @panic("bind point_lights_ubo"); - })); - render.point_lights = point_lights_c[0..MAX_FRAMES_QUEUED]; + }); + render.point_lights = point_lights_c[0..buf_size]; } return render; @@ -123,7 +139,7 @@ pub fn begin(self: *Render) void { gl.bindVertexArray(self.mesh_vao); if (self.gl_fences[self.tripple_buffer_index]) |fence| { - const syncResult = gl.clientWaitSync(fence, gl.SYNC_FLUSH_COMMANDS_BIT, 9999999); + const syncResult = gl.clientWaitSync(fence, gl.SYNC_FLUSH_COMMANDS_BIT, 9999999999); switch (syncResult) { gl.ALREADY_SIGNALED => { @@ -132,6 +148,7 @@ pub fn begin(self: *Render) void { gl.TIMEOUT_EXPIRED => { // oh no, driver will crash soon :( std.log.err("OpenGL clientWaitSync timeout expired D:\n", .{}); + checkGLError(); }, gl.CONDITION_SATISFIED => { // awesome @@ -146,35 +163,43 @@ pub fn begin(self: *Render) void { } self.gl_fences[self.tripple_buffer_index] = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0); +} + +pub fn getPointLights(self: *Render) *PointLightArray { + return @alignCast(@ptrCast(self.point_lights[self.tripple_buffer_index * self.uboAlignedSizeOf(PointLightArray) ..].ptr)); +} + +pub fn flushUBOs(self: *Render) void { + const idx = self.tripple_buffer_index; { - const camera_matrix = &self.camera_matrices[self.tripple_buffer_index]; + const camera_matrix: *CameraMatrices = @alignCast(@ptrCast(self.camera_matrices[idx * self.uboAlignedSizeOf(CameraMatrices) ..].ptr)); camera_matrix.* = .{ .projection = self.camera.projection(), .view = self.camera.view_mat, }; + //gl.flushMappedNamedBufferRange(self.camera_ubo, idx * @sizeOf(CameraMatrices), @sizeOf(CameraMatrices)); gl.bindBufferRange( gl.UNIFORM_BUFFER, UBO.CameraMatrices.value(), self.camera_ubo, - self.tripple_buffer_index * @sizeOf(CameraMatrices), - @sizeOf(CameraMatrices), + idx * self.uboAlignedSizeOf(CameraMatrices), + @intCast(self.uboAlignedSizeOf(CameraMatrices)), ); + checkGLError(); } + // gl.flushMappedNamedBufferRange(self.point_lights_ubo, idx * @sizeOf(PointLightArray), @sizeOf(PointLightArray)); gl.bindBufferRange( gl.UNIFORM_BUFFER, UBO.PointLights.value(), self.point_lights_ubo, - self.tripple_buffer_index * @sizeOf(PointLightArray), - @sizeOf(PointLightArray), + idx * self.uboAlignedSizeOf(PointLightArray), + @intCast(self.uboAlignedSizeOf(PointLightArray)), ); -} - -pub fn getPointLights(self: *Render) *PointLightArray { - return &self.point_lights[self.tripple_buffer_index]; + checkGLError(); } pub fn draw(self: *Render, cmd: DrawCommand) void { @@ -321,14 +346,6 @@ pub const PointLightArray = extern struct { count: c_uint, }; -pub const Material = struct { - albedo: Vec3 = Vec3.one(), - albedo_map: AssetManager.Handle.Texture = .{}, - normal_map: AssetManager.Handle.Texture = .{}, - metallic: f32 = 0, - metallic_map: AssetManager.Handle.Texture = .{}, - roughness: f32 = 1, - roughness_map: AssetManager.Handle.Texture = .{}, - emission: f32 = 0, - emission_map: AssetManager.Handle.Texture = .{}, -}; +fn uboAlignedSizeOf(self: *const Render, comptime T: type) usize { + return std.mem.alignForward(usize, @sizeOf(T), self.ubo_align); +} diff --git a/src/assets/root.zig b/src/assets/root.zig index 4fd5475..701c24a 100644 --- a/src/assets/root.zig +++ b/src/assets/root.zig @@ -1,8 +1,9 @@ pub const AssetId = u64; pub const Handle = struct { + pub const Scene = extern struct { id: AssetId = 0 }; pub const Shader = extern struct { id: AssetId = 0 }; pub const ShaderProgram = extern struct { id: AssetId = 0 }; - pub const Mesh = struct { id: AssetId = 0 }; - pub const Texture = struct { id: AssetId = 0 }; + pub const Mesh = extern struct { id: AssetId = 0 }; + pub const Texture = extern struct { id: AssetId = 0 }; }; diff --git a/src/formats.zig b/src/formats.zig index 67cfcc6..55a0344 100644 --- a/src/formats.zig +++ b/src/formats.zig @@ -1,6 +1,9 @@ const std = @import("std"); const builtin = @import("builtin"); const Handle = @import("assets").Handle; +const za = @import("zalgebra"); +const Vec3 = za.Vec3; +pub const Entity = @import("globals.zig").Entity; pub const native_endian = builtin.cpu.arch.endian(); @@ -229,6 +232,10 @@ pub const Texture = struct { .data = data, }; } + + pub fn free(self: *Texture, allocator: std.mem.Allocator) void { + allocator.free(self.data); + } }; // TODO: this doesn't respect endiannes at all @@ -245,19 +252,111 @@ test "texture write/parse" { var data = [_]u8{ 'h', 'e', 'l', 'l', 'o' }; const source = Texture{ .header = .{ - .format = .bc7_srgb, + .format = .bc7, .width = 123, .height = 234, - .mip_levels = 1, - .size = data.len, + .mip_count = 1, }, - .data = &data, + .data = &.{&data}, }; - var buf: [@sizeOf(Texture.Header) + data.len]u8 = undefined; + var buf: [@sizeOf(Texture.Header) + data.len + 4]u8 = undefined; var stream = std.io.fixedBufferStream(&buf); - try writeTexture(stream.writer(), source); + try writeTexture(stream.writer(), source, native_endian); - const decoded = try Texture.fromBuffer(&buf); + var decoded = try Texture.fromBuffer(std.testing.allocator, &buf); + defer decoded.free(std.testing.allocator); try std.testing.expectEqualDeep(source, decoded); } + +pub const Scene = struct { + pub const Header = extern struct { + magic: [4]u8 = [_]u8{ 'S', 'C', 'N', 'F' }, // Scene Format + entity_count: u32 = 0, + }; + + header: Header = .{}, + + entities: []align(1) const Entity.Data = &.{}, + /// Store parenting info for each entity + /// NOTE: Parent index should never be larger than entity index + /// because entities will be created in order + /// -1 means no parent + parents: []align(1) const i64 = &.{}, + + pub fn fromBuffer(buf: []u8) !Scene { + const header_ptr: *align(1) Header = @ptrCast(buf.ptr); + const header = header_ptr.*; + + if (!std.mem.eql(u8, &header.magic, "SCNF")) { + return error.MagicMatch; + } + + var offset: usize = @sizeOf(Header); + var size: usize = @sizeOf(Entity.Data) * header.entity_count; + const entities: []align(1) Entity.Data = std.mem.bytesAsSlice(Entity.Data, buf[offset .. offset + size]); + offset += size; + size = @sizeOf(i64) * header.entity_count; + const parents: []align(1) i64 = std.mem.bytesAsSlice(i64, buf[offset .. offset + size]); + offset += size; + + return Scene{ + .header = header, + .entities = entities, + .parents = parents, + }; + } +}; + +// TODO: this doesn't respect endiannes at all +pub fn writeScene(writer: anytype, value: Scene, endian: std.builtin.Endian) !void { + try writer.writeStruct(value.header); + + // TODO: make writeSlice? + for (value.entities) |ent| { + try writer.writeStruct(ent); + } + for (value.parents) |parentIdx| { + try writer.writeInt(i64, parentIdx, endian); + } +} + +test "write and read scene" { + var entities = [_]Entity.Data{ + .{ + .flags = .{ .point_light = true }, + .transform = .{}, + }, + .{ + .flags = .{ .point_light = true }, + .transform = .{ .pos = Vec3.new(1, 2, 3) }, + }, + }; + var parents = [_]i64{-1} ** entities.len; + const source = Scene{ + .header = .{ + .entity_count = entities.len, + }, + .entities = &entities, + .parents = &parents, + }; + + var buf: [@sizeOf(Scene.Header) + entities.len * @sizeOf(Entity.Data) + entities.len * @sizeOf(i64)]u8 = undefined; + var stream = std.io.fixedBufferStream(&buf); + try writeScene(stream.writer(), source, native_endian); + + const decoded = try Scene.fromBuffer(&buf); + try std.testing.expectEqualDeep(source, decoded); +} + +pub const Material = extern struct { + albedo: Vec3 = Vec3.one(), + albedo_map: Handle.Texture = .{}, + normal_map: Handle.Texture = .{}, + metallic: f32 = 0, + metallic_map: Handle.Texture = .{}, + roughness: f32 = 1, + roughness_map: Handle.Texture = .{}, + emission: f32 = 0, + emission_map: Handle.Texture = .{}, +}; diff --git a/src/game.zig b/src/game.zig index 9c29d1a..402a954 100644 --- a/src/game.zig +++ b/src/game.zig @@ -127,6 +127,7 @@ export fn game_init(global_allocator: *std.mem.Allocator) void { .frame_fba = std.heap.FixedBufferAllocator.init(frame_arena_buffer), .assetman = AssetManager.init(global_allocator.*, globals.g_mem.frame_fba.allocator()), .render = Render.init(global_allocator.*, globals.g_mem.frame_fba.allocator(), &globals.g_mem.assetman), + .world = .{ .frame_arena = globals.g_mem.frame_fba.allocator() }, }; globals.g_mem.render.camera = &globals.g_mem.free_cam.camera; std.log.debug("actual ptr: {}, correct ptr {}", .{ globals.g_mem.assetman.frame_arena.ptr, globals.g_mem.frame_fba.allocator().ptr }); @@ -142,23 +143,29 @@ export fn game_init(global_allocator: *std.mem.Allocator) void { gl.viewport(0, 0, globals.g_init.width, globals.g_init.height); + const light_root = globals.g_mem.world.addEntity(.{ + .flags = .{ .rotate = true }, + .transform = .{ .pos = Vec3.new(0, 0.1, 0) }, + .rotate = .{ .axis = Vec3.up(), .rate = 60 }, + }); + const light1 = globals.g_mem.world.addEntity(.{ .transform = .{ .pos = Vec3.new(1.8, 1, 0) }, .flags = .{ .point_light = true, .rotate = true }, .point_light = .{ .color_intensity = Vec4.new(1.0, 0.3, 0.1, 100.0), .radius = 0.1 }, - .rotate = .{ .axis = Vec3.up(), .rate = 60 }, + .rotate = .{ .axis = Vec3.up(), .rate = -40 }, }); + light1.ptr.setParent(light_root.handle); - _ = globals.g_mem.world.addEntity(.{ + const light2 = globals.g_mem.world.addEntity(.{ .transform = .{ .pos = Vec3.new(-2, 0, 0) }, - .parent = light1, .flags = .{ .point_light = true, .rotate = true }, .point_light = .{ .color_intensity = Vec4.new(0.2, 0.5, 1.0, 100.0), .radius = 0.1, }, - .rotate = .{ .axis = Vec3.up(), .rate = -20 }, }); + light2.ptr.setParent(light1.handle); _ = globals.g_mem.world.addEntity(.{ .transform = .{ .pos = Vec3.new(1, 0.5, 4) }, @@ -219,6 +226,10 @@ export fn game_init(global_allocator: *std.mem.Allocator) void { }); } } + + const test_scene_root = globals.g_mem.world.createScene(globals.g_assetman.resolveScene(a.Scenes.test_scene.scene)); + + std.log.debug("test scene root idx {}\n", .{test_scene_root.idx}); } export fn game_update() bool { @@ -257,10 +268,11 @@ export fn game_update() bool { }, c.SDL_KEYUP, c.SDL_KEYDOWN => { const pressed = event.key.state == c.SDL_PRESSED; + switch (event.key.keysym.scancode) { // Toggle fullscreen - c.SDL_SCANCODE_F11 => { - if (event.type == c.SDL_KEYDOWN) { + c.SDL_SCANCODE_RETURN => { + if (event.type == c.SDL_KEYDOWN and event.key.keysym.mod & c.KMOD_ALT > 0) { toggleFullScreen() catch continue; } }, @@ -344,15 +356,27 @@ export fn game_update() bool { move.zMut().* -= 1; } - // TODO: make this an entity - gmem.free_cam.update(gmem.delta_time, move, look.scale(0.008)); - const f_width: f32 = @floatFromInt(ginit.width); const f_height: f32 = @floatFromInt(ginit.height); gmem.free_cam.camera.aspect = f_width / f_height; gmem.rotation += 60 * gmem.delta_time; + // TODO: make this an entity + gmem.free_cam.update(gmem.delta_time, move, look.scale(0.008)); + + // Update + { + for (gmem.world.entities[0..gmem.world.entity_count]) |*ent| { + if (!ent.data.flags.active) continue; + + if (ent.data.flags.rotate) { + ent.data.transform.rotate(ent.data.rotate.axis, ent.data.rotate.rate * gmem.delta_time); + } + } + } + + // Render { gmem.render.begin(); defer gmem.render.finish(); @@ -364,25 +388,16 @@ export fn game_update() bool { for (0..gmem.world.entity_count) |i| { const ent = &gmem.world.entities[i]; - if (!ent.flags.active) continue; + if (!ent.data.flags.active) continue; - if (ent.flags.rotate) { - const old_pos = ent.transform.pos; - const new_pos = Mat4.fromRotation( - ent.rotate.rate * gmem.delta_time, - ent.rotate.axis, - ).mulByVec4(Vec4.new(old_pos.x(), old_pos.y(), old_pos.z(), 1)); - ent.transform.setPos(Vec3.new(new_pos.x(), new_pos.y(), new_pos.z())); - } - - if (ent.flags.point_light) { + if (ent.data.flags.point_light) { const pos = ent.globalMatrix(&gmem.world).extractTranslation(); var pos4 = Vec4.new(pos.x(), pos.y(), pos.z(), 1.0); pos4 = gmem.render.camera.view_mat.mulByVec4(pos4); point_lights.lights[point_lights.count] = .{ - .pos_radius = Vec4.new(pos4.x(), pos4.y(), pos4.z(), ent.point_light.radius), - .color_intensity = ent.point_light.color_intensity, + .pos_radius = Vec4.new(pos4.x(), pos4.y(), pos4.z(), ent.data.point_light.radius), + .color_intensity = ent.data.point_light.color_intensity, }; point_lights.count += 1; if (point_lights.count == Render.MAX_POINT_LIGHTS) { @@ -390,25 +405,27 @@ export fn game_update() bool { } } } - } - // Render meshes and lights - for (0..gmem.world.entity_count) |i| { - const ent = &gmem.world.entities[i]; - if (!ent.flags.active) continue; + gmem.render.flushUBOs(); - if (ent.flags.mesh) { - gmem.render.draw(.{ - .mesh = ent.mesh.handle, - .material = ent.mesh.material, - .transform = ent.globalMatrix(&gmem.world).*, - }); - } else if (ent.flags.point_light) { - gmem.render.draw(.{ - .mesh = a.Meshes.sphere, - .material = .{ .albedo = ent.point_light.color() }, - .transform = ent.globalMatrix(&gmem.world).*, - }); + // Render meshes and lights + for (0..gmem.world.entity_count) |i| { + const ent = &gmem.world.entities[i]; + if (!ent.data.flags.active) continue; + + if (ent.data.flags.mesh) { + gmem.render.draw(.{ + .mesh = ent.data.mesh.handle, + .material = ent.data.mesh.material, + .transform = ent.globalMatrix(&gmem.world).*, + }); + } else if (ent.data.flags.point_light) { + gmem.render.draw(.{ + .mesh = a.Meshes.sphere, + .material = .{ .albedo = ent.data.point_light.color() }, + .transform = ent.globalMatrix(&gmem.world).*, + }); + } } } } diff --git a/src/gen/asset_manifest.zig b/src/gen/asset_manifest.zig index 8a814f5..f396c8f 100644 --- a/src/gen/asset_manifest.zig +++ b/src/gen/asset_manifest.zig @@ -1,5 +1,6 @@ pub const manifest = @import("asset_manifest_gen"); +pub const Scenes = manifest.Scenes; pub const Meshes = manifest.Meshes; pub const Shaders = manifest.Shaders; pub const ShaderPrograms = manifest.ShaderPrograms; diff --git a/src/globals.zig b/src/globals.zig index 055f0e7..fdead6e 100644 --- a/src/globals.zig +++ b/src/globals.zig @@ -2,6 +2,9 @@ const std = @import("std"); const c = @import("sdl.zig"); const AssetManager = @import("AssetManager.zig"); const Render = @import("Render.zig"); +const formats = @import("formats.zig"); +const Material = formats.Material; +const Scene = formats.Scene; const za = @import("zalgebra"); const Vec2 = za.Vec2; @@ -37,9 +40,10 @@ pub const Entity = struct { mesh: bool = false, point_light: bool = false, rotate: bool = false, + _pad: u4 = 0, // make it abi sized }; - pub const Transform = struct { + pub const Transform = extern struct { pos: Vec3 = Vec3.zero(), rot: Quat = Quat.identity(), scale: Vec3 = Vec3.one(), @@ -64,13 +68,18 @@ pub const Entity = struct { self.pos = self.pos.add(by); self.dirty(); } + + pub fn rotate(self: *Transform, axis: Vec3, angle: f32) void { + self.rot = self.rot.mul(Quat.fromAxis(angle, axis)); + self.dirty(); + } }; - pub const Mesh = struct { + pub const Mesh = extern struct { handle: AssetManager.Handle.Mesh = .{}, - material: Render.Material = .{}, + material: Material = .{}, }; - pub const PointLight = struct { + pub const PointLight = extern struct { radius: f32 = std.math.floatEps(f32), // should never be 0 or bad things happen color_intensity: Vec4 = Vec4.one(), // x, y, z - color, w - intensity @@ -79,42 +88,53 @@ pub const Entity = struct { return Vec3.new(col.x(), col.y(), col.z()); } }; - pub const Rotate = struct { + pub const Rotate = extern struct { axis: Vec3 = Vec3.up(), rate: f32 = 0, // deg/s }; + /// Serializable entity data + pub const Data = extern struct { + flags: Flags = .{}, + transform: Transform = .{}, + mesh: Mesh = .{}, + point_light: PointLight = .{}, + rotate: Rotate = .{}, + }; + // Entity list and handle management idx: u32 = 0, gen: u32 = 0, // Free list next: ?*Entity = null, - flags: Flags = .{}, parent: ?EntityHandle = null, - transform: Transform = .{}, - mesh: Mesh = .{}, - point_light: PointLight = .{}, - rotate: Rotate = .{}, + + data: Data = .{}, + + pub fn setParent(self: *Entity, parent: ?EntityHandle) void { + self.parent = parent; + self.data.transform.dirty(); + } pub fn localMatrix(self: *Entity) *const Mat4 { - if (self.transform._local_dirty) { - self.transform._local = Mat4.recompose(self.transform.pos, self.transform.rot, self.transform.scale); - self.transform._local_dirty = false; + if (self.data.transform._local_dirty) { + self.data.transform._local = Mat4.recompose(self.data.transform.pos, self.data.transform.rot, self.data.transform.scale); + self.data.transform._local_dirty = false; } - return &self.transform._local; + return &self.data.transform._local; } pub fn globalMatrix(self: *Entity, world: *World) *const Mat4 { // TODO: think how to reduce pointer chasing if (self.parent) |parent_ent| { if (world.getEntity(parent_ent)) |parent| { - if (parent.transform._global_dirty or self.transform._global_dirty) { - self.transform._global = parent.globalMatrix(world).mul(self.localMatrix().*); - self.transform._global_dirty = false; + if (parent.data.transform._global_dirty or self.data.transform._global_dirty) { + self.data.transform._global = parent.globalMatrix(world).mul(self.localMatrix().*); + self.data.transform._global_dirty = false; } - return &self.transform._global; + return &self.data.transform._global; } } @@ -123,16 +143,28 @@ pub const Entity = struct { }; pub const EntityHandle = packed struct { - idx: u32, - gen: u32, + idx: u32 = 0, + gen: u32 = 0, + + /// Returns handle with 0 idx and gen. + /// 0 gen is invalid, valid generations start from 0 + pub fn invalid() EntityHandle { + return EntityHandle{}; + } +}; + +pub const EntityCreateResult = struct { + handle: EntityHandle, + ptr: *Entity, }; pub const World = struct { + frame_arena: std.mem.Allocator, entities: [MAX_ENTITIES]Entity = [_]Entity{.{}} ** MAX_ENTITIES, entity_count: usize = 0, free_entity: ?*Entity = null, - pub fn addEntity(self: *World, entity: Entity) EntityHandle { + pub fn addEntity(self: *World, data: Entity.Data) EntityCreateResult { const ent = result: { if (self.free_entity) |ent| { break :result ent; @@ -146,16 +178,44 @@ pub const World = struct { const next = ent.next; const gen = ent.gen; const idx = ent.idx; - ent.* = entity; + ent.data = data; + // TODO: handle wrapping ent.gen = gen + 1; ent.idx = idx; - ent.flags.active = true; + ent.data.flags.active = true; self.free_entity = next; - return EntityHandle{ .idx = idx, .gen = ent.gen }; + return EntityCreateResult{ + .handle = EntityHandle{ .idx = idx, .gen = ent.gen }, + .ptr = ent, + }; + } + + /// Spawns a scene and returns a hand to the root entity + pub fn createScene(self: *World, scene: *const Scene) EntityHandle { + if (scene.entities.len == 0) { + return EntityHandle.invalid(); + } + + const handles = self.frame_arena.alloc(EntityHandle, scene.entities.len) catch @panic("OOM"); // not handling error, this is unrecoverable + + for (0.., handles, scene.entities, scene.parents) |i, *out_handle, *ent, parent| { + const res = self.addEntity(ent.*); + out_handle.* = res.handle; + + if (parent >= 0) { + std.debug.assert(parent < i); + res.ptr.parent = handles[@intCast(parent)]; + } + } + + return handles[0]; } pub fn getEntity(self: *World, handle: EntityHandle) ?*Entity { + // Gen 0 is always invalid + if (handle.gen == 0) return null; + const ent = &self.entities[handle.idx]; if (ent.gen != handle.gen) { return null; @@ -183,7 +243,7 @@ pub const GameMemory = struct { input_state: InputState = .{}, free_cam: FreeLookCamera = .{}, mouse_focus: bool = false, - world: World = .{}, + world: World, }; pub const InputState = packed struct { diff --git a/tools/asset_compiler.zig b/tools/asset_compiler.zig index 5f8e499..dec0b9a 100644 --- a/tools/asset_compiler.zig +++ b/tools/asset_compiler.zig @@ -7,6 +7,8 @@ const asset_list = @import("asset_list.zig"); const AssetListEntry = asset_list.AssetListEntry; const Vector2 = formats.Vector2; const Vector3 = formats.Vector3; +const za = @import("zalgebra"); +const Vec3 = za.Vec3; const c = @cImport({ @cInclude("assimp/cimport.h"); @cInclude("assimp/scene.h"); @@ -22,8 +24,8 @@ const c = @cImport({ const ASSET_MAX_BYTES = 1024 * 1024 * 1024; pub fn resolveAssetTypeByExtension(path: []const u8) ?AssetType { - if (std.mem.endsWith(u8, path, ".obj")) { - return .Mesh; + if (std.mem.endsWith(u8, path, ".obj") or std.mem.endsWith(u8, path, ".fbx")) { + return .Scene; } if (std.mem.endsWith(u8, path, ".prog")) { return .ShaderProgram; @@ -69,11 +71,11 @@ pub fn main() !void { std.log.debug("rel_input: {s}", .{rel_input}); switch (asset_type) { - .Mesh => try processMesh(allocator, rel_input, output_dir, asset_list_writer), + .Scene => try processScene(allocator, rel_input, output_dir, asset_list_writer), .Shader => try copyFile(asset_type, rel_input, output_dir, asset_list_writer), .ShaderProgram => try processShaderProgram(allocator, rel_input, output_dir, asset_list_writer), .Texture => try processTexture(allocator, rel_input, output_dir, asset_list_writer), - .Scene => return error.NotImplemented, + else => unreachable, } try buf_asset_list_writer.flush(); } @@ -95,7 +97,12 @@ fn copyFile(_type: AssetType, input: []const u8, output_dir: std.fs.Dir, asset_l try asset_list.writeAssetListEntryText(asset_list_writer, asset_list_entry); } -fn createOutput(_type: AssetType, asset_path: AssetPath, output_dir: std.fs.Dir, writer: anytype) !std.fs.File { +const AssetOutput = struct { + file: std.fs.File, + list_entry: AssetListEntry, +}; + +fn createOutput(_type: AssetType, asset_path: AssetPath, output_dir: std.fs.Dir, writer: anytype) !AssetOutput { const asset_list_entry = AssetListEntry{ .type = _type, .src_path = asset_path, @@ -109,10 +116,18 @@ fn createOutput(_type: AssetType, asset_path: AssetPath, output_dir: std.fs.Dir, try asset_list.writeAssetListEntryText(writer, asset_list_entry); - return try output_subdir.createFile(std.fs.path.basename(out_path), .{}); + const file = try output_subdir.createFile(std.fs.path.basename(out_path), .{}); + + return AssetOutput{ + .file = file, + .list_entry = asset_list_entry, + }; } -fn processMesh(allocator: std.mem.Allocator, input: []const u8, output_dir: std.fs.Dir, asset_list_writer: anytype) !void { +/// This can output either a single mesh (for simple formats like obj) +/// or a scene + a bunch of sub assets (meshes, materials, textures, animations, etc.) +/// It all depends on the source asset. +fn processScene(allocator: std.mem.Allocator, input: []const u8, output_dir: std.fs.Dir, asset_list_writer: anytype) !void { const input_z = try std.mem.concatWithSentinel(allocator, u8, &.{input}, 0); const maybe_scene: ?*const c.aiScene = @ptrCast(c.aiImportFile( input_z.ptr, @@ -126,10 +141,103 @@ fn processMesh(allocator: std.mem.Allocator, input: []const u8, output_dir: std. defer c.aiReleaseImport(scene); if (scene.mNumMeshes == 0) return error.NoMeshes; - if (scene.mNumMeshes > 1) return error.TooManyMeshes; - const mesh: *c.aiMesh = @ptrCast(scene.mMeshes[0]); + if (scene.mNumMeshes == 1) { + const mesh: *c.aiMesh = @ptrCast(scene.mMeshes[0]); + var output = try createOutput(.Mesh, AssetPath{ .simple = input }, output_dir, asset_list_writer); + defer output.file.close(); + + return try processMesh(allocator, scene, mesh, output.file); + } else { + const base_asset_path = AssetPath{ .simple = input }; + + const meshes: []*c.aiMesh = @ptrCast(scene.mMeshes[0..@intCast(scene.mNumMeshes)]); + + var mesh_outputs = std.ArrayList(AssetListEntry).init(allocator); + + for (meshes) |mesh| { + const name = mesh.mName.data[0..mesh.mName.length]; + + var output = try createOutput(.Mesh, base_asset_path.subPath(name), output_dir, asset_list_writer); + defer output.file.close(); + + try mesh_outputs.append(output.list_entry); + + try processMesh(allocator, scene, mesh, output.file); + } + + if (scene.mRootNode == null) return; + + var node_to_entity_idx = std.AutoHashMap(*c.aiNode, usize).init(allocator); + var entities = std.ArrayList(formats.Entity.Data).init(allocator); + var parents = std.ArrayList(i64).init(allocator); + + // Breadth first traversal + var nodeq = std.ArrayList(*c.aiNode).init(allocator); + try nodeq.append(@ptrCast(scene.mRootNode)); + + while (nodeq.popOrNull()) |node| { + if (node.mChildren != null) { + const children: []*c.aiNode = @ptrCast(node.mChildren[0..@intCast(node.mNumChildren)]); + for (0..children.len) |i| { + // Reverse order, because pop taks from end of the list + const child = children[children.len - i - 1]; + try nodeq.append(child); + } + } + + try entities.append(.{}); + const idx = entities.items.len - 1; + try node_to_entity_idx.put(node, idx); + + const maybe_parent: ?*c.aiNode = @ptrCast(node.mParent); + if (maybe_parent) |parent| { + const parent_idx = node_to_entity_idx.get(parent) orelse return error.MissingParentIdx; // this is a bug in our code + try parents.append(@intCast(parent_idx)); + } else { + try parents.append(-1); + } + + const ent = &entities.items[idx]; + + // TODO: extract transform + + if (node.mMeshes != null) { + const mesh_indices = node.mMeshes[0..node.mNumMeshes]; + for (mesh_indices) |mesh_idx| { + const mesh_entry = mesh_outputs.items[@intCast(mesh_idx)]; + ent.flags.mesh = true; + ent.flags.rotate = true; + + // TODO: turn multiple meshes into sub-entities + ent.mesh = .{ + .handle = .{ .id = mesh_entry.src_path.hash() }, + // TODO: extract material + }; + ent.rotate = .{ .axis = Vec3.up(), .rate = 80 }; + } + } + } + + const out_scene = formats.Scene{ + .header = .{ + .entity_count = @intCast(entities.items.len), + }, + .entities = entities.items, + .parents = parents.items, + }; + + const output = try createOutput(.Scene, base_asset_path.subPath("scene"), output_dir, asset_list_writer); + defer output.file.close(); + var buf_writer = std.io.bufferedWriter(output.file.writer()); + try formats.writeScene(buf_writer.writer(), out_scene, formats.native_endian); + try buf_writer.flush(); + } +} + +fn processMesh(allocator: std.mem.Allocator, scene: *const c.aiScene, mesh: *const c.aiMesh, out_file: std.fs.File) !void { + _ = scene; // autofix if (mesh.mNormals == null) return error.MissingNormals; if (mesh.mTangents == null) return error.MissingTangents; if (mesh.mTextureCoords[0] == null) return error.MissingUVs; @@ -196,10 +304,7 @@ fn processMesh(allocator: std.mem.Allocator, input: []const u8, output_dir: std. .indices = indices, }; - var out_file = try createOutput(.Mesh, AssetPath{ .simple = input }, output_dir, asset_list_writer); - defer out_file.close(); var buf_writer = std.io.bufferedWriter(out_file.writer()); - try formats.writeMesh( buf_writer.writer(), out_mesh, @@ -236,9 +341,9 @@ fn processShaderProgram(allocator: std.mem.Allocator, input: []const u8, output_ return error.InvalidShaderPath; } - var out_file = try createOutput(.ShaderProgram, AssetPath{ .simple = input }, output_dir, asset_list_writer); - defer out_file.close(); - var buf_writer = std.io.bufferedWriter(out_file.writer()); + const output = try createOutput(.ShaderProgram, AssetPath{ .simple = input }, output_dir, asset_list_writer); + defer output.file.close(); + var buf_writer = std.io.bufferedWriter(output.file.writer()); try formats.writeShaderProgram(buf_writer.writer(), shader_asset_id, program.value.vertex, program.value.fragment, formats.native_endian); try buf_writer.flush(); @@ -347,9 +452,9 @@ fn processTexture(allocator: std.mem.Allocator, input: []const u8, output_dir: s .data = out_data, }; - const out_file = try createOutput(.Texture, AssetPath{ .simple = input }, output_dir, asset_list_writer); - defer out_file.close(); - var buf_writer = std.io.bufferedWriter(out_file.writer()); + const output = try createOutput(.Texture, AssetPath{ .simple = input }, output_dir, asset_list_writer); + defer output.file.close(); + var buf_writer = std.io.bufferedWriter(output.file.writer()); try formats.writeTexture(buf_writer.writer(), texture, formats.native_endian); try buf_writer.flush();