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]);
|
try self.drawImagePlacements(encoder, self.image_placements.items[0..self.image_bg_end]);
|
||||||
|
|
||||||
// Then draw background cells
|
// 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
|
// Then draw images under text
|
||||||
try self.drawImagePlacements(encoder, self.image_placements.items[self.image_bg_end..self.image_text_end]);
|
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});
|
// 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.
|
/// 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.
|
/// This expects all the Metal command encoder state to be setup.
|
||||||
///
|
///
|
||||||
|
@ -18,6 +18,10 @@ pub const Shaders = struct {
|
|||||||
/// foreground.
|
/// foreground.
|
||||||
cell_pipeline: objc.Object,
|
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
|
/// The image shader is the shader used to render images for things
|
||||||
/// like the Kitty image protocol.
|
/// like the Kitty image protocol.
|
||||||
image_pipeline: objc.Object,
|
image_pipeline: objc.Object,
|
||||||
@ -43,6 +47,9 @@ pub const Shaders = struct {
|
|||||||
const cell_pipeline = try initCellPipeline(device, library);
|
const cell_pipeline = try initCellPipeline(device, library);
|
||||||
errdefer cell_pipeline.msgSend(void, objc.sel("release"), .{});
|
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);
|
const image_pipeline = try initImagePipeline(device, library);
|
||||||
errdefer image_pipeline.msgSend(void, objc.sel("release"), .{});
|
errdefer image_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||||
|
|
||||||
@ -66,6 +73,7 @@ pub const Shaders = struct {
|
|||||||
return .{
|
return .{
|
||||||
.library = library,
|
.library = library,
|
||||||
.cell_pipeline = cell_pipeline,
|
.cell_pipeline = cell_pipeline,
|
||||||
|
.cell_bg_pipeline = cell_bg_pipeline,
|
||||||
.image_pipeline = image_pipeline,
|
.image_pipeline = image_pipeline,
|
||||||
.post_pipelines = post_pipelines,
|
.post_pipelines = post_pipelines,
|
||||||
};
|
};
|
||||||
@ -74,6 +82,7 @@ pub const Shaders = struct {
|
|||||||
pub fn deinit(self: *Shaders, alloc: Allocator) void {
|
pub fn deinit(self: *Shaders, alloc: Allocator) void {
|
||||||
// Release our primary shaders
|
// Release our primary shaders
|
||||||
self.cell_pipeline.msgSend(void, objc.sel("release"), .{});
|
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.image_pipeline.msgSend(void, objc.sel("release"), .{});
|
||||||
self.library.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;
|
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.
|
/// Initialize the image render pipeline for our shader library.
|
||||||
fn initImagePipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
fn initImagePipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||||
// Get our vertex and fragment functions
|
// 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
|
// Image Shader
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
|
Reference in New Issue
Block a user