mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
atlas tracks modified/resize state, reallocate on GPU if resized
This commit is contained in:
@ -35,6 +35,19 @@ nodes: std.ArrayListUnmanaged(Node) = .{},
|
|||||||
/// different formats, you must use multiple atlases or convert the textures.
|
/// different formats, you must use multiple atlases or convert the textures.
|
||||||
format: Format = .greyscale,
|
format: Format = .greyscale,
|
||||||
|
|
||||||
|
/// This will be set to true when the atlas has data set on it. It is up
|
||||||
|
/// to the user of the atlas to set this to false when they observe the value.
|
||||||
|
/// This is a useful value to know if you need to send new data to the GPU or
|
||||||
|
/// not.
|
||||||
|
modified: bool = false,
|
||||||
|
|
||||||
|
/// This will be set to true when the atlas has been resized. It is up
|
||||||
|
/// to the user of the atlas to set this to false when they observe the value.
|
||||||
|
/// The resized value is useful for sending textures to the GPU to know if
|
||||||
|
/// a new texture needs to be allocated or if an existing one can be
|
||||||
|
/// updated in-place.
|
||||||
|
resized: bool = false,
|
||||||
|
|
||||||
pub const Format = enum(u3) {
|
pub const Format = enum(u3) {
|
||||||
greyscale = 1,
|
greyscale = 1,
|
||||||
rgb = 3,
|
rgb = 3,
|
||||||
@ -75,6 +88,7 @@ pub fn init(alloc: Allocator, size: u32, format: Format) !Atlas {
|
|||||||
|
|
||||||
// This sets up our initial state
|
// This sets up our initial state
|
||||||
result.clear();
|
result.clear();
|
||||||
|
result.modified = false;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -217,6 +231,8 @@ pub fn set(self: *Atlas, reg: Region, data: []const u8) void {
|
|||||||
data[data_offset .. data_offset + (reg.width * depth)],
|
data[data_offset .. data_offset + (reg.width * depth)],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.modified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grow the texture to the new size, preserving all previously written data.
|
// Grow the texture to the new size, preserving all previously written data.
|
||||||
@ -255,10 +271,15 @@ pub fn grow(self: *Atlas, alloc: Allocator, size_new: u32) Allocator.Error!void
|
|||||||
.width = size_old,
|
.width = size_old,
|
||||||
.height = size_old - 2, // skip the last border row
|
.height = size_old - 2, // skip the last border row
|
||||||
}, data_old[size_old * @enumToInt(self.format) ..]);
|
}, data_old[size_old * @enumToInt(self.format) ..]);
|
||||||
|
|
||||||
|
// We are both modified and resized
|
||||||
|
self.modified = true;
|
||||||
|
self.resized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty the atlas. This doesn't reclaim any previously allocated memory.
|
// Empty the atlas. This doesn't reclaim any previously allocated memory.
|
||||||
pub fn clear(self: *Atlas) void {
|
pub fn clear(self: *Atlas) void {
|
||||||
|
self.modified = true;
|
||||||
std.mem.set(u8, self.data, 0);
|
std.mem.set(u8, self.data, 0);
|
||||||
self.nodes.clearRetainingCapacity();
|
self.nodes.clearRetainingCapacity();
|
||||||
|
|
||||||
@ -274,6 +295,7 @@ test "exact fit" {
|
|||||||
defer atlas.deinit(alloc);
|
defer atlas.deinit(alloc);
|
||||||
|
|
||||||
_ = try atlas.reserve(alloc, 32, 32);
|
_ = try atlas.reserve(alloc, 32, 32);
|
||||||
|
try testing.expect(!atlas.modified);
|
||||||
try testing.expectError(Error.AtlasFull, atlas.reserve(alloc, 1, 1));
|
try testing.expectError(Error.AtlasFull, atlas.reserve(alloc, 1, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,7 +324,9 @@ test "writing data" {
|
|||||||
defer atlas.deinit(alloc);
|
defer atlas.deinit(alloc);
|
||||||
|
|
||||||
const reg = try atlas.reserve(alloc, 2, 2);
|
const reg = try atlas.reserve(alloc, 2, 2);
|
||||||
|
try testing.expect(!atlas.modified);
|
||||||
atlas.set(reg, &[_]u8{ 1, 2, 3, 4 });
|
atlas.set(reg, &[_]u8{ 1, 2, 3, 4 });
|
||||||
|
try testing.expect(atlas.modified);
|
||||||
|
|
||||||
// 33 because of the 1px border and so on
|
// 33 because of the 1px border and so on
|
||||||
try testing.expectEqual(@as(u8, 1), atlas.data[33]);
|
try testing.expectEqual(@as(u8, 1), atlas.data[33]);
|
||||||
@ -326,8 +350,14 @@ test "grow" {
|
|||||||
try testing.expectEqual(@as(u8, 3), atlas.data[9]);
|
try testing.expectEqual(@as(u8, 3), atlas.data[9]);
|
||||||
try testing.expectEqual(@as(u8, 4), atlas.data[10]);
|
try testing.expectEqual(@as(u8, 4), atlas.data[10]);
|
||||||
|
|
||||||
|
// Reset our state
|
||||||
|
atlas.modified = false;
|
||||||
|
atlas.resized = false;
|
||||||
|
|
||||||
// Expand by exactly 1 should fit our new 1x1 block.
|
// Expand by exactly 1 should fit our new 1x1 block.
|
||||||
try atlas.grow(alloc, atlas.size + 1);
|
try atlas.grow(alloc, atlas.size + 1);
|
||||||
|
try testing.expect(atlas.modified);
|
||||||
|
try testing.expect(atlas.resized);
|
||||||
_ = try atlas.reserve(alloc, 1, 1);
|
_ = try atlas.reserve(alloc, 1, 1);
|
||||||
|
|
||||||
// Ensure our data is still set. Not the offsets change due to size.
|
// Ensure our data is still set. Not the offsets change due to size.
|
||||||
|
96
src/Grid.zig
96
src/Grid.zig
@ -46,7 +46,6 @@ texture_color: gl.Texture,
|
|||||||
|
|
||||||
/// The font atlas.
|
/// The font atlas.
|
||||||
font_set: font.FallbackSet,
|
font_set: font.FallbackSet,
|
||||||
atlas_dirty: bool,
|
|
||||||
|
|
||||||
/// Whether the cursor is visible or not. This is used to control cursor
|
/// Whether the cursor is visible or not. This is used to control cursor
|
||||||
/// blinking.
|
/// blinking.
|
||||||
@ -331,7 +330,6 @@ pub fn init(alloc: Allocator, config: *const Config) !Grid {
|
|||||||
.texture = tex,
|
.texture = tex,
|
||||||
.texture_color = tex_color,
|
.texture_color = tex_color,
|
||||||
.font_set = font_set,
|
.font_set = font_set,
|
||||||
.atlas_dirty = false,
|
|
||||||
.cursor_visible = true,
|
.cursor_visible = true,
|
||||||
.cursor_style = .box,
|
.cursor_style = .box,
|
||||||
.background = .{ .r = 0, .g = 0, .b = 0 },
|
.background = .{ .r = 0, .g = 0, .b = 0 },
|
||||||
@ -411,12 +409,9 @@ pub fn finalizeCells(self: *Grid, term: Terminal) !void {
|
|||||||
try self.rebuildCells(term);
|
try self.rebuildCells(term);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If our atlas is dirty, we need to flush it
|
// Try to flush our atlas, this will only do something if there
|
||||||
if (self.atlas_dirty) {
|
// are changes to the atlas.
|
||||||
log.info("atlas dirty, flushing changes", .{});
|
try self.flushAtlas();
|
||||||
try self.flushAtlas();
|
|
||||||
self.atlas_dirty = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn addCursor(self: *Grid, term: Terminal) void {
|
fn addCursor(self: *Grid, term: Terminal) void {
|
||||||
@ -561,7 +556,6 @@ pub fn updateCell(
|
|||||||
|
|
||||||
// Get our glyph. Try our normal font atlas first.
|
// Get our glyph. Try our normal font atlas first.
|
||||||
const goa = try self.font_set.getOrAddGlyph(self.alloc, cell.char, style);
|
const goa = try self.font_set.getOrAddGlyph(self.alloc, cell.char, style);
|
||||||
if (!goa.found_existing) self.atlas_dirty = true;
|
|
||||||
if (goa.family == 1) mode = .fg_color;
|
if (goa.family == 1) mode = .fg_color;
|
||||||
const glyph = goa.glyph;
|
const glyph = goa.glyph;
|
||||||
|
|
||||||
@ -645,34 +639,70 @@ pub fn setScreenSize(self: *Grid, dim: ScreenSize) !void {
|
|||||||
fn flushAtlas(self: *Grid) !void {
|
fn flushAtlas(self: *Grid) !void {
|
||||||
{
|
{
|
||||||
const atlas = &self.font_set.families.items[0].atlas;
|
const atlas = &self.font_set.families.items[0].atlas;
|
||||||
var texbind = try self.texture.bind(.@"2D");
|
if (atlas.modified) {
|
||||||
defer texbind.unbind();
|
atlas.modified = false;
|
||||||
try texbind.subImage2D(
|
var texbind = try self.texture.bind(.@"2D");
|
||||||
0,
|
defer texbind.unbind();
|
||||||
0,
|
|
||||||
0,
|
if (atlas.resized) {
|
||||||
@intCast(c_int, atlas.size),
|
atlas.resized = false;
|
||||||
@intCast(c_int, atlas.size),
|
try texbind.image2D(
|
||||||
.Red,
|
0,
|
||||||
.UnsignedByte,
|
.Red,
|
||||||
atlas.data.ptr,
|
@intCast(c_int, atlas.size),
|
||||||
);
|
@intCast(c_int, atlas.size),
|
||||||
|
0,
|
||||||
|
.Red,
|
||||||
|
.UnsignedByte,
|
||||||
|
atlas.data.ptr,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
try texbind.subImage2D(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
@intCast(c_int, atlas.size),
|
||||||
|
@intCast(c_int, atlas.size),
|
||||||
|
.Red,
|
||||||
|
.UnsignedByte,
|
||||||
|
atlas.data.ptr,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const atlas = &self.font_set.families.items[1].atlas;
|
const atlas = &self.font_set.families.items[1].atlas;
|
||||||
var texbind = try self.texture_color.bind(.@"2D");
|
if (atlas.modified) {
|
||||||
defer texbind.unbind();
|
atlas.modified = false;
|
||||||
try texbind.subImage2D(
|
var texbind = try self.texture_color.bind(.@"2D");
|
||||||
0,
|
defer texbind.unbind();
|
||||||
0,
|
|
||||||
0,
|
if (atlas.resized) {
|
||||||
@intCast(c_int, atlas.size),
|
atlas.resized = false;
|
||||||
@intCast(c_int, atlas.size),
|
try texbind.image2D(
|
||||||
.BGRA,
|
0,
|
||||||
.UnsignedByte,
|
.RGBA,
|
||||||
atlas.data.ptr,
|
@intCast(c_int, atlas.size),
|
||||||
);
|
@intCast(c_int, atlas.size),
|
||||||
|
0,
|
||||||
|
.BGRA,
|
||||||
|
.UnsignedByte,
|
||||||
|
atlas.data.ptr,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
try texbind.subImage2D(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
@intCast(c_int, atlas.size),
|
||||||
|
@intCast(c_int, atlas.size),
|
||||||
|
.BGRA,
|
||||||
|
.UnsignedByte,
|
||||||
|
atlas.data.ptr,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user