aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/renderer/metal/Texture.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-04-04 19:22:04 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-04-05 01:23:06 +0300
commit41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch)
tree3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/renderer/metal/Texture.zig
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/renderer/metal/Texture.zig')
-rw-r--r--vendor/libghostty-vt/src/renderer/metal/Texture.zig202
1 files changed, 202 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/renderer/metal/Texture.zig b/vendor/libghostty-vt/src/renderer/metal/Texture.zig
new file mode 100644
index 00000000..5042919a
--- /dev/null
+++ b/vendor/libghostty-vt/src/renderer/metal/Texture.zig
@@ -0,0 +1,202 @@
+//! Wrapper for handling textures.
+const Self = @This();
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const assert = @import("../../quirks.zig").inlineAssert;
+const objc = @import("objc");
+
+const mtl = @import("api.zig");
+const Metal = @import("../Metal.zig");
+
+const log = std.log.scoped(.metal);
+
+/// Options for initializing a texture.
+pub const Options = struct {
+ /// MTLDevice
+ device: objc.Object,
+ pixel_format: mtl.MTLPixelFormat,
+ resource_options: mtl.MTLResourceOptions,
+ usage: mtl.MTLTextureUsage,
+};
+
+/// The underlying MTLTexture Object.
+texture: objc.Object,
+
+/// The width of this texture.
+width: usize,
+/// The height of this texture.
+height: usize,
+
+/// Bytes per pixel for this texture.
+bpp: usize,
+
+pub const Error = error{
+ /// A Metal API call failed.
+ MetalFailed,
+};
+
+/// Initialize a texture
+pub fn init(
+ opts: Options,
+ width: usize,
+ height: usize,
+ data: ?[]const u8,
+) Error!Self {
+ // Create our descriptor
+ 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;
+ };
+ defer desc.release();
+
+ // Set our properties
+ desc.setProperty("pixelFormat", @intFromEnum(opts.pixel_format));
+ desc.setProperty("width", @as(c_ulong, width));
+ desc.setProperty("height", @as(c_ulong, height));
+ desc.setProperty("resourceOptions", opts.resource_options);
+ desc.setProperty("usage", opts.usage);
+
+ // Initialize
+ const id = opts.device.msgSend(
+ ?*anyopaque,
+ objc.sel("newTextureWithDescriptor:"),
+ .{desc},
+ ) orelse return error.MetalFailed;
+
+ const self: Self = .{
+ .texture = objc.Object.fromId(id),
+ .width = width,
+ .height = height,
+ .bpp = bppOf(opts.pixel_format),
+ };
+
+ // If we have data, we set it here.
+ if (data) |d| {
+ assert(d.len == width * height * self.bpp);
+ try self.replaceRegion(0, 0, width, height, d);
+ }
+
+ return self;
+}
+
+pub fn deinit(self: Self) void {
+ self.texture.release();
+}
+
+/// Replace a region of the texture with the provided data.
+///
+/// Does NOT check the dimensions of the data to ensure correctness.
+pub fn replaceRegion(
+ self: Self,
+ x: usize,
+ y: usize,
+ width: usize,
+ height: usize,
+ data: []const u8,
+) error{}!void {
+ self.texture.msgSend(
+ void,
+ objc.sel("replaceRegion:mipmapLevel:withBytes:bytesPerRow:"),
+ .{
+ mtl.MTLRegion{
+ .origin = .{ .x = x, .y = y, .z = 0 },
+ .size = .{
+ .width = @intCast(width),
+ .height = @intCast(height),
+ .depth = 1,
+ },
+ },
+ @as(c_ulong, 0),
+ @as(*const anyopaque, data.ptr),
+ @as(c_ulong, self.bpp * width),
+ },
+ );
+}
+
+/// Returns the bytes per pixel for the provided pixel format
+fn bppOf(pixel_format: mtl.MTLPixelFormat) usize {
+ return switch (pixel_format) {
+ // Invalid
+ .invalid => @panic("invalid pixel format"),
+
+ // Weird formats I was too lazy to get the sizes of
+ else => @panic("pixel format size unknown (unlikely that this format was actually used, could be memory corruption)"),
+
+ // 8-bit pixel formats
+ .a8unorm,
+ .r8unorm,
+ .r8unorm_srgb,
+ .r8snorm,
+ .r8uint,
+ .r8sint,
+ .rg8unorm,
+ .rg8unorm_srgb,
+ .rg8snorm,
+ .rg8uint,
+ .rg8sint,
+ .stencil8,
+ => 1,
+
+ // 16-bit pixel formats
+ .r16unorm,
+ .r16snorm,
+ .r16uint,
+ .r16sint,
+ .r16float,
+ .rg16unorm,
+ .rg16snorm,
+ .rg16uint,
+ .rg16sint,
+ .rg16float,
+ .b5g6r5unorm,
+ .a1bgr5unorm,
+ .abgr4unorm,
+ .bgr5a1unorm,
+ .depth16unorm,
+ => 2,
+
+ // 32-bit pixel formats
+ .rgba8unorm,
+ .rgba8unorm_srgb,
+ .rgba8snorm,
+ .rgba8uint,
+ .rgba8sint,
+ .bgra8unorm,
+ .bgra8unorm_srgb,
+ .rgb10a2unorm,
+ .rgb10a2uint,
+ .rg11b10float,
+ .rgb9e5float,
+ .bgr10a2unorm,
+ .bgr10_xr,
+ .bgr10_xr_srgb,
+ .r32uint,
+ .r32sint,
+ .r32float,
+ .depth32float,
+ .depth24unorm_stencil8,
+ => 4,
+
+ // 64-bit pixel formats
+ .rg32uint,
+ .rg32sint,
+ .rg32float,
+ .rgba16unorm,
+ .rgba16snorm,
+ .rgba16uint,
+ .rgba16sint,
+ .rgba16float,
+ .bgra10_xr,
+ .bgra10_xr_srgb,
+ => 8,
+
+ // 128-bit pixel formats,
+ .rgba32uint,
+ .rgba32sint,
+ .rgba32float,
+ => 128,
+ };
+}