@vibecook/ghosttea-react 0.8.0 → 0.9.1
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 +56 -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 +23 -4
- package/dist/workspace/Workspace.d.ts.map +1 -1
- package/dist/workspace/Workspace.js +30 -8
- 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
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
/// <reference types="@webgpu/types" />
|
|
2
2
|
import { CursorStyle, GlyphFormat } from "@vibecook/ghosttea-frame";
|
|
3
|
+
import { emptyRenderMetrics } from "../performance.js";
|
|
3
4
|
import { CELL_WIDTH, LINE_HEIGHT, ORIGIN_X, ORIGIN_Y, effectiveCursorStyle, renderedSizeChanged, } from "./types.js";
|
|
4
5
|
import { graphemeCellWidth, splitGraphemes } from "../cell-width.js";
|
|
5
6
|
import { rowsForDamage } from "./render-damage.js";
|
|
7
|
+
import { SHADER_EFFECT_WGSL } from "./shader-effects.js";
|
|
6
8
|
const ATLAS_SIZE = 2048;
|
|
7
9
|
const GEOMETRY_CACHE_LIMIT = 8;
|
|
8
10
|
const FONT_STACK = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace";
|
|
11
|
+
const EFFECT_MODES = {
|
|
12
|
+
"ghosttea:better-crt": 1,
|
|
13
|
+
"ghosttea:crt": 2,
|
|
14
|
+
"ghosttea:vhs": 3,
|
|
15
|
+
"ghosttea:sparks-from-fire": 4,
|
|
16
|
+
};
|
|
17
|
+
export function effectIntermediateTextureCount(effectPassCount) {
|
|
18
|
+
return Math.min(2, Math.max(0, Math.trunc(effectPassCount) - 1));
|
|
19
|
+
}
|
|
9
20
|
const RECT_SHADER = /* wgsl */ `
|
|
10
21
|
struct VertexOutput {
|
|
11
22
|
@builtin(position) position: vec4f,
|
|
@@ -130,54 +141,13 @@ struct VertexOutput {
|
|
|
130
141
|
return textureSample(atlas, atlas_sampler, input.uv);
|
|
131
142
|
}
|
|
132
143
|
`;
|
|
133
|
-
// WebGPU port of ~/.config/ghostty/shaders/bettercrt.glsl. Ghostty applies
|
|
134
|
-
// custom shaders to the completed terminal image, so this must remain a
|
|
135
|
-
// separate pass rather than being approximated per glyph.
|
|
136
|
-
const BETTER_CRT_SHADER = /* wgsl */ `
|
|
137
|
-
@group(0) @binding(0) var terminal_image: texture_2d<f32>;
|
|
138
|
-
@group(0) @binding(1) var terminal_sampler: sampler;
|
|
139
|
-
struct PostProcessConfig {
|
|
140
|
-
// A vec4 keeps the WGSL uniform layout equal to the host's 16-byte buffer.
|
|
141
|
-
enabled: vec4u,
|
|
142
|
-
}
|
|
143
|
-
@group(0) @binding(2) var<uniform> post_process: PostProcessConfig;
|
|
144
|
-
|
|
145
|
-
@vertex fn vertex_main(@builtin(vertex_index) vertex_index: u32) -> @builtin(position) vec4f {
|
|
146
|
-
let positions = array<vec2f, 3>(
|
|
147
|
-
vec2f(-1.0, -1.0),
|
|
148
|
-
vec2f(3.0, -1.0),
|
|
149
|
-
vec2f(-1.0, 3.0),
|
|
150
|
-
);
|
|
151
|
-
return vec4f(positions[vertex_index], 0.0, 1.0);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
@fragment fn fragment_main(@builtin(position) position: vec4f) -> @location(0) vec4f {
|
|
155
|
-
let dimensions = vec2f(textureDimensions(terminal_image));
|
|
156
|
-
var uv = position.xy / dimensions;
|
|
157
|
-
if (post_process.enabled.x == 0u) {
|
|
158
|
-
return textureSample(terminal_image, terminal_sampler, uv);
|
|
159
|
-
}
|
|
160
|
-
var dc = abs(vec2f(0.5) - uv);
|
|
161
|
-
dc *= dc;
|
|
162
|
-
|
|
163
|
-
// These constants are the active Ghostty shader's warp = 0.25 and
|
|
164
|
-
// scan = 0.50 values.
|
|
165
|
-
uv.x -= 0.5;
|
|
166
|
-
uv.x *= 1.0 + dc.y * 0.075;
|
|
167
|
-
uv.x += 0.5;
|
|
168
|
-
uv.y -= 0.5;
|
|
169
|
-
uv.y *= 1.0 + dc.x * 0.10;
|
|
170
|
-
uv.y += 0.5;
|
|
171
|
-
|
|
172
|
-
let scanline = abs(sin(position.y) * 0.125);
|
|
173
|
-
let color = textureSample(terminal_image, terminal_sampler, uv).rgb;
|
|
174
|
-
return vec4f(mix(color, vec3f(0.0), scanline), 1.0);
|
|
175
|
-
}
|
|
176
|
-
`;
|
|
177
144
|
const premultipliedBlend = {
|
|
178
145
|
color: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" },
|
|
179
146
|
alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" },
|
|
180
147
|
};
|
|
148
|
+
function premultipliedClear(color) {
|
|
149
|
+
return { r: color[0] * color[3], g: color[1] * color[3], b: color[2] * color[3], a: color[3] };
|
|
150
|
+
}
|
|
181
151
|
class DynamicVertexBuffer {
|
|
182
152
|
device;
|
|
183
153
|
label;
|
|
@@ -479,16 +449,20 @@ function geometryCacheKey(view, rows, hasNativeRows, atlasGenerations) {
|
|
|
479
449
|
[
|
|
480
450
|
...view.theme.background,
|
|
481
451
|
...view.theme.foreground,
|
|
452
|
+
...view.theme.cursor,
|
|
453
|
+
...(view.theme.cursorText ?? view.theme.background),
|
|
482
454
|
...view.theme.selection,
|
|
483
455
|
...view.theme.selectionForeground,
|
|
484
456
|
].join(","),
|
|
457
|
+
view.theme.backgroundOpacityCells ? "opacity-cells" : "opaque-cells",
|
|
485
458
|
selection,
|
|
459
|
+
effectiveCursorStyle(view) === CursorStyle.Block ? `${view.cursor.x},${view.cursor.y}` : "-",
|
|
486
460
|
atlasGenerations.join(","),
|
|
487
461
|
].join("|");
|
|
488
462
|
}
|
|
489
463
|
function pushCursorVertices(vertices, view, renderRowSet, scale, viewportWidth, viewportHeight) {
|
|
490
464
|
const cursorStyle = effectiveCursorStyle(view);
|
|
491
|
-
if (cursorStyle === null || !renderRowSet.has(view.cursor.y))
|
|
465
|
+
if (cursorStyle === null || cursorStyle === CursorStyle.Block || !renderRowSet.has(view.cursor.y))
|
|
492
466
|
return;
|
|
493
467
|
const x = (ORIGIN_X + view.cursor.x * CELL_WIDTH) * scale;
|
|
494
468
|
const y = (ORIGIN_Y + view.cursor.y * LINE_HEIGHT) * scale;
|
|
@@ -672,6 +646,17 @@ function selectionContains(selection, row, column) {
|
|
|
672
646
|
const last = row === end.row ? end.column : Number.MAX_SAFE_INTEGER;
|
|
673
647
|
return column >= first && column <= last;
|
|
674
648
|
}
|
|
649
|
+
function blockCursorContains(view, row, column, span = 1) {
|
|
650
|
+
return (effectiveCursorStyle(view) === CursorStyle.Block &&
|
|
651
|
+
view.cursor.y === row &&
|
|
652
|
+
view.cursor.x >= column &&
|
|
653
|
+
view.cursor.x < column + Math.max(1, span));
|
|
654
|
+
}
|
|
655
|
+
function pushBlockCursorBackground(vertices, view, renderRows, scale, viewportWidth, viewportHeight) {
|
|
656
|
+
if (effectiveCursorStyle(view) !== CursorStyle.Block || !renderRows.includes(view.cursor.y))
|
|
657
|
+
return;
|
|
658
|
+
pushRectangle(vertices, (ORIGIN_X + view.cursor.x * CELL_WIDTH) * scale, (ORIGIN_Y + view.cursor.y * LINE_HEIGHT) * scale, CELL_WIDTH * scale, LINE_HEIGHT * scale, view.theme.cursor, viewportWidth, viewportHeight);
|
|
659
|
+
}
|
|
675
660
|
function resolveStyle(style, theme) {
|
|
676
661
|
let foreground = style?.foreground ? rgb(style.foreground) : theme.foreground;
|
|
677
662
|
let background = style?.background ? rgb(style.background) : null;
|
|
@@ -680,6 +665,9 @@ function resolveStyle(style, theme) {
|
|
|
680
665
|
foreground = background ?? theme.background;
|
|
681
666
|
background = originalForeground;
|
|
682
667
|
}
|
|
668
|
+
if (background && theme.backgroundOpacityCells) {
|
|
669
|
+
background = [background[0], background[1], background[2], theme.background[3]];
|
|
670
|
+
}
|
|
683
671
|
if (style?.faint)
|
|
684
672
|
foreground = [foreground[0], foreground[1], foreground[2], foreground[3] * 0.55];
|
|
685
673
|
return {
|
|
@@ -822,11 +810,12 @@ export class WebGpuTerminalRenderer {
|
|
|
822
810
|
kind = "webgpu";
|
|
823
811
|
#surfaces = new Map();
|
|
824
812
|
#rectanglePipeline;
|
|
813
|
+
#rectangleOverwritePipeline;
|
|
825
814
|
#glyphPipeline;
|
|
826
815
|
#fallbackGlyphPipeline;
|
|
827
816
|
#colorGlyphPipeline;
|
|
828
|
-
#
|
|
829
|
-
#
|
|
817
|
+
#effectPipeline;
|
|
818
|
+
#effectSampler;
|
|
830
819
|
#monoAtlas;
|
|
831
820
|
#colorAtlas;
|
|
832
821
|
#fallbackAtlas;
|
|
@@ -844,7 +833,10 @@ export class WebGpuTerminalRenderer {
|
|
|
844
833
|
label: "terminal color glyph shader",
|
|
845
834
|
code: COLOR_GLYPH_SHADER,
|
|
846
835
|
});
|
|
847
|
-
const
|
|
836
|
+
const effectModule = device.createShaderModule({
|
|
837
|
+
label: "Ghosttea shader effect registry",
|
|
838
|
+
code: SHADER_EFFECT_WGSL,
|
|
839
|
+
});
|
|
848
840
|
this.#rectanglePipeline = device.createRenderPipeline({
|
|
849
841
|
label: "terminal rectangle pipeline",
|
|
850
842
|
layout: "auto",
|
|
@@ -871,6 +863,28 @@ export class WebGpuTerminalRenderer {
|
|
|
871
863
|
},
|
|
872
864
|
primitive: { topology: "triangle-list" },
|
|
873
865
|
});
|
|
866
|
+
this.#rectangleOverwritePipeline = device.createRenderPipeline({
|
|
867
|
+
label: "terminal row-reset overwrite pipeline",
|
|
868
|
+
layout: "auto",
|
|
869
|
+
vertex: {
|
|
870
|
+
module: rectangleModule,
|
|
871
|
+
entryPoint: "vertex_main",
|
|
872
|
+
buffers: [
|
|
873
|
+
{
|
|
874
|
+
arrayStride: 40,
|
|
875
|
+
stepMode: "instance",
|
|
876
|
+
attributes: [
|
|
877
|
+
{ shaderLocation: 0, offset: 0, format: "float32x2" },
|
|
878
|
+
{ shaderLocation: 1, offset: 8, format: "float32x2" },
|
|
879
|
+
{ shaderLocation: 2, offset: 16, format: "float32x2" },
|
|
880
|
+
{ shaderLocation: 3, offset: 24, format: "float32x4" },
|
|
881
|
+
],
|
|
882
|
+
},
|
|
883
|
+
],
|
|
884
|
+
},
|
|
885
|
+
fragment: { module: rectangleModule, entryPoint: "fragment_main", targets: [{ format }] },
|
|
886
|
+
primitive: { topology: "triangle-list" },
|
|
887
|
+
});
|
|
874
888
|
this.#glyphPipeline = device.createRenderPipeline({
|
|
875
889
|
label: "terminal glyph pipeline",
|
|
876
890
|
layout: "auto",
|
|
@@ -942,15 +956,15 @@ export class WebGpuTerminalRenderer {
|
|
|
942
956
|
},
|
|
943
957
|
primitive: { topology: "triangle-list" },
|
|
944
958
|
});
|
|
945
|
-
this.#
|
|
946
|
-
label: "
|
|
959
|
+
this.#effectPipeline = device.createRenderPipeline({
|
|
960
|
+
label: "Ghosttea ordered shader effect pipeline",
|
|
947
961
|
layout: "auto",
|
|
948
|
-
vertex: { module:
|
|
949
|
-
fragment: { module:
|
|
962
|
+
vertex: { module: effectModule, entryPoint: "vertex_main" },
|
|
963
|
+
fragment: { module: effectModule, entryPoint: "fragment_main", targets: [{ format }] },
|
|
950
964
|
primitive: { topology: "triangle-list" },
|
|
951
965
|
});
|
|
952
|
-
this.#
|
|
953
|
-
label: "
|
|
966
|
+
this.#effectSampler = device.createSampler({
|
|
967
|
+
label: "Ghosttea shader effect sampler",
|
|
954
968
|
addressModeU: "clamp-to-edge",
|
|
955
969
|
addressModeV: "clamp-to-edge",
|
|
956
970
|
minFilter: "linear",
|
|
@@ -984,18 +998,15 @@ export class WebGpuTerminalRenderer {
|
|
|
984
998
|
if (!context)
|
|
985
999
|
throw new Error("WebGPU canvas context unavailable");
|
|
986
1000
|
const sceneTexture = this.#createSceneTexture(canvas.width, canvas.height);
|
|
987
|
-
const postProcessConfigBuffer = this.device.createBuffer({
|
|
988
|
-
label: `Ghostty custom shader configuration ${id}`,
|
|
989
|
-
size: 16,
|
|
990
|
-
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
991
|
-
});
|
|
992
1001
|
const surface = {
|
|
993
1002
|
canvas,
|
|
994
1003
|
context,
|
|
995
1004
|
sceneTexture,
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1005
|
+
effectTextures: [],
|
|
1006
|
+
effectPasses: [],
|
|
1007
|
+
effectSignature: "",
|
|
1008
|
+
effectFrame: 0,
|
|
1009
|
+
lastEffectTime: 0,
|
|
999
1010
|
width: 1,
|
|
1000
1011
|
height: 1,
|
|
1001
1012
|
dpr: 1,
|
|
@@ -1017,7 +1028,10 @@ export class WebGpuTerminalRenderer {
|
|
|
1017
1028
|
return;
|
|
1018
1029
|
surface.context.unconfigure();
|
|
1019
1030
|
surface.sceneTexture.destroy();
|
|
1020
|
-
surface.
|
|
1031
|
+
for (const texture of surface.effectTextures)
|
|
1032
|
+
texture.destroy();
|
|
1033
|
+
for (const pass of surface.effectPasses)
|
|
1034
|
+
pass.configBuffer.destroy();
|
|
1021
1035
|
surface.rectangleBuffer.destroy();
|
|
1022
1036
|
surface.glyphBuffer.destroy();
|
|
1023
1037
|
surface.colorGlyphBuffer.destroy();
|
|
@@ -1041,8 +1055,14 @@ export class WebGpuTerminalRenderer {
|
|
|
1041
1055
|
#configure(surface) {
|
|
1042
1056
|
surface.context.configure({ device: this.device, format: this.format, alphaMode: "premultiplied" });
|
|
1043
1057
|
surface.sceneTexture.destroy();
|
|
1058
|
+
for (const texture of surface.effectTextures)
|
|
1059
|
+
texture.destroy();
|
|
1060
|
+
for (const pass of surface.effectPasses)
|
|
1061
|
+
pass.configBuffer.destroy();
|
|
1044
1062
|
surface.sceneTexture = this.#createSceneTexture(surface.canvas.width, surface.canvas.height);
|
|
1045
|
-
surface.
|
|
1063
|
+
surface.effectTextures = [];
|
|
1064
|
+
surface.effectPasses = [];
|
|
1065
|
+
surface.effectSignature = "";
|
|
1046
1066
|
clearGeometryCache(surface);
|
|
1047
1067
|
surface.sceneValid = false;
|
|
1048
1068
|
}
|
|
@@ -1054,26 +1074,124 @@ export class WebGpuTerminalRenderer {
|
|
|
1054
1074
|
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
|
|
1055
1075
|
});
|
|
1056
1076
|
}
|
|
1057
|
-
#
|
|
1077
|
+
#createEffectTexture(width, height, suffix) {
|
|
1078
|
+
return this.device.createTexture({
|
|
1079
|
+
label: `terminal shader ping-pong ${suffix}`,
|
|
1080
|
+
size: [Math.max(1, width), Math.max(1, height)],
|
|
1081
|
+
format: this.format,
|
|
1082
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
#createEffectBindGroup(texture, configBuffer) {
|
|
1058
1086
|
return this.device.createBindGroup({
|
|
1059
|
-
label: "
|
|
1060
|
-
layout: this.#
|
|
1087
|
+
label: "Ghosttea shader effect bindings",
|
|
1088
|
+
layout: this.#effectPipeline.getBindGroupLayout(0),
|
|
1061
1089
|
entries: [
|
|
1062
1090
|
{ binding: 0, resource: texture.createView() },
|
|
1063
|
-
{ binding: 1, resource: this.#
|
|
1091
|
+
{ binding: 1, resource: this.#effectSampler },
|
|
1064
1092
|
{ binding: 2, resource: { buffer: configBuffer } },
|
|
1065
1093
|
],
|
|
1066
1094
|
});
|
|
1067
1095
|
}
|
|
1096
|
+
#shaderStack(view) {
|
|
1097
|
+
const configured = view.effects?.shaderEffects?.filter((id) => EFFECT_MODES[id] !== undefined) ?? [];
|
|
1098
|
+
if (configured.length > 0)
|
|
1099
|
+
return [...configured];
|
|
1100
|
+
return view.effects?.postProcess === "better-crt" ? ["ghosttea:better-crt"] : [];
|
|
1101
|
+
}
|
|
1102
|
+
#ensureEffectPasses(surface, stack) {
|
|
1103
|
+
const signature = stack.join("\0") || "ghosttea:blit";
|
|
1104
|
+
if (surface.effectSignature === signature)
|
|
1105
|
+
return;
|
|
1106
|
+
for (const pass of surface.effectPasses)
|
|
1107
|
+
pass.configBuffer.destroy();
|
|
1108
|
+
const modes = stack.length > 0 ? stack.map((id) => EFFECT_MODES[id]) : [0];
|
|
1109
|
+
const intermediateCount = effectIntermediateTextureCount(modes.length);
|
|
1110
|
+
while (surface.effectTextures.length > intermediateCount)
|
|
1111
|
+
surface.effectTextures.pop().destroy();
|
|
1112
|
+
while (surface.effectTextures.length < intermediateCount) {
|
|
1113
|
+
const suffix = surface.effectTextures.length === 0 ? "A" : "B";
|
|
1114
|
+
surface.effectTextures.push(this.#createEffectTexture(surface.canvas.width, surface.canvas.height, suffix));
|
|
1115
|
+
}
|
|
1116
|
+
surface.effectPasses = modes.map((mode, index) => {
|
|
1117
|
+
const configBuffer = this.device.createBuffer({
|
|
1118
|
+
label: `Ghosttea shader effect configuration ${index}`,
|
|
1119
|
+
size: 48,
|
|
1120
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
1121
|
+
});
|
|
1122
|
+
const inputTexture = index === 0 ? surface.sceneTexture : surface.effectTextures[(index - 1) % 2];
|
|
1123
|
+
return {
|
|
1124
|
+
mode,
|
|
1125
|
+
configBuffer,
|
|
1126
|
+
bindGroup: this.#createEffectBindGroup(inputTexture, configBuffer),
|
|
1127
|
+
};
|
|
1128
|
+
});
|
|
1129
|
+
surface.effectSignature = signature;
|
|
1130
|
+
surface.effectFrame = 0;
|
|
1131
|
+
surface.lastEffectTime = 0;
|
|
1132
|
+
}
|
|
1133
|
+
#encodeEffectStack(surface, view, encoder) {
|
|
1134
|
+
const stack = this.#shaderStack(view);
|
|
1135
|
+
this.#ensureEffectPasses(surface, stack);
|
|
1136
|
+
const now = view.effects?.animate ? performance.now() / 1_000 : 0;
|
|
1137
|
+
const delta = surface.lastEffectTime > 0 ? Math.min(0.1, Math.max(0, now - surface.lastEffectTime)) : 0;
|
|
1138
|
+
surface.lastEffectTime = now;
|
|
1139
|
+
const passCount = surface.effectPasses.length;
|
|
1140
|
+
for (let index = 0; index < passCount; index += 1) {
|
|
1141
|
+
const effectPass = surface.effectPasses[index];
|
|
1142
|
+
const storage = new ArrayBuffer(48);
|
|
1143
|
+
const values = new DataView(storage);
|
|
1144
|
+
values.setUint32(0, effectPass.mode, true);
|
|
1145
|
+
values.setUint32(4, surface.effectFrame, true);
|
|
1146
|
+
values.setUint32(8, index, true);
|
|
1147
|
+
values.setUint32(12, passCount, true);
|
|
1148
|
+
values.setFloat32(16, surface.canvas.width, true);
|
|
1149
|
+
values.setFloat32(20, surface.canvas.height, true);
|
|
1150
|
+
values.setFloat32(24, now, true);
|
|
1151
|
+
values.setFloat32(28, delta, true);
|
|
1152
|
+
values.setFloat32(32, (ORIGIN_X + view.cursor.x * CELL_WIDTH) * surface.dpr, true);
|
|
1153
|
+
values.setFloat32(36, (ORIGIN_Y + view.cursor.y * LINE_HEIGHT) * surface.dpr, true);
|
|
1154
|
+
values.setFloat32(40, view.cursor.visible ? 1 : 0, true);
|
|
1155
|
+
values.setFloat32(44, view.cursor.style, true);
|
|
1156
|
+
this.device.queue.writeBuffer(effectPass.configBuffer, 0, storage);
|
|
1157
|
+
const last = index === passCount - 1;
|
|
1158
|
+
const output = last
|
|
1159
|
+
? surface.context.getCurrentTexture().createView()
|
|
1160
|
+
: surface.effectTextures[index % 2].createView();
|
|
1161
|
+
const pass = encoder.beginRenderPass({
|
|
1162
|
+
label: `Ghosttea shader effect pass ${index + 1}/${passCount}`,
|
|
1163
|
+
colorAttachments: [
|
|
1164
|
+
{
|
|
1165
|
+
view: output,
|
|
1166
|
+
clearValue: { r: 0, g: 0, b: 0, a: 0 },
|
|
1167
|
+
loadOp: "clear",
|
|
1168
|
+
storeOp: "store",
|
|
1169
|
+
},
|
|
1170
|
+
],
|
|
1171
|
+
});
|
|
1172
|
+
pass.setPipeline(this.#effectPipeline);
|
|
1173
|
+
pass.setBindGroup(0, effectPass.bindGroup);
|
|
1174
|
+
pass.draw(3);
|
|
1175
|
+
pass.end();
|
|
1176
|
+
}
|
|
1177
|
+
surface.effectFrame += 1;
|
|
1178
|
+
return passCount;
|
|
1179
|
+
}
|
|
1180
|
+
#encodeEffectsOnly(surface, view, encoder) {
|
|
1181
|
+
const effectPassCount = this.#encodeEffectStack(surface, view, encoder);
|
|
1182
|
+
if (!this.#performanceMeasurementEnabled)
|
|
1183
|
+
return undefined;
|
|
1184
|
+
return {
|
|
1185
|
+
...emptyRenderMetrics(),
|
|
1186
|
+
canvasPixels: surface.canvas.width * surface.canvas.height,
|
|
1187
|
+
renderPasses: effectPassCount,
|
|
1188
|
+
drawCalls: effectPassCount,
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1068
1191
|
render(id, view) {
|
|
1069
1192
|
const surface = this.#surfaces.get(id);
|
|
1070
1193
|
if (!surface)
|
|
1071
1194
|
return;
|
|
1072
|
-
const postProcessEnabled = view.effects?.postProcess === "better-crt";
|
|
1073
|
-
if (surface.postProcessEnabled !== postProcessEnabled) {
|
|
1074
|
-
surface.postProcessEnabled = postProcessEnabled;
|
|
1075
|
-
this.device.queue.writeBuffer(surface.postProcessConfigBuffer, 0, new Uint32Array([Number(postProcessEnabled), 0, 0, 0]));
|
|
1076
|
-
}
|
|
1077
1195
|
const encoder = this.device.createCommandEncoder({ label: `terminal frame ${id}` });
|
|
1078
1196
|
const metrics = view.damage?.geometryChanged === false
|
|
1079
1197
|
? this.#encodeCachedRender(id, view, encoder)
|
|
@@ -1090,12 +1208,6 @@ export class WebGpuTerminalRenderer {
|
|
|
1090
1208
|
const encoder = this.device.createCommandEncoder({ label: `terminal frame batch (${active.length} panes)` });
|
|
1091
1209
|
const byId = new Map();
|
|
1092
1210
|
for (const { id, view } of active) {
|
|
1093
|
-
const surface = this.#surfaces.get(id);
|
|
1094
|
-
const postProcessEnabled = view.effects?.postProcess === "better-crt";
|
|
1095
|
-
if (surface.postProcessEnabled !== postProcessEnabled) {
|
|
1096
|
-
surface.postProcessEnabled = postProcessEnabled;
|
|
1097
|
-
this.device.queue.writeBuffer(surface.postProcessConfigBuffer, 0, new Uint32Array([Number(postProcessEnabled), 0, 0, 0]));
|
|
1098
|
-
}
|
|
1099
1211
|
byId.set(id, view.damage?.geometryChanged === false
|
|
1100
1212
|
? this.#encodeCachedRender(id, view, encoder)
|
|
1101
1213
|
: this.#encodeRender(id, view, encoder));
|
|
@@ -1138,12 +1250,10 @@ export class WebGpuTerminalRenderer {
|
|
|
1138
1250
|
const bottom = row === rowCount - 1
|
|
1139
1251
|
? viewportHeight
|
|
1140
1252
|
: Math.min(viewportHeight, (ORIGIN_Y + (row + 1) * LINE_HEIGHT) * scale);
|
|
1141
|
-
pushRectangle(rectangleVertices, 0, top, viewportWidth, Math.max(0, bottom - top),
|
|
1142
|
-
// The rectangle pipeline blends. Force the row reset opaque so it
|
|
1143
|
-
// replaces stale scene pixels even when a theme carries alpha.
|
|
1144
|
-
[view.theme.background[0], view.theme.background[1], view.theme.background[2], 1], viewportWidth, viewportHeight);
|
|
1253
|
+
pushRectangle(rectangleVertices, 0, top, viewportWidth, Math.max(0, bottom - top), view.theme.background, viewportWidth, viewportHeight);
|
|
1145
1254
|
}
|
|
1146
1255
|
}
|
|
1256
|
+
const rowResetInstanceCount = rectangleVertices.length / 10;
|
|
1147
1257
|
for (const row of renderRows) {
|
|
1148
1258
|
for (const run of view.nativeStyleRows[row] ?? []) {
|
|
1149
1259
|
const style = styleFor(run.styleId);
|
|
@@ -1164,6 +1274,7 @@ export class WebGpuTerminalRenderer {
|
|
|
1164
1274
|
pushRectangle(rectangleVertices, (ORIGIN_X + first * CELL_WIDTH) * scale, (ORIGIN_Y + row * LINE_HEIGHT) * scale, Math.max(1, last - first + 1) * CELL_WIDTH * scale, LINE_HEIGHT * scale, view.theme.selection, viewportWidth, viewportHeight);
|
|
1165
1275
|
}
|
|
1166
1276
|
}
|
|
1277
|
+
pushBlockCursorBackground(rectangleVertices, view, renderRows, scale, viewportWidth, viewportHeight);
|
|
1167
1278
|
const selectionInstanceCount = rectangleVertices.length / 10 - backgroundInstanceCount;
|
|
1168
1279
|
if (!hasNativeRows) {
|
|
1169
1280
|
this.#fallbackAtlas.prepare(renderRows.flatMap((row) => splitGraphemes(view.rows[row] ?? "")), scale);
|
|
@@ -1179,7 +1290,11 @@ export class WebGpuTerminalRenderer {
|
|
|
1179
1290
|
if (style.invisible)
|
|
1180
1291
|
continue;
|
|
1181
1292
|
const isSelected = selectionContains(selection, row, instance.cellStart);
|
|
1182
|
-
const foreground =
|
|
1293
|
+
const foreground = blockCursorContains(view, row, instance.cellStart, instance.cellSpan)
|
|
1294
|
+
? (view.theme.cursorText ?? view.theme.background)
|
|
1295
|
+
: isSelected
|
|
1296
|
+
? view.theme.selectionForeground
|
|
1297
|
+
: style.foreground;
|
|
1183
1298
|
const boxDrawing = boxCells.get(instance.cellStart);
|
|
1184
1299
|
if (boxDrawing) {
|
|
1185
1300
|
let backdrop = style.background ?? view.theme.background;
|
|
@@ -1211,9 +1326,11 @@ export class WebGpuTerminalRenderer {
|
|
|
1211
1326
|
const glyph = this.#fallbackAtlas.glyph(grapheme, scale);
|
|
1212
1327
|
const cells = glyph?.cells ?? graphemeCellWidth(grapheme);
|
|
1213
1328
|
if (glyph) {
|
|
1214
|
-
const foreground =
|
|
1215
|
-
? view.theme.
|
|
1216
|
-
:
|
|
1329
|
+
const foreground = blockCursorContains(view, row, column, cells)
|
|
1330
|
+
? (view.theme.cursorText ?? view.theme.background)
|
|
1331
|
+
: selectionContains(selection, row, column)
|
|
1332
|
+
? view.theme.selectionForeground
|
|
1333
|
+
: view.theme.foreground;
|
|
1217
1334
|
pushGlyph(fallbackGlyphVertices, (ORIGIN_X + column * CELL_WIDTH) * scale, (ORIGIN_Y + row * LINE_HEIGHT) * scale, CELL_WIDTH * cells * scale, LINE_HEIGHT * scale, glyph, foreground, viewportWidth, viewportHeight);
|
|
1218
1335
|
}
|
|
1219
1336
|
column += cells;
|
|
@@ -1244,6 +1361,7 @@ export class WebGpuTerminalRenderer {
|
|
|
1244
1361
|
glyphData: new Float32Array(glyphVertices),
|
|
1245
1362
|
colorGlyphData: new Float32Array(colorGlyphVertices),
|
|
1246
1363
|
fallbackGlyphData: new Float32Array(fallbackGlyphVertices),
|
|
1364
|
+
rowResetInstanceCount,
|
|
1247
1365
|
backgroundInstanceCount,
|
|
1248
1366
|
selectionInstanceCount,
|
|
1249
1367
|
decorationInstanceStart,
|
|
@@ -1298,12 +1416,10 @@ export class WebGpuTerminalRenderer {
|
|
|
1298
1416
|
const bottom = row === rowCount - 1
|
|
1299
1417
|
? viewportHeight
|
|
1300
1418
|
: Math.min(viewportHeight, (ORIGIN_Y + (row + 1) * LINE_HEIGHT) * scale);
|
|
1301
|
-
pushRectangle(rectangleVertices, 0, top, viewportWidth, Math.max(0, bottom - top),
|
|
1302
|
-
// The rectangle pipeline blends. Force the row reset opaque so it
|
|
1303
|
-
// replaces stale scene pixels even when a theme carries alpha.
|
|
1304
|
-
[view.theme.background[0], view.theme.background[1], view.theme.background[2], 1], viewportWidth, viewportHeight);
|
|
1419
|
+
pushRectangle(rectangleVertices, 0, top, viewportWidth, Math.max(0, bottom - top), view.theme.background, viewportWidth, viewportHeight);
|
|
1305
1420
|
}
|
|
1306
1421
|
}
|
|
1422
|
+
const rowResetInstanceCount = rectangleVertices.length / 10;
|
|
1307
1423
|
for (const row of renderRows) {
|
|
1308
1424
|
for (const run of view.nativeStyleRows[row] ?? []) {
|
|
1309
1425
|
const style = styleFor(run.styleId);
|
|
@@ -1324,6 +1440,7 @@ export class WebGpuTerminalRenderer {
|
|
|
1324
1440
|
pushRectangle(rectangleVertices, (ORIGIN_X + first * CELL_WIDTH) * scale, (ORIGIN_Y + row * LINE_HEIGHT) * scale, Math.max(1, last - first + 1) * CELL_WIDTH * scale, LINE_HEIGHT * scale, view.theme.selection, viewportWidth, viewportHeight);
|
|
1325
1441
|
}
|
|
1326
1442
|
}
|
|
1443
|
+
pushBlockCursorBackground(rectangleVertices, view, renderRows, scale, viewportWidth, viewportHeight);
|
|
1327
1444
|
const selectionInstanceCount = rectangleVertices.length / 10 - backgroundInstanceCount;
|
|
1328
1445
|
const hasNativeRows = view.nativeRows.some((row) => row.length > 0);
|
|
1329
1446
|
if (!hasNativeRows) {
|
|
@@ -1340,7 +1457,11 @@ export class WebGpuTerminalRenderer {
|
|
|
1340
1457
|
if (style.invisible)
|
|
1341
1458
|
continue;
|
|
1342
1459
|
const isSelected = selectionContains(selection, row, instance.cellStart);
|
|
1343
|
-
const foreground =
|
|
1460
|
+
const foreground = blockCursorContains(view, row, instance.cellStart, instance.cellSpan)
|
|
1461
|
+
? (view.theme.cursorText ?? view.theme.background)
|
|
1462
|
+
: isSelected
|
|
1463
|
+
? view.theme.selectionForeground
|
|
1464
|
+
: style.foreground;
|
|
1344
1465
|
const boxDrawing = boxCells.get(instance.cellStart);
|
|
1345
1466
|
if (boxDrawing) {
|
|
1346
1467
|
let backdrop = style.background ?? view.theme.background;
|
|
@@ -1375,9 +1496,11 @@ export class WebGpuTerminalRenderer {
|
|
|
1375
1496
|
const glyph = this.#fallbackAtlas.glyph(grapheme, scale);
|
|
1376
1497
|
const cells = glyph?.cells ?? graphemeCellWidth(grapheme);
|
|
1377
1498
|
if (glyph) {
|
|
1378
|
-
const foreground =
|
|
1379
|
-
? view.theme.
|
|
1380
|
-
:
|
|
1499
|
+
const foreground = blockCursorContains(view, row, column, cells)
|
|
1500
|
+
? (view.theme.cursorText ?? view.theme.background)
|
|
1501
|
+
: selectionContains(selection, row, column)
|
|
1502
|
+
? view.theme.selectionForeground
|
|
1503
|
+
: view.theme.foreground;
|
|
1381
1504
|
pushGlyph(fallbackGlyphVertices, (ORIGIN_X + column * CELL_WIDTH) * scale, (ORIGIN_Y + row * LINE_HEIGHT) * scale, CELL_WIDTH * cells * scale, LINE_HEIGHT * scale, glyph, foreground, viewportWidth, viewportHeight);
|
|
1382
1505
|
}
|
|
1383
1506
|
column += cells;
|
|
@@ -1426,7 +1549,7 @@ export class WebGpuTerminalRenderer {
|
|
|
1426
1549
|
pushRectangle(rectangleVertices, x, y + stroke, stroke, Math.max(0, height - stroke * 2), cursorColor, viewportWidth, viewportHeight);
|
|
1427
1550
|
pushRectangle(rectangleVertices, x + width - stroke, y + stroke, stroke, Math.max(0, height - stroke * 2), cursorColor, viewportWidth, viewportHeight);
|
|
1428
1551
|
}
|
|
1429
|
-
else {
|
|
1552
|
+
else if (cursorStyle !== CursorStyle.Block) {
|
|
1430
1553
|
pushRectangle(rectangleVertices, x, y, width, height, cursorColor, viewportWidth, viewportHeight);
|
|
1431
1554
|
}
|
|
1432
1555
|
}
|
|
@@ -1445,21 +1568,21 @@ export class WebGpuTerminalRenderer {
|
|
|
1445
1568
|
colorAttachments: [
|
|
1446
1569
|
{
|
|
1447
1570
|
view: surface.sceneTexture.createView(),
|
|
1448
|
-
clearValue:
|
|
1449
|
-
r: view.theme.background[0],
|
|
1450
|
-
g: view.theme.background[1],
|
|
1451
|
-
b: view.theme.background[2],
|
|
1452
|
-
a: view.theme.background[3],
|
|
1453
|
-
},
|
|
1571
|
+
clearValue: premultipliedClear(view.theme.background),
|
|
1454
1572
|
loadOp: fullRedraw ? "clear" : "load",
|
|
1455
1573
|
storeOp: "store",
|
|
1456
1574
|
},
|
|
1457
1575
|
],
|
|
1458
1576
|
});
|
|
1459
|
-
if (rectangleBuffer &&
|
|
1577
|
+
if (rectangleBuffer && rowResetInstanceCount > 0) {
|
|
1578
|
+
pass.setPipeline(this.#rectangleOverwritePipeline);
|
|
1579
|
+
pass.setVertexBuffer(0, rectangleBuffer);
|
|
1580
|
+
pass.draw(6, rowResetInstanceCount);
|
|
1581
|
+
}
|
|
1582
|
+
if (rectangleBuffer && backgroundInstanceCount > rowResetInstanceCount) {
|
|
1460
1583
|
pass.setPipeline(this.#rectanglePipeline);
|
|
1461
1584
|
pass.setVertexBuffer(0, rectangleBuffer);
|
|
1462
|
-
pass.draw(6, backgroundInstanceCount);
|
|
1585
|
+
pass.draw(6, backgroundInstanceCount - rowResetInstanceCount, 0, rowResetInstanceCount);
|
|
1463
1586
|
}
|
|
1464
1587
|
if (rectangleBuffer && selectionInstanceCount > 0) {
|
|
1465
1588
|
pass.setPipeline(this.#rectanglePipeline);
|
|
@@ -1495,26 +1618,7 @@ export class WebGpuTerminalRenderer {
|
|
|
1495
1618
|
pass.draw(6, cursorInstanceCount, 0, cursorInstanceStart);
|
|
1496
1619
|
}
|
|
1497
1620
|
pass.end();
|
|
1498
|
-
const
|
|
1499
|
-
label: `Ghostty custom shader pass ${id}`,
|
|
1500
|
-
colorAttachments: [
|
|
1501
|
-
{
|
|
1502
|
-
view: surface.context.getCurrentTexture().createView(),
|
|
1503
|
-
clearValue: {
|
|
1504
|
-
r: view.theme.background[0],
|
|
1505
|
-
g: view.theme.background[1],
|
|
1506
|
-
b: view.theme.background[2],
|
|
1507
|
-
a: 1,
|
|
1508
|
-
},
|
|
1509
|
-
loadOp: "clear",
|
|
1510
|
-
storeOp: "store",
|
|
1511
|
-
},
|
|
1512
|
-
],
|
|
1513
|
-
});
|
|
1514
|
-
postProcessPass.setPipeline(this.#postProcessPipeline);
|
|
1515
|
-
postProcessPass.setBindGroup(0, surface.postProcessBindGroup);
|
|
1516
|
-
postProcessPass.draw(3);
|
|
1517
|
-
postProcessPass.end();
|
|
1621
|
+
const effectPassCount = this.#encodeEffectStack(surface, view, encoder);
|
|
1518
1622
|
surface.sceneValid = true;
|
|
1519
1623
|
if (!this.#performanceMeasurementEnabled)
|
|
1520
1624
|
return undefined;
|
|
@@ -1529,15 +1633,16 @@ export class WebGpuTerminalRenderer {
|
|
|
1529
1633
|
geometryCacheHits: 0,
|
|
1530
1634
|
geometryCacheMisses: 0,
|
|
1531
1635
|
canvasPixels: viewportWidth * viewportHeight,
|
|
1532
|
-
renderPasses:
|
|
1533
|
-
drawCalls: Number(
|
|
1636
|
+
renderPasses: 1 + effectPassCount,
|
|
1637
|
+
drawCalls: Number(rowResetInstanceCount > 0) +
|
|
1638
|
+
Number(backgroundInstanceCount > rowResetInstanceCount) +
|
|
1534
1639
|
Number(selectionInstanceCount > 0) +
|
|
1535
1640
|
Number(glyphData.length > 0) +
|
|
1536
1641
|
Number(colorGlyphData.length > 0) +
|
|
1537
1642
|
Number(fallbackGlyphData.length > 0) +
|
|
1538
1643
|
Number(decorationInstanceCount > 0) +
|
|
1539
1644
|
Number(cursorInstanceCount > 0) +
|
|
1540
|
-
|
|
1645
|
+
effectPassCount,
|
|
1541
1646
|
rectangleVertices: (rectangleVertices.length / 10) * 6,
|
|
1542
1647
|
monoGlyphVertices: (glyphVertices.length / 12) * 6,
|
|
1543
1648
|
colorGlyphVertices: (colorGlyphVertices.length / 12) * 6,
|
|
@@ -1557,10 +1662,13 @@ export class WebGpuTerminalRenderer {
|
|
|
1557
1662
|
const surface = this.#surfaces.get(id);
|
|
1558
1663
|
if (!surface)
|
|
1559
1664
|
return;
|
|
1560
|
-
if (!surface.sceneValid || !view.damage || view.damage.full)
|
|
1665
|
+
if (!surface.sceneValid || !view.damage || view.damage.full) {
|
|
1561
1666
|
return this.#encodeRender(id, view, encoder);
|
|
1667
|
+
}
|
|
1562
1668
|
const rowCount = Math.max(view.rows.length, view.nativeRows.length, view.nativeStyleRows.length);
|
|
1563
1669
|
const damage = rowsForDamage(rowCount, view.damage, surface.sceneValid);
|
|
1670
|
+
if (damage.rows.length === 0)
|
|
1671
|
+
return this.#encodeEffectsOnly(surface, view, encoder);
|
|
1564
1672
|
const monoUploadsBefore = this.#performanceMeasurementEnabled ? this.#monoAtlas.uploadMetrics() : undefined;
|
|
1565
1673
|
const colorUploadsBefore = this.#performanceMeasurementEnabled ? this.#colorAtlas.uploadMetrics() : undefined;
|
|
1566
1674
|
const fallbackUploadsBefore = this.#performanceMeasurementEnabled ? this.#fallbackAtlas.uploadMetrics() : undefined;
|
|
@@ -1603,6 +1711,7 @@ export class WebGpuTerminalRenderer {
|
|
|
1603
1711
|
fallbackGlyphBuffer: promote
|
|
1604
1712
|
? createCachedVertexBuffer(this.device, `cached fallback glyphs ${id}`, cpu.fallbackGlyphData)
|
|
1605
1713
|
: surface.fallbackGlyphBuffer.write(cpu.fallbackGlyphData),
|
|
1714
|
+
rowResetInstanceCount: cpu.rowResetInstanceCount,
|
|
1606
1715
|
backgroundInstanceCount: cpu.backgroundInstanceCount,
|
|
1607
1716
|
selectionInstanceCount: cpu.selectionInstanceCount,
|
|
1608
1717
|
decorationInstanceStart: cpu.decorationInstanceStart,
|
|
@@ -1652,21 +1761,21 @@ export class WebGpuTerminalRenderer {
|
|
|
1652
1761
|
colorAttachments: [
|
|
1653
1762
|
{
|
|
1654
1763
|
view: surface.sceneTexture.createView(),
|
|
1655
|
-
clearValue:
|
|
1656
|
-
r: view.theme.background[0],
|
|
1657
|
-
g: view.theme.background[1],
|
|
1658
|
-
b: view.theme.background[2],
|
|
1659
|
-
a: view.theme.background[3],
|
|
1660
|
-
},
|
|
1764
|
+
clearValue: premultipliedClear(view.theme.background),
|
|
1661
1765
|
loadOp: "load",
|
|
1662
1766
|
storeOp: "store",
|
|
1663
1767
|
},
|
|
1664
1768
|
],
|
|
1665
1769
|
});
|
|
1666
|
-
if (geometry.rectangleBuffer && geometry.
|
|
1770
|
+
if (geometry.rectangleBuffer && geometry.rowResetInstanceCount > 0) {
|
|
1771
|
+
pass.setPipeline(this.#rectangleOverwritePipeline);
|
|
1772
|
+
pass.setVertexBuffer(0, geometry.rectangleBuffer);
|
|
1773
|
+
pass.draw(6, geometry.rowResetInstanceCount);
|
|
1774
|
+
}
|
|
1775
|
+
if (geometry.rectangleBuffer && geometry.backgroundInstanceCount > geometry.rowResetInstanceCount) {
|
|
1667
1776
|
pass.setPipeline(this.#rectanglePipeline);
|
|
1668
1777
|
pass.setVertexBuffer(0, geometry.rectangleBuffer);
|
|
1669
|
-
pass.draw(6, geometry.backgroundInstanceCount);
|
|
1778
|
+
pass.draw(6, geometry.backgroundInstanceCount - geometry.rowResetInstanceCount, 0, geometry.rowResetInstanceCount);
|
|
1670
1779
|
}
|
|
1671
1780
|
if (geometry.rectangleBuffer && geometry.selectionInstanceCount > 0) {
|
|
1672
1781
|
pass.setPipeline(this.#rectanglePipeline);
|
|
@@ -1702,26 +1811,7 @@ export class WebGpuTerminalRenderer {
|
|
|
1702
1811
|
pass.draw(6, cursorInstanceCount, 0, cursorInstanceStart);
|
|
1703
1812
|
}
|
|
1704
1813
|
pass.end();
|
|
1705
|
-
const
|
|
1706
|
-
label: `Ghostty custom shader pass ${id}`,
|
|
1707
|
-
colorAttachments: [
|
|
1708
|
-
{
|
|
1709
|
-
view: surface.context.getCurrentTexture().createView(),
|
|
1710
|
-
clearValue: {
|
|
1711
|
-
r: view.theme.background[0],
|
|
1712
|
-
g: view.theme.background[1],
|
|
1713
|
-
b: view.theme.background[2],
|
|
1714
|
-
a: 1,
|
|
1715
|
-
},
|
|
1716
|
-
loadOp: "clear",
|
|
1717
|
-
storeOp: "store",
|
|
1718
|
-
},
|
|
1719
|
-
],
|
|
1720
|
-
});
|
|
1721
|
-
postProcessPass.setPipeline(this.#postProcessPipeline);
|
|
1722
|
-
postProcessPass.setBindGroup(0, surface.postProcessBindGroup);
|
|
1723
|
-
postProcessPass.draw(3);
|
|
1724
|
-
postProcessPass.end();
|
|
1814
|
+
const effectPassCount = this.#encodeEffectStack(surface, view, encoder);
|
|
1725
1815
|
surface.sceneValid = true;
|
|
1726
1816
|
if (!this.#performanceMeasurementEnabled)
|
|
1727
1817
|
return undefined;
|
|
@@ -1736,15 +1826,16 @@ export class WebGpuTerminalRenderer {
|
|
|
1736
1826
|
geometryCacheHits: Number(cacheHit),
|
|
1737
1827
|
geometryCacheMisses: Number(!cacheHit),
|
|
1738
1828
|
canvasPixels: viewportWidth * viewportHeight,
|
|
1739
|
-
renderPasses:
|
|
1740
|
-
drawCalls: Number(geometry.
|
|
1829
|
+
renderPasses: 1 + effectPassCount,
|
|
1830
|
+
drawCalls: Number(geometry.rowResetInstanceCount > 0) +
|
|
1831
|
+
Number(geometry.backgroundInstanceCount > geometry.rowResetInstanceCount) +
|
|
1741
1832
|
Number(geometry.selectionInstanceCount > 0) +
|
|
1742
1833
|
Number(geometry.glyphInstanceCount > 0) +
|
|
1743
1834
|
Number(geometry.colorGlyphInstanceCount > 0) +
|
|
1744
1835
|
Number(geometry.fallbackGlyphInstanceCount > 0) +
|
|
1745
1836
|
Number(geometry.decorationInstanceCount > 0) +
|
|
1746
1837
|
Number(cursorInstanceCount > 0) +
|
|
1747
|
-
|
|
1838
|
+
effectPassCount,
|
|
1748
1839
|
rectangleVertices: (geometry.rectangleInstanceCount + cursorInstanceCount) * 6,
|
|
1749
1840
|
monoGlyphVertices: geometry.glyphInstanceCount * 6,
|
|
1750
1841
|
colorGlyphVertices: geometry.colorGlyphInstanceCount * 6,
|