From bc7790f5026c1fa105aa09339e337296e1d4333a Mon Sep 17 00:00:00 2001 From: Gabriel Dinner-David Date: Wed, 20 Nov 2024 23:58:45 -0500 Subject: [PATCH] wasm shaping and atlas rendering again --- build.zig | 2 + example/app.ts | 130 ++++++++-------- example/index.html | 1 + example/package-lock.json | 35 +---- example/package.json | 2 +- src/App.zig | 2 +- src/Surface.zig | 8 +- src/build/GhosttyWasm.zig | 79 ++++++++++ src/build/SharedDeps.zig | 49 +++++-- src/build/main.zig | 1 + src/build_config.zig | 4 +- src/cli/args.zig | 2 +- src/config/Config.zig | 28 ++-- src/config/theme.zig | 2 +- src/font/Collection.zig | 21 +++ src/font/DeferredFace.zig | 4 +- src/font/SharedGrid.zig | 48 ++++++ src/font/face/web_canvas.zig | 128 +++++++++------- src/font/main.zig | 2 + src/font/shaper/web_canvas.zig | 187 +++++++++++++++++------- src/main_wasm.zig | 15 +- src/os/desktop.zig | 2 + src/os/homedir.zig | 2 + src/os/main.zig | 7 + src/terminal/kitty/graphics_image.zig | 4 +- src/terminal/kitty/graphics_storage.zig | 3 +- src/terminal/page.zig | 2 +- src/termio/Exec.zig | 6 +- src/termio/Termio.zig | 4 +- 29 files changed, 532 insertions(+), 248 deletions(-) create mode 100644 src/build/GhosttyWasm.zig diff --git a/build.zig b/build.zig index 38d2bca6d..0f7c010e0 100644 --- a/build.zig +++ b/build.zig @@ -15,6 +15,8 @@ pub fn build(b: *std.Build) !void { // Ghostty dependencies used by many artifacts. const deps = try buildpkg.SharedDeps.init(b, &config); const exe = try buildpkg.GhosttyExe.init(b, &config, &deps); + const wasm = try buildpkg.GhosttyWasm.init(b, &config, &deps); + _ = wasm; if (config.emit_helpgen) deps.help_strings.install(); // Ghostty docs diff --git a/example/app.ts b/example/app.ts index 5b426a333..af0b3fcf7 100644 --- a/example/app.ts +++ b/example/app.ts @@ -1,11 +1,11 @@ -import { ZigJS } from "zig-js"; +import { ZigJS } from "zig-js/src/index.ts"; const zjs = new ZigJS(); const importObject = { module: {}, env: { memory: new WebAssembly.Memory({ - initial: 25, + initial: 512, maximum: 65536, shared: true, }), @@ -27,38 +27,36 @@ fetch(url.href) .then((results) => { const memory = importObject.env.memory; const { - malloc, - free, - config_new, + atlas_clear, + atlas_debug_canvas, + atlas_free, + atlas_grow, + atlas_new, + atlas_reserve, + atlas_set, + config_finalize, config_free, config_load_string, - config_finalize, - face_new, - face_free, - face_render_glyph, - face_debug_canvas, - deferred_face_new, + config_new, deferred_face_free, deferred_face_load, - deferred_face_face, - group_new, - group_free, - group_add_face, - group_init_sprite_face, - group_index_for_codepoint, - group_render_glyph, - group_cache_new, - group_cache_free, - group_cache_index_for_codepoint, - group_cache_render_glyph, - group_cache_atlas_grayscale, - group_cache_atlas_color, - atlas_new, - atlas_free, - atlas_debug_canvas, - shaper_new, + deferred_face_new, + face_debug_canvas, + face_free, + face_new, + face_render_glyph, + free, + malloc, shaper_free, + shaper_new, shaper_test, + collection_new, + collection_add_deferred_face, + shared_grid_new, + shared_grid_atlas_grayscale, + shared_grid_atlas_color, + shared_grid_index_for_codepoint, + shared_grid_render_glyph, } = results.instance.exports; // Give us access to the zjs value for debugging. globalThis.zjs = zjs; @@ -98,57 +96,63 @@ fetch(url.href) //free(font_ptr); // Create our group - const group = group_new(32 /* size */); - group_add_face( - group, + const collection = collection_new(24); + collection_add_deferred_face( + collection, 0 /* regular */, deferred_face_new(font_name.ptr, font_name.len, 0 /* text */), ); - group_add_face( - group, + collection_add_deferred_face( + collection, 0 /* regular */, deferred_face_new(font_name.ptr, font_name.len, 1 /* emoji */), ); + const grid = shared_grid_new(collection); // Initialize our sprite font, without this we just use the browser. - group_init_sprite_face(group); + // group_init_sprite_face(group); - // Create our group cache - const group_cache = group_cache_new(group); + // // Create our group cache + // const group_cache = group_cache_new(group); // Render a glyph - // for (let i = 33; i <= 126; i++) { - // const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1); - // group_cache_render_glyph(group_cache, font_idx, i, 0); - // //face_render_glyph(face, atlas, i); - // } + for (let i = 33; i <= 126; i++) { + const font_idx = shared_grid_index_for_codepoint(grid, i, 0, -1); + shared_grid_render_glyph(grid, font_idx, i, 0); + //face_render_glyph(face, atlas, i); + } // - // const emoji = ["🐏","🌞","🌚","🍱","πŸ’Ώ","🐈","πŸ“ƒ","πŸ“€","πŸ•‘","πŸ™ƒ"]; - // for (let i = 0; i < emoji.length; i++) { - // const cp = emoji[i].codePointAt(0); - // const font_idx = group_cache_index_for_codepoint(group_cache, cp, 0, -1 /* best choice */); - // group_cache_render_glyph(group_cache, font_idx, cp, 0); - // } + const emoji = ["🐏", "🌞", "🌚", "🍱", "πŸ’Ώ", "🐈", "πŸ“ƒ", "πŸ“€", "πŸ•‘", "πŸ™ƒ"]; + for (let i = 0; i < emoji.length; i++) { + const cp = emoji[i].codePointAt(0); + const font_idx = shared_grid_index_for_codepoint( + grid, + cp, + 0, + -1 /* best choice */, + ); + shared_grid_render_glyph(grid, font_idx, cp, 0); + } for (let i = 0x2500; i <= 0x257f; i++) { - const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1); - group_cache_render_glyph(group_cache, font_idx, i, 0); + const font_idx = shared_grid_index_for_codepoint(grid, i, 0, -1); + shared_grid_render_glyph(grid, font_idx, i, 0); } for (let i = 0x2580; i <= 0x259f; i++) { - const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1); - group_cache_render_glyph(group_cache, font_idx, i, 0); + const font_idx = shared_grid_index_for_codepoint(grid, i, 0, -1); + shared_grid_render_glyph(grid, font_idx, i, 0); } for (let i = 0x2800; i <= 0x28ff; i++) { - const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1); - group_cache_render_glyph(group_cache, font_idx, i, 0); + const font_idx = shared_grid_index_for_codepoint(grid, i, 0, -1); + shared_grid_render_glyph(grid, font_idx, i, 0); } for (let i = 0x1fb00; i <= 0x1fb3b; i++) { - const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1); - group_cache_render_glyph(group_cache, font_idx, i, 0); + const font_idx = shared_grid_index_for_codepoint(grid, i, 0, -1); + shared_grid_render_glyph(grid, font_idx, i, 0); } for (let i = 0x1fb3c; i <= 0x1fb6b; i++) { - const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1); - group_cache_render_glyph(group_cache, font_idx, i, 0); + const font_idx = shared_grid_index_for_codepoint(grid, i, 0, -1); + shared_grid_render_glyph(grid, font_idx, i, 0); } //face_render_glyph(face, atlas, "ζ©‹".codePointAt(0)); @@ -161,26 +165,26 @@ fetch(url.href) const shaper = shaper_new(120); //const input = makeStr("hello🐏"); const input = makeStr("helloπŸπŸ‘πŸ½"); - shaper_test(shaper, group_cache, input.ptr, input.len); + shaper_test(shaper, grid, input.ptr, input.len); const cp = 1114112; - const font_idx = group_cache_index_for_codepoint( - group_cache, + const font_idx = shared_grid_index_for_codepoint( + grid, cp, 0, -1 /* best choice */, ); - group_cache_render_glyph(group_cache, font_idx, cp, -1); + shared_grid_render_glyph(grid, font_idx, cp, -1); // Debug our atlas canvas { - const atlas = group_cache_atlas_grayscale(group_cache); + const atlas = shared_grid_atlas_grayscale(grid); const id = atlas_debug_canvas(atlas); document.getElementById("atlas-canvas").append(zjs.deleteValue(id)); } { - const atlas = group_cache_atlas_color(group_cache); + const atlas = shared_grid_atlas_color(grid); const id = atlas_debug_canvas(atlas); document.getElementById("atlas-color-canvas").append(zjs.deleteValue(id)); } diff --git a/example/index.html b/example/index.html index 2e66f92d1..02cd65183 100644 --- a/example/index.html +++ b/example/index.html @@ -8,6 +8,7 @@

Open your console, we are just debugging here.

The current grayscale font atlas is rendered below.

+

The current color font atlas is rendered below.

diff --git a/example/package-lock.json b/example/package-lock.json index 3cb4de6f0..c18a6b878 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "zig-js": "file:../vendor/zig-js/js" + "zig-js": "https://gitpkg.vercel.app/mitchellh/zig-js/js?main" }, "devDependencies": { "@parcel/transformer-inline-string": "^2.8.0", @@ -20,20 +20,6 @@ "../js": { "extraneous": true }, - "../vendor/zig-js/js": { - "name": "zig-js-glue", - "version": "0.1.2", - "license": "MIT", - "devDependencies": { - "@parcel/packager-ts": "^2.8.0", - "@parcel/transformer-typescript-types": "^2.8.0", - "@types/jest": "^29.2.3", - "jest": "^29.3.1", - "parcel": "^2.8.0", - "ts-jest": "^29.0.3", - "typescript": "^4.9.3" - } - }, "node_modules/@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -2693,8 +2679,11 @@ } }, "node_modules/zig-js": { - "resolved": "../vendor/zig-js/js", - "link": true + "name": "zig-js-glue", + "version": "0.1.3", + "resolved": "https://gitpkg.vercel.app/mitchellh/zig-js/js?main", + "integrity": "sha512-CdM4TmAINU1fsZMm0S3dH4XzQgCIC4AWfztA2eGRD9Tfk/2LfCZ7RgOED7i26P1Jf2+BCQIBMbxXocV7oxR3Ig==", + "license": "MIT" } }, "dependencies": { @@ -4421,16 +4410,8 @@ "dev": true }, "zig-js": { - "version": "file:../vendor/zig-js/js", - "requires": { - "@parcel/packager-ts": "^2.8.0", - "@parcel/transformer-typescript-types": "^2.8.0", - "@types/jest": "^29.2.3", - "jest": "^29.3.1", - "parcel": "^2.8.0", - "ts-jest": "^29.0.3", - "typescript": "^4.9.3" - } + "version": "https://gitpkg.vercel.app/mitchellh/zig-js/js?main", + "integrity": "sha512-CdM4TmAINU1fsZMm0S3dH4XzQgCIC4AWfztA2eGRD9Tfk/2LfCZ7RgOED7i26P1Jf2+BCQIBMbxXocV7oxR3Ig==" } } } diff --git a/example/package.json b/example/package.json index e884e5c6c..b031a6cc9 100644 --- a/example/package.json +++ b/example/package.json @@ -17,6 +17,6 @@ "typescript": "^4.9.3" }, "dependencies": { - "zig-js": "file:../vendor/zig-js/js" + "zig-js": "https://gitpkg.vercel.app/mitchellh/zig-js/js?main" } } diff --git a/src/App.zig b/src/App.zig index 15859d115..ea489509a 100644 --- a/src/App.zig +++ b/src/App.zig @@ -61,7 +61,7 @@ font_grid_set: font.SharedGridSet, // Used to rate limit desktop notifications. Some platforms (notably macOS) will // run out of resources if desktop notifications are sent too fast and the OS // will kill Ghostty. -last_notification_time: ?std.time.Instant = null, +last_notification_time: ?internal_os.Instant = null, last_notification_digest: u64 = 0, /// The conditional state of the configuration. See the equivalent field diff --git a/src/Surface.zig b/src/Surface.zig index b81a45ecb..bafd22576 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -174,7 +174,7 @@ const Mouse = struct { /// The left click time was the last time the left click was done. This /// is always set on the first left click. left_click_count: u8 = 0, - left_click_time: std.time.Instant = undefined, + left_click_time: internal_os.Instant = undefined, /// The last x/y sent for mouse reports. event_point: ?terminal.point.Coordinate = null, @@ -2857,7 +2857,7 @@ pub fn mouseButtonCallback( // If we are within the interval that the click would register // an increment then we do not extend the selection. - if (std.time.Instant.now()) |now| { + if (internal_os.Instant.now()) |now| { const since = now.since(self.mouse.left_click_time); if (since <= self.config.mouse_interval) { // Click interval very short, we may be increasing @@ -3003,7 +3003,7 @@ pub fn mouseButtonCallback( self.mouse.left_click_ypos = pos.y; // Setup our click counter and timer - if (std.time.Instant.now()) |now| { + if (internal_os.Instant.now()) |now| { // If we have mouse clicks, then we check if the time elapsed // is less than and our interval and if so, increase the count. if (self.mouse.left_click_count > 0) { @@ -4653,7 +4653,7 @@ fn showDesktopNotification(self: *Surface, title: [:0]const u8, body: [:0]const // how fast identical notifications can be sent sequentially. const hash_algorithm = std.hash.Wyhash; - const now = try std.time.Instant.now(); + const now = try internal_os.Instant.now(); // Set a limit of one desktop notification per second so that the OS // doesn't kill us when we run out of resources. diff --git a/src/build/GhosttyWasm.zig b/src/build/GhosttyWasm.zig new file mode 100644 index 000000000..ec0a3e8f4 --- /dev/null +++ b/src/build/GhosttyWasm.zig @@ -0,0 +1,79 @@ +const Ghostty = @This(); + +const std = @import("std"); +const Config = @import("Config.zig"); +const SharedDeps = @import("SharedDeps.zig"); + +/// The primary Ghostty executable. +exe: *std.Build.Step.Compile, + +/// The install step for the executable. +install_step: *std.Build.Step.InstallArtifact, + +pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty { + // Build our Wasm target. + const wasm_crosstarget: std.Target.Query = .{ + .cpu_arch = .wasm32, + .os_tag = .freestanding, + .cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp }, + .cpu_features_add = std.Target.wasm.featureSet(&.{ + // We use this to explicitly request shared memory. + .atomics, + + // Not explicitly used but compiler could use them if they want. + .bulk_memory, + .reference_types, + .sign_ext, + }), + }; + + // Whether we're using wasm shared memory. Some behaviors change. + // For now we require this but I wanted to make the code handle both + // up front. + const wasm_shared: bool = true; + + const wasm = b.addExecutable(.{ + .name = "ghostty-wasm", + .root_source_file = b.path("src/main_wasm.zig"), + .target = b.resolveTargetQuery(wasm_crosstarget), + .optimize = cfg.optimize, + }); + + // So that we can use web workers with our wasm binary + wasm.import_memory = true; + wasm.initial_memory = 65536 * 512; + wasm.entry = .disabled; + // wasm.wasi_exec_model = .reactor; + wasm.rdynamic = true; + wasm.max_memory = 65536 * 65536; // Maximum number of pages in wasm32 + wasm.shared_memory = wasm_shared; + + // Stack protector adds extern requirements that we don't satisfy. + wasm.root_module.stack_protector = false; + + // Add the shared dependencies + _ = try deps.addWasm(wasm); + + // Install + const wasm_install = b.addInstallArtifact(wasm, .{}); + const install = b.addInstallFile(wasm.getEmittedBin(), "../example/ghostty-wasm.wasm"); + wasm_install.step.dependOn(&install.step); + + const step = b.step("wasm", "Build the wasm library"); + step.dependOn(&install.step); + + const test_step = b.step("test-wasm", "Run all tests for wasm"); + const main_test = b.addTest(.{ + .name = "wasm-test", + .root_source_file = b.path("src/main_wasm.zig"), + .target = b.resolveTargetQuery(wasm_crosstarget), + }); + + _ = try deps.addWasm(main_test); + test_step.dependOn(&main_test.step); + + return .{ + .exe = wasm, + .install_step = wasm_install, + }; +} diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index 7d0b64c5b..e2f2c533b 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -84,6 +84,44 @@ fn initTarget( try self.config.addOptions(self.options); } +pub fn addWasm( + self: *const SharedDeps, + step: *std.Build.Step.Compile, +) !LazyPathList { + const b = step.step.owner; + + // We could use our config.target/optimize fields here but its more + // correct to always match our step. + const target = step.root_module.resolved_target.?; + const optimize = step.root_module.optimize.?; + + // We maintain a list of our static libraries and return it so that + // we can build a single fat static library for the final app. + var static_libs = LazyPathList.init(b.allocator); + errdefer static_libs.deinit(); + + // Every exe gets build options populated + step.root_module.addOptions("build_options", self.options); + + const js_dep = b.dependency("zig_js", .{ + .target = target, + .optimize = optimize, + }); + step.root_module.addImport("zig-js", js_dep.module("zig-js")); + step.root_module.addImport("z2d", b.addModule("z2d", .{ + .root_source_file = b.dependency("z2d", .{}).path("src/z2d.zig"), + .target = target, + .optimize = optimize, + })); + step.root_module.addImport("ziglyph", b.dependency("ziglyph", .{ + .target = target, + .optimize = optimize, + }).module("ziglyph")); + + self.unicode_tables.addImport(step); + return static_libs; +} + pub fn add( self: *const SharedDeps, step: *std.Build.Step.Compile, @@ -261,17 +299,6 @@ pub fn add( try static_libs.append(breakpad_dep.artifact("breakpad").getEmittedBin()); } - // Wasm we do manually since it is such a different build. - if (step.rootModuleTarget().cpu.arch == .wasm32) { - const js_dep = b.dependency("zig_js", .{ - .target = target, - .optimize = optimize, - }); - step.root_module.addImport("zig-js", js_dep.module("zig-js")); - - return static_libs; - } - // On Linux, we need to add a couple common library paths that aren't // on the standard search list. i.e. GTK is often in /usr/lib/x86_64-linux-gnu // on x86_64. diff --git a/src/build/main.zig b/src/build/main.zig index a0e67543f..fbe7cda28 100644 --- a/src/build/main.zig +++ b/src/build/main.zig @@ -10,6 +10,7 @@ pub const GitVersion = @import("GitVersion.zig"); pub const GhosttyBench = @import("GhosttyBench.zig"); pub const GhosttyDocs = @import("GhosttyDocs.zig"); pub const GhosttyExe = @import("GhosttyExe.zig"); +pub const GhosttyWasm = @import("GhosttyWasm.zig"); pub const GhosttyFrameData = @import("GhosttyFrameData.zig"); pub const GhosttyLib = @import("GhosttyLib.zig"); pub const GhosttyResources = @import("GhosttyResources.zig"); diff --git a/src/build_config.zig b/src/build_config.zig index b80247aab..8f7b828e6 100644 --- a/src/build_config.zig +++ b/src/build_config.zig @@ -81,8 +81,8 @@ pub const Artifact = enum { pub fn detect() Artifact { if (builtin.target.isWasm()) { - assert(builtin.output_mode == .Obj); - assert(builtin.link_mode == .Static); + // assert(builtin.output_mode == .Obj); + // assert(builtin.link_mode == .Static); return .wasm_module; } diff --git a/src/cli/args.zig b/src/cli/args.zig index 7385e6a3e..7b4d40028 100644 --- a/src/cli/args.zig +++ b/src/cli/args.zig @@ -1335,7 +1335,7 @@ pub fn LineIterator(comptime ReaderType: type) type { } // Constructs a LineIterator (see docs for that). -fn lineIterator(reader: anytype) LineIterator(@TypeOf(reader)) { +pub fn lineIterator(reader: anytype) LineIterator(@TypeOf(reader)) { return .{ .r = reader }; } diff --git a/src/config/Config.zig b/src/config/Config.zig index d191de53a..e9554dbd6 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -3475,22 +3475,24 @@ fn loadTheme(self: *Config, theme: Theme) !void { pub fn finalize(self: *Config) !void { // We always load the theme first because it may set other fields // in our config. - if (self.theme) |theme| { - const different = !std.mem.eql(u8, theme.light, theme.dark); + if (builtin.cpu.arch != .wasm32) { + if (self.theme) |theme| { + const different = !std.mem.eql(u8, theme.light, theme.dark); - // Warning: loadTheme will deinit our existing config and replace - // it so all memory from self prior to this point will be freed. - try self.loadTheme(theme); + // Warning: loadTheme will deinit our existing config and replace + // it so all memory from self prior to this point will be freed. + try self.loadTheme(theme); - // If we have different light vs dark mode themes, disable - // window-theme = auto since that breaks it. - if (different) { - // This setting doesn't make sense with different light/dark themes - // because it'll force the theme based on the Ghostty theme. - if (self.@"window-theme" == .auto) self.@"window-theme" = .system; + // If we have different light vs dark mode themes, disable + // window-theme = auto since that breaks it. + if (different) { + // This setting doesn't make sense with different light/dark themes + // because it'll force the theme based on the Ghostty theme. + if (self.@"window-theme" == .auto) self.@"window-theme" = .system; - // Mark that we use a conditional theme - self._conditional_set.insert(.theme); + // Mark that we use a conditional theme + self._conditional_set.insert(.theme); + } } } diff --git a/src/config/theme.zig b/src/config/theme.zig index 2d206e1f6..53b179274 100644 --- a/src/config/theme.zig +++ b/src/config/theme.zig @@ -39,7 +39,7 @@ pub const Location = enum { // error set since some platforms don't support some // error types. const Error = @TypeOf(err) || switch (builtin.os.tag) { - .ios => error{BufferTooSmall}, + .ios, .wasi => error{BufferTooSmall}, else => error{}, }; diff --git a/src/font/Collection.zig b/src/font/Collection.zig index cb16528aa..7d4d2b2aa 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -687,6 +687,27 @@ pub const Index = packed struct(Index.Backing) { } }; +/// The wasm-compatible API. +pub const Wasm = struct { + const wasm = @import("../os/wasm.zig"); + const alloc = wasm.alloc; + export fn collection_new(points: f32) ?*Collection { + const result = alloc.create(Collection) catch |err| { + log.warn("error creating collection err={}", .{err}); + return null; + }; + result.* = Collection.init(); + result.load_options = .{ .library = Library.init() catch unreachable, .size = .{ .points = points } }; + return result; + } + export fn collection_add_deferred_face(self: *Collection, style: u8, face: *DeferredFace) u16 { + return @bitCast(self.add(alloc, @enumFromInt(style), .{ .deferred = face.* }) catch |err| { + log.warn("error adding deferred face to collection err={}", .{err}); + return 0; + }); + } +}; + test init { const testing = std.testing; const alloc = testing.allocator; diff --git a/src/font/DeferredFace.zig b/src/font/DeferredFace.zig index 3ee104386..f47904e95 100644 --- a/src/font/DeferredFace.zig +++ b/src/font/DeferredFace.zig @@ -254,7 +254,7 @@ fn loadWebCanvas( opts: font.face.Options, ) !Face { const wc = self.wc.?; - return try Face.initNamed(wc.alloc, wc.font_str, opts, wc.presentation); + return try Face.initNamed(wc.alloc, wc.font_str, opts.size, wc.presentation); } /// Returns true if this face can satisfy the given codepoint and @@ -392,7 +392,7 @@ pub const Wasm = struct { } export fn deferred_face_load(self: *DeferredFace, pts: f32) void { - self.load(.{}, .{ .points = pts }) catch |err| { + _ = self.load(.{}, .{ .size = .{ .points = pts } }) catch |err| { log.warn("error loading deferred face err={}", .{err}); return; }; diff --git a/src/font/SharedGrid.zig b/src/font/SharedGrid.zig index 65c7ecd87..4ee965abb 100644 --- a/src/font/SharedGrid.zig +++ b/src/font/SharedGrid.zig @@ -311,6 +311,54 @@ const GlyphKey = struct { const TestMode = enum { normal }; +/// The wasm-compatible API. +pub const Wasm = struct { + const wasm = @import("../os/wasm.zig"); + const alloc = wasm.alloc; + export fn shared_grid_new(c: *Collection) ?*SharedGrid { + const result = alloc.create(SharedGrid) catch |err| { + log.warn("error creating SharedGrid err={}", .{err}); + return null; + }; + result.* = SharedGrid.init(wasm.alloc, .{ .collection = c.* }) catch |err| { + log.warn("error initializing SharedGrid err={}", .{err}); + return null; + }; + return result; + } + export fn shared_grid_atlas_grayscale(self: *SharedGrid) ?*Atlas { + return &self.atlas_grayscale; + } + export fn shared_grid_atlas_color(self: *SharedGrid) ?*Atlas { + return &self.atlas_color; + } + export fn shared_grid_index_for_codepoint(self: *SharedGrid, code: u32, style: u8, presentation: i8) ?*Collection.Index { + const get = self.getIndex(wasm.alloc, code, @enumFromInt(style), if (presentation < 0) null else @enumFromInt(presentation)) catch |err| { + log.warn("error getting SharedGrid index for codepoint err={}", .{err}); + return null; + }; + if (get) |thing| { + const index = wasm.alloc.create(Collection.Index) catch unreachable; + index.* = thing; + return index; + } + return null; + } + + export fn shared_grid_render_glyph(self: *SharedGrid, font_idx: *Collection.Index, code: u32, _: u8) void { + const glyph_index = glyph_index: { + if (font_idx.special()) |special| break :glyph_index switch (special) { + .sprite => code, + }; + self.lock.lockShared(); + defer self.lock.unlockShared(); + const face = self.resolver.collection.getFace(font_idx.*) catch unreachable; + break :glyph_index face.glyphIndex(code) orelse return; + }; + _ = self.renderGlyph(wasm.alloc, font_idx.*, glyph_index, .{ .grid_metrics = self.metrics }) catch unreachable; + } +}; + fn testGrid(mode: TestMode, alloc: Allocator, lib: Library) !SharedGrid { const testFont = font.embedded.regular; diff --git a/src/font/face/web_canvas.zig b/src/font/face/web_canvas.zig index 30540191d..6882768e3 100644 --- a/src/font/face/web_canvas.zig +++ b/src/font/face/web_canvas.zig @@ -26,9 +26,6 @@ pub const Face = struct { /// The presentation for this font. presentation: font.Presentation, - /// Metrics for this font face. These are useful for renderers. - metrics: font.Metrics, - /// The canvas element that we will reuse to render glyphs canvas: js.Object, @@ -58,24 +55,20 @@ pub const Face = struct { const font_str = try alloc.dupe(u8, raw); errdefer alloc.free(font_str); - // Create our canvas that we're going to continue to reuse. - const doc = try js.global.get(js.Object, "document"); - defer doc.deinit(); - const canvas = try doc.call(js.Object, "createElement", .{js.string("canvas")}); + // Create our canvasxx that we're going to continue to reuse. + const OffscreenCanvas = try js.global.get(js.Object, "OffscreenCanvas"); + defer OffscreenCanvas.deinit(); + const canvas = try OffscreenCanvas.new(.{ 0, 0 }); errdefer canvas.deinit(); - var result = Face{ + const result = Face{ .alloc = alloc, .font_str = font_str, .size = size, .presentation = presentation, .canvas = canvas, - - // We're going to calculate these right after initialization. - .metrics = undefined, }; - try result.calcMetrics(); log.debug("face initialized: {s}", .{raw}); return result; @@ -192,8 +185,6 @@ pub const Face = struct { glyph_index: u32, opts: font.face.RenderOptions, ) !font.Glyph { - _ = opts; - var render = try self.renderGlyphInternal(alloc, glyph_index); defer render.deinit(); @@ -227,34 +218,41 @@ pub const Face = struct { atlas.set(region, bitmap_formatted); } - return font.Glyph{ + const glyph = font.Glyph{ .width = render.width, .height = render.height, - // TODO: this can't be right - .offset_x = 0, - .offset_y = 0, + .offset_x = render.x_offset, + .offset_y = render.y_offset + @as(i32, @intCast(opts.grid_metrics.cell_height)), .atlas_x = region.x, .atlas_y = region.y, .advance_x = 0, }; + std.log.err("glyph: {}", .{glyph}); + return glyph; } + pub const GetMetricsError = error{ + OutOfMemory, + InvalidType, + }; + /// Calculate the metrics associated with a given face. - fn calcMetrics(self: *Face) !void { + pub fn getMetrics(self: *Face) GetMetricsError!font.Metrics.FaceMetrics { const ctx = try self.context(); defer ctx.deinit(); + const x_metric = try ctx.call(js.Object, "measureText", .{js.string("x")}); + defer x_metric.deinit(); + const M_metric = try ctx.call(js.Object, "measureText", .{js.string("M")}); + defer M_metric.deinit(); // Cell width is the width of our M text const cell_width: f32 = cell_width: { - const metrics = try ctx.call(js.Object, "measureText", .{js.string("M")}); - defer metrics.deinit(); - // We prefer the bounding box since it is tighter but certain // text such as emoji do not have a bounding box set so we use // the full run width instead. - const bounding_right = try metrics.get(f32, "actualBoundingBoxRight"); + const bounding_right = try M_metric.get(f32, "actualBoundingBoxRight"); if (bounding_right > 0) break :cell_width bounding_right; - break :cell_width try metrics.get(f32, "width"); + break :cell_width try M_metric.get(f32, "width"); }; // To get the cell height we render a high and low character and get @@ -262,29 +260,15 @@ pub const Face = struct { // pixel height but this is a more surefire way to get it. const height_metrics = try ctx.call(js.Object, "measureText", .{js.string("M_")}); defer height_metrics.deinit(); - const asc = try height_metrics.get(f32, "actualBoundingBoxAscent"); - const desc = try height_metrics.get(f32, "actualBoundingBoxDescent"); - const cell_height = asc + desc; - const cell_baseline = desc; + const asc = try height_metrics.get(f32, "fontBoundingBoxAscent"); + const desc = try height_metrics.get(f32, "fontBoundingBoxDescent"); - // There isn't a declared underline position for canvas measurements - // so we just go 1 under the cell height to match freetype logic - // at this time (our freetype logic). - const underline_position = cell_height - 1; - const underline_thickness: f32 = 1; - - const result = font.Metrics{ - .cell_width = @intFromFloat(cell_width), - .cell_height = @intFromFloat(cell_height), - .cell_baseline = @intFromFloat(cell_baseline), - .underline_position = @intFromFloat(underline_position), - .underline_thickness = @intFromFloat(underline_thickness), - .strikethrough_position = @intFromFloat(underline_position), - .strikethrough_thickness = @intFromFloat(underline_thickness), + return .{ + .ascent = asc, + .descent = -desc, + .cell_width = cell_width, + .line_gap = 0, }; - - self.metrics = result; - log.debug("metrics font={s} value={}", .{ self.font_str, self.metrics }); } /// Returns the 2d context configured for drawing @@ -325,6 +309,27 @@ pub const Face = struct { return ctx; } + pub fn hasColor(_: *const Face) bool { + return true; + } + + pub fn isColorGlyph(self: *const Face, cp: u32) bool { + // Render the glyph + var render = self.renderGlyphInternal(self.alloc, cp) catch unreachable; + defer render.deinit(); + + // Inspect the image data for any non-zeros in the RGB value. + // NOTE(perf): this is an easy candidate for SIMD. + var i: usize = 0; + while (i < render.bitmap.len) : (i += 4) { + if (render.bitmap[i] > 0 or + render.bitmap[i + 1] > 0 or + render.bitmap[i + 2] > 0) return true; + } + + return false; + } + /// An internal (web-canvas-only) format for rendered glyphs /// since we do render passes in multiple different situations. const RenderedGlyph = struct { @@ -332,6 +337,8 @@ pub const Face = struct { metrics: js.Object, width: u32, height: u32, + y_offset: i32, + x_offset: i32, bitmap: []u8, pub fn deinit(self: *RenderedGlyph) void { @@ -392,6 +399,15 @@ pub const Face = struct { // Height is our ascender + descender for this char const height = if (!broken_bbox) @as(u32, @intFromFloat(@ceil(asc + desc))) + 1 else width; + const ctx_temp = try self.context(); + try ctx_temp.set("textBaseline", js.string("top")); + // Get the width and height of the render + const metrics_2 = try measure_ctx.call(js.Object, "measureText", .{glyph_str}); + const top_desc = try metrics_2.get(f32, "actualBoundingBoxDescent") + 1; + const y_offset = @as(i32, @intCast(height)) - @as(i32, @intFromFloat(top_desc)); + const x_offset: i32 = @intFromFloat(-left); + ctx_temp.deinit(); + // Note: width and height both get "+ 1" added to them above. This // is important so that there is a 1px border around the glyph to avoid // any clipping in the atlas. @@ -401,15 +417,15 @@ pub const Face = struct { try self.canvas.set("width", width); try self.canvas.set("height", height); - const width_str = try std.fmt.allocPrint(alloc, "{d}px", .{width}); - defer alloc.free(width_str); - const height_str = try std.fmt.allocPrint(alloc, "{d}px", .{height}); - defer alloc.free(height_str); + // const width_str = try std.fmt.allocPrint(alloc, "{d}px", .{width}); + // defer alloc.free(width_str); + // const height_str = try std.fmt.allocPrint(alloc, "{d}px", .{height}); + // defer alloc.free(height_str); - const style = try self.canvas.get(js.Object, "style"); - defer style.deinit(); - try style.set("width", js.string(width_str)); - try style.set("height", js.string(height_str)); + // const style = try self.canvas.get(js.Object, "style"); + // defer style.deinit(); + // try style.set("width", js.string(width_str)); + // try style.set("height", js.string(height_str)); } // Reload our context since we resized the canvas @@ -484,6 +500,8 @@ pub const Face = struct { .width = width, .height = height, .bitmap = bitmap, + .y_offset = y_offset, + .x_offset = x_offset, }; } }; @@ -494,7 +512,7 @@ pub const Wasm = struct { const alloc = wasm.alloc; export fn face_new(ptr: [*]const u8, len: usize, pts: u16, p: u16) ?*Face { - return face_new_(ptr, len, pts, p) catch null; + return face_new_(ptr, len, @floatFromInt(pts), p) catch null; } fn face_new_(ptr: [*]const u8, len: usize, pts: f32, presentation: u16) !*Face { @@ -552,7 +570,7 @@ pub const Wasm = struct { } fn face_render_glyph_(face: *Face, atlas: *font.Atlas, codepoint: u32) !*font.Glyph { - const glyph = try face.renderGlyph(alloc, atlas, codepoint, .{}); + const glyph = try face.renderGlyph(alloc, atlas, codepoint, .{ .grid_metrics = font.Metrics.calc(try face.getMetrics()) }); const result = try alloc.create(font.Glyph); errdefer alloc.destroy(result); diff --git a/src/font/main.zig b/src/font/main.zig index ffeb42f7a..ead14460b 100644 --- a/src/font/main.zig +++ b/src/font/main.zig @@ -33,6 +33,8 @@ comptime { if (builtin.target.isWasm()) { _ = Atlas.Wasm; _ = DeferredFace.Wasm; + _ = SharedGrid.Wasm; + _ = @import("Collection.zig").Wasm; _ = face.web_canvas.Wasm; _ = shape.web_canvas.Wasm; } diff --git a/src/font/shaper/web_canvas.zig b/src/font/shaper/web_canvas.zig index f38ab885a..0864ce048 100644 --- a/src/font/shaper/web_canvas.zig +++ b/src/font/shaper/web_canvas.zig @@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator; const ziglyph = @import("ziglyph"); const font = @import("../main.zig"); const terminal = @import("../../terminal/main.zig"); +const SharedGrid = font.SharedGrid; const log = std.log.scoped(.font_shaper); @@ -30,19 +31,19 @@ pub const Shaper = struct { alloc: Allocator, /// The shared memory used for shaping results. - cell_buf: []font.shape.Cell, + cell_buf: std.ArrayListUnmanaged(font.shape.Cell), /// The shared memory used for storing information about a run. run_buf: RunBuf, /// The cell_buf argument is the buffer to use for storing shaped results. /// This should be at least the number of columns in the terminal. - pub fn init(alloc: Allocator, opts: font.shape.Options) !Shaper { + pub fn init(alloc: Allocator, _: font.shape.Options) !Shaper { // Note: we do not support opts.font_features return Shaper{ .alloc = alloc, - .cell_buf = opts.cell_buf, + .cell_buf = .{}, .run_buf = .{}, }; } @@ -61,14 +62,16 @@ pub const Shaper = struct { /// for a Shaper struct since they share state. pub fn runIterator( self: *Shaper, - group: *font.GroupCache, - row: terminal.Screen.Row, + grid: *SharedGrid, + screen: *const terminal.Screen, + row: terminal.Pin, selection: ?terminal.Selection, cursor_x: ?usize, ) font.shape.RunIterator { return .{ .hooks = .{ .shaper = self }, - .group = group, + .grid = grid, + .screen = screen, .row = row, .selection = selection, .cursor_x = cursor_x, @@ -90,21 +93,22 @@ pub const Shaper = struct { const clusters = self.run_buf.items(.cluster); assert(codepoints.len == clusters.len); + self.cell_buf.clearRetainingCapacity(); switch (codepoints.len) { // Special cases: if we have no codepoints (is this possible?) // then our result is also an empty cell run. - 0 => return self.cell_buf[0..0], + 0 => return self.cell_buf.items[0..0], // If we have only 1 codepoint, then we assume that it is // a single grapheme and just let it through. At this point, // we can't have any more information to do anything else. 1 => { - self.cell_buf[0] = .{ + try self.cell_buf.append(self.alloc, .{ .x = @intCast(clusters[0]), .glyph_index = codepoints[0], - }; + }); - return self.cell_buf[0..1]; + return self.cell_buf.items[0..1]; }, else => {}, @@ -151,10 +155,10 @@ pub const Shaper = struct { switch (len) { // If we have only a single codepoint then just render it // as-is. - 1 => self.cell_buf[cur] = .{ + 1 => try self.cell_buf.append(self.alloc, .{ .x = @intCast(clusters[start]), .glyph_index = codepoints[start], - }, + }), // We must have multiple codepoints (see assert above). In // this case we UTF-8 encode the codepoints and send them @@ -190,13 +194,13 @@ pub const Shaper = struct { }; defer self.alloc.free(cluster); - var face = try run.group.group.faceFromIndex(run.font_index); + var face = try run.grid.resolver.collection.getFace(run.font_index); const index = try face.graphemeGlyphIndex(cluster); - self.cell_buf[cur] = .{ + try self.cell_buf.append(self.alloc, .{ .x = @intCast(clusters[start]), .glyph_index = index, - }; + }); }, } @@ -204,7 +208,7 @@ pub const Shaper = struct { cur += 1; } - return self.cell_buf[0..cur]; + return self.cell_buf.items[0..cur]; } /// The hooks for RunIterator. @@ -238,15 +242,12 @@ pub const Wasm = struct { const wasm = @import("../../os/wasm.zig"); const alloc = wasm.alloc; - export fn shaper_new(cap: usize) ?*Shaper { - return shaper_new_(cap) catch null; + export fn shaper_new() ?*Shaper { + return shaper_new_() catch null; } - fn shaper_new_(cap: usize) !*Shaper { - const cell_buf = try alloc.alloc(font.shape.Cell, cap); - errdefer alloc.free(cell_buf); - - var shaper = try Shaper.init(alloc, .{ .cell_buf = cell_buf }); + fn shaper_new_() !*Shaper { + var shaper = try Shaper.init(alloc, .{}); errdefer shaper.deinit(); const result = try alloc.create(Shaper); @@ -257,7 +258,6 @@ pub const Wasm = struct { export fn shaper_free(ptr: ?*Shaper) void { if (ptr) |v| { - alloc.free(v.cell_buf); v.deinit(); alloc.destroy(v); } @@ -266,45 +266,130 @@ pub const Wasm = struct { /// Runs a test to verify shaping works properly. export fn shaper_test( self: *Shaper, - group: *font.GroupCache, + grid: *SharedGrid, str: [*]const u8, len: usize, ) void { - shaper_test_(self, group, str[0..len]) catch |err| { + shaper_test_(self, grid, str[0..len]) catch |err| { log.warn("error during shaper test err={}", .{err}); }; } + const js = @import("zig-js"); - fn shaper_test_(self: *Shaper, group: *font.GroupCache, str: []const u8) !void { - // Create a terminal and print all our characters into it. - var term = try terminal.Terminal.init(alloc, self.cell_buf.len, 80); + fn createImageData(self: *font.Atlas) !js.Object { + // We need to draw pixels so this is format dependent. + const buf: []u8 = switch (self.format) { + // RGBA is the native ImageData format + .rgba => self.data, + + .grayscale => buf: { + // Convert from A8 to RGBA so every 4th byte is set to a value. + var buf: []u8 = try alloc.alloc(u8, self.data.len * 4); + errdefer alloc.free(buf); + @memset(buf, 0); + for (self.data, 0..) |value, i| { + buf[(i * 4) + 3] = value; + } + break :buf buf; + }, + + else => return error.UnsupportedAtlasFormat, + }; + defer if (buf.ptr != self.data.ptr) alloc.free(buf); + + // Create an ImageData from our buffer and then write it to the canvas + const image_data: js.Object = data: { + // Get our runtime memory + const mem = try js.runtime.get(js.Object, "memory"); + defer mem.deinit(); + const mem_buf = try mem.get(js.Object, "buffer"); + defer mem_buf.deinit(); + + // Create an array that points to our buffer + const arr = arr: { + const Uint8ClampedArray = try js.global.get(js.Object, "Uint8ClampedArray"); + defer Uint8ClampedArray.deinit(); + const arr = try Uint8ClampedArray.new(.{ mem_buf, buf.ptr, buf.len }); + if (!wasm.shared_mem) break :arr arr; + + // If we're sharing memory then we have to copy the data since + // we can't set ImageData directly using a SharedArrayBuffer. + defer arr.deinit(); + break :arr try arr.call(js.Object, "slice", .{}); + }; + defer arr.deinit(); + + // Create the image data from our array + const ImageData = try js.global.get(js.Object, "ImageData"); + defer ImageData.deinit(); + const data = try ImageData.new(.{ arr, self.size, self.size }); + errdefer data.deinit(); + + break :data data; + }; + + return image_data; + } + + fn shaper_test_(self: *Shaper, grid: *SharedGrid, str: []const u8) !void { + // Make a screen with some data + var term = try terminal.Terminal.init(alloc, .{ .cols = 20, .rows = 5 }); defer term.deinit(alloc); + try term.printString(str); - // Iterate over unicode codepoints and add to terminal - { - const view = try std.unicode.Utf8View.init(str); - var iter = view.iterator(); - while (iter.nextCodepoint()) |c| { - try term.print(c); + // Get our run iterator + + var row_it = term.screen.pages.rowIterator(.right_down, .{ .viewport = .{} }, null); + var y: usize = 0; + const Render = struct { + render: SharedGrid.Render, + y: usize, + x: usize, + }; + var cell_list: std.ArrayListUnmanaged(Render) = .{}; + defer cell_list.deinit(wasm.alloc); + while (row_it.next()) |row| { + defer y += 1; + var it = self.runIterator(grid, &term.screen, row, null, null); + while (try it.next(alloc)) |run| { + const cells = try self.shape(run); + for (cells) |cell| { + const render = try grid.renderGlyph(wasm.alloc, run.font_index, cell.glyph_index, .{ .grid_metrics = grid.metrics }); + try cell_list.append(wasm.alloc, .{ .render = render, .x = cell.x, .y = y }); + + log.info("y={} x={} width={} height={} ax={} ay={} base={}", .{ + y * grid.metrics.cell_height, + cell.x * grid.metrics.cell_width, + render.glyph.width, + render.glyph.height, + render.glyph.atlas_x, + render.glyph.atlas_y, + grid.metrics.cell_baseline, + }); + } } } + const colour_data = try createImageData(&grid.atlas_color); + const gray_data = try createImageData(&grid.atlas_grayscale); - // Iterate over the rows and print out all the runs we get. - var rowIter = term.screen.rowIterator(.viewport); - var y: usize = 0; - while (rowIter.next()) |row| { - defer y += 1; - - var iter = self.runIterator(group, row, null, null); - while (try iter.next(alloc)) |run| { - const cells = try self.shape(run); - log.info("y={} run={d} shape={any} idx={}", .{ - y, - run.cells, - cells, - run.font_index, - }); - } + const doc = try js.global.get(js.Object, "document"); + defer doc.deinit(); + const canvas = try doc.call(js.Object, "getElementById", .{js.string("shaper-canvas")}); + errdefer canvas.deinit(); + const ctx = try canvas.call(js.Object, "getContext", .{js.string("2d")}); + defer ctx.deinit(); + for (cell_list.items) |cell| { + const x_start = -@as(isize, @intCast(cell.render.glyph.atlas_x)); + const y_start = -@as(isize, @intCast(cell.render.glyph.atlas_y)); + try ctx.call(void, "putImageData", .{ + if (cell.render.presentation == .emoji) colour_data else gray_data, + x_start + @as(isize, @intCast(cell.x * grid.metrics.cell_width)) + cell.render.glyph.offset_x, + y_start + @as(isize, @intCast((cell.y + 1) * grid.metrics.cell_height)) - cell.render.glyph.offset_y, + cell.render.glyph.atlas_x, + cell.render.glyph.atlas_y, + cell.render.glyph.width, + cell.render.glyph.height, + }); } } }; diff --git a/src/main_wasm.zig b/src/main_wasm.zig index bffe5e4b7..d4b7d63e5 100644 --- a/src/main_wasm.zig +++ b/src/main_wasm.zig @@ -11,16 +11,17 @@ comptime { _ = @import("App.zig").Wasm; } -pub const std_options = struct { +pub const std_options: std.Options = .{ // Set our log level. We try to get as much logging as possible but in // ReleaseSmall mode where we're optimizing for space, we elevate the // log level. - pub const log_level: std.log.Level = switch (builtin.mode) { - .Debug => .debug, - .ReleaseSmall => .warn, - else => .info, - }; + // .log_level = switch (builtin.mode) { + // .Debug => .debug, + // .ReleaseSmall => .warn, + // else => .info, + // }, + .log_level = .info, // Set our log function - pub const logFn = @import("os/wasm/log.zig").log; + .logFn = @import("os/wasm/log.zig").log, }; diff --git a/src/os/desktop.zig b/src/os/desktop.zig index c73f150e0..465d4e751 100644 --- a/src/os/desktop.zig +++ b/src/os/desktop.zig @@ -56,6 +56,8 @@ pub fn launchedFromDesktop() bool { // iPhone/iPad is always launched from the "desktop" .ios => true, + .freestanding => false, + else => @compileError("unsupported platform"), }; } diff --git a/src/os/homedir.zig b/src/os/homedir.zig index b5629fd65..e2733e6ce 100644 --- a/src/os/homedir.zig +++ b/src/os/homedir.zig @@ -20,6 +20,8 @@ pub inline fn home(buf: []u8) !?[]const u8 { // iOS doesn't have a user-writable home directory .ios => null, + .wasi => null, + else => @compileError("unimplemented"), }; } diff --git a/src/os/main.zig b/src/os/main.zig index cb9355931..1d7aaa575 100644 --- a/src/os/main.zig +++ b/src/os/main.zig @@ -12,6 +12,8 @@ const mouse = @import("mouse.zig"); const openpkg = @import("open.zig"); const pipepkg = @import("pipe.zig"); const resourcesdir = @import("resourcesdir.zig"); +const builtin = @import("builtin"); +const std = @import("std"); // Namespaces pub const args = @import("args.zig"); @@ -51,3 +53,8 @@ pub const OpenType = openpkg.Type; pub const pipe = pipepkg.pipe; pub const resourcesDir = resourcesdir.resourcesDir; pub const ShellEscapeWriter = shell.ShellEscapeWriter; +pub const Instant = if (builtin.cpu.arch != .wasm32) std.time.Instant else struct { + fn now() !@This() { + return .{}; + } +}; diff --git a/src/terminal/kitty/graphics_image.zig b/src/terminal/kitty/graphics_image.zig index 094e1622b..ae207f9e7 100644 --- a/src/terminal/kitty/graphics_image.zig +++ b/src/terminal/kitty/graphics_image.zig @@ -349,7 +349,7 @@ pub const LoadingImage = struct { } // Set our time - self.image.transmit_time = std.time.Instant.now() catch |err| { + self.image.transmit_time = internal_os.Instant.now() catch |err| { log.warn("failed to get time: {}", .{err}); return error.InternalError; }; @@ -456,7 +456,7 @@ pub const Image = struct { format: command.Transmission.Format = .rgb, compression: command.Transmission.Compression = .none, data: []const u8 = "", - transmit_time: std.time.Instant = undefined, + transmit_time: internal_os.Instant = undefined, /// Set this to true if this image was loaded by a command that /// doesn't specify an ID or number, since such commands should diff --git a/src/terminal/kitty/graphics_storage.zig b/src/terminal/kitty/graphics_storage.zig index ffd3aa580..97a2839f0 100644 --- a/src/terminal/kitty/graphics_storage.zig +++ b/src/terminal/kitty/graphics_storage.zig @@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; const terminal = @import("../main.zig"); +const internal_os = @import("../../os/main.zig"); const point = @import("../point.zig"); const size = @import("../size.zig"); const command = @import("graphics_command.zig"); @@ -498,7 +499,7 @@ pub const ImageStorage = struct { // bit is fine compared to the megabytes we're looking to save. const Candidate = struct { id: u32, - time: std.time.Instant, + time: internal_os.Instant, used: bool, }; diff --git a/src/terminal/page.zig b/src/terminal/page.zig index 30f6658aa..5b225cf64 100644 --- a/src/terminal/page.zig +++ b/src/terminal/page.zig @@ -320,7 +320,7 @@ pub const Page = struct { /// when runtime safety is enabled. This is a no-op when runtime /// safety is disabled. This uses the libc allocator. pub fn assertIntegrity(self: *const Page) void { - if (comptime build_config.slow_runtime_safety) { + if (comptime build_config.slow_runtime_safety and builtin.cpu.arch != .wasm32) { self.verifyIntegrity(std.heap.c_allocator) catch |err| { log.err("page integrity violation, crashing. err={}", .{err}); @panic("page integrity violation"); diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 864f2e21c..934e3edfd 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -102,7 +102,7 @@ pub fn threadEnter( }; // Track our process start time for abnormal exits - const process_start = try std.time.Instant.now(); + const process_start = try internal_os.Instant.now(); // Create our pipe that we'll use to kill our read thread. // pipe[0] is the read end, pipe[1] is the write end. @@ -354,7 +354,7 @@ fn processExit( // Determine how long the process was running for. const runtime_ms: ?u64 = runtime: { - const process_end = std.time.Instant.now() catch break :runtime null; + const process_end = internal_os.Instant.now() catch break :runtime null; const runtime_ns = process_end.since(execdata.start); const runtime_ms = runtime_ns / std.time.ns_per_ms; break :runtime runtime_ms; @@ -612,7 +612,7 @@ pub const ThreadData = struct { const WRITE_REQ_PREALLOC = std.math.pow(usize, 2, 5); /// Process start time and boolean of whether its already exited. - start: std.time.Instant, + start: internal_os.Instant, exited: bool = false, /// The number of milliseconds below which we consider a process diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig index ab61ae4ca..ec8d20e0f 100644 --- a/src/termio/Termio.zig +++ b/src/termio/Termio.zig @@ -68,7 +68,7 @@ terminal_stream: terminal.Stream(StreamHandler), /// Last time the cursor was reset. This is used to prevent message /// flooding with cursor resets. -last_cursor_reset: ?std.time.Instant = null, +last_cursor_reset: ?internal_os.Instant = null, /// The configuration for this IO that is derived from the main /// configuration. This must be exported so that we don't need to @@ -572,7 +572,7 @@ fn processOutputLocked(self: *Termio, buf: []const u8) void { // non-blink state so it is rendered if visible. If we're under // HEAVY read load, we don't want to send a ton of these so we // use a timer under the covers - if (std.time.Instant.now()) |now| cursor_reset: { + if (internal_os.Instant.now()) |now| cursor_reset: { if (self.last_cursor_reset) |last| { if (now.since(last) <= (500 * std.time.ns_per_ms)) { break :cursor_reset;