@vibecook/ghosttea-react 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/THIRD_PARTY_NOTICES.md +62 -0
- package/dist/appearance/AdvancedConfigSettings.d.ts +12 -0
- package/dist/appearance/AdvancedConfigSettings.d.ts.map +1 -0
- package/dist/appearance/AdvancedConfigSettings.js +362 -0
- package/dist/appearance/AdvancedConfigSettings.js.map +1 -0
- package/dist/appearance/AppearanceSettings.d.ts +21 -0
- package/dist/appearance/AppearanceSettings.d.ts.map +1 -0
- package/dist/appearance/AppearanceSettings.js +175 -0
- package/dist/appearance/AppearanceSettings.js.map +1 -0
- package/dist/appearance/catalog.d.ts +39 -0
- package/dist/appearance/catalog.d.ts.map +1 -0
- package/dist/appearance/catalog.js +56 -0
- package/dist/appearance/catalog.js.map +1 -0
- package/dist/appearance/config-draft.d.ts +35 -0
- package/dist/appearance/config-draft.d.ts.map +1 -0
- package/dist/appearance/config-draft.js +289 -0
- package/dist/appearance/config-draft.js.map +1 -0
- package/dist/appearance/shaders.d.ts +14 -0
- package/dist/appearance/shaders.d.ts.map +1 -0
- package/dist/appearance/shaders.js +71 -0
- package/dist/appearance/shaders.js.map +1 -0
- package/dist/appearance/theme-catalog.generated.json +16260 -0
- package/dist/appearance/types.d.ts +61 -0
- package/dist/appearance/types.d.ts.map +1 -0
- package/dist/appearance/types.js +2 -0
- package/dist/appearance/types.js.map +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +23 -4
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/renderers/canvas-renderer.d.ts.map +1 -1
- package/dist/renderers/canvas-renderer.js +9 -1
- package/dist/renderers/canvas-renderer.js.map +1 -1
- package/dist/renderers/shader-effects.d.ts +2 -0
- package/dist/renderers/shader-effects.d.ts.map +1 -0
- package/dist/renderers/shader-effects.js +206 -0
- package/dist/renderers/shader-effects.js.map +1 -0
- package/dist/renderers/types.d.ts +7 -0
- package/dist/renderers/types.d.ts.map +1 -1
- package/dist/renderers/types.js +11 -0
- package/dist/renderers/types.js.map +1 -1
- package/dist/renderers/webgpu-renderer.d.ts +2 -1
- package/dist/renderers/webgpu-renderer.d.ts.map +1 -1
- package/dist/renderers/webgpu-renderer.js +250 -159
- package/dist/renderers/webgpu-renderer.js.map +1 -1
- package/dist/terminal-render.worker.js +479 -165
- package/dist/terminal-render.worker.js.map +3 -3
- package/dist/workspace/Workspace.d.ts +7 -2
- package/dist/workspace/Workspace.d.ts.map +1 -1
- package/dist/workspace/Workspace.js +15 -7
- package/dist/workspace/Workspace.js.map +1 -1
- package/dist/workspace/index.d.ts +3 -0
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/workspace/index.js +2 -0
- package/dist/workspace/index.js.map +1 -1
- package/dist/workspace/themes.d.ts +4 -0
- package/dist/workspace/themes.d.ts.map +1 -1
- package/dist/workspace/themes.js +4 -0
- package/dist/workspace/themes.js.map +1 -1
- package/dist/workspace.css +1122 -5
- package/package.json +6 -5
|
@@ -254,6 +254,13 @@ function emptyRenderMetrics() {
|
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
// src/renderers/types.ts
|
|
257
|
+
var ANIMATED_SHADER_EFFECTS = /* @__PURE__ */ new Set([
|
|
258
|
+
"ghosttea:vhs",
|
|
259
|
+
"ghosttea:sparks-from-fire"
|
|
260
|
+
]);
|
|
261
|
+
function terminalEffectsNeedAnimation(effects) {
|
|
262
|
+
return effects.animate === true && (effects.shaderEffects?.some((effect) => ANIMATED_SHADER_EFFECTS.has(effect)) ?? false);
|
|
263
|
+
}
|
|
257
264
|
function effectiveCursorStyle(view) {
|
|
258
265
|
if (!view.cursor.visible) return null;
|
|
259
266
|
if (!view.focused) return CursorStyle.HollowBlock;
|
|
@@ -270,11 +277,15 @@ var DEFAULT_THEME = {
|
|
|
270
277
|
background: [40 / 255, 44 / 255, 52 / 255, 1],
|
|
271
278
|
foreground: [1, 1, 1, 1],
|
|
272
279
|
cursor: [1, 1, 1, 1],
|
|
280
|
+
cursorText: [40 / 255, 44 / 255, 52 / 255, 1],
|
|
273
281
|
selection: [1, 1, 1, 1],
|
|
274
|
-
selectionForeground: [40 / 255, 44 / 255, 52 / 255, 1]
|
|
282
|
+
selectionForeground: [40 / 255, 44 / 255, 52 / 255, 1],
|
|
283
|
+
backgroundOpacityCells: false
|
|
275
284
|
};
|
|
276
285
|
var DEFAULT_EFFECTS = {
|
|
277
|
-
postProcess: "none"
|
|
286
|
+
postProcess: "none",
|
|
287
|
+
shaderEffects: [],
|
|
288
|
+
animate: false
|
|
278
289
|
};
|
|
279
290
|
var CELL_WIDTH = 7.83;
|
|
280
291
|
var LINE_HEIGHT = 19;
|
|
@@ -299,7 +310,7 @@ var CanvasTerminalRenderer = class {
|
|
|
299
310
|
this.#performanceMeasurementEnabled = enabled;
|
|
300
311
|
}
|
|
301
312
|
mount(id, canvas) {
|
|
302
|
-
const context = canvas.getContext("2d", { alpha:
|
|
313
|
+
const context = canvas.getContext("2d", { alpha: true });
|
|
303
314
|
if (!context) throw new Error("Canvas2D worker backend unavailable");
|
|
304
315
|
this.#surfaces.set(id, { canvas, context, width: 1, height: 1, dpr: 1 });
|
|
305
316
|
}
|
|
@@ -319,6 +330,7 @@ var CanvasTerminalRenderer = class {
|
|
|
319
330
|
const surface2 = this.#surfaces.get(id);
|
|
320
331
|
if (!surface2) return;
|
|
321
332
|
const { context, width, height, dpr } = surface2;
|
|
333
|
+
context.clearRect(0, 0, surface2.canvas.width, surface2.canvas.height);
|
|
322
334
|
context.save();
|
|
323
335
|
context.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
324
336
|
context.fillStyle = css(view.theme.background);
|
|
@@ -379,6 +391,13 @@ var CanvasTerminalRenderer = class {
|
|
|
379
391
|
context.strokeRect(x + 0.5, y + 0.5, CELL_WIDTH - 1, LINE_HEIGHT - 1);
|
|
380
392
|
} else {
|
|
381
393
|
context.fillRect(x, y, CELL_WIDTH, LINE_HEIGHT);
|
|
394
|
+
context.save();
|
|
395
|
+
context.beginPath();
|
|
396
|
+
context.rect(x, y, CELL_WIDTH, LINE_HEIGHT);
|
|
397
|
+
context.clip();
|
|
398
|
+
context.fillStyle = css(view.theme.cursorText ?? view.theme.background);
|
|
399
|
+
context.fillText(view.rows[view.cursor.y] ?? "", ORIGIN_X, ORIGIN_Y + view.cursor.y * LINE_HEIGHT);
|
|
400
|
+
context.restore();
|
|
382
401
|
}
|
|
383
402
|
}
|
|
384
403
|
context.restore();
|
|
@@ -416,10 +435,226 @@ function rowsForDamage(rowCount, damage, sceneValid) {
|
|
|
416
435
|
return { full: false, rows: [...rows].sort((left, right) => left - right) };
|
|
417
436
|
}
|
|
418
437
|
|
|
438
|
+
// src/renderers/shader-effects.ts
|
|
439
|
+
var SHADER_EFFECT_WGSL = (
|
|
440
|
+
/* wgsl */
|
|
441
|
+
`
|
|
442
|
+
@group(0) @binding(0) var terminal_image: texture_2d<f32>;
|
|
443
|
+
@group(0) @binding(1) var terminal_sampler: sampler;
|
|
444
|
+
|
|
445
|
+
struct EffectConfig {
|
|
446
|
+
mode: u32,
|
|
447
|
+
frame: u32,
|
|
448
|
+
effect_index: u32,
|
|
449
|
+
effect_count: u32,
|
|
450
|
+
resolution: vec2f,
|
|
451
|
+
time: f32,
|
|
452
|
+
time_delta: f32,
|
|
453
|
+
cursor: vec4f,
|
|
454
|
+
}
|
|
455
|
+
@group(0) @binding(2) var<uniform> effect: EffectConfig;
|
|
456
|
+
|
|
457
|
+
@vertex fn vertex_main(@builtin(vertex_index) vertex_index: u32) -> @builtin(position) vec4f {
|
|
458
|
+
let positions = array<vec2f, 3>(
|
|
459
|
+
vec2f(-1.0, -1.0),
|
|
460
|
+
vec2f(3.0, -1.0),
|
|
461
|
+
vec2f(-1.0, 3.0),
|
|
462
|
+
);
|
|
463
|
+
return vec4f(positions[vertex_index], 0.0, 1.0);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
fn sample_image(uv: vec2f) -> vec4f {
|
|
467
|
+
return textureSample(terminal_image, terminal_sampler, clamp(uv, vec2f(0.0), vec2f(1.0)));
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
fn straight_rgb(sampled: vec4f) -> vec3f {
|
|
471
|
+
if (sampled.a <= 0.00001) { return vec3f(0.0); }
|
|
472
|
+
return sampled.rgb / sampled.a;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
fn premultiplied(rgb: vec3f, alpha: f32) -> vec4f {
|
|
476
|
+
return vec4f(max(rgb, vec3f(0.0)) * alpha, alpha);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
fn hash21(p0: vec2f) -> f32 {
|
|
480
|
+
var p = fract(p0 * vec2f(443.8975, 397.2973));
|
|
481
|
+
p += dot(p, p.yx + vec2f(19.19));
|
|
482
|
+
return fract(p.x * p.y);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Existing Better CRT compatibility port. Its source carries the upstream
|
|
486
|
+
// Shadertoy default CC BY-NC-SA terms; this mode preserves the already-bundled
|
|
487
|
+
// behavior while keeping alpha intact.
|
|
488
|
+
fn better_crt(position: vec2f, uv0: vec2f) -> vec4f {
|
|
489
|
+
var uv = uv0;
|
|
490
|
+
var dc = abs(vec2f(0.5) - uv);
|
|
491
|
+
dc *= dc;
|
|
492
|
+
uv.x = (uv.x - 0.5) * (1.0 + dc.y * 0.075) + 0.5;
|
|
493
|
+
uv.y = (uv.y - 0.5) * (1.0 + dc.x * 0.10) + 0.5;
|
|
494
|
+
let scanline = abs(sin(position.y) * 0.125);
|
|
495
|
+
let sampled = sample_image(uv);
|
|
496
|
+
return vec4f(sampled.rgb * (1.0 - scanline), sampled.a);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
fn from_srgb1(value: f32) -> f32 {
|
|
500
|
+
return select(pow(value / 1.055 + 0.055 / 1.055, 2.4), value / 12.92, value <= 0.04045);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
fn to_srgb1(value: f32) -> f32 {
|
|
504
|
+
return select(1.055 * pow(max(value, 0.0), 0.41666) - 0.055, value * 12.92, value < 0.0031308);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
fn from_srgb(value: vec3f) -> vec3f {
|
|
508
|
+
return vec3f(from_srgb1(value.r), from_srgb1(value.g), from_srgb1(value.b));
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
fn to_srgb(value: vec3f) -> vec3f {
|
|
512
|
+
return vec3f(to_srgb1(value.r), to_srgb1(value.g), to_srgb1(value.b));
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// WebGPU adaptation of Timothy Lottes' public-domain CRT scalar. The source
|
|
516
|
+
// port is Unlicensed/public domain. This retains its warp, scan blur, shadow
|
|
517
|
+
// mask, linear-light processing, and corner vignette in a compact GPU form.
|
|
518
|
+
fn public_domain_crt(position: vec2f, uv0: vec2f) -> vec4f {
|
|
519
|
+
let aspect = effect.resolution.x / max(1.0, effect.resolution.y);
|
|
520
|
+
var centered = uv0 * 2.0 - 1.0;
|
|
521
|
+
centered *= vec2f(
|
|
522
|
+
1.0 + centered.y * centered.y / (50.0 * aspect),
|
|
523
|
+
1.0 + centered.x * centered.x / 50.0,
|
|
524
|
+
);
|
|
525
|
+
let uv = centered * 0.5 + 0.5;
|
|
526
|
+
let pixel = 1.0 / effect.resolution;
|
|
527
|
+
let center_sample = sample_image(uv);
|
|
528
|
+
let left = from_srgb(straight_rgb(sample_image(uv - vec2f(pixel.x, 0.0))));
|
|
529
|
+
let center = from_srgb(straight_rgb(center_sample));
|
|
530
|
+
let right = from_srgb(straight_rgb(sample_image(uv + vec2f(pixel.x, 0.0))));
|
|
531
|
+
var color = left * 0.18 + center * 0.64 + right * 0.18;
|
|
532
|
+
let scan = 0.58 + 0.42 * pow(max(0.0, cos(position.y * 2.0943951)), 2.0);
|
|
533
|
+
let mask_index = u32(position.x + position.y * 3.0) % 6u;
|
|
534
|
+
var mask = vec3f(0.65);
|
|
535
|
+
if (mask_index < 2u) { mask.r = 1.0; }
|
|
536
|
+
else if (mask_index < 4u) { mask.g = 1.0; }
|
|
537
|
+
else { mask.b = 1.0; }
|
|
538
|
+
color *= scan * mask;
|
|
539
|
+
let vignette_axis = clamp(1.0 - centered * centered, vec2f(0.0), vec2f(1.0));
|
|
540
|
+
color *= mix(0.5, 1.0, vignette_axis.x * vignette_axis.y);
|
|
541
|
+
let peak = max(0.00001, max(color.r, max(color.g, color.b)));
|
|
542
|
+
let tone = peak / (peak * 0.72 + 0.28);
|
|
543
|
+
color *= tone / peak;
|
|
544
|
+
let inside = all(uv >= vec2f(0.0)) && all(uv <= vec2f(1.0));
|
|
545
|
+
let alpha = select(0.0, center_sample.a, inside);
|
|
546
|
+
return premultiplied(to_srgb(color), alpha);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// MIT-licensed VHS effect by Alex Brinsmead, adapted from the upstream GLSL
|
|
550
|
+
// to premultiplied WebGPU output.
|
|
551
|
+
fn vhs(position: vec2f, uv0: vec2f) -> vec4f {
|
|
552
|
+
let distortion = 1.0;
|
|
553
|
+
let tape_wear = 1.75;
|
|
554
|
+
let color_bleed = 2.0;
|
|
555
|
+
var cc = uv0 - 0.5;
|
|
556
|
+
var warped = clamp(uv0 + cc * (dot(cc, cc) * 0.02), vec2f(0.0), vec2f(1.0));
|
|
557
|
+
warped.x += (
|
|
558
|
+
sin(warped.y * 40.0 + effect.time * 0.5) * 0.000375 +
|
|
559
|
+
sin(warped.y * 80.0 + effect.time * 0.2) * 0.0001875 +
|
|
560
|
+
(hash21(vec2f(floor(position.y), floor(effect.time * 20.0))) - 0.5) * 0.0006
|
|
561
|
+
) * distortion;
|
|
562
|
+
let tracking_y = fract(effect.time * 0.04);
|
|
563
|
+
let band_distance = min(abs(warped.y - tracking_y), 1.0 - abs(warped.y - tracking_y));
|
|
564
|
+
let band = smoothstep(0.05, 0.0, band_distance);
|
|
565
|
+
let horizontal_offset = band * (hash21(vec2f(floor(position.y * 0.5), effect.time * 3.0)) - 0.5) * 0.006;
|
|
566
|
+
let burst_cycle = floor(effect.time / 30.0);
|
|
567
|
+
let burst_start = hash21(vec2f(burst_cycle, 88.0)) * 25.0;
|
|
568
|
+
let burst_time = effect.time - floor(effect.time / 30.0) * 30.0;
|
|
569
|
+
let burst = burst_time > burst_start && burst_time < burst_start + 5.0;
|
|
570
|
+
let jump = select(0.0, (hash21(vec2f(floor(effect.time * 2.0), 11.3)) - 0.5) * 0.08, burst);
|
|
571
|
+
let sample_uv = fract(warped + vec2f(horizontal_offset, jump));
|
|
572
|
+
let aberration = (0.0003 + band * 0.0015) * color_bleed;
|
|
573
|
+
let center_sample = sample_image(sample_uv);
|
|
574
|
+
let red_sample = straight_rgb(sample_image(sample_uv + vec2f(aberration, 0.0)));
|
|
575
|
+
let blue_sample = straight_rgb(sample_image(sample_uv - vec2f(aberration, 0.0)));
|
|
576
|
+
var color = vec3f(red_sample.r, straight_rgb(center_sample).g, blue_sample.b);
|
|
577
|
+
let pixel = 1.0 / effect.resolution.x;
|
|
578
|
+
let neighbor = straight_rgb(sample_image(sample_uv + vec2f(pixel, 0.0))) * 0.4 +
|
|
579
|
+
straight_rgb(sample_image(sample_uv + vec2f(pixel * 3.0, 0.0))) * 0.6;
|
|
580
|
+
let luma_weights = vec3f(0.299, 0.587, 0.114);
|
|
581
|
+
let luma = dot(color, luma_weights);
|
|
582
|
+
color = vec3f(luma) + mix(color - vec3f(luma), neighbor - vec3f(dot(neighbor, luma_weights)), 0.5 * color_bleed);
|
|
583
|
+
color *= 1.8;
|
|
584
|
+
let grain_time = fract(effect.time);
|
|
585
|
+
let noise1 = hash21(position + vec2f(grain_time * 100.0)) - 0.5;
|
|
586
|
+
let noise2 = hash21(position + vec2f(grain_time * 200.0)) - 0.5;
|
|
587
|
+
let gray = dot(color, luma_weights);
|
|
588
|
+
color += (color - vec3f(gray)) * vec3f(noise1, noise2, -noise1) * 0.15 * tape_wear;
|
|
589
|
+
color += vec3f(noise1 * 0.05 * tape_wear);
|
|
590
|
+
color *= mix(vec3f(1.0), vec3f(1.03, 1.01, 0.96), tape_wear);
|
|
591
|
+
let vignette = uv0 * (1.0 - uv0);
|
|
592
|
+
color *= mix(1.0, clamp(vignette.x * vignette.y * 15.0, 0.0, 1.0), 0.35);
|
|
593
|
+
let static_time = fract(effect.time * 20.0);
|
|
594
|
+
let static_hash = hash21(vec2f(floor(position.x / 10.0), floor(position.y / 2.0) + static_time * 500.0) + vec2f(static_time * 77.0));
|
|
595
|
+
color = mix(color, vec3f(1.0), step(0.9991, static_hash) * 0.135);
|
|
596
|
+
return premultiplied(color, center_sample.a);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Ember overlay inspired by Jan Mr\xF3z's CC BY 3.0 shader, adapted for the
|
|
600
|
+
// Ghostty pipeline. Attribution is retained in THIRD_PARTY_NOTICES.md.
|
|
601
|
+
fn sparks(position: vec2f, uv: vec2f) -> vec4f {
|
|
602
|
+
let terminal = sample_image(uv);
|
|
603
|
+
var spark = vec3f(0.0);
|
|
604
|
+
var spark_alpha = 0.0;
|
|
605
|
+
for (var layer = 0u; layer < 8u; layer += 1u) {
|
|
606
|
+
let layer_value = f32(layer);
|
|
607
|
+
let scale = 18.0 + layer_value * 7.0;
|
|
608
|
+
let drift = vec2f(effect.time * (0.35 + layer_value * 0.025), -effect.time * (0.7 + layer_value * 0.05));
|
|
609
|
+
let grid = uv * vec2f(scale, scale * 1.6) + drift;
|
|
610
|
+
let cell = floor(grid);
|
|
611
|
+
let random = hash21(cell + vec2f(layer_value * 13.7, layer_value * 5.1));
|
|
612
|
+
let local = fract(grid) - vec2f(random, fract(random * 17.17));
|
|
613
|
+
let radius = 0.018 + 0.045 * hash21(cell + vec2f(31.0));
|
|
614
|
+
let intensity = smoothstep(radius, 0.0, length(local)) * step(0.79, random);
|
|
615
|
+
let flicker = 0.55 + 0.45 * sin(effect.time * (5.0 + random * 4.0) + random * 31.0);
|
|
616
|
+
let value = intensity * flicker;
|
|
617
|
+
spark += vec3f(1.5, 0.42, 0.05) * value;
|
|
618
|
+
spark_alpha = max(spark_alpha, value * 0.78);
|
|
619
|
+
}
|
|
620
|
+
let terminal_rgb = straight_rgb(terminal);
|
|
621
|
+
let background_weight = 1.0 - smoothstep(0.12, 0.55, dot(terminal_rgb, vec3f(0.2126, 0.7152, 0.0722)));
|
|
622
|
+
let overlay_alpha = spark_alpha * background_weight;
|
|
623
|
+
let alpha = overlay_alpha + terminal.a * (1.0 - overlay_alpha);
|
|
624
|
+
let rgb = select(
|
|
625
|
+
vec3f(0.0),
|
|
626
|
+
(spark * overlay_alpha + terminal_rgb * terminal.a * (1.0 - overlay_alpha)) / max(alpha, 0.00001),
|
|
627
|
+
alpha > 0.00001,
|
|
628
|
+
);
|
|
629
|
+
return premultiplied(rgb, alpha);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
@fragment fn fragment_main(@builtin(position) position: vec4f) -> @location(0) vec4f {
|
|
633
|
+
let uv = position.xy / effect.resolution;
|
|
634
|
+
switch effect.mode {
|
|
635
|
+
case 1u: { return better_crt(position.xy, uv); }
|
|
636
|
+
case 2u: { return public_domain_crt(position.xy, uv); }
|
|
637
|
+
case 3u: { return vhs(position.xy, uv); }
|
|
638
|
+
case 4u: { return sparks(position.xy, uv); }
|
|
639
|
+
default: { return sample_image(uv); }
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
`
|
|
643
|
+
);
|
|
644
|
+
|
|
419
645
|
// src/renderers/webgpu-renderer.ts
|
|
420
646
|
var ATLAS_SIZE = 2048;
|
|
421
647
|
var GEOMETRY_CACHE_LIMIT = 8;
|
|
422
648
|
var FONT_STACK = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace";
|
|
649
|
+
var EFFECT_MODES = {
|
|
650
|
+
"ghosttea:better-crt": 1,
|
|
651
|
+
"ghosttea:crt": 2,
|
|
652
|
+
"ghosttea:vhs": 3,
|
|
653
|
+
"ghosttea:sparks-from-fire": 4
|
|
654
|
+
};
|
|
655
|
+
function effectIntermediateTextureCount(effectPassCount) {
|
|
656
|
+
return Math.min(2, Math.max(0, Math.trunc(effectPassCount) - 1));
|
|
657
|
+
}
|
|
423
658
|
var RECT_SHADER = (
|
|
424
659
|
/* wgsl */
|
|
425
660
|
`
|
|
@@ -553,54 +788,13 @@ struct VertexOutput {
|
|
|
553
788
|
}
|
|
554
789
|
`
|
|
555
790
|
);
|
|
556
|
-
var BETTER_CRT_SHADER = (
|
|
557
|
-
/* wgsl */
|
|
558
|
-
`
|
|
559
|
-
@group(0) @binding(0) var terminal_image: texture_2d<f32>;
|
|
560
|
-
@group(0) @binding(1) var terminal_sampler: sampler;
|
|
561
|
-
struct PostProcessConfig {
|
|
562
|
-
// A vec4 keeps the WGSL uniform layout equal to the host's 16-byte buffer.
|
|
563
|
-
enabled: vec4u,
|
|
564
|
-
}
|
|
565
|
-
@group(0) @binding(2) var<uniform> post_process: PostProcessConfig;
|
|
566
|
-
|
|
567
|
-
@vertex fn vertex_main(@builtin(vertex_index) vertex_index: u32) -> @builtin(position) vec4f {
|
|
568
|
-
let positions = array<vec2f, 3>(
|
|
569
|
-
vec2f(-1.0, -1.0),
|
|
570
|
-
vec2f(3.0, -1.0),
|
|
571
|
-
vec2f(-1.0, 3.0),
|
|
572
|
-
);
|
|
573
|
-
return vec4f(positions[vertex_index], 0.0, 1.0);
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
@fragment fn fragment_main(@builtin(position) position: vec4f) -> @location(0) vec4f {
|
|
577
|
-
let dimensions = vec2f(textureDimensions(terminal_image));
|
|
578
|
-
var uv = position.xy / dimensions;
|
|
579
|
-
if (post_process.enabled.x == 0u) {
|
|
580
|
-
return textureSample(terminal_image, terminal_sampler, uv);
|
|
581
|
-
}
|
|
582
|
-
var dc = abs(vec2f(0.5) - uv);
|
|
583
|
-
dc *= dc;
|
|
584
|
-
|
|
585
|
-
// These constants are the active Ghostty shader's warp = 0.25 and
|
|
586
|
-
// scan = 0.50 values.
|
|
587
|
-
uv.x -= 0.5;
|
|
588
|
-
uv.x *= 1.0 + dc.y * 0.075;
|
|
589
|
-
uv.x += 0.5;
|
|
590
|
-
uv.y -= 0.5;
|
|
591
|
-
uv.y *= 1.0 + dc.x * 0.10;
|
|
592
|
-
uv.y += 0.5;
|
|
593
|
-
|
|
594
|
-
let scanline = abs(sin(position.y) * 0.125);
|
|
595
|
-
let color = textureSample(terminal_image, terminal_sampler, uv).rgb;
|
|
596
|
-
return vec4f(mix(color, vec3f(0.0), scanline), 1.0);
|
|
597
|
-
}
|
|
598
|
-
`
|
|
599
|
-
);
|
|
600
791
|
var premultipliedBlend = {
|
|
601
792
|
color: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" },
|
|
602
793
|
alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" }
|
|
603
794
|
};
|
|
795
|
+
function premultipliedClear(color) {
|
|
796
|
+
return { r: color[0] * color[3], g: color[1] * color[3], b: color[2] * color[3], a: color[3] };
|
|
797
|
+
}
|
|
604
798
|
var DynamicVertexBuffer = class {
|
|
605
799
|
constructor(device, label) {
|
|
606
800
|
this.device = device;
|
|
@@ -890,16 +1084,20 @@ function geometryCacheKey(view, rows, hasNativeRows, atlasGenerations) {
|
|
|
890
1084
|
[
|
|
891
1085
|
...view.theme.background,
|
|
892
1086
|
...view.theme.foreground,
|
|
1087
|
+
...view.theme.cursor,
|
|
1088
|
+
...view.theme.cursorText ?? view.theme.background,
|
|
893
1089
|
...view.theme.selection,
|
|
894
1090
|
...view.theme.selectionForeground
|
|
895
1091
|
].join(","),
|
|
1092
|
+
view.theme.backgroundOpacityCells ? "opacity-cells" : "opaque-cells",
|
|
896
1093
|
selection,
|
|
1094
|
+
effectiveCursorStyle(view) === CursorStyle.Block ? `${view.cursor.x},${view.cursor.y}` : "-",
|
|
897
1095
|
atlasGenerations.join(",")
|
|
898
1096
|
].join("|");
|
|
899
1097
|
}
|
|
900
1098
|
function pushCursorVertices(vertices, view, renderRowSet, scale, viewportWidth, viewportHeight) {
|
|
901
1099
|
const cursorStyle = effectiveCursorStyle(view);
|
|
902
|
-
if (cursorStyle === null || !renderRowSet.has(view.cursor.y)) return;
|
|
1100
|
+
if (cursorStyle === null || cursorStyle === CursorStyle.Block || !renderRowSet.has(view.cursor.y)) return;
|
|
903
1101
|
const x = (ORIGIN_X + view.cursor.x * CELL_WIDTH) * scale;
|
|
904
1102
|
const y = (ORIGIN_Y + view.cursor.y * LINE_HEIGHT) * scale;
|
|
905
1103
|
const width = CELL_WIDTH * scale;
|
|
@@ -1090,6 +1288,22 @@ function selectionContains(selection, row, column) {
|
|
|
1090
1288
|
const last = row === end.row ? end.column : Number.MAX_SAFE_INTEGER;
|
|
1091
1289
|
return column >= first && column <= last;
|
|
1092
1290
|
}
|
|
1291
|
+
function blockCursorContains(view, row, column, span = 1) {
|
|
1292
|
+
return effectiveCursorStyle(view) === CursorStyle.Block && view.cursor.y === row && view.cursor.x >= column && view.cursor.x < column + Math.max(1, span);
|
|
1293
|
+
}
|
|
1294
|
+
function pushBlockCursorBackground(vertices, view, renderRows, scale, viewportWidth, viewportHeight) {
|
|
1295
|
+
if (effectiveCursorStyle(view) !== CursorStyle.Block || !renderRows.includes(view.cursor.y)) return;
|
|
1296
|
+
pushRectangle(
|
|
1297
|
+
vertices,
|
|
1298
|
+
(ORIGIN_X + view.cursor.x * CELL_WIDTH) * scale,
|
|
1299
|
+
(ORIGIN_Y + view.cursor.y * LINE_HEIGHT) * scale,
|
|
1300
|
+
CELL_WIDTH * scale,
|
|
1301
|
+
LINE_HEIGHT * scale,
|
|
1302
|
+
view.theme.cursor,
|
|
1303
|
+
viewportWidth,
|
|
1304
|
+
viewportHeight
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1093
1307
|
function resolveStyle(style, theme) {
|
|
1094
1308
|
let foreground = style?.foreground ? rgb(style.foreground) : theme.foreground;
|
|
1095
1309
|
let background = style?.background ? rgb(style.background) : null;
|
|
@@ -1098,6 +1312,9 @@ function resolveStyle(style, theme) {
|
|
|
1098
1312
|
foreground = background ?? theme.background;
|
|
1099
1313
|
background = originalForeground;
|
|
1100
1314
|
}
|
|
1315
|
+
if (background && theme.backgroundOpacityCells) {
|
|
1316
|
+
background = [background[0], background[1], background[2], theme.background[3]];
|
|
1317
|
+
}
|
|
1101
1318
|
if (style?.faint) foreground = [foreground[0], foreground[1], foreground[2], foreground[3] * 0.55];
|
|
1102
1319
|
return {
|
|
1103
1320
|
foreground,
|
|
@@ -1241,7 +1458,10 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1241
1458
|
label: "terminal color glyph shader",
|
|
1242
1459
|
code: COLOR_GLYPH_SHADER
|
|
1243
1460
|
});
|
|
1244
|
-
const
|
|
1461
|
+
const effectModule = device.createShaderModule({
|
|
1462
|
+
label: "Ghosttea shader effect registry",
|
|
1463
|
+
code: SHADER_EFFECT_WGSL
|
|
1464
|
+
});
|
|
1245
1465
|
this.#rectanglePipeline = device.createRenderPipeline({
|
|
1246
1466
|
label: "terminal rectangle pipeline",
|
|
1247
1467
|
layout: "auto",
|
|
@@ -1268,6 +1488,28 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1268
1488
|
},
|
|
1269
1489
|
primitive: { topology: "triangle-list" }
|
|
1270
1490
|
});
|
|
1491
|
+
this.#rectangleOverwritePipeline = device.createRenderPipeline({
|
|
1492
|
+
label: "terminal row-reset overwrite pipeline",
|
|
1493
|
+
layout: "auto",
|
|
1494
|
+
vertex: {
|
|
1495
|
+
module: rectangleModule,
|
|
1496
|
+
entryPoint: "vertex_main",
|
|
1497
|
+
buffers: [
|
|
1498
|
+
{
|
|
1499
|
+
arrayStride: 40,
|
|
1500
|
+
stepMode: "instance",
|
|
1501
|
+
attributes: [
|
|
1502
|
+
{ shaderLocation: 0, offset: 0, format: "float32x2" },
|
|
1503
|
+
{ shaderLocation: 1, offset: 8, format: "float32x2" },
|
|
1504
|
+
{ shaderLocation: 2, offset: 16, format: "float32x2" },
|
|
1505
|
+
{ shaderLocation: 3, offset: 24, format: "float32x4" }
|
|
1506
|
+
]
|
|
1507
|
+
}
|
|
1508
|
+
]
|
|
1509
|
+
},
|
|
1510
|
+
fragment: { module: rectangleModule, entryPoint: "fragment_main", targets: [{ format }] },
|
|
1511
|
+
primitive: { topology: "triangle-list" }
|
|
1512
|
+
});
|
|
1271
1513
|
this.#glyphPipeline = device.createRenderPipeline({
|
|
1272
1514
|
label: "terminal glyph pipeline",
|
|
1273
1515
|
layout: "auto",
|
|
@@ -1339,15 +1581,15 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1339
1581
|
},
|
|
1340
1582
|
primitive: { topology: "triangle-list" }
|
|
1341
1583
|
});
|
|
1342
|
-
this.#
|
|
1343
|
-
label: "
|
|
1584
|
+
this.#effectPipeline = device.createRenderPipeline({
|
|
1585
|
+
label: "Ghosttea ordered shader effect pipeline",
|
|
1344
1586
|
layout: "auto",
|
|
1345
|
-
vertex: { module:
|
|
1346
|
-
fragment: { module:
|
|
1587
|
+
vertex: { module: effectModule, entryPoint: "vertex_main" },
|
|
1588
|
+
fragment: { module: effectModule, entryPoint: "fragment_main", targets: [{ format }] },
|
|
1347
1589
|
primitive: { topology: "triangle-list" }
|
|
1348
1590
|
});
|
|
1349
|
-
this.#
|
|
1350
|
-
label: "
|
|
1591
|
+
this.#effectSampler = device.createSampler({
|
|
1592
|
+
label: "Ghosttea shader effect sampler",
|
|
1351
1593
|
addressModeU: "clamp-to-edge",
|
|
1352
1594
|
addressModeV: "clamp-to-edge",
|
|
1353
1595
|
minFilter: "linear",
|
|
@@ -1370,11 +1612,12 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1370
1612
|
kind = "webgpu";
|
|
1371
1613
|
#surfaces = /* @__PURE__ */ new Map();
|
|
1372
1614
|
#rectanglePipeline;
|
|
1615
|
+
#rectangleOverwritePipeline;
|
|
1373
1616
|
#glyphPipeline;
|
|
1374
1617
|
#fallbackGlyphPipeline;
|
|
1375
1618
|
#colorGlyphPipeline;
|
|
1376
|
-
#
|
|
1377
|
-
#
|
|
1619
|
+
#effectPipeline;
|
|
1620
|
+
#effectSampler;
|
|
1378
1621
|
#monoAtlas;
|
|
1379
1622
|
#colorAtlas;
|
|
1380
1623
|
#fallbackAtlas;
|
|
@@ -1399,18 +1642,15 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1399
1642
|
const context = canvas.getContext("webgpu");
|
|
1400
1643
|
if (!context) throw new Error("WebGPU canvas context unavailable");
|
|
1401
1644
|
const sceneTexture = this.#createSceneTexture(canvas.width, canvas.height);
|
|
1402
|
-
const postProcessConfigBuffer = this.device.createBuffer({
|
|
1403
|
-
label: `Ghostty custom shader configuration ${id}`,
|
|
1404
|
-
size: 16,
|
|
1405
|
-
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
1406
|
-
});
|
|
1407
1645
|
const surface2 = {
|
|
1408
1646
|
canvas,
|
|
1409
1647
|
context,
|
|
1410
1648
|
sceneTexture,
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1649
|
+
effectTextures: [],
|
|
1650
|
+
effectPasses: [],
|
|
1651
|
+
effectSignature: "",
|
|
1652
|
+
effectFrame: 0,
|
|
1653
|
+
lastEffectTime: 0,
|
|
1414
1654
|
width: 1,
|
|
1415
1655
|
height: 1,
|
|
1416
1656
|
dpr: 1,
|
|
@@ -1431,7 +1671,8 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1431
1671
|
if (!surface2) return;
|
|
1432
1672
|
surface2.context.unconfigure();
|
|
1433
1673
|
surface2.sceneTexture.destroy();
|
|
1434
|
-
surface2.
|
|
1674
|
+
for (const texture of surface2.effectTextures) texture.destroy();
|
|
1675
|
+
for (const pass of surface2.effectPasses) pass.configBuffer.destroy();
|
|
1435
1676
|
surface2.rectangleBuffer.destroy();
|
|
1436
1677
|
surface2.glyphBuffer.destroy();
|
|
1437
1678
|
surface2.colorGlyphBuffer.destroy();
|
|
@@ -1453,11 +1694,12 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1453
1694
|
#configure(surface2) {
|
|
1454
1695
|
surface2.context.configure({ device: this.device, format: this.format, alphaMode: "premultiplied" });
|
|
1455
1696
|
surface2.sceneTexture.destroy();
|
|
1697
|
+
for (const texture of surface2.effectTextures) texture.destroy();
|
|
1698
|
+
for (const pass of surface2.effectPasses) pass.configBuffer.destroy();
|
|
1456
1699
|
surface2.sceneTexture = this.#createSceneTexture(surface2.canvas.width, surface2.canvas.height);
|
|
1457
|
-
surface2.
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
);
|
|
1700
|
+
surface2.effectTextures = [];
|
|
1701
|
+
surface2.effectPasses = [];
|
|
1702
|
+
surface2.effectSignature = "";
|
|
1461
1703
|
clearGeometryCache(surface2);
|
|
1462
1704
|
surface2.sceneValid = false;
|
|
1463
1705
|
}
|
|
@@ -1469,29 +1711,116 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1469
1711
|
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
|
|
1470
1712
|
});
|
|
1471
1713
|
}
|
|
1472
|
-
#
|
|
1714
|
+
#createEffectTexture(width, height, suffix) {
|
|
1715
|
+
return this.device.createTexture({
|
|
1716
|
+
label: `terminal shader ping-pong ${suffix}`,
|
|
1717
|
+
size: [Math.max(1, width), Math.max(1, height)],
|
|
1718
|
+
format: this.format,
|
|
1719
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
#createEffectBindGroup(texture, configBuffer) {
|
|
1473
1723
|
return this.device.createBindGroup({
|
|
1474
|
-
label: "
|
|
1475
|
-
layout: this.#
|
|
1724
|
+
label: "Ghosttea shader effect bindings",
|
|
1725
|
+
layout: this.#effectPipeline.getBindGroupLayout(0),
|
|
1476
1726
|
entries: [
|
|
1477
1727
|
{ binding: 0, resource: texture.createView() },
|
|
1478
|
-
{ binding: 1, resource: this.#
|
|
1728
|
+
{ binding: 1, resource: this.#effectSampler },
|
|
1479
1729
|
{ binding: 2, resource: { buffer: configBuffer } }
|
|
1480
1730
|
]
|
|
1481
1731
|
});
|
|
1482
1732
|
}
|
|
1733
|
+
#shaderStack(view) {
|
|
1734
|
+
const configured = view.effects?.shaderEffects?.filter((id) => EFFECT_MODES[id] !== void 0) ?? [];
|
|
1735
|
+
if (configured.length > 0) return [...configured];
|
|
1736
|
+
return view.effects?.postProcess === "better-crt" ? ["ghosttea:better-crt"] : [];
|
|
1737
|
+
}
|
|
1738
|
+
#ensureEffectPasses(surface2, stack) {
|
|
1739
|
+
const signature = stack.join("\0") || "ghosttea:blit";
|
|
1740
|
+
if (surface2.effectSignature === signature) return;
|
|
1741
|
+
for (const pass of surface2.effectPasses) pass.configBuffer.destroy();
|
|
1742
|
+
const modes = stack.length > 0 ? stack.map((id) => EFFECT_MODES[id]) : [0];
|
|
1743
|
+
const intermediateCount = effectIntermediateTextureCount(modes.length);
|
|
1744
|
+
while (surface2.effectTextures.length > intermediateCount) surface2.effectTextures.pop().destroy();
|
|
1745
|
+
while (surface2.effectTextures.length < intermediateCount) {
|
|
1746
|
+
const suffix = surface2.effectTextures.length === 0 ? "A" : "B";
|
|
1747
|
+
surface2.effectTextures.push(this.#createEffectTexture(surface2.canvas.width, surface2.canvas.height, suffix));
|
|
1748
|
+
}
|
|
1749
|
+
surface2.effectPasses = modes.map((mode, index) => {
|
|
1750
|
+
const configBuffer = this.device.createBuffer({
|
|
1751
|
+
label: `Ghosttea shader effect configuration ${index}`,
|
|
1752
|
+
size: 48,
|
|
1753
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
1754
|
+
});
|
|
1755
|
+
const inputTexture = index === 0 ? surface2.sceneTexture : surface2.effectTextures[(index - 1) % 2];
|
|
1756
|
+
return {
|
|
1757
|
+
mode,
|
|
1758
|
+
configBuffer,
|
|
1759
|
+
bindGroup: this.#createEffectBindGroup(inputTexture, configBuffer)
|
|
1760
|
+
};
|
|
1761
|
+
});
|
|
1762
|
+
surface2.effectSignature = signature;
|
|
1763
|
+
surface2.effectFrame = 0;
|
|
1764
|
+
surface2.lastEffectTime = 0;
|
|
1765
|
+
}
|
|
1766
|
+
#encodeEffectStack(surface2, view, encoder) {
|
|
1767
|
+
const stack = this.#shaderStack(view);
|
|
1768
|
+
this.#ensureEffectPasses(surface2, stack);
|
|
1769
|
+
const now = view.effects?.animate ? performance.now() / 1e3 : 0;
|
|
1770
|
+
const delta = surface2.lastEffectTime > 0 ? Math.min(0.1, Math.max(0, now - surface2.lastEffectTime)) : 0;
|
|
1771
|
+
surface2.lastEffectTime = now;
|
|
1772
|
+
const passCount = surface2.effectPasses.length;
|
|
1773
|
+
for (let index = 0; index < passCount; index += 1) {
|
|
1774
|
+
const effectPass = surface2.effectPasses[index];
|
|
1775
|
+
const storage = new ArrayBuffer(48);
|
|
1776
|
+
const values = new DataView(storage);
|
|
1777
|
+
values.setUint32(0, effectPass.mode, true);
|
|
1778
|
+
values.setUint32(4, surface2.effectFrame, true);
|
|
1779
|
+
values.setUint32(8, index, true);
|
|
1780
|
+
values.setUint32(12, passCount, true);
|
|
1781
|
+
values.setFloat32(16, surface2.canvas.width, true);
|
|
1782
|
+
values.setFloat32(20, surface2.canvas.height, true);
|
|
1783
|
+
values.setFloat32(24, now, true);
|
|
1784
|
+
values.setFloat32(28, delta, true);
|
|
1785
|
+
values.setFloat32(32, (ORIGIN_X + view.cursor.x * CELL_WIDTH) * surface2.dpr, true);
|
|
1786
|
+
values.setFloat32(36, (ORIGIN_Y + view.cursor.y * LINE_HEIGHT) * surface2.dpr, true);
|
|
1787
|
+
values.setFloat32(40, view.cursor.visible ? 1 : 0, true);
|
|
1788
|
+
values.setFloat32(44, view.cursor.style, true);
|
|
1789
|
+
this.device.queue.writeBuffer(effectPass.configBuffer, 0, storage);
|
|
1790
|
+
const last = index === passCount - 1;
|
|
1791
|
+
const output = last ? surface2.context.getCurrentTexture().createView() : surface2.effectTextures[index % 2].createView();
|
|
1792
|
+
const pass = encoder.beginRenderPass({
|
|
1793
|
+
label: `Ghosttea shader effect pass ${index + 1}/${passCount}`,
|
|
1794
|
+
colorAttachments: [
|
|
1795
|
+
{
|
|
1796
|
+
view: output,
|
|
1797
|
+
clearValue: { r: 0, g: 0, b: 0, a: 0 },
|
|
1798
|
+
loadOp: "clear",
|
|
1799
|
+
storeOp: "store"
|
|
1800
|
+
}
|
|
1801
|
+
]
|
|
1802
|
+
});
|
|
1803
|
+
pass.setPipeline(this.#effectPipeline);
|
|
1804
|
+
pass.setBindGroup(0, effectPass.bindGroup);
|
|
1805
|
+
pass.draw(3);
|
|
1806
|
+
pass.end();
|
|
1807
|
+
}
|
|
1808
|
+
surface2.effectFrame += 1;
|
|
1809
|
+
return passCount;
|
|
1810
|
+
}
|
|
1811
|
+
#encodeEffectsOnly(surface2, view, encoder) {
|
|
1812
|
+
const effectPassCount = this.#encodeEffectStack(surface2, view, encoder);
|
|
1813
|
+
if (!this.#performanceMeasurementEnabled) return void 0;
|
|
1814
|
+
return {
|
|
1815
|
+
...emptyRenderMetrics(),
|
|
1816
|
+
canvasPixels: surface2.canvas.width * surface2.canvas.height,
|
|
1817
|
+
renderPasses: effectPassCount,
|
|
1818
|
+
drawCalls: effectPassCount
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1483
1821
|
render(id, view) {
|
|
1484
1822
|
const surface2 = this.#surfaces.get(id);
|
|
1485
1823
|
if (!surface2) return;
|
|
1486
|
-
const postProcessEnabled = view.effects?.postProcess === "better-crt";
|
|
1487
|
-
if (surface2.postProcessEnabled !== postProcessEnabled) {
|
|
1488
|
-
surface2.postProcessEnabled = postProcessEnabled;
|
|
1489
|
-
this.device.queue.writeBuffer(
|
|
1490
|
-
surface2.postProcessConfigBuffer,
|
|
1491
|
-
0,
|
|
1492
|
-
new Uint32Array([Number(postProcessEnabled), 0, 0, 0])
|
|
1493
|
-
);
|
|
1494
|
-
}
|
|
1495
1824
|
const encoder = this.device.createCommandEncoder({ label: `terminal frame ${id}` });
|
|
1496
1825
|
const metrics = view.damage?.geometryChanged === false ? this.#encodeCachedRender(id, view, encoder) : this.#encodeRender(id, view, encoder);
|
|
1497
1826
|
this.device.queue.submit([encoder.finish()]);
|
|
@@ -1504,16 +1833,6 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1504
1833
|
const encoder = this.device.createCommandEncoder({ label: `terminal frame batch (${active.length} panes)` });
|
|
1505
1834
|
const byId = /* @__PURE__ */ new Map();
|
|
1506
1835
|
for (const { id, view } of active) {
|
|
1507
|
-
const surface2 = this.#surfaces.get(id);
|
|
1508
|
-
const postProcessEnabled = view.effects?.postProcess === "better-crt";
|
|
1509
|
-
if (surface2.postProcessEnabled !== postProcessEnabled) {
|
|
1510
|
-
surface2.postProcessEnabled = postProcessEnabled;
|
|
1511
|
-
this.device.queue.writeBuffer(
|
|
1512
|
-
surface2.postProcessConfigBuffer,
|
|
1513
|
-
0,
|
|
1514
|
-
new Uint32Array([Number(postProcessEnabled), 0, 0, 0])
|
|
1515
|
-
);
|
|
1516
|
-
}
|
|
1517
1836
|
byId.set(
|
|
1518
1837
|
id,
|
|
1519
1838
|
view.damage?.geometryChanged === false ? this.#encodeCachedRender(id, view, encoder) : this.#encodeRender(id, view, encoder)
|
|
@@ -1561,14 +1880,13 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1561
1880
|
top,
|
|
1562
1881
|
viewportWidth,
|
|
1563
1882
|
Math.max(0, bottom - top),
|
|
1564
|
-
|
|
1565
|
-
// replaces stale scene pixels even when a theme carries alpha.
|
|
1566
|
-
[view.theme.background[0], view.theme.background[1], view.theme.background[2], 1],
|
|
1883
|
+
view.theme.background,
|
|
1567
1884
|
viewportWidth,
|
|
1568
1885
|
viewportHeight
|
|
1569
1886
|
);
|
|
1570
1887
|
}
|
|
1571
1888
|
}
|
|
1889
|
+
const rowResetInstanceCount = rectangleVertices.length / 10;
|
|
1572
1890
|
for (const row of renderRows) {
|
|
1573
1891
|
for (const run of view.nativeStyleRows[row] ?? []) {
|
|
1574
1892
|
const style = styleFor(run.styleId);
|
|
@@ -1605,6 +1923,7 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1605
1923
|
);
|
|
1606
1924
|
}
|
|
1607
1925
|
}
|
|
1926
|
+
pushBlockCursorBackground(rectangleVertices, view, renderRows, scale, viewportWidth, viewportHeight);
|
|
1608
1927
|
const selectionInstanceCount = rectangleVertices.length / 10 - backgroundInstanceCount;
|
|
1609
1928
|
if (!hasNativeRows) {
|
|
1610
1929
|
this.#fallbackAtlas.prepare(
|
|
@@ -1621,7 +1940,7 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1621
1940
|
const style = styleFor(instance.styleId);
|
|
1622
1941
|
if (style.invisible) continue;
|
|
1623
1942
|
const isSelected = selectionContains(selection, row, instance.cellStart);
|
|
1624
|
-
const foreground = isSelected ? view.theme.selectionForeground : style.foreground;
|
|
1943
|
+
const foreground = blockCursorContains(view, row, instance.cellStart, instance.cellSpan) ? view.theme.cursorText ?? view.theme.background : isSelected ? view.theme.selectionForeground : style.foreground;
|
|
1625
1944
|
const boxDrawing = boxCells.get(instance.cellStart);
|
|
1626
1945
|
if (boxDrawing) {
|
|
1627
1946
|
let backdrop = style.background ?? view.theme.background;
|
|
@@ -1676,7 +1995,7 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1676
1995
|
const glyph = this.#fallbackAtlas.glyph(grapheme, scale);
|
|
1677
1996
|
const cells = glyph?.cells ?? graphemeCellWidth(grapheme);
|
|
1678
1997
|
if (glyph) {
|
|
1679
|
-
const foreground = selectionContains(selection, row, column) ? view.theme.selectionForeground : view.theme.foreground;
|
|
1998
|
+
const foreground = blockCursorContains(view, row, column, cells) ? view.theme.cursorText ?? view.theme.background : selectionContains(selection, row, column) ? view.theme.selectionForeground : view.theme.foreground;
|
|
1680
1999
|
pushGlyph(
|
|
1681
2000
|
fallbackGlyphVertices,
|
|
1682
2001
|
(ORIGIN_X + column * CELL_WIDTH) * scale,
|
|
@@ -1734,6 +2053,7 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1734
2053
|
glyphData: new Float32Array(glyphVertices),
|
|
1735
2054
|
colorGlyphData: new Float32Array(colorGlyphVertices),
|
|
1736
2055
|
fallbackGlyphData: new Float32Array(fallbackGlyphVertices),
|
|
2056
|
+
rowResetInstanceCount,
|
|
1737
2057
|
backgroundInstanceCount,
|
|
1738
2058
|
selectionInstanceCount,
|
|
1739
2059
|
decorationInstanceStart,
|
|
@@ -1792,14 +2112,13 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1792
2112
|
top,
|
|
1793
2113
|
viewportWidth,
|
|
1794
2114
|
Math.max(0, bottom - top),
|
|
1795
|
-
|
|
1796
|
-
// replaces stale scene pixels even when a theme carries alpha.
|
|
1797
|
-
[view.theme.background[0], view.theme.background[1], view.theme.background[2], 1],
|
|
2115
|
+
view.theme.background,
|
|
1798
2116
|
viewportWidth,
|
|
1799
2117
|
viewportHeight
|
|
1800
2118
|
);
|
|
1801
2119
|
}
|
|
1802
2120
|
}
|
|
2121
|
+
const rowResetInstanceCount = rectangleVertices.length / 10;
|
|
1803
2122
|
for (const row of renderRows) {
|
|
1804
2123
|
for (const run of view.nativeStyleRows[row] ?? []) {
|
|
1805
2124
|
const style = styleFor(run.styleId);
|
|
@@ -1836,6 +2155,7 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1836
2155
|
);
|
|
1837
2156
|
}
|
|
1838
2157
|
}
|
|
2158
|
+
pushBlockCursorBackground(rectangleVertices, view, renderRows, scale, viewportWidth, viewportHeight);
|
|
1839
2159
|
const selectionInstanceCount = rectangleVertices.length / 10 - backgroundInstanceCount;
|
|
1840
2160
|
const hasNativeRows = view.nativeRows.some((row) => row.length > 0);
|
|
1841
2161
|
if (!hasNativeRows) {
|
|
@@ -1853,7 +2173,7 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1853
2173
|
const style = styleFor(instance.styleId);
|
|
1854
2174
|
if (style.invisible) continue;
|
|
1855
2175
|
const isSelected = selectionContains(selection, row, instance.cellStart);
|
|
1856
|
-
const foreground = isSelected ? view.theme.selectionForeground : style.foreground;
|
|
2176
|
+
const foreground = blockCursorContains(view, row, instance.cellStart, instance.cellSpan) ? view.theme.cursorText ?? view.theme.background : isSelected ? view.theme.selectionForeground : style.foreground;
|
|
1857
2177
|
const boxDrawing = boxCells.get(instance.cellStart);
|
|
1858
2178
|
if (boxDrawing) {
|
|
1859
2179
|
let backdrop = style.background ?? view.theme.background;
|
|
@@ -1910,7 +2230,7 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
1910
2230
|
const glyph = this.#fallbackAtlas.glyph(grapheme, scale);
|
|
1911
2231
|
const cells = glyph?.cells ?? graphemeCellWidth(grapheme);
|
|
1912
2232
|
if (glyph) {
|
|
1913
|
-
const foreground = selectionContains(selection, row, column) ? view.theme.selectionForeground : view.theme.foreground;
|
|
2233
|
+
const foreground = blockCursorContains(view, row, column, cells) ? view.theme.cursorText ?? view.theme.background : selectionContains(selection, row, column) ? view.theme.selectionForeground : view.theme.foreground;
|
|
1914
2234
|
pushGlyph(
|
|
1915
2235
|
fallbackGlyphVertices,
|
|
1916
2236
|
(ORIGIN_X + column * CELL_WIDTH) * scale,
|
|
@@ -2026,7 +2346,7 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2026
2346
|
viewportWidth,
|
|
2027
2347
|
viewportHeight
|
|
2028
2348
|
);
|
|
2029
|
-
} else {
|
|
2349
|
+
} else if (cursorStyle !== CursorStyle.Block) {
|
|
2030
2350
|
pushRectangle(rectangleVertices, x, y, width, height, cursorColor, viewportWidth, viewportHeight);
|
|
2031
2351
|
}
|
|
2032
2352
|
}
|
|
@@ -2045,21 +2365,21 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2045
2365
|
colorAttachments: [
|
|
2046
2366
|
{
|
|
2047
2367
|
view: surface2.sceneTexture.createView(),
|
|
2048
|
-
clearValue:
|
|
2049
|
-
r: view.theme.background[0],
|
|
2050
|
-
g: view.theme.background[1],
|
|
2051
|
-
b: view.theme.background[2],
|
|
2052
|
-
a: view.theme.background[3]
|
|
2053
|
-
},
|
|
2368
|
+
clearValue: premultipliedClear(view.theme.background),
|
|
2054
2369
|
loadOp: fullRedraw ? "clear" : "load",
|
|
2055
2370
|
storeOp: "store"
|
|
2056
2371
|
}
|
|
2057
2372
|
]
|
|
2058
2373
|
});
|
|
2059
|
-
if (rectangleBuffer &&
|
|
2374
|
+
if (rectangleBuffer && rowResetInstanceCount > 0) {
|
|
2375
|
+
pass.setPipeline(this.#rectangleOverwritePipeline);
|
|
2376
|
+
pass.setVertexBuffer(0, rectangleBuffer);
|
|
2377
|
+
pass.draw(6, rowResetInstanceCount);
|
|
2378
|
+
}
|
|
2379
|
+
if (rectangleBuffer && backgroundInstanceCount > rowResetInstanceCount) {
|
|
2060
2380
|
pass.setPipeline(this.#rectanglePipeline);
|
|
2061
2381
|
pass.setVertexBuffer(0, rectangleBuffer);
|
|
2062
|
-
pass.draw(6, backgroundInstanceCount);
|
|
2382
|
+
pass.draw(6, backgroundInstanceCount - rowResetInstanceCount, 0, rowResetInstanceCount);
|
|
2063
2383
|
}
|
|
2064
2384
|
if (rectangleBuffer && selectionInstanceCount > 0) {
|
|
2065
2385
|
pass.setPipeline(this.#rectanglePipeline);
|
|
@@ -2095,26 +2415,7 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2095
2415
|
pass.draw(6, cursorInstanceCount, 0, cursorInstanceStart);
|
|
2096
2416
|
}
|
|
2097
2417
|
pass.end();
|
|
2098
|
-
const
|
|
2099
|
-
label: `Ghostty custom shader pass ${id}`,
|
|
2100
|
-
colorAttachments: [
|
|
2101
|
-
{
|
|
2102
|
-
view: surface2.context.getCurrentTexture().createView(),
|
|
2103
|
-
clearValue: {
|
|
2104
|
-
r: view.theme.background[0],
|
|
2105
|
-
g: view.theme.background[1],
|
|
2106
|
-
b: view.theme.background[2],
|
|
2107
|
-
a: 1
|
|
2108
|
-
},
|
|
2109
|
-
loadOp: "clear",
|
|
2110
|
-
storeOp: "store"
|
|
2111
|
-
}
|
|
2112
|
-
]
|
|
2113
|
-
});
|
|
2114
|
-
postProcessPass.setPipeline(this.#postProcessPipeline);
|
|
2115
|
-
postProcessPass.setBindGroup(0, surface2.postProcessBindGroup);
|
|
2116
|
-
postProcessPass.draw(3);
|
|
2117
|
-
postProcessPass.end();
|
|
2418
|
+
const effectPassCount = this.#encodeEffectStack(surface2, view, encoder);
|
|
2118
2419
|
surface2.sceneValid = true;
|
|
2119
2420
|
if (!this.#performanceMeasurementEnabled) return void 0;
|
|
2120
2421
|
const monoUploadsAfter = this.#monoAtlas.uploadMetrics();
|
|
@@ -2128,8 +2429,8 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2128
2429
|
geometryCacheHits: 0,
|
|
2129
2430
|
geometryCacheMisses: 0,
|
|
2130
2431
|
canvasPixels: viewportWidth * viewportHeight,
|
|
2131
|
-
renderPasses:
|
|
2132
|
-
drawCalls: Number(
|
|
2432
|
+
renderPasses: 1 + effectPassCount,
|
|
2433
|
+
drawCalls: Number(rowResetInstanceCount > 0) + Number(backgroundInstanceCount > rowResetInstanceCount) + Number(selectionInstanceCount > 0) + Number(glyphData.length > 0) + Number(colorGlyphData.length > 0) + Number(fallbackGlyphData.length > 0) + Number(decorationInstanceCount > 0) + Number(cursorInstanceCount > 0) + effectPassCount,
|
|
2133
2434
|
rectangleVertices: rectangleVertices.length / 10 * 6,
|
|
2134
2435
|
monoGlyphVertices: glyphVertices.length / 12 * 6,
|
|
2135
2436
|
colorGlyphVertices: colorGlyphVertices.length / 12 * 6,
|
|
@@ -2142,9 +2443,12 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2142
2443
|
#encodeCachedRender(id, view, encoder) {
|
|
2143
2444
|
const surface2 = this.#surfaces.get(id);
|
|
2144
2445
|
if (!surface2) return;
|
|
2145
|
-
if (!surface2.sceneValid || !view.damage || view.damage.full)
|
|
2446
|
+
if (!surface2.sceneValid || !view.damage || view.damage.full) {
|
|
2447
|
+
return this.#encodeRender(id, view, encoder);
|
|
2448
|
+
}
|
|
2146
2449
|
const rowCount = Math.max(view.rows.length, view.nativeRows.length, view.nativeStyleRows.length);
|
|
2147
2450
|
const damage = rowsForDamage(rowCount, view.damage, surface2.sceneValid);
|
|
2451
|
+
if (damage.rows.length === 0) return this.#encodeEffectsOnly(surface2, view, encoder);
|
|
2148
2452
|
const monoUploadsBefore = this.#performanceMeasurementEnabled ? this.#monoAtlas.uploadMetrics() : void 0;
|
|
2149
2453
|
const colorUploadsBefore = this.#performanceMeasurementEnabled ? this.#colorAtlas.uploadMetrics() : void 0;
|
|
2150
2454
|
const fallbackUploadsBefore = this.#performanceMeasurementEnabled ? this.#fallbackAtlas.uploadMetrics() : void 0;
|
|
@@ -2187,6 +2491,7 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2187
2491
|
glyphBuffer: promote ? createCachedVertexBuffer(this.device, `cached glyphs ${id}`, cpu.glyphData) : surface2.glyphBuffer.write(cpu.glyphData),
|
|
2188
2492
|
colorGlyphBuffer: promote ? createCachedVertexBuffer(this.device, `cached color glyphs ${id}`, cpu.colorGlyphData) : surface2.colorGlyphBuffer.write(cpu.colorGlyphData),
|
|
2189
2493
|
fallbackGlyphBuffer: promote ? createCachedVertexBuffer(this.device, `cached fallback glyphs ${id}`, cpu.fallbackGlyphData) : surface2.fallbackGlyphBuffer.write(cpu.fallbackGlyphData),
|
|
2494
|
+
rowResetInstanceCount: cpu.rowResetInstanceCount,
|
|
2190
2495
|
backgroundInstanceCount: cpu.backgroundInstanceCount,
|
|
2191
2496
|
selectionInstanceCount: cpu.selectionInstanceCount,
|
|
2192
2497
|
decorationInstanceStart: cpu.decorationInstanceStart,
|
|
@@ -2228,21 +2533,26 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2228
2533
|
colorAttachments: [
|
|
2229
2534
|
{
|
|
2230
2535
|
view: surface2.sceneTexture.createView(),
|
|
2231
|
-
clearValue:
|
|
2232
|
-
r: view.theme.background[0],
|
|
2233
|
-
g: view.theme.background[1],
|
|
2234
|
-
b: view.theme.background[2],
|
|
2235
|
-
a: view.theme.background[3]
|
|
2236
|
-
},
|
|
2536
|
+
clearValue: premultipliedClear(view.theme.background),
|
|
2237
2537
|
loadOp: "load",
|
|
2238
2538
|
storeOp: "store"
|
|
2239
2539
|
}
|
|
2240
2540
|
]
|
|
2241
2541
|
});
|
|
2242
|
-
if (geometry.rectangleBuffer && geometry.
|
|
2542
|
+
if (geometry.rectangleBuffer && geometry.rowResetInstanceCount > 0) {
|
|
2543
|
+
pass.setPipeline(this.#rectangleOverwritePipeline);
|
|
2544
|
+
pass.setVertexBuffer(0, geometry.rectangleBuffer);
|
|
2545
|
+
pass.draw(6, geometry.rowResetInstanceCount);
|
|
2546
|
+
}
|
|
2547
|
+
if (geometry.rectangleBuffer && geometry.backgroundInstanceCount > geometry.rowResetInstanceCount) {
|
|
2243
2548
|
pass.setPipeline(this.#rectanglePipeline);
|
|
2244
2549
|
pass.setVertexBuffer(0, geometry.rectangleBuffer);
|
|
2245
|
-
pass.draw(
|
|
2550
|
+
pass.draw(
|
|
2551
|
+
6,
|
|
2552
|
+
geometry.backgroundInstanceCount - geometry.rowResetInstanceCount,
|
|
2553
|
+
0,
|
|
2554
|
+
geometry.rowResetInstanceCount
|
|
2555
|
+
);
|
|
2246
2556
|
}
|
|
2247
2557
|
if (geometry.rectangleBuffer && geometry.selectionInstanceCount > 0) {
|
|
2248
2558
|
pass.setPipeline(this.#rectanglePipeline);
|
|
@@ -2278,26 +2588,7 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2278
2588
|
pass.draw(6, cursorInstanceCount, 0, cursorInstanceStart);
|
|
2279
2589
|
}
|
|
2280
2590
|
pass.end();
|
|
2281
|
-
const
|
|
2282
|
-
label: `Ghostty custom shader pass ${id}`,
|
|
2283
|
-
colorAttachments: [
|
|
2284
|
-
{
|
|
2285
|
-
view: surface2.context.getCurrentTexture().createView(),
|
|
2286
|
-
clearValue: {
|
|
2287
|
-
r: view.theme.background[0],
|
|
2288
|
-
g: view.theme.background[1],
|
|
2289
|
-
b: view.theme.background[2],
|
|
2290
|
-
a: 1
|
|
2291
|
-
},
|
|
2292
|
-
loadOp: "clear",
|
|
2293
|
-
storeOp: "store"
|
|
2294
|
-
}
|
|
2295
|
-
]
|
|
2296
|
-
});
|
|
2297
|
-
postProcessPass.setPipeline(this.#postProcessPipeline);
|
|
2298
|
-
postProcessPass.setBindGroup(0, surface2.postProcessBindGroup);
|
|
2299
|
-
postProcessPass.draw(3);
|
|
2300
|
-
postProcessPass.end();
|
|
2591
|
+
const effectPassCount = this.#encodeEffectStack(surface2, view, encoder);
|
|
2301
2592
|
surface2.sceneValid = true;
|
|
2302
2593
|
if (!this.#performanceMeasurementEnabled) return void 0;
|
|
2303
2594
|
const monoUploadsAfter = this.#monoAtlas.uploadMetrics();
|
|
@@ -2311,8 +2602,8 @@ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
|
|
|
2311
2602
|
geometryCacheHits: Number(cacheHit),
|
|
2312
2603
|
geometryCacheMisses: Number(!cacheHit),
|
|
2313
2604
|
canvasPixels: viewportWidth * viewportHeight,
|
|
2314
|
-
renderPasses:
|
|
2315
|
-
drawCalls: Number(geometry.
|
|
2605
|
+
renderPasses: 1 + effectPassCount,
|
|
2606
|
+
drawCalls: Number(geometry.rowResetInstanceCount > 0) + Number(geometry.backgroundInstanceCount > geometry.rowResetInstanceCount) + Number(geometry.selectionInstanceCount > 0) + Number(geometry.glyphInstanceCount > 0) + Number(geometry.colorGlyphInstanceCount > 0) + Number(geometry.fallbackGlyphInstanceCount > 0) + Number(geometry.decorationInstanceCount > 0) + Number(cursorInstanceCount > 0) + effectPassCount,
|
|
2316
2607
|
rectangleVertices: (geometry.rectangleInstanceCount + cursorInstanceCount) * 6,
|
|
2317
2608
|
monoGlyphVertices: geometry.glyphInstanceCount * 6,
|
|
2318
2609
|
colorGlyphVertices: geometry.colorGlyphInstanceCount * 6,
|
|
@@ -2438,6 +2729,7 @@ var forceCanvasFallback = false;
|
|
|
2438
2729
|
var partialRenderingEnabled = true;
|
|
2439
2730
|
var pendingFrameCreditBytes = 0;
|
|
2440
2731
|
var frameCreditTimer;
|
|
2732
|
+
var shaderAnimationScheduled = false;
|
|
2441
2733
|
var performanceMeasurement;
|
|
2442
2734
|
function appendPerformanceSample(samples, value) {
|
|
2443
2735
|
if (samples.length < MAX_PERFORMANCE_SAMPLES) samples.push(value);
|
|
@@ -2762,6 +3054,7 @@ async function recoverDevice(info) {
|
|
|
2762
3054
|
next.resize(id, mount2.size);
|
|
2763
3055
|
invalidateFull(id);
|
|
2764
3056
|
}
|
|
3057
|
+
scheduleShaderAnimation();
|
|
2765
3058
|
postToRenderer({ type: "renderer-status", backend: next.kind, recovered: true });
|
|
2766
3059
|
} catch (error) {
|
|
2767
3060
|
console.error("[terminal-renderer] WebGPU recovery failed", error);
|
|
@@ -2785,6 +3078,23 @@ function scheduleFlush() {
|
|
|
2785
3078
|
if (typeof self.requestAnimationFrame === "function") self.requestAnimationFrame(run);
|
|
2786
3079
|
else setTimeout(run, 8);
|
|
2787
3080
|
}
|
|
3081
|
+
function shaderAnimationActive(surfaceId, presentation) {
|
|
3082
|
+
return renderer?.kind === "webgpu" && mounts.has(surfaceId) && !occluded.has(surfaceId) && presentation.focused && terminalEffectsNeedAnimation(presentation.effects);
|
|
3083
|
+
}
|
|
3084
|
+
function scheduleShaderAnimation() {
|
|
3085
|
+
if (shaderAnimationScheduled || ![...surfaces].some(([surfaceId, presentation]) => shaderAnimationActive(surfaceId, presentation)))
|
|
3086
|
+
return;
|
|
3087
|
+
shaderAnimationScheduled = true;
|
|
3088
|
+
const run = () => {
|
|
3089
|
+
shaderAnimationScheduled = false;
|
|
3090
|
+
for (const [surfaceId, presentation] of surfaces) {
|
|
3091
|
+
if (shaderAnimationActive(surfaceId, presentation)) markDirty(surfaceId);
|
|
3092
|
+
}
|
|
3093
|
+
scheduleShaderAnimation();
|
|
3094
|
+
};
|
|
3095
|
+
if (typeof self.requestAnimationFrame === "function") self.requestAnimationFrame(run);
|
|
3096
|
+
else setTimeout(run, 16);
|
|
3097
|
+
}
|
|
2788
3098
|
async function flush() {
|
|
2789
3099
|
const backend = await ensureRenderer();
|
|
2790
3100
|
const ids = [...dirty].filter((id) => !occluded.has(id));
|
|
@@ -2888,6 +3198,7 @@ async function mount(surfaceId, sessionHandle, canvas) {
|
|
|
2888
3198
|
backend.mount(surfaceId, canvas);
|
|
2889
3199
|
backend.resize(surfaceId, entry.size);
|
|
2890
3200
|
invalidateFull(surfaceId);
|
|
3201
|
+
scheduleShaderAnimation();
|
|
2891
3202
|
}
|
|
2892
3203
|
function applyFrame(packet) {
|
|
2893
3204
|
const active = performanceMeasurement;
|
|
@@ -3174,6 +3485,7 @@ self.onmessage = (event) => {
|
|
|
3174
3485
|
invalidateFull(surfaceId);
|
|
3175
3486
|
}
|
|
3176
3487
|
}
|
|
3488
|
+
scheduleShaderAnimation();
|
|
3177
3489
|
} else if (message.type === "selection") {
|
|
3178
3490
|
if (message.surfaceId) {
|
|
3179
3491
|
surface(message.surfaceId, message.sessionHandle).selection = message.selection;
|
|
@@ -3201,11 +3513,13 @@ self.onmessage = (event) => {
|
|
|
3201
3513
|
cursorBlinkTimers.delete(surfaceId);
|
|
3202
3514
|
}
|
|
3203
3515
|
}
|
|
3516
|
+
scheduleShaderAnimation();
|
|
3204
3517
|
} else if (message.type === "focus") {
|
|
3205
3518
|
const value = surface(message.surfaceId, message.sessionHandle);
|
|
3206
3519
|
value.focused = Boolean(message.focused);
|
|
3207
3520
|
scheduleCursorBlink(message.surfaceId, value.focused);
|
|
3208
3521
|
invalidateRows(message.surfaceId, [snapshot(message.sessionHandle).cursor.y]);
|
|
3522
|
+
scheduleShaderAnimation();
|
|
3209
3523
|
} else if (message.type === "cursor-frozen") {
|
|
3210
3524
|
const value = surfaces.get(message.surfaceId);
|
|
3211
3525
|
if (!value) return;
|