summaryrefslogtreecommitdiff
path: root/src/gui/Post.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/Post.zig')
-rw-r--r--src/gui/Post.zig83
1 files changed, 50 insertions, 33 deletions
diff --git a/src/gui/Post.zig b/src/gui/Post.zig
index d92623d7..c529efc0 100644
--- a/src/gui/Post.zig
+++ b/src/gui/Post.zig
@@ -15,7 +15,6 @@ const libc = std.c;
const ghostty_vt = @import("ghostty-vt");
const pardes = @import("../pardes.zig");
const filesystem = @import("../fs.zig");
-const host_io = @import("../host_io.zig");
const gui = @import("gui.zig");
const c = gui.c;
@@ -191,8 +190,9 @@ fn collect(post: *Post, gpa: std.mem.Allocator, device: *c.SDL_GPUDevice, format
}
}
-/// The compile thread: each file behind the prefix, through glslc exactly as
-/// the build calls it (build.zig compileGlsl), its SPIR-V on stdout.
+/// The compile thread: each file behind the prefix, through glslc with the
+/// build's own flags (build.zig compileGlsl), source on stdin, SPIR-V on
+/// stdout.
fn compile(job: *Job) void {
defer job.done.store(true, .release);
defer {
@@ -200,10 +200,10 @@ fn compile(job: *Job) void {
event.type = c.SDL_EVENT_USER;
_ = c.SDL_PushEvent(&event);
}
- for (job.paths[0..job.len], 0..) |path, i| job.results[i] = compileOne(job.gpa, job.io, path, i);
+ for (job.paths[0..job.len], 0..) |path, i| job.results[i] = compileOne(job.gpa, job.io, path);
}
-fn compileOne(gpa: std.mem.Allocator, io: std.Io, path: []const u8, index: usize) Job.Result {
+fn compileOne(gpa: std.mem.Allocator, io: std.Io, path: []const u8) Job.Result {
var home_buf: [4096]u8 = undefined;
const expanded = if (std.mem.startsWith(u8, path, "~/")) if (libc.getenv("HOME")) |home|
std.fmt.bufPrint(&home_buf, "{s}/{s}", .{ std.mem.span(home), path[2..] }) catch path
@@ -212,37 +212,54 @@ fn compileOne(gpa: std.mem.Allocator, io: std.Io, path: []const u8, index: usize
const body = filesystem.readFile(gpa, expanded) catch |err|
return .{ .failed = std.fmt.allocPrint(gpa, "cannot read it ({t})", .{err}) catch return .none };
defer gpa.free(body);
- const dir = if (libc.getenv("XDG_RUNTIME_DIR")) |d| std.mem.span(d) else "/tmp";
- var source_buf: [4096]u8 = undefined;
- const source = std.fmt.bufPrintSentinel(&source_buf, "{s}/pardes-post-{d}-{d}.frag.glsl", .{ dir, libc.getpid(), index }, 0) catch return .none;
- defer _ = libc.unlink(source);
- {
- const fd = libc.open(source, .{ .ACCMODE = .WRONLY, .CREAT = true, .TRUNC = true }, @as(libc.mode_t, 0o600));
- if (fd < 0) return .{ .failed = gpa.dupe(u8, "cannot write its source for glslc") catch return .none };
- defer _ = libc.close(fd);
- if (!host_io.writeFd(fd, prefix) or !host_io.writeFd(fd, body))
- return .{ .failed = gpa.dupe(u8, "cannot write its source for glslc") catch return .none };
- }
- const run = std.process.run(gpa, io, .{
- .argv = &.{ "glslc", "-fshader-stage=fragment", source, "-o", "-" },
- .stdout_limit = .limited(16 << 20),
- .stderr_limit = .limited(64 << 10),
+ // The prefix and the file go to glslc on its stdin: no file of ours is
+ // written anywhere. std's spawn does every allocation before fork and
+ // searches PATH on the stack after it (Io/Threaded.zig spawnPosix,
+ // posixExecv), so a child forked from this thread cannot wedge on a
+ // lock another thread held (the forkShell hazard).
+ var child = std.process.spawn(io, .{
+ .argv = &.{ "glslc", "-fshader-stage=fragment", "-o", "-", "-" },
+ .stdin = .pipe,
+ .stdout = .pipe,
+ .stderr = .pipe,
}) catch |err| return switch (err) {
error.FileNotFound => .missing,
else => .{ .failed = std.fmt.allocPrint(gpa, "glslc did not run ({t})", .{err}) catch return .none },
};
- const clean = switch (run.term) {
+ defer child.kill(io);
+ // glslc reads all of its input before it writes a byte, so writing it
+ // first cannot deadlock against a full output pipe.
+ {
+ var stdin = child.stdin.?;
+ child.stdin = null;
+ defer stdin.close(io);
+ var buf: [4096]u8 = undefined;
+ var writer = stdin.writer(io, &buf);
+ writer.interface.writeAll(prefix) catch {};
+ writer.interface.writeAll(body) catch {};
+ writer.interface.flush() catch {};
+ }
+ var streams: std.Io.File.MultiReader.Buffer(2) = undefined;
+ var reader: std.Io.File.MultiReader = undefined;
+ reader.init(gpa, io, streams.toStreams(), &.{ child.stdout.?, child.stderr.? });
+ defer reader.deinit();
+ while (reader.fill(64, .none)) |_| {
+ if (reader.reader(0).buffered().len > 16 << 20 or reader.reader(1).buffered().len > 64 << 10)
+ return .{ .failed = gpa.dupe(u8, "glslc said too much") catch return .none };
+ } else |err| switch (err) {
+ error.EndOfStream => {},
+ else => return .{ .failed = gpa.dupe(u8, "reading glslc failed") catch return .none },
+ }
+ const term = child.wait(io) catch return .{ .failed = gpa.dupe(u8, "glslc did not finish") catch return .none };
+ const clean = switch (term) {
.exited => |code| code == 0,
else => false,
};
- if (clean) {
- gpa.free(run.stderr);
- return .{ .spirv = run.stdout };
- }
- gpa.free(run.stdout);
- // glslc names the temporary source; the file is what a person knows.
- defer gpa.free(run.stderr);
- const named = std.mem.replaceOwned(u8, gpa, run.stderr, source, path) catch return .{ .failed = gpa.dupe(u8, "glslc failed") catch return .none };
+ if (clean) return .{ .spirv = reader.toOwnedSlice(0) catch return .none };
+ const said = reader.toOwnedSlice(1) catch return .none;
+ // glslc calls its input <stdin>; the file is what a person knows.
+ defer gpa.free(said);
+ const named = std.mem.replaceOwned(u8, gpa, said, "<stdin>", path) catch return .{ .failed = gpa.dupe(u8, "glslc failed") catch return .none };
return .{ .failed = named };
}
@@ -535,9 +552,9 @@ test "ghostty's test shaders compile behind the prefix, and its invalid one fail
if (std.mem.startsWith(u8, entry.name, "ghostty-")) break entry.name;
} else return error.SkipZigTest;
var buf: [512]u8 = undefined;
- for ([_][]const u8{ "crt", "focus", "invalid" }, 0..) |name, i| {
+ for ([_][]const u8{ "crt", "focus", "invalid" }) |name| {
const path = try std.fmt.bufPrint(&buf, "zig-pkg/{s}/src/renderer/shaders/test_shadertoy_{s}.glsl", .{ ghostty, name });
- const result = compileOne(gpa, io, path, i);
+ const result = compileOne(gpa, io, path);
switch (result) {
.missing => return error.SkipZigTest, // no glslc here
.spirv => |spirv| {
@@ -549,9 +566,9 @@ test "ghostty's test shaders compile behind the prefix, and its invalid one fail
.failed => |text| {
defer gpa.free(text);
try std.testing.expectEqualStrings("invalid", name);
- // Named by the file, not by the temporary source glslc read.
+ // Named by the file, not by glslc's <stdin>.
try std.testing.expect(std.mem.indexOf(u8, text, path) != null);
- try std.testing.expect(std.mem.indexOf(u8, text, "pardes-post-") == null);
+ try std.testing.expect(std.mem.indexOf(u8, text, "<stdin>") == null);
},
.none => return error.TestUnexpectedResult,
}