@empty-sekai/sekai-custom-profile-sdk 0.3.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/LICENSE +235 -0
- package/LICENSE-EXCEPTION +32 -0
- package/NOTICE +11 -0
- package/README.en.md +456 -0
- package/README.md +458 -0
- package/dist/allium_renderer_wasm.js +14 -0
- package/dist/allium_renderer_wasm.wasm +0 -0
- package/dist/authoring.d.ts +14 -0
- package/dist/authoring.js +34 -0
- package/dist/cache/glyphPersistentCache.d.ts +145 -0
- package/dist/cache/glyphPersistentCache.js +346 -0
- package/dist/cache/indexedDbGlyphRecordStore.d.ts +26 -0
- package/dist/cache/indexedDbGlyphRecordStore.js +214 -0
- package/dist/cache/sessionImageResourceCache.d.ts +39 -0
- package/dist/cache/sessionImageResourceCache.js +156 -0
- package/dist/emscripten.d.ts +11 -0
- package/dist/emscripten.js +1 -0
- package/dist/fontProvider.d.ts +36 -0
- package/dist/fontProvider.js +84 -0
- package/dist/fontSdfAtlas.d.ts +92 -0
- package/dist/fontSdfAtlas.js +296 -0
- package/dist/gpu/browserSemanticResources.d.ts +64 -0
- package/dist/gpu/browserSemanticResources.js +232 -0
- package/dist/gpu/generalTextRenderPlacement.d.ts +8 -0
- package/dist/gpu/generalTextRenderPlacement.js +131 -0
- package/dist/gpu/previewTransformTextureLayout.d.ts +1 -0
- package/dist/gpu/previewTransformTextureLayout.js +12 -0
- package/dist/gpu/semanticCommandGeometry.d.ts +21 -0
- package/dist/gpu/semanticCommandGeometry.js +226 -0
- package/dist/gpu/semanticCommandPlanner.d.ts +131 -0
- package/dist/gpu/semanticCommandPlanner.js +203 -0
- package/dist/gpu/semanticTextGlyphBridge.d.ts +3 -0
- package/dist/gpu/semanticTextGlyphBridge.js +22 -0
- package/dist/gpu/semanticWebglSceneRenderer.d.ts +70 -0
- package/dist/gpu/semanticWebglSceneRenderer.js +211 -0
- package/dist/gpu/slotRanges.d.ts +5 -0
- package/dist/gpu/slotRanges.js +14 -0
- package/dist/gpu/webglSdfAtlasTexture.d.ts +17 -0
- package/dist/gpu/webglSdfAtlasTexture.js +72 -0
- package/dist/gpu/webglSdfGlyphPipeline.d.ts +26 -0
- package/dist/gpu/webglSdfGlyphPipeline.js +267 -0
- package/dist/gpu/webglSemanticCommandExecutor.d.ts +79 -0
- package/dist/gpu/webglSemanticCommandExecutor.js +686 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +8 -0
- package/dist/interaction/numericTextRegions.d.ts +46 -0
- package/dist/interaction/numericTextRegions.js +44 -0
- package/dist/localizationProvider.d.ts +33 -0
- package/dist/localizationProvider.js +74 -0
- package/dist/originPrebuiltSdfAtlasPackage.d.ts +39 -0
- package/dist/originPrebuiltSdfAtlasPackage.js +316 -0
- package/dist/prebuiltSdfAtlas.d.ts +36 -0
- package/dist/prebuiltSdfAtlas.js +261 -0
- package/dist/protocol.d.ts +456 -0
- package/dist/protocol.js +1 -0
- package/dist/renderer.d.ts +218 -0
- package/dist/renderer.js +641 -0
- package/dist/resourceProvider.d.ts +24 -0
- package/dist/resourceProvider.js +41 -0
- package/dist/telemetry/rendererTelemetry.d.ts +158 -0
- package/dist/telemetry/rendererTelemetry.js +232 -0
- package/dist/third-party/freetype/FTL.txt +169 -0
- package/dist/types/atlas.d.ts +90 -0
- package/dist/types/atlas.js +1 -0
- package/dist/types/authoring.d.ts +81 -0
- package/dist/types/authoring.js +1 -0
- package/dist/types/core.d.ts +128 -0
- package/dist/types/core.js +1 -0
- package/dist/types/freeType.d.ts +54 -0
- package/dist/types/freeType.js +13 -0
- package/dist/types/glyph.d.ts +33 -0
- package/dist/types/glyph.js +1 -0
- package/dist/types/layout.d.ts +25 -0
- package/dist/types/layout.js +1 -0
- package/dist/types/text.d.ts +28 -0
- package/dist/types/text.js +1 -0
- package/dist/worker/glyphWorkScheduler.d.ts +29 -0
- package/dist/worker/glyphWorkScheduler.js +107 -0
- package/dist/worker-client.d.ts +114 -0
- package/dist/worker-client.js +413 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +708 -0
- package/package.json +46 -0
- package/src/atlas.rs +652 -0
- package/src/authoring.ts +42 -0
- package/src/authoring_runtime.rs +303 -0
- package/src/cache/glyphPersistentCache.ts +463 -0
- package/src/cache/indexedDbGlyphRecordStore.ts +238 -0
- package/src/cache/sessionImageResourceCache.ts +186 -0
- package/src/edt.rs +97 -0
- package/src/emscripten.ts +17 -0
- package/src/fontProvider.ts +127 -0
- package/src/fontSdfAtlas.ts +431 -0
- package/src/geometry.rs +149 -0
- package/src/glyph_plan.rs +226 -0
- package/src/gpu/browserSemanticResources.ts +294 -0
- package/src/gpu/generalTextRenderPlacement.ts +175 -0
- package/src/gpu/previewTransformTextureLayout.ts +12 -0
- package/src/gpu/semanticCommandGeometry.ts +260 -0
- package/src/gpu/semanticCommandPlanner.ts +280 -0
- package/src/gpu/semanticTextGlyphBridge.ts +35 -0
- package/src/gpu/semanticWebglSceneRenderer.ts +261 -0
- package/src/gpu/slotRanges.ts +15 -0
- package/src/gpu/webglSdfAtlasTexture.ts +69 -0
- package/src/gpu/webglSdfGlyphPipeline.ts +299 -0
- package/src/gpu/webglSemanticCommandExecutor.ts +739 -0
- package/src/index.ts +84 -0
- package/src/interaction/numericTextRegions.ts +80 -0
- package/src/layout.rs +1882 -0
- package/src/lib.rs +1443 -0
- package/src/localizationProvider.ts +113 -0
- package/src/main.rs +55 -0
- package/src/masterdata_runtime.rs +496 -0
- package/src/originPrebuiltSdfAtlasPackage.ts +403 -0
- package/src/prebuiltSdfAtlas.ts +306 -0
- package/src/protocol.ts +150 -0
- package/src/renderer.ts +792 -0
- package/src/resourceProvider.ts +69 -0
- package/src/scene.rs +665 -0
- package/src/telemetry/rendererTelemetry.ts +358 -0
- package/src/types/atlas.ts +80 -0
- package/src/types/authoring.ts +85 -0
- package/src/types/core.ts +110 -0
- package/src/types/freeType.ts +63 -0
- package/src/types/glyph.ts +33 -0
- package/src/types/layout.ts +28 -0
- package/src/types/text.ts +28 -0
- package/src/worker/glyphWorkScheduler.ts +130 -0
- package/src/worker-client.ts +484 -0
- package/src/worker.ts +895 -0
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
import { SEMANTIC_FLOATS_PER_VERTEX, semanticTextBatchKey, type SemanticBlendMode, type SemanticDrawBatch } from "./semanticCommandGeometry.js";
|
|
2
|
+
import type { SemanticCommandPlan, SemanticCommandStatePatch, SemanticLayerPatch } from "./semanticCommandPlanner.js";
|
|
3
|
+
import { WebglSdfGlyphPipeline } from "./webglSdfGlyphPipeline.js";
|
|
4
|
+
import { WebglSdfAtlasTexture } from "./webglSdfAtlasTexture.js";
|
|
5
|
+
import { packPreviewTransformsForTexture } from "./previewTransformTextureLayout.js";
|
|
6
|
+
import type { SdfAtlas } from "../fontSdfAtlas.js";
|
|
7
|
+
import type { BrowserImageSource } from "./browserSemanticResources.js";
|
|
8
|
+
|
|
9
|
+
const PREVIEW_TRANSFORM_TEXTURE_UNIT = 5;
|
|
10
|
+
const ALPHA_MASK_TEXTURE_UNIT = 6;
|
|
11
|
+
|
|
12
|
+
const CARD_W = 1830;
|
|
13
|
+
const CARD_H = 812;
|
|
14
|
+
|
|
15
|
+
type GpuBatch = {
|
|
16
|
+
source: SemanticDrawBatch;
|
|
17
|
+
vao: WebGLVertexArrayObject;
|
|
18
|
+
vertexBuffer: WebGLBuffer;
|
|
19
|
+
slotBuffer: WebGLBuffer;
|
|
20
|
+
commandSlotBuffer: WebGLBuffer;
|
|
21
|
+
vertices: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type IsolationTarget = {
|
|
25
|
+
framebuffer: WebGLFramebuffer;
|
|
26
|
+
texture: WebGLTexture;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type SemanticGpuMetrics = {
|
|
30
|
+
drawCalls: number;
|
|
31
|
+
geometryBuilds: number;
|
|
32
|
+
vertexBytes: number;
|
|
33
|
+
textureUploads: number;
|
|
34
|
+
textureBytes: number;
|
|
35
|
+
stateUploadBytes: number;
|
|
36
|
+
maskUploadBytes: number;
|
|
37
|
+
glyphGeometryBuilds: number;
|
|
38
|
+
isolationBegins: number;
|
|
39
|
+
isolationComposites: number;
|
|
40
|
+
isolationTargetAllocations: number;
|
|
41
|
+
isolationTextureBytes: number;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export class WebglSemanticCommandExecutor {
|
|
45
|
+
private shapeProgram: WebGLProgram;
|
|
46
|
+
private textureProgram: WebGLProgram;
|
|
47
|
+
private compositeProgram: WebGLProgram;
|
|
48
|
+
private compositeVao: WebGLVertexArrayObject;
|
|
49
|
+
private stateTexture: WebGLTexture;
|
|
50
|
+
private maskTexture: WebGLTexture;
|
|
51
|
+
private commandMaskTexture: WebGLTexture;
|
|
52
|
+
private commandStateTexture: WebGLTexture;
|
|
53
|
+
private previewTransformTexture: WebGLTexture;
|
|
54
|
+
private batches: GpuBatch[] = [];
|
|
55
|
+
private textures = new Map<string, { texture: WebGLTexture; source: BrowserImageSource; bytes: number }>();
|
|
56
|
+
private state = new Float32Array(2);
|
|
57
|
+
private mask = new Uint8Array(1);
|
|
58
|
+
private stateWidth = 1;
|
|
59
|
+
private commandMask = new Uint8Array(1);
|
|
60
|
+
private commandState = new Float32Array(2);
|
|
61
|
+
private previewTransforms = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0]);
|
|
62
|
+
private commandWidth = 1;
|
|
63
|
+
private plan: SemanticCommandPlan | null = null;
|
|
64
|
+
private layerSlotById = new Map<string, number>();
|
|
65
|
+
private readonly glyphPipeline: WebglSdfGlyphPipeline;
|
|
66
|
+
private sdfAtlasTexture: WebglSdfAtlasTexture | null = null;
|
|
67
|
+
private geometryBuilds = 0;
|
|
68
|
+
private textureUploads = 0;
|
|
69
|
+
private textureBytes = 0;
|
|
70
|
+
private readonly isolationTargets: IsolationTarget[] = [];
|
|
71
|
+
private isolationTargetAllocations = 0;
|
|
72
|
+
|
|
73
|
+
constructor(private readonly gl: WebGL2RenderingContext, private readonly canvasWidth = CARD_W, private readonly canvasHeight = CARD_H) {
|
|
74
|
+
this.shapeProgram = createProgram(gl, VERTEX_SHADER, SHAPE_FRAGMENT_SHADER);
|
|
75
|
+
this.textureProgram = createProgram(gl, VERTEX_SHADER, TEXTURE_FRAGMENT_SHADER);
|
|
76
|
+
this.compositeProgram = createProgram(gl, COMPOSITE_VERTEX_SHADER, COMPOSITE_FRAGMENT_SHADER);
|
|
77
|
+
const compositeVao = gl.createVertexArray();
|
|
78
|
+
const stateTexture = gl.createTexture();
|
|
79
|
+
const maskTexture = gl.createTexture();
|
|
80
|
+
const commandMaskTexture = gl.createTexture();
|
|
81
|
+
const commandStateTexture = gl.createTexture();
|
|
82
|
+
const previewTransformTexture = gl.createTexture();
|
|
83
|
+
if (!stateTexture || !maskTexture || !commandMaskTexture || !commandStateTexture || !previewTransformTexture || !compositeVao) throw new Error("semantic WebGL state texture creation failed");
|
|
84
|
+
this.stateTexture = stateTexture;
|
|
85
|
+
this.maskTexture = maskTexture;
|
|
86
|
+
this.commandMaskTexture = commandMaskTexture;
|
|
87
|
+
this.commandStateTexture = commandStateTexture;
|
|
88
|
+
this.previewTransformTexture = previewTransformTexture;
|
|
89
|
+
this.compositeVao = compositeVao;
|
|
90
|
+
this.glyphPipeline = new WebglSdfGlyphPipeline(gl);
|
|
91
|
+
gl.enable(gl.BLEND);
|
|
92
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
setTextGlyphBatch(commandId: string, vertices: Float32Array): void {
|
|
96
|
+
this.glyphPipeline.upload(commandId, vertices);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async setSdfAtlas(atlas: SdfAtlas): Promise<{ bytes: number; rects: number }> {
|
|
100
|
+
this.sdfAtlasTexture ??= new WebglSdfAtlasTexture(this.gl);
|
|
101
|
+
const uploaded = await this.sdfAtlasTexture.uploadUpdates(atlas);
|
|
102
|
+
this.glyphPipeline.setAtlas(this.sdfAtlasTexture.texture());
|
|
103
|
+
return uploaded;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
setScene(plan: SemanticCommandPlan, batches: SemanticDrawBatch[], resources: Map<string, BrowserImageSource>): void {
|
|
107
|
+
this.deleteBatches();
|
|
108
|
+
this.glyphPipeline.clearBatches();
|
|
109
|
+
this.plan = plan;
|
|
110
|
+
const operations = plan.operations();
|
|
111
|
+
this.layerSlotById = new Map(operations.map((operation) => [operation.layerId, operation.layerSlot] as const));
|
|
112
|
+
this.stateWidth = Math.max(1, ...operations.map((operation) => operation.layerSlot + 1));
|
|
113
|
+
this.state = new Float32Array(this.stateWidth * 2);
|
|
114
|
+
this.mask = new Uint8Array(this.stateWidth);
|
|
115
|
+
this.commandWidth = Math.max(1, ...operations.map((operation) => operation.commandSlot + 1));
|
|
116
|
+
this.commandMask = new Uint8Array(this.commandWidth);
|
|
117
|
+
this.commandState = new Float32Array(this.commandWidth * 2);
|
|
118
|
+
this.previewTransforms = new Float32Array(this.stateWidth * 8);
|
|
119
|
+
for (let slot = 0; slot < this.stateWidth; slot += 1) {
|
|
120
|
+
this.previewTransforms.set([1, 0, 0, 0, 0, 1, 0, 0], slot * 8);
|
|
121
|
+
}
|
|
122
|
+
const initialized = new Set<number>();
|
|
123
|
+
for (const operation of operations) {
|
|
124
|
+
this.commandMask[operation.commandSlot] = operation.commandVisible ? 1 : 0;
|
|
125
|
+
this.commandState[operation.commandSlot * 2] = operation.commandTransform.dx;
|
|
126
|
+
this.commandState[operation.commandSlot * 2 + 1] = operation.commandTransform.dy;
|
|
127
|
+
const slot = operation.layerSlot;
|
|
128
|
+
if (initialized.has(slot)) continue;
|
|
129
|
+
initialized.add(slot);
|
|
130
|
+
this.state[slot * 2] = operation.transform.dx;
|
|
131
|
+
this.state[slot * 2 + 1] = operation.transform.dy;
|
|
132
|
+
this.mask[slot] = operation.visible ? 1 : 0;
|
|
133
|
+
}
|
|
134
|
+
this.uploadFullState();
|
|
135
|
+
for (const batch of batches) this.batches.push(this.uploadBatch(batch));
|
|
136
|
+
this.geometryBuilds += 1;
|
|
137
|
+
for (const [key, source] of resources) this.ensureTexture(key, source);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
applyCommandPatches(patches: SemanticCommandStatePatch[]): { commandMaskUploadBytes: number; commandStateUploadBytes: number } {
|
|
141
|
+
if (!this.plan) throw new Error("semantic GPU scene is not initialized");
|
|
142
|
+
this.plan.applyCommandPatches(patches);
|
|
143
|
+
let commandMaskUploadBytes = 0;
|
|
144
|
+
let commandStateUploadBytes = 0;
|
|
145
|
+
for (const patch of patches) {
|
|
146
|
+
if (!Number.isInteger(patch.slot) || patch.slot < 0 || patch.slot >= this.commandWidth) throw new Error(`invalid command slot ${patch.slot}`);
|
|
147
|
+
if (patch.transform != null) {
|
|
148
|
+
const offset = patch.slot * 2;
|
|
149
|
+
this.commandState[offset] = patch.transform.dx;
|
|
150
|
+
this.commandState[offset + 1] = patch.transform.dy;
|
|
151
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, this.commandStateTexture);
|
|
152
|
+
this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, patch.slot, 0, 1, 1, this.gl.RG, this.gl.FLOAT, this.commandState.subarray(offset, offset + 2));
|
|
153
|
+
commandStateUploadBytes += 8;
|
|
154
|
+
}
|
|
155
|
+
if (patch.render_mask != null) {
|
|
156
|
+
this.commandMask[patch.slot] = patch.render_mask ? 1 : 0;
|
|
157
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, this.commandMaskTexture);
|
|
158
|
+
this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, patch.slot, 0, 1, 1, this.gl.RED_INTEGER, this.gl.UNSIGNED_BYTE, this.commandMask.subarray(patch.slot, patch.slot + 1));
|
|
159
|
+
commandMaskUploadBytes += 1;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return { commandMaskUploadBytes, commandStateUploadBytes };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
applyLayerPatches(patches: SemanticLayerPatch[]): { stateUploadBytes: number; maskUploadBytes: number } {
|
|
166
|
+
if (!this.plan) throw new Error("semantic GPU scene is not initialized");
|
|
167
|
+
this.plan.applyLayerPatches(patches);
|
|
168
|
+
const slotByLayer = new Map(this.plan.operations().map((operation) => [operation.layerId, operation.layerSlot] as const));
|
|
169
|
+
let stateUploadBytes = 0;
|
|
170
|
+
let maskUploadBytes = 0;
|
|
171
|
+
for (const patch of patches) {
|
|
172
|
+
const slot = slotByLayer.get(patch.layer_id);
|
|
173
|
+
if (slot == null) throw new Error(`semantic GPU patch references unknown layer ${patch.layer_id}`);
|
|
174
|
+
if (patch.transform) {
|
|
175
|
+
const offset = slot * 2;
|
|
176
|
+
this.state[offset] = patch.transform.dx;
|
|
177
|
+
this.state[offset + 1] = patch.transform.dy;
|
|
178
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, this.stateTexture);
|
|
179
|
+
this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, slot, 0, 1, 1, this.gl.RG, this.gl.FLOAT, this.state.subarray(offset, offset + 2));
|
|
180
|
+
stateUploadBytes += 8;
|
|
181
|
+
}
|
|
182
|
+
if (patch.render_mask != null) {
|
|
183
|
+
this.mask[slot] = patch.render_mask ? 1 : 0;
|
|
184
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, this.maskTexture);
|
|
185
|
+
this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, slot, 0, 1, 1, this.gl.RED_INTEGER, this.gl.UNSIGNED_BYTE, this.mask.subarray(slot, slot + 1));
|
|
186
|
+
maskUploadBytes += 1;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { stateUploadBytes, maskUploadBytes };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
setLayerPreviewTransform(layerId: string, matrix: [number, number, number, number, number, number] | null): { previewUploadBytes: number } {
|
|
193
|
+
if (!this.plan) throw new Error("semantic GPU scene is not initialized");
|
|
194
|
+
const slot = this.layerSlotById.get(layerId);
|
|
195
|
+
if (slot == null) throw new Error(`unknown preview layer ${layerId}`);
|
|
196
|
+
const value = matrix ?? [1, 0, 0, 1, 0, 0];
|
|
197
|
+
const offset = slot * 8;
|
|
198
|
+
this.previewTransforms.set([value[0], value[2], value[4], 0, value[1], value[3], value[5], 0], offset);
|
|
199
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, this.previewTransformTexture);
|
|
200
|
+
this.gl.texSubImage2D(
|
|
201
|
+
this.gl.TEXTURE_2D,
|
|
202
|
+
0,
|
|
203
|
+
slot,
|
|
204
|
+
0,
|
|
205
|
+
1,
|
|
206
|
+
2,
|
|
207
|
+
this.gl.RGBA,
|
|
208
|
+
this.gl.FLOAT,
|
|
209
|
+
this.previewTransforms.subarray(offset, offset + 8),
|
|
210
|
+
);
|
|
211
|
+
return { previewUploadBytes: 32 };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
draw(): SemanticGpuMetrics {
|
|
215
|
+
const gl = this.gl;
|
|
216
|
+
const rootFramebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING) as WebGLFramebuffer | null;
|
|
217
|
+
const rootViewport = gl.getParameter(gl.VIEWPORT) as Int32Array;
|
|
218
|
+
gl.viewport(0, 0, this.canvasWidth, this.canvasHeight);
|
|
219
|
+
gl.clearColor(1, 1, 1, 1);
|
|
220
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
221
|
+
let drawCalls = 0;
|
|
222
|
+
let vertexBytes = 0;
|
|
223
|
+
let isolationBegins = 0;
|
|
224
|
+
let isolationComposites = 0;
|
|
225
|
+
const isolationStack: IsolationTarget[] = [];
|
|
226
|
+
try {
|
|
227
|
+
for (const batch of this.batches) {
|
|
228
|
+
if (batch.source.kind === "composite") {
|
|
229
|
+
const operation = batch.source.compositeOperation;
|
|
230
|
+
if (operation === "marker") continue;
|
|
231
|
+
if (operation === "begin_isolation") {
|
|
232
|
+
const target = this.isolationTarget(isolationStack.length);
|
|
233
|
+
isolationStack.push(target);
|
|
234
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, target.framebuffer);
|
|
235
|
+
gl.viewport(0, 0, this.canvasWidth, this.canvasHeight);
|
|
236
|
+
gl.clearColor(0, 0, 0, 0);
|
|
237
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
238
|
+
isolationBegins += 1;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (operation === "end_isolation") {
|
|
242
|
+
const target = isolationStack.pop();
|
|
243
|
+
if (!target) throw new Error(`semantic isolation end has no matching begin: ${batch.source.commandIds[0] ?? "unknown"}`);
|
|
244
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, isolationStack.at(-1)?.framebuffer ?? rootFramebuffer);
|
|
245
|
+
gl.viewport(0, 0, this.canvasWidth, this.canvasHeight);
|
|
246
|
+
this.drawIsolationTexture(target.texture);
|
|
247
|
+
drawCalls += 1;
|
|
248
|
+
isolationComposites += 1;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
throw new Error(`unsupported semantic composite operation ${String(operation)}`);
|
|
252
|
+
}
|
|
253
|
+
this.setBlendMode(batch.source.blendMode);
|
|
254
|
+
if (batch.source.kind === "text") {
|
|
255
|
+
const glyph = this.glyphPipeline.draw(
|
|
256
|
+
semanticTextBatchKey(batch.source.commandIds),
|
|
257
|
+
this.stateTexture,
|
|
258
|
+
this.maskTexture,
|
|
259
|
+
this.stateWidth,
|
|
260
|
+
this.commandMaskTexture,
|
|
261
|
+
this.commandStateTexture,
|
|
262
|
+
this.commandWidth,
|
|
263
|
+
this.previewTransformTexture,
|
|
264
|
+
);
|
|
265
|
+
drawCalls += glyph.drawCalls;
|
|
266
|
+
vertexBytes += glyph.bytes;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const program = batch.source.kind === "shape" ? this.shapeProgram : this.textureProgram;
|
|
270
|
+
gl.useProgram(program);
|
|
271
|
+
this.bindCommon(program);
|
|
272
|
+
if (batch.source.kind !== "shape") {
|
|
273
|
+
const resource = batch.source.resource;
|
|
274
|
+
if (!resource) throw new Error(`semantic ${batch.source.kind} batch has no resource`);
|
|
275
|
+
const key = resourceIdentity(resource.namespace, resource.key);
|
|
276
|
+
const texture = this.textures.get(key)?.texture;
|
|
277
|
+
if (!texture) throw new Error(`semantic GPU resource not loaded ${key}`);
|
|
278
|
+
gl.activeTexture(gl.TEXTURE2);
|
|
279
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
280
|
+
const filter = gl.NEAREST;
|
|
281
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
|
|
282
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
|
|
283
|
+
gl.uniform1i(gl.getUniformLocation(program, "u_image"), 2);
|
|
284
|
+
gl.uniform1i(gl.getUniformLocation(program, "u_maskMode"), batch.source.kind === "mask" ? 1 : 0);
|
|
285
|
+
const alphaMask = batch.source.maskResource
|
|
286
|
+
? this.textures.get(resourceIdentity(batch.source.maskResource.namespace, batch.source.maskResource.key))?.texture ?? null
|
|
287
|
+
: null;
|
|
288
|
+
gl.activeTexture(gl.TEXTURE0 + ALPHA_MASK_TEXTURE_UNIT);
|
|
289
|
+
gl.bindTexture(gl.TEXTURE_2D, alphaMask);
|
|
290
|
+
gl.uniform1i(gl.getUniformLocation(program, "u_alphaMask"), ALPHA_MASK_TEXTURE_UNIT);
|
|
291
|
+
gl.uniform1i(gl.getUniformLocation(program, "u_hasAlphaMask"), alphaMask ? 1 : 0);
|
|
292
|
+
}
|
|
293
|
+
gl.bindVertexArray(batch.vao);
|
|
294
|
+
gl.drawArrays(gl.TRIANGLES, 0, batch.vertices);
|
|
295
|
+
drawCalls += 1;
|
|
296
|
+
vertexBytes += batch.source.vertices.byteLength + batch.source.layerSlots.byteLength + batch.source.commandSlots.byteLength;
|
|
297
|
+
}
|
|
298
|
+
if (isolationStack.length !== 0) throw new Error(`semantic isolation has ${isolationStack.length} unclosed group(s)`);
|
|
299
|
+
} finally {
|
|
300
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, rootFramebuffer);
|
|
301
|
+
gl.viewport(rootViewport[0], rootViewport[1], rootViewport[2], rootViewport[3]);
|
|
302
|
+
this.setBlendMode("src_over");
|
|
303
|
+
}
|
|
304
|
+
gl.bindVertexArray(null);
|
|
305
|
+
return {
|
|
306
|
+
drawCalls,
|
|
307
|
+
geometryBuilds: this.geometryBuilds,
|
|
308
|
+
vertexBytes,
|
|
309
|
+
textureUploads: this.textureUploads,
|
|
310
|
+
textureBytes: this.textureBytes,
|
|
311
|
+
stateUploadBytes: 0,
|
|
312
|
+
maskUploadBytes: 0,
|
|
313
|
+
glyphGeometryBuilds: this.glyphPipeline.stats().geometryBuilds,
|
|
314
|
+
isolationBegins,
|
|
315
|
+
isolationComposites,
|
|
316
|
+
isolationTargetAllocations: this.isolationTargetAllocations,
|
|
317
|
+
isolationTextureBytes: this.isolationTargets.length * this.canvasWidth * this.canvasHeight * 4,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
destroy(): void {
|
|
322
|
+
this.deleteBatches();
|
|
323
|
+
for (const entry of this.textures.values()) this.gl.deleteTexture(entry.texture);
|
|
324
|
+
this.textures.clear();
|
|
325
|
+
this.gl.deleteTexture(this.stateTexture);
|
|
326
|
+
this.gl.deleteTexture(this.maskTexture);
|
|
327
|
+
this.gl.deleteTexture(this.commandMaskTexture);
|
|
328
|
+
this.gl.deleteTexture(this.commandStateTexture);
|
|
329
|
+
this.gl.deleteTexture(this.previewTransformTexture);
|
|
330
|
+
this.gl.deleteProgram(this.shapeProgram);
|
|
331
|
+
this.gl.deleteProgram(this.textureProgram);
|
|
332
|
+
this.gl.deleteProgram(this.compositeProgram);
|
|
333
|
+
this.gl.deleteVertexArray(this.compositeVao);
|
|
334
|
+
for (const target of this.isolationTargets) {
|
|
335
|
+
this.gl.deleteFramebuffer(target.framebuffer);
|
|
336
|
+
this.gl.deleteTexture(target.texture);
|
|
337
|
+
}
|
|
338
|
+
this.isolationTargets.length = 0;
|
|
339
|
+
this.glyphPipeline.destroy();
|
|
340
|
+
this.sdfAtlasTexture?.destroy();
|
|
341
|
+
this.layerSlotById.clear();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private bindCommon(program: WebGLProgram): void {
|
|
345
|
+
const gl = this.gl;
|
|
346
|
+
gl.uniform2f(gl.getUniformLocation(program, "u_canvas"), this.canvasWidth, this.canvasHeight);
|
|
347
|
+
gl.uniform1f(gl.getUniformLocation(program, "u_stateWidth"), this.stateWidth);
|
|
348
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
349
|
+
gl.bindTexture(gl.TEXTURE_2D, this.stateTexture);
|
|
350
|
+
gl.uniform1i(gl.getUniformLocation(program, "u_state"), 0);
|
|
351
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
352
|
+
gl.bindTexture(gl.TEXTURE_2D, this.maskTexture);
|
|
353
|
+
gl.uniform1i(gl.getUniformLocation(program, "u_mask"), 1);
|
|
354
|
+
gl.activeTexture(gl.TEXTURE3);
|
|
355
|
+
gl.bindTexture(gl.TEXTURE_2D, this.commandMaskTexture);
|
|
356
|
+
gl.uniform1i(gl.getUniformLocation(program, "u_commandMask"), 3);
|
|
357
|
+
gl.activeTexture(gl.TEXTURE4);
|
|
358
|
+
gl.bindTexture(gl.TEXTURE_2D, this.commandStateTexture);
|
|
359
|
+
gl.uniform1i(gl.getUniformLocation(program, "u_commandState"), 4);
|
|
360
|
+
gl.uniform1f(gl.getUniformLocation(program, "u_commandWidth"), this.commandWidth);
|
|
361
|
+
gl.activeTexture(gl.TEXTURE0 + PREVIEW_TRANSFORM_TEXTURE_UNIT);
|
|
362
|
+
gl.bindTexture(gl.TEXTURE_2D, this.previewTransformTexture);
|
|
363
|
+
gl.uniform1i(gl.getUniformLocation(program, "u_previewTransform"), PREVIEW_TRANSFORM_TEXTURE_UNIT);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private setBlendMode(mode: SemanticBlendMode): void {
|
|
367
|
+
const gl = this.gl;
|
|
368
|
+
gl.blendEquation(gl.FUNC_ADD);
|
|
369
|
+
if (mode === "src_over") gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
370
|
+
else if (mode === "src_in") gl.blendFuncSeparate(gl.DST_ALPHA, gl.ZERO, gl.DST_ALPHA, gl.ZERO);
|
|
371
|
+
else if (mode === "dst_in") gl.blendFuncSeparate(gl.ZERO, gl.SRC_ALPHA, gl.ZERO, gl.SRC_ALPHA);
|
|
372
|
+
else if (mode === "add") gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE);
|
|
373
|
+
else throw new Error(`semantic WebGL blend mode is not implemented: ${mode}`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private isolationTarget(depth: number): IsolationTarget {
|
|
377
|
+
const cached = this.isolationTargets[depth];
|
|
378
|
+
if (cached) return cached;
|
|
379
|
+
const gl = this.gl;
|
|
380
|
+
const texture = gl.createTexture();
|
|
381
|
+
const framebuffer = gl.createFramebuffer();
|
|
382
|
+
if (!texture || !framebuffer) throw new Error(`semantic isolation allocation failed at depth ${depth}`);
|
|
383
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
384
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
385
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
386
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
387
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
388
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, this.canvasWidth, this.canvasHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
389
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
390
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
|
391
|
+
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
|
|
392
|
+
gl.deleteFramebuffer(framebuffer);
|
|
393
|
+
gl.deleteTexture(texture);
|
|
394
|
+
throw new Error(`semantic isolation framebuffer is incomplete at depth ${depth}`);
|
|
395
|
+
}
|
|
396
|
+
const target = { framebuffer, texture };
|
|
397
|
+
this.isolationTargets[depth] = target;
|
|
398
|
+
this.isolationTargetAllocations += 1;
|
|
399
|
+
return target;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private drawIsolationTexture(texture: WebGLTexture): void {
|
|
403
|
+
const gl = this.gl;
|
|
404
|
+
this.setBlendMode("src_over");
|
|
405
|
+
gl.useProgram(this.compositeProgram);
|
|
406
|
+
gl.activeTexture(gl.TEXTURE2);
|
|
407
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
408
|
+
gl.uniform1i(gl.getUniformLocation(this.compositeProgram, "u_image"), 2);
|
|
409
|
+
gl.bindVertexArray(this.compositeVao);
|
|
410
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
private uploadFullState(): void {
|
|
414
|
+
const gl = this.gl;
|
|
415
|
+
gl.bindTexture(gl.TEXTURE_2D, this.stateTexture);
|
|
416
|
+
setStateTextureParameters(gl);
|
|
417
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RG32F, this.stateWidth, 1, 0, gl.RG, gl.FLOAT, this.state);
|
|
418
|
+
gl.bindTexture(gl.TEXTURE_2D, this.maskTexture);
|
|
419
|
+
setStateTextureParameters(gl);
|
|
420
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8UI, this.stateWidth, 1, 0, gl.RED_INTEGER, gl.UNSIGNED_BYTE, this.mask);
|
|
421
|
+
gl.bindTexture(gl.TEXTURE_2D, this.commandMaskTexture);
|
|
422
|
+
setStateTextureParameters(gl);
|
|
423
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8UI, this.commandWidth, 1, 0, gl.RED_INTEGER, gl.UNSIGNED_BYTE, this.commandMask);
|
|
424
|
+
gl.bindTexture(gl.TEXTURE_2D, this.commandStateTexture);
|
|
425
|
+
setStateTextureParameters(gl);
|
|
426
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RG32F, this.commandWidth, 1, 0, gl.RG, gl.FLOAT, this.commandState);
|
|
427
|
+
gl.bindTexture(gl.TEXTURE_2D, this.previewTransformTexture);
|
|
428
|
+
setStateTextureParameters(gl);
|
|
429
|
+
gl.texImage2D(
|
|
430
|
+
gl.TEXTURE_2D,
|
|
431
|
+
0,
|
|
432
|
+
gl.RGBA32F,
|
|
433
|
+
this.stateWidth,
|
|
434
|
+
2,
|
|
435
|
+
0,
|
|
436
|
+
gl.RGBA,
|
|
437
|
+
gl.FLOAT,
|
|
438
|
+
packPreviewTransformsForTexture(this.previewTransforms, this.stateWidth),
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private uploadBatch(source: SemanticDrawBatch): GpuBatch {
|
|
443
|
+
const gl = this.gl;
|
|
444
|
+
const vao = gl.createVertexArray();
|
|
445
|
+
const vertexBuffer = gl.createBuffer();
|
|
446
|
+
const slotBuffer = gl.createBuffer();
|
|
447
|
+
const commandSlotBuffer = gl.createBuffer();
|
|
448
|
+
if (!vao || !vertexBuffer || !slotBuffer || !commandSlotBuffer) throw new Error("semantic WebGL batch allocation failed");
|
|
449
|
+
gl.bindVertexArray(vao);
|
|
450
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
|
|
451
|
+
gl.bufferData(gl.ARRAY_BUFFER, source.vertices, gl.STATIC_DRAW);
|
|
452
|
+
const stride = SEMANTIC_FLOATS_PER_VERTEX * 4;
|
|
453
|
+
floatAttribute(gl, 0, 2, stride, 0);
|
|
454
|
+
floatAttribute(gl, 1, 2, stride, 2 * 4);
|
|
455
|
+
floatAttribute(gl, 2, 2, stride, 4 * 4);
|
|
456
|
+
floatAttribute(gl, 3, 4, stride, 6 * 4);
|
|
457
|
+
floatAttribute(gl, 4, 4, stride, 10 * 4);
|
|
458
|
+
floatAttribute(gl, 5, 4, stride, 14 * 4);
|
|
459
|
+
floatAttribute(gl, 7, 4, stride, 18 * 4);
|
|
460
|
+
floatAttribute(gl, 8, 4, stride, 22 * 4);
|
|
461
|
+
floatAttribute(gl, 10, 2, stride, 26 * 4);
|
|
462
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, slotBuffer);
|
|
463
|
+
gl.bufferData(gl.ARRAY_BUFFER, source.layerSlots, gl.STATIC_DRAW);
|
|
464
|
+
gl.enableVertexAttribArray(6);
|
|
465
|
+
gl.vertexAttribIPointer(6, 1, gl.UNSIGNED_INT, 4, 0);
|
|
466
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, commandSlotBuffer);
|
|
467
|
+
gl.bufferData(gl.ARRAY_BUFFER, source.commandSlots, gl.STATIC_DRAW);
|
|
468
|
+
gl.enableVertexAttribArray(9);
|
|
469
|
+
gl.vertexAttribIPointer(9, 1, gl.UNSIGNED_INT, 4, 0);
|
|
470
|
+
gl.bindVertexArray(null);
|
|
471
|
+
return { source, vao, vertexBuffer, slotBuffer, commandSlotBuffer, vertices: source.layerSlots.length };
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
private ensureTexture(key: string, source: BrowserImageSource): void {
|
|
475
|
+
if (this.textures.has(key)) return;
|
|
476
|
+
const gl = this.gl;
|
|
477
|
+
const texture = gl.createTexture();
|
|
478
|
+
if (!texture) throw new Error(`semantic texture allocation failed ${key}`);
|
|
479
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
480
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
481
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
482
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
483
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
484
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
|
|
485
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source.source);
|
|
486
|
+
const bytes = Math.max(0, source.width * source.height * 4);
|
|
487
|
+
this.textures.set(key, { texture, source, bytes });
|
|
488
|
+
this.textureUploads += 1;
|
|
489
|
+
this.textureBytes += bytes;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
private deleteBatches(): void {
|
|
493
|
+
for (const batch of this.batches) {
|
|
494
|
+
this.gl.deleteBuffer(batch.vertexBuffer);
|
|
495
|
+
this.gl.deleteBuffer(batch.slotBuffer);
|
|
496
|
+
this.gl.deleteBuffer(batch.commandSlotBuffer);
|
|
497
|
+
this.gl.deleteVertexArray(batch.vao);
|
|
498
|
+
}
|
|
499
|
+
this.batches = [];
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export function resourceIdentity(namespace: string, key: string): string {
|
|
504
|
+
return `${namespace}\0${key}`;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function floatAttribute(gl: WebGL2RenderingContext, location: number, size: number, stride: number, offset: number): void {
|
|
508
|
+
gl.enableVertexAttribArray(location);
|
|
509
|
+
gl.vertexAttribPointer(location, size, gl.FLOAT, false, stride, offset);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function setStateTextureParameters(gl: WebGL2RenderingContext): void {
|
|
513
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
514
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
515
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
516
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function createProgram(gl: WebGL2RenderingContext, vertexSource: string, fragmentSource: string): WebGLProgram {
|
|
520
|
+
const compile = (type: number, source: string) => {
|
|
521
|
+
const shader = gl.createShader(type);
|
|
522
|
+
if (!shader) throw new Error("semantic shader allocation failed");
|
|
523
|
+
gl.shaderSource(shader, source);
|
|
524
|
+
gl.compileShader(shader);
|
|
525
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(shader) ?? "semantic shader compile failed");
|
|
526
|
+
return shader;
|
|
527
|
+
};
|
|
528
|
+
const vertex = compile(gl.VERTEX_SHADER, vertexSource);
|
|
529
|
+
const fragment = compile(gl.FRAGMENT_SHADER, fragmentSource);
|
|
530
|
+
const program = gl.createProgram();
|
|
531
|
+
if (!program) throw new Error("semantic program allocation failed");
|
|
532
|
+
gl.attachShader(program, vertex);
|
|
533
|
+
gl.attachShader(program, fragment);
|
|
534
|
+
gl.linkProgram(program);
|
|
535
|
+
gl.deleteShader(vertex);
|
|
536
|
+
gl.deleteShader(fragment);
|
|
537
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(program) ?? "semantic program link failed");
|
|
538
|
+
return program;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const VERTEX_SHADER = `#version 300 es
|
|
542
|
+
precision highp float;
|
|
543
|
+
precision highp usampler2D;
|
|
544
|
+
layout(location=0) in vec2 a_position;
|
|
545
|
+
layout(location=1) in vec2 a_uv;
|
|
546
|
+
layout(location=2) in vec2 a_shapeUv;
|
|
547
|
+
layout(location=3) in vec4 a_fill;
|
|
548
|
+
layout(location=4) in vec4 a_stroke;
|
|
549
|
+
layout(location=5) in vec4 a_params;
|
|
550
|
+
layout(location=6) in uint a_layerSlot;
|
|
551
|
+
layout(location=7) in vec4 a_clip01;
|
|
552
|
+
layout(location=8) in vec4 a_clip23;
|
|
553
|
+
layout(location=9) in uint a_commandSlot;
|
|
554
|
+
layout(location=10) in vec2 a_shapeSize;
|
|
555
|
+
uniform vec2 u_canvas;
|
|
556
|
+
uniform sampler2D u_state;
|
|
557
|
+
uniform highp usampler2D u_mask;
|
|
558
|
+
uniform float u_stateWidth;
|
|
559
|
+
uniform highp usampler2D u_commandMask;
|
|
560
|
+
uniform sampler2D u_commandState;
|
|
561
|
+
uniform float u_commandWidth;
|
|
562
|
+
uniform sampler2D u_previewTransform;
|
|
563
|
+
out vec2 v_uv;
|
|
564
|
+
out vec2 v_shapeUv;
|
|
565
|
+
out vec4 v_fill;
|
|
566
|
+
out vec4 v_stroke;
|
|
567
|
+
out vec4 v_params;
|
|
568
|
+
out vec2 v_point;
|
|
569
|
+
out vec4 v_clip01;
|
|
570
|
+
out vec4 v_clip23;
|
|
571
|
+
out vec2 v_shapeSize;
|
|
572
|
+
flat out uint v_visible;
|
|
573
|
+
void main() {
|
|
574
|
+
float stateU = (float(a_layerSlot) + 0.5) / u_stateWidth;
|
|
575
|
+
vec2 dynamicOffset = texture(u_state, vec2(stateU, 0.5)).rg;
|
|
576
|
+
float commandU = (float(a_commandSlot) + 0.5) / u_commandWidth;
|
|
577
|
+
vec2 commandOffset = texture(u_commandState, vec2(commandU, 0.5)).rg;
|
|
578
|
+
vec2 totalOffset = dynamicOffset + commandOffset;
|
|
579
|
+
vec4 preview0 = texelFetch(u_previewTransform, ivec2(int(a_layerSlot), 0), 0);
|
|
580
|
+
vec4 preview1 = texelFetch(u_previewTransform, ivec2(int(a_layerSlot), 1), 0);
|
|
581
|
+
vec2 basePoint = a_position + totalOffset;
|
|
582
|
+
vec2 point = vec2(
|
|
583
|
+
dot(preview0.xy, basePoint) + preview0.z,
|
|
584
|
+
dot(preview1.xy, basePoint) + preview1.z
|
|
585
|
+
);
|
|
586
|
+
gl_Position = vec4(point.x / u_canvas.x * 2.0 - 1.0, 1.0 - point.y / u_canvas.y * 2.0, 0.0, 1.0);
|
|
587
|
+
v_uv = a_uv;
|
|
588
|
+
v_shapeUv = a_shapeUv;
|
|
589
|
+
v_fill = a_fill;
|
|
590
|
+
v_stroke = a_stroke;
|
|
591
|
+
v_params = a_params;
|
|
592
|
+
v_point = point;
|
|
593
|
+
vec2 clip0 = vec2(dot(preview0.xy, a_clip01.xy + dynamicOffset), dot(preview1.xy, a_clip01.xy + dynamicOffset)) + vec2(preview0.z, preview1.z);
|
|
594
|
+
vec2 clip1 = vec2(dot(preview0.xy, a_clip01.zw + dynamicOffset), dot(preview1.xy, a_clip01.zw + dynamicOffset)) + vec2(preview0.z, preview1.z);
|
|
595
|
+
vec2 clip2 = vec2(dot(preview0.xy, a_clip23.xy + dynamicOffset), dot(preview1.xy, a_clip23.xy + dynamicOffset)) + vec2(preview0.z, preview1.z);
|
|
596
|
+
vec2 clip3 = vec2(dot(preview0.xy, a_clip23.zw + dynamicOffset), dot(preview1.xy, a_clip23.zw + dynamicOffset)) + vec2(preview0.z, preview1.z);
|
|
597
|
+
v_clip01 = vec4(clip0, clip1);
|
|
598
|
+
v_clip23 = vec4(clip2, clip3);
|
|
599
|
+
v_shapeSize = a_shapeSize;
|
|
600
|
+
v_visible = texture(u_mask, vec2(stateU, 0.5)).r * texture(u_commandMask, vec2(commandU, 0.5)).r;
|
|
601
|
+
}`;
|
|
602
|
+
|
|
603
|
+
const SHAPE_FRAGMENT_SHADER = `#version 300 es
|
|
604
|
+
precision highp float;
|
|
605
|
+
in vec2 v_uv;
|
|
606
|
+
in vec2 v_shapeUv;
|
|
607
|
+
in vec4 v_fill;
|
|
608
|
+
in vec4 v_stroke;
|
|
609
|
+
in vec4 v_params;
|
|
610
|
+
in vec2 v_point;
|
|
611
|
+
in vec4 v_clip01;
|
|
612
|
+
in vec4 v_clip23;
|
|
613
|
+
in vec2 v_shapeSize;
|
|
614
|
+
flat in uint v_visible;
|
|
615
|
+
out vec4 outColor;
|
|
616
|
+
float shapeDistance() {
|
|
617
|
+
if (v_params.x > 1.5) {
|
|
618
|
+
float localRadius = min(v_shapeSize.x, v_shapeSize.y) * 0.5;
|
|
619
|
+
return (length((v_shapeUv - 0.5) * 2.0) - 1.0) * localRadius;
|
|
620
|
+
}
|
|
621
|
+
vec2 point = (v_shapeUv - 0.5) * v_shapeSize;
|
|
622
|
+
vec2 halfSize = v_shapeSize * 0.5;
|
|
623
|
+
if (v_params.x > 0.5) {
|
|
624
|
+
vec2 radius = min(max(v_params.yz, vec2(0.00001)), halfSize);
|
|
625
|
+
vec2 q = abs(point) - halfSize + radius;
|
|
626
|
+
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - min(radius.x, radius.y);
|
|
627
|
+
}
|
|
628
|
+
vec2 q = abs(point) - halfSize;
|
|
629
|
+
return max(q.x, q.y);
|
|
630
|
+
}
|
|
631
|
+
float cross2(vec2 a, vec2 b) { return a.x * b.y - a.y * b.x; }
|
|
632
|
+
bool insideClip() {
|
|
633
|
+
vec2 p[4] = vec2[4](v_clip01.xy, v_clip01.zw, v_clip23.xy, v_clip23.zw);
|
|
634
|
+
float c0 = cross2(p[1] - p[0], v_point - p[0]);
|
|
635
|
+
float c1 = cross2(p[2] - p[1], v_point - p[1]);
|
|
636
|
+
float c2 = cross2(p[3] - p[2], v_point - p[2]);
|
|
637
|
+
float c3 = cross2(p[0] - p[3], v_point - p[3]);
|
|
638
|
+
return (c0 >= 0.0 && c1 >= 0.0 && c2 >= 0.0 && c3 >= 0.0) || (c0 <= 0.0 && c1 <= 0.0 && c2 <= 0.0 && c3 <= 0.0);
|
|
639
|
+
}
|
|
640
|
+
void main() {
|
|
641
|
+
if (v_visible == uint(0)) discard;
|
|
642
|
+
if (!insideClip()) discard;
|
|
643
|
+
float distance = shapeDistance();
|
|
644
|
+
float aa = max(fwidth(distance), 0.0005);
|
|
645
|
+
float fillCoverage = 1.0 - smoothstep(-aa, aa, distance);
|
|
646
|
+
float strokeHalfWidth = v_params.w * 0.5;
|
|
647
|
+
float strokeCoverage = v_params.w > 0.0 ? 1.0 - smoothstep(strokeHalfWidth - aa, strokeHalfWidth + aa, abs(distance)) : 0.0;
|
|
648
|
+
vec4 color = mix(v_fill, v_stroke, strokeCoverage);
|
|
649
|
+
color.a *= max(fillCoverage, strokeCoverage);
|
|
650
|
+
color.rgb *= color.a;
|
|
651
|
+
outColor = color;
|
|
652
|
+
}`;
|
|
653
|
+
|
|
654
|
+
const TEXTURE_FRAGMENT_SHADER = `#version 300 es
|
|
655
|
+
precision highp float;
|
|
656
|
+
in vec2 v_uv;
|
|
657
|
+
in vec2 v_shapeUv;
|
|
658
|
+
in vec4 v_fill;
|
|
659
|
+
in vec4 v_stroke;
|
|
660
|
+
in vec4 v_params;
|
|
661
|
+
in vec2 v_point;
|
|
662
|
+
in vec4 v_clip01;
|
|
663
|
+
in vec4 v_clip23;
|
|
664
|
+
in vec2 v_shapeSize;
|
|
665
|
+
flat in uint v_visible;
|
|
666
|
+
uniform sampler2D u_image;
|
|
667
|
+
uniform sampler2D u_alphaMask;
|
|
668
|
+
uniform int u_hasAlphaMask;
|
|
669
|
+
uniform int u_maskMode;
|
|
670
|
+
out vec4 outColor;
|
|
671
|
+
float cross2(vec2 a, vec2 b) { return a.x * b.y - a.y * b.x; }
|
|
672
|
+
bool insideClip() {
|
|
673
|
+
vec2 p[4] = vec2[4](v_clip01.xy, v_clip01.zw, v_clip23.xy, v_clip23.zw);
|
|
674
|
+
float c0 = cross2(p[1] - p[0], v_point - p[0]);
|
|
675
|
+
float c1 = cross2(p[2] - p[1], v_point - p[1]);
|
|
676
|
+
float c2 = cross2(p[3] - p[2], v_point - p[2]);
|
|
677
|
+
float c3 = cross2(p[0] - p[3], v_point - p[3]);
|
|
678
|
+
return (c0 >= 0.0 && c1 >= 0.0 && c2 >= 0.0 && c3 >= 0.0) || (c0 <= 0.0 && c1 <= 0.0 && c2 <= 0.0 && c3 <= 0.0);
|
|
679
|
+
}
|
|
680
|
+
void main() {
|
|
681
|
+
if (v_visible == uint(0)) discard;
|
|
682
|
+
if (!insideClip()) discard;
|
|
683
|
+
if (v_params.x > 1.5 && length((v_shapeUv - 0.5) * 2.0) > 1.0) discard;
|
|
684
|
+
if (v_params.x > 0.5 && v_params.x < 1.5) {
|
|
685
|
+
vec2 point = (v_shapeUv - 0.5) * v_shapeSize;
|
|
686
|
+
vec2 halfSize = v_shapeSize * 0.5;
|
|
687
|
+
vec2 radius = min(max(v_params.yz, vec2(0.00001)), halfSize);
|
|
688
|
+
vec2 q = abs(point) - halfSize + radius;
|
|
689
|
+
float distance = length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - min(radius.x, radius.y);
|
|
690
|
+
if (distance > 0.0) discard;
|
|
691
|
+
}
|
|
692
|
+
vec4 sampleColor = texture(u_image, v_uv);
|
|
693
|
+
if (u_hasAlphaMask == 1) sampleColor *= texture(u_alphaMask, v_shapeUv).a;
|
|
694
|
+
vec4 color;
|
|
695
|
+
if (u_maskMode == 1) {
|
|
696
|
+
float outlineSize = clamp(v_params.w, 0.0, 1.0);
|
|
697
|
+
float faceThreshold = 0.5 + outlineSize * 0.2375;
|
|
698
|
+
float outlineThreshold = min(1.0 - outlineSize * 0.95 * 0.75, 0.5);
|
|
699
|
+
float sharp = 1.5 / 255.0;
|
|
700
|
+
// Shape textures store the distance field in RGB and the valid sprite
|
|
701
|
+
// domain in alpha. Transparent ASTC padding may legally contain R=1.0;
|
|
702
|
+
// ignoring alpha turns that padding into a translucent face/outline veil.
|
|
703
|
+
float faceCoverage = clamp((sampleColor.r - faceThreshold + sharp) / (2.0 * sharp), 0.0, 1.0) * sampleColor.a;
|
|
704
|
+
float outerCoverage = clamp((sampleColor.r - outlineThreshold + sharp) / (2.0 * sharp), 0.0, 1.0) * sampleColor.a;
|
|
705
|
+
float outlineCoverage = outerCoverage * (1.0 - faceCoverage);
|
|
706
|
+
float faceAlpha = faceCoverage * floor(clamp(v_fill.a, 0.0, 1.0) * 255.0) / 255.0;
|
|
707
|
+
float outlineAlpha = outlineCoverage * floor(clamp(v_stroke.a, 0.0, 1.0) * 255.0) / 255.0;
|
|
708
|
+
color = vec4(
|
|
709
|
+
v_fill.rgb * faceAlpha + v_stroke.rgb * outlineAlpha * (1.0 - faceAlpha),
|
|
710
|
+
faceAlpha + outlineAlpha * (1.0 - faceAlpha)
|
|
711
|
+
);
|
|
712
|
+
} else {
|
|
713
|
+
color = sampleColor * v_fill;
|
|
714
|
+
color.rgb *= color.a;
|
|
715
|
+
}
|
|
716
|
+
outColor = color;
|
|
717
|
+
}`;
|
|
718
|
+
|
|
719
|
+
const COMPOSITE_VERTEX_SHADER = `#version 300 es
|
|
720
|
+
precision highp float;
|
|
721
|
+
out vec2 v_uv;
|
|
722
|
+
void main() {
|
|
723
|
+
vec2 positions[6] = vec2[6](
|
|
724
|
+
vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(1.0, 1.0),
|
|
725
|
+
vec2(-1.0, -1.0), vec2(1.0, 1.0), vec2(-1.0, 1.0)
|
|
726
|
+
);
|
|
727
|
+
vec2 position = positions[gl_VertexID];
|
|
728
|
+
v_uv = position * 0.5 + 0.5;
|
|
729
|
+
gl_Position = vec4(position, 0.0, 1.0);
|
|
730
|
+
}`;
|
|
731
|
+
|
|
732
|
+
const COMPOSITE_FRAGMENT_SHADER = `#version 300 es
|
|
733
|
+
precision highp float;
|
|
734
|
+
in vec2 v_uv;
|
|
735
|
+
uniform sampler2D u_image;
|
|
736
|
+
out vec4 outColor;
|
|
737
|
+
void main() {
|
|
738
|
+
outColor = texture(u_image, v_uv);
|
|
739
|
+
}`;
|