adding compileShaders.zig
This commit is contained in:
parent
3c1ae7d1b6
commit
5ee9ff78e0
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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!", .{});
|
||||
}
|
||||
Loading…
Reference in New Issue