mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-23 12:16:11 +03:00
font: Atlas updated to use monotonic ID
This commit is contained in:
@ -38,18 +38,16 @@ nodes: std.ArrayListUnmanaged(Node) = .{},
|
||||
/// different formats, you must use multiple atlases or convert the textures.
|
||||
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 incremented every time the atlas is modified. This is useful
|
||||
/// for knowing if the texture data has changed since the last time it was
|
||||
/// sent to the GPU. It is up the user of the atlas to read this value atomically
|
||||
/// to observe it.
|
||||
modified: std.atomic.Value(usize) = .{ .raw = 0 },
|
||||
|
||||
/// 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,
|
||||
/// This will be incremented every time the atlas is resized. This is useful
|
||||
/// for knowing if a GPU texture can be updated in-place or if it requires
|
||||
/// a resize operation.
|
||||
resized: std.atomic.Value(usize) = .{ .raw = 0 },
|
||||
|
||||
pub const Format = enum(u8) {
|
||||
greyscale = 0,
|
||||
@ -99,7 +97,6 @@ pub fn init(alloc: Allocator, size: u32, format: Format) !Atlas {
|
||||
|
||||
// This sets up our initial state
|
||||
result.clear();
|
||||
result.modified = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -243,7 +240,7 @@ pub fn set(self: *Atlas, reg: Region, data: []const u8) void {
|
||||
);
|
||||
}
|
||||
|
||||
self.modified = true;
|
||||
_ = self.modified.fetchAdd(1, .monotonic);
|
||||
}
|
||||
|
||||
// Grow the texture to the new size, preserving all previously written data.
|
||||
@ -284,13 +281,13 @@ pub fn grow(self: *Atlas, alloc: Allocator, size_new: u32) Allocator.Error!void
|
||||
}, data_old[size_old * self.format.depth() ..]);
|
||||
|
||||
// We are both modified and resized
|
||||
self.modified = true;
|
||||
self.resized = true;
|
||||
_ = self.modified.fetchAdd(1, .monotonic);
|
||||
_ = self.resized.fetchAdd(1, .monotonic);
|
||||
}
|
||||
|
||||
// Empty the atlas. This doesn't reclaim any previously allocated memory.
|
||||
pub fn clear(self: *Atlas) void {
|
||||
self.modified = true;
|
||||
_ = self.modified.fetchAdd(1, .monotonic);
|
||||
@memset(self.data, 0);
|
||||
self.nodes.clearRetainingCapacity();
|
||||
|
||||
@ -475,8 +472,9 @@ test "exact fit" {
|
||||
var atlas = try init(alloc, 34, .greyscale); // +2 for 1px border
|
||||
defer atlas.deinit(alloc);
|
||||
|
||||
const modified = atlas.modified.load(.monotonic);
|
||||
_ = try atlas.reserve(alloc, 32, 32);
|
||||
try testing.expect(!atlas.modified);
|
||||
try testing.expectEqual(modified, atlas.modified.load(.monotonic));
|
||||
try testing.expectError(Error.AtlasFull, atlas.reserve(alloc, 1, 1));
|
||||
}
|
||||
|
||||
@ -505,9 +503,10 @@ test "writing data" {
|
||||
defer atlas.deinit(alloc);
|
||||
|
||||
const reg = try atlas.reserve(alloc, 2, 2);
|
||||
try testing.expect(!atlas.modified);
|
||||
const old = atlas.modified.load(.monotonic);
|
||||
atlas.set(reg, &[_]u8{ 1, 2, 3, 4 });
|
||||
try testing.expect(atlas.modified);
|
||||
const new = atlas.modified.load(.monotonic);
|
||||
try testing.expect(new > old);
|
||||
|
||||
// 33 because of the 1px border and so on
|
||||
try testing.expectEqual(@as(u8, 1), atlas.data[33]);
|
||||
@ -531,14 +530,14 @@ test "grow" {
|
||||
try testing.expectEqual(@as(u8, 3), atlas.data[9]);
|
||||
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.
|
||||
const old_modified = atlas.modified.load(.monotonic);
|
||||
const old_resized = atlas.resized.load(.monotonic);
|
||||
try atlas.grow(alloc, atlas.size + 1);
|
||||
try testing.expect(atlas.modified);
|
||||
try testing.expect(atlas.resized);
|
||||
const new_modified = atlas.modified.load(.monotonic);
|
||||
const new_resized = atlas.resized.load(.monotonic);
|
||||
try testing.expect(new_modified > old_modified);
|
||||
try testing.expect(new_resized > old_resized);
|
||||
_ = try atlas.reserve(alloc, 1, 1);
|
||||
|
||||
// Ensure our data is still set. Not the offsets change due to size.
|
||||
|
@ -5,6 +5,7 @@ const macos = @import("macos");
|
||||
const trace = @import("tracy").trace;
|
||||
const font = @import("../main.zig");
|
||||
const Face = font.Face;
|
||||
const Collection = font.Collection;
|
||||
const DeferredFace = font.DeferredFace;
|
||||
const Group = font.Group;
|
||||
const GroupCache = font.GroupCache;
|
||||
@ -397,7 +398,7 @@ test "run iterator" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -416,7 +417,7 @@ test "run iterator" {
|
||||
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -436,7 +437,7 @@ test "run iterator" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -483,7 +484,7 @@ test "run iterator: empty cells with background set" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -524,7 +525,7 @@ test "shape" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -559,7 +560,7 @@ test "shape nerd fonts" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -587,7 +588,7 @@ test "shape inconsolata ligs" {
|
||||
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -611,7 +612,7 @@ test "shape inconsolata ligs" {
|
||||
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -643,7 +644,7 @@ test "shape monaspace ligs" {
|
||||
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -675,7 +676,7 @@ test "shape emoji width" {
|
||||
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -715,7 +716,7 @@ test "shape emoji width long" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -752,7 +753,7 @@ test "shape variation selector VS15" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -787,7 +788,7 @@ test "shape variation selector VS16" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -819,7 +820,7 @@ test "shape with empty cells in between" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -857,7 +858,7 @@ test "shape Chinese characters" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -884,13 +885,6 @@ test "shape box glyphs" {
|
||||
var testdata = try testShaper(alloc);
|
||||
defer testdata.deinit();
|
||||
|
||||
// Setup the box font
|
||||
testdata.cache.group.sprite = font.sprite.Face{
|
||||
.width = 18,
|
||||
.height = 36,
|
||||
.thickness = 2,
|
||||
};
|
||||
|
||||
var buf: [32]u8 = undefined;
|
||||
var buf_idx: usize = 0;
|
||||
buf_idx += try std.unicode.utf8Encode(0x2500, buf[buf_idx..]); // horiz line
|
||||
@ -904,7 +898,7 @@ test "shape box glyphs" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -940,7 +934,7 @@ test "shape selection boundary" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
terminal.Selection.init(
|
||||
@ -963,7 +957,7 @@ test "shape selection boundary" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
terminal.Selection.init(
|
||||
@ -986,7 +980,7 @@ test "shape selection boundary" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
terminal.Selection.init(
|
||||
@ -1009,7 +1003,7 @@ test "shape selection boundary" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
terminal.Selection.init(
|
||||
@ -1032,7 +1026,7 @@ test "shape selection boundary" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
terminal.Selection.init(
|
||||
@ -1068,7 +1062,7 @@ test "shape cursor boundary" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -1087,7 +1081,7 @@ test "shape cursor boundary" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -1106,7 +1100,7 @@ test "shape cursor boundary" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -1125,7 +1119,7 @@ test "shape cursor boundary" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -1157,7 +1151,7 @@ test "shape cursor boundary and colored emoji" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -1176,7 +1170,7 @@ test "shape cursor boundary and colored emoji" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -1193,7 +1187,7 @@ test "shape cursor boundary and colored emoji" {
|
||||
// Get our run iterator
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -1223,7 +1217,7 @@ test "shape cell attribute change" {
|
||||
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -1247,7 +1241,7 @@ test "shape cell attribute change" {
|
||||
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -1272,7 +1266,7 @@ test "shape cell attribute change" {
|
||||
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -1297,7 +1291,7 @@ test "shape cell attribute change" {
|
||||
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -1321,7 +1315,7 @@ test "shape cell attribute change" {
|
||||
|
||||
var shaper = &testdata.shaper;
|
||||
var it = shaper.runIterator(
|
||||
testdata.cache,
|
||||
testdata.grid,
|
||||
&screen,
|
||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||
null,
|
||||
@ -1339,13 +1333,13 @@ test "shape cell attribute change" {
|
||||
const TestShaper = struct {
|
||||
alloc: Allocator,
|
||||
shaper: Shaper,
|
||||
cache: *GroupCache,
|
||||
grid: *SharedGrid,
|
||||
lib: Library,
|
||||
|
||||
pub fn deinit(self: *TestShaper) void {
|
||||
self.shaper.deinit();
|
||||
self.cache.deinit(self.alloc);
|
||||
self.alloc.destroy(self.cache);
|
||||
self.grid.deinit(self.alloc);
|
||||
self.alloc.destroy(self.grid);
|
||||
self.lib.deinit();
|
||||
}
|
||||
};
|
||||
@ -1373,17 +1367,11 @@ fn testShaperWithFont(alloc: Allocator, font_req: TestFont) !TestShaper {
|
||||
var lib = try Library.init();
|
||||
errdefer lib.deinit();
|
||||
|
||||
var cache_ptr = try alloc.create(GroupCache);
|
||||
errdefer alloc.destroy(cache_ptr);
|
||||
cache_ptr.* = try GroupCache.init(alloc, try Group.init(
|
||||
alloc,
|
||||
lib,
|
||||
.{ .points = 12 },
|
||||
));
|
||||
errdefer cache_ptr.*.deinit(alloc);
|
||||
var c = try Collection.init(alloc);
|
||||
c.load_options = .{ .library = lib };
|
||||
|
||||
// Setup group
|
||||
_ = try cache_ptr.group.addFace(.regular, .{ .loaded = try Face.init(
|
||||
_ = try c.add(alloc, .regular, .{ .loaded = try Face.init(
|
||||
lib,
|
||||
testFont,
|
||||
.{ .size = .{ .points = 12 } },
|
||||
@ -1391,7 +1379,7 @@ fn testShaperWithFont(alloc: Allocator, font_req: TestFont) !TestShaper {
|
||||
|
||||
if (font.options.backend != .coretext) {
|
||||
// Coretext doesn't support Noto's format
|
||||
_ = try cache_ptr.group.addFace(.regular, .{ .loaded = try Face.init(
|
||||
_ = try c.add(alloc, .regular, .{ .loaded = try Face.init(
|
||||
lib,
|
||||
testEmoji,
|
||||
.{ .size = .{ .points = 12 } },
|
||||
@ -1408,21 +1396,26 @@ fn testShaperWithFont(alloc: Allocator, font_req: TestFont) !TestShaper {
|
||||
defer disco_it.deinit();
|
||||
var face = (try disco_it.next()).?;
|
||||
errdefer face.deinit();
|
||||
_ = try cache_ptr.group.addFace(.regular, .{ .deferred = face });
|
||||
_ = try c.add(alloc, .regular, .{ .deferred = face });
|
||||
}
|
||||
_ = try cache_ptr.group.addFace(.regular, .{ .loaded = try Face.init(
|
||||
_ = try c.add(alloc, .regular, .{ .loaded = try Face.init(
|
||||
lib,
|
||||
testEmojiText,
|
||||
.{ .size = .{ .points = 12 } },
|
||||
) });
|
||||
|
||||
const grid_ptr = try alloc.create(SharedGrid);
|
||||
errdefer alloc.destroy(grid_ptr);
|
||||
grid_ptr.* = try SharedGrid.init(alloc, .{ .collection = c }, false);
|
||||
errdefer grid_ptr.*.deinit(alloc);
|
||||
|
||||
var shaper = try Shaper.init(alloc, .{});
|
||||
errdefer shaper.deinit();
|
||||
|
||||
return TestShaper{
|
||||
.alloc = alloc,
|
||||
.shaper = shaper,
|
||||
.cache = cache_ptr,
|
||||
.grid = grid_ptr,
|
||||
.lib = lib,
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user