Shader compilation to spir-v

This commit is contained in:
sergeypdev 2024-09-22 23:31:22 +04:00
parent 52cdbef876
commit 2373a47340
21 changed files with 404 additions and 150 deletions

9
.vscode/launch.json vendored
View File

@ -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)

Binary file not shown.

View File

@ -1,6 +0,0 @@
{
"shader": "debug.glsl",
"vertex": true,
"fragment": true,
"compute": false
}

View 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

View File

@ -0,0 +1,5 @@
{
"vertex": "triangle.glsl",
"fragment": "triangle.glsl",
"compute": null
}

View File

@ -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,

View File

@ -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;
}

View File

@ -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),

View File

@ -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);

View File

@ -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);
}

View 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();
}

View File

@ -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,

View File

@ -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",