From ddc0d60ea2103a3378cf9f0d9eaa4cf006bd6c21 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 30 Oct 2022 22:04:37 -0700 Subject: [PATCH] metal: set contentsScale and handle screen scale factor for retina --- src/renderer/Metal.zig | 40 +++++++++++++++++++++------------------- src/shaders/cell.metal | 12 +++++------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 63f19ee60..1b34c93ba 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -2,10 +2,6 @@ //! //! Open questions: //! -//! - This requires a "px_scale" uniform to account for pixel scaling -//! issues with Retina. I'm not 100% sure why this is necessary and why -//! this doesn't happen with OpenGL. -//! pub const Metal = @This(); const std = @import("std"); @@ -84,11 +80,6 @@ const GPUUniforms = extern struct { /// This is calculated based on the size of the screen. projection_matrix: math.Mat, - /// A scale factor to apply to all pixels given as input (including - /// in this uniform i.e. cell_size). This is due to HiDPI screens (Retina) - /// mismatch with the window. - px_scale: [2]f32, - /// Size of a single cell in pixels, unscaled. cell_size: [2]f32, @@ -217,7 +208,6 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal { .cells = .{}, .uniforms = .{ .projection_matrix = undefined, - .px_scale = undefined, .cell_size = undefined, .underline_position = metrics.underline_position, .underline_thickness = metrics.underline_thickness, @@ -264,6 +254,13 @@ pub fn finalizeInit(self: *const Metal, window: glfw.Window) !void { contentView.setProperty("layer", self.swapchain.value); contentView.setProperty("wantsLayer", true); + // Ensure that our metal layer has a content scale set to match the + // scale factor of the window. This avoids magnification issues leading + // to blurry rendering. + const layer = contentView.getProperty(objc.Object, "layer"); + const scaleFactor = nswindow.getProperty(macos.graphics.c.CGFloat, "backingScaleFactor"); + layer.setProperty("contentsScale", scaleFactor); + if (DevMode.enabled) { // Initialize for our window assert(imgui.ImplGlfw.initForOther( @@ -352,24 +349,29 @@ pub fn render( if (critical.screen_size) |screen_size| { const bounds = self.swapchain.getProperty(macos.graphics.Rect, "bounds"); - // Set the size of the drawable surface to the bounds of our surface. - self.swapchain.setProperty("drawableSize", bounds.size); + // Scale the bounds based on the layer content scale so that we + // properly handle Retina. + const scaled: macos.graphics.Size = scaled: { + const scaleFactor = self.swapchain.getProperty(macos.graphics.c.CGFloat, "contentsScale"); + break :scaled .{ + .width = bounds.size.width * scaleFactor, + .height = bounds.size.height * scaleFactor, + }; + }; - // Our drawable surface is usually scaled so we need to figure - // out the scalem amount so our pixels are correct. - const scaleX = @floatCast(f32, bounds.size.width) / @intToFloat(f32, screen_size.width); - const scaleY = @floatCast(f32, bounds.size.height) / @intToFloat(f32, screen_size.height); + // 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, bounds.size.width), - @floatCast(f32, bounds.size.height), + @floatCast(f32, scaled.width), + @floatCast(f32, scaled.height), 0, ), - .px_scale = .{ scaleX, scaleY }, .cell_size = .{ self.cell_size.width, self.cell_size.height }, .underline_position = old.underline_position, .underline_thickness = old.underline_thickness, diff --git a/src/shaders/cell.metal b/src/shaders/cell.metal index 72bb29a6c..1519f60e6 100644 --- a/src/shaders/cell.metal +++ b/src/shaders/cell.metal @@ -14,7 +14,6 @@ enum Mode : uint8_t { struct Uniforms { float4x4 projection_matrix; - float2 px_scale; float2 cell_size; float underline_position; float underline_thickness; @@ -60,7 +59,7 @@ vertex VertexOut uber_vertex( VertexIn input [[ stage_in ]], constant Uniforms &uniforms [[ buffer(1) ]] ) { - float2 cell_size = uniforms.cell_size * uniforms.px_scale; + float2 cell_size = uniforms.cell_size; cell_size.x = cell_size.x * input.cell_width; // Convert the grid x,y into world space x, y by accounting for cell size @@ -95,8 +94,8 @@ vertex VertexOut uber_vertex( case MODE_FG: case MODE_FG_COLOR: { - float2 glyph_size = float2(input.glyph_size) * uniforms.px_scale; - float2 glyph_offset = float2(input.glyph_offset) * uniforms.px_scale; + float2 glyph_size = float2(input.glyph_size); + float2 glyph_offset = float2(input.glyph_offset); // If the glyph is larger than our cell, we need to downsample it. // The "+ 3" here is to give some wiggle room for fonts that are @@ -121,7 +120,6 @@ vertex VertexOut uber_vertex( // Calculate the texture coordinate in pixels. This is NOT normalized // (between 0.0 and 1.0) and must be done in the fragment shader. - // TODO: do I need to px_scale? out.tex_coord = float2(input.glyph_pos) + float2(input.glyph_size) * position; break; } @@ -158,7 +156,7 @@ vertex VertexOut uber_vertex( float2 underline_size = float2(cell_size.x, uniforms.underline_thickness); // Position the underline where we are told to - float2 underline_offset = float2(cell_size.x, uniforms.underline_position * uniforms.px_scale.y); + float2 underline_offset = float2(cell_size.x, uniforms.underline_position); // Go to the bottom of the cell, take away the size of the // underline, and that is our position. We also float it slightly @@ -174,7 +172,7 @@ vertex VertexOut uber_vertex( float2 strikethrough_size = float2(cell_size.x, uniforms.strikethrough_thickness); // Position the strikethrough where we are told to - float2 strikethrough_offset = float2(cell_size.x, uniforms.strikethrough_position * uniforms.px_scale.y); + float2 strikethrough_offset = float2(cell_size.x, uniforms.strikethrough_position); // Go to the bottom of the cell, take away the size of the // strikethrough, and that is our position. We also float it slightly