mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
renderer/metal: emit draw calls for images
This commit is contained in:
@ -33,6 +33,8 @@ const CellBuffer = mtl_buffer.Buffer(mtl_shaders.Cell);
|
|||||||
const ImageBuffer = mtl_buffer.Buffer(mtl_shaders.Image);
|
const ImageBuffer = mtl_buffer.Buffer(mtl_shaders.Image);
|
||||||
const InstanceBuffer = mtl_buffer.Buffer(u16);
|
const InstanceBuffer = mtl_buffer.Buffer(u16);
|
||||||
|
|
||||||
|
const ImagePlacementList = std.ArrayListUnmanaged(mtl_image.Placement);
|
||||||
|
|
||||||
// Get native API access on certain platforms so we can do more customization.
|
// Get native API access on certain platforms so we can do more customization.
|
||||||
const glfwNative = glfw.Native(.{
|
const glfwNative = glfw.Native(.{
|
||||||
.cocoa = builtin.os.tag == .macos,
|
.cocoa = builtin.os.tag == .macos,
|
||||||
@ -82,6 +84,7 @@ font_shaper: font.Shaper,
|
|||||||
|
|
||||||
/// The images that we may render.
|
/// The images that we may render.
|
||||||
images: ImageMap = .{},
|
images: ImageMap = .{},
|
||||||
|
image_placements: ImagePlacementList = .{},
|
||||||
|
|
||||||
/// Metal state
|
/// Metal state
|
||||||
shaders: Shaders, // Compiled shaders
|
shaders: Shaders, // Compiled shaders
|
||||||
@ -285,6 +288,7 @@ pub fn deinit(self: *Metal) void {
|
|||||||
while (it.next()) |kv| kv.value_ptr.deinit(self.alloc);
|
while (it.next()) |kv| kv.value_ptr.deinit(self.alloc);
|
||||||
self.images.deinit(self.alloc);
|
self.images.deinit(self.alloc);
|
||||||
}
|
}
|
||||||
|
self.image_placements.deinit(self.alloc);
|
||||||
|
|
||||||
self.buf_cells_bg.deinit();
|
self.buf_cells_bg.deinit();
|
||||||
self.buf_cells.deinit();
|
self.buf_cells.deinit();
|
||||||
@ -632,49 +636,151 @@ pub fn render(
|
|||||||
);
|
);
|
||||||
defer encoder.msgSend(void, objc.sel("endEncoding"), .{});
|
defer encoder.msgSend(void, objc.sel("endEncoding"), .{});
|
||||||
|
|
||||||
// Use our shader pipeline
|
// Terminal grid
|
||||||
encoder.msgSend(
|
{
|
||||||
void,
|
// Use our shader pipeline
|
||||||
objc.sel("setRenderPipelineState:"),
|
encoder.msgSend(
|
||||||
.{self.shaders.cell_pipeline.value},
|
void,
|
||||||
);
|
objc.sel("setRenderPipelineState:"),
|
||||||
|
.{self.shaders.cell_pipeline.value},
|
||||||
|
);
|
||||||
|
|
||||||
// Set our buffers
|
// Set our buffers
|
||||||
encoder.msgSend(
|
encoder.msgSend(
|
||||||
void,
|
void,
|
||||||
objc.sel("setVertexBytes:length:atIndex:"),
|
objc.sel("setVertexBytes:length:atIndex:"),
|
||||||
.{
|
.{
|
||||||
@as(*const anyopaque, @ptrCast(&self.uniforms)),
|
@as(*const anyopaque, @ptrCast(&self.uniforms)),
|
||||||
@as(c_ulong, @sizeOf(@TypeOf(self.uniforms))),
|
@as(c_ulong, @sizeOf(@TypeOf(self.uniforms))),
|
||||||
@as(c_ulong, 1),
|
@as(c_ulong, 1),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
encoder.msgSend(
|
encoder.msgSend(
|
||||||
void,
|
void,
|
||||||
objc.sel("setFragmentTexture:atIndex:"),
|
objc.sel("setFragmentTexture:atIndex:"),
|
||||||
.{
|
.{
|
||||||
self.texture_greyscale.value,
|
self.texture_greyscale.value,
|
||||||
@as(c_ulong, 0),
|
@as(c_ulong, 0),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
encoder.msgSend(
|
encoder.msgSend(
|
||||||
void,
|
void,
|
||||||
objc.sel("setFragmentTexture:atIndex:"),
|
objc.sel("setFragmentTexture:atIndex:"),
|
||||||
.{
|
.{
|
||||||
self.texture_color.value,
|
self.texture_color.value,
|
||||||
@as(c_ulong, 1),
|
@as(c_ulong, 1),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Issue the draw calls for this shader
|
// Issue the draw calls for this shader
|
||||||
try self.drawCells(encoder, &self.buf_cells_bg, self.cells_bg);
|
try self.drawCells(encoder, &self.buf_cells_bg, self.cells_bg);
|
||||||
try self.drawCells(encoder, &self.buf_cells, self.cells);
|
try self.drawCells(encoder, &self.buf_cells, self.cells);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Images
|
||||||
|
// TODO: these should not go above text
|
||||||
|
if (self.image_placements.items.len > 0) {
|
||||||
|
// Use our image shader pipeline
|
||||||
|
encoder.msgSend(
|
||||||
|
void,
|
||||||
|
objc.sel("setRenderPipelineState:"),
|
||||||
|
.{self.shaders.image_pipeline.value},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set our uniform, which is the only shared buffer
|
||||||
|
encoder.msgSend(
|
||||||
|
void,
|
||||||
|
objc.sel("setVertexBytes:length:atIndex:"),
|
||||||
|
.{
|
||||||
|
@as(*const anyopaque, @ptrCast(&self.uniforms)),
|
||||||
|
@as(c_ulong, @sizeOf(@TypeOf(self.uniforms))),
|
||||||
|
@as(c_ulong, 1),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
for (self.image_placements.items) |placement| {
|
||||||
|
try self.drawImagePlacement(encoder, placement);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 drawImagePlacement(
|
||||||
|
self: *Metal,
|
||||||
|
encoder: objc.Object,
|
||||||
|
p: mtl_image.Placement,
|
||||||
|
) !void {
|
||||||
|
// Look up the image
|
||||||
|
const image = self.images.get(p.image_id) orelse {
|
||||||
|
log.warn("image not found for placement image_id={}", .{p.image_id});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the texture
|
||||||
|
const texture = switch (image) {
|
||||||
|
.ready => |t| t,
|
||||||
|
else => {
|
||||||
|
log.warn("image not ready for placement image_id={}", .{p.image_id});
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create our vertex buffer, which is always exactly one item.
|
||||||
|
// future(mitchellh): we can group rendering multiple instances of a single image
|
||||||
|
const Buffer = mtl_buffer.Buffer(mtl_shaders.Image);
|
||||||
|
var buf = try Buffer.initFill(self.device, &.{.{
|
||||||
|
.grid_pos = .{
|
||||||
|
@as(f32, @floatFromInt(p.x)),
|
||||||
|
@as(f32, @floatFromInt(p.y)),
|
||||||
|
},
|
||||||
|
}});
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
|
// Set our buffer
|
||||||
|
encoder.msgSend(
|
||||||
|
void,
|
||||||
|
objc.sel("setVertexBuffer:offset:atIndex:"),
|
||||||
|
.{ buf.buffer.value, @as(c_ulong, 0), @as(c_ulong, 0) },
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set our texture
|
||||||
|
encoder.msgSend(
|
||||||
|
void,
|
||||||
|
objc.sel("setVertexTexture:atIndex:"),
|
||||||
|
.{
|
||||||
|
texture.value,
|
||||||
|
@as(c_ulong, 0),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
encoder.msgSend(
|
||||||
|
void,
|
||||||
|
objc.sel("setFragmentTexture:atIndex:"),
|
||||||
|
.{
|
||||||
|
texture.value,
|
||||||
|
@as(c_ulong, 0),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Draw!
|
||||||
|
encoder.msgSend(
|
||||||
|
void,
|
||||||
|
objc.sel("drawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferOffset:instanceCount:"),
|
||||||
|
.{
|
||||||
|
@intFromEnum(mtl.MTLPrimitiveType.triangle),
|
||||||
|
@as(c_ulong, 6),
|
||||||
|
@intFromEnum(mtl.MTLIndexType.uint16),
|
||||||
|
self.buf_instance.buffer.value,
|
||||||
|
@as(c_ulong, 0),
|
||||||
|
@as(c_ulong, 1),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// log.debug("drawImagePlacement: {}", .{p});
|
||||||
|
}
|
||||||
|
|
||||||
/// 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.
|
||||||
///
|
///
|
||||||
@ -718,6 +824,10 @@ fn prepKittyGraphics(
|
|||||||
) !void {
|
) !void {
|
||||||
defer screen.kitty_images.dirty = false;
|
defer screen.kitty_images.dirty = false;
|
||||||
|
|
||||||
|
// We always clear our previous placements no matter what because
|
||||||
|
// we rebuild them from scratch.
|
||||||
|
self.image_placements.clearRetainingCapacity();
|
||||||
|
|
||||||
// Go through our known images and if there are any that are no longer
|
// Go through our known images and if there are any that are no longer
|
||||||
// in use then mark them to be freed.
|
// in use then mark them to be freed.
|
||||||
//
|
//
|
||||||
@ -737,33 +847,42 @@ fn prepKittyGraphics(
|
|||||||
while (it.next()) |kv| {
|
while (it.next()) |kv| {
|
||||||
// If we already know about this image then do nothing
|
// If we already know about this image then do nothing
|
||||||
const gop = try self.images.getOrPut(self.alloc, kv.key_ptr.image_id);
|
const gop = try self.images.getOrPut(self.alloc, kv.key_ptr.image_id);
|
||||||
if (gop.found_existing) continue;
|
if (!gop.found_existing) {
|
||||||
|
// Find the image in storage
|
||||||
|
const image = screen.kitty_images.imageById(kv.key_ptr.image_id) orelse {
|
||||||
|
log.warn(
|
||||||
|
"missing image for placement, ignoring image_id={}",
|
||||||
|
.{kv.key_ptr.image_id},
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
// Find the image in storage
|
// Copy the data into the pending state.
|
||||||
const image = screen.kitty_images.imageById(kv.key_ptr.image_id) orelse {
|
const data = try self.alloc.dupe(u8, image.data);
|
||||||
log.warn(
|
errdefer self.alloc.free(data);
|
||||||
"missing image for placement, ignoring image_id={}",
|
|
||||||
.{kv.key_ptr.image_id},
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Copy the data into the pending state.
|
// Store it in the map
|
||||||
const data = try self.alloc.dupe(u8, image.data);
|
const p: Image.Pending = .{
|
||||||
errdefer self.alloc.free(data);
|
.width = image.width,
|
||||||
|
.height = image.height,
|
||||||
|
.data = data.ptr,
|
||||||
|
};
|
||||||
|
|
||||||
// Store it in the map
|
gop.value_ptr.* = switch (image.format) {
|
||||||
const p: Image.Pending = .{
|
.rgb => .{ .pending_rgb = p },
|
||||||
.width = image.width,
|
.rgba => .{ .pending_rgba = p },
|
||||||
.height = image.height,
|
.png => unreachable, // should be decoded by now
|
||||||
.data = data.ptr,
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
gop.value_ptr.* = switch (image.format) {
|
// Accumulate the placement
|
||||||
.rgb => .{ .pending_rgb = p },
|
try self.image_placements.append(self.alloc, .{
|
||||||
.rgba => .{ .pending_rgba = p },
|
.image_id = kv.key_ptr.image_id,
|
||||||
.png => unreachable, // should be decoded by now
|
|
||||||
};
|
// TODO
|
||||||
|
.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,17 @@ const objc = @import("objc");
|
|||||||
|
|
||||||
const mtl = @import("api.zig");
|
const mtl = @import("api.zig");
|
||||||
|
|
||||||
|
/// Represents a single image placement on the grid. A placement is a
|
||||||
|
/// request to render an instance of an image.
|
||||||
|
pub const Placement = struct {
|
||||||
|
/// The image being rendered. This MUST be in the image map.
|
||||||
|
image_id: u32,
|
||||||
|
|
||||||
|
/// The grid x/y where this placement is located.
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
};
|
||||||
|
|
||||||
/// The map used for storing images.
|
/// The map used for storing images.
|
||||||
pub const ImageMap = std.AutoHashMapUnmanaged(u32, Image);
|
pub const ImageMap = std.AutoHashMapUnmanaged(u32, Image);
|
||||||
|
|
||||||
|
@ -204,7 +204,7 @@ struct ImageVertexOut {
|
|||||||
vertex ImageVertexOut image_vertex(
|
vertex ImageVertexOut image_vertex(
|
||||||
unsigned int vid [[ vertex_id ]],
|
unsigned int vid [[ vertex_id ]],
|
||||||
ImageVertexIn input [[ stage_in ]],
|
ImageVertexIn input [[ stage_in ]],
|
||||||
texture2d<float> image [[ texture(0) ]],
|
texture2d<uint> image [[ texture(0) ]],
|
||||||
constant Uniforms &uniforms [[ buffer(1) ]]
|
constant Uniforms &uniforms [[ buffer(1) ]]
|
||||||
) {
|
) {
|
||||||
// The position of our image starts at the top-left of the grid cell.
|
// The position of our image starts at the top-left of the grid cell.
|
||||||
@ -244,8 +244,13 @@ vertex ImageVertexOut image_vertex(
|
|||||||
|
|
||||||
fragment float4 image_fragment(
|
fragment float4 image_fragment(
|
||||||
ImageVertexOut in [[ stage_in ]],
|
ImageVertexOut in [[ stage_in ]],
|
||||||
texture2d<float> image [[ texture(0) ]]
|
texture2d<uint> image [[ texture(0) ]]
|
||||||
) {
|
) {
|
||||||
constexpr sampler textureSampler(address::clamp_to_edge, filter::linear);
|
constexpr sampler textureSampler(address::clamp_to_edge, filter::linear);
|
||||||
return image.sample(textureSampler, in.tex_coord);
|
|
||||||
|
// Ehhhhh our texture is in RGBA8Uint but our color attachment is
|
||||||
|
// BGRA8Unorm. So we need to convert it. We should really be converting
|
||||||
|
// our texture to BGRA8Unorm.
|
||||||
|
uint4 rgba = image.sample(textureSampler, in.tex_coord);
|
||||||
|
return float4(rgba) / 255.0f;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user