Shader compilation to spir-v
This commit is contained in:
parent
52cdbef876
commit
2373a47340
9
.vscode/launch.json
vendored
9
.vscode/launch.json
vendored
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
BIN
assets/bistro.glb
(Stored with Git LFS)
BIN
assets/bistro.glb
(Stored with Git LFS)
Binary file not shown.
@ -1,6 +0,0 @@
|
||||
{
|
||||
"shader": "debug.glsl",
|
||||
"vertex": true,
|
||||
"fragment": true,
|
||||
"compute": false
|
||||
}
|
23
assets/shaders/triangle.glsl
Normal file
23
assets/shaders/triangle.glsl
Normal file
@ -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
|
5
assets/shaders/triangle.prog
Normal file
5
assets/shaders/triangle.prog
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"vertex": "triangle.glsl",
|
||||
"fragment": "triangle.glsl",
|
||||
"compute": null
|
||||
}
|
36
build.zig
36
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,15 +204,7 @@ 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 (!is_known_ext) continue;
|
||||
|
||||
if (shouldProcessAsset(ext)) {
|
||||
const run_assetc = b.addRunArtifact(assetc);
|
||||
run_assetc.rename_step_with_output_arg = false;
|
||||
|
||||
@ -217,6 +213,9 @@ fn buildAssets(b: *std.Build, install_assetc_step: *Step, step: *Step, assetc: *
|
||||
|
||||
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 })));
|
||||
|
||||
@ -231,10 +230,25 @@ fn buildAssets(b: *std.Build, install_assetc_step: *Step, step: *Step, assetc: *
|
||||
});
|
||||
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,
|
||||
|
@ -315,29 +315,20 @@ 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);
|
||||
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(shader.source, .fragment);
|
||||
const fragment_shader = try self.compileShader(graphics_pipeline.fragment.source, .fragment);
|
||||
defer gl.deleteShader(fragment_shader);
|
||||
|
||||
gl.attachShader(prog, vertex_shader);
|
||||
@ -346,14 +337,16 @@ fn loadShaderProgramErr(self: *AssetManager, id: AssetId, permuted_id: AssetId,
|
||||
defer gl.detachShader(prog, fragment_shader);
|
||||
|
||||
gl.linkProgram(prog);
|
||||
} else {
|
||||
const compute_shader = try self.compileShader(shader.source, .compute);
|
||||
},
|
||||
.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.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;
|
||||
}
|
||||
|
@ -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),
|
||||
|
136
src/formats.zig
136
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");
|
||||
}
|
||||
}
|
||||
|
||||
shader: Handle.Shader,
|
||||
flags: Flags,
|
||||
pub const GraphicsPipeline = struct {
|
||||
// TODO: extend to support tesselation, geometry, mesh shaders and etc
|
||||
vertex: ShaderStage,
|
||||
fragment: ShaderStage,
|
||||
};
|
||||
|
||||
pub fn fromBuffer(buf: []u8) *align(1) ShaderProgram {
|
||||
return @ptrCast(buf);
|
||||
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);
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
38
tools/CompileShaderPipeline.zig
Normal file
38
tools/CompileShaderPipeline.zig
Normal file
@ -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();
|
||||
}
|
@ -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 <rel_path> <input> <output>\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;
|
||||
}
|
||||
|
||||
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.TooManyUnnamedArgs;
|
||||
}
|
||||
required_args[required_arg_cursor] = flag;
|
||||
required_arg_cursor += 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] <input_file> <output_dir>
|
||||
\\
|
||||
\\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/<hash>/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,
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user