mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
renderer/metal: load custom shaders
This commit is contained in:
@ -599,6 +599,29 @@ keybind: Keybinds = .{},
|
|||||||
/// need KAM, you don't need it.
|
/// need KAM, you don't need it.
|
||||||
@"vt-kam-allowed": bool = false,
|
@"vt-kam-allowed": bool = false,
|
||||||
|
|
||||||
|
/// Custom shaders to run after the default shaders. This is a file path
|
||||||
|
/// to a GLSL-syntax shader for all platforms.
|
||||||
|
///
|
||||||
|
/// WARNING: Invalid shaders can cause Ghostty to become unusable such as by
|
||||||
|
/// causing the window to be completely black. If this happens, you can
|
||||||
|
/// unset this configuration to disable the shader.
|
||||||
|
///
|
||||||
|
/// The shader API is identical to the ShaderToy API: you specify a `mainImage`
|
||||||
|
/// function and the available uniforms match ShaderToy. The iChannel0 uniform
|
||||||
|
/// is a texture containing the rendered terminal screen.
|
||||||
|
///
|
||||||
|
/// If the shader fails to compile, the shader will be ignored. Any errors
|
||||||
|
/// related to shader compilation will not show up as configuration errors
|
||||||
|
/// and only show up in the log, since shader compilation happens after
|
||||||
|
/// configuration loading on the dedicated render thread. If your shader is
|
||||||
|
/// not working, another way to debug is to run the `ghostty
|
||||||
|
/// +custom-shader-compile` command which will compile the shader and show any
|
||||||
|
/// errors. For interactive development, use ShaderToy.com.
|
||||||
|
///
|
||||||
|
/// This can be repeated multiple times to load multiple shaders. The shaders
|
||||||
|
/// will be run in the order they are specified.
|
||||||
|
@"custom-shader": RepeatablePath = .{},
|
||||||
|
|
||||||
/// If anything other than false, fullscreen mode on macOS will not use the
|
/// If anything other than false, fullscreen mode on macOS will not use the
|
||||||
/// native fullscreen, but make the window fullscreen without animations and
|
/// native fullscreen, but make the window fullscreen without animations and
|
||||||
/// using a new space. It's faster than the native fullscreen mode since it
|
/// using a new space. It's faster than the native fullscreen mode since it
|
||||||
|
@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator;
|
|||||||
const build_config = @import("build_config.zig");
|
const build_config = @import("build_config.zig");
|
||||||
const options = @import("build_options");
|
const options = @import("build_options");
|
||||||
const glfw = @import("glfw");
|
const glfw = @import("glfw");
|
||||||
|
const glslang = @import("glslang");
|
||||||
const macos = @import("macos");
|
const macos = @import("macos");
|
||||||
const tracy = @import("tracy");
|
const tracy = @import("tracy");
|
||||||
const cli = @import("cli.zig");
|
const cli = @import("cli.zig");
|
||||||
@ -267,6 +268,9 @@ pub const GlobalState = struct {
|
|||||||
// We need to make sure the process locale is set properly. Locale
|
// We need to make sure the process locale is set properly. Locale
|
||||||
// affects a lot of behaviors in a shell.
|
// affects a lot of behaviors in a shell.
|
||||||
try internal_os.ensureLocale(self.alloc);
|
try internal_os.ensureLocale(self.alloc);
|
||||||
|
|
||||||
|
// Initialize glslang for shader compilation
|
||||||
|
try glslang.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cleans up the global state. This doesn't _need_ to be called but
|
/// Cleans up the global state. This doesn't _need_ to be called but
|
||||||
|
@ -10,6 +10,7 @@ const glfw = @import("glfw");
|
|||||||
const objc = @import("objc");
|
const objc = @import("objc");
|
||||||
const macos = @import("macos");
|
const macos = @import("macos");
|
||||||
const imgui = @import("imgui");
|
const imgui = @import("imgui");
|
||||||
|
const glslang = @import("glslang");
|
||||||
const apprt = @import("../apprt.zig");
|
const apprt = @import("../apprt.zig");
|
||||||
const configpkg = @import("../config.zig");
|
const configpkg = @import("../config.zig");
|
||||||
const font = @import("../font/main.zig");
|
const font = @import("../font/main.zig");
|
||||||
@ -17,8 +18,10 @@ const terminal = @import("../terminal/main.zig");
|
|||||||
const renderer = @import("../renderer.zig");
|
const renderer = @import("../renderer.zig");
|
||||||
const math = @import("../math.zig");
|
const math = @import("../math.zig");
|
||||||
const Surface = @import("../Surface.zig");
|
const Surface = @import("../Surface.zig");
|
||||||
|
const shadertoy = @import("shadertoy.zig");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
const Terminal = terminal.Terminal;
|
const Terminal = terminal.Terminal;
|
||||||
|
|
||||||
const mtl = @import("metal/api.zig");
|
const mtl = @import("metal/api.zig");
|
||||||
@ -116,8 +119,10 @@ texture_color: objc.Object, // MTLTexture
|
|||||||
/// configuration. This must be exported so that we don't need to
|
/// configuration. This must be exported so that we don't need to
|
||||||
/// pass around Config pointers which makes memory management a pain.
|
/// pass around Config pointers which makes memory management a pain.
|
||||||
pub const DerivedConfig = struct {
|
pub const DerivedConfig = struct {
|
||||||
|
arena: ArenaAllocator,
|
||||||
|
|
||||||
font_thicken: bool,
|
font_thicken: bool,
|
||||||
font_features: std.ArrayList([]const u8),
|
font_features: std.ArrayListUnmanaged([]const u8),
|
||||||
font_styles: font.Group.StyleStatus,
|
font_styles: font.Group.StyleStatus,
|
||||||
cursor_color: ?terminal.color.RGB,
|
cursor_color: ?terminal.color.RGB,
|
||||||
cursor_opacity: f64,
|
cursor_opacity: f64,
|
||||||
@ -128,17 +133,21 @@ pub const DerivedConfig = struct {
|
|||||||
selection_background: ?terminal.color.RGB,
|
selection_background: ?terminal.color.RGB,
|
||||||
selection_foreground: ?terminal.color.RGB,
|
selection_foreground: ?terminal.color.RGB,
|
||||||
invert_selection_fg_bg: bool,
|
invert_selection_fg_bg: bool,
|
||||||
|
custom_shaders: std.ArrayListUnmanaged([]const u8),
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
alloc_gpa: Allocator,
|
alloc_gpa: Allocator,
|
||||||
config: *const configpkg.Config,
|
config: *const configpkg.Config,
|
||||||
) !DerivedConfig {
|
) !DerivedConfig {
|
||||||
|
var arena = ArenaAllocator.init(alloc_gpa);
|
||||||
|
errdefer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
// Copy our shaders
|
||||||
|
const custom_shaders = try config.@"custom-shader".value.list.clone(alloc);
|
||||||
|
|
||||||
// Copy our font features
|
// Copy our font features
|
||||||
var font_features = features: {
|
const font_features = try config.@"font-feature".list.clone(alloc);
|
||||||
var clone = try config.@"font-feature".list.clone(alloc_gpa);
|
|
||||||
break :features clone.toManaged(alloc_gpa);
|
|
||||||
};
|
|
||||||
errdefer font_features.deinit();
|
|
||||||
|
|
||||||
// Get our font styles
|
// Get our font styles
|
||||||
var font_styles = font.Group.StyleStatus.initFill(true);
|
var font_styles = font.Group.StyleStatus.initFill(true);
|
||||||
@ -177,11 +186,15 @@ pub const DerivedConfig = struct {
|
|||||||
bg.toTerminalRGB()
|
bg.toTerminalRGB()
|
||||||
else
|
else
|
||||||
null,
|
null,
|
||||||
|
|
||||||
|
.custom_shaders = custom_shaders,
|
||||||
|
|
||||||
|
.arena = arena,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *DerivedConfig) void {
|
pub fn deinit(self: *DerivedConfig) void {
|
||||||
self.font_features.deinit();
|
self.arena.deinit();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -203,6 +216,10 @@ pub fn surfaceInit(surface: *apprt.Surface) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
||||||
|
var arena = ArenaAllocator.init(alloc);
|
||||||
|
defer arena.deinit();
|
||||||
|
const arena_alloc = arena.allocator();
|
||||||
|
|
||||||
// Initialize our metal stuff
|
// Initialize our metal stuff
|
||||||
const device = objc.Object.fromId(mtl.MTLCreateSystemDefaultDevice());
|
const device = objc.Object.fromId(mtl.MTLCreateSystemDefaultDevice());
|
||||||
const queue = device.msgSend(objc.Object, objc.sel("newCommandQueue"), .{});
|
const queue = device.msgSend(objc.Object, objc.sel("newCommandQueue"), .{});
|
||||||
@ -256,6 +273,17 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
});
|
});
|
||||||
errdefer buf_instance.deinit();
|
errdefer buf_instance.deinit();
|
||||||
|
|
||||||
|
// Load our custom shaders
|
||||||
|
const custom_shaders: []const [:0]const u8 = shadertoy.loadFromFiles(
|
||||||
|
arena_alloc,
|
||||||
|
options.config.custom_shaders.items,
|
||||||
|
.msl,
|
||||||
|
) catch |err| err: {
|
||||||
|
log.warn("error loading custom shaders err={}", .{err});
|
||||||
|
break :err &.{};
|
||||||
|
};
|
||||||
|
_ = custom_shaders;
|
||||||
|
|
||||||
// Initialize our shaders
|
// Initialize our shaders
|
||||||
var shaders = try Shaders.init(alloc, device, &.{
|
var shaders = try Shaders.init(alloc, device, &.{
|
||||||
@embedFile("shaders/temp3.metal"),
|
@embedFile("shaders/temp3.metal"),
|
||||||
|
@ -2,9 +2,81 @@ const std = @import("std");
|
|||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
const glslang = @import("glslang");
|
const glslang = @import("glslang");
|
||||||
const spvcross = @import("spirv_cross");
|
const spvcross = @import("spirv_cross");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.shadertoy);
|
||||||
|
|
||||||
|
/// The target to load shaders for.
|
||||||
|
pub const Target = enum { msl };
|
||||||
|
|
||||||
|
/// Load a set of shaders from files and convert them to the target
|
||||||
|
/// format. The shader order is preserved.
|
||||||
|
pub fn loadFromFiles(
|
||||||
|
alloc_gpa: Allocator,
|
||||||
|
paths: []const []const u8,
|
||||||
|
target: Target,
|
||||||
|
) ![]const [:0]const u8 {
|
||||||
|
var list = std.ArrayList([:0]const u8).init(alloc_gpa);
|
||||||
|
defer list.deinit();
|
||||||
|
errdefer for (list.items) |shader| alloc_gpa.free(shader);
|
||||||
|
|
||||||
|
for (paths) |path| {
|
||||||
|
const shader = try loadFromFile(alloc_gpa, path, target);
|
||||||
|
log.info("loaded custom shader path={s}", .{path});
|
||||||
|
try list.append(shader);
|
||||||
|
}
|
||||||
|
|
||||||
|
return try list.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a single shader from a file and convert it to the target language
|
||||||
|
/// ready to be used with renderers.
|
||||||
|
pub fn loadFromFile(
|
||||||
|
alloc_gpa: Allocator,
|
||||||
|
path: []const u8,
|
||||||
|
target: Target,
|
||||||
|
) ![:0]const u8 {
|
||||||
|
var arena = ArenaAllocator.init(alloc_gpa);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
// Load the shader fiel
|
||||||
|
const cwd = std.fs.cwd();
|
||||||
|
const file = try cwd.openFile(path, .{});
|
||||||
|
defer file.close();
|
||||||
|
|
||||||
|
// Read it all into memory -- we don't expect shaders to be large.
|
||||||
|
var buf_reader = std.io.bufferedReader(file.reader());
|
||||||
|
const src = try buf_reader.reader().readAllAlloc(
|
||||||
|
alloc,
|
||||||
|
4 * 1024 * 1024, // 4MB
|
||||||
|
);
|
||||||
|
|
||||||
|
// Convert to full GLSL
|
||||||
|
const glsl: [:0]const u8 = glsl: {
|
||||||
|
var list = std.ArrayList(u8).init(alloc);
|
||||||
|
try glslFromShader(list.writer(), src);
|
||||||
|
try list.append(0);
|
||||||
|
break :glsl list.items[0 .. list.items.len - 1 :0];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert to SPIR-V
|
||||||
|
const spirv: []const u8 = spirv: {
|
||||||
|
var list = std.ArrayList(u8).init(alloc);
|
||||||
|
try spirvFromGlsl(list.writer(), null, glsl);
|
||||||
|
break :spirv list.items;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert to MSL
|
||||||
|
return switch (target) {
|
||||||
|
// Important: using the alloc_gpa here on purpose because this
|
||||||
|
// is the final result that will be returned to the caller.
|
||||||
|
.msl => try mslFromSpv(alloc_gpa, spirv),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert a ShaderToy shader into valid GLSL.
|
/// Convert a ShaderToy shader into valid GLSL.
|
||||||
///
|
///
|
||||||
/// ShaderToy shaders aren't full shaders, they're just implementing a
|
/// ShaderToy shaders aren't full shaders, they're just implementing a
|
||||||
|
Reference in New Issue
Block a user