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.
images: ImageMap = .{},
image_placements: ImagePlacementList = .{},
image_bg_end: u32 = 0,
image_text_end: u32 = 0,
/// Metal state
shaders: Shaders, // Compiled shaders
@ -636,78 +638,56 @@ pub fn render(
);
defer encoder.msgSend(void, objc.sel("endEncoding"), .{});
// Terminal grid
{
// Use our shader pipeline
encoder.msgSend(
void,
objc.sel("setRenderPipelineState:"),
.{self.shaders.cell_pipeline.value},
);
// Draw background images first
try self.drawImagePlacements(encoder, self.image_placements.items[0..self.image_bg_end]);
// 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),
},
);
// Then draw background cells
try self.drawCells(encoder, &self.buf_cells_bg, self.cells_bg);
// 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, self.cells);
}
// Then draw images under text
try self.drawImagePlacements(encoder, self.image_placements.items[0..self.image_text_end]);
// 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},
);
// Then draw fg cells
try self.drawCells(encoder, &self.buf_cells, self.cells);
// 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);
}
}
// Then draw remaining images
try self.drawImagePlacements(encoder, self.image_placements.items[self.image_text_end..]);
}
buffer.msgSend(void, objc.sel("presentDrawable:"), .{drawable.value});
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(
self: *Metal,
encoder: objc.Object,
@ -809,27 +789,61 @@ fn drawCells(
buf: *CellBuffer,
cells: std.ArrayListUnmanaged(mtl_shaders.Cell),
) !void {
if (cells.items.len == 0) return;
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(
void,
objc.sel("setVertexBuffer:offset:atIndex:"),
.{ buf.buffer.value, @as(c_ulong, 0), @as(c_ulong, 0) },
);
if (cells.items.len > 0) {
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, cells.items.len),
},
);
}
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, cells.items.len),
},
);
}
/// 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.
const image_sel = kv.value_ptr.selection(image, t);
const image_sel = p.selection(image, t);
if (!image_sel.within(top, bot)) continue;
// 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
const viewport = kv.value_ptr.point.toViewport(&t.screen);
const viewport = p.point.toViewport(&t.screen);
// Calculate the source rectangle
const source_x = @min(image.width, p.source_x);
@ -937,12 +951,13 @@ fn prepKittyGraphics(
if (image.width > 0 and image.height > 0) {
try self.image_placements.append(self.alloc, .{
.image_id = kv.key_ptr.image_id,
.x = @intCast(kv.value_ptr.point.x),
.x = @intCast(p.point.x),
.y = @intCast(viewport.y),
.z = p.z,
.width = dest_width,
.height = dest_height,
.cell_offset_x = kv.value_ptr.x_offset,
.cell_offset_y = kv.value_ptr.y_offset,
.cell_offset_x = p.x_offset,
.cell_offset_y = p.y_offset,
.source_x = source_x,
.source_y = source_y,
.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.

View File

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

View File

@ -198,9 +198,16 @@ pub const CommandParser = struct {
}
}
// Parse the value as a string
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);
// Only "z" is currently signed. This is a bit of a kloodge; if more
// fields become signed we can rethink this but for now we parse
// "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
self.kv_temp_len = 0;
@ -419,7 +426,7 @@ pub const Display = struct {
rows: u32 = 0, // r
cursor_movement: CursorMovement = .after, // C
virtual_placement: bool = false, // U
z: u32 = 0, // z
z: i32 = 0, // z
pub const CursorMovement = enum {
after, // 0
@ -490,7 +497,8 @@ pub const Display = struct {
}
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;
@ -693,7 +701,7 @@ pub const Delete = union(enum) {
delete: bool = false, // uppercase
x: u32 = 0, // x
y: u32 = 0, // y
z: u32 = 0, // z
z: i32 = 0, // z
},
// x/X
@ -711,7 +719,7 @@ pub const Delete = union(enum) {
// z/Z
z: struct {
delete: bool = false, // uppercase
z: u32 = 0, // z
z: i32 = 0, // z
},
fn parse(kv: KV) !Delete {
@ -773,7 +781,8 @@ pub const Delete = union(enum) {
result.intersect_cell_z.y = 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;
@ -800,7 +809,8 @@ pub const Delete = union(enum) {
'z', 'Z' => blk: {
var result: Delete = .{ .z = .{ .delete = what == 'Z' } };
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;

View File

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

View File

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