adding compileShaders.zig

This commit is contained in:
peterino2 2025-09-01 14:25:32 -07:00
parent 3c1ae7d1b6
commit 5ee9ff78e0
2 changed files with 317 additions and 0 deletions

View File

@ -0,0 +1,25 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "compileShaders",
.root_source_file = b.path("compileShaders.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the compileShaders tool");
run_step.dependOn(&run_cmd.step);
}

View File

@ -0,0 +1,292 @@
const std = @import("std");
const builtin = @import("builtin");
var stringsArena: std.heap.ArenaAllocator = undefined;
fn printLog(comptime fmt: []const u8, args: anytype) void {
std.debug.print(fmt ++ "\n", args);
}
fn printErr(comptime fmt: []const u8, args: anytype) void {
std.debug.print("ERROR: " ++ fmt ++ "\n", args);
}
const OutputFormat = struct {
extension: []const u8,
args: []const []const u8,
};
const ShaderFile = struct {
path: []const u8,
base_name: []const u8,
source_dir: []const u8,
};
fn findContentDir(allocator: std.mem.Allocator) ![]const u8 {
// First try local content directory
var content_dir = std.fs.cwd().openDir("content", .{}) catch |err| switch (err) {
error.FileNotFound => {
// Walk up directories looking for content.txt
var current_dir = try std.fs.cwd().realpathAlloc(allocator, ".");
defer allocator.free(current_dir);
var last_path: ?[]const u8 = null;
while (last_path == null or !std.mem.eql(u8, current_dir, last_path.?)) {
const content_txt_path = try std.fs.path.join(allocator, &.{ current_dir, "content.txt" });
defer allocator.free(content_txt_path);
if (std.fs.cwd().openFile(content_txt_path, .{})) |file| {
defer file.close();
const content = try file.readToEndAlloc(allocator, 1024);
defer allocator.free(content);
const rel_path = std.mem.trim(u8, content, " \t\n\r");
const abs_path = try std.fs.path.join(allocator, &.{ current_dir, rel_path });
return try stringsArena.allocator().dupe(u8, abs_path);
} else |_| {
last_path = current_dir;
const parent = std.fs.path.dirname(current_dir) orelse break;
current_dir = try allocator.dupe(u8, parent);
if (last_path) |lp| allocator.free(lp);
}
}
return error.ContentDirNotFound;
},
else => return err,
};
defer content_dir.close();
const cwd_path = try std.fs.cwd().realpathAlloc(allocator, ".");
defer allocator.free(cwd_path);
const content_path = try std.fs.path.join(allocator, &.{ cwd_path, "content" });
return try stringsArena.allocator().dupe(u8, content_path);
}
fn getShadercrossPath(allocator: std.mem.Allocator) ![]const u8 {
const cwd_path = try std.fs.cwd().realpathAlloc(allocator, ".");
defer allocator.free(cwd_path);
const lib_path = try std.fs.path.join(allocator, &.{ cwd_path, "..", "..", "lib", "sdl3", "shadercross", "bin" });
defer allocator.free(lib_path);
const platform_dir = switch (builtin.os.tag) {
.linux => "linux",
.macos => "osx",
.windows => "win64",
else => return error.UnsupportedPlatform,
};
const platform_path = try std.fs.path.join(allocator, &.{ lib_path, platform_dir });
defer allocator.free(platform_path);
const exe_name = if (builtin.os.tag == .windows) "shadercross.exe" else "shadercross";
const exe_path = try std.fs.path.join(allocator, &.{ platform_path, exe_name });
return try stringsArena.allocator().dupe(u8, exe_path);
}
fn discoverShaders(allocator: std.mem.Allocator) !std.ArrayList(ShaderFile) {
var result = std.ArrayList(ShaderFile).init(allocator);
const cwd_path = try std.fs.cwd().realpathAlloc(allocator, ".");
defer allocator.free(cwd_path);
const engine_path = try std.fs.path.join(allocator, &.{ cwd_path, "..", "..", "engine" });
defer allocator.free(engine_path);
var engine_dir = std.fs.cwd().openDir(engine_path, .{ .iterate = true }) catch |err| {
printErr("Failed to open engine directory: {}", .{err});
return result;
};
defer engine_dir.close();
var engine_iter = engine_dir.iterate();
while (try engine_iter.next()) |entry| {
if (entry.kind != .directory) continue;
const module_path = try std.fs.path.join(allocator, &.{ engine_path, entry.name });
defer allocator.free(module_path);
const shaders_path = try std.fs.path.join(allocator, &.{ module_path, "shaders" });
defer allocator.free(shaders_path);
var shaders_dir = std.fs.cwd().openDir(shaders_path, .{ .iterate = true }) catch continue;
defer shaders_dir.close();
var shader_iter = shaders_dir.iterate();
while (try shader_iter.next()) |shader_entry| {
if (shader_entry.kind != .file) continue;
if (!std.mem.endsWith(u8, shader_entry.name, ".hlsl")) continue;
const full_path = try std.fs.path.join(allocator, &.{ shaders_path, shader_entry.name });
const base_name = shader_entry.name[0 .. shader_entry.name.len - 5]; // Remove .hlsl
try result.append(.{
.path = try stringsArena.allocator().dupe(u8, full_path),
.base_name = try stringsArena.allocator().dupe(u8, base_name),
.source_dir = try stringsArena.allocator().dupe(u8, shaders_path),
});
}
}
return result;
}
fn runCommand(allocator: std.mem.Allocator, argv: []const []const u8) !void {
printLog("Running command: {s}", .{try std.mem.join(allocator, " ", argv)});
var child = std.process.Child.init(argv, allocator);
child.stdout_behavior = .Inherit;
child.stderr_behavior = .Inherit;
const term = try child.spawnAndWait();
switch (term) {
.Exited => |code| {
if (code != 0) {
return error.CommandFailed;
}
},
else => return error.CommandFailed,
}
}
fn ensureDir(path: []const u8) !void {
std.fs.cwd().makePath(path) catch |err| switch (err) {
error.PathAlreadyExists => {},
else => return err,
};
}
fn cookShaders(allocator: std.mem.Allocator, shader_files: []const ShaderFile, content_dir: []const u8, shadercross_path: []const u8) !void {
const cooked_root = try std.fs.path.join(allocator, &.{ content_dir, "_shaders" });
defer allocator.free(cooked_root);
const output_formats = [_]OutputFormat{
.{ .extension = "dxil", .args = &.{} },
.{ .extension = "spv", .args = &.{} },
.{ .extension = "msl", .args = &.{} },
};
var spv_files = std.ArrayList(struct { spv_path: []const u8, source_dir: []const u8 }).init(allocator);
defer spv_files.deinit();
// Compile shaders to all formats
for (output_formats) |format| {
const format_dir = try std.fs.path.join(allocator, &.{ cooked_root, format.extension });
defer allocator.free(format_dir);
try ensureDir(format_dir);
for (shader_files) |shader| {
const output_name = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ shader.base_name, format.extension });
defer allocator.free(output_name);
const output_path = try std.fs.path.join(allocator, &.{ format_dir, output_name });
defer allocator.free(output_path);
var cmd_args = std.ArrayList([]const u8).init(allocator);
defer cmd_args.deinit();
try cmd_args.append(shadercross_path);
try cmd_args.append(shader.path);
for (format.args) |arg| {
try cmd_args.append(arg);
}
try cmd_args.append("-o");
try cmd_args.append(output_path);
try runCommand(allocator, cmd_args.items);
// Store SPV files for reflection processing
if (std.mem.eql(u8, format.extension, "spv")) {
try spv_files.append(.{
.spv_path = try stringsArena.allocator().dupe(u8, output_path),
.source_dir = shader.source_dir,
});
}
}
}
// Generate reflection data for SPV files
for (spv_files.items) |spv_file| {
// Get file size to ensure it's not empty
const file = std.fs.cwd().openFile(spv_file.spv_path, .{}) catch continue;
defer file.close();
const file_size = try file.getEndPos();
if (file_size <= 1) {
printErr("SPV file is empty or too small: {s}", .{spv_file.spv_path});
continue;
}
const base_name = std.fs.path.basename(spv_file.spv_path);
const name_no_ext = base_name[0 .. base_name.len - 4]; // Remove .spv
const json_name = try std.fmt.allocPrint(allocator, "{s}.json", .{name_no_ext});
defer allocator.free(json_name);
const json_path = try std.fs.path.join(allocator, &.{ spv_file.source_dir, json_name });
defer allocator.free(json_path);
const cmd_args = [_][]const u8{
"spirv-cross",
spv_file.spv_path,
"--reflect",
"--output",
json_path,
};
try runCommand(allocator, &cmd_args);
}
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
defer arena.deinit();
const allocator = arena.allocator();
stringsArena = std.heap.ArenaAllocator.init(gpa.allocator());
defer stringsArena.deinit();
printLog("Starting shader compilation...", .{});
// Find content directory
const content_dir = findContentDir(allocator) catch |err| {
printErr("Failed to find content directory: {}", .{err});
return;
};
printLog("Content target dir: {s}", .{content_dir});
// Get shadercross path
const shadercross_path = getShadercrossPath(allocator) catch |err| {
printErr("Failed to get shadercross path: {}", .{err});
return;
};
printLog("Shadercross path: {s}", .{shadercross_path});
// Discover shader files
var shader_files = discoverShaders(allocator) catch |err| {
printErr("Failed to discover shaders: {}", .{err});
return;
};
defer shader_files.deinit();
printLog("Found {} shader files:", .{shader_files.items.len});
for (shader_files.items) |shader| {
printLog(" {s}", .{shader.path});
}
// Cook shaders
cookShaders(allocator, shader_files.items, content_dir, shadercross_path) catch |err| {
printErr("Failed to cook shaders: {}", .{err});
return;
};
printLog("Shader compilation completed successfully!", .{});
}