mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-21 11:16:08 +03:00
renderer/opengl: convert to SharedGrid, new windows/tabs are frozen
This commit is contained in:
@ -72,8 +72,12 @@ gl_cells_written: usize = 0,
|
||||
gl_state: ?GLState = null,
|
||||
|
||||
/// The font structures.
|
||||
font_group: *font.GroupCache,
|
||||
font_grid: *font.SharedGrid,
|
||||
font_shaper: font.Shaper,
|
||||
texture_greyscale_modified: usize = 0,
|
||||
texture_greyscale_resized: usize = 0,
|
||||
texture_color_modified: usize = 0,
|
||||
texture_color_resized: usize = 0,
|
||||
|
||||
/// True if the window is focused
|
||||
focused: bool,
|
||||
@ -333,14 +337,13 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
|
||||
});
|
||||
errdefer shaper.deinit();
|
||||
|
||||
// Setup our font metrics uniform
|
||||
const metrics = try resetFontMetrics(
|
||||
alloc,
|
||||
options.font_group,
|
||||
options.config.font_thicken,
|
||||
);
|
||||
// For the remainder of the setup we lock our font grid data because
|
||||
// we're reading it.
|
||||
const grid = options.font_grid;
|
||||
grid.lock.lockShared();
|
||||
defer grid.lock.unlockShared();
|
||||
|
||||
var gl_state = try GLState.init(alloc, options.config, options.font_group);
|
||||
var gl_state = try GLState.init(alloc, options.config, grid);
|
||||
errdefer gl_state.deinit();
|
||||
|
||||
return OpenGL{
|
||||
@ -348,10 +351,10 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
|
||||
.config = options.config,
|
||||
.cells_bg = .{},
|
||||
.cells = .{},
|
||||
.grid_metrics = metrics,
|
||||
.grid_metrics = grid.metrics,
|
||||
.screen_size = null,
|
||||
.gl_state = gl_state,
|
||||
.font_group = options.font_group,
|
||||
.font_grid = grid,
|
||||
.font_shaper = shaper,
|
||||
.draw_background = options.config.background,
|
||||
.focused = true,
|
||||
@ -360,7 +363,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
|
||||
.cursor_color = options.config.cursor_color,
|
||||
.padding = options.padding,
|
||||
.surface_mailbox = options.surface_mailbox,
|
||||
.deferred_font_size = .{ .metrics = metrics },
|
||||
.deferred_font_size = .{ .metrics = grid.metrics },
|
||||
.deferred_config = .{},
|
||||
};
|
||||
}
|
||||
@ -470,15 +473,16 @@ pub fn displayRealize(self: *OpenGL) !void {
|
||||
if (single_threaded_draw) self.draw_mutex.lock();
|
||||
defer if (single_threaded_draw) self.draw_mutex.unlock();
|
||||
|
||||
// Reset our GPU uniforms
|
||||
const metrics = try resetFontMetrics(
|
||||
self.alloc,
|
||||
self.font_group,
|
||||
self.config.font_thicken,
|
||||
);
|
||||
|
||||
// Make our new state
|
||||
var gl_state = try GLState.init(self.alloc, self.config, self.font_group);
|
||||
var gl_state = gl_state: {
|
||||
self.font_grid.lock.lockShared();
|
||||
defer self.font_grid.lock.unlockShared();
|
||||
break :gl_state try GLState.init(
|
||||
self.alloc,
|
||||
self.config,
|
||||
self.font_grid,
|
||||
);
|
||||
};
|
||||
errdefer gl_state.deinit();
|
||||
|
||||
// Unrealize if we have to
|
||||
@ -491,14 +495,16 @@ pub fn displayRealize(self: *OpenGL) !void {
|
||||
// reflush everything
|
||||
self.gl_cells_size = 0;
|
||||
self.gl_cells_written = 0;
|
||||
self.font_group.atlas_greyscale.modified = true;
|
||||
self.font_group.atlas_color.modified = true;
|
||||
self.texture_greyscale_modified = 0;
|
||||
self.texture_color_modified = 0;
|
||||
self.texture_greyscale_resized = 0;
|
||||
self.texture_color_resized = 0;
|
||||
|
||||
// We need to reset our uniforms
|
||||
if (self.screen_size) |size| {
|
||||
self.deferred_screen_size = .{ .size = size };
|
||||
}
|
||||
self.deferred_font_size = .{ .metrics = metrics };
|
||||
self.deferred_font_size = .{ .metrics = self.grid_metrics };
|
||||
self.deferred_config = .{};
|
||||
}
|
||||
|
||||
@ -585,64 +591,40 @@ pub fn setFocus(self: *OpenGL, focus: bool) !void {
|
||||
///
|
||||
/// Must be called on the render thread.
|
||||
pub fn setFontSize(self: *OpenGL, size: font.face.DesiredSize) !void {
|
||||
if (single_threaded_draw) self.draw_mutex.lock();
|
||||
defer if (single_threaded_draw) self.draw_mutex.unlock();
|
||||
|
||||
log.info("set font size={}", .{size});
|
||||
|
||||
// Set our new size, this will also reset our font atlas.
|
||||
try self.font_group.setSize(size);
|
||||
|
||||
// Reset our GPU uniforms
|
||||
const metrics = try resetFontMetrics(
|
||||
self.alloc,
|
||||
self.font_group,
|
||||
self.config.font_thicken,
|
||||
);
|
||||
|
||||
// Defer our GPU updates
|
||||
self.deferred_font_size = .{ .metrics = metrics };
|
||||
|
||||
// Recalculate our cell size. If it is the same as before, then we do
|
||||
// nothing since the grid size couldn't have possibly changed.
|
||||
if (std.meta.eql(self.grid_metrics, metrics)) return;
|
||||
self.grid_metrics = metrics;
|
||||
|
||||
// Notify the window that the cell size changed.
|
||||
_ = self.surface_mailbox.push(.{
|
||||
.cell_size = .{
|
||||
.width = metrics.cell_width,
|
||||
.height = metrics.cell_height,
|
||||
},
|
||||
}, .{ .forever = {} });
|
||||
}
|
||||
|
||||
/// Reload the font metrics, recalculate cell size, and send that all
|
||||
/// down to the GPU.
|
||||
fn resetFontMetrics(
|
||||
alloc: Allocator,
|
||||
font_group: *font.GroupCache,
|
||||
font_thicken: bool,
|
||||
) !font.face.Metrics {
|
||||
// Get our cell metrics based on a regular font ascii 'M'. Why 'M'?
|
||||
// Doesn't matter, any normal ASCII will do we're just trying to make
|
||||
// sure we use the regular font.
|
||||
const metrics = metrics: {
|
||||
const index = (try font_group.indexForCodepoint(alloc, 'M', .regular, .text)).?;
|
||||
const face = try font_group.group.faceFromIndex(index);
|
||||
break :metrics face.metrics;
|
||||
};
|
||||
log.debug("cell dimensions={}", .{metrics});
|
||||
|
||||
// Set details for our sprite font
|
||||
font_group.group.sprite = font.sprite.Face{
|
||||
.width = metrics.cell_width,
|
||||
.height = metrics.cell_height,
|
||||
.thickness = metrics.underline_thickness * @as(u32, if (font_thicken) 2 else 1),
|
||||
.underline_position = metrics.underline_position,
|
||||
};
|
||||
|
||||
return metrics;
|
||||
_ = self;
|
||||
_ = size;
|
||||
if (true) @panic("TODO"); // TODO(fontmem)
|
||||
//
|
||||
// if (single_threaded_draw) self.draw_mutex.lock();
|
||||
// defer if (single_threaded_draw) self.draw_mutex.unlock();
|
||||
//
|
||||
// log.info("set font size={}", .{size});
|
||||
//
|
||||
// // Set our new size, this will also reset our font atlas.
|
||||
// try self.font_group.setSize(size);
|
||||
//
|
||||
// // Reset our GPU uniforms
|
||||
// const metrics = try resetFontMetrics(
|
||||
// self.alloc,
|
||||
// self.font_group,
|
||||
// self.config.font_thicken,
|
||||
// );
|
||||
//
|
||||
// // Defer our GPU updates
|
||||
// self.deferred_font_size = .{ .metrics = metrics };
|
||||
//
|
||||
// // Recalculate our cell size. If it is the same as before, then we do
|
||||
// // nothing since the grid size couldn't have possibly changed.
|
||||
// if (std.meta.eql(self.grid_metrics, metrics)) return;
|
||||
// self.grid_metrics = metrics;
|
||||
//
|
||||
// // Notify the window that the cell size changed.
|
||||
// _ = self.surface_mailbox.push(.{
|
||||
// .cell_size = .{
|
||||
// .width = metrics.cell_width,
|
||||
// .height = metrics.cell_height,
|
||||
// },
|
||||
// }, .{ .forever = {} });
|
||||
}
|
||||
|
||||
/// The primary render callback that is completely thread-safe.
|
||||
@ -1056,7 +1038,7 @@ pub fn rebuildCells(
|
||||
|
||||
// Split our row into runs and shape each one.
|
||||
var iter = self.font_shaper.runIterator(
|
||||
self.font_group,
|
||||
self.font_grid,
|
||||
screen,
|
||||
row,
|
||||
selection,
|
||||
@ -1170,33 +1152,21 @@ fn addPreeditCell(
|
||||
const bg = self.foreground_color;
|
||||
const fg = self.background_color;
|
||||
|
||||
// Get the font for this codepoint.
|
||||
const font_index = if (self.font_group.indexForCodepoint(
|
||||
// Render the glyph for our preedit text
|
||||
const render_ = self.font_grid.renderCodepoint(
|
||||
self.alloc,
|
||||
@intCast(cp.codepoint),
|
||||
.regular,
|
||||
.text,
|
||||
)) |index| index orelse return else |_| return;
|
||||
|
||||
// Get the font face so we can get the glyph
|
||||
const face = self.font_group.group.faceFromIndex(font_index) catch |err| {
|
||||
log.warn("error getting face for font_index={} err={}", .{ font_index, err });
|
||||
return;
|
||||
};
|
||||
|
||||
// Use the face to now get the glyph index
|
||||
const glyph_index = face.glyphIndex(@intCast(cp.codepoint)) orelse return;
|
||||
|
||||
// Render the glyph for our preedit text
|
||||
const glyph = self.font_group.renderGlyph(
|
||||
self.alloc,
|
||||
font_index,
|
||||
glyph_index,
|
||||
.{ .grid_metrics = self.grid_metrics },
|
||||
) catch |err| {
|
||||
log.warn("error rendering preedit glyph err={}", .{err});
|
||||
return;
|
||||
};
|
||||
const render = render_ orelse {
|
||||
log.warn("failed to find font for preedit codepoint={X}", .{cp.codepoint});
|
||||
return;
|
||||
};
|
||||
|
||||
// Add our opaque background cell
|
||||
self.cells_bg.appendAssumeCapacity(.{
|
||||
@ -1226,12 +1196,12 @@ fn addPreeditCell(
|
||||
.grid_col = @intCast(x),
|
||||
.grid_row = @intCast(y),
|
||||
.grid_width = if (cp.wide) 2 else 1,
|
||||
.glyph_x = glyph.atlas_x,
|
||||
.glyph_y = glyph.atlas_y,
|
||||
.glyph_width = glyph.width,
|
||||
.glyph_height = glyph.height,
|
||||
.glyph_offset_x = glyph.offset_x,
|
||||
.glyph_offset_y = glyph.offset_y,
|
||||
.glyph_x = render.glyph.atlas_x,
|
||||
.glyph_y = render.glyph.atlas_y,
|
||||
.glyph_width = render.glyph.width,
|
||||
.glyph_height = render.glyph.height,
|
||||
.glyph_offset_x = render.glyph.offset_x,
|
||||
.glyph_offset_y = render.glyph.offset_y,
|
||||
.r = fg.r,
|
||||
.g = fg.g,
|
||||
.b = fg.b,
|
||||
@ -1275,13 +1245,13 @@ fn addCursor(
|
||||
.underline => .underline,
|
||||
};
|
||||
|
||||
const glyph = self.font_group.renderGlyph(
|
||||
const render = self.font_grid.renderGlyph(
|
||||
self.alloc,
|
||||
font.sprite_index,
|
||||
@intFromEnum(sprite),
|
||||
.{
|
||||
.grid_metrics = self.grid_metrics,
|
||||
.cell_width = if (wide) 2 else 1,
|
||||
.grid_metrics = self.grid_metrics,
|
||||
},
|
||||
) catch |err| {
|
||||
log.warn("error rendering cursor glyph err={}", .{err});
|
||||
@ -1301,12 +1271,12 @@ fn addCursor(
|
||||
.bg_g = 0,
|
||||
.bg_b = 0,
|
||||
.bg_a = 0,
|
||||
.glyph_x = glyph.atlas_x,
|
||||
.glyph_y = glyph.atlas_y,
|
||||
.glyph_width = glyph.width,
|
||||
.glyph_height = glyph.height,
|
||||
.glyph_offset_x = glyph.offset_x,
|
||||
.glyph_offset_y = glyph.offset_y,
|
||||
.glyph_x = render.glyph.atlas_x,
|
||||
.glyph_y = render.glyph.atlas_y,
|
||||
.glyph_width = render.glyph.width,
|
||||
.glyph_height = render.glyph.height,
|
||||
.glyph_offset_x = render.glyph.offset_x,
|
||||
.glyph_offset_y = render.glyph.offset_y,
|
||||
});
|
||||
|
||||
return &self.cells.items[self.cells.items.len - 1];
|
||||
@ -1455,7 +1425,7 @@ fn updateCell(
|
||||
// If the cell has a character, draw it
|
||||
if (cell.hasText()) fg: {
|
||||
// Render
|
||||
const glyph = try self.font_group.renderGlyph(
|
||||
const render = try self.font_grid.renderGlyph(
|
||||
self.alloc,
|
||||
shaper_run.font_index,
|
||||
shaper_cell.glyph_index orelse break :fg,
|
||||
@ -1467,9 +1437,8 @@ fn updateCell(
|
||||
|
||||
// If we're rendering a color font, we use the color atlas
|
||||
const mode: CellProgram.CellMode = switch (try fgMode(
|
||||
&self.font_group.group,
|
||||
render.presentation,
|
||||
cell_pin,
|
||||
shaper_run,
|
||||
)) {
|
||||
.normal => .fg,
|
||||
.color => .fg_color,
|
||||
@ -1481,12 +1450,12 @@ fn updateCell(
|
||||
.grid_col = @intCast(x),
|
||||
.grid_row = @intCast(y),
|
||||
.grid_width = cell.gridWidth(),
|
||||
.glyph_x = glyph.atlas_x,
|
||||
.glyph_y = glyph.atlas_y,
|
||||
.glyph_width = glyph.width,
|
||||
.glyph_height = glyph.height,
|
||||
.glyph_offset_x = glyph.offset_x + shaper_cell.x_offset,
|
||||
.glyph_offset_y = glyph.offset_y + shaper_cell.y_offset,
|
||||
.glyph_x = render.glyph.atlas_x,
|
||||
.glyph_y = render.glyph.atlas_y,
|
||||
.glyph_width = render.glyph.width,
|
||||
.glyph_height = render.glyph.height,
|
||||
.glyph_offset_x = render.glyph.offset_x + shaper_cell.x_offset,
|
||||
.glyph_offset_y = render.glyph.offset_y + shaper_cell.y_offset,
|
||||
.r = colors.fg.r,
|
||||
.g = colors.fg.g,
|
||||
.b = colors.fg.b,
|
||||
@ -1508,13 +1477,13 @@ fn updateCell(
|
||||
.curly => .underline_curly,
|
||||
};
|
||||
|
||||
const underline_glyph = try self.font_group.renderGlyph(
|
||||
const render = try self.font_grid.renderGlyph(
|
||||
self.alloc,
|
||||
font.sprite_index,
|
||||
@intFromEnum(sprite),
|
||||
.{
|
||||
.grid_metrics = self.grid_metrics,
|
||||
.cell_width = if (cell.wide == .wide) 2 else 1,
|
||||
.grid_metrics = self.grid_metrics,
|
||||
},
|
||||
);
|
||||
|
||||
@ -1525,12 +1494,12 @@ fn updateCell(
|
||||
.grid_col = @intCast(x),
|
||||
.grid_row = @intCast(y),
|
||||
.grid_width = cell.gridWidth(),
|
||||
.glyph_x = underline_glyph.atlas_x,
|
||||
.glyph_y = underline_glyph.atlas_y,
|
||||
.glyph_width = underline_glyph.width,
|
||||
.glyph_height = underline_glyph.height,
|
||||
.glyph_offset_x = underline_glyph.offset_x,
|
||||
.glyph_offset_y = underline_glyph.offset_y,
|
||||
.glyph_x = render.glyph.atlas_x,
|
||||
.glyph_y = render.glyph.atlas_y,
|
||||
.glyph_width = render.glyph.width,
|
||||
.glyph_height = render.glyph.height,
|
||||
.glyph_offset_x = render.glyph.offset_x,
|
||||
.glyph_offset_y = render.glyph.offset_y,
|
||||
.r = color.r,
|
||||
.g = color.g,
|
||||
.b = color.b,
|
||||
@ -1587,10 +1556,12 @@ pub fn changeConfig(self: *OpenGL, config: *DerivedConfig) !void {
|
||||
// so to be safe we just always reset it. This has a performance hit
|
||||
// when its not necessary but config reloading shouldn't be so
|
||||
// common to cause a problem.
|
||||
self.font_group.reset();
|
||||
self.font_group.group.styles = config.font_styles;
|
||||
self.font_group.atlas_greyscale.clear();
|
||||
self.font_group.atlas_color.clear();
|
||||
//
|
||||
// TODO(fontmem): see Metal
|
||||
// self.font_group.reset();
|
||||
// self.font_group.group.styles = config.font_styles;
|
||||
// self.font_group.atlas_greyscale.clear();
|
||||
// self.font_group.atlas_color.clear();
|
||||
|
||||
// We always redo the font shaper in case font features changed. We
|
||||
// could check to see if there was an actual config change but this is
|
||||
@ -1657,71 +1628,85 @@ pub fn setScreenSize(
|
||||
fn flushAtlas(self: *OpenGL) !void {
|
||||
const gl_state = self.gl_state orelse return;
|
||||
|
||||
{
|
||||
const atlas = &self.font_group.atlas_greyscale;
|
||||
if (atlas.modified) {
|
||||
atlas.modified = false;
|
||||
var texbind = try gl_state.texture.bind(.@"2D");
|
||||
defer texbind.unbind();
|
||||
texture: {
|
||||
// If the texture isn't modified we do nothing
|
||||
const atlas = &self.font_grid.atlas_greyscale;
|
||||
const modified = atlas.modified.load(.monotonic);
|
||||
if (modified <= self.texture_greyscale_modified) break :texture;
|
||||
self.texture_greyscale_modified = modified;
|
||||
|
||||
if (atlas.resized) {
|
||||
atlas.resized = false;
|
||||
try texbind.image2D(
|
||||
0,
|
||||
.red,
|
||||
@intCast(atlas.size),
|
||||
@intCast(atlas.size),
|
||||
0,
|
||||
.red,
|
||||
.UnsignedByte,
|
||||
atlas.data.ptr,
|
||||
);
|
||||
} else {
|
||||
try texbind.subImage2D(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@intCast(atlas.size),
|
||||
@intCast(atlas.size),
|
||||
.red,
|
||||
.UnsignedByte,
|
||||
atlas.data.ptr,
|
||||
);
|
||||
}
|
||||
// If it is modified we need to grab a read-lock
|
||||
self.font_grid.lock.lockShared();
|
||||
defer self.font_grid.lock.unlockShared();
|
||||
|
||||
var texbind = try gl_state.texture.bind(.@"2D");
|
||||
defer texbind.unbind();
|
||||
|
||||
const resized = atlas.resized.load(.monotonic);
|
||||
if (resized > self.texture_greyscale_resized) {
|
||||
self.texture_greyscale_resized = resized;
|
||||
try texbind.image2D(
|
||||
0,
|
||||
.red,
|
||||
@intCast(atlas.size),
|
||||
@intCast(atlas.size),
|
||||
0,
|
||||
.red,
|
||||
.UnsignedByte,
|
||||
atlas.data.ptr,
|
||||
);
|
||||
} else {
|
||||
try texbind.subImage2D(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@intCast(atlas.size),
|
||||
@intCast(atlas.size),
|
||||
.red,
|
||||
.UnsignedByte,
|
||||
atlas.data.ptr,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const atlas = &self.font_group.atlas_color;
|
||||
if (atlas.modified) {
|
||||
atlas.modified = false;
|
||||
var texbind = try gl_state.texture_color.bind(.@"2D");
|
||||
defer texbind.unbind();
|
||||
texture: {
|
||||
// If the texture isn't modified we do nothing
|
||||
const atlas = &self.font_grid.atlas_color;
|
||||
const modified = atlas.modified.load(.monotonic);
|
||||
if (modified <= self.texture_color_modified) break :texture;
|
||||
self.texture_color_modified = modified;
|
||||
|
||||
if (atlas.resized) {
|
||||
atlas.resized = false;
|
||||
try texbind.image2D(
|
||||
0,
|
||||
.rgba,
|
||||
@intCast(atlas.size),
|
||||
@intCast(atlas.size),
|
||||
0,
|
||||
.bgra,
|
||||
.UnsignedByte,
|
||||
atlas.data.ptr,
|
||||
);
|
||||
} else {
|
||||
try texbind.subImage2D(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@intCast(atlas.size),
|
||||
@intCast(atlas.size),
|
||||
.bgra,
|
||||
.UnsignedByte,
|
||||
atlas.data.ptr,
|
||||
);
|
||||
}
|
||||
// If it is modified we need to grab a read-lock
|
||||
self.font_grid.lock.lockShared();
|
||||
defer self.font_grid.lock.unlockShared();
|
||||
|
||||
var texbind = try gl_state.texture_color.bind(.@"2D");
|
||||
defer texbind.unbind();
|
||||
|
||||
const resized = atlas.resized.load(.monotonic);
|
||||
if (resized > self.texture_color_resized) {
|
||||
self.texture_color_resized = resized;
|
||||
try texbind.image2D(
|
||||
0,
|
||||
.rgba,
|
||||
@intCast(atlas.size),
|
||||
@intCast(atlas.size),
|
||||
0,
|
||||
.bgra,
|
||||
.UnsignedByte,
|
||||
atlas.data.ptr,
|
||||
);
|
||||
} else {
|
||||
try texbind.subImage2D(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@intCast(atlas.size),
|
||||
@intCast(atlas.size),
|
||||
.bgra,
|
||||
.UnsignedByte,
|
||||
atlas.data.ptr,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1999,7 +1984,7 @@ const GLState = struct {
|
||||
pub fn init(
|
||||
alloc: Allocator,
|
||||
config: DerivedConfig,
|
||||
font_group: *font.GroupCache,
|
||||
font_grid: *font.SharedGrid,
|
||||
) !GLState {
|
||||
var arena = ArenaAllocator.init(alloc);
|
||||
defer arena.deinit();
|
||||
@ -2045,12 +2030,12 @@ const GLState = struct {
|
||||
try texbind.image2D(
|
||||
0,
|
||||
.red,
|
||||
@intCast(font_group.atlas_greyscale.size),
|
||||
@intCast(font_group.atlas_greyscale.size),
|
||||
@intCast(font_grid.atlas_greyscale.size),
|
||||
@intCast(font_grid.atlas_greyscale.size),
|
||||
0,
|
||||
.red,
|
||||
.UnsignedByte,
|
||||
font_group.atlas_greyscale.data.ptr,
|
||||
font_grid.atlas_greyscale.data.ptr,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2066,12 +2051,12 @@ const GLState = struct {
|
||||
try texbind.image2D(
|
||||
0,
|
||||
.rgba,
|
||||
@intCast(font_group.atlas_color.size),
|
||||
@intCast(font_group.atlas_color.size),
|
||||
@intCast(font_grid.atlas_color.size),
|
||||
@intCast(font_grid.atlas_color.size),
|
||||
0,
|
||||
.bgra,
|
||||
.UnsignedByte,
|
||||
font_group.atlas_color.data.ptr,
|
||||
font_grid.atlas_color.data.ptr,
|
||||
);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user