Rewrite how all assets are loaded to make it easier to use

This commit is contained in:
sergeypdev 2024-02-09 01:34:33 +04:00
parent d91484e992
commit a837984d55
10 changed files with 384 additions and 187 deletions

4
assets/shaders/mesh.prog Normal file
View File

@ -0,0 +1,4 @@
{
"vertex": "mesh.vert.glsl",
"fragment": "mesh.frag.glsl"
}

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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