gtk: render!

This commit is contained in:
Mitchell Hashimoto
2023-02-23 11:19:51 -08:00
parent b19f9b2aff
commit 6acf67ec66
9 changed files with 106 additions and 36 deletions

View File

@ -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,

View File

@ -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();

View File

@ -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 });

View File

@ -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(&gtkRealize), self, null, c.G_CONNECT_DEFAULT);
opts.gl_area, _ = c.g_signal_connect_data(opts.gl_area, "destroy", c.G_CALLBACK(&gtkDestroy), self, null, c.G_CONNECT_DEFAULT);
"realize", _ = c.g_signal_connect_data(opts.gl_area, "render", c.G_CALLBACK(&gtkRender), self, null, c.G_CONNECT_DEFAULT);
c.G_CALLBACK(&gtkRealize), _ = c.g_signal_connect_data(opts.gl_area, "resize", c.G_CALLBACK(&gtkResize), self, null, c.G_CONNECT_DEFAULT);
self,
null,
c.G_CONNECT_DEFAULT,
);
_ = c.g_signal_connect_data(
opts.gl_area,
"render",
c.G_CALLBACK(&gtkRender),
null,
null,
c.G_CONNECT_DEFAULT,
);
_ = c.g_signal_connect_data(
opts.gl_area,
"destroy",
c.G_CALLBACK(&gtkDestroy),
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 {

View File

@ -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;

View File

@ -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;
} }

View File

@ -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{};
} }

View File

@ -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 };
} }

View File

@ -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 {