mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
run rendering on another real thread (still bugs)
This commit is contained in:
@ -1,6 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const stream = @import("stream.zig");
|
const stream = @import("stream.zig");
|
||||||
|
|
||||||
|
pub const c = @import("c.zig");
|
||||||
pub const Loop = @import("Loop.zig");
|
pub const Loop = @import("Loop.zig");
|
||||||
pub const Async = @import("Async.zig");
|
pub const Async = @import("Async.zig");
|
||||||
pub const Idle = @import("Idle.zig");
|
pub const Idle = @import("Idle.zig");
|
||||||
|
@ -59,6 +59,12 @@ renderer: renderer.OpenGL,
|
|||||||
/// The render state
|
/// The render state
|
||||||
renderer_state: renderer.State,
|
renderer_state: renderer.State,
|
||||||
|
|
||||||
|
/// The renderer thread manager
|
||||||
|
renderer_thread: renderer.Thread,
|
||||||
|
|
||||||
|
/// The actual thread
|
||||||
|
renderer_thr: std.Thread,
|
||||||
|
|
||||||
/// The underlying pty for this window.
|
/// The underlying pty for this window.
|
||||||
pty: Pty,
|
pty: Pty,
|
||||||
|
|
||||||
@ -450,6 +456,15 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo
|
|||||||
mutex.* = .{};
|
mutex.* = .{};
|
||||||
errdefer alloc.destroy(mutex);
|
errdefer alloc.destroy(mutex);
|
||||||
|
|
||||||
|
// Create the renderer thread
|
||||||
|
var render_thread = try renderer.Thread.init(
|
||||||
|
alloc,
|
||||||
|
window,
|
||||||
|
&self.renderer,
|
||||||
|
&self.renderer_state,
|
||||||
|
);
|
||||||
|
errdefer render_thread.deinit();
|
||||||
|
|
||||||
self.* = .{
|
self.* = .{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.alloc_io_arena = io_arena,
|
.alloc_io_arena = io_arena,
|
||||||
@ -459,6 +474,7 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo
|
|||||||
.cursor = cursor,
|
.cursor = cursor,
|
||||||
.focused = false,
|
.focused = false,
|
||||||
.renderer = renderer_impl,
|
.renderer = renderer_impl,
|
||||||
|
.renderer_thread = render_thread,
|
||||||
.renderer_state = .{
|
.renderer_state = .{
|
||||||
.mutex = mutex,
|
.mutex = mutex,
|
||||||
.cursor = .{
|
.cursor = .{
|
||||||
@ -469,6 +485,7 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo
|
|||||||
.terminal = &self.terminal,
|
.terminal = &self.terminal,
|
||||||
.devmode = if (!DevMode.enabled) null else &DevMode.instance,
|
.devmode = if (!DevMode.enabled) null else &DevMode.instance,
|
||||||
},
|
},
|
||||||
|
.renderer_thr = undefined,
|
||||||
.pty = pty,
|
.pty = pty,
|
||||||
.command = cmd,
|
.command = cmd,
|
||||||
.mouse = .{},
|
.mouse = .{},
|
||||||
@ -489,7 +506,7 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo
|
|||||||
|
|
||||||
// Setup our callbacks and user data
|
// Setup our callbacks and user data
|
||||||
window.setUserPointer(self);
|
window.setUserPointer(self);
|
||||||
window.setSizeCallback(sizeCallback);
|
//window.setSizeCallback(sizeCallback);
|
||||||
window.setCharCallback(charCallback);
|
window.setCharCallback(charCallback);
|
||||||
window.setKeyCallback(keyCallback);
|
window.setKeyCallback(keyCallback);
|
||||||
window.setFocusCallback(focusCallback);
|
window.setFocusCallback(focusCallback);
|
||||||
@ -537,10 +554,33 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo
|
|||||||
DevMode.instance.window = self;
|
DevMode.instance.window = self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unload our context prior to switching over to the renderer thread
|
||||||
|
// because OpenGL requires it to be unloaded.
|
||||||
|
gl.glad.unload();
|
||||||
|
try glfw.makeContextCurrent(null);
|
||||||
|
|
||||||
|
// Start our renderer thread
|
||||||
|
self.renderer_thr = try std.Thread.spawn(
|
||||||
|
.{},
|
||||||
|
renderer.Thread.threadMain,
|
||||||
|
.{&self.renderer_thread},
|
||||||
|
);
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy(self: *Window) void {
|
pub fn destroy(self: *Window) void {
|
||||||
|
{
|
||||||
|
// Stop rendering thread
|
||||||
|
self.renderer_thread.stop.send() catch |err|
|
||||||
|
log.err("error notifying renderer thread to stop, may stall err={}", .{err});
|
||||||
|
self.renderer_thr.join();
|
||||||
|
|
||||||
|
// We need to become the active rendering thread again
|
||||||
|
self.renderer.threadEnter(self.window) catch unreachable;
|
||||||
|
self.renderer_thread.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
if (DevMode.enabled) {
|
if (DevMode.enabled) {
|
||||||
// Clear the window
|
// Clear the window
|
||||||
DevMode.instance.window = null;
|
DevMode.instance.window = null;
|
||||||
@ -1470,6 +1510,10 @@ fn cursorTimerCallback(t: *libuv.Timer) void {
|
|||||||
|
|
||||||
const win = t.getData(Window) orelse return;
|
const win = t.getData(Window) orelse return;
|
||||||
|
|
||||||
|
// We are modifying renderer state from here on out
|
||||||
|
win.renderer_state.mutex.lock();
|
||||||
|
defer win.renderer_state.mutex.unlock();
|
||||||
|
|
||||||
// If the cursor is currently invisible, then we do nothing. Ideally
|
// If the cursor is currently invisible, then we do nothing. Ideally
|
||||||
// in this state the timer would be cancelled but no big deal.
|
// in this state the timer would be cancelled but no big deal.
|
||||||
if (!win.renderer_state.cursor.visible) return;
|
if (!win.renderer_state.cursor.visible) return;
|
||||||
@ -1589,9 +1633,9 @@ fn renderTimerCallback(t: *libuv.Timer) void {
|
|||||||
|
|
||||||
const win = t.getData(Window).?;
|
const win = t.getData(Window).?;
|
||||||
|
|
||||||
// Render
|
// Trigger a render
|
||||||
win.renderer.render(win.window, win.renderer_state) catch |err|
|
win.renderer_thread.wakeup.send() catch |err|
|
||||||
log.warn("error rendering err={}", .{err});
|
log.err("error sending render notification err={}", .{err});
|
||||||
|
|
||||||
// Record our run
|
// Record our run
|
||||||
win.render_timer.tick();
|
win.render_timer.tick();
|
||||||
|
@ -325,7 +325,9 @@ pub fn deinit(self: *OpenGL) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Callback called by renderer.Thread when it begins.
|
/// Callback called by renderer.Thread when it begins.
|
||||||
pub fn threadEnter(window: glfw.Window) !void {
|
pub fn threadEnter(self: *const OpenGL, window: glfw.Window) !void {
|
||||||
|
_ = self;
|
||||||
|
|
||||||
// We need to make the OpenGL context current. OpenGL requires
|
// We need to make the OpenGL context current. OpenGL requires
|
||||||
// that a single thread own the a single OpenGL context (if any). This
|
// that a single thread own the a single OpenGL context (if any). This
|
||||||
// ensures that the context switches over to our thread. Important:
|
// ensures that the context switches over to our thread. Important:
|
||||||
@ -350,7 +352,9 @@ pub fn threadEnter(window: glfw.Window) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Callback called by renderer.Thread when it exits.
|
/// Callback called by renderer.Thread when it exits.
|
||||||
pub fn threadExit() void {
|
pub fn threadExit(self: *const OpenGL) void {
|
||||||
|
_ = self;
|
||||||
|
|
||||||
gl.glad.unload();
|
gl.glad.unload();
|
||||||
glfw.makeContextCurrent(null) catch {};
|
glfw.makeContextCurrent(null) catch {};
|
||||||
}
|
}
|
||||||
|
40
src/renderer/State.zig
Normal file
40
src/renderer/State.zig
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
//! This is the render state that is given to a renderer.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const DevMode = @import("../DevMode.zig");
|
||||||
|
const terminal = @import("../terminal/main.zig");
|
||||||
|
|
||||||
|
/// The mutex that must be held while reading any of the data in the
|
||||||
|
/// members of this state. Note that the state itself is NOT protected
|
||||||
|
/// by the mutex and is NOT thread-safe, only the members values of the
|
||||||
|
/// state (i.e. the terminal, devmode, etc. values).
|
||||||
|
mutex: *std.Thread.Mutex,
|
||||||
|
|
||||||
|
/// A new screen size if the screen was resized.
|
||||||
|
resize: ?Resize = null,
|
||||||
|
|
||||||
|
/// Cursor configuration for rendering
|
||||||
|
cursor: Cursor,
|
||||||
|
|
||||||
|
/// The terminal data.
|
||||||
|
terminal: *terminal.Terminal,
|
||||||
|
|
||||||
|
/// The devmode data.
|
||||||
|
devmode: ?*const DevMode = null,
|
||||||
|
|
||||||
|
pub const Cursor = struct {
|
||||||
|
/// Current cursor style. This can be set by escape sequences. To get
|
||||||
|
/// the default style, the config has to be referenced.
|
||||||
|
style: terminal.CursorStyle = .default,
|
||||||
|
|
||||||
|
/// Whether the cursor is visible at all. This should not be used for
|
||||||
|
/// "blink" settings, see "blink" for that. This is used to turn the
|
||||||
|
/// cursor ON or OFF.
|
||||||
|
visible: bool = true,
|
||||||
|
|
||||||
|
/// Whether the cursor is currently blinking. If it is blinking, then
|
||||||
|
/// the cursor will not be rendered.
|
||||||
|
blink: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Resize = struct {};
|
@ -10,7 +10,7 @@ const renderer = @import("../renderer.zig");
|
|||||||
const gl = @import("../opengl.zig");
|
const gl = @import("../opengl.zig");
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const log = std.log.named(.renderer_thread);
|
const log = std.log.scoped(.renderer_thread);
|
||||||
|
|
||||||
/// The main event loop for the application. The user data of this loop
|
/// The main event loop for the application. The user data of this loop
|
||||||
/// is always the allocator used to create the loop. This is a convenience
|
/// is always the allocator used to create the loop. This is a convenience
|
||||||
@ -21,10 +21,27 @@ loop: libuv.Loop,
|
|||||||
/// any thread.
|
/// any thread.
|
||||||
wakeup: libuv.Async,
|
wakeup: libuv.Async,
|
||||||
|
|
||||||
|
/// This can be used to stop the renderer on the next loop iteration.
|
||||||
|
stop: libuv.Async,
|
||||||
|
|
||||||
|
/// The windo we're rendering to.
|
||||||
|
window: glfw.Window,
|
||||||
|
|
||||||
|
/// The underlying renderer implementation.
|
||||||
|
renderer: *renderer.OpenGL,
|
||||||
|
|
||||||
|
/// Pointer to the shared state that is used to generate the final render.
|
||||||
|
state: *const renderer.State,
|
||||||
|
|
||||||
/// Initialize the thread. This does not START the thread. This only sets
|
/// Initialize the thread. This does not START the thread. This only sets
|
||||||
/// up all the internal state necessary prior to starting the thread. It
|
/// up all the internal state necessary prior to starting the thread. It
|
||||||
/// is up to the caller to start the thread with the threadMain entrypoint.
|
/// is up to the caller to start the thread with the threadMain entrypoint.
|
||||||
pub fn init(alloc: Allocator) !Thread {
|
pub fn init(
|
||||||
|
alloc: Allocator,
|
||||||
|
window: glfw.Window,
|
||||||
|
renderer_impl: *renderer.OpenGL,
|
||||||
|
state: *const renderer.State,
|
||||||
|
) !Thread {
|
||||||
// We always store allocator pointer on the loop data so that
|
// We always store allocator pointer on the loop data so that
|
||||||
// handles can use our global allocator.
|
// handles can use our global allocator.
|
||||||
const allocPtr = try alloc.create(Allocator);
|
const allocPtr = try alloc.create(Allocator);
|
||||||
@ -37,10 +54,17 @@ pub fn init(alloc: Allocator) !Thread {
|
|||||||
loop.setData(allocPtr);
|
loop.setData(allocPtr);
|
||||||
|
|
||||||
// This async handle is used to "wake up" the renderer and force a render.
|
// This async handle is used to "wake up" the renderer and force a render.
|
||||||
var async_h = try libuv.Async.init(alloc, loop, (struct {
|
var wakeup_h = try libuv.Async.init(alloc, loop, renderCallback);
|
||||||
fn callback(_: *libuv.Async) void {}
|
errdefer wakeup_h.close((struct {
|
||||||
|
fn callback(h: *libuv.Async) void {
|
||||||
|
const loop_alloc = h.loop().getData(Allocator).?.*;
|
||||||
|
h.deinit(loop_alloc);
|
||||||
|
}
|
||||||
}).callback);
|
}).callback);
|
||||||
errdefer async_h.close((struct {
|
|
||||||
|
// This async handle is used to stop the loop and force the thread to end.
|
||||||
|
var stop_h = try libuv.Async.init(alloc, loop, stopCallback);
|
||||||
|
errdefer stop_h.close((struct {
|
||||||
fn callback(h: *libuv.Async) void {
|
fn callback(h: *libuv.Async) void {
|
||||||
const loop_alloc = h.loop().getData(Allocator).?.*;
|
const loop_alloc = h.loop().getData(Allocator).?.*;
|
||||||
h.deinit(loop_alloc);
|
h.deinit(loop_alloc);
|
||||||
@ -48,9 +72,12 @@ pub fn init(alloc: Allocator) !Thread {
|
|||||||
}).callback);
|
}).callback);
|
||||||
|
|
||||||
return Thread{
|
return Thread{
|
||||||
.alloc = alloc,
|
|
||||||
.loop = loop,
|
.loop = loop,
|
||||||
.notifier = async_h,
|
.wakeup = wakeup_h,
|
||||||
|
.stop = stop_h,
|
||||||
|
.window = window,
|
||||||
|
.renderer = renderer_impl,
|
||||||
|
.state = state,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +88,20 @@ pub fn deinit(self: *Thread) void {
|
|||||||
const alloc_ptr = self.loop.getData(Allocator).?;
|
const alloc_ptr = self.loop.getData(Allocator).?;
|
||||||
const alloc = alloc_ptr.*;
|
const alloc = alloc_ptr.*;
|
||||||
|
|
||||||
|
// Schedule our handles to close
|
||||||
|
self.stop.close((struct {
|
||||||
|
fn callback(h: *libuv.Async) void {
|
||||||
|
const handle_alloc = h.loop().getData(Allocator).?.*;
|
||||||
|
h.deinit(handle_alloc);
|
||||||
|
}
|
||||||
|
}).callback);
|
||||||
|
self.wakeup.close((struct {
|
||||||
|
fn callback(h: *libuv.Async) void {
|
||||||
|
const handle_alloc = h.loop().getData(Allocator).?.*;
|
||||||
|
h.deinit(handle_alloc);
|
||||||
|
}
|
||||||
|
}).callback);
|
||||||
|
|
||||||
// Run the loop one more time, because destroying our other things
|
// Run the loop one more time, because destroying our other things
|
||||||
// like windows usually cancel all our event loop stuff and we need
|
// like windows usually cancel all our event loop stuff and we need
|
||||||
// one more run through to finalize all the closes.
|
// one more run through to finalize all the closes.
|
||||||
@ -74,40 +115,54 @@ pub fn deinit(self: *Thread) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The main entrypoint for the thread.
|
/// The main entrypoint for the thread.
|
||||||
pub fn threadMain(
|
pub fn threadMain(self: *Thread) void {
|
||||||
window: glfw.Window,
|
|
||||||
renderer_impl: *const renderer.OpenGL,
|
|
||||||
) void {
|
|
||||||
// Call child function so we can use errors...
|
// Call child function so we can use errors...
|
||||||
threadMain_(
|
self.threadMain_() catch |err| {
|
||||||
window,
|
|
||||||
renderer_impl,
|
|
||||||
) catch |err| {
|
|
||||||
// In the future, we should expose this on the thread struct.
|
// In the future, we should expose this on the thread struct.
|
||||||
log.warn("error in renderer err={}", .{err});
|
log.warn("error in renderer err={}", .{err});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn threadMain_(
|
fn threadMain_(self: *Thread) !void {
|
||||||
self: *const Thread,
|
|
||||||
window: glfw.Window,
|
|
||||||
renderer_impl: *const renderer.OpenGL,
|
|
||||||
) !void {
|
|
||||||
const Renderer = switch (@TypeOf(renderer_impl)) {
|
|
||||||
.Pointer => |p| p.child,
|
|
||||||
.Struct => |s| s,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Run our thread start/end callbacks. This is important because some
|
// Run our thread start/end callbacks. This is important because some
|
||||||
// renderers have to do per-thread setup. For example, OpenGL has to set
|
// renderers have to do per-thread setup. For example, OpenGL has to set
|
||||||
// some thread-local state since that is how it works.
|
// some thread-local state since that is how it works.
|
||||||
if (@hasDecl(Renderer, "threadEnter")) try renderer_impl.threadEnter(window);
|
const Renderer = RendererType();
|
||||||
defer if (@hasDecl(Renderer, "threadExit")) renderer_impl.threadExit();
|
if (@hasDecl(Renderer, "threadEnter")) try self.renderer.threadEnter(self.window);
|
||||||
|
defer if (@hasDecl(Renderer, "threadExit")) self.renderer.threadExit();
|
||||||
|
|
||||||
// Setup our timer handle which is used to perform the actual render.
|
// Set up our async handler to support rendering
|
||||||
// TODO
|
self.wakeup.setData(self);
|
||||||
|
defer self.wakeup.setData(null);
|
||||||
|
|
||||||
// Run
|
// Run
|
||||||
log.debug("starting renderer thread", .{});
|
log.debug("starting renderer thread", .{});
|
||||||
try self.loop.run(.default);
|
defer log.debug("exiting renderer thread", .{});
|
||||||
|
_ = try self.loop.run(.default);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn renderCallback(h: *libuv.Async) void {
|
||||||
|
const t = h.getData(Thread) orelse {
|
||||||
|
// This shouldn't happen so we log it.
|
||||||
|
log.warn("render callback fired without data set", .{});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
t.renderer.render(t.window, t.state.*) catch |err|
|
||||||
|
log.warn("error rendering err={}", .{err});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stopCallback(h: *libuv.Async) void {
|
||||||
|
h.loop().stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is unnecessary right now but is logic we'll need for when we
|
||||||
|
// abstract renderers out.
|
||||||
|
fn RendererType() type {
|
||||||
|
const self: Thread = undefined;
|
||||||
|
return switch (@typeInfo(@TypeOf(self.renderer))) {
|
||||||
|
.Pointer => |p| p.child,
|
||||||
|
.Struct => |s| s,
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user