mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
gtk: render!
This commit is contained in:
12
src/App.zig
12
src/App.zig
@ -128,6 +128,7 @@ fn drainMailbox(self: *App, rt_app: *apprt.App) !void {
|
|||||||
.close => |surface| try self.closeSurface(rt_app, surface),
|
.close => |surface| try self.closeSurface(rt_app, surface),
|
||||||
.quit => try self.setQuit(),
|
.quit => try self.setQuit(),
|
||||||
.surface_message => |msg| try self.surfaceMessage(msg.surface, msg.message),
|
.surface_message => |msg| try self.surfaceMessage(msg.surface, msg.message),
|
||||||
|
.redraw_surface => |surface| try self.redrawSurface(rt_app, surface),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,6 +138,11 @@ fn closeSurface(self: *App, rt_app: *apprt.App, surface: *Surface) !void {
|
|||||||
rt_app.closeSurface(surface.rt_surface);
|
rt_app.closeSurface(surface.rt_surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn redrawSurface(self: *App, rt_app: *apprt.App, surface: *apprt.Surface) !void {
|
||||||
|
if (!self.hasSurface(&surface.core_surface)) return;
|
||||||
|
rt_app.redrawSurface(surface);
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new window
|
/// Create a new window
|
||||||
fn newWindow(self: *App, rt_app: *apprt.App, msg: Message.NewWindow) !void {
|
fn newWindow(self: *App, rt_app: *apprt.App, msg: Message.NewWindow) !void {
|
||||||
if (!@hasDecl(apprt.App, "newWindow")) {
|
if (!@hasDecl(apprt.App, "newWindow")) {
|
||||||
@ -230,6 +236,12 @@ pub const Message = union(enum) {
|
|||||||
message: apprt.surface.Message,
|
message: apprt.surface.Message,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Redraw a surface. This only has an effect for runtimes that
|
||||||
|
/// use single-threaded draws. To redraw a surface for all runtimes,
|
||||||
|
/// wake up the renderer thread. The renderer thread will send this
|
||||||
|
/// message if it needs to.
|
||||||
|
redraw_surface: *apprt.Surface,
|
||||||
|
|
||||||
const NewWindow = struct {
|
const NewWindow = struct {
|
||||||
/// The parent surface
|
/// The parent surface
|
||||||
parent: ?*Surface = null,
|
parent: ?*Surface = null,
|
||||||
|
@ -316,6 +316,7 @@ pub fn init(
|
|||||||
rt_surface,
|
rt_surface,
|
||||||
&self.renderer,
|
&self.renderer,
|
||||||
&self.renderer_state,
|
&self.renderer_state,
|
||||||
|
app_mailbox,
|
||||||
);
|
);
|
||||||
errdefer render_thread.deinit();
|
errdefer render_thread.deinit();
|
||||||
|
|
||||||
|
@ -137,6 +137,13 @@ pub const App = struct {
|
|||||||
self.app.alloc.destroy(surface);
|
self.app.alloc.destroy(surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn redrawSurface(self: *App, surface: *Surface) void {
|
||||||
|
_ = self;
|
||||||
|
_ = surface;
|
||||||
|
|
||||||
|
@panic("This should never be called for GLFW.");
|
||||||
|
}
|
||||||
|
|
||||||
fn glfwErrorCallback(code: glfw.ErrorCode, desc: [:0]const u8) void {
|
fn glfwErrorCallback(code: glfw.ErrorCode, desc: [:0]const u8) void {
|
||||||
std.log.warn("glfw error={} message={s}", .{ code, desc });
|
std.log.warn("glfw error={} message={s}", .{ code, desc });
|
||||||
|
|
||||||
|
@ -115,6 +115,11 @@ pub const App = struct {
|
|||||||
@panic("This should not be called with GTK.");
|
@panic("This should not be called with GTK.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn redrawSurface(self: *App, surface: *Surface) void {
|
||||||
|
_ = self;
|
||||||
|
surface.invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn newWindow(self: *App, parent_: ?*CoreSurface) !void {
|
pub fn newWindow(self: *App, parent_: ?*CoreSurface) !void {
|
||||||
_ = parent_;
|
_ = parent_;
|
||||||
|
|
||||||
@ -169,42 +174,30 @@ pub const Surface = struct {
|
|||||||
/// The app we're part of
|
/// The app we're part of
|
||||||
app: *App,
|
app: *App,
|
||||||
|
|
||||||
|
/// Our GTK area
|
||||||
|
gl_area: *c.GtkGLArea,
|
||||||
|
|
||||||
/// The core surface backing this surface
|
/// The core surface backing this surface
|
||||||
core_surface: CoreSurface,
|
core_surface: CoreSurface,
|
||||||
|
|
||||||
|
/// Cached metrics about the surface from GTK callbacks.
|
||||||
|
size: apprt.SurfaceSize,
|
||||||
|
|
||||||
pub fn init(self: *Surface, app: *App, opts: Options) !void {
|
pub fn init(self: *Surface, app: *App, opts: Options) !void {
|
||||||
// Build our result
|
// Build our result
|
||||||
self.* = .{
|
self.* = .{
|
||||||
.app = app,
|
.app = app,
|
||||||
|
.gl_area = opts.gl_area,
|
||||||
.core_surface = undefined,
|
.core_surface = undefined,
|
||||||
|
.size = .{ .width = 800, .height = 600 },
|
||||||
};
|
};
|
||||||
errdefer self.* = undefined;
|
errdefer self.* = undefined;
|
||||||
|
|
||||||
// Create the GL area that will contain our surface
|
// Create the GL area that will contain our surface
|
||||||
_ = c.g_signal_connect_data(
|
_ = c.g_signal_connect_data(opts.gl_area, "realize", c.G_CALLBACK(>kRealize), self, null, c.G_CONNECT_DEFAULT);
|
||||||
opts.gl_area,
|
_ = c.g_signal_connect_data(opts.gl_area, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT);
|
||||||
"realize",
|
_ = c.g_signal_connect_data(opts.gl_area, "render", c.G_CALLBACK(>kRender), self, null, c.G_CONNECT_DEFAULT);
|
||||||
c.G_CALLBACK(>kRealize),
|
_ = c.g_signal_connect_data(opts.gl_area, "resize", c.G_CALLBACK(>kResize), self, null, c.G_CONNECT_DEFAULT);
|
||||||
self,
|
|
||||||
null,
|
|
||||||
c.G_CONNECT_DEFAULT,
|
|
||||||
);
|
|
||||||
_ = c.g_signal_connect_data(
|
|
||||||
opts.gl_area,
|
|
||||||
"render",
|
|
||||||
c.G_CALLBACK(>kRender),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
c.G_CONNECT_DEFAULT,
|
|
||||||
);
|
|
||||||
_ = c.g_signal_connect_data(
|
|
||||||
opts.gl_area,
|
|
||||||
"destroy",
|
|
||||||
c.G_CALLBACK(>kDestroy),
|
|
||||||
self,
|
|
||||||
null,
|
|
||||||
c.G_CONNECT_DEFAULT,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(self: *Surface) !void {
|
fn realize(self: *Surface) !void {
|
||||||
@ -220,16 +213,33 @@ pub const Surface = struct {
|
|||||||
self,
|
self,
|
||||||
);
|
);
|
||||||
errdefer self.core_surface.deinit();
|
errdefer self.core_surface.deinit();
|
||||||
|
|
||||||
|
// Note we're realized
|
||||||
|
self.realized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(self: *Surface) !void {
|
||||||
|
try self.core_surface.renderer.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invalidate the surface so that it forces a redraw on the next tick.
|
||||||
|
fn invalidate(self: *Surface) void {
|
||||||
|
c.gtk_gl_area_queue_render(self.gl_area);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gtkRealize(area: *c.GtkGLArea, ud: ?*anyopaque) callconv(.C) void {
|
fn gtkRealize(area: *c.GtkGLArea, ud: ?*anyopaque) callconv(.C) void {
|
||||||
_ = area;
|
|
||||||
|
|
||||||
log.debug("gl surface realized", .{});
|
log.debug("gl surface realized", .{});
|
||||||
|
|
||||||
|
// We need to make the context current so we can call GL functions.
|
||||||
|
c.gtk_gl_area_make_current(area);
|
||||||
|
if (c.gtk_gl_area_get_error(area)) |err| {
|
||||||
|
log.err("surface failed to realize: {s}", .{err.*.message});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// realize means that our OpenGL context is ready, so we can now
|
// realize means that our OpenGL context is ready, so we can now
|
||||||
// initialize the core surface which will setup the renderer.
|
// initialize the core surface which will setup the renderer.
|
||||||
const self = userdataSelf(ud orelse return);
|
const self = userdataSelf(ud.?);
|
||||||
self.realize() catch |err| {
|
self.realize() catch |err| {
|
||||||
// TODO: we need to destroy the GL area here.
|
// TODO: we need to destroy the GL area here.
|
||||||
log.err("surface failed to realize: {}", .{err});
|
log.err("surface failed to realize: {}", .{err});
|
||||||
@ -237,24 +247,45 @@ pub const Surface = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// render singal
|
||||||
fn gtkRender(area: *c.GtkGLArea, ctx: *c.GdkGLContext, ud: ?*anyopaque) callconv(.C) c.gboolean {
|
fn gtkRender(area: *c.GtkGLArea, ctx: *c.GdkGLContext, ud: ?*anyopaque) callconv(.C) c.gboolean {
|
||||||
_ = area;
|
_ = area;
|
||||||
_ = ctx;
|
_ = ctx;
|
||||||
_ = ud;
|
|
||||||
log.debug("gl render", .{});
|
|
||||||
|
|
||||||
const opengl = @import("../renderer/opengl/main.zig");
|
const self = userdataSelf(ud.?);
|
||||||
opengl.clearColor(0, 0.5, 1, 1);
|
self.render() catch |err| {
|
||||||
opengl.clear(opengl.c.GL_COLOR_BUFFER_BIT);
|
log.err("surface failed to render: {}", .{err});
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// render singal
|
||||||
|
fn gtkResize(area: *c.GtkGLArea, width: c.gint, height: c.gint, ud: ?*anyopaque) callconv(.C) void {
|
||||||
|
_ = area;
|
||||||
|
log.debug("gl resize {} {}", .{ width, height });
|
||||||
|
|
||||||
|
const self = userdataSelf(ud.?);
|
||||||
|
self.size = .{
|
||||||
|
.width = @intCast(u32, width),
|
||||||
|
.height = @intCast(u32, height),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Call the primary callback.
|
||||||
|
if (self.realized) {
|
||||||
|
self.core_surface.sizeCallback(self.size) catch |err| {
|
||||||
|
log.err("error in size callback err={}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// "destroy" signal for surface
|
/// "destroy" signal for surface
|
||||||
fn gtkDestroy(v: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void {
|
fn gtkDestroy(v: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void {
|
||||||
_ = v;
|
_ = v;
|
||||||
|
|
||||||
const self = userdataSelf(ud orelse return);
|
const self = userdataSelf(ud.?);
|
||||||
const alloc = self.app.core_app.alloc;
|
const alloc = self.app.core_app.alloc;
|
||||||
self.deinit();
|
self.deinit();
|
||||||
alloc.destroy(self);
|
alloc.destroy(self);
|
||||||
@ -283,8 +314,7 @@ pub const Surface = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getSize(self: *const Surface) !apprt.SurfaceSize {
|
pub fn getSize(self: *const Surface) !apprt.SurfaceSize {
|
||||||
_ = self;
|
return self.size;
|
||||||
return .{ .width = 800, .height = 600 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setSizeLimits(self: *Surface, min: apprt.SurfaceSize, max_: ?apprt.SurfaceSize) !void {
|
pub fn setSizeLimits(self: *Surface, min: apprt.SurfaceSize, max_: ?apprt.SurfaceSize) !void {
|
||||||
|
@ -34,7 +34,7 @@ const CellsLRU = lru.AutoHashMap(struct {
|
|||||||
/// The runtime can request a single-threaded draw by setting this boolean
|
/// 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
|
/// to true. In this case, the renderer.draw() call is expected to be called
|
||||||
/// from the runtime.
|
/// from the runtime.
|
||||||
const single_threaded_draw = if (@hasDecl(apprt.Surface, "opengl_single_threaded_draw"))
|
pub const single_threaded_draw = if (@hasDecl(apprt.Surface, "opengl_single_threaded_draw"))
|
||||||
apprt.Surface.opengl_single_threaded_draw
|
apprt.Surface.opengl_single_threaded_draw
|
||||||
else
|
else
|
||||||
false;
|
false;
|
||||||
|
@ -10,6 +10,7 @@ const apprt = @import("../apprt.zig");
|
|||||||
const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue;
|
const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue;
|
||||||
const tracy = @import("tracy");
|
const tracy = @import("tracy");
|
||||||
const trace = tracy.trace;
|
const trace = tracy.trace;
|
||||||
|
const App = @import("../App.zig");
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const log = std.log.scoped(.renderer_thread);
|
const log = std.log.scoped(.renderer_thread);
|
||||||
@ -60,6 +61,9 @@ state: *renderer.State,
|
|||||||
/// this is a blocking queue so if it is full you will get errors (or block).
|
/// this is a blocking queue so if it is full you will get errors (or block).
|
||||||
mailbox: *Mailbox,
|
mailbox: *Mailbox,
|
||||||
|
|
||||||
|
/// Mailbox to send messages to the app thread
|
||||||
|
app_mailbox: App.Mailbox,
|
||||||
|
|
||||||
/// 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.
|
||||||
@ -68,6 +72,7 @@ pub fn init(
|
|||||||
surface: *apprt.Surface,
|
surface: *apprt.Surface,
|
||||||
renderer_impl: *renderer.Renderer,
|
renderer_impl: *renderer.Renderer,
|
||||||
state: *renderer.State,
|
state: *renderer.State,
|
||||||
|
app_mailbox: App.Mailbox,
|
||||||
) !Thread {
|
) !Thread {
|
||||||
// Create our event loop.
|
// Create our event loop.
|
||||||
var loop = try xev.Loop.init(.{});
|
var loop = try xev.Loop.init(.{});
|
||||||
@ -104,6 +109,7 @@ pub fn init(
|
|||||||
.renderer = renderer_impl,
|
.renderer = renderer_impl,
|
||||||
.state = state,
|
.state = state,
|
||||||
.mailbox = mailbox,
|
.mailbox = mailbox,
|
||||||
|
.app_mailbox = app_mailbox,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,6 +313,15 @@ fn renderCallback(
|
|||||||
|
|
||||||
t.renderer.render(t.surface, t.state) catch |err|
|
t.renderer.render(t.surface, t.state) catch |err|
|
||||||
log.warn("error rendering err={}", .{err});
|
log.warn("error rendering err={}", .{err});
|
||||||
|
|
||||||
|
// If we're doing single-threaded GPU calls then we also wake up the
|
||||||
|
// app thread to redraw at this point.
|
||||||
|
if (renderer.Renderer == renderer.OpenGL and
|
||||||
|
renderer.OpenGL.single_threaded_draw)
|
||||||
|
{
|
||||||
|
_ = t.app_mailbox.push(.{ .redraw_surface = t.surface }, .{ .instant = {} });
|
||||||
|
}
|
||||||
|
|
||||||
return .disarm;
|
return .disarm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +69,7 @@ pub inline fn link(p: Program) !void {
|
|||||||
|
|
||||||
pub inline fn use(p: Program) !Binding {
|
pub inline fn use(p: Program) !Binding {
|
||||||
glad.context.UseProgram.?(p.id);
|
glad.context.UseProgram.?(p.id);
|
||||||
|
try errors.getError();
|
||||||
return Binding{};
|
return Binding{};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ id: c.GLuint,
|
|||||||
|
|
||||||
pub inline fn active(target: c.GLenum) !void {
|
pub inline fn active(target: c.GLenum) !void {
|
||||||
glad.context.ActiveTexture.?(target);
|
glad.context.ActiveTexture.?(target);
|
||||||
|
try errors.getError();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enun for possible texture binding targets.
|
/// Enun for possible texture binding targets.
|
||||||
@ -153,6 +154,7 @@ pub inline fn create() !Texture {
|
|||||||
/// glBindTexture
|
/// glBindTexture
|
||||||
pub inline fn bind(v: Texture, target: Target) !Binding {
|
pub inline fn bind(v: Texture, target: Target) !Binding {
|
||||||
glad.context.BindTexture.?(@enumToInt(target), v.id);
|
glad.context.BindTexture.?(@enumToInt(target), v.id);
|
||||||
|
try errors.getError();
|
||||||
return Binding{ .target = target };
|
return Binding{ .target = target };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ const VertexArray = @This();
|
|||||||
|
|
||||||
const c = @import("c.zig");
|
const c = @import("c.zig");
|
||||||
const glad = @import("glad.zig");
|
const glad = @import("glad.zig");
|
||||||
|
const errors = @import("errors.zig");
|
||||||
|
|
||||||
id: c.GLuint,
|
id: c.GLuint,
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ pub inline fn unbind() !void {
|
|||||||
/// glBindVertexArray
|
/// glBindVertexArray
|
||||||
pub inline fn bind(v: VertexArray) !void {
|
pub inline fn bind(v: VertexArray) !void {
|
||||||
glad.context.BindVertexArray.?(v.id);
|
glad.context.BindVertexArray.?(v.id);
|
||||||
|
try errors.getError();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn destroy(v: VertexArray) void {
|
pub inline fn destroy(v: VertexArray) void {
|
||||||
|
Reference in New Issue
Block a user