mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
Merge pull request #903 from mitchellh/macos-update
Custom Shaders (Metal and OpenGL)
This commit is contained in:
@ -10,3 +10,6 @@ macos/
|
|||||||
|
|
||||||
# website dev run
|
# website dev run
|
||||||
website/.next
|
website/.next
|
||||||
|
|
||||||
|
# shaders
|
||||||
|
*.frag
|
||||||
|
20
build.zig
20
build.zig
@ -643,6 +643,14 @@ fn addDeps(
|
|||||||
.optimize = step.optimize,
|
.optimize = step.optimize,
|
||||||
.@"enable-libpng" = true,
|
.@"enable-libpng" = true,
|
||||||
});
|
});
|
||||||
|
const glslang_dep = b.dependency("glslang", .{
|
||||||
|
.target = step.target,
|
||||||
|
.optimize = step.optimize,
|
||||||
|
});
|
||||||
|
const spirv_cross_dep = b.dependency("spirv_cross", .{
|
||||||
|
.target = step.target,
|
||||||
|
.optimize = step.optimize,
|
||||||
|
});
|
||||||
const mach_glfw_dep = b.dependency("mach_glfw", .{
|
const mach_glfw_dep = b.dependency("mach_glfw", .{
|
||||||
.target = step.target,
|
.target = step.target,
|
||||||
.optimize = step.optimize,
|
.optimize = step.optimize,
|
||||||
@ -655,6 +663,7 @@ fn addDeps(
|
|||||||
.target = step.target,
|
.target = step.target,
|
||||||
.optimize = step.optimize,
|
.optimize = step.optimize,
|
||||||
});
|
});
|
||||||
|
const opengl_dep = b.dependency("opengl", .{});
|
||||||
const pixman_dep = b.dependency("pixman", .{
|
const pixman_dep = b.dependency("pixman", .{
|
||||||
.target = step.target,
|
.target = step.target,
|
||||||
.optimize = step.optimize,
|
.optimize = step.optimize,
|
||||||
@ -718,8 +727,11 @@ fn addDeps(
|
|||||||
fontconfig_dep.module("fontconfig"),
|
fontconfig_dep.module("fontconfig"),
|
||||||
);
|
);
|
||||||
step.addModule("freetype", freetype_dep.module("freetype"));
|
step.addModule("freetype", freetype_dep.module("freetype"));
|
||||||
|
step.addModule("glslang", glslang_dep.module("glslang"));
|
||||||
|
step.addModule("spirv_cross", spirv_cross_dep.module("spirv_cross"));
|
||||||
step.addModule("harfbuzz", harfbuzz_dep.module("harfbuzz"));
|
step.addModule("harfbuzz", harfbuzz_dep.module("harfbuzz"));
|
||||||
step.addModule("xev", libxev_dep.module("xev"));
|
step.addModule("xev", libxev_dep.module("xev"));
|
||||||
|
step.addModule("opengl", opengl_dep.module("opengl"));
|
||||||
step.addModule("pixman", pixman_dep.module("pixman"));
|
step.addModule("pixman", pixman_dep.module("pixman"));
|
||||||
step.addModule("ziglyph", ziglyph_dep.module("ziglyph"));
|
step.addModule("ziglyph", ziglyph_dep.module("ziglyph"));
|
||||||
|
|
||||||
@ -743,6 +755,14 @@ fn addDeps(
|
|||||||
try static_libs.append(tracy_dep.artifact("tracy").getEmittedBin());
|
try static_libs.append(tracy_dep.artifact("tracy").getEmittedBin());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Glslang
|
||||||
|
step.linkLibrary(glslang_dep.artifact("glslang"));
|
||||||
|
try static_libs.append(glslang_dep.artifact("glslang").getEmittedBin());
|
||||||
|
|
||||||
|
// Spirv-Cross
|
||||||
|
step.linkLibrary(spirv_cross_dep.artifact("spirv_cross"));
|
||||||
|
try static_libs.append(spirv_cross_dep.artifact("spirv_cross").getEmittedBin());
|
||||||
|
|
||||||
// Dynamic link
|
// Dynamic link
|
||||||
if (!static) {
|
if (!static) {
|
||||||
step.addIncludePath(freetype_dep.path(""));
|
step.addIncludePath(freetype_dep.path(""));
|
||||||
|
@ -13,8 +13,8 @@
|
|||||||
.hash = "12202da6b8e9024c653f5d67f55a8065b401c42b3c08b69333d95400fe85d6019a59",
|
.hash = "12202da6b8e9024c653f5d67f55a8065b401c42b3c08b69333d95400fe85d6019a59",
|
||||||
},
|
},
|
||||||
.zig_objc = .{
|
.zig_objc = .{
|
||||||
.url = "https://github.com/mitchellh/zig-objc/archive/146a50bb018d8e1ac5b9a1454d9db9a5eba5361f.tar.gz",
|
.url = "https://github.com/mitchellh/zig-objc/archive/a38331cb6ee366b3f22d0068297810ef14c0c400.tar.gz",
|
||||||
.hash = "12209f62dae4fccae478f5bd5670725c55308d8d985506110ba122ee2fb5e73122e0",
|
.hash = "1220dcb34ec79a9b02c46372a41a446212f2366e7c69c8eba68e88f0f25b5ddf475d",
|
||||||
},
|
},
|
||||||
.zig_js = .{
|
.zig_js = .{
|
||||||
.url = "https://github.com/mitchellh/zig-js/archive/60ac42ab137461cdba2b38cc6c5e16376470aae6.tar.gz",
|
.url = "https://github.com/mitchellh/zig-js/archive/60ac42ab137461cdba2b38cc6c5e16376470aae6.tar.gz",
|
||||||
@ -32,10 +32,15 @@
|
|||||||
.harfbuzz = .{ .path = "./pkg/harfbuzz" },
|
.harfbuzz = .{ .path = "./pkg/harfbuzz" },
|
||||||
.libpng = .{ .path = "./pkg/libpng" },
|
.libpng = .{ .path = "./pkg/libpng" },
|
||||||
.macos = .{ .path = "./pkg/macos" },
|
.macos = .{ .path = "./pkg/macos" },
|
||||||
|
.opengl = .{ .path = "./pkg/opengl" },
|
||||||
.pixman = .{ .path = "./pkg/pixman" },
|
.pixman = .{ .path = "./pkg/pixman" },
|
||||||
.tracy = .{ .path = "./pkg/tracy" },
|
.tracy = .{ .path = "./pkg/tracy" },
|
||||||
.zlib = .{ .path = "./pkg/zlib" },
|
.zlib = .{ .path = "./pkg/zlib" },
|
||||||
|
|
||||||
|
// Shader translation
|
||||||
|
.glslang = .{ .path = "./pkg/glslang" },
|
||||||
|
.spirv_cross = .{ .path = "./pkg/spirv-cross" },
|
||||||
|
|
||||||
// System headers
|
// System headers
|
||||||
.apple_sdk = .{ .path = "./pkg/apple-sdk" },
|
.apple_sdk = .{ .path = "./pkg/apple-sdk" },
|
||||||
},
|
},
|
||||||
|
137
pkg/glslang/build.zig
Normal file
137
pkg/glslang/build.zig
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) !void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
_ = b.addModule("glslang", .{ .source_file = .{ .path = "main.zig" } });
|
||||||
|
|
||||||
|
const upstream = b.dependency("glslang", .{});
|
||||||
|
const lib = try buildGlslang(b, upstream, target, optimize);
|
||||||
|
b.installArtifact(lib);
|
||||||
|
|
||||||
|
{
|
||||||
|
const test_exe = b.addTest(.{
|
||||||
|
.name = "test",
|
||||||
|
.root_source_file = .{ .path = "main.zig" },
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
test_exe.linkLibrary(lib);
|
||||||
|
const tests_run = b.addRunArtifact(test_exe);
|
||||||
|
const test_step = b.step("test", "Run tests");
|
||||||
|
test_step.dependOn(&tests_run.step);
|
||||||
|
|
||||||
|
// Uncomment this if we're debugging tests
|
||||||
|
// b.installArtifact(test_exe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buildGlslang(
|
||||||
|
b: *std.Build,
|
||||||
|
upstream: *std.Build.Dependency,
|
||||||
|
target: std.zig.CrossTarget,
|
||||||
|
optimize: std.builtin.OptimizeMode,
|
||||||
|
) !*std.Build.Step.Compile {
|
||||||
|
const lib = b.addStaticLibrary(.{
|
||||||
|
.name = "glslang",
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
lib.linkLibC();
|
||||||
|
lib.linkLibCpp();
|
||||||
|
lib.addIncludePath(upstream.path(""));
|
||||||
|
lib.addIncludePath(.{ .path = "override" });
|
||||||
|
|
||||||
|
var flags = std.ArrayList([]const u8).init(b.allocator);
|
||||||
|
defer flags.deinit();
|
||||||
|
try flags.appendSlice(&.{
|
||||||
|
"-fno-sanitize=undefined",
|
||||||
|
"-fno-sanitize-trap=undefined",
|
||||||
|
});
|
||||||
|
|
||||||
|
lib.addCSourceFiles(.{
|
||||||
|
.dependency = upstream,
|
||||||
|
.flags = flags.items,
|
||||||
|
.files = &.{
|
||||||
|
// GenericCodeGen
|
||||||
|
"glslang/GenericCodeGen/CodeGen.cpp",
|
||||||
|
"glslang/GenericCodeGen/Link.cpp",
|
||||||
|
|
||||||
|
// MachineIndependent
|
||||||
|
//"MachineIndependent/glslang.y",
|
||||||
|
"glslang/MachineIndependent/glslang_tab.cpp",
|
||||||
|
"glslang/MachineIndependent/attribute.cpp",
|
||||||
|
"glslang/MachineIndependent/Constant.cpp",
|
||||||
|
"glslang/MachineIndependent/iomapper.cpp",
|
||||||
|
"glslang/MachineIndependent/InfoSink.cpp",
|
||||||
|
"glslang/MachineIndependent/Initialize.cpp",
|
||||||
|
"glslang/MachineIndependent/IntermTraverse.cpp",
|
||||||
|
"glslang/MachineIndependent/Intermediate.cpp",
|
||||||
|
"glslang/MachineIndependent/ParseContextBase.cpp",
|
||||||
|
"glslang/MachineIndependent/ParseHelper.cpp",
|
||||||
|
"glslang/MachineIndependent/PoolAlloc.cpp",
|
||||||
|
"glslang/MachineIndependent/RemoveTree.cpp",
|
||||||
|
"glslang/MachineIndependent/Scan.cpp",
|
||||||
|
"glslang/MachineIndependent/ShaderLang.cpp",
|
||||||
|
"glslang/MachineIndependent/SpirvIntrinsics.cpp",
|
||||||
|
"glslang/MachineIndependent/SymbolTable.cpp",
|
||||||
|
"glslang/MachineIndependent/Versions.cpp",
|
||||||
|
"glslang/MachineIndependent/intermOut.cpp",
|
||||||
|
"glslang/MachineIndependent/limits.cpp",
|
||||||
|
"glslang/MachineIndependent/linkValidate.cpp",
|
||||||
|
"glslang/MachineIndependent/parseConst.cpp",
|
||||||
|
"glslang/MachineIndependent/reflection.cpp",
|
||||||
|
"glslang/MachineIndependent/preprocessor/Pp.cpp",
|
||||||
|
"glslang/MachineIndependent/preprocessor/PpAtom.cpp",
|
||||||
|
"glslang/MachineIndependent/preprocessor/PpContext.cpp",
|
||||||
|
"glslang/MachineIndependent/preprocessor/PpScanner.cpp",
|
||||||
|
"glslang/MachineIndependent/preprocessor/PpTokens.cpp",
|
||||||
|
"glslang/MachineIndependent/propagateNoContraction.cpp",
|
||||||
|
|
||||||
|
// C Interface
|
||||||
|
"glslang/CInterface/glslang_c_interface.cpp",
|
||||||
|
|
||||||
|
// ResourceLimits
|
||||||
|
"glslang/ResourceLimits/ResourceLimits.cpp",
|
||||||
|
"glslang/ResourceLimits/resource_limits_c.cpp",
|
||||||
|
|
||||||
|
// SPIRV
|
||||||
|
"SPIRV/GlslangToSpv.cpp",
|
||||||
|
"SPIRV/InReadableOrder.cpp",
|
||||||
|
"SPIRV/Logger.cpp",
|
||||||
|
"SPIRV/SpvBuilder.cpp",
|
||||||
|
"SPIRV/SpvPostProcess.cpp",
|
||||||
|
"SPIRV/doc.cpp",
|
||||||
|
"SPIRV/disassemble.cpp",
|
||||||
|
"SPIRV/CInterface/spirv_c_interface.cpp",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!target.isWindows()) {
|
||||||
|
lib.addCSourceFiles(.{
|
||||||
|
.dependency = upstream,
|
||||||
|
.flags = flags.items,
|
||||||
|
.files = &.{
|
||||||
|
"glslang/OSDependent/Unix/ossource.cpp",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
lib.addCSourceFiles(.{
|
||||||
|
.dependency = upstream,
|
||||||
|
.flags = flags.items,
|
||||||
|
.files = &.{
|
||||||
|
"glslang/OSDependent/Windows/ossource.cpp",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
lib.installHeadersDirectoryOptions(.{
|
||||||
|
.source_dir = upstream.path(""),
|
||||||
|
.install_dir = .header,
|
||||||
|
.install_subdir = "",
|
||||||
|
.include_extensions = &.{".h"},
|
||||||
|
});
|
||||||
|
|
||||||
|
return lib;
|
||||||
|
}
|
11
pkg/glslang/build.zig.zon
Normal file
11
pkg/glslang/build.zig.zon
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
.{
|
||||||
|
.name = "glslang",
|
||||||
|
.version = "13.1.1",
|
||||||
|
.paths = .{""},
|
||||||
|
.dependencies = .{
|
||||||
|
.glslang = .{
|
||||||
|
.url = "https://github.com/KhronosGroup/glslang/archive/refs/tags/13.1.1.tar.gz",
|
||||||
|
.hash = "1220481fe19def1172cd0728743019c0f440181a6342b62d03e24d05c70141516799",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
4
pkg/glslang/c.zig
Normal file
4
pkg/glslang/c.zig
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pub usingnamespace @cImport({
|
||||||
|
@cInclude("glslang/Include/glslang_c_interface.h");
|
||||||
|
@cInclude("glslang/Public/resource_limits_c.h");
|
||||||
|
});
|
9
pkg/glslang/init.zig
Normal file
9
pkg/glslang/init.zig
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
const c = @import("c.zig");
|
||||||
|
|
||||||
|
pub fn init() !void {
|
||||||
|
if (c.glslang_initialize_process() == 0) return error.GlslangInitFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finalize() void {
|
||||||
|
c.glslang_finalize_process();
|
||||||
|
}
|
9
pkg/glslang/main.zig
Normal file
9
pkg/glslang/main.zig
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
pub const c = @import("c.zig");
|
||||||
|
pub const testing = @import("test.zig");
|
||||||
|
pub usingnamespace @import("init.zig");
|
||||||
|
pub usingnamespace @import("program.zig");
|
||||||
|
pub usingnamespace @import("shader.zig");
|
||||||
|
|
||||||
|
test {
|
||||||
|
@import("std").testing.refAllDecls(@This());
|
||||||
|
}
|
62
pkg/glslang/override/glslang/build_info.h
Normal file
62
pkg/glslang/override/glslang/build_info.h
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// Copyright (C) 2020 The Khronos Group Inc.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions
|
||||||
|
// are met:
|
||||||
|
//
|
||||||
|
// Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
//
|
||||||
|
// Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following
|
||||||
|
// disclaimer in the documentation and/or other materials provided
|
||||||
|
// with the distribution.
|
||||||
|
//
|
||||||
|
// Neither the name of The Khronos Group Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived
|
||||||
|
// from this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
// COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||||
|
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||||
|
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
#ifndef GLSLANG_BUILD_INFO
|
||||||
|
#define GLSLANG_BUILD_INFO
|
||||||
|
|
||||||
|
#define GLSLANG_VERSION_MAJOR 13
|
||||||
|
#define GLSLANG_VERSION_MINOR 1
|
||||||
|
#define GLSLANG_VERSION_PATCH 1
|
||||||
|
#define GLSLANG_VERSION_FLAVOR ""
|
||||||
|
|
||||||
|
#define GLSLANG_VERSION_GREATER_THAN(major, minor, patch) \
|
||||||
|
((GLSLANG_VERSION_MAJOR) > (major) || ((major) == GLSLANG_VERSION_MAJOR && \
|
||||||
|
((GLSLANG_VERSION_MINOR) > (minor) || ((minor) == GLSLANG_VERSION_MINOR && \
|
||||||
|
(GLSLANG_VERSION_PATCH) > (patch)))))
|
||||||
|
|
||||||
|
#define GLSLANG_VERSION_GREATER_OR_EQUAL_TO(major, minor, patch) \
|
||||||
|
((GLSLANG_VERSION_MAJOR) > (major) || ((major) == GLSLANG_VERSION_MAJOR && \
|
||||||
|
((GLSLANG_VERSION_MINOR) > (minor) || ((minor) == GLSLANG_VERSION_MINOR && \
|
||||||
|
(GLSLANG_VERSION_PATCH >= (patch))))))
|
||||||
|
|
||||||
|
#define GLSLANG_VERSION_LESS_THAN(major, minor, patch) \
|
||||||
|
((GLSLANG_VERSION_MAJOR) < (major) || ((major) == GLSLANG_VERSION_MAJOR && \
|
||||||
|
((GLSLANG_VERSION_MINOR) < (minor) || ((minor) == GLSLANG_VERSION_MINOR && \
|
||||||
|
(GLSLANG_VERSION_PATCH) < (patch)))))
|
||||||
|
|
||||||
|
#define GLSLANG_VERSION_LESS_OR_EQUAL_TO(major, minor, patch) \
|
||||||
|
((GLSLANG_VERSION_MAJOR) < (major) || ((major) == GLSLANG_VERSION_MAJOR && \
|
||||||
|
((GLSLANG_VERSION_MINOR) < (minor) || ((minor) == GLSLANG_VERSION_MINOR && \
|
||||||
|
(GLSLANG_VERSION_PATCH <= (patch))))))
|
||||||
|
|
||||||
|
#endif // GLSLANG_BUILD_INFO
|
60
pkg/glslang/program.zig
Normal file
60
pkg/glslang/program.zig
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const c = @import("c.zig");
|
||||||
|
const testlib = @import("test.zig");
|
||||||
|
const Shader = @import("shader.zig").Shader;
|
||||||
|
|
||||||
|
pub const Program = opaque {
|
||||||
|
pub fn create() !*Program {
|
||||||
|
if (c.glslang_program_create()) |ptr| return @ptrCast(ptr);
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(self: *Program) void {
|
||||||
|
c.glslang_program_delete(@ptrCast(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addShader(self: *Program, shader: *Shader) void {
|
||||||
|
c.glslang_program_add_shader(@ptrCast(self), @ptrCast(shader));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn link(self: *Program, messages: c_int) !void {
|
||||||
|
if (c.glslang_program_link(@ptrCast(self), messages) != 0) return;
|
||||||
|
return error.GlslangFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spirvGenerate(self: *Program, stage: c.glslang_stage_t) void {
|
||||||
|
c.glslang_program_SPIRV_generate(@ptrCast(self), stage);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spirvGetSize(self: *Program) usize {
|
||||||
|
return @intCast(c.glslang_program_SPIRV_get_size(@ptrCast(self)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spirvGet(self: *Program, buf: []u32) void {
|
||||||
|
c.glslang_program_SPIRV_get(@ptrCast(self), buf.ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spirvGetPtr(self: *Program) ![*]u32 {
|
||||||
|
return @ptrCast(c.glslang_program_SPIRV_get_ptr(@ptrCast(self)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spirvGetMessages(self: *Program) ![:0]const u8 {
|
||||||
|
const ptr = c.glslang_program_SPIRV_get_messages(@ptrCast(self));
|
||||||
|
return std.mem.sliceTo(ptr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getInfoLog(self: *Program) ![:0]const u8 {
|
||||||
|
const ptr = c.glslang_program_get_info_log(@ptrCast(self));
|
||||||
|
return std.mem.sliceTo(ptr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getDebugInfoLog(self: *Program) ![:0]const u8 {
|
||||||
|
const ptr = c.glslang_program_get_info_debug_log(@ptrCast(self));
|
||||||
|
return std.mem.sliceTo(ptr, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test {
|
||||||
|
var program = try Program.create();
|
||||||
|
defer program.delete();
|
||||||
|
}
|
58
pkg/glslang/shader.zig
Normal file
58
pkg/glslang/shader.zig
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const c = @import("c.zig");
|
||||||
|
const testlib = @import("test.zig");
|
||||||
|
|
||||||
|
pub const Shader = opaque {
|
||||||
|
pub fn create(input: *const c.glslang_input_t) !*Shader {
|
||||||
|
if (c.glslang_shader_create(input)) |ptr| return @ptrCast(ptr);
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(self: *Shader) void {
|
||||||
|
c.glslang_shader_delete(@ptrCast(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn preprocess(self: *Shader, input: *const c.glslang_input_t) !void {
|
||||||
|
if (c.glslang_shader_preprocess(@ptrCast(self), input) == 0)
|
||||||
|
return error.GlslangFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(self: *Shader, input: *const c.glslang_input_t) !void {
|
||||||
|
if (c.glslang_shader_parse(@ptrCast(self), input) == 0)
|
||||||
|
return error.GlslangFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getInfoLog(self: *Shader) ![:0]const u8 {
|
||||||
|
const ptr = c.glslang_shader_get_info_log(@ptrCast(self));
|
||||||
|
return std.mem.sliceTo(ptr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getDebugInfoLog(self: *Shader) ![:0]const u8 {
|
||||||
|
const ptr = c.glslang_shader_get_info_debug_log(@ptrCast(self));
|
||||||
|
return std.mem.sliceTo(ptr, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test {
|
||||||
|
const input: c.glslang_input_t = .{
|
||||||
|
.language = c.GLSLANG_SOURCE_GLSL,
|
||||||
|
.stage = c.GLSLANG_STAGE_FRAGMENT,
|
||||||
|
.client = c.GLSLANG_CLIENT_VULKAN,
|
||||||
|
.client_version = c.GLSLANG_TARGET_VULKAN_1_2,
|
||||||
|
.target_language = c.GLSLANG_TARGET_SPV,
|
||||||
|
.target_language_version = c.GLSLANG_TARGET_SPV_1_5,
|
||||||
|
.code = @embedFile("test/simple.frag"),
|
||||||
|
.default_version = 100,
|
||||||
|
.default_profile = c.GLSLANG_NO_PROFILE,
|
||||||
|
.force_default_version_and_profile = 0,
|
||||||
|
.forward_compatible = 0,
|
||||||
|
.messages = c.GLSLANG_MSG_DEFAULT_BIT,
|
||||||
|
.resource = c.glslang_default_resource(),
|
||||||
|
};
|
||||||
|
|
||||||
|
try testlib.ensureInit();
|
||||||
|
const shader = try Shader.create(&input);
|
||||||
|
defer shader.delete();
|
||||||
|
try shader.preprocess(&input);
|
||||||
|
try shader.parse(&input);
|
||||||
|
}
|
10
pkg/glslang/test.zig
Normal file
10
pkg/glslang/test.zig
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
const glslang = @import("main.zig");
|
||||||
|
|
||||||
|
var initialized: bool = false;
|
||||||
|
|
||||||
|
/// Call this function before any other tests in this package to ensure that
|
||||||
|
/// the glslang library is initialized.
|
||||||
|
pub fn ensureInit() !void {
|
||||||
|
if (initialized) return;
|
||||||
|
try glslang.init();
|
||||||
|
}
|
56
pkg/glslang/test/simple.frag
Normal file
56
pkg/glslang/test/simple.frag
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#version 430 core
|
||||||
|
|
||||||
|
layout(binding = 0) uniform Globals {
|
||||||
|
uniform vec3 iResolution;
|
||||||
|
uniform float iTime;
|
||||||
|
uniform float iTimeDelta;
|
||||||
|
uniform float iFrameRate;
|
||||||
|
uniform int iFrame;
|
||||||
|
uniform float iChannelTime[4];
|
||||||
|
uniform vec3 iChannelResolution[4];
|
||||||
|
uniform vec4 iMouse;
|
||||||
|
uniform vec4 iDate;
|
||||||
|
uniform float iSampleRate;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout(binding = 0) uniform sampler2D iChannel0;
|
||||||
|
layout(binding = 1) uniform sampler2D iChannel1;
|
||||||
|
layout(binding = 2) uniform sampler2D iChannel2;
|
||||||
|
layout(binding = 3) uniform sampler2D iChannel3;
|
||||||
|
|
||||||
|
layout(location = 0) in vec4 gl_FragCoord;
|
||||||
|
layout(location = 0) out vec4 _fragColor;
|
||||||
|
|
||||||
|
#define texture2D texture
|
||||||
|
|
||||||
|
void mainImage( out vec4 fragColor, in vec2 fragCoord );
|
||||||
|
void main() { mainImage (_fragColor, gl_FragCoord.xy); }
|
||||||
|
|
||||||
|
#define t iTime
|
||||||
|
|
||||||
|
void mainImage( out vec4 fragColor, in vec2 fragCoord )
|
||||||
|
{
|
||||||
|
// Normalized pixel coordinates (from 0 to 1)
|
||||||
|
vec2 uv = ( fragCoord - .5*iResolution.xy) / iResolution.y;
|
||||||
|
vec3 col = vec3(0.);
|
||||||
|
float a = atan(uv.y,uv.x);
|
||||||
|
float r = 0.5*length(uv);
|
||||||
|
float counter = 100.;
|
||||||
|
a = 4.*a+20.*r+50.*cos(r)*cos(.1*t)+abs(a*r);
|
||||||
|
float f = 0.02*abs(cos(a))/(r*r);
|
||||||
|
|
||||||
|
|
||||||
|
vec2 v = vec2(0.);
|
||||||
|
for(float i=0.;i<counter;i++){
|
||||||
|
v = mat2(v,-v.y,v.x) * v + vec2(2.*f+cos(0.5*t*(exp(-0.2* r))),-cos(t*r*r)*cos(0.5*t));
|
||||||
|
if(length(v)>2.){
|
||||||
|
counter = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
col=vec3(min(0.9,1.2*exp(-pow(f,0.45)*counter)));
|
||||||
|
|
||||||
|
fragColor = min(0.9,1.2*exp(-pow(f,0.45)*counter) )
|
||||||
|
* ( 0.7 + 0.3* cos(10.*r - 2.*t -vec4(.7,1.4,2.1,0) ) );
|
||||||
|
}
|
@ -28,6 +28,7 @@ pub fn build(b: *std.Build) !void {
|
|||||||
lib.linkFramework("CoreFoundation");
|
lib.linkFramework("CoreFoundation");
|
||||||
lib.linkFramework("CoreGraphics");
|
lib.linkFramework("CoreGraphics");
|
||||||
lib.linkFramework("CoreText");
|
lib.linkFramework("CoreText");
|
||||||
|
lib.linkFramework("CoreVideo");
|
||||||
if (!target.isNative()) try apple_sdk.addPaths(b, lib);
|
if (!target.isNative()) try apple_sdk.addPaths(b, lib);
|
||||||
|
|
||||||
b.installArtifact(lib);
|
b.installArtifact(lib);
|
||||||
|
@ -2,6 +2,7 @@ pub const foundation = @import("foundation.zig");
|
|||||||
pub const graphics = @import("graphics.zig");
|
pub const graphics = @import("graphics.zig");
|
||||||
pub const os = @import("os.zig");
|
pub const os = @import("os.zig");
|
||||||
pub const text = @import("text.zig");
|
pub const text = @import("text.zig");
|
||||||
|
pub const video = @import("video.zig");
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@import("std").testing.refAllDecls(@This());
|
@import("std").testing.refAllDecls(@This());
|
||||||
|
6
pkg/macos/video.zig
Normal file
6
pkg/macos/video.zig
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
pub const c = @import("video/c.zig");
|
||||||
|
pub usingnamespace @import("video/display_link.zig");
|
||||||
|
|
||||||
|
test {
|
||||||
|
@import("std").testing.refAllDecls(@This());
|
||||||
|
}
|
3
pkg/macos/video/c.zig
Normal file
3
pkg/macos/video/c.zig
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub usingnamespace @cImport({
|
||||||
|
@cInclude("CoreVideo/CoreVideo.h");
|
||||||
|
});
|
71
pkg/macos/video/display_link.zig
Normal file
71
pkg/macos/video/display_link.zig
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const c = @import("c.zig");
|
||||||
|
|
||||||
|
pub const DisplayLink = opaque {
|
||||||
|
pub const Error = error{
|
||||||
|
InvalidOperation,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn createWithActiveCGDisplays() Allocator.Error!*DisplayLink {
|
||||||
|
var result: ?*DisplayLink = null;
|
||||||
|
if (c.CVDisplayLinkCreateWithActiveCGDisplays(
|
||||||
|
@ptrCast(&result),
|
||||||
|
) != c.kCVReturnSuccess)
|
||||||
|
return error.OutOfMemory;
|
||||||
|
|
||||||
|
return result orelse error.OutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(self: *DisplayLink) void {
|
||||||
|
c.CVDisplayLinkRelease(@ptrCast(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(self: *DisplayLink) Error!void {
|
||||||
|
if (c.CVDisplayLinkStart(@ptrCast(self)) != c.kCVReturnSuccess)
|
||||||
|
return error.InvalidOperation;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(self: *DisplayLink) Error!void {
|
||||||
|
if (c.CVDisplayLinkStop(@ptrCast(self)) != c.kCVReturnSuccess)
|
||||||
|
return error.InvalidOperation;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isRunning(self: *DisplayLink) bool {
|
||||||
|
return c.CVDisplayLinkIsRunning(@ptrCast(self)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: this purposely throws away a ton of arguments I didn't need.
|
||||||
|
// It would be trivial to refactor this into Zig types and properly
|
||||||
|
// pass this through.
|
||||||
|
pub fn setOutputCallback(
|
||||||
|
self: *DisplayLink,
|
||||||
|
comptime callbackFn: *const fn (*DisplayLink, ?*anyopaque) void,
|
||||||
|
userinfo: ?*anyopaque,
|
||||||
|
) Error!void {
|
||||||
|
if (c.CVDisplayLinkSetOutputCallback(
|
||||||
|
@ptrCast(self),
|
||||||
|
@ptrCast(&(struct {
|
||||||
|
fn callback(
|
||||||
|
displayLink: *DisplayLink,
|
||||||
|
inNow: *const c.CVTimeStamp,
|
||||||
|
inOutputTime: *const c.CVTimeStamp,
|
||||||
|
flagsIn: c.CVOptionFlags,
|
||||||
|
flagsOut: *c.CVOptionFlags,
|
||||||
|
inner_userinfo: ?*anyopaque,
|
||||||
|
) callconv(.C) c.CVReturn {
|
||||||
|
_ = inNow;
|
||||||
|
_ = inOutputTime;
|
||||||
|
_ = flagsIn;
|
||||||
|
_ = flagsOut;
|
||||||
|
|
||||||
|
callbackFn(displayLink, inner_userinfo);
|
||||||
|
return c.kCVReturnSuccess;
|
||||||
|
}
|
||||||
|
}).callback),
|
||||||
|
userinfo,
|
||||||
|
) != c.kCVReturnSuccess)
|
||||||
|
return error.InvalidOperation;
|
||||||
|
}
|
||||||
|
};
|
@ -7,76 +7,106 @@ const glad = @import("glad.zig");
|
|||||||
|
|
||||||
id: c.GLuint,
|
id: c.GLuint,
|
||||||
|
|
||||||
/// Enum for possible binding targets.
|
/// Create a single buffer.
|
||||||
pub const Target = enum(c_uint) {
|
pub fn create() !Buffer {
|
||||||
ArrayBuffer = c.GL_ARRAY_BUFFER,
|
var vbo: c.GLuint = undefined;
|
||||||
ElementArrayBuffer = c.GL_ELEMENT_ARRAY_BUFFER,
|
glad.context.GenBuffers.?(1, &vbo);
|
||||||
_,
|
return Buffer{ .id = vbo };
|
||||||
};
|
}
|
||||||
|
|
||||||
/// Enum for possible buffer usages.
|
/// glBindBuffer
|
||||||
pub const Usage = enum(c_uint) {
|
pub fn bind(self: Buffer, target: Target) !Binding {
|
||||||
StreamDraw = c.GL_STREAM_DRAW,
|
glad.context.BindBuffer.?(@intFromEnum(target), self.id);
|
||||||
StreamRead = c.GL_STREAM_READ,
|
return Binding{ .id = self.id, .target = target };
|
||||||
StreamCopy = c.GL_STREAM_COPY,
|
}
|
||||||
StaticDraw = c.GL_STATIC_DRAW,
|
|
||||||
StaticRead = c.GL_STATIC_READ,
|
pub fn destroy(self: Buffer) void {
|
||||||
StaticCopy = c.GL_STATIC_COPY,
|
glad.context.DeleteBuffers.?(1, &self.id);
|
||||||
DynamicDraw = c.GL_DYNAMIC_DRAW,
|
}
|
||||||
DynamicRead = c.GL_DYNAMIC_READ,
|
|
||||||
DynamicCopy = c.GL_DYNAMIC_COPY,
|
pub fn bindBase(self: Buffer, target: Target, idx: c.GLuint) !void {
|
||||||
_,
|
glad.context.BindBufferBase.?(
|
||||||
};
|
@intFromEnum(target),
|
||||||
|
idx,
|
||||||
|
self.id,
|
||||||
|
);
|
||||||
|
try errors.getError();
|
||||||
|
}
|
||||||
|
|
||||||
/// Binding is a bound buffer. By using this for functions that operate
|
/// Binding is a bound buffer. By using this for functions that operate
|
||||||
/// on bound buffers, you can easily defer unbinding and in safety-enabled
|
/// on bound buffers, you can easily defer unbinding and in safety-enabled
|
||||||
/// modes verify that unbound buffers are never accessed.
|
/// modes verify that unbound buffers are never accessed.
|
||||||
pub const Binding = struct {
|
pub const Binding = struct {
|
||||||
|
id: c.GLuint,
|
||||||
target: Target,
|
target: Target,
|
||||||
|
|
||||||
|
pub fn unbind(b: Binding) void {
|
||||||
|
glad.context.BindBuffer.?(@intFromEnum(b.target), 0);
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the data of this bound buffer. The data can be any array-like
|
/// Sets the data of this bound buffer. The data can be any array-like
|
||||||
/// type. The size of the data is automatically determined based on the type.
|
/// type. The size of the data is automatically determined based on the type.
|
||||||
pub inline fn setData(
|
pub fn setData(
|
||||||
b: Binding,
|
b: Binding,
|
||||||
data: anytype,
|
data: anytype,
|
||||||
usage: Usage,
|
usage: Usage,
|
||||||
) !void {
|
) !void {
|
||||||
const info = dataInfo(&data);
|
const info = dataInfo(&data);
|
||||||
glad.context.BufferData.?(@intFromEnum(b.target), info.size, info.ptr, @intFromEnum(usage));
|
glad.context.BufferData.?(
|
||||||
|
@intFromEnum(b.target),
|
||||||
|
info.size,
|
||||||
|
info.ptr,
|
||||||
|
@intFromEnum(usage),
|
||||||
|
);
|
||||||
try errors.getError();
|
try errors.getError();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the data of this bound buffer. The data can be any array-like
|
/// Sets the data of this bound buffer. The data can be any array-like
|
||||||
/// type. The size of the data is automatically determined based on the type.
|
/// type. The size of the data is automatically determined based on the type.
|
||||||
pub inline fn setSubData(
|
pub fn setSubData(
|
||||||
b: Binding,
|
b: Binding,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
data: anytype,
|
data: anytype,
|
||||||
) !void {
|
) !void {
|
||||||
const info = dataInfo(data);
|
const info = dataInfo(data);
|
||||||
glad.context.BufferSubData.?(@intFromEnum(b.target), @intCast(offset), info.size, info.ptr);
|
glad.context.BufferSubData.?(
|
||||||
|
@intFromEnum(b.target),
|
||||||
|
@intCast(offset),
|
||||||
|
info.size,
|
||||||
|
info.ptr,
|
||||||
|
);
|
||||||
try errors.getError();
|
try errors.getError();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the buffer data with a null buffer that is expected to be
|
/// Sets the buffer data with a null buffer that is expected to be
|
||||||
/// filled in the future using subData. This requires the type just so
|
/// filled in the future using subData. This requires the type just so
|
||||||
/// we can setup the data size.
|
/// we can setup the data size.
|
||||||
pub inline fn setDataNull(
|
pub fn setDataNull(
|
||||||
b: Binding,
|
b: Binding,
|
||||||
comptime T: type,
|
comptime T: type,
|
||||||
usage: Usage,
|
usage: Usage,
|
||||||
) !void {
|
) !void {
|
||||||
glad.context.BufferData.?(@intFromEnum(b.target), @sizeOf(T), null, @intFromEnum(usage));
|
glad.context.BufferData.?(
|
||||||
|
@intFromEnum(b.target),
|
||||||
|
@sizeOf(T),
|
||||||
|
null,
|
||||||
|
@intFromEnum(usage),
|
||||||
|
);
|
||||||
try errors.getError();
|
try errors.getError();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same as setDataNull but lets you manually specify the buffer size.
|
/// Same as setDataNull but lets you manually specify the buffer size.
|
||||||
pub inline fn setDataNullManual(
|
pub fn setDataNullManual(
|
||||||
b: Binding,
|
b: Binding,
|
||||||
size: usize,
|
size: usize,
|
||||||
usage: Usage,
|
usage: Usage,
|
||||||
) !void {
|
) !void {
|
||||||
glad.context.BufferData.?(@intFromEnum(b.target), @intCast(size), null, @intFromEnum(usage));
|
glad.context.BufferData.?(
|
||||||
|
@intFromEnum(b.target),
|
||||||
|
@intCast(size),
|
||||||
|
null,
|
||||||
|
@intFromEnum(usage),
|
||||||
|
);
|
||||||
try errors.getError();
|
try errors.getError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +117,7 @@ pub const Binding = struct {
|
|||||||
return switch (@typeInfo(@TypeOf(data))) {
|
return switch (@typeInfo(@TypeOf(data))) {
|
||||||
.Pointer => |ptr| switch (ptr.size) {
|
.Pointer => |ptr| switch (ptr.size) {
|
||||||
.One => .{
|
.One => .{
|
||||||
.size = @sizeOf(ptr.child) * data.len,
|
.size = @sizeOf(ptr.child),
|
||||||
.ptr = data,
|
.ptr = data,
|
||||||
},
|
},
|
||||||
.Slice => .{
|
.Slice => .{
|
||||||
@ -106,7 +136,7 @@ pub const Binding = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn enableAttribArray(_: Binding, idx: c.GLuint) !void {
|
pub fn enableAttribArray(_: Binding, idx: c.GLuint) !void {
|
||||||
glad.context.EnableVertexAttribArray.?(idx);
|
glad.context.EnableVertexAttribArray.?(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +188,7 @@ pub const Binding = struct {
|
|||||||
try errors.getError();
|
try errors.getError();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn attributeAdvanced(
|
pub fn attributeAdvanced(
|
||||||
_: Binding,
|
_: Binding,
|
||||||
idx: c.GLuint,
|
idx: c.GLuint,
|
||||||
size: c.GLint,
|
size: c.GLint,
|
||||||
@ -177,7 +207,7 @@ pub const Binding = struct {
|
|||||||
try errors.getError();
|
try errors.getError();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn attributeIAdvanced(
|
pub fn attributeIAdvanced(
|
||||||
_: Binding,
|
_: Binding,
|
||||||
idx: c.GLuint,
|
idx: c.GLuint,
|
||||||
size: c.GLint,
|
size: c.GLint,
|
||||||
@ -193,26 +223,26 @@ pub const Binding = struct {
|
|||||||
glad.context.VertexAttribIPointer.?(idx, size, typ, stride, offsetPtr);
|
glad.context.VertexAttribIPointer.?(idx, size, typ, stride, offsetPtr);
|
||||||
try errors.getError();
|
try errors.getError();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn unbind(b: *Binding) void {
|
|
||||||
glad.context.BindBuffer.?(@intFromEnum(b.target), 0);
|
|
||||||
b.* = undefined;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Create a single buffer.
|
/// Enum for possible binding targets.
|
||||||
pub inline fn create() !Buffer {
|
pub const Target = enum(c_uint) {
|
||||||
var vbo: c.GLuint = undefined;
|
array = c.GL_ARRAY_BUFFER,
|
||||||
glad.context.GenBuffers.?(1, &vbo);
|
element_array = c.GL_ELEMENT_ARRAY_BUFFER,
|
||||||
return Buffer{ .id = vbo };
|
uniform = c.GL_UNIFORM_BUFFER,
|
||||||
}
|
_,
|
||||||
|
};
|
||||||
|
|
||||||
/// glBindBuffer
|
/// Enum for possible buffer usages.
|
||||||
pub inline fn bind(v: Buffer, target: Target) !Binding {
|
pub const Usage = enum(c_uint) {
|
||||||
glad.context.BindBuffer.?(@intFromEnum(target), v.id);
|
stream_draw = c.GL_STREAM_DRAW,
|
||||||
return Binding{ .target = target };
|
stream_read = c.GL_STREAM_READ,
|
||||||
}
|
stream_copy = c.GL_STREAM_COPY,
|
||||||
|
static_draw = c.GL_STATIC_DRAW,
|
||||||
pub inline fn destroy(v: Buffer) void {
|
static_read = c.GL_STATIC_READ,
|
||||||
glad.context.DeleteBuffers.?(1, &v.id);
|
static_copy = c.GL_STATIC_COPY,
|
||||||
}
|
dynamic_draw = c.GL_DYNAMIC_DRAW,
|
||||||
|
dynamic_read = c.GL_DYNAMIC_READ,
|
||||||
|
dynamic_copy = c.GL_DYNAMIC_COPY,
|
||||||
|
_,
|
||||||
|
};
|
92
pkg/opengl/Framebuffer.zig
Normal file
92
pkg/opengl/Framebuffer.zig
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
const Framebuffer = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const c = @import("c.zig");
|
||||||
|
const errors = @import("errors.zig");
|
||||||
|
const glad = @import("glad.zig");
|
||||||
|
const Texture = @import("Texture.zig");
|
||||||
|
|
||||||
|
id: c.GLuint,
|
||||||
|
|
||||||
|
/// Create a single buffer.
|
||||||
|
pub fn create() !Framebuffer {
|
||||||
|
var fbo: c.GLuint = undefined;
|
||||||
|
glad.context.GenFramebuffers.?(1, &fbo);
|
||||||
|
return .{ .id = fbo };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(v: Framebuffer) void {
|
||||||
|
glad.context.DeleteFramebuffers.?(1, &v.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bind(v: Framebuffer, target: Target) !Binding {
|
||||||
|
// The default framebuffer is documented as being zero but
|
||||||
|
// on multiple OpenGL drivers its not zero, so we grab it
|
||||||
|
// at runtime.
|
||||||
|
var current: c.GLint = undefined;
|
||||||
|
glad.context.GetIntegerv.?(c.GL_FRAMEBUFFER_BINDING, ¤t);
|
||||||
|
glad.context.BindFramebuffer.?(@intFromEnum(target), v.id);
|
||||||
|
return .{ .target = target, .previous = @intCast(current) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enum for possible binding targets.
|
||||||
|
pub const Target = enum(c_uint) {
|
||||||
|
framebuffer = c.GL_FRAMEBUFFER,
|
||||||
|
draw = c.GL_DRAW_FRAMEBUFFER,
|
||||||
|
read = c.GL_READ_FRAMEBUFFER,
|
||||||
|
_,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Attachment = enum(c_uint) {
|
||||||
|
color0 = c.GL_COLOR_ATTACHMENT0,
|
||||||
|
depth = c.GL_DEPTH_ATTACHMENT,
|
||||||
|
stencil = c.GL_STENCIL_ATTACHMENT,
|
||||||
|
depth_stencil = c.GL_DEPTH_STENCIL_ATTACHMENT,
|
||||||
|
_,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Status = enum(c_uint) {
|
||||||
|
complete = c.GL_FRAMEBUFFER_COMPLETE,
|
||||||
|
undefined = c.GL_FRAMEBUFFER_UNDEFINED,
|
||||||
|
incomplete_attachment = c.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT,
|
||||||
|
incomplete_missing_attachment = c.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT,
|
||||||
|
incomplete_draw_buffer = c.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER,
|
||||||
|
incomplete_read_buffer = c.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER,
|
||||||
|
unsupported = c.GL_FRAMEBUFFER_UNSUPPORTED,
|
||||||
|
incomplete_multisample = c.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE,
|
||||||
|
incomplete_layer_targets = c.GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS,
|
||||||
|
_,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Binding = struct {
|
||||||
|
target: Target,
|
||||||
|
previous: c.GLuint,
|
||||||
|
|
||||||
|
pub fn unbind(self: Binding) void {
|
||||||
|
glad.context.BindFramebuffer.?(
|
||||||
|
@intFromEnum(self.target),
|
||||||
|
self.previous,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn texture2D(
|
||||||
|
self: Binding,
|
||||||
|
attachment: Attachment,
|
||||||
|
textarget: Texture.Target,
|
||||||
|
texture: Texture,
|
||||||
|
level: c.GLint,
|
||||||
|
) !void {
|
||||||
|
glad.context.FramebufferTexture2D.?(
|
||||||
|
@intFromEnum(self.target),
|
||||||
|
@intFromEnum(attachment),
|
||||||
|
@intFromEnum(textarget),
|
||||||
|
texture.id,
|
||||||
|
level,
|
||||||
|
);
|
||||||
|
try errors.getError();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checkStatus(self: Binding) Status {
|
||||||
|
return @enumFromInt(glad.context.CheckFramebufferStatus.?(@intFromEnum(self.target)));
|
||||||
|
}
|
||||||
|
};
|
@ -11,23 +11,22 @@ const glad = @import("glad.zig");
|
|||||||
|
|
||||||
id: c.GLuint,
|
id: c.GLuint,
|
||||||
|
|
||||||
const Binding = struct {
|
pub const Binding = struct {
|
||||||
pub inline fn unbind(_: Binding) void {
|
pub fn unbind(_: Binding) void {
|
||||||
glad.context.UseProgram.?(0);
|
glad.context.UseProgram.?(0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub inline fn create() !Program {
|
pub fn create() !Program {
|
||||||
const id = glad.context.CreateProgram.?();
|
const id = glad.context.CreateProgram.?();
|
||||||
if (id == 0) try errors.mustError();
|
if (id == 0) try errors.mustError();
|
||||||
|
|
||||||
log.debug("program created id={}", .{id});
|
log.debug("program created id={}", .{id});
|
||||||
return Program{ .id = id };
|
return .{ .id = id };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a program from a vertex and fragment shader source. This will
|
/// Create a program from a vertex and fragment shader source. This will
|
||||||
/// compile and link the vertex and fragment shader.
|
/// compile and link the vertex and fragment shader.
|
||||||
pub inline fn createVF(vsrc: [:0]const u8, fsrc: [:0]const u8) !Program {
|
pub fn createVF(vsrc: [:0]const u8, fsrc: [:0]const u8) !Program {
|
||||||
const vs = try Shader.create(c.GL_VERTEX_SHADER);
|
const vs = try Shader.create(c.GL_VERTEX_SHADER);
|
||||||
try vs.setSourceAndCompile(vsrc);
|
try vs.setSourceAndCompile(vsrc);
|
||||||
defer vs.destroy();
|
defer vs.destroy();
|
||||||
@ -44,12 +43,18 @@ pub inline fn createVF(vsrc: [:0]const u8, fsrc: [:0]const u8) !Program {
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn attachShader(p: Program, s: Shader) !void {
|
pub fn destroy(p: Program) void {
|
||||||
|
assert(p.id != 0);
|
||||||
|
glad.context.DeleteProgram.?(p.id);
|
||||||
|
log.debug("program destroyed id={}", .{p.id});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn attachShader(p: Program, s: Shader) !void {
|
||||||
glad.context.AttachShader.?(p.id, s.id);
|
glad.context.AttachShader.?(p.id, s.id);
|
||||||
try errors.getError();
|
try errors.getError();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn link(p: Program) !void {
|
pub fn link(p: Program) !void {
|
||||||
glad.context.LinkProgram.?(p.id);
|
glad.context.LinkProgram.?(p.id);
|
||||||
|
|
||||||
// Check if linking succeeded
|
// Check if linking succeeded
|
||||||
@ -67,14 +72,23 @@ pub inline fn link(p: Program) !void {
|
|||||||
return error.CompileFailed;
|
return error.CompileFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn use(p: Program) !Binding {
|
pub fn use(p: Program) !Binding {
|
||||||
glad.context.UseProgram.?(p.id);
|
glad.context.UseProgram.?(p.id);
|
||||||
try errors.getError();
|
try errors.getError();
|
||||||
return Binding{};
|
return .{};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uniformBlockBinding(
|
||||||
|
self: Program,
|
||||||
|
index: c.GLuint,
|
||||||
|
binding: c.GLuint,
|
||||||
|
) !void {
|
||||||
|
glad.context.UniformBlockBinding.?(self.id, index, binding);
|
||||||
|
try errors.getError();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Requires the program is currently in use.
|
/// Requires the program is currently in use.
|
||||||
pub inline fn setUniform(
|
pub fn setUniform(
|
||||||
p: Program,
|
p: Program,
|
||||||
n: [:0]const u8,
|
n: [:0]const u8,
|
||||||
value: anytype,
|
value: anytype,
|
||||||
@ -115,14 +129,8 @@ pub inline fn setUniform(
|
|||||||
//
|
//
|
||||||
// NOTE(mitchellh): we can add a dynamic version that uses an allocator
|
// NOTE(mitchellh): we can add a dynamic version that uses an allocator
|
||||||
// if we ever need it.
|
// if we ever need it.
|
||||||
pub inline fn getInfoLog(s: Program) [512]u8 {
|
pub fn getInfoLog(s: Program) [512]u8 {
|
||||||
var msg: [512]u8 = undefined;
|
var msg: [512]u8 = undefined;
|
||||||
glad.context.GetProgramInfoLog.?(s.id, msg.len, null, &msg);
|
glad.context.GetProgramInfoLog.?(s.id, msg.len, null, &msg);
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn destroy(p: Program) void {
|
|
||||||
assert(p.id != 0);
|
|
||||||
glad.context.DeleteProgram.?(p.id);
|
|
||||||
log.debug("program destroyed id={}", .{p.id});
|
|
||||||
}
|
|
@ -7,11 +7,29 @@ const glad = @import("glad.zig");
|
|||||||
|
|
||||||
id: c.GLuint,
|
id: c.GLuint,
|
||||||
|
|
||||||
pub inline fn active(target: c.GLenum) !void {
|
pub fn active(target: c.GLenum) !void {
|
||||||
glad.context.ActiveTexture.?(target);
|
glad.context.ActiveTexture.?(target);
|
||||||
try errors.getError();
|
try errors.getError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a single texture.
|
||||||
|
pub fn create() !Texture {
|
||||||
|
var id: c.GLuint = undefined;
|
||||||
|
glad.context.GenTextures.?(1, &id);
|
||||||
|
return .{ .id = id };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// glBindTexture
|
||||||
|
pub fn bind(v: Texture, target: Target) !Binding {
|
||||||
|
glad.context.BindTexture.?(@intFromEnum(target), v.id);
|
||||||
|
try errors.getError();
|
||||||
|
return .{ .target = target };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(v: Texture) void {
|
||||||
|
glad.context.DeleteTextures.?(1, &v.id);
|
||||||
|
}
|
||||||
|
|
||||||
/// Enun for possible texture binding targets.
|
/// Enun for possible texture binding targets.
|
||||||
pub const Target = enum(c_uint) {
|
pub const Target = enum(c_uint) {
|
||||||
@"1D" = c.GL_TEXTURE_1D,
|
@"1D" = c.GL_TEXTURE_1D,
|
||||||
@ -48,8 +66,9 @@ pub const Parameter = enum(c_uint) {
|
|||||||
|
|
||||||
/// Internal format enum for texture images.
|
/// Internal format enum for texture images.
|
||||||
pub const InternalFormat = enum(c_int) {
|
pub const InternalFormat = enum(c_int) {
|
||||||
Red = c.GL_RED,
|
red = c.GL_RED,
|
||||||
RGBA = c.GL_RGBA,
|
rgb = c.GL_RGB,
|
||||||
|
rgba = c.GL_RGBA,
|
||||||
|
|
||||||
// There are so many more that I haven't filled in.
|
// There are so many more that I haven't filled in.
|
||||||
_,
|
_,
|
||||||
@ -57,8 +76,9 @@ pub const InternalFormat = enum(c_int) {
|
|||||||
|
|
||||||
/// Format for texture images
|
/// Format for texture images
|
||||||
pub const Format = enum(c_uint) {
|
pub const Format = enum(c_uint) {
|
||||||
Red = c.GL_RED,
|
red = c.GL_RED,
|
||||||
BGRA = c.GL_BGRA,
|
rgb = c.GL_RGB,
|
||||||
|
bgra = c.GL_BGRA,
|
||||||
|
|
||||||
// There are so many more that I haven't filled in.
|
// There are so many more that I haven't filled in.
|
||||||
_,
|
_,
|
||||||
@ -75,9 +95,8 @@ pub const DataType = enum(c_uint) {
|
|||||||
pub const Binding = struct {
|
pub const Binding = struct {
|
||||||
target: Target,
|
target: Target,
|
||||||
|
|
||||||
pub inline fn unbind(b: *Binding) void {
|
pub fn unbind(b: *const Binding) void {
|
||||||
glad.context.BindTexture.?(@intFromEnum(b.target), 0);
|
glad.context.BindTexture.?(@intFromEnum(b.target), 0);
|
||||||
b.* = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generateMipmap(b: Binding) void {
|
pub fn generateMipmap(b: Binding) void {
|
||||||
@ -143,21 +162,3 @@ pub const Binding = struct {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Create a single texture.
|
|
||||||
pub inline fn create() !Texture {
|
|
||||||
var id: c.GLuint = undefined;
|
|
||||||
glad.context.GenTextures.?(1, &id);
|
|
||||||
return Texture{ .id = id };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// glBindTexture
|
|
||||||
pub inline fn bind(v: Texture, target: Target) !Binding {
|
|
||||||
glad.context.BindTexture.?(@intFromEnum(target), v.id);
|
|
||||||
try errors.getError();
|
|
||||||
return Binding{ .target = target };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub inline fn destroy(v: Texture) void {
|
|
||||||
glad.context.DeleteTextures.?(1, &v.id);
|
|
||||||
}
|
|
@ -7,23 +7,26 @@ const errors = @import("errors.zig");
|
|||||||
id: c.GLuint,
|
id: c.GLuint,
|
||||||
|
|
||||||
/// Create a single vertex array object.
|
/// Create a single vertex array object.
|
||||||
pub inline fn create() !VertexArray {
|
pub fn create() !VertexArray {
|
||||||
var vao: c.GLuint = undefined;
|
var vao: c.GLuint = undefined;
|
||||||
glad.context.GenVertexArrays.?(1, &vao);
|
glad.context.GenVertexArrays.?(1, &vao);
|
||||||
return VertexArray{ .id = vao };
|
return VertexArray{ .id = vao };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unbind any active vertex array.
|
|
||||||
pub inline fn unbind() !void {
|
|
||||||
glad.context.BindVertexArray.?(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// glBindVertexArray
|
/// glBindVertexArray
|
||||||
pub inline fn bind(v: VertexArray) !void {
|
pub fn bind(v: VertexArray) !Binding {
|
||||||
glad.context.BindVertexArray.?(v.id);
|
glad.context.BindVertexArray.?(v.id);
|
||||||
try errors.getError();
|
try errors.getError();
|
||||||
|
return .{};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn destroy(v: VertexArray) void {
|
pub fn destroy(v: VertexArray) void {
|
||||||
glad.context.DeleteVertexArrays.?(1, &v.id);
|
glad.context.DeleteVertexArrays.?(1, &v.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const Binding = struct {
|
||||||
|
pub fn unbind(self: Binding) void {
|
||||||
|
_ = self;
|
||||||
|
glad.context.BindVertexArray.?(0);
|
||||||
|
}
|
||||||
|
};
|
5
pkg/opengl/build.zig
Normal file
5
pkg/opengl/build.zig
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) !void {
|
||||||
|
_ = b.addModule("opengl", .{ .source_file = .{ .path = "main.zig" } });
|
||||||
|
}
|
@ -17,6 +17,7 @@ pub usingnamespace @import("draw.zig");
|
|||||||
|
|
||||||
pub const ext = @import("extensions.zig");
|
pub const ext = @import("extensions.zig");
|
||||||
pub const Buffer = @import("Buffer.zig");
|
pub const Buffer = @import("Buffer.zig");
|
||||||
|
pub const Framebuffer = @import("Framebuffer.zig");
|
||||||
pub const Program = @import("Program.zig");
|
pub const Program = @import("Program.zig");
|
||||||
pub const Shader = @import("Shader.zig");
|
pub const Shader = @import("Shader.zig");
|
||||||
pub const Texture = @import("Texture.zig");
|
pub const Texture = @import("Texture.zig");
|
85
pkg/spirv-cross/build.zig
Normal file
85
pkg/spirv-cross/build.zig
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) !void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
_ = b.addModule("spirv_cross", .{ .source_file = .{ .path = "main.zig" } });
|
||||||
|
|
||||||
|
const upstream = b.dependency("spirv_cross", .{});
|
||||||
|
const lib = try buildSpirvCross(b, upstream, target, optimize);
|
||||||
|
b.installArtifact(lib);
|
||||||
|
|
||||||
|
{
|
||||||
|
const test_exe = b.addTest(.{
|
||||||
|
.name = "test",
|
||||||
|
.root_source_file = .{ .path = "main.zig" },
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
test_exe.linkLibrary(lib);
|
||||||
|
const tests_run = b.addRunArtifact(test_exe);
|
||||||
|
const test_step = b.step("test", "Run tests");
|
||||||
|
test_step.dependOn(&tests_run.step);
|
||||||
|
|
||||||
|
// Uncomment this if we're debugging tests
|
||||||
|
// b.installArtifact(test_exe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buildSpirvCross(
|
||||||
|
b: *std.Build,
|
||||||
|
upstream: *std.Build.Dependency,
|
||||||
|
target: std.zig.CrossTarget,
|
||||||
|
optimize: std.builtin.OptimizeMode,
|
||||||
|
) !*std.Build.Step.Compile {
|
||||||
|
const lib = b.addStaticLibrary(.{
|
||||||
|
.name = "spirv_cross",
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
lib.linkLibC();
|
||||||
|
lib.linkLibCpp();
|
||||||
|
//lib.addIncludePath(upstream.path(""));
|
||||||
|
//lib.addIncludePath(.{ .path = "override" });
|
||||||
|
|
||||||
|
var flags = std.ArrayList([]const u8).init(b.allocator);
|
||||||
|
defer flags.deinit();
|
||||||
|
try flags.appendSlice(&.{
|
||||||
|
"-DSPIRV_CROSS_C_API_GLSL=1",
|
||||||
|
"-DSPIRV_CROSS_C_API_MSL=1",
|
||||||
|
|
||||||
|
"-fno-sanitize=undefined",
|
||||||
|
"-fno-sanitize-trap=undefined",
|
||||||
|
});
|
||||||
|
|
||||||
|
lib.addCSourceFiles(.{
|
||||||
|
.dependency = upstream,
|
||||||
|
.flags = flags.items,
|
||||||
|
.files = &.{
|
||||||
|
// Core
|
||||||
|
"spirv_cross.cpp",
|
||||||
|
"spirv_parser.cpp",
|
||||||
|
"spirv_cross_parsed_ir.cpp",
|
||||||
|
"spirv_cfg.cpp",
|
||||||
|
|
||||||
|
// C
|
||||||
|
"spirv_cross_c.cpp",
|
||||||
|
|
||||||
|
// GLSL
|
||||||
|
"spirv_glsl.cpp",
|
||||||
|
|
||||||
|
// MSL
|
||||||
|
"spirv_msl.cpp",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
lib.installHeadersDirectoryOptions(.{
|
||||||
|
.source_dir = upstream.path(""),
|
||||||
|
.install_dir = .header,
|
||||||
|
.install_subdir = "",
|
||||||
|
.include_extensions = &.{".h"},
|
||||||
|
});
|
||||||
|
|
||||||
|
return lib;
|
||||||
|
}
|
11
pkg/spirv-cross/build.zig.zon
Normal file
11
pkg/spirv-cross/build.zig.zon
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
.{
|
||||||
|
.name = "spirv-cross",
|
||||||
|
.version = "13.1.1",
|
||||||
|
.paths = .{""},
|
||||||
|
.dependencies = .{
|
||||||
|
.spirv_cross = .{
|
||||||
|
.url = "https://github.com/KhronosGroup/SPIRV-Cross/archive/4818f7e7ef7b7078a3a7a5a52c4a338e0dda22f4.tar.gz",
|
||||||
|
.hash = "1220b2d8a6cff1926ef28a29e312a0a503b555ebc2f082230b882410f49e672ac9c6",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
3
pkg/spirv-cross/c.zig
Normal file
3
pkg/spirv-cross/c.zig
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub usingnamespace @cImport({
|
||||||
|
@cInclude("spirv_cross_c.h");
|
||||||
|
});
|
1
pkg/spirv-cross/main.zig
Normal file
1
pkg/spirv-cross/main.zig
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub const c = @import("c.zig");
|
@ -291,7 +291,7 @@ pub const App = struct {
|
|||||||
tabbing_id: *macos.foundation.String,
|
tabbing_id: *macos.foundation.String,
|
||||||
|
|
||||||
pub fn init() !Darwin {
|
pub fn init() !Darwin {
|
||||||
const NSWindow = objc.Class.getClass("NSWindow").?;
|
const NSWindow = objc.getClass("NSWindow").?;
|
||||||
NSWindow.msgSend(void, objc.sel("setAllowsAutomaticWindowTabbing:"), .{true});
|
NSWindow.msgSend(void, objc.sel("setAllowsAutomaticWindowTabbing:"), .{true});
|
||||||
|
|
||||||
// Our tabbing ID allows all of our windows to group together
|
// Our tabbing ID allows all of our windows to group together
|
||||||
|
@ -6,7 +6,7 @@ const assert = std.debug.assert;
|
|||||||
const cimgui = @import("cimgui");
|
const cimgui = @import("cimgui");
|
||||||
const c = @import("c.zig");
|
const c = @import("c.zig");
|
||||||
const key = @import("key.zig");
|
const key = @import("key.zig");
|
||||||
const gl = @import("../../renderer/opengl/main.zig");
|
const gl = @import("opengl");
|
||||||
const input = @import("../../input.zig");
|
const input = @import("../../input.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.gtk_imgui_widget);
|
const log = std.log.scoped(.gtk_imgui_widget);
|
||||||
|
@ -252,7 +252,7 @@ pub fn deinit(self: *Surface) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render(self: *Surface) !void {
|
fn render(self: *Surface) !void {
|
||||||
try self.core_surface.renderer.draw();
|
try self.core_surface.renderer.drawFrame(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queue the inspector to render if we have one.
|
/// Queue the inspector to render if we have one.
|
||||||
|
@ -528,7 +528,7 @@ keybind: Keybinds = .{},
|
|||||||
///
|
///
|
||||||
/// Cycles are not allowed. If a cycle is detected, an error will be logged
|
/// Cycles are not allowed. If a cycle is detected, an error will be logged
|
||||||
/// and the configuration file will be ignored.
|
/// and the configuration file will be ignored.
|
||||||
@"config-file": RepeatableString = .{},
|
@"config-file": RepeatablePath = .{},
|
||||||
|
|
||||||
/// Confirms that a surface should be closed before closing it. This defaults
|
/// Confirms that a surface should be closed before closing it. This defaults
|
||||||
/// to true. If set to false, surfaces will close without any confirmation.
|
/// to true. If set to false, surfaces will close without any confirmation.
|
||||||
@ -599,6 +599,46 @@ 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.
|
||||||
|
///
|
||||||
|
/// On Linux, this requires OpenGL 4.2. Ghostty typically only requires
|
||||||
|
/// OpenGL 3.3, but custom shaders push that requirement up to 4.2.
|
||||||
|
///
|
||||||
|
/// 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. 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.
|
||||||
|
///
|
||||||
|
/// Changing this value at runtime and reloading the configuration will only
|
||||||
|
/// affect new windows, tabs, and splits.
|
||||||
|
@"custom-shader": RepeatablePath = .{},
|
||||||
|
|
||||||
|
/// If true (default), the focused terminal surface will run an animation
|
||||||
|
/// loop when custom shaders are used. This uses slightly more CPU (generally
|
||||||
|
/// less than 10%) but allows the shader to animate. This only runs if there
|
||||||
|
/// are custom shaders.
|
||||||
|
///
|
||||||
|
/// If this is set to false, the terminal and custom shader will only render
|
||||||
|
/// when the terminal is updated. This is more efficient but the shader will
|
||||||
|
/// not animate.
|
||||||
|
///
|
||||||
|
/// This value can be changed at runtime and will affect all currently
|
||||||
|
/// open terminals.
|
||||||
|
@"custom-shader-animation": bool = true,
|
||||||
|
|
||||||
/// 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
|
||||||
@ -1143,7 +1183,7 @@ pub fn loadDefaultFiles(self: *Config, alloc: Allocator) !void {
|
|||||||
var buf_reader = std.io.bufferedReader(file.reader());
|
var buf_reader = std.io.bufferedReader(file.reader());
|
||||||
var iter = cli.args.lineIterator(buf_reader.reader());
|
var iter = cli.args.lineIterator(buf_reader.reader());
|
||||||
try cli.args.parse(Config, alloc, self, &iter);
|
try cli.args.parse(Config, alloc, self, &iter);
|
||||||
try self.expandConfigFiles(std.fs.path.dirname(config_path).?);
|
try self.expandPaths(std.fs.path.dirname(config_path).?);
|
||||||
} else |err| switch (err) {
|
} else |err| switch (err) {
|
||||||
error.FileNotFound => std.log.info(
|
error.FileNotFound => std.log.info(
|
||||||
"homedir config not found, not loading path={s}",
|
"homedir config not found, not loading path={s}",
|
||||||
@ -1172,15 +1212,15 @@ pub fn loadCliArgs(self: *Config, alloc_gpa: Allocator) !void {
|
|||||||
try cli.args.parse(Config, alloc_gpa, self, &iter);
|
try cli.args.parse(Config, alloc_gpa, self, &iter);
|
||||||
|
|
||||||
// Config files loaded from the CLI args are relative to pwd
|
// Config files loaded from the CLI args are relative to pwd
|
||||||
if (self.@"config-file".list.items.len > 0) {
|
if (self.@"config-file".value.list.items.len > 0) {
|
||||||
var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
||||||
try self.expandConfigFiles(try std.fs.cwd().realpath(".", &buf));
|
try self.expandPaths(try std.fs.cwd().realpath(".", &buf));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load and parse the config files that were added in the "config-file" key.
|
/// Load and parse the config files that were added in the "config-file" key.
|
||||||
pub fn loadRecursiveFiles(self: *Config, alloc_gpa: Allocator) !void {
|
pub fn loadRecursiveFiles(self: *Config, alloc_gpa: Allocator) !void {
|
||||||
if (self.@"config-file".list.items.len == 0) return;
|
if (self.@"config-file".value.list.items.len == 0) return;
|
||||||
const arena_alloc = self._arena.?.allocator();
|
const arena_alloc = self._arena.?.allocator();
|
||||||
|
|
||||||
// Keeps track of loaded files to prevent cycles.
|
// Keeps track of loaded files to prevent cycles.
|
||||||
@ -1189,8 +1229,8 @@ pub fn loadRecursiveFiles(self: *Config, alloc_gpa: Allocator) !void {
|
|||||||
|
|
||||||
const cwd = std.fs.cwd();
|
const cwd = std.fs.cwd();
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < self.@"config-file".list.items.len) : (i += 1) {
|
while (i < self.@"config-file".value.list.items.len) : (i += 1) {
|
||||||
const path = self.@"config-file".list.items[i];
|
const path = self.@"config-file".value.list.items[i];
|
||||||
|
|
||||||
// Error paths
|
// Error paths
|
||||||
if (path.len == 0) continue;
|
if (path.len == 0) continue;
|
||||||
@ -1227,37 +1267,22 @@ pub fn loadRecursiveFiles(self: *Config, alloc_gpa: Allocator) !void {
|
|||||||
var buf_reader = std.io.bufferedReader(file.reader());
|
var buf_reader = std.io.bufferedReader(file.reader());
|
||||||
var iter = cli.args.lineIterator(buf_reader.reader());
|
var iter = cli.args.lineIterator(buf_reader.reader());
|
||||||
try cli.args.parse(Config, alloc_gpa, self, &iter);
|
try cli.args.parse(Config, alloc_gpa, self, &iter);
|
||||||
try self.expandConfigFiles(std.fs.path.dirname(path).?);
|
try self.expandPaths(std.fs.path.dirname(path).?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expand the relative paths in config-files to be absolute paths
|
/// Expand the relative paths in config-files to be absolute paths
|
||||||
/// relative to the base directory.
|
/// relative to the base directory.
|
||||||
fn expandConfigFiles(self: *Config, base: []const u8) !void {
|
fn expandPaths(self: *Config, base: []const u8) !void {
|
||||||
assert(std.fs.path.isAbsolute(base));
|
|
||||||
var dir = try std.fs.cwd().openDir(base, .{});
|
|
||||||
defer dir.close();
|
|
||||||
|
|
||||||
const arena_alloc = self._arena.?.allocator();
|
const arena_alloc = self._arena.?.allocator();
|
||||||
for (self.@"config-file".list.items, 0..) |path, i| {
|
inline for (@typeInfo(Config).Struct.fields) |field| {
|
||||||
// If it is already absolute we can ignore it.
|
if (field.type == RepeatablePath) {
|
||||||
if (path.len == 0 or std.fs.path.isAbsolute(path)) continue;
|
try @field(self, field.name).expand(
|
||||||
|
|
||||||
// If it isn't absolute, we need to make it absolute relative to the base.
|
|
||||||
const abs = dir.realpathAlloc(arena_alloc, path) catch |err| {
|
|
||||||
try self._errors.add(arena_alloc, .{
|
|
||||||
.message = try std.fmt.allocPrintZ(
|
|
||||||
arena_alloc,
|
arena_alloc,
|
||||||
"error resolving config-file {s}: {}",
|
base,
|
||||||
.{ path, err },
|
&self._errors,
|
||||||
),
|
);
|
||||||
});
|
}
|
||||||
self.@"config-file".list.items[i] = "";
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
log.debug("expanding config-file path relative={s} abs={s}", .{ path, abs });
|
|
||||||
self.@"config-file".list.items[i] = abs;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1889,6 +1914,69 @@ pub const RepeatableString = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// RepeatablePath is like repeatable string but represents a path value.
|
||||||
|
/// The difference is that when loading the configuration any values for
|
||||||
|
/// this will be automatically expanded relative to the path of the config
|
||||||
|
/// file.
|
||||||
|
pub const RepeatablePath = struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
value: RepeatableString = .{},
|
||||||
|
|
||||||
|
pub fn parseCLI(self: *Self, alloc: Allocator, input: ?[]const u8) !void {
|
||||||
|
return self.value.parseCLI(alloc, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deep copy of the struct. Required by Config.
|
||||||
|
pub fn clone(self: *const Self, alloc: Allocator) !Self {
|
||||||
|
return .{
|
||||||
|
.value = try self.value.clone(alloc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compare if two of our value are requal. Required by Config.
|
||||||
|
pub fn equal(self: Self, other: Self) bool {
|
||||||
|
return self.value.equal(other.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expand all the paths relative to the base directory.
|
||||||
|
pub fn expand(
|
||||||
|
self: *Self,
|
||||||
|
alloc: Allocator,
|
||||||
|
base: []const u8,
|
||||||
|
errors: *ErrorList,
|
||||||
|
) !void {
|
||||||
|
assert(std.fs.path.isAbsolute(base));
|
||||||
|
var dir = try std.fs.cwd().openDir(base, .{});
|
||||||
|
defer dir.close();
|
||||||
|
|
||||||
|
for (self.value.list.items, 0..) |path, i| {
|
||||||
|
// If it is already absolute we can ignore it.
|
||||||
|
if (path.len == 0 or std.fs.path.isAbsolute(path)) continue;
|
||||||
|
|
||||||
|
// If it isn't absolute, we need to make it absolute relative
|
||||||
|
// to the base.
|
||||||
|
const abs = dir.realpathAlloc(alloc, path) catch |err| {
|
||||||
|
try errors.add(alloc, .{
|
||||||
|
.message = try std.fmt.allocPrintZ(
|
||||||
|
alloc,
|
||||||
|
"error resolving config-file {s}: {}",
|
||||||
|
.{ path, err },
|
||||||
|
),
|
||||||
|
});
|
||||||
|
self.value.list.items[i] = "";
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"expanding config-file path relative={s} abs={s}",
|
||||||
|
.{ path, abs },
|
||||||
|
);
|
||||||
|
self.value.list.items[i] = abs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/// FontVariation is a repeatable configuration value that sets a single
|
/// FontVariation is a repeatable configuration value that sets a single
|
||||||
/// font variation value. Font variations are configurations for what
|
/// font variation value. Font variations are configurations for what
|
||||||
/// are often called "variable fonts." The font files usually end in
|
/// are often called "variable fonts." The font files usually end in
|
||||||
|
@ -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
|
||||||
|
@ -70,7 +70,7 @@ fn setLangFromCocoa() void {
|
|||||||
defer pool.deinit();
|
defer pool.deinit();
|
||||||
|
|
||||||
// The classes we're going to need.
|
// The classes we're going to need.
|
||||||
const NSLocale = objc.Class.getClass("NSLocale") orelse {
|
const NSLocale = objc.getClass("NSLocale") orelse {
|
||||||
log.err("NSLocale class not found. Locale may be incorrect.", .{});
|
log.err("NSLocale class not found. Locale may be incorrect.", .{});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@ const objc = @import("objc");
|
|||||||
pub fn macosVersionAtLeast(major: i64, minor: i64, patch: i64) bool {
|
pub fn macosVersionAtLeast(major: i64, minor: i64, patch: i64) bool {
|
||||||
assert(builtin.target.isDarwin());
|
assert(builtin.target.isDarwin());
|
||||||
|
|
||||||
const NSProcessInfo = objc.Class.getClass("NSProcessInfo").?;
|
const NSProcessInfo = objc.getClass("NSProcessInfo").?;
|
||||||
const info = NSProcessInfo.msgSend(objc.Object, objc.sel("processInfo"), .{});
|
const info = NSProcessInfo.msgSend(objc.Object, objc.sel("processInfo"), .{});
|
||||||
return info.msgSend(bool, objc.sel("isOperatingSystemAtLeastVersion:"), .{
|
return info.msgSend(bool, objc.sel("isOperatingSystemAtLeastVersion:"), .{
|
||||||
NSOperatingSystemVersion{ .major = major, .minor = minor, .patch = patch },
|
NSOperatingSystemVersion{ .major = major, .minor = minor, .patch = patch },
|
||||||
|
@ -9,7 +9,7 @@ const log = std.log.scoped(.os);
|
|||||||
pub fn clickInterval() ?u32 {
|
pub fn clickInterval() ?u32 {
|
||||||
// On macOS, we can ask the system.
|
// On macOS, we can ask the system.
|
||||||
if (comptime builtin.target.isDarwin()) {
|
if (comptime builtin.target.isDarwin()) {
|
||||||
const NSEvent = objc.Class.getClass("NSEvent") orelse {
|
const NSEvent = objc.getClass("NSEvent") orelse {
|
||||||
log.err("NSEvent class not found. Can't get click interval.", .{});
|
log.err("NSEvent class not found. Can't get click interval.", .{});
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
@ -15,6 +15,7 @@ const WasmTarget = @import("os/wasm/target.zig").Target;
|
|||||||
pub usingnamespace @import("renderer/cursor.zig");
|
pub usingnamespace @import("renderer/cursor.zig");
|
||||||
pub usingnamespace @import("renderer/message.zig");
|
pub usingnamespace @import("renderer/message.zig");
|
||||||
pub usingnamespace @import("renderer/size.zig");
|
pub usingnamespace @import("renderer/size.zig");
|
||||||
|
pub const shadertoy = @import("renderer/shadertoy.zig");
|
||||||
pub const Metal = @import("renderer/Metal.zig");
|
pub const Metal = @import("renderer/Metal.zig");
|
||||||
pub const OpenGL = @import("renderer/OpenGL.zig");
|
pub const OpenGL = @import("renderer/OpenGL.zig");
|
||||||
pub const WebGL = @import("renderer/WebGL.zig");
|
pub const WebGL = @import("renderer/WebGL.zig");
|
||||||
|
@ -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,13 +18,16 @@ 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");
|
||||||
const mtl_buffer = @import("metal/buffer.zig");
|
const mtl_buffer = @import("metal/buffer.zig");
|
||||||
const mtl_image = @import("metal/image.zig");
|
const mtl_image = @import("metal/image.zig");
|
||||||
|
const mtl_sampler = @import("metal/sampler.zig");
|
||||||
const mtl_shaders = @import("metal/shaders.zig");
|
const mtl_shaders = @import("metal/shaders.zig");
|
||||||
const Image = mtl_image.Image;
|
const Image = mtl_image.Image;
|
||||||
const ImageMap = mtl_image.ImageMap;
|
const ImageMap = mtl_image.ImageMap;
|
||||||
@ -76,6 +80,10 @@ background_color: terminal.color.RGB,
|
|||||||
/// by a terminal application
|
/// by a terminal application
|
||||||
cursor_color: ?terminal.color.RGB,
|
cursor_color: ?terminal.color.RGB,
|
||||||
|
|
||||||
|
/// The current frame background color. This is only updated during
|
||||||
|
/// the updateFrame method.
|
||||||
|
current_background_color: terminal.color.RGB,
|
||||||
|
|
||||||
/// The current set of cells to render. This is rebuilt on every frame
|
/// The current set of cells to render. This is rebuilt on every frame
|
||||||
/// but we keep this around so that we don't reallocate. Each set of
|
/// but we keep this around so that we don't reallocate. Each set of
|
||||||
/// cells goes into a separate shader.
|
/// cells goes into a separate shader.
|
||||||
@ -108,12 +116,31 @@ swapchain: objc.Object, // CAMetalLayer
|
|||||||
texture_greyscale: objc.Object, // MTLTexture
|
texture_greyscale: objc.Object, // MTLTexture
|
||||||
texture_color: objc.Object, // MTLTexture
|
texture_color: objc.Object, // MTLTexture
|
||||||
|
|
||||||
|
/// Custom shader state. This is only set if we have custom shaders.
|
||||||
|
custom_shader_state: ?CustomShaderState = null,
|
||||||
|
|
||||||
|
pub const CustomShaderState = struct {
|
||||||
|
/// The screen texture that we render the terminal to. If we don't have
|
||||||
|
/// custom shaders, we render directly to the drawable.
|
||||||
|
screen_texture: objc.Object, // MTLTexture
|
||||||
|
sampler: mtl_sampler.Sampler,
|
||||||
|
uniforms: mtl_shaders.PostUniforms,
|
||||||
|
last_frame_time: std.time.Instant,
|
||||||
|
|
||||||
|
pub fn deinit(self: *CustomShaderState) void {
|
||||||
|
deinitMTLResource(self.screen_texture);
|
||||||
|
self.sampler.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/// The configuration for this renderer that is derived from the main
|
/// The configuration for this renderer that is derived from the main
|
||||||
/// 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,
|
||||||
@ -124,17 +151,22 @@ 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),
|
||||||
|
custom_shader_animation: bool,
|
||||||
|
|
||||||
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);
|
||||||
@ -173,11 +205,16 @@ pub const DerivedConfig = struct {
|
|||||||
bg.toTerminalRGB()
|
bg.toTerminalRGB()
|
||||||
else
|
else
|
||||||
null,
|
null,
|
||||||
|
|
||||||
|
.custom_shaders = custom_shaders,
|
||||||
|
.custom_shader_animation = config.@"custom-shader-animation",
|
||||||
|
|
||||||
|
.arena = arena,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *DerivedConfig) void {
|
pub fn deinit(self: *DerivedConfig) void {
|
||||||
self.font_features.deinit();
|
self.arena.deinit();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -199,11 +236,15 @@ 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"), .{});
|
||||||
const swapchain = swapchain: {
|
const swapchain = swapchain: {
|
||||||
const CAMetalLayer = objc.Class.getClass("CAMetalLayer").?;
|
const CAMetalLayer = objc.getClass("CAMetalLayer").?;
|
||||||
const swapchain = CAMetalLayer.msgSend(objc.Object, objc.sel("layer"), .{});
|
const swapchain = CAMetalLayer.msgSend(objc.Object, objc.sel("layer"), .{});
|
||||||
swapchain.setProperty("device", device.value);
|
swapchain.setProperty("device", device.value);
|
||||||
swapchain.setProperty("opaque", options.config.background_opacity >= 1);
|
swapchain.setProperty("opaque", options.config.background_opacity >= 1);
|
||||||
@ -252,9 +293,50 @@ 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 &.{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// If we have custom shaders then setup our state
|
||||||
|
var custom_shader_state: ?CustomShaderState = state: {
|
||||||
|
if (custom_shaders.len == 0) break :state null;
|
||||||
|
|
||||||
|
// Build our sampler for our texture
|
||||||
|
var sampler = try mtl_sampler.Sampler.init(device);
|
||||||
|
errdefer sampler.deinit();
|
||||||
|
|
||||||
|
break :state .{
|
||||||
|
// Resolution and screen texture will be fixed up by first
|
||||||
|
// call to setScreenSize. This happens before any draw call.
|
||||||
|
.screen_texture = undefined,
|
||||||
|
.sampler = sampler,
|
||||||
|
.uniforms = .{
|
||||||
|
.resolution = .{ 0, 0, 1 },
|
||||||
|
.time = 1,
|
||||||
|
.time_delta = 1,
|
||||||
|
.frame_rate = 1,
|
||||||
|
.frame = 1,
|
||||||
|
.channel_time = [1][4]f32{.{ 0, 0, 0, 0 }} ** 4,
|
||||||
|
.channel_resolution = [1][4]f32{.{ 0, 0, 0, 0 }} ** 4,
|
||||||
|
.mouse = .{ 0, 0, 0, 0 },
|
||||||
|
.date = .{ 0, 0, 0, 0 },
|
||||||
|
.sample_rate = 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
.last_frame_time = try std.time.Instant.now(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
errdefer if (custom_shader_state) |*state| state.deinit();
|
||||||
|
|
||||||
// Initialize our shaders
|
// Initialize our shaders
|
||||||
var shaders = try Shaders.init(device);
|
var shaders = try Shaders.init(alloc, device, custom_shaders);
|
||||||
errdefer shaders.deinit();
|
errdefer shaders.deinit(alloc);
|
||||||
|
|
||||||
// Font atlas textures
|
// Font atlas textures
|
||||||
const texture_greyscale = try initAtlasTexture(device, &options.font_group.atlas_greyscale);
|
const texture_greyscale = try initAtlasTexture(device, &options.font_group.atlas_greyscale);
|
||||||
@ -271,6 +353,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
.foreground_color = options.config.foreground,
|
.foreground_color = options.config.foreground,
|
||||||
.background_color = options.config.background,
|
.background_color = options.config.background,
|
||||||
.cursor_color = options.config.cursor_color,
|
.cursor_color = options.config.cursor_color,
|
||||||
|
.current_background_color = options.config.background,
|
||||||
|
|
||||||
// Render state
|
// Render state
|
||||||
.cells_bg = .{},
|
.cells_bg = .{},
|
||||||
@ -298,6 +381,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
.swapchain = swapchain,
|
.swapchain = swapchain,
|
||||||
.texture_greyscale = texture_greyscale,
|
.texture_greyscale = texture_greyscale,
|
||||||
.texture_color = texture_color,
|
.texture_color = texture_color,
|
||||||
|
.custom_shader_state = custom_shader_state,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,7 +407,9 @@ pub fn deinit(self: *Metal) void {
|
|||||||
deinitMTLResource(self.texture_color);
|
deinitMTLResource(self.texture_color);
|
||||||
self.queue.msgSend(void, objc.sel("release"), .{});
|
self.queue.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
self.shaders.deinit();
|
if (self.custom_shader_state) |*state| state.deinit();
|
||||||
|
|
||||||
|
self.shaders.deinit(self.alloc);
|
||||||
|
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
@ -384,6 +470,13 @@ pub fn threadExit(self: *const Metal) void {
|
|||||||
// Metal requires no per-thread state.
|
// Metal requires no per-thread state.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True if our renderer has animations so that a higher frequency
|
||||||
|
/// timer is used.
|
||||||
|
pub fn hasAnimations(self: *const Metal) bool {
|
||||||
|
return self.custom_shader_state != null and
|
||||||
|
self.config.custom_shader_animation;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the grid size for a given screen size. This is safe to call
|
/// Returns the grid size for a given screen size. This is safe to call
|
||||||
/// on any thread.
|
/// on any thread.
|
||||||
fn gridSize(self: *Metal) ?renderer.GridSize {
|
fn gridSize(self: *Metal) ?renderer.GridSize {
|
||||||
@ -448,8 +541,8 @@ pub fn setFontSize(self: *Metal, size: font.face.DesiredSize) !void {
|
|||||||
}, .{ .forever = {} });
|
}, .{ .forever = {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The primary render callback that is completely thread-safe.
|
/// Update the frame data.
|
||||||
pub fn render(
|
pub fn updateFrame(
|
||||||
self: *Metal,
|
self: *Metal,
|
||||||
surface: *apprt.Surface,
|
surface: *apprt.Surface,
|
||||||
state: *renderer.State,
|
state: *renderer.State,
|
||||||
@ -535,10 +628,6 @@ pub fn render(
|
|||||||
};
|
};
|
||||||
defer critical.screen.deinit();
|
defer critical.screen.deinit();
|
||||||
|
|
||||||
// @autoreleasepool {}
|
|
||||||
const pool = objc.AutoreleasePool.init();
|
|
||||||
defer pool.deinit();
|
|
||||||
|
|
||||||
// Build our GPU cells
|
// Build our GPU cells
|
||||||
try self.rebuildCells(
|
try self.rebuildCells(
|
||||||
critical.selection,
|
critical.selection,
|
||||||
@ -547,18 +636,8 @@ pub fn render(
|
|||||||
critical.cursor_style,
|
critical.cursor_style,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get our drawable (CAMetalDrawable)
|
// Update our background color
|
||||||
const drawable = self.swapchain.msgSend(objc.Object, objc.sel("nextDrawable"), .{});
|
self.current_background_color = critical.bg;
|
||||||
|
|
||||||
// If our font atlas changed, sync the texture data
|
|
||||||
if (self.font_group.atlas_greyscale.modified) {
|
|
||||||
try syncAtlasTexture(self.device, &self.font_group.atlas_greyscale, &self.texture_greyscale);
|
|
||||||
self.font_group.atlas_greyscale.modified = false;
|
|
||||||
}
|
|
||||||
if (self.font_group.atlas_color.modified) {
|
|
||||||
try syncAtlasTexture(self.device, &self.font_group.atlas_color, &self.texture_color);
|
|
||||||
self.font_group.atlas_color.modified = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go through our images and see if we need to setup any textures.
|
// Go through our images and see if we need to setup any textures.
|
||||||
{
|
{
|
||||||
@ -580,6 +659,45 @@ pub fn render(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw the frame to the screen.
|
||||||
|
pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
|
||||||
|
_ = surface;
|
||||||
|
|
||||||
|
// If we have custom shaders, update the animation time.
|
||||||
|
if (self.custom_shader_state) |*state| {
|
||||||
|
const now = std.time.Instant.now() catch state.last_frame_time;
|
||||||
|
const since_ns: f32 = @floatFromInt(now.since(state.last_frame_time));
|
||||||
|
state.uniforms.time = since_ns / std.time.ns_per_s;
|
||||||
|
state.uniforms.time_delta = since_ns / std.time.ns_per_s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @autoreleasepool {}
|
||||||
|
const pool = objc.AutoreleasePool.init();
|
||||||
|
defer pool.deinit();
|
||||||
|
|
||||||
|
// Get our drawable (CAMetalDrawable)
|
||||||
|
const drawable = self.swapchain.msgSend(objc.Object, objc.sel("nextDrawable"), .{});
|
||||||
|
|
||||||
|
// Get our screen texture. If we don't have a dedicated screen texture
|
||||||
|
// then we just use the drawable texture.
|
||||||
|
const screen_texture = if (self.custom_shader_state) |state|
|
||||||
|
state.screen_texture
|
||||||
|
else tex: {
|
||||||
|
const texture = drawable.msgSend(objc.c.id, objc.sel("texture"), .{});
|
||||||
|
break :tex objc.Object.fromId(texture);
|
||||||
|
};
|
||||||
|
|
||||||
|
// If our font atlas changed, sync the texture data
|
||||||
|
if (self.font_group.atlas_greyscale.modified) {
|
||||||
|
try syncAtlasTexture(self.device, &self.font_group.atlas_greyscale, &self.texture_greyscale);
|
||||||
|
self.font_group.atlas_greyscale.modified = false;
|
||||||
|
}
|
||||||
|
if (self.font_group.atlas_color.modified) {
|
||||||
|
try syncAtlasTexture(self.device, &self.font_group.atlas_color, &self.texture_color);
|
||||||
|
self.font_group.atlas_color.modified = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Command buffer (MTLCommandBuffer)
|
// Command buffer (MTLCommandBuffer)
|
||||||
const buffer = self.queue.msgSend(objc.Object, objc.sel("commandBuffer"), .{});
|
const buffer = self.queue.msgSend(objc.Object, objc.sel("commandBuffer"), .{});
|
||||||
@ -587,7 +705,7 @@ pub fn render(
|
|||||||
{
|
{
|
||||||
// MTLRenderPassDescriptor
|
// MTLRenderPassDescriptor
|
||||||
const desc = desc: {
|
const desc = desc: {
|
||||||
const MTLRenderPassDescriptor = objc.Class.getClass("MTLRenderPassDescriptor").?;
|
const MTLRenderPassDescriptor = objc.getClass("MTLRenderPassDescriptor").?;
|
||||||
const desc = MTLRenderPassDescriptor.msgSend(
|
const desc = MTLRenderPassDescriptor.msgSend(
|
||||||
objc.Object,
|
objc.Object,
|
||||||
objc.sel("renderPassDescriptor"),
|
objc.sel("renderPassDescriptor"),
|
||||||
@ -607,14 +725,14 @@ pub fn render(
|
|||||||
// Ghostty in XCode in debug mode it returns a CaptureMTLDrawable
|
// Ghostty in XCode in debug mode it returns a CaptureMTLDrawable
|
||||||
// which ironically doesn't implement CAMetalDrawable as a
|
// which ironically doesn't implement CAMetalDrawable as a
|
||||||
// property so we just send a message.
|
// property so we just send a message.
|
||||||
const texture = drawable.msgSend(objc.c.id, objc.sel("texture"), .{});
|
//const texture = drawable.msgSend(objc.c.id, objc.sel("texture"), .{});
|
||||||
attachment.setProperty("loadAction", @intFromEnum(mtl.MTLLoadAction.clear));
|
attachment.setProperty("loadAction", @intFromEnum(mtl.MTLLoadAction.clear));
|
||||||
attachment.setProperty("storeAction", @intFromEnum(mtl.MTLStoreAction.store));
|
attachment.setProperty("storeAction", @intFromEnum(mtl.MTLStoreAction.store));
|
||||||
attachment.setProperty("texture", texture);
|
attachment.setProperty("texture", screen_texture.value);
|
||||||
attachment.setProperty("clearColor", mtl.MTLClearColor{
|
attachment.setProperty("clearColor", mtl.MTLClearColor{
|
||||||
.red = @as(f32, @floatFromInt(critical.bg.r)) / 255,
|
.red = @as(f32, @floatFromInt(self.current_background_color.r)) / 255,
|
||||||
.green = @as(f32, @floatFromInt(critical.bg.g)) / 255,
|
.green = @as(f32, @floatFromInt(self.current_background_color.g)) / 255,
|
||||||
.blue = @as(f32, @floatFromInt(critical.bg.b)) / 255,
|
.blue = @as(f32, @floatFromInt(self.current_background_color.b)) / 255,
|
||||||
.alpha = self.config.background_opacity,
|
.alpha = self.config.background_opacity,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -646,10 +764,118 @@ pub fn render(
|
|||||||
try self.drawImagePlacements(encoder, self.image_placements.items[self.image_text_end..]);
|
try self.drawImagePlacements(encoder, self.image_placements.items[self.image_text_end..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have custom shaders AND we have a screen texture, then we
|
||||||
|
// render the custom shaders.
|
||||||
|
if (self.custom_shader_state) |state| {
|
||||||
|
// MTLRenderPassDescriptor
|
||||||
|
const desc = desc: {
|
||||||
|
const MTLRenderPassDescriptor = objc.getClass("MTLRenderPassDescriptor").?;
|
||||||
|
const desc = MTLRenderPassDescriptor.msgSend(
|
||||||
|
objc.Object,
|
||||||
|
objc.sel("renderPassDescriptor"),
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set our color attachment to be our drawable surface.
|
||||||
|
const attachments = objc.Object.fromId(desc.getProperty(?*anyopaque, "colorAttachments"));
|
||||||
|
{
|
||||||
|
const attachment = attachments.msgSend(
|
||||||
|
objc.Object,
|
||||||
|
objc.sel("objectAtIndexedSubscript:"),
|
||||||
|
.{@as(c_ulong, 0)},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Texture is a property of CAMetalDrawable but if you run
|
||||||
|
// Ghostty in XCode in debug mode it returns a CaptureMTLDrawable
|
||||||
|
// which ironically doesn't implement CAMetalDrawable as a
|
||||||
|
// property so we just send a message.
|
||||||
|
const texture = drawable.msgSend(objc.c.id, objc.sel("texture"), .{});
|
||||||
|
attachment.setProperty("loadAction", @intFromEnum(mtl.MTLLoadAction.clear));
|
||||||
|
attachment.setProperty("storeAction", @intFromEnum(mtl.MTLStoreAction.store));
|
||||||
|
attachment.setProperty("texture", texture);
|
||||||
|
attachment.setProperty("clearColor", mtl.MTLClearColor{
|
||||||
|
.red = 0,
|
||||||
|
.green = 0,
|
||||||
|
.blue = 0,
|
||||||
|
.alpha = 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
break :desc desc;
|
||||||
|
};
|
||||||
|
|
||||||
|
// MTLRenderCommandEncoder
|
||||||
|
const encoder = buffer.msgSend(
|
||||||
|
objc.Object,
|
||||||
|
objc.sel("renderCommandEncoderWithDescriptor:"),
|
||||||
|
.{desc.value},
|
||||||
|
);
|
||||||
|
defer encoder.msgSend(void, objc.sel("endEncoding"), .{});
|
||||||
|
|
||||||
|
for (self.shaders.post_pipelines) |pipeline| {
|
||||||
|
try self.drawPostShader(encoder, pipeline, &state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buffer.msgSend(void, objc.sel("presentDrawable:"), .{drawable.value});
|
buffer.msgSend(void, objc.sel("presentDrawable:"), .{drawable.value});
|
||||||
buffer.msgSend(void, objc.sel("commit"), .{});
|
buffer.msgSend(void, objc.sel("commit"), .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn drawPostShader(
|
||||||
|
self: *Metal,
|
||||||
|
encoder: objc.Object,
|
||||||
|
pipeline: objc.Object,
|
||||||
|
state: *const CustomShaderState,
|
||||||
|
) !void {
|
||||||
|
_ = self;
|
||||||
|
|
||||||
|
// Use our custom shader pipeline
|
||||||
|
encoder.msgSend(
|
||||||
|
void,
|
||||||
|
objc.sel("setRenderPipelineState:"),
|
||||||
|
.{pipeline.value},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set our sampler
|
||||||
|
encoder.msgSend(
|
||||||
|
void,
|
||||||
|
objc.sel("setFragmentSamplerState:atIndex:"),
|
||||||
|
.{ state.sampler.sampler.value, @as(c_ulong, 0) },
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set our uniforms
|
||||||
|
encoder.msgSend(
|
||||||
|
void,
|
||||||
|
objc.sel("setFragmentBytes:length:atIndex:"),
|
||||||
|
.{
|
||||||
|
@as(*const anyopaque, @ptrCast(&state.uniforms)),
|
||||||
|
@as(c_ulong, @sizeOf(@TypeOf(state.uniforms))),
|
||||||
|
@as(c_ulong, 0),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Screen texture
|
||||||
|
encoder.msgSend(
|
||||||
|
void,
|
||||||
|
objc.sel("setFragmentTexture:atIndex:"),
|
||||||
|
.{
|
||||||
|
state.screen_texture.value,
|
||||||
|
@as(c_ulong, 0),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Draw!
|
||||||
|
encoder.msgSend(
|
||||||
|
void,
|
||||||
|
objc.sel("drawPrimitives:vertexStart:vertexCount:"),
|
||||||
|
.{
|
||||||
|
@intFromEnum(mtl.MTLPrimitiveType.triangle_strip),
|
||||||
|
@as(c_ulong, 0),
|
||||||
|
@as(c_ulong, 4),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn drawImagePlacements(
|
fn drawImagePlacements(
|
||||||
self: *Metal,
|
self: *Metal,
|
||||||
encoder: objc.Object,
|
encoder: objc.Object,
|
||||||
@ -1070,6 +1296,51 @@ pub fn setScreenSize(
|
|||||||
self.cells.clearAndFree(self.alloc);
|
self.cells.clearAndFree(self.alloc);
|
||||||
self.cells_bg.clearAndFree(self.alloc);
|
self.cells_bg.clearAndFree(self.alloc);
|
||||||
|
|
||||||
|
// If we have custom shaders then we update the state
|
||||||
|
if (self.custom_shader_state) |*state| {
|
||||||
|
// Only free our previous texture if this isn't our first
|
||||||
|
// time setting the custom shader state.
|
||||||
|
if (state.uniforms.resolution[0] > 0) {
|
||||||
|
deinitMTLResource(state.screen_texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.uniforms.resolution = .{
|
||||||
|
@floatFromInt(dim.width),
|
||||||
|
@floatFromInt(dim.height),
|
||||||
|
1,
|
||||||
|
};
|
||||||
|
|
||||||
|
state.screen_texture = screen_texture: {
|
||||||
|
// This texture is the size of our drawable but supports being a
|
||||||
|
// render target AND reading so that the custom shaders can read from it.
|
||||||
|
const desc = init: {
|
||||||
|
const Class = objc.getClass("MTLTextureDescriptor").?;
|
||||||
|
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
|
||||||
|
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
|
||||||
|
break :init id_init;
|
||||||
|
};
|
||||||
|
desc.setProperty("pixelFormat", @intFromEnum(mtl.MTLPixelFormat.bgra8unorm));
|
||||||
|
desc.setProperty("width", @as(c_ulong, @intCast(dim.width)));
|
||||||
|
desc.setProperty("height", @as(c_ulong, @intCast(dim.height)));
|
||||||
|
desc.setProperty(
|
||||||
|
"usage",
|
||||||
|
@intFromEnum(mtl.MTLTextureUsage.render_target) |
|
||||||
|
@intFromEnum(mtl.MTLTextureUsage.shader_read) |
|
||||||
|
@intFromEnum(mtl.MTLTextureUsage.shader_write),
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we fail to create the texture, then we just don't have a screen
|
||||||
|
// texture and our custom shaders won't run.
|
||||||
|
const id = self.device.msgSend(
|
||||||
|
?*anyopaque,
|
||||||
|
objc.sel("newTextureWithDescriptor:"),
|
||||||
|
.{desc},
|
||||||
|
) orelse return error.MetalFailed;
|
||||||
|
|
||||||
|
break :screen_texture objc.Object.fromId(id);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
log.debug("screen size screen={} grid={}, cell={}", .{ dim, grid_size, self.cell_size });
|
log.debug("screen size screen={} grid={}, cell={}", .{ dim, grid_size, self.cell_size });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1625,7 +1896,7 @@ fn initAtlasTexture(device: objc.Object, atlas: *const font.Atlas) !objc.Object
|
|||||||
|
|
||||||
// Create our descriptor
|
// Create our descriptor
|
||||||
const desc = init: {
|
const desc = init: {
|
||||||
const Class = objc.Class.getClass("MTLTextureDescriptor").?;
|
const Class = objc.getClass("MTLTextureDescriptor").?;
|
||||||
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
|
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
|
||||||
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
|
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
|
||||||
break :init id_init;
|
break :init id_init;
|
||||||
|
@ -7,6 +7,8 @@ const glfw = @import("glfw");
|
|||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
const shadertoy = @import("shadertoy.zig");
|
||||||
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");
|
||||||
@ -14,11 +16,14 @@ const imgui = @import("imgui");
|
|||||||
const renderer = @import("../renderer.zig");
|
const renderer = @import("../renderer.zig");
|
||||||
const terminal = @import("../terminal/main.zig");
|
const terminal = @import("../terminal/main.zig");
|
||||||
const Terminal = terminal.Terminal;
|
const Terminal = terminal.Terminal;
|
||||||
const gl = @import("opengl/main.zig");
|
const gl = @import("opengl");
|
||||||
const trace = @import("tracy").trace;
|
const trace = @import("tracy").trace;
|
||||||
const math = @import("../math.zig");
|
const math = @import("../math.zig");
|
||||||
const Surface = @import("../Surface.zig");
|
const Surface = @import("../Surface.zig");
|
||||||
|
|
||||||
|
const CellProgram = @import("opengl/CellProgram.zig");
|
||||||
|
const custom = @import("opengl/custom.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.grid);
|
const log = std.log.scoped(.grid);
|
||||||
|
|
||||||
/// The runtime can request a single-threaded draw by setting this boolean
|
/// The runtime can request a single-threaded draw by setting this boolean
|
||||||
@ -45,8 +50,8 @@ screen_size: ?renderer.ScreenSize,
|
|||||||
|
|
||||||
/// The current set of cells to render. Each set of cells goes into
|
/// The current set of cells to render. Each set of cells goes into
|
||||||
/// a separate shader call.
|
/// a separate shader call.
|
||||||
cells_bg: std.ArrayListUnmanaged(GPUCell),
|
cells_bg: std.ArrayListUnmanaged(CellProgram.Cell),
|
||||||
cells: std.ArrayListUnmanaged(GPUCell),
|
cells: std.ArrayListUnmanaged(CellProgram.Cell),
|
||||||
|
|
||||||
/// The size of the cells list that was sent to the GPU. This is used
|
/// The size of the cells list that was sent to the GPU. This is used
|
||||||
/// to detect when the cells array was reallocated/resized and handle that
|
/// to detect when the cells array was reallocated/resized and handle that
|
||||||
@ -102,8 +107,11 @@ draw_background: terminal.color.RGB,
|
|||||||
const SetScreenSize = struct {
|
const SetScreenSize = struct {
|
||||||
size: renderer.ScreenSize,
|
size: renderer.ScreenSize,
|
||||||
|
|
||||||
fn apply(self: SetScreenSize, r: *const OpenGL) !void {
|
fn apply(self: SetScreenSize, r: *OpenGL) !void {
|
||||||
const gl_state = r.gl_state orelse return error.OpenGLUninitialized;
|
const gl_state: *GLState = if (r.gl_state) |*v|
|
||||||
|
v
|
||||||
|
else
|
||||||
|
return error.OpenGLUninitialized;
|
||||||
|
|
||||||
// Apply our padding
|
// Apply our padding
|
||||||
const padding = if (r.padding.balance)
|
const padding = if (r.padding.balance)
|
||||||
@ -130,7 +138,7 @@ const SetScreenSize = struct {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Update the projection uniform within our shader
|
// Update the projection uniform within our shader
|
||||||
try gl_state.program.setUniform(
|
try gl_state.cell_program.program.setUniform(
|
||||||
"projection",
|
"projection",
|
||||||
|
|
||||||
// 2D orthographic projection with the full w/h
|
// 2D orthographic projection with the full w/h
|
||||||
@ -141,6 +149,11 @@ const SetScreenSize = struct {
|
|||||||
-1 * @as(f32, @floatFromInt(padding.top)),
|
-1 * @as(f32, @floatFromInt(padding.top)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Update our custom shader resolution
|
||||||
|
if (gl_state.custom) |*custom_state| {
|
||||||
|
try custom_state.setScreenSize(self.size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -150,84 +163,32 @@ const SetFontSize = struct {
|
|||||||
fn apply(self: SetFontSize, r: *const OpenGL) !void {
|
fn apply(self: SetFontSize, r: *const OpenGL) !void {
|
||||||
const gl_state = r.gl_state orelse return error.OpenGLUninitialized;
|
const gl_state = r.gl_state orelse return error.OpenGLUninitialized;
|
||||||
|
|
||||||
try gl_state.program.setUniform(
|
try gl_state.cell_program.program.setUniform(
|
||||||
"cell_size",
|
"cell_size",
|
||||||
@Vector(2, f32){
|
@Vector(2, f32){
|
||||||
@floatFromInt(self.metrics.cell_width),
|
@floatFromInt(self.metrics.cell_width),
|
||||||
@floatFromInt(self.metrics.cell_height),
|
@floatFromInt(self.metrics.cell_height),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
try gl_state.program.setUniform(
|
try gl_state.cell_program.program.setUniform(
|
||||||
"strikethrough_position",
|
"strikethrough_position",
|
||||||
@as(f32, @floatFromInt(self.metrics.strikethrough_position)),
|
@as(f32, @floatFromInt(self.metrics.strikethrough_position)),
|
||||||
);
|
);
|
||||||
try gl_state.program.setUniform(
|
try gl_state.cell_program.program.setUniform(
|
||||||
"strikethrough_thickness",
|
"strikethrough_thickness",
|
||||||
@as(f32, @floatFromInt(self.metrics.strikethrough_thickness)),
|
@as(f32, @floatFromInt(self.metrics.strikethrough_thickness)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The raw structure that maps directly to the buffer sent to the vertex shader.
|
|
||||||
/// This must be "extern" so that the field order is not reordered by the
|
|
||||||
/// Zig compiler.
|
|
||||||
const GPUCell = extern struct {
|
|
||||||
/// vec2 grid_coord
|
|
||||||
grid_col: u16,
|
|
||||||
grid_row: u16,
|
|
||||||
|
|
||||||
/// vec2 glyph_pos
|
|
||||||
glyph_x: u32 = 0,
|
|
||||||
glyph_y: u32 = 0,
|
|
||||||
|
|
||||||
/// vec2 glyph_size
|
|
||||||
glyph_width: u32 = 0,
|
|
||||||
glyph_height: u32 = 0,
|
|
||||||
|
|
||||||
/// vec2 glyph_size
|
|
||||||
glyph_offset_x: i32 = 0,
|
|
||||||
glyph_offset_y: i32 = 0,
|
|
||||||
|
|
||||||
/// vec4 fg_color_in
|
|
||||||
fg_r: u8,
|
|
||||||
fg_g: u8,
|
|
||||||
fg_b: u8,
|
|
||||||
fg_a: u8,
|
|
||||||
|
|
||||||
/// vec4 bg_color_in
|
|
||||||
bg_r: u8,
|
|
||||||
bg_g: u8,
|
|
||||||
bg_b: u8,
|
|
||||||
bg_a: u8,
|
|
||||||
|
|
||||||
/// uint mode
|
|
||||||
mode: GPUCellMode,
|
|
||||||
|
|
||||||
/// The width in grid cells that a rendering takes.
|
|
||||||
grid_width: u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
const GPUCellMode = enum(u8) {
|
|
||||||
bg = 1,
|
|
||||||
fg = 2,
|
|
||||||
fg_color = 7,
|
|
||||||
strikethrough = 8,
|
|
||||||
|
|
||||||
// Non-exhaustive because masks change it
|
|
||||||
_,
|
|
||||||
|
|
||||||
/// Apply a mask to the mode.
|
|
||||||
pub fn mask(self: GPUCellMode, m: GPUCellMode) GPUCellMode {
|
|
||||||
return @enumFromInt(@intFromEnum(self) | @intFromEnum(m));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The configuration for this renderer that is derived from the main
|
/// The configuration for this renderer that is derived from the main
|
||||||
/// 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_text: ?terminal.color.RGB,
|
cursor_text: ?terminal.color.RGB,
|
||||||
@ -238,17 +199,22 @@ 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),
|
||||||
|
custom_shader_animation: bool,
|
||||||
|
|
||||||
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);
|
||||||
@ -287,11 +253,16 @@ pub const DerivedConfig = struct {
|
|||||||
bg.toTerminalRGB()
|
bg.toTerminalRGB()
|
||||||
else
|
else
|
||||||
null,
|
null,
|
||||||
|
|
||||||
|
.custom_shaders = custom_shaders,
|
||||||
|
.custom_shader_animation = config.@"custom-shader-animation",
|
||||||
|
|
||||||
|
.arena = arena,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *DerivedConfig) void {
|
pub fn deinit(self: *DerivedConfig) void {
|
||||||
self.font_features.deinit();
|
self.arena.deinit();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -309,7 +280,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
|
|||||||
options.config.font_thicken,
|
options.config.font_thicken,
|
||||||
);
|
);
|
||||||
|
|
||||||
var gl_state = try GLState.init(options.font_group);
|
var gl_state = try GLState.init(alloc, options.config, options.font_group);
|
||||||
errdefer gl_state.deinit();
|
errdefer gl_state.deinit();
|
||||||
|
|
||||||
return OpenGL{
|
return OpenGL{
|
||||||
@ -336,7 +307,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
|
|||||||
pub fn deinit(self: *OpenGL) void {
|
pub fn deinit(self: *OpenGL) void {
|
||||||
self.font_shaper.deinit();
|
self.font_shaper.deinit();
|
||||||
|
|
||||||
if (self.gl_state) |*v| v.deinit();
|
if (self.gl_state) |*v| v.deinit(self.alloc);
|
||||||
|
|
||||||
self.cells.deinit(self.alloc);
|
self.cells.deinit(self.alloc);
|
||||||
self.cells_bg.deinit(self.alloc);
|
self.cells_bg.deinit(self.alloc);
|
||||||
@ -410,7 +381,7 @@ pub fn displayUnrealized(self: *OpenGL) void {
|
|||||||
defer if (single_threaded_draw) self.draw_mutex.unlock();
|
defer if (single_threaded_draw) self.draw_mutex.unlock();
|
||||||
|
|
||||||
if (self.gl_state) |*v| {
|
if (self.gl_state) |*v| {
|
||||||
v.deinit();
|
v.deinit(self.alloc);
|
||||||
self.gl_state = null;
|
self.gl_state = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -428,11 +399,11 @@ pub fn displayRealize(self: *OpenGL) !void {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Make our new state
|
// Make our new state
|
||||||
var gl_state = try GLState.init(self.font_group);
|
var gl_state = try GLState.init(self.alloc, self.config, self.font_group);
|
||||||
errdefer gl_state.deinit();
|
errdefer gl_state.deinit();
|
||||||
|
|
||||||
// Unrealize if we have to
|
// Unrealize if we have to
|
||||||
if (self.gl_state) |*v| v.deinit();
|
if (self.gl_state) |*v| v.deinit(self.alloc);
|
||||||
|
|
||||||
// Set our new state
|
// Set our new state
|
||||||
self.gl_state = gl_state;
|
self.gl_state = gl_state;
|
||||||
@ -506,6 +477,13 @@ pub fn threadExit(self: *const OpenGL) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True if our renderer has animations so that a higher frequency
|
||||||
|
/// timer is used.
|
||||||
|
pub fn hasAnimations(self: *const OpenGL) bool {
|
||||||
|
const state = self.gl_state orelse return false;
|
||||||
|
return state.custom != null and self.config.custom_shader_animation;
|
||||||
|
}
|
||||||
|
|
||||||
/// Callback when the focus changes for the terminal this is rendering.
|
/// Callback when the focus changes for the terminal this is rendering.
|
||||||
///
|
///
|
||||||
/// Must be called on the render thread.
|
/// Must be called on the render thread.
|
||||||
@ -576,12 +554,14 @@ fn resetFontMetrics(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The primary render callback that is completely thread-safe.
|
/// The primary render callback that is completely thread-safe.
|
||||||
pub fn render(
|
pub fn updateFrame(
|
||||||
self: *OpenGL,
|
self: *OpenGL,
|
||||||
surface: *apprt.Surface,
|
surface: *apprt.Surface,
|
||||||
state: *renderer.State,
|
state: *renderer.State,
|
||||||
cursor_blink_visible: bool,
|
cursor_blink_visible: bool,
|
||||||
) !void {
|
) !void {
|
||||||
|
_ = surface;
|
||||||
|
|
||||||
// Data we extract out of the critical area.
|
// Data we extract out of the critical area.
|
||||||
const Critical = struct {
|
const Critical = struct {
|
||||||
gl_bg: terminal.color.RGB,
|
gl_bg: terminal.color.RGB,
|
||||||
@ -669,19 +649,6 @@ pub fn render(
|
|||||||
critical.cursor_style,
|
critical.cursor_style,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're out of the critical path now. Let's render. We only render if
|
|
||||||
// we're not single threaded. If we're single threaded we expect the
|
|
||||||
// runtime to call draw.
|
|
||||||
if (single_threaded_draw) return;
|
|
||||||
|
|
||||||
try self.draw();
|
|
||||||
|
|
||||||
// Swap our window buffers
|
|
||||||
switch (apprt.runtime) {
|
|
||||||
else => @compileError("unsupported runtime"),
|
|
||||||
apprt.glfw => surface.window.swapBuffers(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// rebuildCells rebuilds all the GPU cells from our CPU state. This is a
|
/// rebuildCells rebuilds all the GPU cells from our CPU state. This is a
|
||||||
@ -735,7 +702,7 @@ pub fn rebuildCells(
|
|||||||
// This is the cell that has [mode == .fg] and is underneath our cursor.
|
// This is the cell that has [mode == .fg] and is underneath our cursor.
|
||||||
// We keep track of it so that we can invert the colors so the character
|
// We keep track of it so that we can invert the colors so the character
|
||||||
// remains visible.
|
// remains visible.
|
||||||
var cursor_cell: ?GPUCell = null;
|
var cursor_cell: ?CellProgram.Cell = null;
|
||||||
|
|
||||||
// Build each cell
|
// Build each cell
|
||||||
var rowIter = screen.rowIterator(.viewport);
|
var rowIter = screen.rowIterator(.viewport);
|
||||||
@ -868,15 +835,15 @@ pub fn rebuildCells(
|
|||||||
if (cursor_cell) |*cell| {
|
if (cursor_cell) |*cell| {
|
||||||
if (cell.mode == .fg) {
|
if (cell.mode == .fg) {
|
||||||
if (self.config.cursor_text) |txt| {
|
if (self.config.cursor_text) |txt| {
|
||||||
cell.fg_r = txt.r;
|
cell.r = txt.r;
|
||||||
cell.fg_g = txt.g;
|
cell.g = txt.g;
|
||||||
cell.fg_b = txt.b;
|
cell.b = txt.b;
|
||||||
cell.fg_a = 255;
|
cell.a = 255;
|
||||||
} else {
|
} else {
|
||||||
cell.fg_r = 0;
|
cell.r = 0;
|
||||||
cell.fg_g = 0;
|
cell.g = 0;
|
||||||
cell.fg_b = 0;
|
cell.b = 0;
|
||||||
cell.fg_a = 255;
|
cell.a = 255;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.cells.appendAssumeCapacity(cell.*);
|
self.cells.appendAssumeCapacity(cell.*);
|
||||||
@ -940,14 +907,10 @@ fn addPreeditCell(
|
|||||||
.glyph_height = 0,
|
.glyph_height = 0,
|
||||||
.glyph_offset_x = 0,
|
.glyph_offset_x = 0,
|
||||||
.glyph_offset_y = 0,
|
.glyph_offset_y = 0,
|
||||||
.fg_r = 0,
|
.r = bg.r,
|
||||||
.fg_g = 0,
|
.g = bg.g,
|
||||||
.fg_b = 0,
|
.b = bg.b,
|
||||||
.fg_a = 0,
|
.a = 255,
|
||||||
.bg_r = bg.r,
|
|
||||||
.bg_g = bg.g,
|
|
||||||
.bg_b = bg.b,
|
|
||||||
.bg_a = 255,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add our text
|
// Add our text
|
||||||
@ -962,14 +925,10 @@ fn addPreeditCell(
|
|||||||
.glyph_height = glyph.height,
|
.glyph_height = glyph.height,
|
||||||
.glyph_offset_x = glyph.offset_x,
|
.glyph_offset_x = glyph.offset_x,
|
||||||
.glyph_offset_y = glyph.offset_y,
|
.glyph_offset_y = glyph.offset_y,
|
||||||
.fg_r = fg.r,
|
.r = fg.r,
|
||||||
.fg_g = fg.g,
|
.g = fg.g,
|
||||||
.fg_b = fg.b,
|
.b = fg.b,
|
||||||
.fg_a = 255,
|
.a = 255,
|
||||||
.bg_r = 0,
|
|
||||||
.bg_g = 0,
|
|
||||||
.bg_b = 0,
|
|
||||||
.bg_a = 0,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -977,7 +936,7 @@ fn addCursor(
|
|||||||
self: *OpenGL,
|
self: *OpenGL,
|
||||||
screen: *terminal.Screen,
|
screen: *terminal.Screen,
|
||||||
cursor_style: renderer.CursorStyle,
|
cursor_style: renderer.CursorStyle,
|
||||||
) ?*const GPUCell {
|
) ?*const CellProgram.Cell {
|
||||||
// Add the cursor. We render the cursor over the wide character if
|
// Add the cursor. We render the cursor over the wide character if
|
||||||
// we're on the wide characer tail.
|
// we're on the wide characer tail.
|
||||||
const wide, const x = cell: {
|
const wide, const x = cell: {
|
||||||
@ -1027,14 +986,10 @@ fn addCursor(
|
|||||||
.grid_col = @intCast(x),
|
.grid_col = @intCast(x),
|
||||||
.grid_row = @intCast(screen.cursor.y),
|
.grid_row = @intCast(screen.cursor.y),
|
||||||
.grid_width = if (wide) 2 else 1,
|
.grid_width = if (wide) 2 else 1,
|
||||||
.fg_r = color.r,
|
.r = color.r,
|
||||||
.fg_g = color.g,
|
.g = color.g,
|
||||||
.fg_b = color.b,
|
.b = color.b,
|
||||||
.fg_a = alpha,
|
.a = alpha,
|
||||||
.bg_r = 0,
|
|
||||||
.bg_g = 0,
|
|
||||||
.bg_b = 0,
|
|
||||||
.bg_a = 0,
|
|
||||||
.glyph_x = glyph.atlas_x,
|
.glyph_x = glyph.atlas_x,
|
||||||
.glyph_y = glyph.atlas_y,
|
.glyph_y = glyph.atlas_y,
|
||||||
.glyph_width = glyph.width,
|
.glyph_width = glyph.width,
|
||||||
@ -1182,14 +1137,10 @@ pub fn updateCell(
|
|||||||
.glyph_height = 0,
|
.glyph_height = 0,
|
||||||
.glyph_offset_x = 0,
|
.glyph_offset_x = 0,
|
||||||
.glyph_offset_y = 0,
|
.glyph_offset_y = 0,
|
||||||
.fg_r = 0,
|
.r = rgb.r,
|
||||||
.fg_g = 0,
|
.g = rgb.g,
|
||||||
.fg_b = 0,
|
.b = rgb.b,
|
||||||
.fg_a = 0,
|
.a = bg_alpha,
|
||||||
.bg_r = rgb.r,
|
|
||||||
.bg_g = rgb.g,
|
|
||||||
.bg_b = rgb.b,
|
|
||||||
.bg_a = bg_alpha,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1208,7 +1159,7 @@ pub fn updateCell(
|
|||||||
|
|
||||||
// If we're rendering a color font, we use the color atlas
|
// If we're rendering a color font, we use the color atlas
|
||||||
const presentation = try self.font_group.group.presentationFromIndex(shaper_run.font_index);
|
const presentation = try self.font_group.group.presentationFromIndex(shaper_run.font_index);
|
||||||
const mode: GPUCellMode = switch (presentation) {
|
const mode: CellProgram.CellMode = switch (presentation) {
|
||||||
.text => .fg,
|
.text => .fg,
|
||||||
.emoji => .fg_color,
|
.emoji => .fg_color,
|
||||||
};
|
};
|
||||||
@ -1224,14 +1175,10 @@ pub fn updateCell(
|
|||||||
.glyph_height = glyph.height,
|
.glyph_height = glyph.height,
|
||||||
.glyph_offset_x = glyph.offset_x,
|
.glyph_offset_x = glyph.offset_x,
|
||||||
.glyph_offset_y = glyph.offset_y,
|
.glyph_offset_y = glyph.offset_y,
|
||||||
.fg_r = colors.fg.r,
|
.r = colors.fg.r,
|
||||||
.fg_g = colors.fg.g,
|
.g = colors.fg.g,
|
||||||
.fg_b = colors.fg.b,
|
.b = colors.fg.b,
|
||||||
.fg_a = alpha,
|
.a = alpha,
|
||||||
.bg_r = 0,
|
|
||||||
.bg_g = 0,
|
|
||||||
.bg_b = 0,
|
|
||||||
.bg_a = 0,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1265,14 +1212,10 @@ pub fn updateCell(
|
|||||||
.glyph_height = underline_glyph.height,
|
.glyph_height = underline_glyph.height,
|
||||||
.glyph_offset_x = underline_glyph.offset_x,
|
.glyph_offset_x = underline_glyph.offset_x,
|
||||||
.glyph_offset_y = underline_glyph.offset_y,
|
.glyph_offset_y = underline_glyph.offset_y,
|
||||||
.fg_r = color.r,
|
.r = color.r,
|
||||||
.fg_g = color.g,
|
.g = color.g,
|
||||||
.fg_b = color.b,
|
.b = color.b,
|
||||||
.fg_a = alpha,
|
.a = alpha,
|
||||||
.bg_r = 0,
|
|
||||||
.bg_g = 0,
|
|
||||||
.bg_b = 0,
|
|
||||||
.bg_a = 0,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1288,14 +1231,10 @@ pub fn updateCell(
|
|||||||
.glyph_height = 0,
|
.glyph_height = 0,
|
||||||
.glyph_offset_x = 0,
|
.glyph_offset_x = 0,
|
||||||
.glyph_offset_y = 0,
|
.glyph_offset_y = 0,
|
||||||
.fg_r = colors.fg.r,
|
.r = colors.fg.r,
|
||||||
.fg_g = colors.fg.g,
|
.g = colors.fg.g,
|
||||||
.fg_b = colors.fg.b,
|
.b = colors.fg.b,
|
||||||
.fg_a = alpha,
|
.a = alpha,
|
||||||
.bg_r = 0,
|
|
||||||
.bg_g = 0,
|
|
||||||
.bg_b = 0,
|
|
||||||
.bg_a = 0,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1388,11 +1327,11 @@ fn flushAtlas(self: *OpenGL) !void {
|
|||||||
atlas.resized = false;
|
atlas.resized = false;
|
||||||
try texbind.image2D(
|
try texbind.image2D(
|
||||||
0,
|
0,
|
||||||
.Red,
|
.red,
|
||||||
@intCast(atlas.size),
|
@intCast(atlas.size),
|
||||||
@intCast(atlas.size),
|
@intCast(atlas.size),
|
||||||
0,
|
0,
|
||||||
.Red,
|
.red,
|
||||||
.UnsignedByte,
|
.UnsignedByte,
|
||||||
atlas.data.ptr,
|
atlas.data.ptr,
|
||||||
);
|
);
|
||||||
@ -1403,7 +1342,7 @@ fn flushAtlas(self: *OpenGL) !void {
|
|||||||
0,
|
0,
|
||||||
@intCast(atlas.size),
|
@intCast(atlas.size),
|
||||||
@intCast(atlas.size),
|
@intCast(atlas.size),
|
||||||
.Red,
|
.red,
|
||||||
.UnsignedByte,
|
.UnsignedByte,
|
||||||
atlas.data.ptr,
|
atlas.data.ptr,
|
||||||
);
|
);
|
||||||
@ -1422,11 +1361,11 @@ fn flushAtlas(self: *OpenGL) !void {
|
|||||||
atlas.resized = false;
|
atlas.resized = false;
|
||||||
try texbind.image2D(
|
try texbind.image2D(
|
||||||
0,
|
0,
|
||||||
.RGBA,
|
.rgba,
|
||||||
@intCast(atlas.size),
|
@intCast(atlas.size),
|
||||||
@intCast(atlas.size),
|
@intCast(atlas.size),
|
||||||
0,
|
0,
|
||||||
.BGRA,
|
.bgra,
|
||||||
.UnsignedByte,
|
.UnsignedByte,
|
||||||
atlas.data.ptr,
|
atlas.data.ptr,
|
||||||
);
|
);
|
||||||
@ -1437,7 +1376,7 @@ fn flushAtlas(self: *OpenGL) !void {
|
|||||||
0,
|
0,
|
||||||
@intCast(atlas.size),
|
@intCast(atlas.size),
|
||||||
@intCast(atlas.size),
|
@intCast(atlas.size),
|
||||||
.BGRA,
|
.bgra,
|
||||||
.UnsignedByte,
|
.UnsignedByte,
|
||||||
atlas.data.ptr,
|
atlas.data.ptr,
|
||||||
);
|
);
|
||||||
@ -1448,19 +1387,71 @@ fn flushAtlas(self: *OpenGL) !void {
|
|||||||
|
|
||||||
/// Render renders the current cell state. This will not modify any of
|
/// Render renders the current cell state. This will not modify any of
|
||||||
/// the cells.
|
/// the cells.
|
||||||
pub fn draw(self: *OpenGL) !void {
|
pub fn drawFrame(self: *OpenGL, surface: *apprt.Surface) !void {
|
||||||
const t = trace(@src());
|
const t = trace(@src());
|
||||||
defer t.end();
|
defer t.end();
|
||||||
|
|
||||||
// If we're in single-threaded more we grab a lock since we use shared data.
|
// If we're in single-threaded more we grab a lock since we use shared data.
|
||||||
if (single_threaded_draw) self.draw_mutex.lock();
|
if (single_threaded_draw) self.draw_mutex.lock();
|
||||||
defer if (single_threaded_draw) self.draw_mutex.unlock();
|
defer if (single_threaded_draw) self.draw_mutex.unlock();
|
||||||
const gl_state = self.gl_state orelse return;
|
const gl_state: *GLState = if (self.gl_state) |*v| v else return;
|
||||||
|
|
||||||
|
// Draw our terminal cells
|
||||||
|
try self.drawCellProgram(gl_state);
|
||||||
|
|
||||||
|
// Draw our custom shaders
|
||||||
|
if (gl_state.custom) |*custom_state| {
|
||||||
|
try self.drawCustomPrograms(custom_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap our window buffers
|
||||||
|
switch (apprt.runtime) {
|
||||||
|
apprt.glfw => surface.window.swapBuffers(),
|
||||||
|
apprt.gtk => {},
|
||||||
|
else => @compileError("unsupported runtime"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw the custom shaders.
|
||||||
|
fn drawCustomPrograms(
|
||||||
|
self: *OpenGL,
|
||||||
|
custom_state: *custom.State,
|
||||||
|
) !void {
|
||||||
|
_ = self;
|
||||||
|
|
||||||
|
// Bind our state that is global to all custom shaders
|
||||||
|
const custom_bind = try custom_state.bind();
|
||||||
|
defer custom_bind.unbind();
|
||||||
|
|
||||||
|
// Setup the new frame
|
||||||
|
try custom_state.newFrame();
|
||||||
|
|
||||||
|
// Go through each custom shader and draw it.
|
||||||
|
for (custom_state.programs) |program| {
|
||||||
|
// Bind our cell program state, buffers
|
||||||
|
const bind = try program.bind();
|
||||||
|
defer bind.unbind();
|
||||||
|
try bind.draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs the cell program (shaders) to draw the terminal grid.
|
||||||
|
fn drawCellProgram(
|
||||||
|
self: *OpenGL,
|
||||||
|
gl_state: *const GLState,
|
||||||
|
) !void {
|
||||||
// Try to flush our atlas, this will only do something if there
|
// Try to flush our atlas, this will only do something if there
|
||||||
// are changes to the atlas.
|
// are changes to the atlas.
|
||||||
try self.flushAtlas();
|
try self.flushAtlas();
|
||||||
|
|
||||||
|
// If we have custom shaders, then we draw to the custom
|
||||||
|
// shader framebuffer.
|
||||||
|
const fbobind: ?gl.Framebuffer.Binding = fbobind: {
|
||||||
|
const state = gl_state.custom orelse break :fbobind null;
|
||||||
|
break :fbobind try state.fbo.bind(.framebuffer);
|
||||||
|
};
|
||||||
|
defer if (fbobind) |v| v.unbind();
|
||||||
|
|
||||||
// Clear the surface
|
// Clear the surface
|
||||||
gl.clearColor(
|
gl.clearColor(
|
||||||
@as(f32, @floatFromInt(self.draw_background.r)) / 255,
|
@as(f32, @floatFromInt(self.draw_background.r)) / 255,
|
||||||
@ -1470,17 +1461,9 @@ pub fn draw(self: *OpenGL) !void {
|
|||||||
);
|
);
|
||||||
gl.clear(gl.c.GL_COLOR_BUFFER_BIT);
|
gl.clear(gl.c.GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
// Setup our VAO
|
// Bind our cell program state, buffers
|
||||||
try gl_state.vao.bind();
|
const bind = try gl_state.cell_program.bind();
|
||||||
defer gl.VertexArray.unbind() catch null;
|
defer bind.unbind();
|
||||||
|
|
||||||
// Bind EBO
|
|
||||||
var ebobind = try gl_state.ebo.bind(.ElementArrayBuffer);
|
|
||||||
defer ebobind.unbind();
|
|
||||||
|
|
||||||
// Bind VBO and set data
|
|
||||||
var binding = try gl_state.vbo.bind(.ArrayBuffer);
|
|
||||||
defer binding.unbind();
|
|
||||||
|
|
||||||
// Bind our textures
|
// Bind our textures
|
||||||
try gl.Texture.active(gl.c.GL_TEXTURE0);
|
try gl.Texture.active(gl.c.GL_TEXTURE0);
|
||||||
@ -1491,10 +1474,6 @@ pub fn draw(self: *OpenGL) !void {
|
|||||||
var texbind1 = try gl_state.texture_color.bind(.@"2D");
|
var texbind1 = try gl_state.texture_color.bind(.@"2D");
|
||||||
defer texbind1.unbind();
|
defer texbind1.unbind();
|
||||||
|
|
||||||
// Pick our shader to use
|
|
||||||
const pbind = try gl_state.program.use();
|
|
||||||
defer pbind.unbind();
|
|
||||||
|
|
||||||
// If we have deferred operations, run them.
|
// If we have deferred operations, run them.
|
||||||
if (self.deferred_screen_size) |v| {
|
if (self.deferred_screen_size) |v| {
|
||||||
try v.apply(self);
|
try v.apply(self);
|
||||||
@ -1505,8 +1484,9 @@ pub fn draw(self: *OpenGL) !void {
|
|||||||
self.deferred_font_size = null;
|
self.deferred_font_size = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try self.drawCells(binding, self.cells_bg);
|
// Draw our background, then draw the fg on top of it.
|
||||||
try self.drawCells(binding, self.cells);
|
try self.drawCells(bind.vbo, self.cells_bg);
|
||||||
|
try self.drawCells(bind.vbo, self.cells);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads some set of cell data into our buffer and issues a draw call.
|
/// Loads some set of cell data into our buffer and issues a draw call.
|
||||||
@ -1517,7 +1497,7 @@ pub fn draw(self: *OpenGL) !void {
|
|||||||
fn drawCells(
|
fn drawCells(
|
||||||
self: *OpenGL,
|
self: *OpenGL,
|
||||||
binding: gl.Buffer.Binding,
|
binding: gl.Buffer.Binding,
|
||||||
cells: std.ArrayListUnmanaged(GPUCell),
|
cells: std.ArrayListUnmanaged(CellProgram.Cell),
|
||||||
) !void {
|
) !void {
|
||||||
// If we have no cells to render, then we render nothing.
|
// If we have no cells to render, then we render nothing.
|
||||||
if (cells.items.len == 0) return;
|
if (cells.items.len == 0) return;
|
||||||
@ -1534,8 +1514,8 @@ fn drawCells(
|
|||||||
});
|
});
|
||||||
|
|
||||||
try binding.setDataNullManual(
|
try binding.setDataNullManual(
|
||||||
@sizeOf(GPUCell) * cells.capacity,
|
@sizeOf(CellProgram.Cell) * cells.capacity,
|
||||||
.StaticDraw,
|
.static_draw,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.gl_cells_size = cells.capacity;
|
self.gl_cells_size = cells.capacity;
|
||||||
@ -1546,7 +1526,7 @@ fn drawCells(
|
|||||||
if (self.gl_cells_written < cells.items.len) {
|
if (self.gl_cells_written < cells.items.len) {
|
||||||
const data = cells.items[self.gl_cells_written..];
|
const data = cells.items[self.gl_cells_written..];
|
||||||
// log.info("sending {} cells to GPU", .{data.len});
|
// log.info("sending {} cells to GPU", .{data.len});
|
||||||
try binding.setSubData(self.gl_cells_written * @sizeOf(GPUCell), data);
|
try binding.setSubData(self.gl_cells_written * @sizeOf(CellProgram.Cell), data);
|
||||||
|
|
||||||
self.gl_cells_written += data.len;
|
self.gl_cells_written += data.len;
|
||||||
assert(data.len > 0);
|
assert(data.len > 0);
|
||||||
@ -1565,14 +1545,41 @@ fn drawCells(
|
|||||||
/// easy to create/destroy these as a set in situations i.e. where the
|
/// easy to create/destroy these as a set in situations i.e. where the
|
||||||
/// OpenGL context is replaced.
|
/// OpenGL context is replaced.
|
||||||
const GLState = struct {
|
const GLState = struct {
|
||||||
program: gl.Program,
|
cell_program: CellProgram,
|
||||||
vao: gl.VertexArray,
|
|
||||||
ebo: gl.Buffer,
|
|
||||||
vbo: gl.Buffer,
|
|
||||||
texture: gl.Texture,
|
texture: gl.Texture,
|
||||||
texture_color: gl.Texture,
|
texture_color: gl.Texture,
|
||||||
|
custom: ?custom.State,
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
alloc: Allocator,
|
||||||
|
config: DerivedConfig,
|
||||||
|
font_group: *font.GroupCache,
|
||||||
|
) !GLState {
|
||||||
|
var arena = ArenaAllocator.init(alloc);
|
||||||
|
defer arena.deinit();
|
||||||
|
const arena_alloc = arena.allocator();
|
||||||
|
|
||||||
|
// Load our custom shaders
|
||||||
|
const custom_state: ?custom.State = custom: {
|
||||||
|
const shaders: []const [:0]const u8 = shadertoy.loadFromFiles(
|
||||||
|
arena_alloc,
|
||||||
|
config.custom_shaders.items,
|
||||||
|
.glsl,
|
||||||
|
) catch |err| err: {
|
||||||
|
log.warn("error loading custom shaders err={}", .{err});
|
||||||
|
break :err &.{};
|
||||||
|
};
|
||||||
|
if (shaders.len == 0) break :custom null;
|
||||||
|
|
||||||
|
break :custom custom.State.init(
|
||||||
|
alloc,
|
||||||
|
shaders,
|
||||||
|
) catch |err| err: {
|
||||||
|
log.warn("error initializing custom shaders err={}", .{err});
|
||||||
|
break :err null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
pub fn init(font_group: *font.GroupCache) !GLState {
|
|
||||||
// Blending for text. We use GL_ONE here because we should be using
|
// Blending for text. We use GL_ONE here because we should be using
|
||||||
// premultiplied alpha for all our colors in our fragment shaders.
|
// premultiplied alpha for all our colors in our fragment shaders.
|
||||||
// This avoids having a blurry border where transparency is expected on
|
// This avoids having a blurry border where transparency is expected on
|
||||||
@ -1580,74 +1587,6 @@ const GLState = struct {
|
|||||||
try gl.enable(gl.c.GL_BLEND);
|
try gl.enable(gl.c.GL_BLEND);
|
||||||
try gl.blendFunc(gl.c.GL_ONE, gl.c.GL_ONE_MINUS_SRC_ALPHA);
|
try gl.blendFunc(gl.c.GL_ONE, gl.c.GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
// Shader
|
|
||||||
const program = try gl.Program.createVF(
|
|
||||||
@embedFile("shaders/cell.v.glsl"),
|
|
||||||
@embedFile("shaders/cell.f.glsl"),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set our cell dimensions
|
|
||||||
const pbind = try program.use();
|
|
||||||
defer pbind.unbind();
|
|
||||||
|
|
||||||
// Set all of our texture indexes
|
|
||||||
try program.setUniform("text", 0);
|
|
||||||
try program.setUniform("text_color", 1);
|
|
||||||
|
|
||||||
// Setup our VAO
|
|
||||||
const vao = try gl.VertexArray.create();
|
|
||||||
errdefer vao.destroy();
|
|
||||||
try vao.bind();
|
|
||||||
defer gl.VertexArray.unbind() catch null;
|
|
||||||
|
|
||||||
// Element buffer (EBO)
|
|
||||||
const ebo = try gl.Buffer.create();
|
|
||||||
errdefer ebo.destroy();
|
|
||||||
var ebobind = try ebo.bind(.ElementArrayBuffer);
|
|
||||||
defer ebobind.unbind();
|
|
||||||
try ebobind.setData([6]u8{
|
|
||||||
0, 1, 3, // Top-left triangle
|
|
||||||
1, 2, 3, // Bottom-right triangle
|
|
||||||
}, .StaticDraw);
|
|
||||||
|
|
||||||
// Vertex buffer (VBO)
|
|
||||||
const vbo = try gl.Buffer.create();
|
|
||||||
errdefer vbo.destroy();
|
|
||||||
var vbobind = try vbo.bind(.ArrayBuffer);
|
|
||||||
defer vbobind.unbind();
|
|
||||||
var offset: usize = 0;
|
|
||||||
try vbobind.attributeAdvanced(0, 2, gl.c.GL_UNSIGNED_SHORT, false, @sizeOf(GPUCell), offset);
|
|
||||||
offset += 2 * @sizeOf(u16);
|
|
||||||
try vbobind.attributeAdvanced(1, 2, gl.c.GL_UNSIGNED_INT, false, @sizeOf(GPUCell), offset);
|
|
||||||
offset += 2 * @sizeOf(u32);
|
|
||||||
try vbobind.attributeAdvanced(2, 2, gl.c.GL_UNSIGNED_INT, false, @sizeOf(GPUCell), offset);
|
|
||||||
offset += 2 * @sizeOf(u32);
|
|
||||||
try vbobind.attributeAdvanced(3, 2, gl.c.GL_INT, false, @sizeOf(GPUCell), offset);
|
|
||||||
offset += 2 * @sizeOf(i32);
|
|
||||||
try vbobind.attributeAdvanced(4, 4, gl.c.GL_UNSIGNED_BYTE, false, @sizeOf(GPUCell), offset);
|
|
||||||
offset += 4 * @sizeOf(u8);
|
|
||||||
try vbobind.attributeAdvanced(5, 4, gl.c.GL_UNSIGNED_BYTE, false, @sizeOf(GPUCell), offset);
|
|
||||||
offset += 4 * @sizeOf(u8);
|
|
||||||
try vbobind.attributeIAdvanced(6, 1, gl.c.GL_UNSIGNED_BYTE, @sizeOf(GPUCell), offset);
|
|
||||||
offset += 1 * @sizeOf(u8);
|
|
||||||
try vbobind.attributeIAdvanced(7, 1, gl.c.GL_UNSIGNED_BYTE, @sizeOf(GPUCell), offset);
|
|
||||||
try vbobind.enableAttribArray(0);
|
|
||||||
try vbobind.enableAttribArray(1);
|
|
||||||
try vbobind.enableAttribArray(2);
|
|
||||||
try vbobind.enableAttribArray(3);
|
|
||||||
try vbobind.enableAttribArray(4);
|
|
||||||
try vbobind.enableAttribArray(5);
|
|
||||||
try vbobind.enableAttribArray(6);
|
|
||||||
try vbobind.enableAttribArray(7);
|
|
||||||
try vbobind.attributeDivisor(0, 1);
|
|
||||||
try vbobind.attributeDivisor(1, 1);
|
|
||||||
try vbobind.attributeDivisor(2, 1);
|
|
||||||
try vbobind.attributeDivisor(3, 1);
|
|
||||||
try vbobind.attributeDivisor(4, 1);
|
|
||||||
try vbobind.attributeDivisor(5, 1);
|
|
||||||
try vbobind.attributeDivisor(6, 1);
|
|
||||||
try vbobind.attributeDivisor(7, 1);
|
|
||||||
|
|
||||||
// Build our texture
|
// Build our texture
|
||||||
const tex = try gl.Texture.create();
|
const tex = try gl.Texture.create();
|
||||||
errdefer tex.destroy();
|
errdefer tex.destroy();
|
||||||
@ -1659,11 +1598,11 @@ const GLState = struct {
|
|||||||
try texbind.parameter(.MagFilter, gl.c.GL_LINEAR);
|
try texbind.parameter(.MagFilter, gl.c.GL_LINEAR);
|
||||||
try texbind.image2D(
|
try texbind.image2D(
|
||||||
0,
|
0,
|
||||||
.Red,
|
.red,
|
||||||
@intCast(font_group.atlas_greyscale.size),
|
@intCast(font_group.atlas_greyscale.size),
|
||||||
@intCast(font_group.atlas_greyscale.size),
|
@intCast(font_group.atlas_greyscale.size),
|
||||||
0,
|
0,
|
||||||
.Red,
|
.red,
|
||||||
.UnsignedByte,
|
.UnsignedByte,
|
||||||
font_group.atlas_greyscale.data.ptr,
|
font_group.atlas_greyscale.data.ptr,
|
||||||
);
|
);
|
||||||
@ -1680,32 +1619,32 @@ const GLState = struct {
|
|||||||
try texbind.parameter(.MagFilter, gl.c.GL_LINEAR);
|
try texbind.parameter(.MagFilter, gl.c.GL_LINEAR);
|
||||||
try texbind.image2D(
|
try texbind.image2D(
|
||||||
0,
|
0,
|
||||||
.RGBA,
|
.rgba,
|
||||||
@intCast(font_group.atlas_color.size),
|
@intCast(font_group.atlas_color.size),
|
||||||
@intCast(font_group.atlas_color.size),
|
@intCast(font_group.atlas_color.size),
|
||||||
0,
|
0,
|
||||||
.BGRA,
|
.bgra,
|
||||||
.UnsignedByte,
|
.UnsignedByte,
|
||||||
font_group.atlas_color.data.ptr,
|
font_group.atlas_color.data.ptr,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build our cell renderer
|
||||||
|
const cell_program = try CellProgram.init();
|
||||||
|
errdefer cell_program.deinit();
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.program = program,
|
.cell_program = cell_program,
|
||||||
.vao = vao,
|
|
||||||
.ebo = ebo,
|
|
||||||
.vbo = vbo,
|
|
||||||
.texture = tex,
|
.texture = tex,
|
||||||
.texture_color = tex_color,
|
.texture_color = tex_color,
|
||||||
|
.custom = custom_state,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *GLState) void {
|
pub fn deinit(self: *GLState, alloc: Allocator) void {
|
||||||
|
if (self.custom) |v| v.deinit(alloc);
|
||||||
self.texture.destroy();
|
self.texture.destroy();
|
||||||
self.texture_color.destroy();
|
self.texture_color.destroy();
|
||||||
self.vbo.destroy();
|
self.cell_program.deinit();
|
||||||
self.ebo.destroy();
|
|
||||||
self.vao.destroy();
|
|
||||||
self.program.destroy();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -15,6 +15,7 @@ const App = @import("../App.zig");
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const log = std.log.scoped(.renderer_thread);
|
const log = std.log.scoped(.renderer_thread);
|
||||||
|
|
||||||
|
const DRAW_INTERVAL = 33; // 30 FPS
|
||||||
const CURSOR_BLINK_INTERVAL = 600;
|
const CURSOR_BLINK_INTERVAL = 600;
|
||||||
|
|
||||||
/// The type used for sending messages to the IO thread. For now this is
|
/// The type used for sending messages to the IO thread. For now this is
|
||||||
@ -43,6 +44,13 @@ stop_c: xev.Completion = .{},
|
|||||||
render_h: xev.Timer,
|
render_h: xev.Timer,
|
||||||
render_c: xev.Completion = .{},
|
render_c: xev.Completion = .{},
|
||||||
|
|
||||||
|
/// The timer used for draw calls. Draw calls don't update from the
|
||||||
|
/// terminal state so they're much cheaper. They're used for animation
|
||||||
|
/// and are paused when the terminal is not focused.
|
||||||
|
draw_h: xev.Timer,
|
||||||
|
draw_c: xev.Completion = .{},
|
||||||
|
draw_active: bool = false,
|
||||||
|
|
||||||
/// The timer used for cursor blinking
|
/// The timer used for cursor blinking
|
||||||
cursor_h: xev.Timer,
|
cursor_h: xev.Timer,
|
||||||
cursor_c: xev.Completion = .{},
|
cursor_c: xev.Completion = .{},
|
||||||
@ -100,6 +108,10 @@ pub fn init(
|
|||||||
var render_h = try xev.Timer.init();
|
var render_h = try xev.Timer.init();
|
||||||
errdefer render_h.deinit();
|
errdefer render_h.deinit();
|
||||||
|
|
||||||
|
// Draw timer, see comments.
|
||||||
|
var draw_h = try xev.Timer.init();
|
||||||
|
errdefer draw_h.deinit();
|
||||||
|
|
||||||
// Setup a timer for blinking the cursor
|
// Setup a timer for blinking the cursor
|
||||||
var cursor_timer = try xev.Timer.init();
|
var cursor_timer = try xev.Timer.init();
|
||||||
errdefer cursor_timer.deinit();
|
errdefer cursor_timer.deinit();
|
||||||
@ -114,6 +126,7 @@ pub fn init(
|
|||||||
.wakeup = wakeup_h,
|
.wakeup = wakeup_h,
|
||||||
.stop = stop_h,
|
.stop = stop_h,
|
||||||
.render_h = render_h,
|
.render_h = render_h,
|
||||||
|
.draw_h = draw_h,
|
||||||
.cursor_h = cursor_timer,
|
.cursor_h = cursor_timer,
|
||||||
.surface = surface,
|
.surface = surface,
|
||||||
.renderer = renderer_impl,
|
.renderer = renderer_impl,
|
||||||
@ -129,6 +142,7 @@ pub fn deinit(self: *Thread) void {
|
|||||||
self.stop.deinit();
|
self.stop.deinit();
|
||||||
self.wakeup.deinit();
|
self.wakeup.deinit();
|
||||||
self.render_h.deinit();
|
self.render_h.deinit();
|
||||||
|
self.draw_h.deinit();
|
||||||
self.cursor_h.deinit();
|
self.cursor_h.deinit();
|
||||||
self.loop.deinit();
|
self.loop.deinit();
|
||||||
|
|
||||||
@ -172,27 +186,8 @@ fn threadMain_(self: *Thread) !void {
|
|||||||
cursorTimerCallback,
|
cursorTimerCallback,
|
||||||
);
|
);
|
||||||
|
|
||||||
// If we are using tracy, then we setup a prepare handle so that
|
// Start the draw timer
|
||||||
// we can mark the frame.
|
self.startDrawTimer();
|
||||||
// TODO
|
|
||||||
// var frame_h: libuv.Prepare = if (!tracy.enabled) undefined else frame_h: {
|
|
||||||
// const alloc_ptr = self.loop.getData(Allocator).?;
|
|
||||||
// const alloc = alloc_ptr.*;
|
|
||||||
// const h = try libuv.Prepare.init(alloc, self.loop);
|
|
||||||
// h.setData(self);
|
|
||||||
// try h.start(prepFrameCallback);
|
|
||||||
//
|
|
||||||
// break :frame_h h;
|
|
||||||
// };
|
|
||||||
// defer if (tracy.enabled) {
|
|
||||||
// frame_h.close((struct {
|
|
||||||
// fn callback(h: *libuv.Prepare) void {
|
|
||||||
// const alloc_h = h.loop().getData(Allocator).?.*;
|
|
||||||
// h.deinit(alloc_h);
|
|
||||||
// }
|
|
||||||
// }).callback);
|
|
||||||
// _ = self.loop.run(.nowait) catch {};
|
|
||||||
// };
|
|
||||||
|
|
||||||
// Run
|
// Run
|
||||||
log.debug("starting renderer thread", .{});
|
log.debug("starting renderer thread", .{});
|
||||||
@ -200,6 +195,34 @@ fn threadMain_(self: *Thread) !void {
|
|||||||
_ = try self.loop.run(.until_done);
|
_ = try self.loop.run(.until_done);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn startDrawTimer(self: *Thread) void {
|
||||||
|
// If our renderer doesn't suppoort animations then we never run this.
|
||||||
|
if (!@hasDecl(renderer.Renderer, "hasAnimations")) return;
|
||||||
|
if (!self.renderer.hasAnimations()) return;
|
||||||
|
|
||||||
|
// Set our active state so it knows we're running. We set this before
|
||||||
|
// even checking the active state in case we have a pending shutdown.
|
||||||
|
self.draw_active = true;
|
||||||
|
|
||||||
|
// If our draw timer is already active, then we don't have to do anything.
|
||||||
|
if (self.draw_c.state() == .active) return;
|
||||||
|
|
||||||
|
// Start the timer which loops
|
||||||
|
self.draw_h.run(
|
||||||
|
&self.loop,
|
||||||
|
&self.draw_c,
|
||||||
|
DRAW_INTERVAL,
|
||||||
|
Thread,
|
||||||
|
self,
|
||||||
|
drawCallback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stopDrawTimer(self: *Thread) void {
|
||||||
|
// This will stop the draw on the next iteration.
|
||||||
|
self.draw_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
/// Drain the mailbox.
|
/// Drain the mailbox.
|
||||||
fn drainMailbox(self: *Thread) !void {
|
fn drainMailbox(self: *Thread) !void {
|
||||||
const zone = trace(@src());
|
const zone = trace(@src());
|
||||||
@ -213,6 +236,9 @@ fn drainMailbox(self: *Thread) !void {
|
|||||||
try self.renderer.setFocus(v);
|
try self.renderer.setFocus(v);
|
||||||
|
|
||||||
if (!v) {
|
if (!v) {
|
||||||
|
// Stop the draw timer
|
||||||
|
self.stopDrawTimer();
|
||||||
|
|
||||||
// If we're not focused, then we stop the cursor blink
|
// If we're not focused, then we stop the cursor blink
|
||||||
if (self.cursor_c.state() == .active and
|
if (self.cursor_c.state() == .active and
|
||||||
self.cursor_c_cancel.state() == .dead)
|
self.cursor_c_cancel.state() == .dead)
|
||||||
@ -227,6 +253,9 @@ fn drainMailbox(self: *Thread) !void {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Start the draw timer
|
||||||
|
self.startDrawTimer();
|
||||||
|
|
||||||
// If we're focused, we immediately show the cursor again
|
// If we're focused, we immediately show the cursor again
|
||||||
// and then restart the timer.
|
// and then restart the timer.
|
||||||
if (self.cursor_c.state() != .active) {
|
if (self.cursor_c.state() != .active) {
|
||||||
@ -281,6 +310,11 @@ fn drainMailbox(self: *Thread) !void {
|
|||||||
.change_config => |config| {
|
.change_config => |config| {
|
||||||
defer config.alloc.destroy(config.ptr);
|
defer config.alloc.destroy(config.ptr);
|
||||||
try self.renderer.changeConfig(config.ptr);
|
try self.renderer.changeConfig(config.ptr);
|
||||||
|
|
||||||
|
// Stop and start the draw timer to capture the new
|
||||||
|
// hasAnimations value.
|
||||||
|
self.stopDrawTimer();
|
||||||
|
self.startDrawTimer();
|
||||||
},
|
},
|
||||||
|
|
||||||
.inspector => |v| self.flags.has_inspector = v,
|
.inspector => |v| self.flags.has_inspector = v,
|
||||||
@ -325,6 +359,41 @@ fn wakeupCallback(
|
|||||||
return .rearm;
|
return .rearm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn drawCallback(
|
||||||
|
self_: ?*Thread,
|
||||||
|
_: *xev.Loop,
|
||||||
|
_: *xev.Completion,
|
||||||
|
r: xev.Timer.RunError!void,
|
||||||
|
) xev.CallbackAction {
|
||||||
|
_ = r catch unreachable;
|
||||||
|
const t = self_ orelse {
|
||||||
|
// This shouldn't happen so we log it.
|
||||||
|
log.warn("render callback fired without data set", .{});
|
||||||
|
return .disarm;
|
||||||
|
};
|
||||||
|
|
||||||
|
// If we're doing single-threaded GPU calls then we just wake up the
|
||||||
|
// app thread to redraw at this point.
|
||||||
|
if (renderer.Renderer == renderer.OpenGL and
|
||||||
|
renderer.OpenGL.single_threaded_draw)
|
||||||
|
{
|
||||||
|
_ = t.app_mailbox.push(
|
||||||
|
.{ .redraw_surface = t.surface },
|
||||||
|
.{ .instant = {} },
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
t.renderer.drawFrame(t.surface) catch |err|
|
||||||
|
log.warn("error drawing err={}", .{err});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only continue if we're still active
|
||||||
|
if (t.draw_active) {
|
||||||
|
t.draw_h.run(&t.loop, &t.draw_c, DRAW_INTERVAL, Thread, t, drawCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
return .disarm;
|
||||||
|
}
|
||||||
|
|
||||||
fn renderCallback(
|
fn renderCallback(
|
||||||
self_: ?*Thread,
|
self_: ?*Thread,
|
||||||
_: *xev.Loop,
|
_: *xev.Loop,
|
||||||
@ -346,7 +415,8 @@ fn renderCallback(
|
|||||||
_ = t.app_mailbox.push(.{ .redraw_inspector = t.surface }, .{ .instant = {} });
|
_ = t.app_mailbox.push(.{ .redraw_inspector = t.surface }, .{ .instant = {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
t.renderer.render(
|
// Update our frame data
|
||||||
|
t.renderer.updateFrame(
|
||||||
t.surface,
|
t.surface,
|
||||||
t.state,
|
t.state,
|
||||||
t.flags.cursor_blink_visible,
|
t.flags.cursor_blink_visible,
|
||||||
@ -359,8 +429,13 @@ fn renderCallback(
|
|||||||
renderer.OpenGL.single_threaded_draw)
|
renderer.OpenGL.single_threaded_draw)
|
||||||
{
|
{
|
||||||
_ = t.app_mailbox.push(.{ .redraw_surface = t.surface }, .{ .instant = {} });
|
_ = t.app_mailbox.push(.{ .redraw_surface = t.surface }, .{ .instant = {} });
|
||||||
|
return .disarm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
t.renderer.drawFrame(t.surface) catch |err|
|
||||||
|
log.warn("error drawing err={}", .{err});
|
||||||
|
|
||||||
return .disarm;
|
return .disarm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +57,7 @@ pub const MTLVertexStepFunction = enum(c_ulong) {
|
|||||||
/// https://developer.apple.com/documentation/metal/mtlpixelformat?language=objc
|
/// https://developer.apple.com/documentation/metal/mtlpixelformat?language=objc
|
||||||
pub const MTLPixelFormat = enum(c_ulong) {
|
pub const MTLPixelFormat = enum(c_ulong) {
|
||||||
r8unorm = 10,
|
r8unorm = 10,
|
||||||
|
rgba8unorm = 70,
|
||||||
rgba8uint = 73,
|
rgba8uint = 73,
|
||||||
bgra8unorm = 80,
|
bgra8unorm = 80,
|
||||||
};
|
};
|
||||||
@ -66,6 +67,22 @@ pub const MTLPurgeableState = enum(c_ulong) {
|
|||||||
empty = 4,
|
empty = 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// https://developer.apple.com/documentation/metal/mtlsamplerminmagfilter?language=objc
|
||||||
|
pub const MTLSamplerMinMagFilter = enum(c_ulong) {
|
||||||
|
nearest = 0,
|
||||||
|
linear = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// https://developer.apple.com/documentation/metal/mtlsampleraddressmode?language=objc
|
||||||
|
pub const MTLSamplerAddressMode = enum(c_ulong) {
|
||||||
|
clamp_to_edge = 0,
|
||||||
|
mirror_clamp_to_edge = 1,
|
||||||
|
repeat = 2,
|
||||||
|
mirror_repeat = 3,
|
||||||
|
clamp_to_zero = 4,
|
||||||
|
clamp_to_border_color = 5,
|
||||||
|
};
|
||||||
|
|
||||||
/// https://developer.apple.com/documentation/metal/mtlblendfactor?language=objc
|
/// https://developer.apple.com/documentation/metal/mtlblendfactor?language=objc
|
||||||
pub const MTLBlendFactor = enum(c_ulong) {
|
pub const MTLBlendFactor = enum(c_ulong) {
|
||||||
zero = 0,
|
zero = 0,
|
||||||
@ -98,6 +115,15 @@ pub const MTLBlendOperation = enum(c_ulong) {
|
|||||||
max = 4,
|
max = 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// https://developer.apple.com/documentation/metal/mtltextureusage?language=objc<D-j>
|
||||||
|
pub const MTLTextureUsage = enum(c_ulong) {
|
||||||
|
unknown = 0,
|
||||||
|
shader_read = 1,
|
||||||
|
shader_write = 2,
|
||||||
|
render_target = 4,
|
||||||
|
pixel_format_view = 8,
|
||||||
|
};
|
||||||
|
|
||||||
/// https://developer.apple.com/documentation/metal/mtlresourceoptions?language=objc
|
/// https://developer.apple.com/documentation/metal/mtlresourceoptions?language=objc
|
||||||
/// (incomplete, we only use this mode so we just hardcode it)
|
/// (incomplete, we only use this mode so we just hardcode it)
|
||||||
pub const MTLResourceStorageModeShared: c_ulong = @intFromEnum(MTLStorageMode.shared) << 4;
|
pub const MTLResourceStorageModeShared: c_ulong = @intFromEnum(MTLStorageMode.shared) << 4;
|
||||||
|
@ -218,7 +218,7 @@ pub const Image = union(enum) {
|
|||||||
fn initTexture(p: Pending, device: objc.Object) !objc.Object {
|
fn initTexture(p: Pending, device: objc.Object) !objc.Object {
|
||||||
// Create our descriptor
|
// Create our descriptor
|
||||||
const desc = init: {
|
const desc = init: {
|
||||||
const Class = objc.Class.getClass("MTLTextureDescriptor").?;
|
const Class = objc.getClass("MTLTextureDescriptor").?;
|
||||||
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
|
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
|
||||||
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
|
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
|
||||||
break :init id_init;
|
break :init id_init;
|
||||||
|
38
src/renderer/metal/sampler.zig
Normal file
38
src/renderer/metal/sampler.zig
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const objc = @import("objc");
|
||||||
|
|
||||||
|
const mtl = @import("api.zig");
|
||||||
|
|
||||||
|
pub const Sampler = struct {
|
||||||
|
sampler: objc.Object,
|
||||||
|
|
||||||
|
pub fn init(device: objc.Object) !Sampler {
|
||||||
|
const desc = init: {
|
||||||
|
const Class = objc.getClass("MTLSamplerDescriptor").?;
|
||||||
|
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
|
||||||
|
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
|
||||||
|
break :init id_init;
|
||||||
|
};
|
||||||
|
defer desc.msgSend(void, objc.sel("release"), .{});
|
||||||
|
desc.setProperty("rAddressMode", @intFromEnum(mtl.MTLSamplerAddressMode.clamp_to_edge));
|
||||||
|
desc.setProperty("sAddressMode", @intFromEnum(mtl.MTLSamplerAddressMode.clamp_to_edge));
|
||||||
|
desc.setProperty("tAddressMode", @intFromEnum(mtl.MTLSamplerAddressMode.clamp_to_edge));
|
||||||
|
desc.setProperty("minFilter", @intFromEnum(mtl.MTLSamplerMinMagFilter.linear));
|
||||||
|
desc.setProperty("magFilter", @intFromEnum(mtl.MTLSamplerMinMagFilter.linear));
|
||||||
|
|
||||||
|
const sampler = device.msgSend(
|
||||||
|
objc.Object,
|
||||||
|
objc.sel("newSamplerStateWithDescriptor:"),
|
||||||
|
.{desc},
|
||||||
|
);
|
||||||
|
errdefer sampler.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
|
return .{ .sampler = sampler };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Sampler) void {
|
||||||
|
self.sampler.msgSend(void, objc.sel("release"), .{});
|
||||||
|
}
|
||||||
|
};
|
@ -12,10 +12,31 @@ const log = std.log.scoped(.metal);
|
|||||||
/// This contains the state for the shaders used by the Metal renderer.
|
/// This contains the state for the shaders used by the Metal renderer.
|
||||||
pub const Shaders = struct {
|
pub const Shaders = struct {
|
||||||
library: objc.Object,
|
library: objc.Object,
|
||||||
|
|
||||||
|
/// The cell shader is the shader used to render the terminal cells.
|
||||||
|
/// It is a single shader that is used for both the background and
|
||||||
|
/// foreground.
|
||||||
cell_pipeline: objc.Object,
|
cell_pipeline: objc.Object,
|
||||||
|
|
||||||
|
/// The image shader is the shader used to render images for things
|
||||||
|
/// like the Kitty image protocol.
|
||||||
image_pipeline: objc.Object,
|
image_pipeline: objc.Object,
|
||||||
|
|
||||||
pub fn init(device: objc.Object) !Shaders {
|
/// Custom shaders to run against the final drawable texture. This
|
||||||
|
/// can be used to apply a lot of effects. Each shader is run in sequence
|
||||||
|
/// against the output of the previous shader.
|
||||||
|
post_pipelines: []const objc.Object,
|
||||||
|
|
||||||
|
/// Initialize our shader set.
|
||||||
|
///
|
||||||
|
/// "post_shaders" is an optional list of postprocess shaders to run
|
||||||
|
/// against the final drawable texture. This is an array of shader source
|
||||||
|
/// code, not file paths.
|
||||||
|
pub fn init(
|
||||||
|
alloc: Allocator,
|
||||||
|
device: objc.Object,
|
||||||
|
post_shaders: []const [:0]const u8,
|
||||||
|
) !Shaders {
|
||||||
const library = try initLibrary(device);
|
const library = try initLibrary(device);
|
||||||
errdefer library.msgSend(void, objc.sel("release"), .{});
|
errdefer library.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
@ -25,17 +46,44 @@ pub const Shaders = struct {
|
|||||||
const image_pipeline = try initImagePipeline(device, library);
|
const image_pipeline = try initImagePipeline(device, library);
|
||||||
errdefer image_pipeline.msgSend(void, objc.sel("release"), .{});
|
errdefer image_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
|
const post_pipelines: []const objc.Object = initPostPipelines(
|
||||||
|
alloc,
|
||||||
|
device,
|
||||||
|
library,
|
||||||
|
post_shaders,
|
||||||
|
) catch |err| err: {
|
||||||
|
// If an error happens while building postprocess shaders we
|
||||||
|
// want to just not use any postprocess shaders since we don't
|
||||||
|
// want to block Ghostty from working.
|
||||||
|
log.warn("error initializing postprocess shaders err={}", .{err});
|
||||||
|
break :err &.{};
|
||||||
|
};
|
||||||
|
errdefer if (post_pipelines.len > 0) {
|
||||||
|
for (post_pipelines) |pipeline| pipeline.msgSend(void, objc.sel("release"), .{});
|
||||||
|
alloc.free(post_pipelines);
|
||||||
|
};
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.library = library,
|
.library = library,
|
||||||
.cell_pipeline = cell_pipeline,
|
.cell_pipeline = cell_pipeline,
|
||||||
.image_pipeline = image_pipeline,
|
.image_pipeline = image_pipeline,
|
||||||
|
.post_pipelines = post_pipelines,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Shaders) void {
|
pub fn deinit(self: *Shaders, alloc: Allocator) void {
|
||||||
|
// Release our primary shaders
|
||||||
self.cell_pipeline.msgSend(void, objc.sel("release"), .{});
|
self.cell_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||||
self.image_pipeline.msgSend(void, objc.sel("release"), .{});
|
self.image_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||||
self.library.msgSend(void, objc.sel("release"), .{});
|
self.library.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
|
// Release our postprocess shaders
|
||||||
|
if (self.post_pipelines.len > 0) {
|
||||||
|
for (self.post_pipelines) |pipeline| {
|
||||||
|
pipeline.msgSend(void, objc.sel("release"), .{});
|
||||||
|
}
|
||||||
|
alloc.free(self.post_pipelines);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -79,6 +127,23 @@ pub const Uniforms = extern struct {
|
|||||||
strikethrough_thickness: f32,
|
strikethrough_thickness: f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The uniforms used for custom postprocess shaders.
|
||||||
|
pub const PostUniforms = extern struct {
|
||||||
|
// Note: all of the explicit aligmnments are copied from the
|
||||||
|
// MSL developer reference just so that we can be sure that we got
|
||||||
|
// it all exactly right.
|
||||||
|
resolution: [3]f32 align(16),
|
||||||
|
time: f32 align(4),
|
||||||
|
time_delta: f32 align(4),
|
||||||
|
frame_rate: f32 align(4),
|
||||||
|
frame: i32 align(4),
|
||||||
|
channel_time: [4][4]f32 align(16),
|
||||||
|
channel_resolution: [4][4]f32 align(16),
|
||||||
|
mouse: [4]f32 align(16),
|
||||||
|
date: [4]f32 align(16),
|
||||||
|
sample_rate: f32 align(4),
|
||||||
|
};
|
||||||
|
|
||||||
/// Initialize the MTLLibrary. A MTLLibrary is a collection of shaders.
|
/// Initialize the MTLLibrary. A MTLLibrary is a collection of shaders.
|
||||||
fn initLibrary(device: objc.Object) !objc.Object {
|
fn initLibrary(device: objc.Object) !objc.Object {
|
||||||
// Hardcoded since this file isn't meant to be reusable.
|
// Hardcoded since this file isn't meant to be reusable.
|
||||||
@ -105,6 +170,129 @@ fn initLibrary(device: objc.Object) !objc.Object {
|
|||||||
return library;
|
return library;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initialize our custom shader pipelines. The shaders argument is a
|
||||||
|
/// set of shader source code, not file paths.
|
||||||
|
fn initPostPipelines(
|
||||||
|
alloc: Allocator,
|
||||||
|
device: objc.Object,
|
||||||
|
library: objc.Object,
|
||||||
|
shaders: []const [:0]const u8,
|
||||||
|
) ![]const objc.Object {
|
||||||
|
// If we have no shaders, do nothing.
|
||||||
|
if (shaders.len == 0) return &.{};
|
||||||
|
|
||||||
|
// Keeps track of how many shaders we successfully wrote.
|
||||||
|
var i: usize = 0;
|
||||||
|
|
||||||
|
// Initialize our result set. If any error happens, we undo everything.
|
||||||
|
var pipelines = try alloc.alloc(objc.Object, shaders.len);
|
||||||
|
errdefer {
|
||||||
|
for (pipelines[0..i]) |pipeline| {
|
||||||
|
pipeline.msgSend(void, objc.sel("release"), .{});
|
||||||
|
}
|
||||||
|
alloc.free(pipelines);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build each shader. Note we don't use "0.." to build our index
|
||||||
|
// because we need to keep track of our length to clean up above.
|
||||||
|
for (shaders) |source| {
|
||||||
|
pipelines[i] = try initPostPipeline(device, library, source);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pipelines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize a single custom shader pipeline from shader source.
|
||||||
|
fn initPostPipeline(
|
||||||
|
device: objc.Object,
|
||||||
|
library: objc.Object,
|
||||||
|
data: [:0]const u8,
|
||||||
|
) !objc.Object {
|
||||||
|
// Create our library which has the shader source
|
||||||
|
const post_library = library: {
|
||||||
|
const source = try macos.foundation.String.createWithBytes(
|
||||||
|
data,
|
||||||
|
.utf8,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
defer source.release();
|
||||||
|
|
||||||
|
var err: ?*anyopaque = null;
|
||||||
|
const post_library = device.msgSend(
|
||||||
|
objc.Object,
|
||||||
|
objc.sel("newLibraryWithSource:options:error:"),
|
||||||
|
.{ source, @as(?*anyopaque, null), &err },
|
||||||
|
);
|
||||||
|
try checkError(err);
|
||||||
|
errdefer post_library.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
|
break :library post_library;
|
||||||
|
};
|
||||||
|
defer post_library.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
|
// Get our vertex and fragment functions
|
||||||
|
const func_vert = func_vert: {
|
||||||
|
const str = try macos.foundation.String.createWithBytes(
|
||||||
|
"post_vertex",
|
||||||
|
.utf8,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
defer str.release();
|
||||||
|
|
||||||
|
const ptr = library.msgSend(?*anyopaque, objc.sel("newFunctionWithName:"), .{str});
|
||||||
|
break :func_vert objc.Object.fromId(ptr.?);
|
||||||
|
};
|
||||||
|
const func_frag = func_frag: {
|
||||||
|
const str = try macos.foundation.String.createWithBytes(
|
||||||
|
"main0",
|
||||||
|
.utf8,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
defer str.release();
|
||||||
|
|
||||||
|
const ptr = post_library.msgSend(?*anyopaque, objc.sel("newFunctionWithName:"), .{str});
|
||||||
|
break :func_frag objc.Object.fromId(ptr.?);
|
||||||
|
};
|
||||||
|
defer func_vert.msgSend(void, objc.sel("release"), .{});
|
||||||
|
defer func_frag.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
|
// Create our descriptor
|
||||||
|
const desc = init: {
|
||||||
|
const Class = objc.getClass("MTLRenderPipelineDescriptor").?;
|
||||||
|
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
|
||||||
|
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
|
||||||
|
break :init id_init;
|
||||||
|
};
|
||||||
|
defer desc.msgSend(void, objc.sel("release"), .{});
|
||||||
|
desc.setProperty("vertexFunction", func_vert);
|
||||||
|
desc.setProperty("fragmentFunction", func_frag);
|
||||||
|
|
||||||
|
// Set our color attachment
|
||||||
|
const attachments = objc.Object.fromId(desc.getProperty(?*anyopaque, "colorAttachments"));
|
||||||
|
{
|
||||||
|
const attachment = attachments.msgSend(
|
||||||
|
objc.Object,
|
||||||
|
objc.sel("objectAtIndexedSubscript:"),
|
||||||
|
.{@as(c_ulong, 0)},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Value is MTLPixelFormatBGRA8Unorm
|
||||||
|
attachment.setProperty("pixelFormat", @as(c_ulong, 80));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make our state
|
||||||
|
var err: ?*anyopaque = null;
|
||||||
|
const pipeline_state = device.msgSend(
|
||||||
|
objc.Object,
|
||||||
|
objc.sel("newRenderPipelineStateWithDescriptor:error:"),
|
||||||
|
.{ desc, &err },
|
||||||
|
);
|
||||||
|
try checkError(err);
|
||||||
|
|
||||||
|
return pipeline_state;
|
||||||
|
}
|
||||||
|
|
||||||
/// Initialize the cell render pipeline for our shader library.
|
/// Initialize the cell render pipeline for our shader library.
|
||||||
fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||||
// Get our vertex and fragment functions
|
// Get our vertex and fragment functions
|
||||||
@ -130,6 +318,8 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
const ptr = library.msgSend(?*anyopaque, objc.sel("newFunctionWithName:"), .{str});
|
const ptr = library.msgSend(?*anyopaque, objc.sel("newFunctionWithName:"), .{str});
|
||||||
break :func_frag objc.Object.fromId(ptr.?);
|
break :func_frag objc.Object.fromId(ptr.?);
|
||||||
};
|
};
|
||||||
|
defer func_vert.msgSend(void, objc.sel("release"), .{});
|
||||||
|
defer func_frag.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
// Create the vertex descriptor. The vertex descriptor describes the
|
// Create the vertex descriptor. The vertex descriptor describes the
|
||||||
// data layout of the vertex inputs. We use indexed (or "instanced")
|
// data layout of the vertex inputs. We use indexed (or "instanced")
|
||||||
@ -137,7 +327,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
// Cell as input.
|
// Cell as input.
|
||||||
const vertex_desc = vertex_desc: {
|
const vertex_desc = vertex_desc: {
|
||||||
const desc = init: {
|
const desc = init: {
|
||||||
const Class = objc.Class.getClass("MTLVertexDescriptor").?;
|
const Class = objc.getClass("MTLVertexDescriptor").?;
|
||||||
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
|
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
|
||||||
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
|
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
|
||||||
break :init id_init;
|
break :init id_init;
|
||||||
@ -239,14 +429,16 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
|
|
||||||
break :vertex_desc desc;
|
break :vertex_desc desc;
|
||||||
};
|
};
|
||||||
|
defer vertex_desc.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
// Create our descriptor
|
// Create our descriptor
|
||||||
const desc = init: {
|
const desc = init: {
|
||||||
const Class = objc.Class.getClass("MTLRenderPipelineDescriptor").?;
|
const Class = objc.getClass("MTLRenderPipelineDescriptor").?;
|
||||||
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
|
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
|
||||||
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
|
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
|
||||||
break :init id_init;
|
break :init id_init;
|
||||||
};
|
};
|
||||||
|
defer desc.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
// Set our properties
|
// Set our properties
|
||||||
desc.setProperty("vertexFunction", func_vert);
|
desc.setProperty("vertexFunction", func_vert);
|
||||||
@ -284,6 +476,7 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
.{ desc, &err },
|
.{ desc, &err },
|
||||||
);
|
);
|
||||||
try checkError(err);
|
try checkError(err);
|
||||||
|
errdefer pipeline_state.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
return pipeline_state;
|
return pipeline_state;
|
||||||
}
|
}
|
||||||
@ -313,6 +506,8 @@ fn initImagePipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
const ptr = library.msgSend(?*anyopaque, objc.sel("newFunctionWithName:"), .{str});
|
const ptr = library.msgSend(?*anyopaque, objc.sel("newFunctionWithName:"), .{str});
|
||||||
break :func_frag objc.Object.fromId(ptr.?);
|
break :func_frag objc.Object.fromId(ptr.?);
|
||||||
};
|
};
|
||||||
|
defer func_vert.msgSend(void, objc.sel("release"), .{});
|
||||||
|
defer func_frag.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
// Create the vertex descriptor. The vertex descriptor describes the
|
// Create the vertex descriptor. The vertex descriptor describes the
|
||||||
// data layout of the vertex inputs. We use indexed (or "instanced")
|
// data layout of the vertex inputs. We use indexed (or "instanced")
|
||||||
@ -320,7 +515,7 @@ fn initImagePipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
// Image as input.
|
// Image as input.
|
||||||
const vertex_desc = vertex_desc: {
|
const vertex_desc = vertex_desc: {
|
||||||
const desc = init: {
|
const desc = init: {
|
||||||
const Class = objc.Class.getClass("MTLVertexDescriptor").?;
|
const Class = objc.getClass("MTLVertexDescriptor").?;
|
||||||
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
|
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
|
||||||
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
|
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
|
||||||
break :init id_init;
|
break :init id_init;
|
||||||
@ -389,14 +584,16 @@ fn initImagePipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
|||||||
|
|
||||||
break :vertex_desc desc;
|
break :vertex_desc desc;
|
||||||
};
|
};
|
||||||
|
defer vertex_desc.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
// Create our descriptor
|
// Create our descriptor
|
||||||
const desc = init: {
|
const desc = init: {
|
||||||
const Class = objc.Class.getClass("MTLRenderPipelineDescriptor").?;
|
const Class = objc.getClass("MTLRenderPipelineDescriptor").?;
|
||||||
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
|
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
|
||||||
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
|
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
|
||||||
break :init id_init;
|
break :init id_init;
|
||||||
};
|
};
|
||||||
|
defer desc.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
// Set our properties
|
// Set our properties
|
||||||
desc.setProperty("vertexFunction", func_vert);
|
desc.setProperty("vertexFunction", func_vert);
|
||||||
|
174
src/renderer/opengl/CellProgram.zig
Normal file
174
src/renderer/opengl/CellProgram.zig
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
/// The OpenGL program for rendering terminal cells.
|
||||||
|
const CellProgram = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const gl = @import("opengl");
|
||||||
|
|
||||||
|
program: gl.Program,
|
||||||
|
vao: gl.VertexArray,
|
||||||
|
ebo: gl.Buffer,
|
||||||
|
vbo: gl.Buffer,
|
||||||
|
|
||||||
|
/// The raw structure that maps directly to the buffer sent to the vertex shader.
|
||||||
|
/// This must be "extern" so that the field order is not reordered by the
|
||||||
|
/// Zig compiler.
|
||||||
|
pub const Cell = extern struct {
|
||||||
|
/// vec2 grid_coord
|
||||||
|
grid_col: u16,
|
||||||
|
grid_row: u16,
|
||||||
|
|
||||||
|
/// vec2 glyph_pos
|
||||||
|
glyph_x: u32 = 0,
|
||||||
|
glyph_y: u32 = 0,
|
||||||
|
|
||||||
|
/// vec2 glyph_size
|
||||||
|
glyph_width: u32 = 0,
|
||||||
|
glyph_height: u32 = 0,
|
||||||
|
|
||||||
|
/// vec2 glyph_offset
|
||||||
|
glyph_offset_x: i32 = 0,
|
||||||
|
glyph_offset_y: i32 = 0,
|
||||||
|
|
||||||
|
/// vec4 fg_color_in
|
||||||
|
r: u8,
|
||||||
|
g: u8,
|
||||||
|
b: u8,
|
||||||
|
a: u8,
|
||||||
|
|
||||||
|
/// uint mode
|
||||||
|
mode: CellMode,
|
||||||
|
|
||||||
|
/// The width in grid cells that a rendering takes.
|
||||||
|
grid_width: u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CellMode = enum(u8) {
|
||||||
|
bg = 1,
|
||||||
|
fg = 2,
|
||||||
|
fg_color = 7,
|
||||||
|
strikethrough = 8,
|
||||||
|
|
||||||
|
// Non-exhaustive because masks change it
|
||||||
|
_,
|
||||||
|
|
||||||
|
/// Apply a mask to the mode.
|
||||||
|
pub fn mask(self: CellMode, m: CellMode) CellMode {
|
||||||
|
return @enumFromInt(@intFromEnum(self) | @intFromEnum(m));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init() !CellProgram {
|
||||||
|
// Load and compile our shaders.
|
||||||
|
const program = try gl.Program.createVF(
|
||||||
|
@embedFile("../shaders/cell.v.glsl"),
|
||||||
|
@embedFile("../shaders/cell.f.glsl"),
|
||||||
|
);
|
||||||
|
errdefer program.destroy();
|
||||||
|
|
||||||
|
// Set our cell dimensions
|
||||||
|
const pbind = try program.use();
|
||||||
|
defer pbind.unbind();
|
||||||
|
|
||||||
|
// Set all of our texture indexes
|
||||||
|
try program.setUniform("text", 0);
|
||||||
|
try program.setUniform("text_color", 1);
|
||||||
|
|
||||||
|
// Setup our VAO
|
||||||
|
const vao = try gl.VertexArray.create();
|
||||||
|
errdefer vao.destroy();
|
||||||
|
const vaobind = try vao.bind();
|
||||||
|
defer vaobind.unbind();
|
||||||
|
|
||||||
|
// Element buffer (EBO)
|
||||||
|
const ebo = try gl.Buffer.create();
|
||||||
|
errdefer ebo.destroy();
|
||||||
|
var ebobind = try ebo.bind(.element_array);
|
||||||
|
defer ebobind.unbind();
|
||||||
|
try ebobind.setData([6]u8{
|
||||||
|
0, 1, 3, // Top-left triangle
|
||||||
|
1, 2, 3, // Bottom-right triangle
|
||||||
|
}, .static_draw);
|
||||||
|
|
||||||
|
// Vertex buffer (VBO)
|
||||||
|
const vbo = try gl.Buffer.create();
|
||||||
|
errdefer vbo.destroy();
|
||||||
|
var vbobind = try vbo.bind(.array);
|
||||||
|
defer vbobind.unbind();
|
||||||
|
var offset: usize = 0;
|
||||||
|
try vbobind.attributeAdvanced(0, 2, gl.c.GL_UNSIGNED_SHORT, false, @sizeOf(Cell), offset);
|
||||||
|
offset += 2 * @sizeOf(u16);
|
||||||
|
try vbobind.attributeAdvanced(1, 2, gl.c.GL_UNSIGNED_INT, false, @sizeOf(Cell), offset);
|
||||||
|
offset += 2 * @sizeOf(u32);
|
||||||
|
try vbobind.attributeAdvanced(2, 2, gl.c.GL_UNSIGNED_INT, false, @sizeOf(Cell), offset);
|
||||||
|
offset += 2 * @sizeOf(u32);
|
||||||
|
try vbobind.attributeAdvanced(3, 2, gl.c.GL_INT, false, @sizeOf(Cell), offset);
|
||||||
|
offset += 2 * @sizeOf(i32);
|
||||||
|
try vbobind.attributeAdvanced(4, 4, gl.c.GL_UNSIGNED_BYTE, false, @sizeOf(Cell), offset);
|
||||||
|
offset += 4 * @sizeOf(u8);
|
||||||
|
try vbobind.attributeIAdvanced(5, 1, gl.c.GL_UNSIGNED_BYTE, @sizeOf(Cell), offset);
|
||||||
|
offset += 1 * @sizeOf(u8);
|
||||||
|
try vbobind.attributeIAdvanced(6, 1, gl.c.GL_UNSIGNED_BYTE, @sizeOf(Cell), offset);
|
||||||
|
try vbobind.enableAttribArray(0);
|
||||||
|
try vbobind.enableAttribArray(1);
|
||||||
|
try vbobind.enableAttribArray(2);
|
||||||
|
try vbobind.enableAttribArray(3);
|
||||||
|
try vbobind.enableAttribArray(4);
|
||||||
|
try vbobind.enableAttribArray(5);
|
||||||
|
try vbobind.enableAttribArray(6);
|
||||||
|
try vbobind.attributeDivisor(0, 1);
|
||||||
|
try vbobind.attributeDivisor(1, 1);
|
||||||
|
try vbobind.attributeDivisor(2, 1);
|
||||||
|
try vbobind.attributeDivisor(3, 1);
|
||||||
|
try vbobind.attributeDivisor(4, 1);
|
||||||
|
try vbobind.attributeDivisor(5, 1);
|
||||||
|
try vbobind.attributeDivisor(6, 1);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.program = program,
|
||||||
|
.vao = vao,
|
||||||
|
.ebo = ebo,
|
||||||
|
.vbo = vbo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bind(self: CellProgram) !Binding {
|
||||||
|
const program = try self.program.use();
|
||||||
|
errdefer program.unbind();
|
||||||
|
|
||||||
|
const vao = try self.vao.bind();
|
||||||
|
errdefer vao.unbind();
|
||||||
|
|
||||||
|
const ebo = try self.ebo.bind(.element_array);
|
||||||
|
errdefer ebo.unbind();
|
||||||
|
|
||||||
|
const vbo = try self.vbo.bind(.array);
|
||||||
|
errdefer vbo.unbind();
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.program = program,
|
||||||
|
.vao = vao,
|
||||||
|
.ebo = ebo,
|
||||||
|
.vbo = vbo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: CellProgram) void {
|
||||||
|
self.vbo.destroy();
|
||||||
|
self.ebo.destroy();
|
||||||
|
self.vao.destroy();
|
||||||
|
self.program.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Binding = struct {
|
||||||
|
program: gl.Program.Binding,
|
||||||
|
vao: gl.VertexArray.Binding,
|
||||||
|
ebo: gl.Buffer.Binding,
|
||||||
|
vbo: gl.Buffer.Binding,
|
||||||
|
|
||||||
|
pub fn unbind(self: Binding) void {
|
||||||
|
self.vbo.unbind();
|
||||||
|
self.ebo.unbind();
|
||||||
|
self.vao.unbind();
|
||||||
|
self.program.unbind();
|
||||||
|
}
|
||||||
|
};
|
289
src/renderer/opengl/custom.zig
Normal file
289
src/renderer/opengl/custom.zig
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const gl = @import("opengl");
|
||||||
|
const ScreenSize = @import("../size.zig").ScreenSize;
|
||||||
|
|
||||||
|
const log = std.log.scoped(.opengl_custom);
|
||||||
|
|
||||||
|
/// The "INDEX" is the index into the global GL state and the
|
||||||
|
/// "BINDING" is the binding location in the shader.
|
||||||
|
const UNIFORM_INDEX: gl.c.GLuint = 0;
|
||||||
|
const UNIFORM_BINDING: gl.c.GLuint = 0;
|
||||||
|
|
||||||
|
/// Global uniforms for custom shaders.
|
||||||
|
pub const Uniforms = extern struct {
|
||||||
|
resolution: [3]f32 align(16) = .{ 0, 0, 0 },
|
||||||
|
time: f32 align(4) = 1,
|
||||||
|
time_delta: f32 align(4) = 1,
|
||||||
|
frame_rate: f32 align(4) = 1,
|
||||||
|
frame: i32 align(4) = 1,
|
||||||
|
channel_time: [4][4]f32 align(16) = [1][4]f32{.{ 0, 0, 0, 0 }} ** 4,
|
||||||
|
channel_resolution: [4][4]f32 align(16) = [1][4]f32{.{ 0, 0, 0, 0 }} ** 4,
|
||||||
|
mouse: [4]f32 align(16) = .{ 0, 0, 0, 0 },
|
||||||
|
date: [4]f32 align(16) = .{ 0, 0, 0, 0 },
|
||||||
|
sample_rate: f32 align(4) = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The state associated with custom shaders. This should only be initialized
|
||||||
|
/// if there is at least one custom shader.
|
||||||
|
///
|
||||||
|
/// To use this, the main terminal shader should render to the framebuffer
|
||||||
|
/// specified by "fbo". The resulting "fb_texture" will contain the color
|
||||||
|
/// attachment. This is then used as the iChannel0 input to the custom
|
||||||
|
/// shader.
|
||||||
|
pub const State = struct {
|
||||||
|
/// The uniform data
|
||||||
|
uniforms: Uniforms,
|
||||||
|
|
||||||
|
/// The OpenGL buffers
|
||||||
|
fbo: gl.Framebuffer,
|
||||||
|
ubo: gl.Buffer,
|
||||||
|
vao: gl.VertexArray,
|
||||||
|
ebo: gl.Buffer,
|
||||||
|
fb_texture: gl.Texture,
|
||||||
|
|
||||||
|
/// The set of programs for the custom shaders.
|
||||||
|
programs: []const Program,
|
||||||
|
|
||||||
|
/// The last time the frame was drawn. This is used to update
|
||||||
|
/// the time uniform.
|
||||||
|
first_frame_time: std.time.Instant,
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
alloc: Allocator,
|
||||||
|
srcs: []const [:0]const u8,
|
||||||
|
) !State {
|
||||||
|
if (srcs.len == 0) return error.OneCustomShaderRequired;
|
||||||
|
|
||||||
|
// Create our programs
|
||||||
|
var programs = std.ArrayList(Program).init(alloc);
|
||||||
|
defer programs.deinit();
|
||||||
|
errdefer for (programs.items) |p| p.deinit();
|
||||||
|
for (srcs) |src| {
|
||||||
|
try programs.append(try Program.init(src));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the texture for the framebuffer
|
||||||
|
const fb_tex = try gl.Texture.create();
|
||||||
|
errdefer fb_tex.destroy();
|
||||||
|
{
|
||||||
|
const texbind = try fb_tex.bind(.@"2D");
|
||||||
|
try texbind.parameter(.WrapS, gl.c.GL_CLAMP_TO_EDGE);
|
||||||
|
try texbind.parameter(.WrapT, gl.c.GL_CLAMP_TO_EDGE);
|
||||||
|
try texbind.parameter(.MinFilter, gl.c.GL_LINEAR);
|
||||||
|
try texbind.parameter(.MagFilter, gl.c.GL_LINEAR);
|
||||||
|
try texbind.image2D(
|
||||||
|
0,
|
||||||
|
.rgb,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
.rgb,
|
||||||
|
.UnsignedByte,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create our framebuffer for rendering off screen.
|
||||||
|
// The shader prior to custom shaders should use this
|
||||||
|
// framebuffer.
|
||||||
|
const fbo = try gl.Framebuffer.create();
|
||||||
|
errdefer fbo.destroy();
|
||||||
|
const fbbind = try fbo.bind(.framebuffer);
|
||||||
|
defer fbbind.unbind();
|
||||||
|
try fbbind.texture2D(.color0, .@"2D", fb_tex, 0);
|
||||||
|
const fbstatus = fbbind.checkStatus();
|
||||||
|
if (fbstatus != .complete) {
|
||||||
|
log.warn(
|
||||||
|
"framebuffer is not complete state={}",
|
||||||
|
.{fbstatus},
|
||||||
|
);
|
||||||
|
return error.InvalidFramebuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create our uniform buffer that is shared across all
|
||||||
|
// custom shaders
|
||||||
|
const ubo = try gl.Buffer.create();
|
||||||
|
errdefer ubo.destroy();
|
||||||
|
{
|
||||||
|
var ubobind = try ubo.bind(.uniform);
|
||||||
|
defer ubobind.unbind();
|
||||||
|
try ubobind.setDataNull(Uniforms, .static_draw);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup our VAO for the custom shader.
|
||||||
|
const vao = try gl.VertexArray.create();
|
||||||
|
errdefer vao.destroy();
|
||||||
|
const vaobind = try vao.bind();
|
||||||
|
defer vaobind.unbind();
|
||||||
|
|
||||||
|
// Element buffer (EBO)
|
||||||
|
const ebo = try gl.Buffer.create();
|
||||||
|
errdefer ebo.destroy();
|
||||||
|
var ebobind = try ebo.bind(.element_array);
|
||||||
|
defer ebobind.unbind();
|
||||||
|
try ebobind.setData([6]u8{
|
||||||
|
0, 1, 3, // Top-left triangle
|
||||||
|
1, 2, 3, // Bottom-right triangle
|
||||||
|
}, .static_draw);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.programs = try programs.toOwnedSlice(),
|
||||||
|
.uniforms = .{},
|
||||||
|
.fbo = fbo,
|
||||||
|
.ubo = ubo,
|
||||||
|
.vao = vao,
|
||||||
|
.ebo = ebo,
|
||||||
|
.fb_texture = fb_tex,
|
||||||
|
.first_frame_time = try std.time.Instant.now(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *const State, alloc: Allocator) void {
|
||||||
|
for (self.programs) |p| p.deinit();
|
||||||
|
alloc.free(self.programs);
|
||||||
|
self.ubo.destroy();
|
||||||
|
self.ebo.destroy();
|
||||||
|
self.vao.destroy();
|
||||||
|
self.fb_texture.destroy();
|
||||||
|
self.fbo.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setScreenSize(self: *State, size: ScreenSize) !void {
|
||||||
|
// Update our uniforms
|
||||||
|
self.uniforms.resolution = .{
|
||||||
|
@floatFromInt(size.width),
|
||||||
|
@floatFromInt(size.height),
|
||||||
|
1,
|
||||||
|
};
|
||||||
|
try self.syncUniforms();
|
||||||
|
|
||||||
|
// Update our texture
|
||||||
|
const texbind = try self.fb_texture.bind(.@"2D");
|
||||||
|
try texbind.image2D(
|
||||||
|
0,
|
||||||
|
.rgb,
|
||||||
|
@intCast(size.width),
|
||||||
|
@intCast(size.height),
|
||||||
|
0,
|
||||||
|
.rgb,
|
||||||
|
.UnsignedByte,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call this prior to drawing a frame to update the time
|
||||||
|
/// and synchronize the uniforms. This synchronizes uniforms
|
||||||
|
/// so you should make changes to uniforms prior to calling
|
||||||
|
/// this.
|
||||||
|
pub fn newFrame(self: *State) !void {
|
||||||
|
// Update our frame time
|
||||||
|
const now = std.time.Instant.now() catch self.first_frame_time;
|
||||||
|
const since_ns: f32 = @floatFromInt(now.since(self.first_frame_time));
|
||||||
|
self.uniforms.time = since_ns / std.time.ns_per_s;
|
||||||
|
self.uniforms.time_delta = since_ns / std.time.ns_per_s;
|
||||||
|
|
||||||
|
// Sync our uniform changes
|
||||||
|
try self.syncUniforms();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn syncUniforms(self: *State) !void {
|
||||||
|
var ubobind = try self.ubo.bind(.uniform);
|
||||||
|
defer ubobind.unbind();
|
||||||
|
try ubobind.setData(self.uniforms, .static_draw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call this to bind all the necessary OpenGL resources for
|
||||||
|
/// all custom shaders. Each individual shader needs to be bound
|
||||||
|
/// one at a time too.
|
||||||
|
pub fn bind(self: *const State) !Binding {
|
||||||
|
// Move our uniform buffer into proper global index. Note that
|
||||||
|
// in theory we can do this globally once and never worry about
|
||||||
|
// it again. I don't think we're high-performance enough at all
|
||||||
|
// to worry about that and this makes it so you can just move
|
||||||
|
// around CustomProgram usage without worrying about clobbering
|
||||||
|
// the global state.
|
||||||
|
try self.ubo.bindBase(.uniform, UNIFORM_INDEX);
|
||||||
|
|
||||||
|
// Bind our texture that is shared amongst all
|
||||||
|
try gl.Texture.active(gl.c.GL_TEXTURE0);
|
||||||
|
var texbind = try self.fb_texture.bind(.@"2D");
|
||||||
|
errdefer texbind.unbind();
|
||||||
|
|
||||||
|
const vao = try self.vao.bind();
|
||||||
|
errdefer vao.unbind();
|
||||||
|
|
||||||
|
const ebo = try self.ebo.bind(.element_array);
|
||||||
|
errdefer ebo.unbind();
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.vao = vao,
|
||||||
|
.ebo = ebo,
|
||||||
|
.fb_texture = texbind,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Binding = struct {
|
||||||
|
vao: gl.VertexArray.Binding,
|
||||||
|
ebo: gl.Buffer.Binding,
|
||||||
|
fb_texture: gl.Texture.Binding,
|
||||||
|
|
||||||
|
pub fn unbind(self: Binding) void {
|
||||||
|
self.ebo.unbind();
|
||||||
|
self.vao.unbind();
|
||||||
|
self.fb_texture.unbind();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A single OpenGL program (combined shaders) for custom shaders.
|
||||||
|
pub const Program = struct {
|
||||||
|
program: gl.Program,
|
||||||
|
|
||||||
|
pub fn init(src: [:0]const u8) !Program {
|
||||||
|
const program = try gl.Program.createVF(
|
||||||
|
@embedFile("../shaders/custom.v.glsl"),
|
||||||
|
src,
|
||||||
|
//@embedFile("../shaders/temp.f.glsl"),
|
||||||
|
);
|
||||||
|
errdefer program.destroy();
|
||||||
|
|
||||||
|
// Map our uniform buffer to the global GL state
|
||||||
|
try program.uniformBlockBinding(UNIFORM_INDEX, UNIFORM_BINDING);
|
||||||
|
|
||||||
|
return .{ .program = program };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *const Program) void {
|
||||||
|
self.program.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bind the program for use. This should be called so that draw can
|
||||||
|
/// be called.
|
||||||
|
pub fn bind(self: *const Program) !Binding {
|
||||||
|
const program = try self.program.use();
|
||||||
|
errdefer program.unbind();
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.program = program,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Binding = struct {
|
||||||
|
program: gl.Program.Binding,
|
||||||
|
|
||||||
|
pub fn unbind(self: Binding) void {
|
||||||
|
self.program.unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(self: Binding) !void {
|
||||||
|
_ = self;
|
||||||
|
try gl.drawElementsInstanced(
|
||||||
|
gl.c.GL_TRIANGLES,
|
||||||
|
6,
|
||||||
|
gl.c.GL_UNSIGNED_BYTE,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
@ -253,3 +253,20 @@ fragment float4 image_fragment(
|
|||||||
uint4 rgba = image.sample(textureSampler, in.tex_coord);
|
uint4 rgba = image.sample(textureSampler, in.tex_coord);
|
||||||
return float4(rgba) / 255.0f;
|
return float4(rgba) / 255.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------
|
||||||
|
// Post Shader
|
||||||
|
//-------------------------------------------------------------------
|
||||||
|
#pragma mark - Post Shader
|
||||||
|
|
||||||
|
struct PostVertexOut {
|
||||||
|
float4 position [[ position ]];
|
||||||
|
};
|
||||||
|
|
||||||
|
constant float2 post_pos[4] = { {-1,-1}, {1,-1}, {-1,1}, {1,1 } };
|
||||||
|
|
||||||
|
vertex PostVertexOut post_vertex(uint id [[ vertex_id ]]) {
|
||||||
|
PostVertexOut out;
|
||||||
|
out.position = float4(post_pos[id], 0, 1);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
@ -21,20 +21,18 @@ layout (location = 2) in vec2 glyph_size;
|
|||||||
// Offset of the top-left corner of the glyph when rendered in a rect.
|
// Offset of the top-left corner of the glyph when rendered in a rect.
|
||||||
layout (location = 3) in vec2 glyph_offset;
|
layout (location = 3) in vec2 glyph_offset;
|
||||||
|
|
||||||
// The background color for this cell in RGBA (0 to 1.0)
|
// The color for this cell in RGBA (0 to 1.0). Background or foreground
|
||||||
layout (location = 4) in vec4 fg_color_in;
|
// depends on mode.
|
||||||
|
layout (location = 4) in vec4 color_in;
|
||||||
// The background color for this cell in RGBA (0 to 1.0)
|
|
||||||
layout (location = 5) in vec4 bg_color_in;
|
|
||||||
|
|
||||||
// The mode of this shader. The mode determines what fields are used,
|
// The mode of this shader. The mode determines what fields are used,
|
||||||
// what the output will be, etc. This shader is capable of executing in
|
// what the output will be, etc. This shader is capable of executing in
|
||||||
// multiple "modes" so that we can share some logic and so that we can draw
|
// multiple "modes" so that we can share some logic and so that we can draw
|
||||||
// the entire terminal grid in a single GPU pass.
|
// the entire terminal grid in a single GPU pass.
|
||||||
layout (location = 6) in uint mode_in;
|
layout (location = 5) in uint mode_in;
|
||||||
|
|
||||||
// The width in cells of this item.
|
// The width in cells of this item.
|
||||||
layout (location = 7) in uint grid_width;
|
layout (location = 6) in uint grid_width;
|
||||||
|
|
||||||
// The background or foreground color for the fragment, depending on
|
// The background or foreground color for the fragment, depending on
|
||||||
// whether this is a background or foreground pass.
|
// whether this is a background or foreground pass.
|
||||||
@ -117,7 +115,7 @@ void main() {
|
|||||||
cell_pos = cell_pos + cell_size_scaled * position;
|
cell_pos = cell_pos + cell_size_scaled * position;
|
||||||
|
|
||||||
gl_Position = projection * vec4(cell_pos, cell_z, 1.0);
|
gl_Position = projection * vec4(cell_pos, cell_z, 1.0);
|
||||||
color = bg_color_in / 255.0;
|
color = color_in / 255.0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MODE_FG:
|
case MODE_FG:
|
||||||
@ -150,7 +148,7 @@ void main() {
|
|||||||
glyph_tex_coords = glyph_tex_pos + glyph_tex_size * position;
|
glyph_tex_coords = glyph_tex_pos + glyph_tex_size * position;
|
||||||
|
|
||||||
// Set our foreground color output
|
// Set our foreground color output
|
||||||
color = fg_color_in / 255.;
|
color = color_in / 255.;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MODE_STRIKETHROUGH:
|
case MODE_STRIKETHROUGH:
|
||||||
@ -166,7 +164,7 @@ void main() {
|
|||||||
cell_pos = cell_pos + strikethrough_offset - (strikethrough_size * position);
|
cell_pos = cell_pos + strikethrough_offset - (strikethrough_size * position);
|
||||||
|
|
||||||
gl_Position = projection * vec4(cell_pos, cell_z, 1.0);
|
gl_Position = projection * vec4(cell_pos, cell_z, 1.0);
|
||||||
color = fg_color_in / 255.0;
|
color = color_in / 255.0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
8
src/renderer/shaders/custom.v.glsl
Normal file
8
src/renderer/shaders/custom.v.glsl
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#version 330 core
|
||||||
|
|
||||||
|
void main(){
|
||||||
|
vec2 position;
|
||||||
|
position.x = (gl_VertexID == 0 || gl_VertexID == 1) ? -1. : 1.;
|
||||||
|
position.y = (gl_VertexID == 0 || gl_VertexID == 3) ? 1. : -1.;
|
||||||
|
gl_Position = vec4(position.xy, 0.0f, 1.0f);
|
||||||
|
}
|
29
src/renderer/shaders/shadertoy_prefix.glsl
Normal file
29
src/renderer/shaders/shadertoy_prefix.glsl
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#version 430 core
|
||||||
|
|
||||||
|
layout(binding = 0) uniform Globals {
|
||||||
|
uniform vec3 iResolution;
|
||||||
|
uniform float iTime;
|
||||||
|
uniform float iTimeDelta;
|
||||||
|
uniform float iFrameRate;
|
||||||
|
uniform int iFrame;
|
||||||
|
uniform float iChannelTime[4];
|
||||||
|
uniform vec3 iChannelResolution[4];
|
||||||
|
uniform vec4 iMouse;
|
||||||
|
uniform vec4 iDate;
|
||||||
|
uniform float iSampleRate;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout(binding = 0) uniform sampler2D iChannel0;
|
||||||
|
|
||||||
|
// These are unused currently by Ghostty:
|
||||||
|
// layout(binding = 1) uniform sampler2D iChannel1;
|
||||||
|
// layout(binding = 2) uniform sampler2D iChannel2;
|
||||||
|
// layout(binding = 3) uniform sampler2D iChannel3;
|
||||||
|
|
||||||
|
layout(location = 0) in vec4 gl_FragCoord;
|
||||||
|
layout(location = 0) out vec4 _fragColor;
|
||||||
|
|
||||||
|
#define texture2D texture
|
||||||
|
|
||||||
|
void mainImage( out vec4 fragColor, in vec2 fragCoord );
|
||||||
|
void main() { mainImage (_fragColor, gl_FragCoord.xy); }
|
59
src/renderer/shaders/test_shadertoy_crt.glsl
Normal file
59
src/renderer/shaders/test_shadertoy_crt.glsl
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// This shader is NOT BUILD INTO Ghostty. This is only here for unit tests.
|
||||||
|
|
||||||
|
// Loosely based on postprocessing shader by inigo quilez, License Creative
|
||||||
|
// Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
|
||||||
|
|
||||||
|
vec2 curve(vec2 uv)
|
||||||
|
{
|
||||||
|
uv = (uv - 0.5) * 2.0;
|
||||||
|
uv *= 1.1;
|
||||||
|
uv.x *= 1.0 + pow((abs(uv.y) / 5.0), 2.0);
|
||||||
|
uv.y *= 1.0 + pow((abs(uv.x) / 4.0), 2.0);
|
||||||
|
uv = (uv / 2.0) + 0.5;
|
||||||
|
uv = uv *0.92 + 0.04;
|
||||||
|
return uv;
|
||||||
|
}
|
||||||
|
void mainImage( out vec4 fragColor, in vec2 fragCoord )
|
||||||
|
{
|
||||||
|
vec2 q = fragCoord.xy / iResolution.xy;
|
||||||
|
vec2 uv = q;
|
||||||
|
uv = curve( uv );
|
||||||
|
vec3 oricol = texture( iChannel0, vec2(q.x,q.y) ).xyz;
|
||||||
|
vec3 col;
|
||||||
|
float x = sin(0.3*iTime+uv.y*21.0)*sin(0.7*iTime+uv.y*29.0)*sin(0.3+0.33*iTime+uv.y*31.0)*0.0017;
|
||||||
|
|
||||||
|
col.r = texture(iChannel0,vec2(x+uv.x+0.001,uv.y+0.001)).x+0.05;
|
||||||
|
col.g = texture(iChannel0,vec2(x+uv.x+0.000,uv.y-0.002)).y+0.05;
|
||||||
|
col.b = texture(iChannel0,vec2(x+uv.x-0.002,uv.y+0.000)).z+0.05;
|
||||||
|
col.r += 0.08*texture(iChannel0,0.75*vec2(x+0.025, -0.027)+vec2(uv.x+0.001,uv.y+0.001)).x;
|
||||||
|
col.g += 0.05*texture(iChannel0,0.75*vec2(x+-0.022, -0.02)+vec2(uv.x+0.000,uv.y-0.002)).y;
|
||||||
|
col.b += 0.08*texture(iChannel0,0.75*vec2(x+-0.02, -0.018)+vec2(uv.x-0.002,uv.y+0.000)).z;
|
||||||
|
|
||||||
|
col = clamp(col*0.6+0.4*col*col*1.0,0.0,1.0);
|
||||||
|
|
||||||
|
float vig = (0.0 + 1.0*16.0*uv.x*uv.y*(1.0-uv.x)*(1.0-uv.y));
|
||||||
|
col *= vec3(pow(vig,0.3));
|
||||||
|
|
||||||
|
col *= vec3(0.95,1.05,0.95);
|
||||||
|
col *= 2.8;
|
||||||
|
|
||||||
|
float scans = clamp( 0.35+0.35*sin(3.5*iTime+uv.y*iResolution.y*1.5), 0.0, 1.0);
|
||||||
|
|
||||||
|
float s = pow(scans,1.7);
|
||||||
|
col = col*vec3( 0.4+0.7*s) ;
|
||||||
|
|
||||||
|
col *= 1.0+0.01*sin(110.0*iTime);
|
||||||
|
if (uv.x < 0.0 || uv.x > 1.0)
|
||||||
|
col *= 0.0;
|
||||||
|
if (uv.y < 0.0 || uv.y > 1.0)
|
||||||
|
col *= 0.0;
|
||||||
|
|
||||||
|
col*=1.0-0.65*vec3(clamp((mod(fragCoord.x, 2.0)-1.0)*2.0,0.0,1.0));
|
||||||
|
|
||||||
|
float comp = smoothstep( 0.1, 0.9, sin(iTime) );
|
||||||
|
|
||||||
|
// Remove the next line to stop cross-fade between original and postprocess
|
||||||
|
// col = mix( col, oricol, comp );
|
||||||
|
|
||||||
|
fragColor = vec4(col,1.0);
|
||||||
|
}
|
12
src/renderer/shaders/test_shadertoy_invalid.glsl
Normal file
12
src/renderer/shaders/test_shadertoy_invalid.glsl
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
vec2 curve(vec2 uv)
|
||||||
|
{
|
||||||
|
uv = (uv - 0.5) * 2.0;
|
||||||
|
uv *= 1.1;
|
||||||
|
uv.x *= 1.0 + pow((abs(uv.y) / 5.0), 2.0);
|
||||||
|
uv.y *= 1.0 + pow((abs(uv.x) / 4.0), 2.0);
|
||||||
|
uv = (uv / 2.0) + 0.5;
|
||||||
|
uv = uv *0.92 + 0.04;
|
||||||
|
return uv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Missing mainImage!
|
365
src/renderer/shadertoy.zig
Normal file
365
src/renderer/shadertoy.zig
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
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 { glsl, 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: {
|
||||||
|
// SpirV pointer must be aligned to 4 bytes since we expect
|
||||||
|
// a slice of words.
|
||||||
|
var list = std.ArrayListAligned(u8, @alignOf(u32)).init(alloc);
|
||||||
|
var errlog: SpirvLog = .{ .alloc = alloc };
|
||||||
|
defer errlog.deinit();
|
||||||
|
spirvFromGlsl(list.writer(), &errlog, glsl) catch |err| {
|
||||||
|
if (errlog.info.len > 0 or errlog.debug.len > 0) {
|
||||||
|
log.warn("spirv error path={s} info={s} debug={s}", .{
|
||||||
|
path,
|
||||||
|
errlog.info,
|
||||||
|
errlog.debug,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
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.
|
||||||
|
.glsl => try glslFromSpv(alloc_gpa, spirv),
|
||||||
|
.msl => try mslFromSpv(alloc_gpa, spirv),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a ShaderToy shader into valid GLSL.
|
||||||
|
///
|
||||||
|
/// ShaderToy shaders aren't full shaders, they're just implementing a
|
||||||
|
/// mainImage function and don't define any of the uniforms. This function
|
||||||
|
/// will convert the ShaderToy shader into a valid GLSL shader that can be
|
||||||
|
/// compiled and linked.
|
||||||
|
pub fn glslFromShader(writer: anytype, src: []const u8) !void {
|
||||||
|
const prefix = @embedFile("shaders/shadertoy_prefix.glsl");
|
||||||
|
try writer.writeAll(prefix);
|
||||||
|
try writer.writeAll("\n\n");
|
||||||
|
try writer.writeAll(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a GLSL shader into SPIR-V assembly.
|
||||||
|
pub fn spirvFromGlsl(
|
||||||
|
writer: anytype,
|
||||||
|
errlog: ?*SpirvLog,
|
||||||
|
src: [:0]const u8,
|
||||||
|
) !void {
|
||||||
|
// So we can run unit tests without fear.
|
||||||
|
if (builtin.is_test) try glslang.testing.ensureInit();
|
||||||
|
|
||||||
|
const c = glslang.c;
|
||||||
|
const input: c.glslang_input_t = .{
|
||||||
|
.language = c.GLSLANG_SOURCE_GLSL,
|
||||||
|
.stage = c.GLSLANG_STAGE_FRAGMENT,
|
||||||
|
.client = c.GLSLANG_CLIENT_VULKAN,
|
||||||
|
.client_version = c.GLSLANG_TARGET_VULKAN_1_2,
|
||||||
|
.target_language = c.GLSLANG_TARGET_SPV,
|
||||||
|
.target_language_version = c.GLSLANG_TARGET_SPV_1_5,
|
||||||
|
.code = src.ptr,
|
||||||
|
.default_version = 100,
|
||||||
|
.default_profile = c.GLSLANG_NO_PROFILE,
|
||||||
|
.force_default_version_and_profile = 0,
|
||||||
|
.forward_compatible = 0,
|
||||||
|
.messages = c.GLSLANG_MSG_DEFAULT_BIT,
|
||||||
|
.resource = c.glslang_default_resource(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const shader = try glslang.Shader.create(&input);
|
||||||
|
defer shader.delete();
|
||||||
|
|
||||||
|
shader.preprocess(&input) catch |err| {
|
||||||
|
if (errlog) |ptr| ptr.fromShader(shader) catch {};
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
shader.parse(&input) catch |err| {
|
||||||
|
if (errlog) |ptr| ptr.fromShader(shader) catch {};
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
|
||||||
|
const program = try glslang.Program.create();
|
||||||
|
defer program.delete();
|
||||||
|
program.addShader(shader);
|
||||||
|
program.link(
|
||||||
|
c.GLSLANG_MSG_SPV_RULES_BIT |
|
||||||
|
c.GLSLANG_MSG_VULKAN_RULES_BIT,
|
||||||
|
) catch |err| {
|
||||||
|
if (errlog) |ptr| ptr.fromProgram(program) catch {};
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
program.spirvGenerate(c.GLSLANG_STAGE_FRAGMENT);
|
||||||
|
const size = program.spirvGetSize();
|
||||||
|
const ptr = try program.spirvGetPtr();
|
||||||
|
const ptr_u8: [*]u8 = @ptrCast(ptr);
|
||||||
|
const slice_u8: []u8 = ptr_u8[0 .. size * 4];
|
||||||
|
try writer.writeAll(slice_u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve errors from spirv compilation.
|
||||||
|
pub const SpirvLog = struct {
|
||||||
|
alloc: Allocator,
|
||||||
|
info: [:0]const u8 = "",
|
||||||
|
debug: [:0]const u8 = "",
|
||||||
|
|
||||||
|
pub fn deinit(self: *const SpirvLog) void {
|
||||||
|
if (self.info.len > 0) self.alloc.free(self.info);
|
||||||
|
if (self.debug.len > 0) self.alloc.free(self.debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fromShader(self: *SpirvLog, shader: *glslang.Shader) !void {
|
||||||
|
const info = try shader.getInfoLog();
|
||||||
|
const debug = try shader.getDebugInfoLog();
|
||||||
|
self.info = "";
|
||||||
|
self.debug = "";
|
||||||
|
if (info.len > 0) self.info = try self.alloc.dupeZ(u8, info);
|
||||||
|
if (debug.len > 0) self.debug = try self.alloc.dupeZ(u8, debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fromProgram(self: *SpirvLog, program: *glslang.Program) !void {
|
||||||
|
const info = try program.getInfoLog();
|
||||||
|
const debug = try program.getDebugInfoLog();
|
||||||
|
self.info = "";
|
||||||
|
self.debug = "";
|
||||||
|
if (info.len > 0) self.info = try self.alloc.dupeZ(u8, info);
|
||||||
|
if (debug.len > 0) self.debug = try self.alloc.dupeZ(u8, debug);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Convert SPIR-V binary to MSL.
|
||||||
|
pub fn mslFromSpv(alloc: Allocator, spv: []const u8) ![:0]const u8 {
|
||||||
|
return try spvCross(alloc, spvcross.c.SPVC_BACKEND_MSL, spv, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert SPIR-V binary to GLSL..
|
||||||
|
pub fn glslFromSpv(alloc: Allocator, spv: []const u8) ![:0]const u8 {
|
||||||
|
// Our minimum version for shadertoy shaders is OpenGL 4.2 because
|
||||||
|
// Spirv-Cross generates binding locations for uniforms which is
|
||||||
|
// only supported in OpenGL 4.2 and above.
|
||||||
|
//
|
||||||
|
// If we can figure out a way to NOT do this then we can lower this
|
||||||
|
// version.
|
||||||
|
const GLSL_VERSION = 420;
|
||||||
|
|
||||||
|
const c = spvcross.c;
|
||||||
|
return try spvCross(alloc, c.SPVC_BACKEND_GLSL, spv, (struct {
|
||||||
|
fn setOptions(options: c.spvc_compiler_options) error{SpvcFailed}!void {
|
||||||
|
if (c.spvc_compiler_options_set_uint(
|
||||||
|
options,
|
||||||
|
c.SPVC_COMPILER_OPTION_GLSL_VERSION,
|
||||||
|
GLSL_VERSION,
|
||||||
|
) != c.SPVC_SUCCESS) {
|
||||||
|
return error.SpvcFailed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).setOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spvCross(
|
||||||
|
alloc: Allocator,
|
||||||
|
backend: spvcross.c.spvc_backend,
|
||||||
|
spv: []const u8,
|
||||||
|
comptime optionsFn_: ?*const fn (c: spvcross.c.spvc_compiler_options) error{SpvcFailed}!void,
|
||||||
|
) ![:0]const u8 {
|
||||||
|
// Spir-V is always a multiple of 4 because it is written as a series of words
|
||||||
|
if (@mod(spv.len, 4) != 0) return error.SpirvInvalid;
|
||||||
|
|
||||||
|
// Compiler context
|
||||||
|
const c = spvcross.c;
|
||||||
|
var ctx: c.spvc_context = undefined;
|
||||||
|
if (c.spvc_context_create(&ctx) != c.SPVC_SUCCESS) return error.SpvcFailed;
|
||||||
|
defer c.spvc_context_destroy(ctx);
|
||||||
|
|
||||||
|
// It would be better to get this out into an output parameter to
|
||||||
|
// show users but for now we can just log it.
|
||||||
|
c.spvc_context_set_error_callback(ctx, @ptrCast(&(struct {
|
||||||
|
fn callback(_: ?*anyopaque, msg_ptr: [*c]const u8) callconv(.C) void {
|
||||||
|
const msg = std.mem.sliceTo(msg_ptr, 0);
|
||||||
|
std.log.warn("spirv-cross error message={s}", .{msg});
|
||||||
|
}
|
||||||
|
}).callback), null);
|
||||||
|
|
||||||
|
// Parse the Spir-V binary to an IR
|
||||||
|
var ir: c.spvc_parsed_ir = undefined;
|
||||||
|
if (c.spvc_context_parse_spirv(
|
||||||
|
ctx,
|
||||||
|
@ptrCast(@alignCast(spv.ptr)),
|
||||||
|
spv.len / 4,
|
||||||
|
&ir,
|
||||||
|
) != c.SPVC_SUCCESS) {
|
||||||
|
return error.SpvcFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build our compiler to GLSL
|
||||||
|
var compiler: c.spvc_compiler = undefined;
|
||||||
|
if (c.spvc_context_create_compiler(
|
||||||
|
ctx,
|
||||||
|
backend,
|
||||||
|
ir,
|
||||||
|
c.SPVC_CAPTURE_MODE_TAKE_OWNERSHIP,
|
||||||
|
&compiler,
|
||||||
|
) != c.SPVC_SUCCESS) {
|
||||||
|
return error.SpvcFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup our options if we have any
|
||||||
|
if (optionsFn_) |optionsFn| {
|
||||||
|
var options: c.spvc_compiler_options = undefined;
|
||||||
|
if (c.spvc_compiler_create_compiler_options(compiler, &options) != c.SPVC_SUCCESS) {
|
||||||
|
return error.SpvcFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
try optionsFn(options);
|
||||||
|
|
||||||
|
if (c.spvc_compiler_install_compiler_options(compiler, options) != c.SPVC_SUCCESS) {
|
||||||
|
return error.SpvcFailed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile the resulting string. This string pointer is owned by the
|
||||||
|
// context so we don't need to free it.
|
||||||
|
var result: [*:0]const u8 = undefined;
|
||||||
|
if (c.spvc_compiler_compile(compiler, @ptrCast(&result)) != c.SPVC_SUCCESS) {
|
||||||
|
return error.SpvcFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return try alloc.dupeZ(u8, std.mem.sliceTo(result, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert ShaderToy shader to null-terminated glsl for testing.
|
||||||
|
fn testGlslZ(alloc: Allocator, src: []const u8) ![:0]const u8 {
|
||||||
|
var list = std.ArrayList(u8).init(alloc);
|
||||||
|
defer list.deinit();
|
||||||
|
try glslFromShader(list.writer(), src);
|
||||||
|
return try list.toOwnedSliceSentinel(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "spirv" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
const src = try testGlslZ(alloc, test_crt);
|
||||||
|
defer alloc.free(src);
|
||||||
|
|
||||||
|
var buf: [4096 * 4]u8 = undefined;
|
||||||
|
var buf_stream = std.io.fixedBufferStream(&buf);
|
||||||
|
const writer = buf_stream.writer();
|
||||||
|
try spirvFromGlsl(writer, null, src);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "spirv invalid" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
const src = try testGlslZ(alloc, test_invalid);
|
||||||
|
defer alloc.free(src);
|
||||||
|
|
||||||
|
var buf: [4096 * 4]u8 = undefined;
|
||||||
|
var buf_stream = std.io.fixedBufferStream(&buf);
|
||||||
|
const writer = buf_stream.writer();
|
||||||
|
|
||||||
|
var errlog: SpirvLog = .{ .alloc = alloc };
|
||||||
|
defer errlog.deinit();
|
||||||
|
try testing.expectError(error.GlslangFailed, spirvFromGlsl(writer, &errlog, src));
|
||||||
|
try testing.expect(errlog.info.len > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "shadertoy to msl" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
const src = try testGlslZ(alloc, test_crt);
|
||||||
|
defer alloc.free(src);
|
||||||
|
|
||||||
|
var spvlist = std.ArrayListAligned(u8, @alignOf(u32)).init(alloc);
|
||||||
|
defer spvlist.deinit();
|
||||||
|
try spirvFromGlsl(spvlist.writer(), null, src);
|
||||||
|
|
||||||
|
const msl = try mslFromSpv(alloc, spvlist.items);
|
||||||
|
defer alloc.free(msl);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "shadertoy to glsl" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
const src = try testGlslZ(alloc, test_crt);
|
||||||
|
defer alloc.free(src);
|
||||||
|
|
||||||
|
var spvlist = std.ArrayListAligned(u8, @alignOf(u32)).init(alloc);
|
||||||
|
defer spvlist.deinit();
|
||||||
|
try spirvFromGlsl(spvlist.writer(), null, src);
|
||||||
|
|
||||||
|
const glsl = try glslFromSpv(alloc, spvlist.items);
|
||||||
|
defer alloc.free(glsl);
|
||||||
|
|
||||||
|
// log.warn("glsl={s}", .{glsl});
|
||||||
|
}
|
||||||
|
|
||||||
|
const test_crt = @embedFile("shaders/test_shadertoy_crt.glsl");
|
||||||
|
const test_invalid = @embedFile("shaders/test_shadertoy_invalid.glsl");
|
Reference in New Issue
Block a user