opengl: enable single-threaded draw

This commit is contained in:
Mitchell Hashimoto
2023-02-23 10:36:45 -08:00
parent 7eb7cae9e0
commit b19f9b2aff
2 changed files with 66 additions and 25 deletions

View File

@ -153,6 +153,10 @@ pub const App = struct {
}; };
pub const Surface = struct { pub const Surface = struct {
/// This is detected by the OpenGL renderer to move to a single-threaded
/// draw operation. This basically puts locks around our draw path.
pub const opengl_single_threaded_draw = true;
pub const Options = struct { pub const Options = struct {
gl_area: *c.GtkGLArea, gl_area: *c.GtkGLArea,
}; };

View File

@ -22,15 +22,25 @@ const Surface = @import("../Surface.zig");
const log = std.log.scoped(.grid); const log = std.log.scoped(.grid);
// The LRU is keyed by (screen, row_id) since we need to cache rows /// The LRU is keyed by (screen, row_id) since we need to cache rows
// separately for alt screens. By storing that in the key, we very likely /// separately for alt screens. By storing that in the key, we very likely
// have the cache already for when the primary screen is reactivated. /// have the cache already for when the primary screen is reactivated.
const CellsLRU = lru.AutoHashMap(struct { const CellsLRU = lru.AutoHashMap(struct {
selection: ?terminal.Selection, selection: ?terminal.Selection,
screen: terminal.Terminal.ScreenType, screen: terminal.Terminal.ScreenType,
row_id: terminal.Screen.RowHeader.Id, row_id: terminal.Screen.RowHeader.Id,
}, std.ArrayListUnmanaged(GPUCell)); }, std.ArrayListUnmanaged(GPUCell));
/// The runtime can request a single-threaded draw by setting this boolean
/// to true. In this case, the renderer.draw() call is expected to be called
/// from the runtime.
const single_threaded_draw = if (@hasDecl(apprt.Surface, "opengl_single_threaded_draw"))
apprt.Surface.opengl_single_threaded_draw
else
false;
const DrawMutex = if (single_threaded_draw) std.Thread.Mutex else void;
const drawMutexZero = if (DrawMutex == void) void{} else .{};
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
/// Current cell dimensions for this grid. /// Current cell dimensions for this grid.
@ -96,6 +106,13 @@ surface_mailbox: apprt.surface.Mailbox,
/// simple we apply all OpenGL context changes in the render() call. /// simple we apply all OpenGL context changes in the render() call.
deferred_screen_size: ?SetScreenSize = null, deferred_screen_size: ?SetScreenSize = null,
/// If we're drawing with single threaded operations
draw_mutex: DrawMutex = drawMutexZero,
/// Current background to draw. This may not match self.background if the
/// terminal is in reversed mode.
draw_background: terminal.color.RGB,
/// Defererred OpenGL operation to update the screen size. /// Defererred OpenGL operation to update the screen size.
const SetScreenSize = struct { const SetScreenSize = struct {
size: renderer.ScreenSize, size: renderer.ScreenSize,
@ -350,6 +367,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
.cursor_color = if (options.config.@"cursor-color") |col| col.toTerminalRGB() else null, .cursor_color = if (options.config.@"cursor-color") |col| col.toTerminalRGB() else null,
.background = options.config.background.toTerminalRGB(), .background = options.config.background.toTerminalRGB(),
.foreground = options.config.foreground.toTerminalRGB(), .foreground = options.config.foreground.toTerminalRGB(),
.draw_background = options.config.background.toTerminalRGB(),
.selection_background = if (options.config.@"selection-background") |bg| .selection_background = if (options.config.@"selection-background") |bg|
bg.toTerminalRGB() bg.toTerminalRGB()
else else
@ -554,6 +572,7 @@ pub fn blinkCursor(self: *OpenGL, reset: bool) void {
/// Must be called on the render thread. /// Must be called on the render thread.
pub fn setFontSize(self: *OpenGL, size: font.face.DesiredSize) !void { pub fn setFontSize(self: *OpenGL, size: font.face.DesiredSize) !void {
log.info("set font size={}", .{size}); log.info("set font size={}", .{size});
if (apprt.runtime == apprt.gtk) @panic("TODO: make thread safe");
// Set our new size, this will also reset our font atlas. // Set our new size, this will also reset our font atlas.
try self.font_group.setSize(size); try self.font_group.setSize(size);
@ -617,7 +636,6 @@ pub fn render(
surface: *apprt.Surface, surface: *apprt.Surface,
state: *renderer.State, state: *renderer.State,
) !void { ) !void {
log.warn("RENDER", .{});
// Data we extract out of the critical area. // Data we extract out of the critical area.
const Critical = struct { const Critical = struct {
gl_bg: terminal.color.RGB, gl_bg: terminal.color.RGB,
@ -703,28 +721,28 @@ pub fn render(
}; };
defer critical.screen.deinit(); defer critical.screen.deinit();
// Build our GPU cells // Grab our draw mutex if we have it and update our data
try self.rebuildCells( {
critical.active_screen, if (single_threaded_draw) self.draw_mutex.lock();
critical.selection, defer if (single_threaded_draw) self.draw_mutex.unlock();
&critical.screen,
critical.draw_cursor,
);
// Try to flush our atlas, this will only do something if there // Set our draw data
// are changes to the atlas. self.draw_background = critical.gl_bg;
try self.flushAtlas();
// Clear the surface // Build our GPU cells
gl.clearColor( try self.rebuildCells(
@intToFloat(f32, critical.gl_bg.r) / 255, critical.active_screen,
@intToFloat(f32, critical.gl_bg.g) / 255, critical.selection,
@intToFloat(f32, critical.gl_bg.b) / 255, &critical.screen,
1.0, critical.draw_cursor,
); );
gl.clear(gl.c.GL_COLOR_BUFFER_BIT); }
// We're out of the critical path now. Let's render. We only render if
// we're not single threaded. If we're single threaded we expect the
// runtime to call draw.
if (single_threaded_draw) return;
// We're out of the critical path now. Let's first render our terminal.
try self.draw(); try self.draw();
// If we have devmode, then render that // If we have devmode, then render that
@ -735,8 +753,10 @@ pub fn render(
} }
// Swap our window buffers // Swap our window buffers
if (apprt.runtime == apprt.gtk) @panic("TODO"); switch (apprt.runtime) {
surface.window.swapBuffers(); else => @compileError("unsupported runtime"),
apprt.glfw => surface.window.swapBuffers(),
}
} }
/// rebuildCells rebuilds all the GPU cells from our CPU state. This is a /// rebuildCells rebuilds all the GPU cells from our CPU state. This is a
@ -1259,9 +1279,26 @@ pub fn draw(self: *OpenGL) !void {
const t = trace(@src()); const t = trace(@src());
defer t.end(); defer t.end();
// If we're in single-threaded more we grab a lock since we use shared data.
if (single_threaded_draw) self.draw_mutex.lock();
defer if (single_threaded_draw) self.draw_mutex.unlock();
// If we have no cells to render, then we render nothing. // If we have no cells to render, then we render nothing.
if (self.cells.items.len == 0) return; if (self.cells.items.len == 0) return;
// Try to flush our atlas, this will only do something if there
// are changes to the atlas.
try self.flushAtlas();
// Clear the surface
gl.clearColor(
@intToFloat(f32, self.draw_background.r) / 255,
@intToFloat(f32, self.draw_background.g) / 255,
@intToFloat(f32, self.draw_background.b) / 255,
1.0,
);
gl.clear(gl.c.GL_COLOR_BUFFER_BIT);
// Setup our VAO // Setup our VAO
try self.vao.bind(); try self.vao.bind();
defer gl.VertexArray.unbind() catch null; defer gl.VertexArray.unbind() catch null;