mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
renderer/metal: dedicated cell bg shader
This commit is contained in:
@ -920,7 +920,7 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
|
||||
try self.drawImagePlacements(encoder, self.image_placements.items[0..self.image_bg_end]);
|
||||
|
||||
// Then draw background cells
|
||||
try self.drawCells(encoder, frame, frame.cells_bg, self.cells_bg.items.len);
|
||||
try self.drawCellBgs(encoder, frame, self.cells_bg.items.len);
|
||||
|
||||
// Then draw images under text
|
||||
try self.drawImagePlacements(encoder, self.image_placements.items[self.image_bg_end..self.image_text_end]);
|
||||
@ -1215,6 +1215,50 @@ fn drawImagePlacement(
|
||||
// log.debug("drawImagePlacement: {}", .{p});
|
||||
}
|
||||
|
||||
/// Draw the cell backgrounds.
|
||||
fn drawCellBgs(
|
||||
self: *Metal,
|
||||
encoder: objc.Object,
|
||||
frame: *const FrameState,
|
||||
len: usize,
|
||||
) !void {
|
||||
// This triggers an assertion in the Metal API if we try to draw
|
||||
// with an instance count of 0 so just bail.
|
||||
if (len == 0) return;
|
||||
|
||||
// Use our shader pipeline
|
||||
encoder.msgSend(
|
||||
void,
|
||||
objc.sel("setRenderPipelineState:"),
|
||||
.{self.shaders.cell_bg_pipeline.value},
|
||||
);
|
||||
|
||||
// Set our buffers
|
||||
encoder.msgSend(
|
||||
void,
|
||||
objc.sel("setVertexBuffer:offset:atIndex:"),
|
||||
.{ frame.cells_bg.buffer.value, @as(c_ulong, 0), @as(c_ulong, 0) },
|
||||
);
|
||||
encoder.msgSend(
|
||||
void,
|
||||
objc.sel("setVertexBuffer:offset:atIndex:"),
|
||||
.{ frame.uniforms.buffer.value, @as(c_ulong, 0), @as(c_ulong, 1) },
|
||||
);
|
||||
|
||||
encoder.msgSend(
|
||||
void,
|
||||
objc.sel("drawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferOffset:instanceCount:"),
|
||||
.{
|
||||
@intFromEnum(mtl.MTLPrimitiveType.triangle),
|
||||
@as(c_ulong, 6),
|
||||
@intFromEnum(mtl.MTLIndexType.uint16),
|
||||
self.gpu_state.instance.buffer.value,
|
||||
@as(c_ulong, 0),
|
||||
@as(c_ulong, len),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Loads some set of cell data into our buffer and issues a draw call.
|
||||
/// This expects all the Metal command encoder state to be setup.
|
||||
///
|
||||
|
@ -18,6 +18,10 @@ pub const Shaders = struct {
|
||||
/// foreground.
|
||||
cell_pipeline: objc.Object,
|
||||
|
||||
/// The cell background shader is the shader used to render the
|
||||
/// background of terminal cells.
|
||||
cell_bg_pipeline: objc.Object,
|
||||
|
||||
/// The image shader is the shader used to render images for things
|
||||
/// like the Kitty image protocol.
|
||||
image_pipeline: objc.Object,
|
||||
@ -43,6 +47,9 @@ pub const Shaders = struct {
|
||||
const cell_pipeline = try initCellPipeline(device, library);
|
||||
errdefer cell_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||
|
||||
const cell_bg_pipeline = try initCellBgPipeline(device, library);
|
||||
errdefer cell_bg_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||
|
||||
const image_pipeline = try initImagePipeline(device, library);
|
||||
errdefer image_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||
|
||||
@ -66,6 +73,7 @@ pub const Shaders = struct {
|
||||
return .{
|
||||
.library = library,
|
||||
.cell_pipeline = cell_pipeline,
|
||||
.cell_bg_pipeline = cell_bg_pipeline,
|
||||
.image_pipeline = image_pipeline,
|
||||
.post_pipelines = post_pipelines,
|
||||
};
|
||||
@ -74,6 +82,7 @@ pub const Shaders = struct {
|
||||
pub fn deinit(self: *Shaders, alloc: Allocator) void {
|
||||
// Release our primary shaders
|
||||
self.cell_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||
self.cell_bg_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||
self.image_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||
self.library.msgSend(void, objc.sel("release"), .{});
|
||||
|
||||
@ -493,6 +502,173 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
return pipeline_state;
|
||||
}
|
||||
|
||||
/// This is a single parameter for the cell bg shader.
|
||||
pub const CellBg = extern struct {
|
||||
mode: Mode,
|
||||
grid_pos: [2]f32,
|
||||
cell_width: u8,
|
||||
color: [4]u8,
|
||||
|
||||
pub const Mode = enum(u8) {
|
||||
rgb = 1,
|
||||
};
|
||||
};
|
||||
|
||||
/// Initialize the cell background render pipeline for our shader library.
|
||||
fn initCellBgPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
// Get our vertex and fragment functions
|
||||
const func_vert = func_vert: {
|
||||
const str = try macos.foundation.String.createWithBytes(
|
||||
"cell_bg_vertex",
|
||||
.utf8,
|
||||
false,
|
||||
);
|
||||
defer str.release();
|
||||
|
||||
const ptr = library.msgSend(?*anyopaque, objc.sel("newFunctionWithName:"), .{str});
|
||||
break :func_vert objc.Object.fromId(ptr.?);
|
||||
};
|
||||
defer func_vert.msgSend(void, objc.sel("release"), .{});
|
||||
const func_frag = func_frag: {
|
||||
const str = try macos.foundation.String.createWithBytes(
|
||||
"cell_bg_fragment",
|
||||
.utf8,
|
||||
false,
|
||||
);
|
||||
defer str.release();
|
||||
|
||||
const ptr = library.msgSend(?*anyopaque, objc.sel("newFunctionWithName:"), .{str});
|
||||
break :func_frag objc.Object.fromId(ptr.?);
|
||||
};
|
||||
defer func_frag.msgSend(void, objc.sel("release"), .{});
|
||||
|
||||
// Create the vertex descriptor. The vertex descriptor describes the
|
||||
// data layout of the vertex inputs. We use indexed (or "instanced")
|
||||
// rendering, so this makes it so that each instance gets a single
|
||||
// Cell as input.
|
||||
const vertex_desc = vertex_desc: {
|
||||
const desc = init: {
|
||||
const Class = objc.getClass("MTLVertexDescriptor").?;
|
||||
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;
|
||||
};
|
||||
|
||||
// Our attributes are the fields of the input
|
||||
const attrs = objc.Object.fromId(desc.getProperty(?*anyopaque, "attributes"));
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 0)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(CellBg, "mode")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 1)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.float2));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "grid_pos")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 2)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "cell_width")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 3)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar4));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "color")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
|
||||
// The layout describes how and when we fetch the next vertex input.
|
||||
const layouts = objc.Object.fromId(desc.getProperty(?*anyopaque, "layouts"));
|
||||
{
|
||||
const layout = layouts.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 0)},
|
||||
);
|
||||
|
||||
// Access each Cell per instance, not per vertex.
|
||||
layout.setProperty("stepFunction", @intFromEnum(mtl.MTLVertexStepFunction.per_instance));
|
||||
layout.setProperty("stride", @as(c_ulong, @sizeOf(Cell)));
|
||||
}
|
||||
|
||||
break :vertex_desc desc;
|
||||
};
|
||||
defer vertex_desc.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"), .{});
|
||||
|
||||
// Set our properties
|
||||
desc.setProperty("vertexFunction", func_vert);
|
||||
desc.setProperty("fragmentFunction", func_frag);
|
||||
desc.setProperty("vertexDescriptor", vertex_desc);
|
||||
|
||||
// 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));
|
||||
|
||||
// Blending. This is required so that our text we render on top
|
||||
// of our drawable properly blends into the bg.
|
||||
attachment.setProperty("blendingEnabled", true);
|
||||
attachment.setProperty("rgbBlendOperation", @intFromEnum(mtl.MTLBlendOperation.add));
|
||||
attachment.setProperty("alphaBlendOperation", @intFromEnum(mtl.MTLBlendOperation.add));
|
||||
attachment.setProperty("sourceRGBBlendFactor", @intFromEnum(mtl.MTLBlendFactor.one));
|
||||
attachment.setProperty("sourceAlphaBlendFactor", @intFromEnum(mtl.MTLBlendFactor.one));
|
||||
attachment.setProperty("destinationRGBBlendFactor", @intFromEnum(mtl.MTLBlendFactor.one_minus_source_alpha));
|
||||
attachment.setProperty("destinationAlphaBlendFactor", @intFromEnum(mtl.MTLBlendFactor.one_minus_source_alpha));
|
||||
}
|
||||
|
||||
// Make our state
|
||||
var err: ?*anyopaque = null;
|
||||
const pipeline_state = device.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("newRenderPipelineStateWithDescriptor:error:"),
|
||||
.{ desc, &err },
|
||||
);
|
||||
try checkError(err);
|
||||
errdefer pipeline_state.msgSend(void, objc.sel("release"), .{});
|
||||
|
||||
return pipeline_state;
|
||||
}
|
||||
|
||||
/// Initialize the image render pipeline for our shader library.
|
||||
fn initImagePipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
// Get our vertex and fragment functions
|
||||
|
@ -231,6 +231,78 @@ fragment float4 uber_fragment(VertexOut in [[stage_in]],
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Cell Background Shader
|
||||
//-------------------------------------------------------------------
|
||||
#pragma mark - Cell BG Shader
|
||||
|
||||
// The possible modes that a cell bg entry can take.
|
||||
enum CellbgMode : uint8_t {
|
||||
MODE_RGB = 1u,
|
||||
};
|
||||
|
||||
struct CellBgVertexIn {
|
||||
// The mode for this cell.
|
||||
uint8_t mode [[attribute(0)]];
|
||||
|
||||
// The grid coordinates (x, y) where x < columns and y < rows
|
||||
float2 grid_pos [[attribute(1)]];
|
||||
|
||||
// The width of the cell in cells (i.e. 2 for double-wide).
|
||||
uint8_t cell_width [[attribute(2)]];
|
||||
|
||||
// The color. For BG modes, this is the bg color, for FG modes this is
|
||||
// the text color. For styles, this is the color of the style.
|
||||
uchar4 color [[attribute(3)]];
|
||||
};
|
||||
|
||||
struct CellBgVertexOut {
|
||||
float4 position [[position]];
|
||||
float4 color;
|
||||
};
|
||||
|
||||
vertex CellBgVertexOut cell_bg_vertex(unsigned int vid [[vertex_id]],
|
||||
CellBgVertexIn input [[stage_in]],
|
||||
constant Uniforms& uniforms
|
||||
[[buffer(1)]]) {
|
||||
// Convert the grid x,y into world space x, y by accounting for cell size
|
||||
float2 cell_pos = uniforms.cell_size * input.grid_pos;
|
||||
|
||||
// Scaled cell size for the cell width
|
||||
float2 cell_size_scaled = uniforms.cell_size;
|
||||
cell_size_scaled.x = cell_size_scaled.x * input.cell_width;
|
||||
|
||||
// Turn the cell position into a vertex point depending on the
|
||||
// vertex ID. Since we use instanced drawing, we have 4 vertices
|
||||
// for each corner of the cell. We can use vertex ID to determine
|
||||
// which one we're looking at. Using this, we can use 1 or 0 to keep
|
||||
// or discard the value for the vertex.
|
||||
//
|
||||
// 0 = top-right
|
||||
// 1 = bot-right
|
||||
// 2 = bot-left
|
||||
// 3 = top-left
|
||||
float2 position;
|
||||
position.x = (vid == 0 || vid == 1) ? 1.0f : 0.0f;
|
||||
position.y = (vid == 0 || vid == 3) ? 0.0f : 1.0f;
|
||||
|
||||
// Calculate the final position of our cell in world space.
|
||||
// We have to add our cell size since our vertices are offset
|
||||
// one cell up and to the left. (Do the math to verify yourself)
|
||||
cell_pos = cell_pos + cell_size_scaled * position;
|
||||
|
||||
CellBgVertexOut out;
|
||||
out.color = float4(input.color) / 255.0f;
|
||||
out.position =
|
||||
uniforms.projection_matrix * float4(cell_pos.x, cell_pos.y, 0.0f, 1.0f);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
fragment float4 cell_bg_fragment(CellBgVertexOut in [[stage_in]]) {
|
||||
return in.color;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Image Shader
|
||||
//-------------------------------------------------------------------
|
||||
|
Reference in New Issue
Block a user