terminal/kitty-gfx: honor "z" setting

This commit is contained in:
Mitchell Hashimoto
2023-08-23 09:55:13 -07:00
parent 33c21c7339
commit 4b38fb96db
5 changed files with 150 additions and 90 deletions

View File

@ -85,6 +85,8 @@ font_shaper: font.Shaper,
/// The images that we may render. /// The images that we may render.
images: ImageMap = .{}, images: ImageMap = .{},
image_placements: ImagePlacementList = .{}, image_placements: ImagePlacementList = .{},
image_bg_end: u32 = 0,
image_text_end: u32 = 0,
/// Metal state /// Metal state
shaders: Shaders, // Compiled shaders shaders: Shaders, // Compiled shaders
@ -636,78 +638,56 @@ pub fn render(
); );
defer encoder.msgSend(void, objc.sel("endEncoding"), .{}); defer encoder.msgSend(void, objc.sel("endEncoding"), .{});
// Terminal grid // Draw background images first
{ try self.drawImagePlacements(encoder, self.image_placements.items[0..self.image_bg_end]);
// Use our shader pipeline
encoder.msgSend(
void,
objc.sel("setRenderPipelineState:"),
.{self.shaders.cell_pipeline.value},
);
// Set our buffers // Then draw background cells
encoder.msgSend( try self.drawCells(encoder, &self.buf_cells_bg, self.cells_bg);
void,
objc.sel("setVertexBytes:length:atIndex:"),
.{
@as(*const anyopaque, @ptrCast(&self.uniforms)),
@as(c_ulong, @sizeOf(@TypeOf(self.uniforms))),
@as(c_ulong, 1),
},
);
encoder.msgSend(
void,
objc.sel("setFragmentTexture:atIndex:"),
.{
self.texture_greyscale.value,
@as(c_ulong, 0),
},
);
encoder.msgSend(
void,
objc.sel("setFragmentTexture:atIndex:"),
.{
self.texture_color.value,
@as(c_ulong, 1),
},
);
// Issue the draw calls for this shader // Then draw images under text
try self.drawCells(encoder, &self.buf_cells_bg, self.cells_bg); try self.drawImagePlacements(encoder, self.image_placements.items[0..self.image_text_end]);
try self.drawCells(encoder, &self.buf_cells, self.cells);
}
// Images // Then draw fg cells
// TODO: these should not go above text try self.drawCells(encoder, &self.buf_cells, self.cells);
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 // Then draw remaining images
encoder.msgSend( try self.drawImagePlacements(encoder, self.image_placements.items[self.image_text_end..]);
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 drawImagePlacements(
self: *Metal,
encoder: objc.Object,
placements: []const mtl_image.Placement,
) !void {
if (placements.len == 0) return;
// 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 (placements) |placement| {
try self.drawImagePlacement(encoder, placement);
}
}
fn drawImagePlacement( fn drawImagePlacement(
self: *Metal, self: *Metal,
encoder: objc.Object, encoder: objc.Object,
@ -809,27 +789,61 @@ fn drawCells(
buf: *CellBuffer, buf: *CellBuffer,
cells: std.ArrayListUnmanaged(mtl_shaders.Cell), cells: std.ArrayListUnmanaged(mtl_shaders.Cell),
) !void { ) !void {
if (cells.items.len == 0) return;
try buf.sync(self.device, cells.items); try buf.sync(self.device, cells.items);
// Use our shader pipeline
encoder.msgSend(
void,
objc.sel("setRenderPipelineState:"),
.{self.shaders.cell_pipeline.value},
);
// Set our buffers
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),
},
);
encoder.msgSend(
void,
objc.sel("setFragmentTexture:atIndex:"),
.{
self.texture_greyscale.value,
@as(c_ulong, 0),
},
);
encoder.msgSend(
void,
objc.sel("setFragmentTexture:atIndex:"),
.{
self.texture_color.value,
@as(c_ulong, 1),
},
);
encoder.msgSend( encoder.msgSend(
void, void,
objc.sel("setVertexBuffer:offset:atIndex:"), objc.sel("setVertexBuffer:offset:atIndex:"),
.{ buf.buffer.value, @as(c_ulong, 0), @as(c_ulong, 0) }, .{ buf.buffer.value, @as(c_ulong, 0), @as(c_ulong, 0) },
); );
if (cells.items.len > 0) { encoder.msgSend(
encoder.msgSend( void,
void, objc.sel("drawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferOffset:instanceCount:"),
objc.sel("drawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferOffset:instanceCount:"), .{
.{ @intFromEnum(mtl.MTLPrimitiveType.triangle),
@intFromEnum(mtl.MTLPrimitiveType.triangle), @as(c_ulong, 6),
@as(c_ulong, 6), @intFromEnum(mtl.MTLIndexType.uint16),
@intFromEnum(mtl.MTLIndexType.uint16), self.buf_instance.buffer.value,
self.buf_instance.buffer.value, @as(c_ulong, 0),
@as(c_ulong, 0), @as(c_ulong, cells.items.len),
@as(c_ulong, cells.items.len), },
}, );
);
}
} }
/// This goes through the Kitty graphic placements and accumulates the /// This goes through the Kitty graphic placements and accumulates the
@ -882,7 +896,7 @@ fn prepKittyGraphics(
}; };
// If the selection isn't within our viewport then skip it. // If the selection isn't within our viewport then skip it.
const image_sel = kv.value_ptr.selection(image, t); const image_sel = p.selection(image, t);
if (!image_sel.within(top, bot)) continue; if (!image_sel.within(top, bot)) continue;
// If the top left is outside the viewport we need to calc an offset // If the top left is outside the viewport we need to calc an offset
@ -915,7 +929,7 @@ fn prepKittyGraphics(
} }
// Convert our screen point to a viewport point // Convert our screen point to a viewport point
const viewport = kv.value_ptr.point.toViewport(&t.screen); const viewport = p.point.toViewport(&t.screen);
// Calculate the source rectangle // Calculate the source rectangle
const source_x = @min(image.width, p.source_x); const source_x = @min(image.width, p.source_x);
@ -937,12 +951,13 @@ fn prepKittyGraphics(
if (image.width > 0 and image.height > 0) { if (image.width > 0 and image.height > 0) {
try self.image_placements.append(self.alloc, .{ try self.image_placements.append(self.alloc, .{
.image_id = kv.key_ptr.image_id, .image_id = kv.key_ptr.image_id,
.x = @intCast(kv.value_ptr.point.x), .x = @intCast(p.point.x),
.y = @intCast(viewport.y), .y = @intCast(viewport.y),
.z = p.z,
.width = dest_width, .width = dest_width,
.height = dest_height, .height = dest_height,
.cell_offset_x = kv.value_ptr.x_offset, .cell_offset_x = p.x_offset,
.cell_offset_y = kv.value_ptr.y_offset, .cell_offset_y = p.y_offset,
.source_x = source_x, .source_x = source_x,
.source_y = source_y, .source_y = source_y,
.source_width = source_width, .source_width = source_width,
@ -950,6 +965,36 @@ fn prepKittyGraphics(
}); });
} }
} }
// Sort the placements by their Z value.
std.mem.sortUnstable(
mtl_image.Placement,
self.image_placements.items,
{},
struct {
fn lessThan(
ctx: void,
lhs: mtl_image.Placement,
rhs: mtl_image.Placement,
) bool {
_ = ctx;
return lhs.z < rhs.z or (lhs.z == rhs.z and lhs.image_id < rhs.image_id);
}
}.lessThan,
);
// Find our indices
self.image_bg_end = 0;
self.image_text_end = 0;
const bg_limit = std.math.minInt(i32) / 2;
for (self.image_placements.items, 0..) |p, i| {
if (self.image_bg_end == 0 and p.z >= bg_limit) {
self.image_bg_end = @intCast(i);
}
if (self.image_text_end == 0 and p.z >= 0) {
self.image_text_end = @intCast(i);
}
}
} }
/// Update the configuration. /// Update the configuration.

