From f73753ac6f22e415d1349a1d18d93574229126d7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 14 Nov 2022 12:24:38 -0800 Subject: [PATCH 1/4] opengl: automatically pad render area to balance non-grid space --- src/renderer/OpenGL.zig | 76 ++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 4450dae67..09c7b17b9 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -547,10 +547,6 @@ pub fn render( if (critical.screen_size) |size| { // Update our grid size try self.setScreenSize(size); - - // Update our viewport for this context to be the entire window. - // OpenGL works in pixels, so we have to use the pixel size. - try gl.viewport(0, 0, @intCast(i32, size.width), @intCast(i32, size.height)); } // Build our GPU cells @@ -950,24 +946,40 @@ pub fn updateCell( /// Set the screen size for rendering. This will update the projection /// used for the shader so that the scaling of the grid is correct. fn setScreenSize(self: *OpenGL, dim: renderer.ScreenSize) !void { - // Update the projection uniform within our shader - const bind = try self.program.use(); - defer bind.unbind(); - try self.program.setUniform( - "projection", - - // 2D orthographic projection with the full w/h - math.ortho2d( - 0, - @intToFloat(f32, dim.width), - @intToFloat(f32, dim.height), - 0, - ), - ); - // Recalculate the rows/columns. const grid_size = renderer.GridSize.init(dim, self.cell_size); + // Determine if we need to pad the window. For "auto" padding, we take + // the leftover amounts on the right/bottom that don't fit a full grid cell + // and we split them equal across all boundaries. + + // The size of our full grid + const grid_width = @intToFloat(f32, grid_size.columns) * self.cell_size.width; + const grid_height = @intToFloat(f32, grid_size.rows) * self.cell_size.height; + + // The empty space to the right of a line and bottom of the last row + const space_right = @intToFloat(f32, dim.width) - grid_width; + const space_bot = @intToFloat(f32, dim.height) - grid_height; + + // The left/right padding is just an equal split. + const padding_right = @floatToInt(i32, @floor(space_right / 2)); + const padding_left = padding_right; + + // The top/bottom padding is interesting. Subjectively, lots of padding + // at the top looks bad. So instead of always being equal (like left/right), + // we force the top padding to be at most equal to the left, and the bottom + // padding is the difference thereafter. + const padding_top = @min(padding_left, @floatToInt(i32, @floor(space_bot / 2))); + const padding_bot = @floatToInt(i32, space_bot - @intToFloat(f32, padding_top)); + + // The full padded dimensions of the renderable area. + const padded_dim: renderer.ScreenSize = .{ + .width = dim.width - @intCast(u32, padding_left + padding_right), + .height = dim.height - @intCast(u32, padding_bot + padding_top), + }; + + log.debug("screen size padded={} screen={} grid={} cell={}", .{ padded_dim, dim, grid_size, self.cell_size }); + // Update our LRU. We arbitrarily support a certain number of pages here. // We also always support a minimum number of caching in case a user // is resizing tiny then growing again we can save some of the renders. @@ -983,7 +995,31 @@ fn setScreenSize(self: *OpenGL, dim: renderer.ScreenSize) !void { self.alloc.free(self.font_shaper.cell_buf); self.font_shaper.cell_buf = shape_buf; - log.debug("screen size screen={} grid={}, cell={}", .{ dim, grid_size, self.cell_size }); + // Update our viewport for this context to be the entire window. + // OpenGL works in pixels, so we have to use the pixel size. + try gl.viewport( + padding_left, + padding_bot, + @intCast(i32, padded_dim.width), + @intCast(i32, padded_dim.height), + ); + + // Update the projection uniform within our shader + { + const bind = try self.program.use(); + defer bind.unbind(); + try self.program.setUniform( + "projection", + + // 2D orthographic projection with the full w/h + math.ortho2d( + 0, + @intToFloat(f32, padded_dim.width), + @intToFloat(f32, padded_dim.height), + 0, + ), + ); + } } /// Updates the font texture atlas if it is dirty. From 024cd65ac83ea7a3f20c7a2b924c3ce4dd20f892 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 14 Nov 2022 12:37:29 -0800 Subject: [PATCH 2/4] metal: implement auto padding --- src/renderer/Metal.zig | 86 ++++++++++++++++++++++++++++++------------ 1 file changed, 61 insertions(+), 25 deletions(-) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index e9e915a61..809324bfe 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -390,14 +390,13 @@ pub fn render( defer objc_autoreleasePoolPop(pool); // If we're resizing, then we have to update a bunch of things... - if (critical.screen_size) |screen_size| { - // Update our grid size - try self.setScreenSize(screen_size); - - const bounds = self.swapchain.getProperty(macos.graphics.Rect, "bounds"); + if (critical.screen_size) |_| { + // Note: we ignore the screen size value because our view should be + // automatically updated by being in the window. // Scale the bounds based on the layer content scale so that we // properly handle Retina. + const bounds = self.swapchain.getProperty(macos.graphics.Rect, "bounds"); const scaled: macos.graphics.Size = scaled: { const scaleFactor = self.swapchain.getProperty(macos.graphics.c.CGFloat, "contentsScale"); break :scaled .{ @@ -406,25 +405,8 @@ pub fn render( }; }; - // Set the size of the drawable surface to the scaled bounds - self.swapchain.setProperty("drawableSize", scaled); - //log.warn("bounds={} screen={} scaled={}", .{ bounds, screen_size, scaled }); - - // Setup our uniforms - const old = self.uniforms; - self.uniforms = .{ - .projection_matrix = math.ortho2d( - 0, - @floatCast(f32, scaled.width), - @floatCast(f32, scaled.height), - 0, - ), - .cell_size = .{ self.cell_size.width, self.cell_size.height }, - .underline_position = old.underline_position, - .underline_thickness = old.underline_thickness, - .strikethrough_position = old.strikethrough_position, - .strikethrough_thickness = old.strikethrough_thickness, - }; + // Handle our new size + try self.setScreenSize(scaled); } // Build our GPU cells @@ -572,10 +554,45 @@ pub fn render( } /// Resize the screen. -fn setScreenSize(self: *Metal, dim: renderer.ScreenSize) !void { +fn setScreenSize(self: *Metal, bounds: macos.graphics.Size) !void { + // Easier to work with our own types + const dim: renderer.ScreenSize = .{ + .width = @floatToInt(u32, bounds.width), + .height = @floatToInt(u32, bounds.height), + }; + // Recalculate the rows/columns. const grid_size = renderer.GridSize.init(dim, self.cell_size); + // Determine if we need to pad the window. For "auto" padding, we take + // the leftover amounts on the right/bottom that don't fit a full grid cell + // and we split them equal across all boundaries. + + // The size of our full grid + const grid_width = @intToFloat(f32, grid_size.columns) * self.cell_size.width; + const grid_height = @intToFloat(f32, grid_size.rows) * self.cell_size.height; + + // The empty space to the right of a line and bottom of the last row + const space_right = @intToFloat(f32, dim.width) - grid_width; + const space_bot = @intToFloat(f32, dim.height) - grid_height; + + // The left/right padding is just an equal split. + const padding_right = @floatToInt(i32, @floor(space_right / 2)); + const padding_left = padding_right; + + // The top/bottom padding is interesting. Subjectively, lots of padding + // at the top looks bad. So instead of always being equal (like left/right), + // we force the top padding to be at most equal to the left, and the bottom + // padding is the difference thereafter. + const padding_top = @min(padding_left, @floatToInt(i32, @floor(space_bot / 2))); + const padding_bot = @floatToInt(i32, space_bot - @intToFloat(f32, padding_top)); + + // The full padded dimensions of the renderable area. + const padded_dim: renderer.ScreenSize = .{ + .width = dim.width - @intCast(u32, padding_left + padding_right), + .height = dim.height - @intCast(u32, padding_bot + padding_top), + }; + // Update our shaper // TODO: don't reallocate if it is close enough (but bigger) var shape_buf = try self.alloc.alloc(font.Shaper.Cell, grid_size.columns * 2); @@ -583,6 +600,25 @@ fn setScreenSize(self: *Metal, dim: renderer.ScreenSize) !void { self.alloc.free(self.font_shaper.cell_buf); self.font_shaper.cell_buf = shape_buf; + // Set the size of the drawable surface to the bounds + self.swapchain.setProperty("drawableSize", bounds); + + // Setup our uniforms + const old = self.uniforms; + self.uniforms = .{ + .projection_matrix = math.ortho2d( + @intToFloat(f32, -1 * padding_left), + @intToFloat(f32, padded_dim.width), + @intToFloat(f32, padded_dim.height), + @intToFloat(f32, -1 * padding_top), + ), + .cell_size = .{ self.cell_size.width, self.cell_size.height }, + .underline_position = old.underline_position, + .underline_thickness = old.underline_thickness, + .strikethrough_position = old.strikethrough_position, + .strikethrough_thickness = old.strikethrough_thickness, + }; + log.debug("screen size screen={} grid={}, cell={}", .{ dim, grid_size, self.cell_size }); } From 4cab24a3daaeb8a307de48d17b5fee322116cf47 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 14 Nov 2022 13:10:12 -0800 Subject: [PATCH 3/4] extract auto-padding code to shared logic --- src/renderer/Metal.zig | 31 ++++----------------------- src/renderer/OpenGL.zig | 31 ++++----------------------- src/renderer/size.zig | 46 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 809324bfe..3985d7c6c 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -567,31 +567,8 @@ fn setScreenSize(self: *Metal, bounds: macos.graphics.Size) !void { // Determine if we need to pad the window. For "auto" padding, we take // the leftover amounts on the right/bottom that don't fit a full grid cell // and we split them equal across all boundaries. - - // The size of our full grid - const grid_width = @intToFloat(f32, grid_size.columns) * self.cell_size.width; - const grid_height = @intToFloat(f32, grid_size.rows) * self.cell_size.height; - - // The empty space to the right of a line and bottom of the last row - const space_right = @intToFloat(f32, dim.width) - grid_width; - const space_bot = @intToFloat(f32, dim.height) - grid_height; - - // The left/right padding is just an equal split. - const padding_right = @floatToInt(i32, @floor(space_right / 2)); - const padding_left = padding_right; - - // The top/bottom padding is interesting. Subjectively, lots of padding - // at the top looks bad. So instead of always being equal (like left/right), - // we force the top padding to be at most equal to the left, and the bottom - // padding is the difference thereafter. - const padding_top = @min(padding_left, @floatToInt(i32, @floor(space_bot / 2))); - const padding_bot = @floatToInt(i32, space_bot - @intToFloat(f32, padding_top)); - - // The full padded dimensions of the renderable area. - const padded_dim: renderer.ScreenSize = .{ - .width = dim.width - @intCast(u32, padding_left + padding_right), - .height = dim.height - @intCast(u32, padding_bot + padding_top), - }; + const padding = renderer.Padding.balanced(dim, grid_size, self.cell_size); + const padded_dim = dim.subPadding(padding); // Update our shaper // TODO: don't reallocate if it is close enough (but bigger) @@ -607,10 +584,10 @@ fn setScreenSize(self: *Metal, bounds: macos.graphics.Size) !void { const old = self.uniforms; self.uniforms = .{ .projection_matrix = math.ortho2d( - @intToFloat(f32, -1 * padding_left), + -1 * padding.left, @intToFloat(f32, padded_dim.width), @intToFloat(f32, padded_dim.height), - @intToFloat(f32, -1 * padding_top), + -1 * padding.top, ), .cell_size = .{ self.cell_size.width, self.cell_size.height }, .underline_position = old.underline_position, diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 09c7b17b9..eb94bb866 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -952,31 +952,8 @@ fn setScreenSize(self: *OpenGL, dim: renderer.ScreenSize) !void { // Determine if we need to pad the window. For "auto" padding, we take // the leftover amounts on the right/bottom that don't fit a full grid cell // and we split them equal across all boundaries. - - // The size of our full grid - const grid_width = @intToFloat(f32, grid_size.columns) * self.cell_size.width; - const grid_height = @intToFloat(f32, grid_size.rows) * self.cell_size.height; - - // The empty space to the right of a line and bottom of the last row - const space_right = @intToFloat(f32, dim.width) - grid_width; - const space_bot = @intToFloat(f32, dim.height) - grid_height; - - // The left/right padding is just an equal split. - const padding_right = @floatToInt(i32, @floor(space_right / 2)); - const padding_left = padding_right; - - // The top/bottom padding is interesting. Subjectively, lots of padding - // at the top looks bad. So instead of always being equal (like left/right), - // we force the top padding to be at most equal to the left, and the bottom - // padding is the difference thereafter. - const padding_top = @min(padding_left, @floatToInt(i32, @floor(space_bot / 2))); - const padding_bot = @floatToInt(i32, space_bot - @intToFloat(f32, padding_top)); - - // The full padded dimensions of the renderable area. - const padded_dim: renderer.ScreenSize = .{ - .width = dim.width - @intCast(u32, padding_left + padding_right), - .height = dim.height - @intCast(u32, padding_bot + padding_top), - }; + const padding = renderer.Padding.balanced(dim, grid_size, self.cell_size); + const padded_dim = dim.subPadding(padding); log.debug("screen size padded={} screen={} grid={} cell={}", .{ padded_dim, dim, grid_size, self.cell_size }); @@ -998,8 +975,8 @@ fn setScreenSize(self: *OpenGL, dim: renderer.ScreenSize) !void { // Update our viewport for this context to be the entire window. // OpenGL works in pixels, so we have to use the pixel size. try gl.viewport( - padding_left, - padding_bot, + @floatToInt(i32, padding.left), + @floatToInt(i32, padding.bottom), @intCast(i32, padded_dim.width), @intCast(i32, padded_dim.height), ); diff --git a/src/renderer/size.zig b/src/renderer/size.zig index cce9ca4b1..849603941 100644 --- a/src/renderer/size.zig +++ b/src/renderer/size.zig @@ -44,6 +44,14 @@ pub const CellSize = struct { pub const ScreenSize = struct { width: u32, height: u32, + + /// Subtract padding from the screen size. + pub fn subPadding(self: ScreenSize, padding: Padding) ScreenSize { + return .{ + .width = self.width - @floatToInt(u32, padding.left + padding.right), + .height = self.height - @floatToInt(u32, padding.top + padding.bottom), + }; + } }; /// The dimensions of the grid itself, in rows/columns units. @@ -68,6 +76,44 @@ pub const GridSize = struct { } }; +/// The padding to add to a screen. +pub const Padding = struct { + top: f32, + bottom: f32, + right: f32, + left: f32, + + /// Returns padding that balances the whitespace around the screen + /// for the given grid and cell sizes. + pub fn balanced(screen: ScreenSize, grid: GridSize, cell: CellSize) Padding { + // The size of our full grid + const grid_width = @intToFloat(f32, grid.columns) * cell.width; + const grid_height = @intToFloat(f32, grid.rows) * cell.height; + + // The empty space to the right of a line and bottom of the last row + const space_right = @intToFloat(f32, screen.width) - grid_width; + const space_bot = @intToFloat(f32, screen.height) - grid_height; + + // The left/right padding is just an equal split. + const padding_right = @floor(space_right / 2); + const padding_left = padding_right; + + // The top/bottom padding is interesting. Subjectively, lots of padding + // at the top looks bad. So instead of always being equal (like left/right), + // we force the top padding to be at most equal to the left, and the bottom + // padding is the difference thereafter. + const padding_top = @min(padding_left, @floor(space_bot / 2)); + const padding_bot = space_bot - padding_top; + + return .{ + .top = padding_top, + .bottom = padding_bot, + .right = padding_right, + .left = padding_left, + }; + } +}; + test "GridSize update exact" { const testing = std.testing; From 184b43ebd6e860b44ec541e163544fc42b0ae760 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 14 Nov 2022 13:13:50 -0800 Subject: [PATCH 4/4] opengl: padding is done via ortho matrix rather than viewport --- src/renderer/OpenGL.zig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index eb94bb866..076bcd2bb 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -975,10 +975,10 @@ fn setScreenSize(self: *OpenGL, dim: renderer.ScreenSize) !void { // Update our viewport for this context to be the entire window. // OpenGL works in pixels, so we have to use the pixel size. try gl.viewport( - @floatToInt(i32, padding.left), - @floatToInt(i32, padding.bottom), - @intCast(i32, padded_dim.width), - @intCast(i32, padded_dim.height), + 0, + 0, + @intCast(i32, dim.width), + @intCast(i32, dim.height), ); // Update the projection uniform within our shader @@ -990,10 +990,10 @@ fn setScreenSize(self: *OpenGL, dim: renderer.ScreenSize) !void { // 2D orthographic projection with the full w/h math.ortho2d( - 0, + -1 * padding.left, @intToFloat(f32, padded_dim.width), @intToFloat(f32, padded_dim.height), - 0, + -1 * padding.top, ), ); }