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),
.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,

View File

@ -316,6 +316,7 @@ pub fn init(
rt_surface,
&self.renderer,
&self.renderer_state,
app_mailbox,
);
errdefer render_thread.deinit();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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