View File

@ -14,6 +14,7 @@ pub const Placement = struct {
/// The grid x/y where this placement is located. /// The grid x/y where this placement is located.
x: u32, x: u32,
y: u32, y: u32,
z: i32,
/// The width/height of the placed image. /// The width/height of the placed image.
width: u32, width: u32,

View File

@ -198,9 +198,16 @@ pub const CommandParser = struct {
} }
} }
// Parse the value as a string // Only "z" is currently signed. This is a bit of a kloodge; if more
const v = try std.fmt.parseInt(u32, self.kv_temp[0..self.kv_temp_len], 10); // fields become signed we can rethink this but for now we parse
try self.kv.put(alloc, self.kv_current, v); // "z" as i32 then bitcast it to u32 then bitcast it back later.
if (self.kv_current == 'z') {
const v = try std.fmt.parseInt(i32, self.kv_temp[0..self.kv_temp_len], 10);
try self.kv.put(alloc, self.kv_current, @bitCast(v));
} else {
const v = try std.fmt.parseInt(u32, self.kv_temp[0..self.kv_temp_len], 10);
try self.kv.put(alloc, self.kv_current, v);
}
// Clear our temp buffer // Clear our temp buffer
self.kv_temp_len = 0; self.kv_temp_len = 0;
@ -419,7 +426,7 @@ pub const Display = struct {
rows: u32 = 0, // r rows: u32 = 0, // r
cursor_movement: CursorMovement = .after, // C cursor_movement: CursorMovement = .after, // C
virtual_placement: bool = false, // U virtual_placement: bool = false, // U
z: u32 = 0, // z z: i32 = 0, // z
pub const CursorMovement = enum { pub const CursorMovement = enum {
after, // 0 after, // 0
@ -490,7 +497,8 @@ pub const Display = struct {
} }
if (kv.get('z')) |v| { if (kv.get('z')) |v| {
result.z = v; // We can bitcast here because of how we parse it earlier.
result.z = @bitCast(v);
} }
return result; return result;
@ -693,7 +701,7 @@ pub const Delete = union(enum) {
delete: bool = false, // uppercase delete: bool = false, // uppercase
x: u32 = 0, // x x: u32 = 0, // x
y: u32 = 0, // y y: u32 = 0, // y
z: u32 = 0, // z z: i32 = 0, // z
}, },
// x/X // x/X
@ -711,7 +719,7 @@ pub const Delete = union(enum) {
// z/Z // z/Z
z: struct { z: struct {
delete: bool = false, // uppercase delete: bool = false, // uppercase
z: u32 = 0, // z z: i32 = 0, // z
}, },
fn parse(kv: KV) !Delete { fn parse(kv: KV) !Delete {
@ -773,7 +781,8 @@ pub const Delete = union(enum) {
result.intersect_cell_z.y = v; result.intersect_cell_z.y = v;
} }
if (kv.get('z')) |v| { if (kv.get('z')) |v| {
result.intersect_cell_z.z = v; // We can bitcast here because of how we parse it earlier.
result.intersect_cell_z.z = @bitCast(v);
} }
break :blk result; break :blk result;
@ -800,7 +809,8 @@ pub const Delete = union(enum) {
'z', 'Z' => blk: { 'z', 'Z' => blk: {
var result: Delete = .{ .z = .{ .delete = what == 'Z' } }; var result: Delete = .{ .z = .{ .delete = what == 'Z' } };
if (kv.get('z')) |v| { if (kv.get('z')) |v| {
result.z.z = v; // We can bitcast here because of how we parse it earlier.
result.z.z = @bitCast(v);
} }
break :blk result; break :blk result;

View File

@ -178,6 +178,7 @@ fn display(
.source_height = d.height, .source_height = d.height,
.columns = d.columns, .columns = d.columns,
.rows = d.rows, .rows = d.rows,
.z = d.z,
}; };
storage.addPlacement(alloc, img.id, d.placement_id, p) catch |err| { storage.addPlacement(alloc, img.id, d.placement_id, p) catch |err| {
encodeError(&result, err); encodeError(&result, err);

View File

@ -170,6 +170,9 @@ pub const ImageStorage = struct {
columns: u32 = 0, columns: u32 = 0,
rows: u32 = 0, rows: u32 = 0,
/// The z-index for this placement.
z: i32 = 0,
/// Returns a selection of the entire rectangle this placement /// Returns a selection of the entire rectangle this placement
/// occupies within the screen. /// occupies within the screen.
pub fn selection( pub fn selection(