52933806
Honor wl_output buffer scale in text-compare mode
a73x 2026-04-09 13:17
Commit message
src/main.zig
| Old | New | ||
|---|---|---|---|
| @@ -16,6 +16,35 @@ const GridSize = struct { | |||
| 16 | rows: u16, | 16 | rows: u16, |
| 17 | }; | 17 | }; |
| 18 | 18 | ||
| 19 | const ScaledGeometry = struct { | ||
| 20 | buffer_scale: i32, | ||
| 21 | px_size: u32, | ||
| 22 | cell_w_px: u32, // buffer pixels | ||
| 23 | cell_h_px: u32, // buffer pixels | ||
| 24 | baseline_px: u32, | ||
| 25 | }; | ||
| 26 | |||
| 27 | fn rebuildFaceForScale( | ||
| 28 | face: *font.Face, | ||
| 29 | atlas: *font.Atlas, | ||
| 30 | font_path: [:0]const u8, | ||
| 31 | font_index: c_int, | ||
| 32 | base_px_size: u32, | ||
| 33 | buffer_scale: i32, | ||
| 34 | ) !ScaledGeometry { | ||
| 35 | const scale: u32 = @intCast(@max(@as(i32, 1), buffer_scale)); | ||
| 36 | const new_px = base_px_size * scale; | ||
| 37 | try face.reinit(font_path, font_index, new_px); | ||
| 38 | atlas.reset(); | ||
| 39 | return .{ | ||
| 40 | .buffer_scale = @intCast(scale), | ||
| 41 | .px_size = new_px, | ||
| 42 | .cell_w_px = face.cellWidth(), | ||
| 43 | .cell_h_px = face.cellHeight(), | ||
| 44 | .baseline_px = face.baseline(), | ||
| 45 | }; | ||
| 46 | } | ||
| 47 | |||
| 19 | fn writePtyFromTerminal(_: *vt.Terminal, ctx: ?*anyopaque, data: []const u8) void { | 48 | fn writePtyFromTerminal(_: *vt.Terminal, ctx: ?*anyopaque, data: []const u8) void { |
| 20 | const p: *pty.Pty = @ptrCast(@alignCast(ctx orelse return)); | 49 | const p: *pty.Pty = @ptrCast(@alignCast(ctx orelse return)); |
| 21 | _ = p.write(data) catch |err| { | 50 | _ = p.write(data) catch |err| { |
| @@ -1771,23 +1800,35 @@ fn runTextCoverageCompare(alloc: std.mem.Allocator) !void { | |||
| 1771 | var face = try font.Face.init(alloc, font_lookup.path, font_lookup.index, config.font_size_px); | 1800 | var face = try font.Face.init(alloc, font_lookup.path, font_lookup.index, config.font_size_px); |
| 1772 | defer face.deinit(); | 1801 | defer face.deinit(); |
| 1773 | 1802 | ||
| 1774 | var atlas = try font.Atlas.init(alloc, 1024, 1024); | 1803 | var atlas = try font.Atlas.init(alloc, 2048, 2048); |
| 1775 | defer atlas.deinit(); | 1804 | defer atlas.deinit(); |
| 1776 | 1805 | ||
| 1806 | var geom: ScaledGeometry = .{ | ||
| 1807 | .buffer_scale = 1, | ||
| 1808 | .px_size = config.font_size_px, | ||
| 1809 | .cell_w_px = face.cellWidth(), | ||
| 1810 | .cell_h_px = face.cellHeight(), | ||
| 1811 | .baseline_px = face.baseline(), | ||
| 1812 | }; | ||
| 1813 | |||
| 1777 | var scene = try buildTextCoverageCompareScene(alloc, &face, &atlas); | 1814 | var scene = try buildTextCoverageCompareScene(alloc, &face, &atlas); |
| 1778 | defer scene.deinit(alloc); | 1815 | defer scene.deinit(alloc); |
| 1779 | 1816 | ||
| 1780 | const cell_w = face.cellWidth(); | 1817 | // Initial surface-coordinate window size. Prefer whatever the compositor |
| 1781 | const cell_h = face.cellHeight(); | 1818 | // configured us at (already stored on window by xdgToplevelListener). If |
| 1782 | window.width = scene.window_cols * cell_w; | 1819 | // the initial configure was (0, 0) — meaning "client chooses" — use a |
| 1783 | window.height = scene.window_rows * cell_h; | 1820 | // size that fits the scene exactly at scale=1. |
| 1821 | if (window.width == 800 and window.height == 600) { | ||
| 1822 | window.width = scene.window_cols * geom.cell_w_px; | ||
| 1823 | window.height = scene.window_rows * geom.cell_h_px; | ||
| 1824 | } | ||
| 1784 | 1825 | ||
| 1785 | var ctx = try renderer.Context.init( | 1826 | var ctx = try renderer.Context.init( |
| 1786 | alloc, | 1827 | alloc, |
| 1787 | @ptrCast(conn.display), | 1828 | @ptrCast(conn.display), |
| 1788 | @ptrCast(window.surface), | 1829 | @ptrCast(window.surface), |
| 1789 | window.width, | 1830 | window.width * @as(u32, @intCast(geom.buffer_scale)), |
| 1790 | window.height, | 1831 | window.height * @as(u32, @intCast(geom.buffer_scale)), |
| 1791 | ); | 1832 | ); |
| 1792 | defer ctx.deinit(); | 1833 | defer ctx.deinit(); |
| 1793 | 1834 | ||
| @@ -1801,6 +1842,7 @@ fn runTextCoverageCompare(alloc: std.mem.Allocator) !void { | |||
| 1801 | }; | 1842 | }; |
| 1802 | var last_window_w = window.width; | 1843 | var last_window_w = window.width; |
| 1803 | var last_window_h = window.height; | 1844 | var last_window_h = window.height; |
| 1845 | var last_scale: i32 = geom.buffer_scale; | ||
| 1804 | 1846 | ||
| 1805 | while (!window.should_close) { | 1847 | while (!window.should_close) { |
| 1806 | _ = conn.display.flush(); | 1848 | _ = conn.display.flush(); |
| @@ -1815,9 +1857,42 @@ fn runTextCoverageCompare(alloc: std.mem.Allocator) !void { | |||
| 1815 | } | 1857 | } |
| 1816 | _ = conn.display.dispatchPending(); | 1858 | _ = conn.display.dispatchPending(); |
| 1817 | 1859 | ||
| 1818 | if (window.width != last_window_w or window.height != last_window_h) { | 1860 | const current_scale = window.bufferScale(); |
| 1861 | const scale_changed = current_scale != last_scale; | ||
| 1862 | const size_changed = window.width != last_window_w or window.height != last_window_h; | ||
| 1863 | |||
| 1864 | if (scale_changed or size_changed) { | ||
| 1819 | _ = try ctx.vkd.deviceWaitIdle(ctx.device); | 1865 | _ = try ctx.vkd.deviceWaitIdle(ctx.device); |
| 1820 | try ctx.recreateSwapchain(window.width, window.height); | 1866 | |
| 1867 | if (scale_changed) { | ||
| 1868 | geom = try rebuildFaceForScale( | ||
| 1869 | &face, | ||
| 1870 | &atlas, | ||
| 1871 | font_lookup.path, | ||
| 1872 | font_lookup.index, | ||
| 1873 | config.font_size_px, | ||
| 1874 | current_scale, | ||
| 1875 | ); | ||
| 1876 | // Rebuild the scene against the fresh atlas. | ||
| 1877 | scene.deinit(alloc); | ||
| 1878 | scene = try buildTextCoverageCompareScene(alloc, &face, &atlas); | ||
| 1879 | |||
| 1880 | // Do NOT touch window.width/window.height here — those reflect the | ||
| 1881 | // compositor's configured surface size (from xdg_toplevel.configure). | ||
| 1882 | // Overwriting them forced sway to non-integer-scale our buffer to fit | ||
| 1883 | // its tile, which was the actual cause of the residual fuzz. | ||
| 1884 | |||
| 1885 | window.surface.setBufferScale(geom.buffer_scale); | ||
| 1886 | try ctx.uploadAtlas(atlas.pixels); | ||
| 1887 | atlas.dirty = false; | ||
| 1888 | try ctx.uploadInstances(scene.instances.items); | ||
| 1889 | last_scale = current_scale; | ||
| 1890 | } | ||
| 1891 | |||
| 1892 | const buf_w = window.width * @as(u32, @intCast(geom.buffer_scale)); | ||
| 1893 | const buf_h = window.height * @as(u32, @intCast(geom.buffer_scale)); | ||
| 1894 | try ctx.recreateSwapchain(buf_w, buf_h); | ||
| 1895 | |||
| 1821 | last_window_w = window.width; | 1896 | last_window_w = window.width; |
| 1822 | last_window_h = window.height; | 1897 | last_window_h = window.height; |
| 1823 | } | 1898 | } |
| @@ -1825,13 +1900,15 @@ fn runTextCoverageCompare(alloc: std.mem.Allocator) !void { | |||
| 1825 | drawTextCoverageCompareFrame( | 1900 | drawTextCoverageCompareFrame( |
| 1826 | &ctx, | 1901 | &ctx, |
| 1827 | &scene, | 1902 | &scene, |
| 1828 | cell_w, | 1903 | geom.cell_w_px, |
| 1829 | cell_h, | 1904 | geom.cell_h_px, |
| 1830 | .{ 0.0, 0.0, 0.0, 1.0 }, | 1905 | .{ 0.0, 0.0, 0.0, 1.0 }, |
| 1831 | ) catch |err| switch (err) { | 1906 | ) catch |err| switch (err) { |
| 1832 | error.OutOfDateKHR => { | 1907 | error.OutOfDateKHR => { |
| 1833 | _ = try ctx.vkd.deviceWaitIdle(ctx.device); | 1908 | _ = try ctx.vkd.deviceWaitIdle(ctx.device); |
| 1834 | try ctx.recreateSwapchain(window.width, window.height); | 1909 | const buf_w = window.width * @as(u32, @intCast(geom.buffer_scale)); |
| 1910 | const buf_h = window.height * @as(u32, @intCast(geom.buffer_scale)); | ||
| 1911 | try ctx.recreateSwapchain(buf_w, buf_h); | ||
| 1835 | last_window_w = window.width; | 1912 | last_window_w = window.width; |
| 1836 | last_window_h = window.height; | 1913 | last_window_h = window.height; |
| 1837 | continue; | 1914 | continue; |