diff --git a/src/font/Atlas.zig b/src/font/Atlas.zig index 01ee5caa6..fcdf6ec53 100644 --- a/src/font/Atlas.zig +++ b/src/font/Atlas.zig @@ -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. diff --git a/src/font/shaper/coretext.zig b/src/font/shaper/coretext.zig index 2ea316637..3aaaef5d3 100644 --- a/src/font/shaper/coretext.zig +++ b/src/font/shaper/coretext.zig @@ -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, }; }