diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-04 19:22:04 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 01:23:06 +0300 |
| commit | 41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch) | |
| tree | 3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/renderer/metal/RenderPass.zig | |
| parent | 55c138263e190a1e79d4b997bfb9397306c0ec1f (diff) | |
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/renderer/metal/RenderPass.zig')
| -rw-r--r-- | vendor/libghostty-vt/src/renderer/metal/RenderPass.zig | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/renderer/metal/RenderPass.zig b/vendor/libghostty-vt/src/renderer/metal/RenderPass.zig new file mode 100644 index 00000000..f204e177 --- /dev/null +++ b/vendor/libghostty-vt/src/renderer/metal/RenderPass.zig @@ -0,0 +1,229 @@ +//! Wrapper for handling render passes. +const Self = @This(); + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const objc = @import("objc"); + +const mtl = @import("api.zig"); +const Pipeline = @import("Pipeline.zig"); +const Sampler = @import("Sampler.zig"); +const Texture = @import("Texture.zig"); +const Target = @import("Target.zig"); + +const log = std.log.scoped(.metal); + +/// Options for beginning a render pass. +pub const Options = struct { + /// MTLCommandBuffer + command_buffer: objc.Object, + /// Color attachments for this render pass. + attachments: []const Attachment, + + /// Describes a color attachment. + pub const Attachment = struct { + target: union(enum) { + texture: Texture, + target: Target, + }, + clear_color: ?[4]f64 = null, + }; +}; + +/// Describes a step in a render pass. +pub const Step = struct { + pipeline: Pipeline, + /// MTLBuffer + uniforms: ?objc.Object = null, + /// MTLBuffer + buffers: []const ?objc.Object = &.{}, + textures: []const ?Texture = &.{}, + /// Set of samplers to use for this step. The index maps to an index + /// of a fragment texture, set via setFragmentSamplerState(_:index:). + samplers: []const ?Sampler = &.{}, + draw: Draw, + + /// Describes the draw call for this step. + pub const Draw = struct { + type: mtl.MTLPrimitiveType, + vertex_count: usize, + instance_count: usize = 1, + }; +}; + +/// MTLRenderCommandEncoder +encoder: objc.Object, + +/// Begin a render pass. +pub fn begin( + opts: Options, +) Self { + // Create a pass descriptor + 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"), + ); + for (opts.attachments, 0..) |at, i| { + const attachment = attachments.msgSend( + objc.Object, + objc.sel("objectAtIndexedSubscript:"), + .{@as(c_ulong, i)}, + ); + + attachment.setProperty( + "loadAction", + @intFromEnum(@as( + mtl.MTLLoadAction, + if (at.clear_color != null) + .clear + else + .load, + )), + ); + attachment.setProperty( + "storeAction", + @intFromEnum(mtl.MTLStoreAction.store), + ); + attachment.setProperty("texture", switch (at.target) { + .texture => |t| t.texture.value, + .target => |t| t.texture.value, + }); + if (at.clear_color) |c| attachment.setProperty( + "clearColor", + mtl.MTLClearColor{ + .red = c[0], + .green = c[1], + .blue = c[2], + .alpha = c[3], + }, + ); + } + + break :desc desc; + }; + + // MTLRenderCommandEncoder + const encoder = opts.command_buffer.msgSend( + objc.Object, + objc.sel("renderCommandEncoderWithDescriptor:"), + .{desc.value}, + ); + + return .{ .encoder = encoder }; +} + +/// Add a step to this render pass. +pub fn step(self: *const Self, s: Step) void { + if (s.draw.instance_count == 0) return; + + // Set pipeline state + self.encoder.msgSend( + void, + objc.sel("setRenderPipelineState:"), + .{s.pipeline.state.value}, + ); + + if (s.buffers.len > 0) { + // We reserve index 0 for the vertex buffer, this isn't very + // flexible but it lines up with the API we have for OpenGL. + if (s.buffers[0]) |buf| { + self.encoder.msgSend( + void, + objc.sel("setVertexBuffer:offset:atIndex:"), + .{ buf.value, @as(c_ulong, 0), @as(c_ulong, 0) }, + ); + self.encoder.msgSend( + void, + objc.sel("setFragmentBuffer:offset:atIndex:"), + .{ buf.value, @as(c_ulong, 0), @as(c_ulong, 0) }, + ); + } + + // Set the rest of the buffers starting at index 2, this is + // so that we can use index 1 for the uniforms if present. + // + // Also, we set buffers (and textures) for both stages. + // + // Again, not very flexible, but it's consistent and predictable, + // and we need to treat the uniforms as special because of OpenGL. + // + // TODO: Maybe in the future add info to the pipeline struct which + // allows it to define a mapping between provided buffers and + // what index they get set at for the vertex / fragment stage. + for (s.buffers[1..], 2..) |b, i| if (b) |buf| { + self.encoder.msgSend( + void, + objc.sel("setVertexBuffer:offset:atIndex:"), + .{ buf.value, @as(c_ulong, 0), @as(c_ulong, i) }, + ); + self.encoder.msgSend( + void, + objc.sel("setFragmentBuffer:offset:atIndex:"), + .{ buf.value, @as(c_ulong, 0), @as(c_ulong, i) }, + ); + }; + } + + // Set the uniforms as buffer index 1 if present. + if (s.uniforms) |buf| { + self.encoder.msgSend( + void, + objc.sel("setVertexBuffer:offset:atIndex:"), + .{ buf.value, @as(c_ulong, 0), @as(c_ulong, 1) }, + ); + self.encoder.msgSend( + void, + objc.sel("setFragmentBuffer:offset:atIndex:"), + .{ buf.value, @as(c_ulong, 0), @as(c_ulong, 1) }, + ); + } + + // Set textures. + for (s.textures, 0..) |t, i| if (t) |tex| { + self.encoder.msgSend( + void, + objc.sel("setVertexTexture:atIndex:"), + .{ tex.texture.value, @as(c_ulong, i) }, + ); + self.encoder.msgSend( + void, + objc.sel("setFragmentTexture:atIndex:"), + .{ tex.texture.value, @as(c_ulong, i) }, + ); + }; + + // Set samplers. + for (s.samplers, 0..) |samp, i| if (samp) |sampler| { + self.encoder.msgSend( + void, + objc.sel("setFragmentSamplerState:atIndex:"), + .{ sampler.sampler.value, @as(c_ulong, i) }, + ); + }; + + // Draw! + self.encoder.msgSend( + void, + objc.sel("drawPrimitives:vertexStart:vertexCount:instanceCount:"), + .{ + @intFromEnum(s.draw.type), + @as(c_ulong, 0), + @as(c_ulong, s.draw.vertex_count), + @as(c_ulong, s.draw.instance_count), + }, + ); +} + +/// Complete this render pass. +/// This struct can no longer be used after calling this. +pub fn complete(self: *const Self) void { + self.encoder.msgSend(void, objc.sel("endEncoding"), .{}); +} |
