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.
|
||||
@"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
|
||||
/// native fullscreen, but make the window fullscreen without animations and
|
||||
/// 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 options = @import("build_options");
|
||||
const glfw = @import("glfw");
|
||||
const glslang = @import("glslang");
|
||||
const macos = @import("macos");
|
||||
const tracy = @import("tracy");
|
||||
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
|
||||
// affects a lot of behaviors in a shell.
|
||||
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
|
||||
|
@ -10,6 +10,7 @@ const glfw = @import("glfw");
|
||||
const objc = @import("objc");
|
||||
const macos = @import("macos");
|
||||
const imgui = @import("imgui");
|
||||
const glslang = @import("glslang");
|
||||
const apprt = @import("../apprt.zig");
|
||||
const configpkg = @import("../config.zig");
|
||||
const font = @import("../font/main.zig");
|
||||
@ -17,8 +18,10 @@ const terminal = @import("../terminal/main.zig");
|
||||
const renderer = @import("../renderer.zig");
|
||||
const math = @import("../math.zig");
|
||||
const Surface = @import("../Surface.zig");
|
||||
const shadertoy = @import("shadertoy.zig");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const Terminal = terminal.Terminal;
|
||||
|
||||
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
|
||||
/// pass around Config pointers which makes memory management a pain.
|
||||
pub const DerivedConfig = struct {
|
||||
arena: ArenaAllocator,
|
||||
|
||||
font_thicken: bool,
|
||||
font_features: std.ArrayList([]const u8),
|
||||
font_features: std.ArrayListUnmanaged([]const u8),
|
||||
font_styles: font.Group.StyleStatus,
|
||||
cursor_color: ?terminal.color.RGB,
|
||||
cursor_opacity: f64,
|
||||
@ -128,17 +133,21 @@ pub const DerivedConfig = struct {
|
||||
selection_background: ?terminal.color.RGB,
|
||||
selection_foreground: ?terminal.color.RGB,
|
||||
invert_selection_fg_bg: bool,
|
||||
custom_shaders: std.ArrayListUnmanaged([]const u8),
|
||||
|
||||
pub fn init(
|
||||
alloc_gpa: Allocator,
|
||||
config: *const configpkg.Config,
|
||||
) !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
|
||||
var font_features = features: {
|
||||
var clone = try config.@"font-feature".list.clone(alloc_gpa);
|
||||
break :features clone.toManaged(alloc_gpa);
|
||||
};
|
||||
errdefer font_features.deinit();
|
||||
const font_features = try config.@"font-feature".list.clone(alloc);
|
||||
|
||||
// Get our font styles
|
||||
var font_styles = font.Group.StyleStatus.initFill(true);
|
||||
@ -177,11 +186,15 @@ pub const DerivedConfig = struct {
|
||||
bg.toTerminalRGB()
|
||||
else
|
||||
null,
|
||||
|
||||
.custom_shaders = custom_shaders,
|
||||
|
||||
.arena = arena,
|
||||
};
|
||||
}
|
||||
|
||||
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 {
|
||||
var arena = ArenaAllocator.init(alloc);
|
||||
defer arena.deinit();
|
||||
const arena_alloc = arena.allocator();
|
||||
|
||||
// Initialize our metal stuff
|
||||
const device = objc.Object.fromId(mtl.MTLCreateSystemDefaultDevice());
|
||||
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();
|
||||
|
||||
// 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
|
||||
var shaders = try Shaders.init(alloc, device, &.{
|
||||
@embedFile("shaders/temp3.metal"),
|
||||
|
@ -2,9 +2,81 @@ const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const glslang = @import("glslang");
|
||||
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.
|
||||
///
|
||||
/// ShaderToy shaders aren't full shaders, they're just implementing a
|
||||
|
Reference in New Issue
Block a user