Rewrite how all assets are loaded to make it easier to use
This commit is contained in:
parent
d91484e992
commit
a837984d55
4
assets/shaders/mesh.prog
Normal file
4
assets/shaders/mesh.prog
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"vertex": "mesh.vert.glsl",
|
||||
"fragment": "mesh.frag.glsl"
|
||||
}
|
81
build.zig
81
build.zig
@ -22,10 +22,16 @@ pub fn build(b: *Build) void {
|
||||
"Prioritize performance, safety, or binary size for build time tools",
|
||||
) orelse .Debug;
|
||||
|
||||
const assets_mod = b.addModule("assets", .{ .root_source_file = .{ .path = "src/assets/root.zig" } });
|
||||
const asset_manifest_mod = b.addModule("asset_manifest", .{ .root_source_file = .{ .path = "src/gen/asset_manifest.zig" } });
|
||||
asset_manifest_mod.addImport("assets", assets_mod);
|
||||
|
||||
const assets_step = b.step("assets", "Build and install assets");
|
||||
b.getInstallStep().dependOn(assets_step);
|
||||
|
||||
const assetc = buildAssetCompiler(b, buildOptimize);
|
||||
assetc.root_module.addImport("assets", assets_mod);
|
||||
assetc.root_module.addImport("asset_manifest", asset_manifest_mod);
|
||||
|
||||
buildAssets(b, assets_step, assetc, "assets") catch |err| {
|
||||
std.log.err("Failed to build assets {}\n", .{err});
|
||||
@ -49,6 +55,8 @@ pub fn build(b: *Build) void {
|
||||
|
||||
lib.linkLibrary(sdl2);
|
||||
lib.root_module.addImport("zlm", zlm_dep.module("zlm"));
|
||||
lib.root_module.addImport("assets", assets_mod);
|
||||
lib.root_module.addImport("asset_manifest", asset_manifest_mod);
|
||||
|
||||
const install_lib = b.addInstallArtifact(lib, .{ .dest_dir = .{ .override = .prefix } });
|
||||
b.getInstallStep().dependOn(&install_lib.step);
|
||||
@ -120,7 +128,7 @@ pub fn build(b: *Build) void {
|
||||
}
|
||||
|
||||
const NestedAssetDef = union(enum) {
|
||||
path: std.StringHashMapUnmanaged(NestedAssetDef),
|
||||
path: std.StringArrayHashMapUnmanaged(NestedAssetDef),
|
||||
asset: usize,
|
||||
|
||||
pub fn put(self: *NestedAssetDef, allocator: std.mem.Allocator, path: []const u8, id: usize) !void {
|
||||
@ -135,7 +143,9 @@ const NestedAssetDef = union(enum) {
|
||||
while (iter.next()) |comp| {
|
||||
if (comp.name.ptr == filename.ptr) break;
|
||||
const gop = try current.getOrPut(allocator, comp.name);
|
||||
gop.value_ptr.* = NestedAssetDef{ .path = .{} };
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = NestedAssetDef{ .path = .{} };
|
||||
}
|
||||
current = &gop.value_ptr.path;
|
||||
}
|
||||
|
||||
@ -159,6 +169,8 @@ fn buildAssets(b: *std.Build, step: *Step, assetc: *Step.Compile, path: []const
|
||||
|
||||
var asset_id: usize = 1; // Start at 1 because asset id 0 = null asset
|
||||
var meshes = NestedAssetDef{ .path = .{} };
|
||||
var shaders = NestedAssetDef{ .path = .{} };
|
||||
var shader_programs = NestedAssetDef{ .path = .{} };
|
||||
var asset_paths = std.ArrayList([]const u8).init(b.allocator);
|
||||
|
||||
var walker = try assetsDir.walk(b.allocator);
|
||||
@ -176,8 +188,8 @@ fn buildAssets(b: *std.Build, step: *Step, assetc: *Step.Compile, path: []const
|
||||
const compiled_file = run_assetc.addOutputFileArg(out_name);
|
||||
|
||||
const out_path = b.pathJoin(&.{
|
||||
std.fs.path.dirname(entry.path) orelse ".",
|
||||
path,
|
||||
std.fs.path.dirname(entry.path) orelse "",
|
||||
out_name,
|
||||
});
|
||||
const install_asset = b.addInstallFileWithDir(
|
||||
@ -202,10 +214,43 @@ fn buildAssets(b: *std.Build, step: *Step, assetc: *Step.Compile, path: []const
|
||||
});
|
||||
const install_shader = b.addInstallFileWithDir(.{ .path = out_path }, .prefix, out_path);
|
||||
step.dependOn(&install_shader.step);
|
||||
|
||||
{
|
||||
const id = asset_id;
|
||||
asset_id += 1;
|
||||
try shaders.put(b.allocator, out_path, id);
|
||||
try asset_paths.append(out_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Shader program
|
||||
if (std.mem.endsWith(u8, entry.basename, ".prog")) {
|
||||
const run_assetc = b.addRunArtifact(assetc);
|
||||
run_assetc.addFileArg(.{ .path = b.pathJoin(&.{ path, entry.path }) });
|
||||
const compiled_file = run_assetc.addOutputFileArg(b.dupe(entry.basename));
|
||||
|
||||
const out_path = b.pathJoin(&.{
|
||||
path,
|
||||
entry.path,
|
||||
});
|
||||
const install_asset = b.addInstallFileWithDir(
|
||||
compiled_file,
|
||||
.prefix,
|
||||
out_path,
|
||||
);
|
||||
step.dependOn(&install_asset.step);
|
||||
|
||||
{
|
||||
const id = asset_id;
|
||||
asset_id += 1;
|
||||
try shader_programs.put(b.allocator, out_path, id);
|
||||
try asset_paths.append(out_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const manifest_step = try writeAssetManifest(b, step, asset_paths.items, &meshes);
|
||||
const manifest_step = try writeAssetManifest(b, step, asset_paths.items, &meshes, &shaders, &shader_programs);
|
||||
|
||||
assetc.step.dependOn(&manifest_step.step);
|
||||
}
|
||||
|
||||
@ -229,26 +274,44 @@ fn writeNestedAssetDef(writer: anytype, handle: []const u8, name: []const u8, as
|
||||
}
|
||||
}
|
||||
|
||||
fn writeAssetManifest(b: *Build, asset_step: *Step, asset_paths: [][]const u8, meshes: *NestedAssetDef) !*Step.WriteFile {
|
||||
fn writeAssetManifest(
|
||||
b: *Build,
|
||||
asset_step: *Step,
|
||||
asset_paths: [][]const u8,
|
||||
meshes: *NestedAssetDef,
|
||||
shaders: *NestedAssetDef,
|
||||
shader_programs: *NestedAssetDef,
|
||||
) !*Step.WriteFile {
|
||||
var mesh_asset_manifest = std.ArrayList(u8).init(b.allocator);
|
||||
const writer = mesh_asset_manifest.writer();
|
||||
|
||||
try writer.writeAll("// Generated file, do not edit manually!\n\n");
|
||||
try writer.writeAll("const Handle = @import(\"Assets.zig\").Handle;\n\n");
|
||||
try writer.writeAll("const std = @import(\"std\");\n");
|
||||
// TODO: import AssetId instead of harcoding u32s
|
||||
try writer.writeAll("const Handle = @import(\"assets\").Handle;\n\n");
|
||||
|
||||
try writeNestedAssetDef(writer, "Mesh", "Meshes", meshes, 0);
|
||||
try writer.writeByte('\n');
|
||||
try writeNestedAssetDef(writer, "Shader", "Shaders", shaders, 0);
|
||||
try writer.writeByte('\n');
|
||||
try writeNestedAssetDef(writer, "ShaderProgram", "ShaderPrograms", shader_programs, 0);
|
||||
try writer.writeByte('\n');
|
||||
|
||||
try writer.writeAll("pub const asset_paths = [_][]const u8{\n");
|
||||
for (asset_paths) |path| {
|
||||
try std.fmt.format(writer, " \"{}\",\n", .{std.zig.fmtEscapes(path)});
|
||||
}
|
||||
try writer.writeAll("};\n");
|
||||
try writer.writeAll("};\n\n");
|
||||
|
||||
try writer.writeAll("pub fn getPath(asset_id: u32) []const u8 { return asset_paths[asset_id - 1]; }\n");
|
||||
try writer.writeAll("pub const asset_path_to_asset_id = std.ComptimeStringMap(u32, .{\n");
|
||||
for (asset_paths, 0..) |path, i| {
|
||||
try std.fmt.format(writer, " .{{ \"{}\", {} }},\n", .{ std.zig.fmtEscapes(path), i + 1 });
|
||||
}
|
||||
try writer.writeAll("});\n\n");
|
||||
|
||||
const result = mesh_asset_manifest.toOwnedSlice() catch @panic("OOM");
|
||||
const write_step = b.addWriteFiles();
|
||||
write_step.addBytesToSource(result, "src/asset_manifest.zig");
|
||||
write_step.addBytesToSource(result, "src/gen/asset_manifest.gen.zig");
|
||||
asset_step.dependOn(&write_step.step);
|
||||
return write_step;
|
||||
}
|
||||
|
@ -18,66 +18,37 @@ const std = @import("std");
|
||||
const gl = @import("gl.zig");
|
||||
const fs_utils = @import("fs/utils.zig");
|
||||
const formats = @import("formats.zig");
|
||||
const asset_manifest = @import("asset_manifest.zig");
|
||||
const asset_manifest = @import("asset_manifest");
|
||||
const assets = @import("assets");
|
||||
|
||||
pub const Assets = @This();
|
||||
pub const AssetId = assets.AssetId;
|
||||
pub const Handle = assets.Handle;
|
||||
|
||||
pub const AssetManager = @This();
|
||||
|
||||
// const ShaderProgramHandle = struct { id: gl.GLuint };
|
||||
// const handle: ShaderProgramHandle = assets.loadShaderProgram(.{ .vertex = "shaders/vertex.glsl", .fragment = "shaders/fragment.glsl" });
|
||||
// assets.unloadShaderProgram(handle);
|
||||
|
||||
const AssetIdList = std.SegmentedList(AssetId, 4);
|
||||
|
||||
const SHADER_MAX_BYTES = 1024 * 1024 * 50;
|
||||
const MESH_MAX_BYTES = 1024 * 1024 * 500;
|
||||
|
||||
const AssetId = u32;
|
||||
|
||||
pub const Handle = struct {
|
||||
pub const ShaderProgram = struct {
|
||||
id: AssetId = 0,
|
||||
|
||||
pub fn resolve(self: ShaderProgram, assets: *Assets) gl.GLuint {
|
||||
if (self.id == 0) return 0;
|
||||
|
||||
const asset = assets.loaded_assets.getPtr(self.id) orelse unreachable;
|
||||
|
||||
switch (asset.*) {
|
||||
.shaderProgram => |*shader| {
|
||||
return shader.program;
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
};
|
||||
pub const Mesh = struct {
|
||||
id: AssetId = 0,
|
||||
|
||||
// Returns a VAO
|
||||
pub fn resolve(self: Mesh, assets: *Assets) *const LoadedMesh {
|
||||
if (assets.loaded_assets.getPtr(self.id)) |asset| {
|
||||
switch (asset.*) {
|
||||
.mesh => |*mesh| {
|
||||
return mesh;
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
return loadMesh(assets, self.id);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
frame_arena: std.mem.Allocator,
|
||||
|
||||
// All assets are relative to exe dir
|
||||
exe_dir: std.fs.Dir,
|
||||
|
||||
modified_times: std.AutoHashMapUnmanaged(AssetId, i128) = .{},
|
||||
// Mapping from asset to all assets it depends on
|
||||
dependencies: std.AutoHashMapUnmanaged(AssetId, std.SegmentedList(AssetId, 4)) = .{},
|
||||
// Mapping from asset to all assets that depend on it
|
||||
dependees: std.AutoHashMapUnmanaged(AssetId, std.SegmentedList(AssetId, 4)) = .{},
|
||||
loaded_assets: std.AutoHashMapUnmanaged(AssetId, LoadedAsset) = .{},
|
||||
|
||||
next_id: AssetId = 10,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator) Assets {
|
||||
pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator) AssetManager {
|
||||
var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
||||
const exe_dir_path = std.fs.selfExeDirPath(&buf) catch @panic("can't find self exe dir path");
|
||||
const exe_dir = std.fs.openDirAbsolute(exe_dir_path, .{}) catch @panic("can't open self exe dir path");
|
||||
@ -89,30 +60,61 @@ pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator) Assets
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Assets) void {
|
||||
pub fn deinit(self: *AssetManager) void {
|
||||
self.loaded_assets.deinit(self.allocator);
|
||||
}
|
||||
|
||||
pub fn resolveShader(self: *AssetManager, handle: Handle.Shader, shader_type: ShaderType) *const LoadedShader {
|
||||
if (handle.id == 0) return &NullShader;
|
||||
|
||||
if (self.loaded_assets.getPtr(handle.id)) |asset| {
|
||||
return &asset.shader;
|
||||
}
|
||||
|
||||
return self.loadShader(handle.id, shader_type);
|
||||
}
|
||||
|
||||
pub fn resolveShaderProgram(self: *AssetManager, handle: Handle.ShaderProgram) *const LoadedShaderProgram {
|
||||
if (handle.id == 0) return &NullShaderProgram;
|
||||
|
||||
if (self.loaded_assets.getPtr(handle.id)) |asset| {
|
||||
switch (asset.*) {
|
||||
.shaderProgram => |*shader| {
|
||||
return shader;
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
return self.loadShaderProgram(handle);
|
||||
}
|
||||
|
||||
pub fn resolveMesh(self: *AssetManager, handle: Handle.Mesh) *const LoadedMesh {
|
||||
if (self.loaded_assets.getPtr(handle.id)) |asset| {
|
||||
switch (asset.*) {
|
||||
.mesh => |*mesh| {
|
||||
return mesh;
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
return self.loadMesh(handle.id);
|
||||
}
|
||||
|
||||
// TODO: proper watching
|
||||
pub fn watchChanges(self: *Assets) void {
|
||||
pub fn watchChanges(self: *AssetManager) void {
|
||||
var iter = self.loaded_assets.iterator();
|
||||
while (iter.next()) |entry| {
|
||||
switch (entry.value_ptr.*) {
|
||||
.shaderProgram => |*shader| {
|
||||
if (self.didUpdate(shader.definition.vertex, &shader.vert_modified) or self.didUpdate(shader.definition.fragment, &shader.frag_modified)) {
|
||||
self.reloadAsset(entry.key_ptr.*);
|
||||
}
|
||||
},
|
||||
.mesh => |*mesh| {
|
||||
if (self.didUpdate(asset_manifest.getPath(entry.key_ptr.*), &mesh.modified)) {
|
||||
self.reloadAsset(entry.key_ptr.*);
|
||||
}
|
||||
},
|
||||
const gop = self.modified_times.getOrPut(self.allocator, entry.key_ptr.*) catch return;
|
||||
if (self.didUpdate(asset_manifest.getPath(entry.key_ptr.*), gop.value_ptr)) {
|
||||
// self.dependees.get
|
||||
// self.reloadAsset(entry.key_ptr.*);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn didUpdate(self: *Assets, path: []const u8, last_modified: *i128) bool {
|
||||
fn didUpdate(self: *AssetManager, path: []const u8, last_modified: *i128) bool {
|
||||
const mod = fs_utils.getFileModifiedRelative(self.exe_dir, path) catch |err| {
|
||||
std.log.err("ERROR: {}\nfailed to check file modtime {s}\n", .{ err, path });
|
||||
return false;
|
||||
@ -122,69 +124,35 @@ fn didUpdate(self: *Assets, path: []const u8, last_modified: *i128) bool {
|
||||
return updated;
|
||||
}
|
||||
|
||||
pub fn reloadAsset(self: *Assets, asset_id: AssetId) void {
|
||||
const asset = self.loaded_assets.getPtr(asset_id) orelse @panic("trying to reload unloaded asset");
|
||||
|
||||
switch (asset.*) {
|
||||
.shaderProgram => |*shader| {
|
||||
_ = self.loadShaderProgramErr(shader.program, shader.definition) catch |err| {
|
||||
std.log.err("Failed to reload shader program {}\n", .{err});
|
||||
};
|
||||
},
|
||||
.mesh => {
|
||||
std.log.debug("reloading mesh {s}\n", .{asset_manifest.getPath(asset_id)});
|
||||
_ = self.loadMeshErr(
|
||||
asset_id,
|
||||
) catch |err| {
|
||||
std.log.err("Fauled to reload mesh {}\n", .{err});
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub const ShaderProgramDefinition = struct {
|
||||
// TODO: think about how to store paths?
|
||||
vertex: []const u8,
|
||||
fragment: []const u8,
|
||||
};
|
||||
pub fn loadShaderProgram(self: *Assets, params: ShaderProgramDefinition) Handle.ShaderProgram {
|
||||
const prog = gl.createProgram();
|
||||
errdefer gl.deleteProgram(prog);
|
||||
const mods = self.loadShaderProgramErr(prog, params) catch |err| {
|
||||
std.log.err("Failed to load shader program {}\nDefinition: {}\n", .{ err, params });
|
||||
|
||||
return .{ .id = 0 };
|
||||
pub fn loadShaderProgram(self: *AssetManager, handle: Handle.ShaderProgram) *const LoadedShaderProgram {
|
||||
return self.loadShaderProgramErr(handle.id) catch |err| {
|
||||
std.log.err("Failed to load shader program {}\n", .{err});
|
||||
|
||||
return &NullShaderProgram;
|
||||
};
|
||||
const id = self.nextId();
|
||||
self.loaded_assets.put(self.allocator, id, .{
|
||||
.shaderProgram = .{
|
||||
.definition = .{
|
||||
.vertex = self.allocator.dupe(u8, params.vertex) catch @panic("OOM"),
|
||||
.fragment = self.allocator.dupe(u8, params.fragment) catch @panic("OOM"),
|
||||
},
|
||||
.program = prog,
|
||||
.vert_modified = mods.vert_modified,
|
||||
.frag_modified = mods.frag_modified,
|
||||
},
|
||||
}) catch @panic("OOM"); // handle this better
|
||||
|
||||
return .{ .id = id };
|
||||
}
|
||||
|
||||
fn loadShaderProgramErr(self: *Assets, prog: gl.GLuint, params: ShaderProgramDefinition) !struct { vert_modified: i128, frag_modified: i128 } {
|
||||
const vertex_file = try self.loadFile(self.frame_arena, params.vertex, SHADER_MAX_BYTES);
|
||||
const vertex_shader = try loadShader(self.frame_arena, .vertex, vertex_file.bytes);
|
||||
defer gl.deleteShader(vertex_shader);
|
||||
fn loadShaderProgramErr(self: *AssetManager, 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 fragment_file = try self.loadFile(self.frame_arena, params.fragment, SHADER_MAX_BYTES);
|
||||
const fragment_shader = try loadShader(self.frame_arena, .fragment, fragment_file.bytes);
|
||||
defer gl.deleteShader(fragment_shader);
|
||||
const vertex = self.resolveShader(program.vertex, .vertex);
|
||||
const fragment = self.resolveShader(program.fragment, .fragment);
|
||||
|
||||
gl.attachShader(prog, vertex_shader);
|
||||
defer gl.detachShader(prog, vertex_shader);
|
||||
try self.addDependencies(id, &.{ program.vertex.id, program.fragment.id });
|
||||
|
||||
gl.attachShader(prog, fragment_shader);
|
||||
defer gl.detachShader(prog, fragment_shader);
|
||||
const prog = gl.createProgram();
|
||||
errdefer gl.deleteProgram(prog);
|
||||
|
||||
gl.attachShader(prog, vertex.shader);
|
||||
errdefer gl.detachShader(prog, vertex.shader);
|
||||
gl.attachShader(prog, fragment.shader);
|
||||
errdefer gl.detachShader(prog, fragment.shader);
|
||||
|
||||
gl.linkProgram(prog);
|
||||
|
||||
@ -204,12 +172,23 @@ fn loadShaderProgramErr(self: *Assets, prog: gl.GLuint, params: ShaderProgramDef
|
||||
return error.ProgramLinkFailed;
|
||||
}
|
||||
|
||||
return .{ .vert_modified = vertex_file.modified, .frag_modified = fragment_file.modified };
|
||||
try self.loaded_assets.put(self.allocator, id, .{
|
||||
.shaderProgram = .{ .program = prog },
|
||||
});
|
||||
try self.modified_times.put(self.allocator, id, data.modified);
|
||||
|
||||
return &self.loaded_assets.getPtr(id).?.shaderProgram;
|
||||
}
|
||||
|
||||
const NullMesh = LoadedMesh{
|
||||
.modified = 0,
|
||||
const NullShader = LoadedShader{
|
||||
.shader = 0,
|
||||
};
|
||||
|
||||
const NullShaderProgram = LoadedShaderProgram{
|
||||
.program = 0,
|
||||
};
|
||||
|
||||
const NullMesh = LoadedMesh{
|
||||
.positions = BufferSlice{
|
||||
.buffer = 0,
|
||||
.offset = 0,
|
||||
@ -228,14 +207,14 @@ const NullMesh = LoadedMesh{
|
||||
},
|
||||
};
|
||||
|
||||
pub fn loadMesh(self: *Assets, id: AssetId) *const LoadedMesh {
|
||||
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) });
|
||||
return &NullMesh;
|
||||
};
|
||||
}
|
||||
|
||||
fn loadMeshErr(self: *Assets, id: AssetId) !*const LoadedMesh {
|
||||
fn loadMeshErr(self: *AssetManager, id: AssetId) !*const LoadedMesh {
|
||||
const path = asset_manifest.getPath(id);
|
||||
const data = try self.loadFile(self.frame_arena, path, MESH_MAX_BYTES);
|
||||
const mesh = formats.Mesh.fromBuffer(data.bytes);
|
||||
@ -273,8 +252,6 @@ fn loadMeshErr(self: *Assets, id: AssetId) !*const LoadedMesh {
|
||||
// gl.bindVertexBuffer(_bindingindex: GLuint, _buffer: GLuint, _offset: GLintptr, _stride: GLsizei)
|
||||
|
||||
const loaded_mesh = LoadedMesh{
|
||||
.modified = data.modified,
|
||||
|
||||
.positions = .{
|
||||
.buffer = vertices,
|
||||
.offset = 0,
|
||||
@ -294,32 +271,24 @@ fn loadMeshErr(self: *Assets, id: AssetId) !*const LoadedMesh {
|
||||
};
|
||||
|
||||
try self.loaded_assets.put(self.allocator, id, .{ .mesh = loaded_mesh });
|
||||
return @ptrCast(&self.loaded_assets.getPtr(id).?.mesh);
|
||||
}
|
||||
|
||||
fn nextId(self: *Assets) AssetId {
|
||||
const id = self.next_id;
|
||||
self.next_id += 1;
|
||||
|
||||
return id;
|
||||
return &self.loaded_assets.getPtr(id).?.mesh;
|
||||
}
|
||||
|
||||
const LoadedAsset = union(enum) {
|
||||
shader: LoadedShader,
|
||||
shaderProgram: LoadedShaderProgram,
|
||||
mesh: LoadedMesh,
|
||||
};
|
||||
|
||||
const LoadedShader = struct {
|
||||
shader: gl.GLuint = 0,
|
||||
};
|
||||
|
||||
const LoadedShaderProgram = struct {
|
||||
definition: ShaderProgramDefinition,
|
||||
// TODO: rendering abstraction in the future maybe
|
||||
program: gl.GLuint,
|
||||
vert_modified: i128,
|
||||
frag_modified: i128,
|
||||
};
|
||||
|
||||
const LoadedMesh = struct {
|
||||
modified: i128,
|
||||
|
||||
positions: BufferSlice,
|
||||
normals: BufferSlice,
|
||||
indices: IndexSlice,
|
||||
@ -342,12 +311,7 @@ pub const IndexSlice = struct {
|
||||
type: gl.GLenum,
|
||||
};
|
||||
|
||||
const WatchedAssetFile = struct {
|
||||
modified: i128,
|
||||
asset_id: AssetId,
|
||||
};
|
||||
|
||||
const ShaderType = enum {
|
||||
pub const ShaderType = enum {
|
||||
vertex,
|
||||
fragment,
|
||||
|
||||
@ -364,7 +328,7 @@ const AssetData = struct {
|
||||
modified: i128,
|
||||
};
|
||||
|
||||
fn loadFile(self: *Assets, allocator: std.mem.Allocator, path: []const u8, max_size: usize) !AssetData {
|
||||
fn loadFile(self: *AssetManager, allocator: std.mem.Allocator, path: []const u8, max_size: usize) !AssetData {
|
||||
const file = try self.exe_dir.openFile(path, .{});
|
||||
defer file.close();
|
||||
const meta = try file.metadata();
|
||||
@ -373,11 +337,21 @@ fn loadFile(self: *Assets, allocator: std.mem.Allocator, path: []const u8, max_s
|
||||
return .{ .bytes = bytes, .modified = meta.modified() };
|
||||
}
|
||||
|
||||
fn loadShader(arena: std.mem.Allocator, shader_type: ShaderType, source: []const u8) !gl.GLuint {
|
||||
fn loadShader(self: *AssetManager, id: AssetId, shader_type: ShaderType) *const LoadedShader {
|
||||
return self.loadShaderErr(id, shader_type) catch |err| {
|
||||
std.log.err("Error: {} when loading shader id {} {s}", .{ err, id, asset_manifest.getPath(id) });
|
||||
return &NullShader;
|
||||
};
|
||||
}
|
||||
|
||||
fn loadShaderErr(self: *AssetManager, id: AssetId, shader_type: ShaderType) !*LoadedShader {
|
||||
const path = asset_manifest.getPath(id);
|
||||
|
||||
const data = try self.loadFile(self.frame_arena, path, SHADER_MAX_BYTES);
|
||||
const shader = gl.createShader(shader_type.goGLType());
|
||||
errdefer gl.deleteShader(shader);
|
||||
std.debug.assert(shader != 0); // should only happen if incorect shader type is passed
|
||||
gl.shaderSource(shader, 1, &[_][*c]const u8{@ptrCast(source)}, &[_]gl.GLint{@intCast(source.len)});
|
||||
gl.shaderSource(shader, 1, &[_][*c]const u8{@ptrCast(data.bytes)}, &[_]gl.GLint{@intCast(data.bytes.len)});
|
||||
gl.compileShader(shader);
|
||||
var success: c_int = 0;
|
||||
gl.getShaderiv(shader, gl.COMPILE_STATUS, &success);
|
||||
@ -385,14 +359,35 @@ fn loadShader(arena: std.mem.Allocator, shader_type: ShaderType, source: []const
|
||||
var info_len: gl.GLint = 0;
|
||||
gl.getShaderiv(shader, gl.INFO_LOG_LENGTH, &info_len);
|
||||
if (info_len > 0) {
|
||||
const info_log = try arena.allocSentinel(u8, @intCast(info_len - 1), 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}\n", .{info_log});
|
||||
std.log.err("ERROR::SHADER::COMPILATION_FAILED\n{s}\n{s}\n", .{ data.bytes, info_log });
|
||||
} else {
|
||||
std.log.err("ERROR::SHADER::COMPILIATION_FAILED\nNo info log.\n", .{});
|
||||
std.log.err("ERROR::SHADER::COMPILIATION_FAILED\n{s}\nNo info log.\n", .{data.bytes});
|
||||
}
|
||||
return error.ShaderCompilationFailed;
|
||||
}
|
||||
|
||||
return shader;
|
||||
try self.loaded_assets.put(self.allocator, id, .{ .shader = LoadedShader{ .shader = shader } });
|
||||
try self.modified_times.put(self.allocator, id, data.modified);
|
||||
|
||||
return &self.loaded_assets.getPtr(id).?.shader;
|
||||
}
|
||||
|
||||
fn addDependencies(self: *AssetManager, id: AssetId, dependencies: []const AssetId) !void {
|
||||
{
|
||||
const gop = try self.dependencies.getOrPut(self.allocator, id);
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = .{};
|
||||
}
|
||||
try gop.value_ptr.appendSlice(self.allocator, dependencies);
|
||||
}
|
||||
|
||||
for (dependencies) |dep| {
|
||||
const gop = try self.dependees.getOrPut(self.allocator, dep);
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = .{};
|
||||
}
|
||||
try gop.value_ptr.append(self.allocator, id);
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
// Generated file, do not edit manually!
|
||||
|
||||
const Handle = @import("Assets.zig").Handle;
|
||||
|
||||
pub const Meshes = struct {
|
||||
pub const bunny = Handle.Mesh{ .id = 1 };
|
||||
};
|
||||
pub const asset_paths = [_][]const u8{
|
||||
".\\assets\\bunny.mesh",
|
||||
};
|
||||
pub fn getPath(asset_id: u32) []const u8 { return asset_paths[asset_id - 1]; }
|
7
src/assets/root.zig
Normal file
7
src/assets/root.zig
Normal file
@ -0,0 +1,7 @@
|
||||
pub const AssetId = u32;
|
||||
|
||||
pub const Handle = struct {
|
||||
pub const Shader = extern struct { id: AssetId = 0 };
|
||||
pub const ShaderProgram = extern struct { id: AssetId = 0 };
|
||||
pub const Mesh = struct { id: AssetId = 0 };
|
||||
};
|
@ -1,5 +1,6 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const Handle = @import("assets").Handle;
|
||||
|
||||
pub const native_endian = builtin.cpu.arch.endian();
|
||||
|
||||
@ -68,6 +69,20 @@ pub fn writeMesh(writer: anytype, value: Mesh, endian: std.builtin.Endian) !void
|
||||
}
|
||||
}
|
||||
|
||||
pub const ShaderProgram = extern struct {
|
||||
vertex: Handle.Shader,
|
||||
fragment: Handle.Shader,
|
||||
|
||||
pub fn fromBuffer(buf: []u8) *align(1) ShaderProgram {
|
||||
return @ptrCast(buf);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn writeShaderProgram(writer: anytype, vertex: u32, fragment: u32, endian: std.builtin.Endian) !void {
|
||||
try writer.writeInt(u32, vertex, endian);
|
||||
try writer.writeInt(u32, fragment, endian);
|
||||
}
|
||||
|
||||
fn writeVector3(writer: anytype, value: Vector3, endian: std.builtin.Endian) !void {
|
||||
try writeFloat(writer, value.x, endian);
|
||||
try writeFloat(writer, value.y, endian);
|
||||
|
34
src/game.zig
34
src/game.zig
@ -1,12 +1,12 @@
|
||||
const std = @import("std");
|
||||
const c = @import("c.zig");
|
||||
const gl = @import("gl.zig");
|
||||
const Assets = @import("Assets.zig");
|
||||
const AssetManager = @import("AssetManager.zig");
|
||||
const formats = @import("formats.zig");
|
||||
const zlm = @import("zlm");
|
||||
const Vec3 = zlm.Vec3;
|
||||
const Mat4 = zlm.Mat4;
|
||||
const a = @import("asset_manifest.zig");
|
||||
const a = @import("asset_manifest");
|
||||
|
||||
const FRAME_ARENA_SIZE = 1024 * 1024 * 512;
|
||||
|
||||
@ -41,14 +41,11 @@ pub const InitMemory = struct {
|
||||
pub const GameMemory = struct {
|
||||
global_allocator: std.mem.Allocator,
|
||||
frame_fba: std.heap.FixedBufferAllocator,
|
||||
assets: Assets,
|
||||
assetman: AssetManager,
|
||||
counter: i32 = 0,
|
||||
triangle_vao: gl.GLuint = 0,
|
||||
triangle_vbo: gl.GLuint = 0,
|
||||
shader_program: Assets.Handle.ShaderProgram = .{},
|
||||
mesh_program: Assets.Handle.ShaderProgram = .{},
|
||||
mesh_vao: gl.GLuint = 0,
|
||||
mesh: Assets.Handle.Mesh = .{},
|
||||
camera_ubo: gl.GLuint = 0,
|
||||
camera_matrices: []CameraMatrices,
|
||||
current_camera_matrix: usize = 0,
|
||||
@ -58,7 +55,7 @@ pub const GameMemory = struct {
|
||||
var g_init_exists = false;
|
||||
var g_init: *InitMemory = undefined;
|
||||
var g_mem: *GameMemory = undefined;
|
||||
var g_assets: *Assets = undefined;
|
||||
var g_assetman: *AssetManager = undefined;
|
||||
|
||||
fn game_init_window_err(global_allocator: std.mem.Allocator) !void {
|
||||
try sdl_try(c.SDL_Init(c.SDL_INIT_EVERYTHING));
|
||||
@ -140,17 +137,19 @@ fn checkError() void {
|
||||
}
|
||||
}
|
||||
|
||||
const mesh_program = a.ShaderPrograms.mesh;
|
||||
|
||||
export fn game_init(global_allocator: *std.mem.Allocator) void {
|
||||
std.log.debug("game_init\n", .{});
|
||||
g_mem = global_allocator.create(GameMemory) catch @panic("OOM");
|
||||
const frame_arena_buffer = global_allocator.alloc(u8, FRAME_ARENA_SIZE) catch @panic("OOM");
|
||||
g_mem.global_allocator = global_allocator.*;
|
||||
g_mem.frame_fba = std.heap.FixedBufferAllocator.init(frame_arena_buffer);
|
||||
g_mem.assets = Assets.init(
|
||||
g_mem.assetman = AssetManager.init(
|
||||
global_allocator.*,
|
||||
g_mem.frame_fba.allocator(),
|
||||
);
|
||||
g_assets = &g_mem.assets;
|
||||
g_assetman = &g_mem.assetman;
|
||||
|
||||
loadGL();
|
||||
|
||||
@ -173,11 +172,8 @@ export fn game_init(global_allocator: *std.mem.Allocator) void {
|
||||
gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, @sizeOf(f32) * 3, @ptrFromInt(0));
|
||||
gl.enableVertexAttribArray(0);
|
||||
|
||||
g_mem.shader_program = g_assets.loadShaderProgram(.{ .vertex = "assets/shaders/vert.glsl", .fragment = "assets/shaders/frag.glsl" });
|
||||
|
||||
// MESH PROGRAM
|
||||
g_mem.mesh_program = g_assets.loadShaderProgram(.{ .vertex = "assets/shaders/mesh.vert.glsl", .fragment = "assets/shaders/mesh.frag.glsl" });
|
||||
const mesh_program_name = g_mem.mesh_program.resolve(g_assets);
|
||||
const mesh_program_name = g_assetman.resolveShaderProgram(mesh_program).program;
|
||||
|
||||
gl.uniformBlockBinding(mesh_program_name, 0, UBO.CameraMatrices.value());
|
||||
|
||||
@ -199,10 +195,6 @@ export fn game_init(global_allocator: *std.mem.Allocator) void {
|
||||
gl.vertexArrayAttribFormat(vao, Attrib.Normal.value(), 3, gl.FLOAT, gl.FALSE, 0);
|
||||
gl.enableVertexArrayAttrib(vao, Attrib.Normal.value());
|
||||
|
||||
// MESH ITSELF
|
||||
// TODO: asset paths relative to exe
|
||||
g_mem.mesh = a.Meshes.bunny;
|
||||
|
||||
var camera_ubo: gl.GLuint = 0;
|
||||
gl.createBuffers(1, &camera_ubo);
|
||||
|
||||
@ -293,7 +285,7 @@ export fn game_update() bool {
|
||||
gl.clearColor(0.0, 0.0, 0.0, 1.0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||
|
||||
gl.useProgram(g_mem.mesh_program.resolve(g_assets));
|
||||
gl.useProgram(g_assetman.resolveShaderProgram(a.ShaderPrograms.mesh).program);
|
||||
gl.bindVertexArray(g_mem.mesh_vao);
|
||||
|
||||
gl.bindBufferRange(
|
||||
@ -306,7 +298,7 @@ export fn game_update() bool {
|
||||
g_mem.rotation += 0.001;
|
||||
gl.uniformMatrix4fv(1, 1, gl.FALSE, @ptrCast(&Mat4.createAngleAxis(Vec3.unitY, g_mem.rotation).fields));
|
||||
|
||||
const mesh = g_mem.mesh.resolve(g_assets);
|
||||
const mesh = g_assetman.resolveMesh(a.Meshes.bunny);
|
||||
mesh.positions.bind(Attrib.Position.value());
|
||||
mesh.normals.bind(Attrib.Normal.value());
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, mesh.indices.buffer);
|
||||
@ -320,7 +312,7 @@ export fn game_update() bool {
|
||||
c.SDL_GL_SwapWindow(g_init.window);
|
||||
c.SDL_Delay(1);
|
||||
|
||||
g_assets.watchChanges();
|
||||
g_assetman.watchChanges();
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -349,7 +341,7 @@ export fn game_hot_reload(init_memory: ?*anyopaque, gmemory: ?*anyopaque) void {
|
||||
}
|
||||
if (gmemory) |gmem| {
|
||||
g_mem = @alignCast(@ptrCast(gmem));
|
||||
g_assets = &g_mem.assets;
|
||||
g_assetman = &g_mem.assetman;
|
||||
}
|
||||
if (g_init_exists) {
|
||||
c.SDL_RaiseWindow(g_init.window);
|
||||
|
37
src/gen/asset_manifest.gen.zig
Normal file
37
src/gen/asset_manifest.gen.zig
Normal file
@ -0,0 +1,37 @@
|
||||
// Generated file, do not edit manually!
|
||||
|
||||
const std = @import("std");
|
||||
const Handle = @import("assets").Handle;
|
||||
|
||||
pub const Meshes = struct {
|
||||
pub const bunny = Handle.Mesh{ .id = 1 };
|
||||
};
|
||||
|
||||
pub const Shaders = struct {
|
||||
pub const frag = Handle.Shader{ .id = 2 };
|
||||
pub const @"mesh.frag" = Handle.Shader{ .id = 3 };
|
||||
pub const @"mesh.vert" = Handle.Shader{ .id = 5 };
|
||||
pub const vert = Handle.Shader{ .id = 6 };
|
||||
};
|
||||
|
||||
pub const ShaderPrograms = struct {
|
||||
pub const mesh = Handle.ShaderProgram{ .id = 4 };
|
||||
};
|
||||
|
||||
pub const asset_paths = [_][]const u8{
|
||||
"assets\\bunny.mesh",
|
||||
"assets\\shaders\\frag.glsl",
|
||||
"assets\\shaders\\mesh.frag.glsl",
|
||||
"assets\\shaders\\mesh.prog",
|
||||
"assets\\shaders\\mesh.vert.glsl",
|
||||
"assets\\shaders\\vert.glsl",
|
||||
};
|
||||
|
||||
pub const asset_path_to_asset_id = std.ComptimeStringMap(u32, .{
|
||||
.{ "assets\\bunny.mesh", 1 },
|
||||
.{ "assets\\shaders\\frag.glsl", 2 },
|
||||
.{ "assets\\shaders\\mesh.frag.glsl", 3 },
|
||||
.{ "assets\\shaders\\mesh.prog", 4 },
|
||||
.{ "assets\\shaders\\mesh.vert.glsl", 5 },
|
||||
.{ "assets\\shaders\\vert.glsl", 6 },
|
||||
});
|
15
src/gen/asset_manifest.zig
Normal file
15
src/gen/asset_manifest.zig
Normal file
@ -0,0 +1,15 @@
|
||||
pub const manifest = @import("asset_manifest.gen.zig");
|
||||
|
||||
pub const Meshes = manifest.Meshes;
|
||||
pub const Shaders = manifest.Shaders;
|
||||
pub const ShaderPrograms = manifest.ShaderPrograms;
|
||||
|
||||
pub fn getPath(asset_id: u32) []const u8 {
|
||||
if (asset_id == 0) return "";
|
||||
|
||||
return manifest.asset_paths[asset_id - 1];
|
||||
}
|
||||
|
||||
pub fn getAssetByPath(path: []const u8) u32 {
|
||||
return manifest.asset_path_to_asset_id.get(path) orelse 0;
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
const std = @import("std");
|
||||
const formats = @import("formats");
|
||||
const asset_manifest = @import("asset_manifest");
|
||||
const Vector3 = formats.Vector3;
|
||||
const c = @cImport({
|
||||
@cInclude("assimp/cimport.h");
|
||||
@ -8,18 +9,48 @@ const c = @cImport({
|
||||
@cInclude("assimp/postprocess.h");
|
||||
});
|
||||
|
||||
const ASSET_MAX_BYTES = 1024 * 1024 * 1024;
|
||||
|
||||
const AssetType = enum {
|
||||
Mesh,
|
||||
Shader,
|
||||
ShaderProgram,
|
||||
};
|
||||
|
||||
pub fn resolveAssetTypeByExtension(path: []const u8) ?AssetType {
|
||||
if (std.mem.endsWith(u8, path, ".obj")) {
|
||||
return .Mesh;
|
||||
}
|
||||
if (std.mem.endsWith(u8, path, ".prog")) {
|
||||
return .ShaderProgram;
|
||||
}
|
||||
if (std.mem.endsWith(u8, path, ".glsl")) {
|
||||
return .Shader;
|
||||
}
|
||||
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 <basedir> <input> <output>\n", .{});
|
||||
return error.MissingArgs;
|
||||
}
|
||||
|
||||
const input = std.os.argv[argv.len - 2];
|
||||
const output = std.mem.span(std.os.argv[argv.len - 1]);
|
||||
const input = argv[argv.len - 2];
|
||||
const output = std.mem.span(argv[argv.len - 1]);
|
||||
|
||||
std.log.debug("input: {s}\n", .{input});
|
||||
const asset_type = resolveAssetTypeByExtension(std.mem.span(input)) orelse return error.UnknownAssetType;
|
||||
|
||||
switch (asset_type) {
|
||||
.Mesh => try processMesh(allocator, input, output),
|
||||
.ShaderProgram => try processShaderProgram(allocator, std.mem.span(input), output),
|
||||
else => return error.DontProcessShaders,
|
||||
}
|
||||
}
|
||||
|
||||
fn processMesh(allocator: std.mem.Allocator, input: [*:0]const u8, output: []const u8) !void {
|
||||
const maybe_scene: ?*const c.aiScene = @ptrCast(c.aiImportFile(
|
||||
input,
|
||||
c.aiProcess_CalcTangentSpace | c.aiProcess_Triangulate | c.aiProcess_JoinIdenticalVertices | c.aiProcess_SortByPType | c.aiProcess_GenNormals,
|
||||
@ -79,3 +110,52 @@ pub fn main() !void {
|
||||
);
|
||||
try buf_writer.flush();
|
||||
}
|
||||
|
||||
fn processShaderProgram(allocator: std.mem.Allocator, absolute_input: []const u8, output: []const u8) !void {
|
||||
var cwd_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
||||
const cwd_path = try std.os.getcwd(&cwd_buf);
|
||||
|
||||
const input = try std.fs.path.relative(allocator, cwd_path, absolute_input);
|
||||
defer allocator.free(input);
|
||||
|
||||
const input_dir = std.fs.path.dirname(input).?;
|
||||
|
||||
var file_contents: []u8 = undefined;
|
||||
{
|
||||
const input_file = try std.fs.cwd().openFile(input, .{});
|
||||
defer input_file.close();
|
||||
file_contents = try input_file.readToEndAlloc(allocator, ASSET_MAX_BYTES);
|
||||
}
|
||||
defer allocator.free(file_contents);
|
||||
|
||||
const ShaderProgram = struct {
|
||||
vertex: []const u8,
|
||||
fragment: []const u8,
|
||||
};
|
||||
const program = try std.json.parseFromSlice(ShaderProgram, allocator, file_contents, .{});
|
||||
defer program.deinit();
|
||||
|
||||
const vertex_path = try std.fs.path.resolve(allocator, &.{ input_dir, program.value.vertex });
|
||||
|
||||
const vertex_asset_id = asset_manifest.getAssetByPath(vertex_path);
|
||||
if (vertex_asset_id == 0) {
|
||||
std.log.debug("{s}\n", .{vertex_path});
|
||||
return error.InvalidVertexAssetPath;
|
||||
}
|
||||
|
||||
const frag_path = try std.fs.path.resolve(allocator, &.{ input_dir, program.value.fragment });
|
||||
defer allocator.free(frag_path);
|
||||
|
||||
const frag_asset_id = asset_manifest.getAssetByPath(frag_path);
|
||||
if (vertex_asset_id == 0) {
|
||||
std.log.debug("{s}\n", .{frag_path});
|
||||
return error.InvalidFragmentAssetPath;
|
||||
}
|
||||
|
||||
const out_file = try std.fs.createFileAbsolute(output, .{});
|
||||
defer out_file.close();
|
||||
var buf_writer = std.io.bufferedWriter(out_file.writer());
|
||||
|
||||
try formats.writeShaderProgram(buf_writer.writer(), vertex_asset_id, frag_asset_id, formats.native_endian);
|
||||
try buf_writer.flush();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user