mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +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),
|
||||
.quit => try self.setQuit(),
|
||||
.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);
|
||||
}
|
||||
|
||||
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
|
||||
fn newWindow(self: *App, rt_app: *apprt.App, msg: Message.NewWindow) !void {
|
||||
if (!@hasDecl(apprt.App, "newWindow")) {
|
||||
@ -230,6 +236,12 @@ pub const Message = union(enum) {
|
||||
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 {
|
||||
/// The parent surface
|
||||
parent: ?*Surface = null,
|
||||
|
@ -316,6 +316,7 @@ pub fn init(
|
||||
rt_surface,
|
||||
&self.renderer,
|
||||
&self.renderer_state,
|
||||
app_mailbox,
|
||||
);
|
||||
errdefer render_thread.deinit();
|
||||
|
||||
|
@ -137,6 +137,13 @@ pub const App = struct {
|
||||
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 {
|
||||
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.");
|
||||
}
|
||||
|
||||
pub fn redrawSurface(self: *App, surface: *Surface) void {
|
||||
_ = self;
|
||||
surface.invalidate();
|
||||
}
|
||||
|
||||
pub fn newWindow(self: *App, parent_: ?*CoreSurface) !void {
|
||||
_ = parent_;
|
||||
|
||||
@ -169,42 +174,30 @@ pub const Surface = struct {
|
||||
/// The app we're part of
|
||||
app: *App,
|
||||
|
||||
/// Our GTK area
|
||||
gl_area: *c.GtkGLArea,
|
||||
|
||||
/// The core surface backing this surface
|
||||
core_surface: CoreSurface,
|
||||
|
||||
/// Cached metrics about the surface from GTK callbacks.
|
||||
size: apprt.SurfaceSize,
|
||||
|
||||
pub fn init(self: *Surface, app: *App, opts: Options) !void {
|
||||
// Build our result
|
||||
self.* = .{
|
||||
.app = app,
|
||||
.gl_area = opts.gl_area,
|
||||
.core_surface = undefined,
|
||||
.size = .{ .width = 800, .height = 600 },
|
||||
};
|
||||
errdefer self.* = undefined;
|
||||
|
||||
// Create the GL area that will contain our surface
|
||||
_ = c.g_signal_connect_data(
|
||||
opts.gl_area,
|
||||
"realize",
|
||||
c.G_CALLBACK(>kRealize),
|
||||
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,
|
||||
);
|
||||
_ = c.g_signal_connect_data(opts.gl_area, "realize", c.G_CALLBACK(>kRealize), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(opts.gl_area, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(opts.gl_area, "render", c.G_CALLBACK(>kRender), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(opts.gl_area, "resize", c.G_CALLBACK(>kResize), self, null, c.G_CONNECT_DEFAULT);
|
||||
}
|
||||
|
||||
fn realize(self: *Surface) !void {
|
||||
@ -220,16 +213,33 @@ pub const Surface = struct {
|
||||
self,
|
||||
);
|
||||
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 {
|
||||
_ = area;
|
||||
|
||||
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
|
||||
// initialize the core surface which will setup the renderer.
|
||||
const self = userdataSelf(ud orelse return);
|
||||
const self = userdataSelf(ud.?);
|
||||
self.realize() catch |err| {
|
||||
// TODO: we need to destroy the GL area here.
|
||||
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 {
|
||||
_ = area;
|
||||
_ = ctx;
|
||||
_ = ud;
|
||||
log.debug("gl render", .{});
|
||||
|
||||
const opengl = @import("../renderer/opengl/main.zig");
|
||||
opengl.clearColor(0, 0.5, 1, 1);
|
||||
opengl.clear(opengl.c.GL_COLOR_BUFFER_BIT);
|
||||
const self = userdataSelf(ud.?);
|
||||
self.render() catch |err| {
|
||||
log.err("surface failed to render: {}", .{err});
|
||||
return 0;
|
||||
};
|
||||
|
||||
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
|
||||
fn gtkDestroy(v: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void {
|
||||
_ = v;
|
||||
|
||||
const self = userdataSelf(ud orelse return);
|
||||
const self = userdataSelf(ud.?);
|
||||
const alloc = self.app.core_app.alloc;
|
||||
self.deinit();
|
||||
alloc.destroy(self);
|
||||
@ -283,8 +314,7 @@ pub const Surface = struct {
|
||||
}
|
||||
|
||||
pub fn getSize(self: *const Surface) !apprt.SurfaceSize {
|
||||
_ = self;
|
||||
return .{ .width = 800, .height = 600 };
|
||||
return self.size;
|
||||
}
|
||||
|
||||
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
|
||||
/// 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"))
|
||||
pub const single_threaded_draw = if (@hasDecl(apprt.Surface, "opengl_single_threaded_draw"))
|
||||
apprt.Surface.opengl_single_threaded_draw
|
||||
else
|
||||
false;
|
||||
|
@ -10,6 +10,7 @@ const apprt = @import("../apprt.zig");
|
||||
const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue;
|
||||
const tracy = @import("tracy");
|
||||
const trace = tracy.trace;
|
||||
const App = @import("../App.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
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).
|
||||
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
|
||||
/// 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.
|
||||
@ -68,6 +72,7 @@ pub fn init(
|
||||
surface: *apprt.Surface,
|
||||
renderer_impl: *renderer.Renderer,
|
||||
state: *renderer.State,
|
||||
app_mailbox: App.Mailbox,
|
||||
) !Thread {
|
||||
// Create our event loop.
|
||||
var loop = try xev.Loop.init(.{});
|
||||
@ -104,6 +109,7 @@ pub fn init(
|
||||
.renderer = renderer_impl,
|
||||
.state = state,
|
||||
.mailbox = mailbox,
|
||||
.app_mailbox = app_mailbox,
|
||||
};
|
||||
}
|
||||
|
||||
@ -307,6 +313,15 @@ fn renderCallback(
|
||||
|
||||
t.renderer.render(t.surface, t.state) catch |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;
|
||||
}
|
||||
|
||||
|
@ -69,6 +69,7 @@ pub inline fn link(p: Program) !void {
|
||||
|
||||
pub inline fn use(p: Program) !Binding {
|
||||
glad.context.UseProgram.?(p.id);
|
||||
try errors.getError();
|
||||
return Binding{};
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ id: c.GLuint,
|
||||
|
||||
pub inline fn active(target: c.GLenum) !void {
|
||||
glad.context.ActiveTexture.?(target);
|
||||
try errors.getError();
|
||||
}
|
||||
|
||||
/// Enun for possible texture binding targets.
|
||||
@ -153,6 +154,7 @@ pub inline fn create() !Texture {
|
||||
/// glBindTexture
|
||||
pub inline fn bind(v: Texture, target: Target) !Binding {
|
||||
glad.context.BindTexture.?(@enumToInt(target), v.id);
|
||||
try errors.getError();
|
||||
return Binding{ .target = target };
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ const VertexArray = @This();
|
||||
|
||||
const c = @import("c.zig");
|
||||
const glad = @import("glad.zig");
|
||||
const errors = @import("errors.zig");
|
||||
|
||||
id: c.GLuint,
|
||||
|
||||
@ -20,6 +21,7 @@ pub inline fn unbind() !void {
|
||||
/// glBindVertexArray
|
||||
pub inline fn bind(v: VertexArray) !void {
|
||||
glad.context.BindVertexArray.?(v.id);
|
||||
try errors.getError();
|
||||
}
|
||||
|
||||
pub inline fn destroy(v: VertexArray) void {
|
||||
|
Reference in New Issue
Block a user