@longzai-intelligence-liquid-glass/webgpu 0.0.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 ADDED
@@ -0,0 +1,12 @@
1
+ # @longzai-intelligence-liquid-glass/webgpu
2
+
3
+ Liquid Glass WebGPU 渲染后端(experimental):WGSL 多 pass 管线。
4
+
5
+ ## 用法
6
+
7
+ ```bash
8
+ bun run --cwd packages/webgpu lint # 代码检查
9
+ bun run --cwd packages/webgpu typecheck # 类型检查(tsgo)
10
+ bun run --cwd packages/webgpu test # 单元测试
11
+ bun run --cwd packages/webgpu build # 构建(lzi-builder → dist)
12
+ ```
@@ -0,0 +1,266 @@
1
+ import { BackdropMetrics, LiquidGlassParams, LiquidGlassRenderer, LiquidGlassSize, RendererCapability } from "@longzai-intelligence-liquid-glass/core";
2
+ //#region src/shaders.constants.d.ts
3
+ /**
4
+ * WebGPU 着色器(WGSL)
5
+ *
6
+ * liquid-dom 的多 pass 概念落地(experimental):
7
+ * - 主合成 pass:SDF 高度剖面 → 折射位移 → 三通道色散 → tint/specular;
8
+ * - 降采样 / 升采样 pass:渐进模糊(box 降采样 + 双线性升采样);
9
+ * - 亮度统计 pass:输出背景亮度,驱动自适应色调(liquid-dom adaptive tint)。
10
+ */
11
+ /**
12
+ * uniform 参数结构
13
+ */
14
+ declare const PARAMS_STRUCT = "\nstruct Params {\n resolution: vec2f,\n halfSize: vec2f,\n radius: f32,\n refraction: f32,\n band: f32,\n bevel: f32,\n bevelExp: f32,\n dispersion: f32,\n blur: f32,\n frost: f32,\n tint: vec4f,\n saturation: f32,\n brightness: f32,\n specular: f32,\n specularExp: f32,\n tilt: vec2f,\n time: f32,\n _pad0: f32,\n _pad1: f32,\n};\n";
15
+ /**
16
+ * 顶点着色器(全屏 quad,输出 uv)
17
+ */
18
+ declare const VERTEX_WGSL = "\nstruct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n};\n\n@vertex\nfn main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\n var quad = array<vec2f, 4>(\n vec2f(-1.0, -1.0),\n vec2f(1.0, -1.0),\n vec2f(-1.0, 1.0),\n vec2f(1.0, 1.0),\n );\n let pos = quad[vertexIndex];\n var output: VertexOutput;\n output.position = vec4f(pos, 0.0, 1.0);\n output.uv = pos * 0.5 + vec2f(0.5);\n return output;\n}\n";
19
+ /**
20
+ * 降采样 pass:4 采样 box 滤波(liquid-dom DOWNSAMPLE 思路)
21
+ */
22
+ declare const DOWNSAMPLE_WGSL = "\n@group(0) @binding(0) var srcSampler: sampler;\n@group(0) @binding(1) var srcTex: texture_2d<f32>;\n\n@fragment\nfn main(@location(0) uv: vec2f) -> @location(0) vec4f {\n let texel = 1.0 / vec2f(textureDimensions(srcTex));\n var color = textureSample(srcTex, srcSampler, uv) * 4.0;\n color += textureSample(srcTex, srcSampler, uv + vec2f(texel.x, texel.y));\n color += textureSample(srcTex, srcSampler, uv + vec2f(-texel.x, texel.y));\n color += textureSample(srcTex, srcSampler, uv + vec2f(texel.x, -texel.y));\n color += textureSample(srcTex, srcSampler, uv + vec2f(-texel.x, -texel.y));\n return color / 8.0;\n}\n";
23
+ /**
24
+ * 升采样 pass:双线性 4 tap(liquid-dom UPSAMPLE 思路)
25
+ */
26
+ declare const UPSAMPLE_WGSL = "\n@group(0) @binding(0) var srcSampler: sampler;\n@group(0) @binding(1) var srcTex: texture_2d<f32>;\n\n@fragment\nfn main(@location(0) uv: vec2f) -> @location(0) vec4f {\n let texel = 1.0 / vec2f(textureDimensions(srcTex));\n var color = textureSample(srcTex, srcSampler, uv) * 4.0;\n color += textureSample(srcTex, srcSampler, uv + vec2f(texel.x, 0.0));\n color += textureSample(srcTex, srcSampler, uv + vec2f(-texel.x, 0.0));\n color += textureSample(srcTex, srcSampler, uv + vec2f(0.0, texel.y));\n color += textureSample(srcTex, srcSampler, uv + vec2f(0.0, -texel.y));\n return color / 8.0;\n}\n";
27
+ /**
28
+ * 主合成 pass:完整玻璃管线(位移折射 + 三通道色散 + tint + rim specular)
29
+ */
30
+ declare const MAIN_WGSL = "\n\nstruct Params {\n resolution: vec2f,\n halfSize: vec2f,\n radius: f32,\n refraction: f32,\n band: f32,\n bevel: f32,\n bevelExp: f32,\n dispersion: f32,\n blur: f32,\n frost: f32,\n tint: vec4f,\n saturation: f32,\n brightness: f32,\n specular: f32,\n specularExp: f32,\n tilt: vec2f,\n time: f32,\n _pad0: f32,\n _pad1: f32,\n};\n\n\n@group(0) @binding(0) var params: Params;\n@group(0) @binding(1) var backdropSampler: sampler;\n@group(0) @binding(2) var backdropTex: texture_2d<f32>;\n\n/**\n * 圆角矩形 SDF\n */\nfn sdRoundBox(p: vec2f, b: vec2f, r: f32) -> f32 {\n let q = abs(p) - b + vec2f(r);\n let outside = length(max(q, vec2f(0.0)));\n let inside = min(max(q.x, q.y), 0.0);\n return outside + inside - r;\n}\n\n/**\n * SDF 数值梯度 → 单位外法线\n */\nfn sdNormal(p: vec2f, b: vec2f, r: f32) -> vec2f {\n let e = 1.0;\n let dx = sdRoundBox(p + vec2f(e, 0.0), b, r) - sdRoundBox(p - vec2f(e, 0.0), b, r);\n let dy = sdRoundBox(p + vec2f(0.0, e), b, r) - sdRoundBox(p - vec2f(0.0, e), b, r);\n return normalize(vec2f(dx, dy) + vec2f(1e-6));\n}\n\n@fragment\nfn main(@location(0) uv: vec2f) -> @location(0) vec4f {\n let p = (uv - vec2f(0.5)) * params.resolution + params.tilt * 20.0;\n let sdf = sdRoundBox(p, params.halfSize, params.radius);\n let normal = sdNormal(p, params.halfSize, params.radius);\n\n let edge = clamp(1.0 + sdf / max(params.band, 1e-4), 0.0, 1.0);\n let height = params.refraction * edge + params.bevel * pow(edge, params.bevelExp);\n let displacement = -normal * height * params.band;\n\n let uvDisp = uv + displacement / params.resolution;\n\n // 三通道色散:R/B 沿位移对称缩放(蓝端更大)\n let rScale = 1.0 - 0.5 * params.dispersion;\n let bScale = 1.0 + 0.7 * params.dispersion;\n var color = vec3f(\n textureSample(backdropTex, backdropSampler, uv + displacement * rScale / params.resolution).r,\n textureSample(backdropTex, backdropSampler, uvDisp).g,\n textureSample(backdropTex, backdropSampler, uv + displacement * bScale / params.resolution).b,\n );\n\n // 饱和度 / 亮度\n let luma = dot(color, vec3f(0.299, 0.587, 0.114));\n color = mix(vec3f(luma), color, params.saturation) * params.brightness;\n\n // tint 着色\n color = mix(color, params.tint.rgb, params.tint.a);\n\n // 双光斑 rim specular(主光随时间轻移 + 对侧镜像光)\n let lightDir = normalize(vec2f(cos(params.time * 0.3), sin(params.time * 0.3)) + vec2f(0.6, -0.6));\n let rimMain = pow(clamp(dot(normal, lightDir), 0.0, 1.0), params.specularExp);\n let rimMirror = pow(clamp(dot(normal, -lightDir), 0.0, 1.0), params.specularExp) * 0.5;\n color += vec3f((rimMain + rimMirror) * params.specular * edge);\n\n // SDF mask(1.5px 抗锯齿)\n let mask = 1.0 - smoothstep(-1.5, 1.5, sdf);\n return vec4f(color, mask);\n}\n";
31
+ /**
32
+ * 亮度统计 pass:输出背景亮度(r 通道),供 readback 直方图求 P50/P90
33
+ */
34
+ declare const LUMA_WGSL = "\n@group(0) @binding(0) var srcSampler: sampler;\n@group(0) @binding(1) var srcTex: texture_2d<f32>;\n\n@fragment\nfn main(@location(0) uv: vec2f) -> @location(0) vec4f {\n let c = textureSample(srcTex, srcSampler, uv).rgb;\n let luma = dot(c, vec3f(0.299, 0.587, 0.114));\n return vec4f(luma, 0.0, 0.0, 1.0);\n}\n";
35
+ //#endregion
36
+ //#region src/renderer-context.types.d.ts
37
+ /**
38
+ * 背景源(帧 + 脏标记;HTML-in-Canvas 实验特性开启时可用 DOM canvas 源,
39
+ * 否则使用图片 / 视频 / canvas 纹理源降级)
40
+ */
41
+ type WebGPUBackdropSource = {
42
+ /**
43
+ * 背景源标识
44
+ */
45
+ readonly id: string;
46
+ /**
47
+ * 捕获当前背景帧(canvas / image / video)
48
+ *
49
+ * @returns 可上传 WebGPU 纹理的帧
50
+ */
51
+ capture(): HTMLCanvasElement | HTMLImageElement | HTMLVideoElement | Promise<HTMLCanvasElement | HTMLImageElement | HTMLVideoElement>;
52
+ /**
53
+ * 帧是否变化(决定是否重传纹理)
54
+ */
55
+ isDirty(): boolean;
56
+ /**
57
+ * 释放资源
58
+ */
59
+ dispose(): void;
60
+ };
61
+ /**
62
+ * 渲染上下文(构造入参)
63
+ */
64
+ type WebGPURendererContext = {
65
+ /**
66
+ * 初始参数
67
+ */
68
+ params: LiquidGlassParams;
69
+ /**
70
+ * 初始尺寸
71
+ */
72
+ size: LiquidGlassSize;
73
+ /**
74
+ * 背景源(缺省时渲染纯 tint 玻璃)
75
+ */
76
+ backdropSource?: WebGPUBackdropSource;
77
+ };
78
+ //#endregion
79
+ //#region src/webgpu-renderer.utils.d.ts
80
+ /**
81
+ * WebGPU 渲染器(experimental)
82
+ */
83
+ declare class WebGPURenderer implements LiquidGlassRenderer<HTMLElement> {
84
+ /**
85
+ * 后端标识
86
+ */
87
+ readonly backend: 'webgpu';
88
+ /**
89
+ * 宿主元素
90
+ */
91
+ private host;
92
+ /**
93
+ * 渲染 canvas
94
+ */
95
+ private canvas;
96
+ /**
97
+ * WebGPU 上下文
98
+ */
99
+ private context;
100
+ /**
101
+ * GPU 设备
102
+ */
103
+ private device;
104
+ /**
105
+ * 输出纹理格式
106
+ */
107
+ private format;
108
+ /**
109
+ * uniform 缓冲
110
+ */
111
+ private uniformBuffer;
112
+ /**
113
+ * uniform 浮点数组
114
+ */
115
+ private uniformData;
116
+ /**
117
+ * 管线集合
118
+ */
119
+ private pipelines;
120
+ /**
121
+ * 纹理与缓冲集合
122
+ */
123
+ private resources;
124
+ /**
125
+ * 当前参数
126
+ */
127
+ private params;
128
+ /**
129
+ * 当前尺寸
130
+ */
131
+ private size;
132
+ /**
133
+ * 背景源
134
+ */
135
+ private source;
136
+ /**
137
+ * 初始化完成 promise
138
+ */
139
+ private ready;
140
+ /**
141
+ * 渲染循环 id
142
+ */
143
+ private rafId;
144
+ /**
145
+ * 捕获进行中标记
146
+ */
147
+ private capturing;
148
+ /**
149
+ * 上次 metrics 采样时间(节流)
150
+ */
151
+ private lastMetricsTime;
152
+ /**
153
+ * 构造渲染器
154
+ *
155
+ * @param context - 渲染上下文
156
+ */
157
+ constructor(context: WebGPURendererContext);
158
+ /**
159
+ * 等待设备初始化完成
160
+ *
161
+ * @returns 初始化 promise
162
+ */
163
+ whenReady(): Promise<void>;
164
+ /**
165
+ * 挂载到 DOM 元素
166
+ *
167
+ * @param target - 宿主元素
168
+ */
169
+ mount(target: HTMLElement): void;
170
+ /**
171
+ * 热更新参数
172
+ *
173
+ * @param params - 完整参数
174
+ */
175
+ update(params: LiquidGlassParams): void;
176
+ /**
177
+ * 尺寸变化
178
+ *
179
+ * @param size - 新尺寸
180
+ */
181
+ resize(size: LiquidGlassSize): void;
182
+ /**
183
+ * 更换背景源
184
+ *
185
+ * @param source - WebGPU 背景源
186
+ */
187
+ setBackdropSource(source: WebGPUBackdropSource): void;
188
+ /**
189
+ * 获取能力描述(experimental 标注)
190
+ *
191
+ * @returns 能力描述
192
+ */
193
+ getCapabilities(): RendererCapability;
194
+ /**
195
+ * 读取背景亮度统计(250ms 节流;自适应色调数据源)
196
+ *
197
+ * @returns 亮度统计;未就绪返回 null
198
+ */
199
+ getMetrics(): Promise<BackdropMetrics | null>;
200
+ /**
201
+ * 卸载并释放资源(幂等)
202
+ */
203
+ unmount(): void;
204
+ /**
205
+ * 初始化设备、管线与资源
206
+ */
207
+ private init;
208
+ /**
209
+ * 构建四条渲染管线
210
+ *
211
+ * @param device - GPU 设备
212
+ */
213
+ private buildPipelines;
214
+ /**
215
+ * 构建尺寸无关资源(uniform 缓冲、采样器、亮度统计)
216
+ *
217
+ * @param device - GPU 设备
218
+ */
219
+ private buildStaticResources;
220
+ /**
221
+ * 重建尺寸相关纹理(背景 / 模糊 ping-pong)
222
+ */
223
+ private recreateTargets;
224
+ /**
225
+ * 捕获背景并上传纹理
226
+ */
227
+ private captureBackdrop;
228
+ /**
229
+ * 启动渲染循环
230
+ */
231
+ private startLoop;
232
+ /**
233
+ * 渲染一帧(可选渐进模糊 pass + 主合成 pass)
234
+ */
235
+ private renderFrame;
236
+ }
237
+ //#endregion
238
+ //#region src/support.utils.d.ts
239
+ /**
240
+ * WebGPU 路线支持检测
241
+ */
242
+ /**
243
+ * 支持检测结果
244
+ */
245
+ type WebGPUSupport = {
246
+ /**
247
+ * 是否支持 WebGPU 路线
248
+ */
249
+ supported: boolean;
250
+ /**
251
+ * 不支持原因(支持时为空)
252
+ */
253
+ reason: string;
254
+ /**
255
+ * HTML-in-Canvas(DOM 折射)实验特性是否可用
256
+ */
257
+ htmlInCanvas: boolean;
258
+ };
259
+ /**
260
+ * 检测当前环境对 WebGPU 的支持
261
+ *
262
+ * @returns 支持检测结果
263
+ */
264
+ declare function detectWebGPUSupport(): WebGPUSupport;
265
+ //#endregion
266
+ export { DOWNSAMPLE_WGSL, LUMA_WGSL, MAIN_WGSL, PARAMS_STRUCT, UPSAMPLE_WGSL, VERTEX_WGSL, type WebGPUBackdropSource, WebGPURenderer, type WebGPURendererContext, type WebGPUSupport, detectWebGPUSupport };
package/dist/index.js ADDED
@@ -0,0 +1,146 @@
1
+ const e=`
2
+ struct Params {
3
+ resolution: vec2f,
4
+ halfSize: vec2f,
5
+ radius: f32,
6
+ refraction: f32,
7
+ band: f32,
8
+ bevel: f32,
9
+ bevelExp: f32,
10
+ dispersion: f32,
11
+ blur: f32,
12
+ frost: f32,
13
+ tint: vec4f,
14
+ saturation: f32,
15
+ brightness: f32,
16
+ specular: f32,
17
+ specularExp: f32,
18
+ tilt: vec2f,
19
+ time: f32,
20
+ _pad0: f32,
21
+ _pad1: f32,
22
+ };
23
+ `,t=`
24
+ struct VertexOutput {
25
+ @builtin(position) position: vec4f,
26
+ @location(0) uv: vec2f,
27
+ };
28
+
29
+ @vertex
30
+ fn main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
31
+ var quad = array<vec2f, 4>(
32
+ vec2f(-1.0, -1.0),
33
+ vec2f(1.0, -1.0),
34
+ vec2f(-1.0, 1.0),
35
+ vec2f(1.0, 1.0),
36
+ );
37
+ let pos = quad[vertexIndex];
38
+ var output: VertexOutput;
39
+ output.position = vec4f(pos, 0.0, 1.0);
40
+ output.uv = pos * 0.5 + vec2f(0.5);
41
+ return output;
42
+ }
43
+ `,n=`
44
+ @group(0) @binding(0) var srcSampler: sampler;
45
+ @group(0) @binding(1) var srcTex: texture_2d<f32>;
46
+
47
+ @fragment
48
+ fn main(@location(0) uv: vec2f) -> @location(0) vec4f {
49
+ let texel = 1.0 / vec2f(textureDimensions(srcTex));
50
+ var color = textureSample(srcTex, srcSampler, uv) * 4.0;
51
+ color += textureSample(srcTex, srcSampler, uv + vec2f(texel.x, texel.y));
52
+ color += textureSample(srcTex, srcSampler, uv + vec2f(-texel.x, texel.y));
53
+ color += textureSample(srcTex, srcSampler, uv + vec2f(texel.x, -texel.y));
54
+ color += textureSample(srcTex, srcSampler, uv + vec2f(-texel.x, -texel.y));
55
+ return color / 8.0;
56
+ }
57
+ `,r=`
58
+ @group(0) @binding(0) var srcSampler: sampler;
59
+ @group(0) @binding(1) var srcTex: texture_2d<f32>;
60
+
61
+ @fragment
62
+ fn main(@location(0) uv: vec2f) -> @location(0) vec4f {
63
+ let texel = 1.0 / vec2f(textureDimensions(srcTex));
64
+ var color = textureSample(srcTex, srcSampler, uv) * 4.0;
65
+ color += textureSample(srcTex, srcSampler, uv + vec2f(texel.x, 0.0));
66
+ color += textureSample(srcTex, srcSampler, uv + vec2f(-texel.x, 0.0));
67
+ color += textureSample(srcTex, srcSampler, uv + vec2f(0.0, texel.y));
68
+ color += textureSample(srcTex, srcSampler, uv + vec2f(0.0, -texel.y));
69
+ return color / 8.0;
70
+ }
71
+ `,i=`
72
+ ${e}
73
+
74
+ @group(0) @binding(0) var params: Params;
75
+ @group(0) @binding(1) var backdropSampler: sampler;
76
+ @group(0) @binding(2) var backdropTex: texture_2d<f32>;
77
+
78
+ /**
79
+ * 圆角矩形 SDF
80
+ */
81
+ fn sdRoundBox(p: vec2f, b: vec2f, r: f32) -> f32 {
82
+ let q = abs(p) - b + vec2f(r);
83
+ let outside = length(max(q, vec2f(0.0)));
84
+ let inside = min(max(q.x, q.y), 0.0);
85
+ return outside + inside - r;
86
+ }
87
+
88
+ /**
89
+ * SDF 数值梯度 → 单位外法线
90
+ */
91
+ fn sdNormal(p: vec2f, b: vec2f, r: f32) -> vec2f {
92
+ let e = 1.0;
93
+ let dx = sdRoundBox(p + vec2f(e, 0.0), b, r) - sdRoundBox(p - vec2f(e, 0.0), b, r);
94
+ let dy = sdRoundBox(p + vec2f(0.0, e), b, r) - sdRoundBox(p - vec2f(0.0, e), b, r);
95
+ return normalize(vec2f(dx, dy) + vec2f(1e-6));
96
+ }
97
+
98
+ @fragment
99
+ fn main(@location(0) uv: vec2f) -> @location(0) vec4f {
100
+ let p = (uv - vec2f(0.5)) * params.resolution + params.tilt * 20.0;
101
+ let sdf = sdRoundBox(p, params.halfSize, params.radius);
102
+ let normal = sdNormal(p, params.halfSize, params.radius);
103
+
104
+ let edge = clamp(1.0 + sdf / max(params.band, 1e-4), 0.0, 1.0);
105
+ let height = params.refraction * edge + params.bevel * pow(edge, params.bevelExp);
106
+ let displacement = -normal * height * params.band;
107
+
108
+ let uvDisp = uv + displacement / params.resolution;
109
+
110
+ // 三通道色散:R/B 沿位移对称缩放(蓝端更大)
111
+ let rScale = 1.0 - 0.5 * params.dispersion;
112
+ let bScale = 1.0 + 0.7 * params.dispersion;
113
+ var color = vec3f(
114
+ textureSample(backdropTex, backdropSampler, uv + displacement * rScale / params.resolution).r,
115
+ textureSample(backdropTex, backdropSampler, uvDisp).g,
116
+ textureSample(backdropTex, backdropSampler, uv + displacement * bScale / params.resolution).b,
117
+ );
118
+
119
+ // 饱和度 / 亮度
120
+ let luma = dot(color, vec3f(0.299, 0.587, 0.114));
121
+ color = mix(vec3f(luma), color, params.saturation) * params.brightness;
122
+
123
+ // tint 着色
124
+ color = mix(color, params.tint.rgb, params.tint.a);
125
+
126
+ // 双光斑 rim specular(主光随时间轻移 + 对侧镜像光)
127
+ let lightDir = normalize(vec2f(cos(params.time * 0.3), sin(params.time * 0.3)) + vec2f(0.6, -0.6));
128
+ let rimMain = pow(clamp(dot(normal, lightDir), 0.0, 1.0), params.specularExp);
129
+ let rimMirror = pow(clamp(dot(normal, -lightDir), 0.0, 1.0), params.specularExp) * 0.5;
130
+ color += vec3f((rimMain + rimMirror) * params.specular * edge);
131
+
132
+ // SDF mask(1.5px 抗锯齿)
133
+ let mask = 1.0 - smoothstep(-1.5, 1.5, sdf);
134
+ return vec4f(color, mask);
135
+ }
136
+ `,a=`
137
+ @group(0) @binding(0) var srcSampler: sampler;
138
+ @group(0) @binding(1) var srcTex: texture_2d<f32>;
139
+
140
+ @fragment
141
+ fn main(@location(0) uv: vec2f) -> @location(0) vec4f {
142
+ let c = textureSample(srcTex, srcSampler, uv).rgb;
143
+ let luma = dot(c, vec3f(0.299, 0.587, 0.114));
144
+ return vec4f(luma, 0.0, 0.0, 1.0);
145
+ }
146
+ `;function o(e){let t=e.reduce((e,t)=>e+t,0);if(t===0)return{averageLuminance:0,luminanceP50:0,luminanceP90:0};let n=0;for(let t=0;t<e.length;t+=1)n+=(e[t]??0)*t;let r=n=>{let r=0;for(let i=0;i<e.length;i+=1)if(r+=e[i]??0,r/t>=n)return i/255;return 1};return{averageLuminance:n/t/255,luminanceP50:r(.5),luminanceP90:r(.9)}}function s(e){let t=Array(256).fill(0);for(let n=0;n+3<e.length;n+=4){let r=e[n]??0;t[r]=(t[r]??0)+1}return t}function c(e,t,n){return e.beginRenderPass({colorAttachments:[{view:t.createView(),loadOp:`clear`,storeOp:`store`,clearValue:{r:0,g:0,b:0,a:n}}]})}function l(e,t,n,r){return e.createBindGroup({layout:t.getBindGroupLayout(0),entries:[{binding:0,resource:n},{binding:1,resource:r}]})}function u(e,t,n,r,i){if(r.blur+r.frost*8<=.5)return n.backdrop.createView();let a=c(i,n.blurHalf,1);a.setPipeline(t.down),a.setBindGroup(0,l(e,t.down,n.sampler,n.backdrop.createView())),a.draw(4),a.end();let o=c(i,n.blurFull,1);return o.setPipeline(t.up),o.setBindGroup(0,l(e,t.up,n.sampler,n.blurHalf.createView())),o.draw(4),o.end(),n.blurFull.createView()}function d(e,t,n,r){let i=c(r,n.luma,1);i.setPipeline(t.luma),i.setBindGroup(0,l(e,t.luma,n.sampler,n.backdrop.createView())),i.draw(4),i.end(),r.copyTextureToBuffer({texture:n.luma},{buffer:n.lumaBuffer,bytesPerRow:256,rowsPerImage:64},[64,64])}function f(e,t,n){let r=t.shape===`circle`?Math.min(t.width,t.height)/2:t.width/2,i=t.shape===`circle`?Math.min(t.width,t.height)/2:t.height/2,a=t.shape===`circle`?Math.min(r,i):Math.min(t.cornerRadius,Math.min(r,i)-.5);e[0]=n.width,e[1]=n.height,e[2]=r,e[3]=i,e[4]=a,e[5]=t.refraction,e[6]=t.refractionHeight,e[7]=t.bevel,e[8]=t.bevelExponent,e[9]=t.dispersion,e[10]=t.blur,e[11]=t.frost,e[12]=t.tint.r,e[13]=t.tint.g,e[14]=t.tint.b,e[15]=t.tint.a,e[16]=t.saturation,e[17]=t.brightness,e[18]=t.specular,e[19]=t.specularExponent,e[20]=0,e[21]=0,e[22]=performance.now()/1e3}var p=class{backend=`webgpu`;host=null;canvas=null;context=null;device=null;format=`bgra8unorm`;uniformBuffer=null;uniformData=new Float32Array(28);pipelines=null;resources=null;params;size;source=null;ready=Promise.resolve();rafId=0;capturing=!1;lastMetricsTime=0;constructor(e){this.params=e.params,this.size=e.size,this.source=e.backdropSource??null}whenReady(){return this.ready}mount(e){this.host=e;let t=document.createElement(`canvas`);t.style.position=`absolute`,t.style.inset=`0`,t.style.width=`100%`,t.style.height=`100%`,t.style.pointerEvents=`none`,t.style.borderRadius=this.params.shape===`circle`?`50%`:`${this.params.cornerRadius}px`,getComputedStyle(e).position===`static`&&(e.style.position=`relative`),e.appendChild(t),this.canvas=t,this.ready=this.init()}update(e){this.params=e,this.canvas!==null&&(this.canvas.style.borderRadius=e.shape===`circle`?`50%`:`${e.cornerRadius}px`)}resize(e){this.size=e,this.recreateTargets()}setBackdropSource(e){this.source=e}getCapabilities(){return{backend:`webgpu`,supportsRefraction:!0,supportsDispersion:!0,supportsBlur:!0,supportsSpecular:!0,supportsDomBackdrop:!1,experimental:`依赖 WebGPU;DOM 折射需 HTML-in-Canvas 实验特性,未开启时使用纹理源降级`}}async getMetrics(){let e=performance.now(),t=this.device;if(e-this.lastMetricsTime<250||t===null||this.pipelines===null||this.resources===null)return null;this.lastMetricsTime=e;try{let e=t.createCommandEncoder();d(t,this.pipelines,this.resources,e),t.queue.submit([e.finish()]),await this.resources.lumaBuffer.mapAsync(GPUMapMode.READ);let n=new Uint8Array(this.resources.lumaBuffer.getMappedRange().slice(0));return this.resources.lumaBuffer.unmap(),o(s(n))}catch{return null}}unmount(){cancelAnimationFrame(this.rafId),this.source!==null&&this.source.dispose(),this.resources?.backdrop.destroy(),this.resources?.blurHalf.destroy(),this.resources?.blurFull.destroy(),this.resources?.luma.destroy(),this.resources?.lumaBuffer.destroy(),this.uniformBuffer?.destroy(),this.device?.destroy(),this.canvas!==null&&this.canvas.remove(),this.host=null,this.canvas=null,this.context=null,this.device=null,this.resources=null,this.source=null}async init(){if(this.canvas===null||navigator.gpu===void 0)return;let e=await navigator.gpu.requestAdapter();if(e===null)return;let t=await e.requestDevice(),n=this.canvas.getContext(`webgpu`);if(n===null)return;this.device=t,this.context=n,this.format=navigator.gpu.getPreferredCanvasFormat();let r=window.devicePixelRatio>0?window.devicePixelRatio:1;this.canvas.width=Math.max(2,Math.round(this.size.width*r)),this.canvas.height=Math.max(2,Math.round(this.size.height*r)),n.configure({device:t,format:this.format,alphaMode:`premultiplied`}),this.buildPipelines(t),this.buildStaticResources(t),await this.recreateTargets(),await this.captureBackdrop(),this.startLoop()}buildPipelines(e){let t=(t,n)=>({layout:`auto`,vertex:{module:e.createShaderModule({code:t}),entryPoint:`main`},fragment:{module:e.createShaderModule({code:t}),entryPoint:`main`,targets:[{format:n}]},primitive:{topology:`triangle-strip`}});this.pipelines={down:e.createRenderPipeline(t(n,`rgba8unorm`)),up:e.createRenderPipeline(t(r,`rgba8unorm`)),main:e.createRenderPipeline(t(i,this.format)),luma:e.createRenderPipeline(t(a,`rgba8unorm`))}}buildStaticResources(e){this.uniformBuffer=e.createBuffer({size:112,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),this.resources={backdrop:m(e),blurHalf:m(e),blurFull:m(e),luma:e.createTexture({size:[64,64],format:`rgba8unorm`,usage:GPUTextureUsage.RENDER_ATTACHMENT|GPUTextureUsage.COPY_SRC}),lumaBuffer:e.createBuffer({size:16384,usage:GPUBufferUsage.COPY_DST|GPUBufferUsage.MAP_READ}),sampler:e.createSampler({addressModeU:`clamp-to-edge`,addressModeV:`clamp-to-edge`,minFilter:`linear`,magFilter:`linear`})}}async recreateTargets(){let e=this.device;if(e===null||this.canvas===null||this.resources===null)return;let t=Math.max(2,this.canvas.width),n=Math.max(2,this.canvas.height);this.resources.backdrop.destroy(),this.resources.blurHalf.destroy(),this.resources.blurFull.destroy(),this.resources.backdrop=e.createTexture({size:[t,n],format:`rgba8unorm`,usage:GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.COPY_DST|GPUTextureUsage.RENDER_ATTACHMENT}),this.resources.blurHalf=e.createTexture({size:[Math.max(2,Math.floor(t/2)),Math.max(2,Math.floor(n/2))],format:`rgba8unorm`,usage:GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.RENDER_ATTACHMENT}),this.resources.blurFull=e.createTexture({size:[t,n],format:`rgba8unorm`,usage:GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.RENDER_ATTACHMENT})}async captureBackdrop(){let e=this.device;if(!(this.source===null||e===null||this.resources===null||this.canvas===null||this.capturing)){this.capturing=!0;try{let t=await this.source.capture();e.queue.copyExternalImageToTexture({source:t},{texture:this.resources.backdrop},[this.canvas.width,this.canvas.height])}catch{}finally{this.capturing=!1}}}startLoop(){let e=()=>{this.source!==null&&this.source.isDirty()&&this.captureBackdrop(),this.renderFrame(),this.rafId=requestAnimationFrame(e)};this.rafId=requestAnimationFrame(e)}renderFrame(){let e=this.device;if(e===null||this.context===null||this.pipelines===null||this.resources===null||this.uniformBuffer===null)return;f(this.uniformData,this.params,this.size),e.queue.writeBuffer(this.uniformBuffer,0,this.uniformData);let t=e.createCommandEncoder(),n=u(e,this.pipelines,this.resources,this.params,t),r=c(t,this.context.getCurrentTexture(),0);r.setPipeline(this.pipelines.main),r.setBindGroup(0,e.createBindGroup({layout:this.pipelines.main.getBindGroupLayout(0),entries:[{binding:0,resource:{buffer:this.uniformBuffer}},{binding:1,resource:this.resources.sampler},{binding:2,resource:n}]})),r.draw(4),r.end(),e.queue.submit([t.finish()])}};function m(e){return e.createTexture({size:[1,1],format:`rgba8unorm`,usage:GPUTextureUsage.TEXTURE_BINDING})}function h(){return typeof navigator>`u`||typeof window>`u`?{supported:!1,reason:`SSR 或非浏览器环境`,htmlInCanvas:!1}:navigator.gpu===void 0?{supported:!1,reason:`navigator.gpu 不可用`,htmlInCanvas:!1}:{supported:!0,reason:``,htmlInCanvas:typeof HTMLCanvasElement<`u`&&`layoutSubtree`in HTMLCanvasElement.prototype}}export{n as DOWNSAMPLE_WGSL,a as LUMA_WGSL,i as MAIN_WGSL,e as PARAMS_STRUCT,r as UPSAMPLE_WGSL,t as VERTEX_WGSL,p as WebGPURenderer,h as detectWebGPUSupport};
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@longzai-intelligence-liquid-glass/webgpu",
3
+ "version": "0.0.1",
4
+ "description": "Liquid Glass WebGPU 渲染后端(experimental)",
5
+ "license": "UNLICENSED",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "type": "module",
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "build": "lzi-builder",
20
+ "build:prod": "NODE_ENV=production bun run build",
21
+ "prepublishOnly": "bun run build:prod",
22
+ "typecheck": "bun run typecheck:app && bun run typecheck:node && bun run typecheck:test",
23
+ "typecheck:app": "lzi-tsgo typecheck tsconfig/app.json",
24
+ "typecheck:node": "lzi-tsgo typecheck tsconfig/node.json",
25
+ "typecheck:test": "lzi-tsgo typecheck tsconfig/test.json",
26
+ "lint": "oxlint && oxfmt --check",
27
+ "lint:fix": "oxlint --fix && oxfmt",
28
+ "test": "lzi-bun-cli test",
29
+ "test:watch": "lzi-bun-cli test --watch",
30
+ "test:coverage": "lzi-bun-cli test --coverage",
31
+ "clean": "lzi-dev-cli clean"
32
+ },
33
+ "dependencies": {
34
+ "@longzai-intelligence-liquid-glass/core": "0.0.1"
35
+ },
36
+ "devDependencies": {
37
+ "@webgpu/types": "^0.1.64"
38
+ },
39
+ "packageManager": "bun@1.3.14"
40
+ }