diff --git a/.vscode/launch.json b/.vscode/launch.json index c869f5c..0417348 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,6 +11,15 @@ "target": "./zig-out/learnopengl", "cwd": "${workspaceRoot}", "valuesFormatting": "parseText" + }, + { + "name": "Assetc", + "type": "gdb", + "request": "launch", + "target": "./zig-out/assetc", + "arguments": "./assets/shaders/triangle.prog ./out/assets", + "cwd": "${workspaceRoot}", + "valuesFormatting": "parseText" } ] } \ No newline at end of file diff --git a/assets/bistro.glb b/assets/bistro.glb deleted file mode 100644 index f49154f..0000000 --- a/assets/bistro.glb +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4cc5e42450cd88527529ba80fc6fd60ceb26b93adb71eb8a6a2adba20ced2f5e -size 1306880024 diff --git a/assets/shaders/bloom_downsample.prog b/assets/shaders/bloom_downsample.prog1 similarity index 100% rename from assets/shaders/bloom_downsample.prog rename to assets/shaders/bloom_downsample.prog1 diff --git a/assets/shaders/bloom_upsample.prog b/assets/shaders/bloom_upsample.prog1 similarity index 100% rename from assets/shaders/bloom_upsample.prog rename to assets/shaders/bloom_upsample.prog1 diff --git a/assets/shaders/cube_shadow.prog b/assets/shaders/cube_shadow.prog1 similarity index 100% rename from assets/shaders/cube_shadow.prog rename to assets/shaders/cube_shadow.prog1 diff --git a/assets/shaders/debug.prog b/assets/shaders/debug.prog deleted file mode 100644 index b65b560..0000000 --- a/assets/shaders/debug.prog +++ /dev/null @@ -1,6 +0,0 @@ -{ - "shader": "debug.glsl", - "vertex": true, - "fragment": true, - "compute": false -} diff --git a/assets/shaders/mesh.prog b/assets/shaders/mesh.prog1 similarity index 100% rename from assets/shaders/mesh.prog rename to assets/shaders/mesh.prog1 diff --git a/assets/shaders/post_process.prog b/assets/shaders/post_process.prog1 similarity index 100% rename from assets/shaders/post_process.prog rename to assets/shaders/post_process.prog1 diff --git a/assets/shaders/shadow.prog b/assets/shaders/shadow.prog1 similarity index 100% rename from assets/shaders/shadow.prog rename to assets/shaders/shadow.prog1 diff --git a/assets/shaders/triangle.glsl b/assets/shaders/triangle.glsl new file mode 100644 index 0000000..e73e51e --- /dev/null +++ b/assets/shaders/triangle.glsl @@ -0,0 +1,23 @@ +#if VERTEX_SHADER + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); +} + +#endif + +#if FRAGMENT_SHADER + +layout(location = 0) out vec4 FragColor; + +void main() { + FragColor = vec4(0.6, 0.8, 1.0, 1.0); +} + +#endif diff --git a/assets/shaders/triangle.prog b/assets/shaders/triangle.prog new file mode 100644 index 0000000..0c7f579 --- /dev/null +++ b/assets/shaders/triangle.prog @@ -0,0 +1,5 @@ +{ + "vertex": "triangle.glsl", + "fragment": "triangle.glsl", + "compute": null +} diff --git a/assets/shaders/unlit.prog b/assets/shaders/unlit.prog1 similarity index 100% rename from assets/shaders/unlit.prog rename to assets/shaders/unlit.prog1 diff --git a/assets/shaders/z_prepass.prog b/assets/shaders/z_prepass.prog1 similarity index 100% rename from assets/shaders/z_prepass.prog rename to assets/shaders/z_prepass.prog1 diff --git a/build.zig b/build.zig index 3538ddf..1f09508 100644 --- a/build.zig +++ b/build.zig @@ -2,6 +2,8 @@ const std = @import("std"); const Build = std.Build; const Step = Build.Step; const GenerateAssetManifest = @import("tools/GenerateAssetManifest.zig"); +const asset_types = @import("tools/types.zig"); +const formats = @import("src/formats.zig"); fn buildVulkanWrapper(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *Build.Module { const registry = b.dependency("vulkan_headers", .{}).path("registry/vk.xml"); @@ -168,9 +170,8 @@ pub fn build(b: *Build) void { test_step.dependOn(&run_exe_unit_tests.step); } -const asset_extensions = [_][]const u8{ +const assetc_extensions = [_][]const u8{ "obj", - "prog", "png", "dds", "tga", @@ -179,8 +180,11 @@ const asset_extensions = [_][]const u8{ "fbx", "gltf", "glb", + "prog", }; +// const shader_program_ext = "prog"; + // Find all assets and cook them using assetc fn buildAssets(b: *std.Build, install_assetc_step: *Step, step: *Step, assetc: *Step.Compile, path: []const u8) !Build.LazyPath { const assetsPath = b.pathFromRoot(path); @@ -200,41 +204,51 @@ fn buildAssets(b: *std.Build, install_assetc_step: *Step, step: *Step, assetc: * const ext = ext_with_dot[1..]; - var is_known_ext = false; - for (asset_extensions) |known_ext| { - if (std.mem.eql(u8, known_ext, ext)) { - is_known_ext = true; - break; - } + if (shouldProcessAsset(ext)) { + const run_assetc = b.addRunArtifact(assetc); + run_assetc.rename_step_with_output_arg = false; + + gen_asset_manifest.addAssetListFile(run_assetc.captureStdOut()); + run_assetc.step.dependOn(install_assetc_step); + + run_assetc.addPathDir(b.pathFromRoot("libs/ispc_texcomp/lib")); + + run_assetc.addArg("-d"); + _ = run_assetc.addDepFileOutputArg("depfile.d"); + + // Absolute input file arg, this will add it to step deps, cache and all that good stuff + run_assetc.addFileArg(b.path(b.pathJoin(&.{ path, entry.path }))); + + // Generated output dir. Output asset(s) will be placed there at the same relative path as input + const result_dir = run_assetc.addOutputFileArg("assets"); + run_assetc.setName(b.fmt("assetc ({s})", .{entry.basename})); + + const install_assets = b.addInstallDirectory(.{ + .source_dir = result_dir, + .install_dir = .prefix, + .install_subdir = path, + }); + step.dependOn(&install_assets.step); } - if (!is_known_ext) continue; - - const run_assetc = b.addRunArtifact(assetc); - run_assetc.rename_step_with_output_arg = false; - - gen_asset_manifest.addAssetListFile(run_assetc.captureStdOut()); - run_assetc.step.dependOn(install_assetc_step); - - run_assetc.addPathDir(b.pathFromRoot("libs/ispc_texcomp/lib")); - - // Absolute input file arg, this will add it to step deps, cache and all that good stuff - run_assetc.addFileArg(b.path(b.pathJoin(&.{ path, entry.path }))); - - // Generated output dir. Output asset(s) will be placed there at the same relative path as input - const result_dir = run_assetc.addOutputFileArg("assets"); - run_assetc.setName(b.fmt("assetc ({s})", .{entry.basename})); - - const install_assets = b.addInstallDirectory(.{ - .source_dir = result_dir, - .install_dir = .prefix, - .install_subdir = path, - }); - step.dependOn(&install_assets.step); } return asset_manifest_file; } +fn resolveRelativePath(b: *std.Build, from: []const u8, to: []const u8) []const u8 { + return std.fs.path.relative(b.allocator, from, to) catch @panic("Failed to resolve relative path"); +} + +fn shouldProcessAsset(ext: []const u8) bool { + for (assetc_extensions) |known_ext| { + if (std.mem.eql(u8, known_ext, ext)) { + return true; + } + } + + return false; +} + fn buildAssetCompiler(b: *Build, optimize: std.builtin.OptimizeMode, assets_mod: *Build.Module) *Step.Compile { const assimp_dep = b.dependency("zig-assimp", .{ .target = b.host, diff --git a/src/AssetManager.zig b/src/AssetManager.zig index b3b7ae8..dd02e1a 100644 --- a/src/AssetManager.zig +++ b/src/AssetManager.zig @@ -315,45 +315,38 @@ pub fn loadShaderProgram(self: *AssetManager, handle: Handle.ShaderProgram, perm }; } -fn loadShaderProgramErr(self: *AssetManager, id: AssetId, permuted_id: AssetId, defines: []const DefinePair) !LoadedShaderProgram { +fn loadShaderProgramErr(self: *AssetManager, id: AssetId, permuted_id: AssetId) !LoadedShaderProgram { const data = try self.loadFile(self.frame_arena, asset_manifest.getPath(id), SHADER_MAX_BYTES); - const program = formats.ShaderProgram.fromBuffer(data.bytes); - - const graphics_pipeline = program.flags.vertex and program.flags.fragment; - const compute_pipeline = program.flags.compute; - - if (!graphics_pipeline and !compute_pipeline) { - std.log.err("Can't compile shader program {s} without vertex AND fragment shaders or a compute shader\n", .{asset_manifest.getPath(id)}); - return error.UnsupportedShader; - } - - // TODO: !!! this will keep shader source in memory as long as shader program is in memory - // probably don't want this! - const shader = self.resolveShaderWithDefines(program.shader, defines); + var serializer = formats.Serializer{ .write = false, .endian = formats.native_endian, .stream = .{ .const_buffer = data.bytes } }; + var program: formats.ShaderProgram = undefined; + try program.serialize(&serializer); const prog = gl.createProgram(); errdefer gl.deleteProgram(prog); - if (program.flags.vertex and program.flags.fragment) { - const vertex_shader = try self.compileShader(shader.source, .vertex); - defer gl.deleteShader(vertex_shader); - const fragment_shader = try self.compileShader(shader.source, .fragment); - defer gl.deleteShader(fragment_shader); + switch (program) { + .graphics => |graphics_pipeline| { + const vertex_shader = try self.compileShader(graphics_pipeline.vertex.source, .vertex); + defer gl.deleteShader(vertex_shader); + const fragment_shader = try self.compileShader(graphics_pipeline.fragment.source, .fragment); + defer gl.deleteShader(fragment_shader); - gl.attachShader(prog, vertex_shader); - defer gl.detachShader(prog, vertex_shader); - gl.attachShader(prog, fragment_shader); - defer gl.detachShader(prog, fragment_shader); + gl.attachShader(prog, vertex_shader); + defer gl.detachShader(prog, vertex_shader); + gl.attachShader(prog, fragment_shader); + defer gl.detachShader(prog, fragment_shader); - gl.linkProgram(prog); - } else { - const compute_shader = try self.compileShader(shader.source, .compute); - defer gl.deleteShader(compute_shader); + gl.linkProgram(prog); + }, + .compute => |compute_pipeline| { + const compute_shader = try self.compileShader(compute_pipeline.compute.source, .compute); + defer gl.deleteShader(compute_shader); - gl.attachShader(prog, compute_shader); - defer gl.detachShader(prog, compute_shader); + gl.attachShader(prog, compute_shader); + defer gl.detachShader(prog, compute_shader); - gl.linkProgram(prog); + gl.linkProgram(prog); + }, } var success: c_int = 0; @@ -399,8 +392,6 @@ fn loadShaderProgramErr(self: *AssetManager, id: AssetId, permuted_id: AssetId, .shaderProgram = loaded_shader_program, }); try self.modified_times.put(self.allocator, id, data.modified); - - try self.addDependencies(permuted_id, &.{ id, shader.permuted_id }); } return loaded_shader_program; @@ -1463,12 +1454,8 @@ fn compileShader(self: *AssetManager, source: []const u8, shader_type: ShaderTyp errdefer gl.deleteShader(shader); std.debug.assert(shader != 0); // should only happen if incorect shader type is passed const defines = shader_type.getDefines(); - gl.shaderSource( - shader, - 2, - &[_][*c]const u8{ @ptrCast(shader_type.getDefines()), @ptrCast(source) }, - &[_]gl.GLint{ @intCast(defines.len), @intCast(source.len) }, - ); + // Spirv + gl.shaderBinary(1, &shader, 0x9551, source.ptr, @intCast(source.len)); gl.compileShader(shader); var success: c_int = 0; gl.getShaderiv(shader, gl.COMPILE_STATUS, &success); @@ -1478,9 +1465,9 @@ fn compileShader(self: *AssetManager, source: []const u8, shader_type: ShaderTyp if (info_len > 0) { const info_log = try self.frame_arena.allocSentinel(u8, @intCast(info_len - 1), 0); gl.getShaderInfoLog(shader, @intCast(info_log.len), null, info_log); - std.log.err("ERROR::SHADER::COMPILATION_FAILED\n{s}{s}\n{s}\n", .{ defines, source, info_log }); + std.log.err("ERROR::SHADER::COMPILATION_FAILED\n{s}\n{s}\n", .{ defines, info_log }); } else { - std.log.err("ERROR::SHADER::COMPILIATION_FAILED\n{s}{s}\nNo info log.\n", .{ defines, source }); + std.log.err("ERROR::SHADER::COMPILIATION_FAILED\n{s}\nNo info log.\n", .{defines}); } return error.ShaderCompilationFailed; } diff --git a/src/GraphicsContext.zig b/src/GraphicsContext.zig index cfcc1da..51b327c 100644 --- a/src/GraphicsContext.zig +++ b/src/GraphicsContext.zig @@ -466,6 +466,7 @@ fn selectQueues(instance: Instance, device: vk.PhysicalDevice) !DeviceQueueConfi } else { for (queue_family_props, 0..) |props, family_idx| { // Jackpot, generous Jensen provided us with an all powerfull queue family, use it for everything + // TODO: actually, still need to use the dedicated transfer queue for CPU->GPU transfers to be async for sure if (props.queue_flags.contains(.{ .graphics_bit = true, .compute_bit = true, .transfer_bit = true }) and props.queue_count >= 4) { graphics = .{ .family = @intCast(family_idx), diff --git a/src/formats.zig b/src/formats.zig index 97e50e4..01e2e3f 100644 --- a/src/formats.zig +++ b/src/formats.zig @@ -120,50 +120,118 @@ pub fn writeMesh(writer: anytype, value: Mesh, endian: std.builtin.Endian) !void } } -pub const ShaderProgram = extern struct { - pub const Flags = packed struct { - vertex: bool, - fragment: bool, - compute: bool, - _pad: u5 = 0, +pub const ShaderProgramPipelineType = enum(u8) { graphics, compute }; + +pub const ShaderProgram = union(ShaderProgramPipelineType) { + pub const ShaderStage = struct { + source: []u8, }; - comptime { - if (@bitSizeOf(Flags) != 8) { - @compileError("ShaderProgram.Flags needs to be updated"); + + pub const GraphicsPipeline = struct { + // TODO: extend to support tesselation, geometry, mesh shaders and etc + vertex: ShaderStage, + fragment: ShaderStage, + }; + + pub const ComputePipeline = struct { + compute: ShaderStage, + }; + + // TODO: add raytracing pipelines + + graphics: GraphicsPipeline, + compute: ComputePipeline, + + pub fn serialize(self: *ShaderProgram, serializer: *Serializer) !void { + var tag_num: u8 = @intFromEnum(self.*); + try serializer.serializeInt(u8, &tag_num); + const tag: ShaderProgramPipelineType = @enumFromInt(tag_num); + + switch (tag) { + .graphics => { + // Kinda hacky, but can't come up with anything better + if (!serializer.write) { + self.* = .{ .graphics = undefined }; + } + try serializer.serializeByteSlice(&self.graphics.vertex.source); + try serializer.serializeByteSlice(&self.graphics.fragment.source); + }, + .compute => { + if (!serializer.write) { + self.* = .{ .compute = undefined }; + } + try serializer.serializeByteSlice(&self.compute.compute.source); + }, } } - - shader: Handle.Shader, - flags: Flags, - - pub fn fromBuffer(buf: []u8) *align(1) ShaderProgram { - return @ptrCast(buf); - } }; -test "ShaderProgram serialization" { - const source = ShaderProgram{ - .flags = .{ .vertex = true, .fragment = true, .compute = true }, - .shader = .{ .id = 123 }, - }; +pub const Serializer = struct { + // data_version: i32, + write: bool = false, + endian: std.builtin.Endian, + stream: std.io.StreamSource, - var buf: [@sizeOf(ShaderProgram)]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - try writeShaderProgram(stream.writer(), source.shader.id, source.flags.vertex, source.flags.fragment, source.flags.compute, native_endian); + // Don't use usize as it will break on 32 bit systems + pub fn serializeInt(self: *Serializer, comptime T: type, data: *T) !void { + var buf: [@divExact(@typeInfo(T).Int.bits, 8)]u8 = undefined; + if (self.write) { + std.mem.writeInt(T, &buf, data.*, self.endian); + _ = try self.stream.write(&buf); + } else { + _ = try self.stream.read(&buf); + data.* = std.mem.readInt(T, &buf, self.endian); + } + } - const result: *align(1) ShaderProgram = @ptrCast(&buf); + pub fn serializeBool(self: *Serializer, data: *bool) !void { + var buf: [1]u8 = undefined; + if (self.write) { + buf[0] = if (data.*) 1 else 0; + _ = try self.stream.write(&buf); + } else { + _ = try self.stream.read(&buf); + data.* = buf[0] != 0; + } + } - try std.testing.expectEqual(source, result.*); -} + pub fn serializeFloat(self: *Serializer, data: *f32) !void { + var buf: [4]u8 = undefined; + if (self.write) { + std.mem.writeInt(u32, &buf, @bitCast(data.*), self.endian); + _ = try self.stream.write(&buf); + } else { + _ = try self.stream.read(&buf); + data.* = @bitCast(std.mem.readInt(u32, &buf, self.endian)); + } + } -pub fn writeShaderProgram(writer: anytype, shader: u64, vertex: bool, fragment: bool, compute: bool, endian: std.builtin.Endian) !void { - try writer.writeInt(u64, shader, endian); - try writer.writeInt( - u8, - @bitCast(ShaderProgram.Flags{ .vertex = vertex, .fragment = fragment, .compute = compute }), - endian, - ); -} + pub fn serializeDouble(self: *Serializer, data: *f64) !void { + var buf: [8]u8 = undefined; + if (self.write) { + std.mem.writeInt(u64, &buf, @bitCast(data.*), self.endian); + _ = try self.stream.write(&buf); + } else { + _ = try self.stream.read(&buf); + data.* = @bitCast(std.mem.readInt(u64, &buf, self.endian)); + } + } + + pub fn serializeBytes(self: *Serializer, data: *[]u8) !void { + if (self.write) { + _ = try self.stream.write(data.*); + } else { + _ = try self.stream.read(data.*); + } + } + + pub fn serializeByteSlice(self: *Serializer, data: *[]u8) !void { + var len: u32 = @intCast(data.len); + try self.serializeInt(u32, &len); + data.len = @intCast(len); + try self.serializeBytes(data); + } +}; fn writeVector2(writer: anytype, value: Vector2, endian: std.builtin.Endian) !void { try writeFloat(writer, value.x, endian); diff --git a/src/game.zig b/src/game.zig index 5162e7a..1265d40 100644 --- a/src/game.zig +++ b/src/game.zig @@ -287,9 +287,9 @@ export fn game_init(global_allocator: *std.mem.Allocator) void { // } // } - const scene = globals.g_mem.world.createScene(globals.g_assetman.resolveScene(a.Scenes.bistro.scene)); - const ent = globals.g_mem.world.getEntity(scene) orelse @panic("WTF"); - ent.data.transform.pos = Vec3.new(0, 0, 0); + // const scene = globals.g_mem.world.createScene(globals.g_assetman.resolveScene(a.Scenes.bistro.scene)); + // const ent = globals.g_mem.world.getEntity(scene) orelse @panic("WTF"); + // ent.data.transform.pos = Vec3.new(0, 0, 0); // ent.data.transform.scale = Vec3.one().scale(1.0); } diff --git a/tools/CompileShaderPipeline.zig b/tools/CompileShaderPipeline.zig new file mode 100644 index 0000000..e845405 --- /dev/null +++ b/tools/CompileShaderPipeline.zig @@ -0,0 +1,38 @@ +const std = @import("std"); +const Build = std.Build; +const Step = Build.Step; + +const CompileShaderProgram = @This(); + +step: Step, +program_path: Build.LazyPath, + +pub fn create(b: *Build, program_path: Build.LazyPath) *CompileShaderProgram { + const csp = b.allocator.create(CompileShaderProgram) catch @panic("OOM"); + + csp.* = .{ + .step = Step.init(.{ + .id = .custom, + .name = b.fmt("CompileShaderProgram ({s}))"), + .owner = b, + .makeFn = make, + }), + .program_path = program_path, + }; + + return csp; +} + +fn make(step: *Step, prog_node: std.Progress.Node) !void { + _ = prog_node; // autofix + const b = step.owner; + var arena = std.heap.ArenaAllocator.init(b.allocator); + defer arena.deinit(); + + const alloc = arena.allocator(); + const self: *CompileShaderProgram = @fieldParentPtr("step", step); + _ = self; // autofix + + var man = b.graph.cache.obtain(); + defer man.deinit(); +} diff --git a/tools/asset_compiler.zig b/tools/asset_compiler.zig index 1b3698a..6fb6b91 100644 --- a/tools/asset_compiler.zig +++ b/tools/asset_compiler.zig @@ -41,25 +41,73 @@ pub fn resolveAssetTypeByExtension(path: []const u8) ?AssetType { if (std.mem.endsWith(u8, path, ".prog")) { return .ShaderProgram; } - if (std.mem.endsWith(u8, path, ".glsl")) { - return .Shader; - } if (std.mem.endsWith(u8, path, ".png") or std.mem.endsWith(u8, path, ".jpg") or std.mem.endsWith(u8, path, ".exr") or std.mem.endsWith(u8, path, ".dds") or std.mem.endsWith(u8, path, ".tga")) { return .Texture; } return null; } -pub fn main() !void { - const allocator = std.heap.c_allocator; - const argv = std.os.argv; - if (argv.len < 3) { - std.log.err("usage assetc \n", .{}); - return error.MissingArgs; +const Args = struct { + dep_file: ?[]const u8 = null, + input_file: []const u8 = "", + output_dir: []const u8 = "", +}; + +fn parseArgs(allocator: std.mem.Allocator) !Args { + var args = try std.process.argsWithAllocator(allocator); + defer args.deinit(); + + var result: Args = .{}; + + // Skip program + _ = args.skip(); + + var required_args: [2][]const u8 = undefined; + var required_arg_cursor: usize = 0; + + while (args.next()) |flag| { + // Flags + if (std.mem.eql(u8, flag, "-d") or std.mem.eql(u8, flag, "--depfile")) { + const dep_file = args.next() orelse return error.MissingDepFile; + result.dep_file = dep_file; + continue; + } + + if (required_arg_cursor >= required_args.len) { + return error.TooManyUnnamedArgs; + } + required_args[required_arg_cursor] = flag; + required_arg_cursor += 1; } - const abs_input = std.mem.span(argv[argv.len - 2]); - const output_dir_path = std.mem.span(argv[argv.len - 1]); + if (required_arg_cursor != required_args.len) { + return error.MissingRequiredArgs; + } + + result.input_file = required_args[0]; + result.output_dir = required_args[1]; + + return result; +} + +pub fn main() !void { + const allocator = std.heap.c_allocator; + + const args = parseArgs(allocator) catch |err| { + std.debug.print("{s}\n", .{std.os.argv}); + std.debug.print( + \\usage: assetc [flags] + \\ + \\flags: + \\ -d, --depfile - output a depfile + \\ + \\error: {} + , .{err}); + return err; + }; + + const abs_input = args.input_file; + const output_dir_path = args.output_dir; // HACK: build.zig gives us a path like: zig-cache/o//assets // assetc outputs paths including the "assets/" prefix, so we do an equivalent @@ -72,6 +120,8 @@ pub fn main() !void { const rel_input = try std.fs.path.relative(allocator, cwd_path, abs_input); const rel_output = try std.fs.path.relative(allocator, cwd_path, output_dirname); + std.debug.print("rel_input: {s}\nrel_output: {s}\n", .{ rel_input, rel_output }); + var output_dir = try std.fs.cwd().makeOpenPath(rel_output, .{}); defer output_dir.close(); @@ -84,12 +134,16 @@ pub fn main() !void { switch (asset_type) { .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), + .ShaderProgram => try processShaderProgram(allocator, rel_input, output_dir, args.dep_file, asset_list_writer), .Texture => try processTextureFromFile(allocator, rel_input, output_dir, asset_list_writer), else => unreachable, } try buf_asset_list_writer.flush(); + + if (args.dep_file) |dep_file_path| { + const dep_file = try std.fs.cwd().createFile(dep_file_path, .{ .read = true, .truncate = false }); + dep_file.close(); + } } fn copyFile(_type: AssetType, input: []const u8, output_dir: std.fs.Dir, asset_list_writer: anytype) !void { @@ -517,7 +571,38 @@ fn processMesh(allocator: std.mem.Allocator, scene: *const c.aiScene, material_o try buf_writer.flush(); } -fn processShaderProgram(allocator: std.mem.Allocator, input: []const u8, output_dir: std.fs.Dir, asset_list_writer: anytype) !void { +// Returns spirv binary source +// Caller owns memory +fn processShader(allocator: std.mem.Allocator, flags: []const []const u8, input: []const u8, dep_file: ?[]const u8) ![]u8 { + // TODO: make sure output is stdout + const result = try std.process.Child.run(.{ + .allocator = allocator, + .argv = try std.mem.concat(allocator, []const u8, &.{ + &.{ "glslc", "--target-env=vulkan1.3", "-std=460core", "-g", "-o", "-" }, + if (dep_file) |dep| &.{ "-MD", "-MF", dep } else &.{}, + flags, + &.{input}, + }), + }); + defer allocator.free(result.stderr); + errdefer allocator.free(result.stdout); + + switch (result.term) { + .Exited => |status| { + if (status != 0) { + std.log.debug("Shader compilation failed with status {}:\n{s}\n", .{ result.term.Exited, result.stdout }); + return error.ShaderCompileError; + } + }, + else => { + return error.UnknownShaderCompileError; + }, + } + + return result.stdout; +} + +fn processShaderProgram(allocator: std.mem.Allocator, input: []const u8, output_dir: std.fs.Dir, dep_file: ?[]const u8, asset_list_writer: anytype) !void { const input_dir = std.fs.path.dirname(input).?; var file_contents: []u8 = undefined; @@ -528,31 +613,67 @@ fn processShaderProgram(allocator: std.mem.Allocator, input: []const u8, output_ } defer allocator.free(file_contents); - const ShaderProgram = struct { - shader: []const u8, - vertex: bool, - fragment: bool, - compute: bool, + // const InputShaderStage = struct { + // source: []const u8, + // entry: []const u8, + // }; + + const InputShaderProgram = struct { + vertex: ?[]const u8, + fragment: ?[]const u8, + compute: ?[]const u8, }; - const program = try std.json.parseFromSlice(ShaderProgram, allocator, file_contents, .{}); + const program = try std.json.parseFromSlice(InputShaderProgram, allocator, file_contents, .{}); defer program.deinit(); - const shader_path = try std.fs.path.resolve(allocator, &.{ input_dir, program.value.shader }); + var result: formats.ShaderProgram = undefined; - const relative_path = try std.fs.path.relative(allocator, try std.fs.cwd().realpathAlloc(allocator, "."), shader_path); - const shader_asset_id = types.AssetPath.fromString(relative_path).hash(); - if (shader_asset_id == 0) { - std.log.debug("{s}\n", .{shader_path}); - return error.InvalidShaderPath; + if (program.value.vertex != null and program.value.fragment != null) { + result = .{ .graphics = undefined }; + // TODO: remove duplication + { + const stage = program.value.vertex.?; + const shader_source_path = try std.fs.path.resolve(allocator, &.{ input_dir, stage }); + const relative_path = try std.fs.path.relative(allocator, try std.fs.cwd().realpathAlloc(allocator, "."), shader_source_path); + + const shader_source = try processShader(allocator, &.{ "-DVERTEX_SHADER=1", "-fshader-stage=vert" }, relative_path, dep_file); + result.graphics.vertex.source = shader_source; + } + { + const stage = program.value.fragment.?; + const shader_source_path = try std.fs.path.resolve(allocator, &.{ input_dir, stage }); + const relative_path = try std.fs.path.relative(allocator, try std.fs.cwd().realpathAlloc(allocator, "."), shader_source_path); + + const shader_source = try processShader(allocator, &.{ "-DFRAGMENT_SHADER=1", "-fshader-stage=frag" }, relative_path, dep_file); + result.graphics.fragment.source = shader_source; + } + } else if (program.value.compute != null) { + result = .{ .compute = undefined }; + + const stage = program.value.compute.?; + const shader_source_path = try std.fs.path.resolve(allocator, &.{ input_dir, stage }); + const relative_path = try std.fs.path.relative(allocator, try std.fs.cwd().realpathAlloc(allocator, "."), shader_source_path); + + const shader_source = try processShader(allocator, &.{ "-DCOMPUTE_SHADER=1", "-fshader-stage=compute" }, relative_path, dep_file); + result.compute.compute.source = shader_source; + } else { + std.log.err("Provide vertex and fragment shaders for a graphics pipeline or a compute shader for a compute pipeline\n", .{}); + return error.InvalidPipelines; } 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, program.value.compute, formats.native_endian); - try buf_writer.flush(); + var serializer = formats.Serializer{ + .stream = std.io.StreamSource{ .file = output.file }, + .write = true, + // TODO: figure out target endianness + .endian = formats.native_endian, + }; + + try result.serialize(&serializer); } + const MipLevel = struct { width: usize, height: usize, diff --git a/tools/types.zig b/tools/types.zig index 58065c4..780ee22 100644 --- a/tools/types.zig +++ b/tools/types.zig @@ -3,7 +3,6 @@ const std = @import("std"); pub const AssetType = enum { Scene, Mesh, - Shader, ShaderProgram, Texture, Material, @@ -12,7 +11,6 @@ pub const AssetType = enum { return switch (self) { .Scene => "Scenes", .Mesh => "Meshes", - .Shader => "Shaders", .ShaderProgram => "ShaderPrograms", .Texture => "Textures", .Material => "Materials", @@ -23,7 +21,6 @@ pub const AssetType = enum { return switch (self) { .Scene => "scn", .Mesh => "mesh", - .Shader => "glsl", .ShaderProgram => "prog", .Texture => "tex", .Material => "mat",