lgimgui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -0
- package/demo/public/materials.json +809 -0
- package/package.json +39 -0
- package/src/core/curve.js +71 -0
- package/src/core/frame.js +272 -0
- package/src/core/geometry.js +61 -0
- package/src/core/ids.js +31 -0
- package/src/core/input.js +294 -0
- package/src/core/layout.js +220 -0
- package/src/core/spring.js +113 -0
- package/src/core/state.js +54 -0
- package/src/core/theme.js +77 -0
- package/src/index.js +221 -0
- package/src/material/adapt.js +72 -0
- package/src/material/material.js +131 -0
- package/src/material/optics.js +49 -0
- package/src/material/params.js +83 -0
- package/src/material/presets.js +291 -0
- package/src/render/batch.js +236 -0
- package/src/render/context.js +138 -0
- package/src/render/glyphs.js +177 -0
- package/src/render/probe.js +64 -0
- package/src/render/pyramid.js +48 -0
- package/src/render/renderer.js +296 -0
- package/src/render/shaders/color.js +58 -0
- package/src/render/shaders/glass.js +241 -0
- package/src/render/shaders/ui.js +194 -0
- package/src/widgets/composite.js +355 -0
- package/src/widgets/controls.js +545 -0
- package/src/widgets/desktop.js +144 -0
- package/src/widgets/glass.js +326 -0
- package/src/widgets/primitives.js +355 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { normalizeMaterial } from './params.js'
|
|
2
|
+
|
|
3
|
+
function tint(r, g, b, a) {
|
|
4
|
+
return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255), a }
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const ROLE_PRESETS = {
|
|
8
|
+
window: normalizeMaterial({
|
|
9
|
+
cornerRadius: 36,
|
|
10
|
+
refThickness: 10,
|
|
11
|
+
refFactor: 1.38,
|
|
12
|
+
refDispersion: 7.2,
|
|
13
|
+
refFresnelRange: 21,
|
|
14
|
+
refFresnelHardness: 14,
|
|
15
|
+
refFresnelFactor: 15,
|
|
16
|
+
glareRange: 16,
|
|
17
|
+
glareHardness: 27,
|
|
18
|
+
glareFactor: 93,
|
|
19
|
+
glareConvergence: 82,
|
|
20
|
+
glareOppositeFactor: 100,
|
|
21
|
+
glareAngle: -180,
|
|
22
|
+
blurRadius: 92,
|
|
23
|
+
blurEdge: false,
|
|
24
|
+
tint: tint(1, 1, 1, 1),
|
|
25
|
+
shadowExpand: 12,
|
|
26
|
+
shadowFactor: 26,
|
|
27
|
+
shadowPosition: { x: 0, y: 3 },
|
|
28
|
+
borderWidth: 1,
|
|
29
|
+
borderColor: { r: 255, g: 255, b: 255, a: 0.45 },
|
|
30
|
+
adaptTint: 0,
|
|
31
|
+
adaptShadow: 0,
|
|
32
|
+
}),
|
|
33
|
+
dock: normalizeMaterial({
|
|
34
|
+
cornerRadius: 36,
|
|
35
|
+
refThickness: 7,
|
|
36
|
+
refFactor: 2.77,
|
|
37
|
+
refDispersion: 14.22,
|
|
38
|
+
refFresnelRange: 19,
|
|
39
|
+
refFresnelHardness: 47,
|
|
40
|
+
refFresnelFactor: 43,
|
|
41
|
+
glareRange: 18,
|
|
42
|
+
glareHardness: 43,
|
|
43
|
+
glareFactor: 34,
|
|
44
|
+
glareConvergence: 30,
|
|
45
|
+
glareOppositeFactor: 57,
|
|
46
|
+
glareAngle: 45,
|
|
47
|
+
blurRadius: 48,
|
|
48
|
+
blurEdge: true,
|
|
49
|
+
tint: tint(0.93, 0.95, 0.99, 0.36),
|
|
50
|
+
shadowExpand: 18,
|
|
51
|
+
shadowFactor: 4,
|
|
52
|
+
shadowPosition: { x: 0, y: -3 },
|
|
53
|
+
}),
|
|
54
|
+
clear: normalizeMaterial({
|
|
55
|
+
cornerRadius: 14,
|
|
56
|
+
refThickness: 10,
|
|
57
|
+
refFactor: 1.41,
|
|
58
|
+
refDispersion: 20.82,
|
|
59
|
+
refFresnelRange: 22,
|
|
60
|
+
refFresnelHardness: 16,
|
|
61
|
+
refFresnelFactor: 47,
|
|
62
|
+
glareRange: 35,
|
|
63
|
+
glareHardness: 5,
|
|
64
|
+
glareFactor: 47,
|
|
65
|
+
glareConvergence: 38,
|
|
66
|
+
glareOppositeFactor: 61,
|
|
67
|
+
glareAngle: 45,
|
|
68
|
+
blurRadius: 15,
|
|
69
|
+
blurEdge: false,
|
|
70
|
+
tint: tint(0.86, 0.9, 0.98, 0.22),
|
|
71
|
+
shadowExpand: 15,
|
|
72
|
+
shadowFactor: 4,
|
|
73
|
+
shadowPosition: { x: 0, y: -3 },
|
|
74
|
+
}),
|
|
75
|
+
thumb: normalizeMaterial({
|
|
76
|
+
cornerRadius: 60,
|
|
77
|
+
refThickness: 13,
|
|
78
|
+
refFactor: 1.9,
|
|
79
|
+
refDispersion: 32,
|
|
80
|
+
refFresnelRange: 30,
|
|
81
|
+
refFresnelHardness: 12,
|
|
82
|
+
refFresnelFactor: 38,
|
|
83
|
+
glareRange: 34,
|
|
84
|
+
glareHardness: 10,
|
|
85
|
+
glareFactor: 70,
|
|
86
|
+
glareConvergence: 55,
|
|
87
|
+
glareOppositeFactor: 75,
|
|
88
|
+
glareAngle: -45,
|
|
89
|
+
blurRadius: 4,
|
|
90
|
+
blurEdge: false,
|
|
91
|
+
tint: tint(1, 1, 1, 0.06),
|
|
92
|
+
shadowExpand: 14,
|
|
93
|
+
shadowFactor: 8,
|
|
94
|
+
shadowPosition: { x: 0, y: -3 },
|
|
95
|
+
}),
|
|
96
|
+
control: normalizeMaterial({
|
|
97
|
+
cornerRadius: 60,
|
|
98
|
+
refThickness: 13,
|
|
99
|
+
refFactor: 1.5,
|
|
100
|
+
refDispersion: 7,
|
|
101
|
+
refFresnelRange: 38,
|
|
102
|
+
refFresnelHardness: 11,
|
|
103
|
+
refFresnelFactor: 21,
|
|
104
|
+
glareRange: 35,
|
|
105
|
+
glareHardness: 15,
|
|
106
|
+
glareFactor: 43,
|
|
107
|
+
glareConvergence: 50,
|
|
108
|
+
glareOppositeFactor: 80,
|
|
109
|
+
glareAngle: -45,
|
|
110
|
+
blurRadius: 14,
|
|
111
|
+
blurEdge: false,
|
|
112
|
+
tint: tint(0.9, 0.9, 0.88, 0.5),
|
|
113
|
+
shadowExpand: 18,
|
|
114
|
+
shadowFactor: 14,
|
|
115
|
+
shadowPosition: { x: 0, y: -4 },
|
|
116
|
+
}),
|
|
117
|
+
accent: normalizeMaterial({
|
|
118
|
+
cornerRadius: 60,
|
|
119
|
+
refThickness: 12,
|
|
120
|
+
refFactor: 1.5,
|
|
121
|
+
refDispersion: 5,
|
|
122
|
+
refFresnelRange: 30,
|
|
123
|
+
refFresnelHardness: 12,
|
|
124
|
+
refFresnelFactor: 30,
|
|
125
|
+
glareRange: 30,
|
|
126
|
+
glareHardness: 12,
|
|
127
|
+
glareFactor: 60,
|
|
128
|
+
glareConvergence: 50,
|
|
129
|
+
glareOppositeFactor: 70,
|
|
130
|
+
glareAngle: -45,
|
|
131
|
+
blurRadius: 20,
|
|
132
|
+
blurEdge: false,
|
|
133
|
+
tint: tint(0.17, 0.44, 1, 0.75),
|
|
134
|
+
shadowExpand: 16,
|
|
135
|
+
shadowFactor: 10,
|
|
136
|
+
shadowPosition: { x: 0, y: -4 },
|
|
137
|
+
}),
|
|
138
|
+
toolbar: normalizeMaterial({
|
|
139
|
+
cornerRadius: 60,
|
|
140
|
+
refThickness: 5,
|
|
141
|
+
refFactor: 1.12,
|
|
142
|
+
refDispersion: 0,
|
|
143
|
+
refFresnelRange: 18,
|
|
144
|
+
refFresnelHardness: 12,
|
|
145
|
+
refFresnelFactor: 10,
|
|
146
|
+
glareRange: 20,
|
|
147
|
+
glareHardness: 10,
|
|
148
|
+
glareFactor: 12,
|
|
149
|
+
glareConvergence: 50,
|
|
150
|
+
glareOppositeFactor: 50,
|
|
151
|
+
glareAngle: -45,
|
|
152
|
+
blurRadius: 60,
|
|
153
|
+
blurEdge: true,
|
|
154
|
+
tint: tint(1, 1, 1, 0.78),
|
|
155
|
+
shadowExpand: 16,
|
|
156
|
+
shadowFactor: 9,
|
|
157
|
+
shadowPosition: { x: 0, y: -3 },
|
|
158
|
+
}),
|
|
159
|
+
menu: normalizeMaterial({
|
|
160
|
+
cornerRadius: 16,
|
|
161
|
+
refThickness: 8,
|
|
162
|
+
refFactor: 1.3,
|
|
163
|
+
refDispersion: 4,
|
|
164
|
+
refFresnelRange: 30,
|
|
165
|
+
refFresnelHardness: 12,
|
|
166
|
+
refFresnelFactor: 18,
|
|
167
|
+
glareRange: 30,
|
|
168
|
+
glareHardness: 15,
|
|
169
|
+
glareFactor: 30,
|
|
170
|
+
glareConvergence: 50,
|
|
171
|
+
glareOppositeFactor: 70,
|
|
172
|
+
glareAngle: -45,
|
|
173
|
+
blurRadius: 60,
|
|
174
|
+
blurEdge: true,
|
|
175
|
+
tint: tint(0.96, 0.96, 0.97, 0.62),
|
|
176
|
+
shadowExpand: 24,
|
|
177
|
+
shadowFactor: 16,
|
|
178
|
+
shadowPosition: { x: 0, y: -6 },
|
|
179
|
+
borderWidth: 1,
|
|
180
|
+
borderColor: { r: 255, g: 255, b: 255, a: 0.5 },
|
|
181
|
+
}),
|
|
182
|
+
pane: normalizeMaterial({
|
|
183
|
+
cornerRadius: 36,
|
|
184
|
+
refThickness: 4,
|
|
185
|
+
refFactor: 1.08,
|
|
186
|
+
refDispersion: 0,
|
|
187
|
+
refFresnelRange: 20,
|
|
188
|
+
refFresnelHardness: 10,
|
|
189
|
+
refFresnelFactor: 8,
|
|
190
|
+
glareRange: 0,
|
|
191
|
+
glareHardness: 0,
|
|
192
|
+
glareFactor: 0,
|
|
193
|
+
glareConvergence: 0,
|
|
194
|
+
glareOppositeFactor: 0,
|
|
195
|
+
glareAngle: 0,
|
|
196
|
+
blurRadius: 90,
|
|
197
|
+
blurEdge: true,
|
|
198
|
+
tint: tint(1, 1, 1, 0.24),
|
|
199
|
+
shadowExpand: 14,
|
|
200
|
+
shadowFactor: 5,
|
|
201
|
+
shadowPosition: { x: 0, y: -3 },
|
|
202
|
+
}),
|
|
203
|
+
thin: normalizeMaterial({
|
|
204
|
+
cornerRadius: 14,
|
|
205
|
+
refThickness: 1,
|
|
206
|
+
refFactor: 1,
|
|
207
|
+
refDispersion: 0,
|
|
208
|
+
refFresnelRange: 0,
|
|
209
|
+
refFresnelHardness: 0,
|
|
210
|
+
refFresnelFactor: 0,
|
|
211
|
+
glareRange: 0,
|
|
212
|
+
glareHardness: 0,
|
|
213
|
+
glareFactor: 0,
|
|
214
|
+
glareConvergence: 0,
|
|
215
|
+
glareOppositeFactor: 0,
|
|
216
|
+
glareAngle: 0,
|
|
217
|
+
blurRadius: 102,
|
|
218
|
+
blurEdge: true,
|
|
219
|
+
tint: tint(1, 1, 1, 0.18),
|
|
220
|
+
shadowExpand: 12,
|
|
221
|
+
shadowFactor: 4,
|
|
222
|
+
shadowPosition: { x: 0, y: -3 },
|
|
223
|
+
}),
|
|
224
|
+
studio: normalizeMaterial({ cornerRadius: 40 }),
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export const ROLES = Object.keys(ROLE_PRESETS)
|
|
228
|
+
|
|
229
|
+
export const ROLE_SURFACE = {
|
|
230
|
+
window: 'large',
|
|
231
|
+
dock: 'small',
|
|
232
|
+
clear: 'small',
|
|
233
|
+
thumb: 'small',
|
|
234
|
+
control: 'small',
|
|
235
|
+
accent: 'small',
|
|
236
|
+
toolbar: 'small',
|
|
237
|
+
menu: 'large',
|
|
238
|
+
pane: 'large',
|
|
239
|
+
thin: 'small',
|
|
240
|
+
studio: 'small',
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function clonePresets(presets = ROLE_PRESETS) {
|
|
244
|
+
return structuredClone(presets)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export const MATERIALS_VERSION = 1
|
|
248
|
+
|
|
249
|
+
const VARIANTS = ['unfocused', 'dark']
|
|
250
|
+
|
|
251
|
+
export function serializeMaterials(materials) {
|
|
252
|
+
const roles = {}
|
|
253
|
+
for (const role of Object.keys(materials)) {
|
|
254
|
+
if (VARIANTS.includes(role)) continue
|
|
255
|
+
roles[role] = structuredClone(materials[role])
|
|
256
|
+
}
|
|
257
|
+
const out = { version: MATERIALS_VERSION, roles }
|
|
258
|
+
for (const variant of VARIANTS) {
|
|
259
|
+
out[variant] = {}
|
|
260
|
+
for (const role of Object.keys(materials[variant] ?? {})) out[variant][role] = structuredClone(materials[variant][role])
|
|
261
|
+
}
|
|
262
|
+
return out
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function parseMaterials(data) {
|
|
266
|
+
const source = typeof data === 'string' ? JSON.parse(data) : data
|
|
267
|
+
if (!source || typeof source !== 'object') throw new Error('materials file is not an object')
|
|
268
|
+
const roles = source.roles ?? source
|
|
269
|
+
const result = {}
|
|
270
|
+
for (const [role, material] of Object.entries(roles)) {
|
|
271
|
+
if (VARIANTS.includes(role) || role === 'version') continue
|
|
272
|
+
result[role] = normalizeMaterial(material, ROLE_PRESETS[role])
|
|
273
|
+
}
|
|
274
|
+
for (const variant of VARIANTS) {
|
|
275
|
+
if (!source[variant]) continue
|
|
276
|
+
result[variant] = {}
|
|
277
|
+
for (const [role, material] of Object.entries(source[variant])) result[variant][role] = normalizeMaterial(material, ROLE_PRESETS[role])
|
|
278
|
+
}
|
|
279
|
+
return result
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function assignMaterials(target, parsed) {
|
|
283
|
+
for (const key of Object.keys(target)) delete target[key]
|
|
284
|
+
for (const preset of Object.keys(ROLE_PRESETS)) target[preset] = structuredClone(ROLE_PRESETS[preset])
|
|
285
|
+
for (const [role, material] of Object.entries(parsed)) {
|
|
286
|
+
if (VARIANTS.includes(role)) continue
|
|
287
|
+
target[role] = material
|
|
288
|
+
}
|
|
289
|
+
for (const variant of VARIANTS) if (parsed[variant]) target[variant] = parsed[variant]
|
|
290
|
+
return target
|
|
291
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { compileProgram, setUniforms } from './context.js'
|
|
2
|
+
import {
|
|
3
|
+
uiVertex,
|
|
4
|
+
uiFragment,
|
|
5
|
+
MODE_FILL,
|
|
6
|
+
MODE_IMAGE,
|
|
7
|
+
MODE_GLYPH,
|
|
8
|
+
MODE_STROKE,
|
|
9
|
+
MODE_VIBRANT_GLYPH,
|
|
10
|
+
MODE_GRADIENT,
|
|
11
|
+
MODE_TINTED_FILL,
|
|
12
|
+
} from './shaders/ui.js'
|
|
13
|
+
|
|
14
|
+
const FLOATS_PER_INSTANCE = 32
|
|
15
|
+
const ATTRIBUTES = 8
|
|
16
|
+
const NO_CLIP = [0, 0, 0, 0]
|
|
17
|
+
const NO_FADE = [0, 0, 0, 0]
|
|
18
|
+
|
|
19
|
+
export function createBatch(gl, atlas) {
|
|
20
|
+
const program = compileProgram(gl, uiVertex, uiFragment, 'ui')
|
|
21
|
+
const vao = gl.createVertexArray()
|
|
22
|
+
const buffer = gl.createBuffer()
|
|
23
|
+
let capacity = 1024
|
|
24
|
+
let data = new Float32Array(capacity * FLOATS_PER_INSTANCE)
|
|
25
|
+
let count = 0
|
|
26
|
+
let mode = -1
|
|
27
|
+
let texture = null
|
|
28
|
+
let frameContext = null
|
|
29
|
+
|
|
30
|
+
gl.bindVertexArray(vao)
|
|
31
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
|
|
32
|
+
gl.bufferData(gl.ARRAY_BUFFER, data.byteLength, gl.DYNAMIC_DRAW)
|
|
33
|
+
for (let i = 0; i < ATTRIBUTES; i++) {
|
|
34
|
+
gl.enableVertexAttribArray(i)
|
|
35
|
+
gl.vertexAttribPointer(i, 4, gl.FLOAT, false, FLOATS_PER_INSTANCE * 4, i * 16)
|
|
36
|
+
gl.vertexAttribDivisor(i, 1)
|
|
37
|
+
}
|
|
38
|
+
gl.bindVertexArray(null)
|
|
39
|
+
|
|
40
|
+
function grow() {
|
|
41
|
+
capacity *= 2
|
|
42
|
+
const next = new Float32Array(capacity * FLOATS_PER_INSTANCE)
|
|
43
|
+
next.set(data)
|
|
44
|
+
data = next
|
|
45
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
|
|
46
|
+
gl.bufferData(gl.ARRAY_BUFFER, data.byteLength, gl.DYNAMIC_DRAW)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function begin(context) {
|
|
50
|
+
frameContext = context
|
|
51
|
+
count = 0
|
|
52
|
+
mode = -1
|
|
53
|
+
texture = null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function flush() {
|
|
57
|
+
if (count === 0) return
|
|
58
|
+
gl.useProgram(program.program)
|
|
59
|
+
gl.bindVertexArray(vao)
|
|
60
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
|
|
61
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, 0, data.subarray(0, count * FLOATS_PER_INSTANCE))
|
|
62
|
+
if (mode === MODE_GLYPH || mode === MODE_VIBRANT_GLYPH) atlas.upload()
|
|
63
|
+
gl.activeTexture(gl.TEXTURE0)
|
|
64
|
+
gl.bindTexture(gl.TEXTURE_2D, texture ?? atlas.texture)
|
|
65
|
+
gl.activeTexture(gl.TEXTURE1)
|
|
66
|
+
gl.bindTexture(gl.TEXTURE_2D, frameContext.sceneTexture)
|
|
67
|
+
setUniforms(gl, program, {
|
|
68
|
+
u_viewport: [frameContext.width, frameContext.height],
|
|
69
|
+
u_resolution: [frameContext.width * frameContext.dpr, frameContext.height * frameContext.dpr],
|
|
70
|
+
u_dpr: frameContext.dpr,
|
|
71
|
+
u_mode: mode,
|
|
72
|
+
u_texture: 0,
|
|
73
|
+
u_scene: 1,
|
|
74
|
+
})
|
|
75
|
+
gl.enable(gl.BLEND)
|
|
76
|
+
gl.blendEquation(gl.FUNC_ADD)
|
|
77
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
|
|
78
|
+
gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, count)
|
|
79
|
+
gl.bindVertexArray(null)
|
|
80
|
+
count = 0
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function push(nextMode, nextTexture, values) {
|
|
84
|
+
if (nextMode !== mode || nextTexture !== texture) {
|
|
85
|
+
flush()
|
|
86
|
+
mode = nextMode
|
|
87
|
+
texture = nextTexture
|
|
88
|
+
}
|
|
89
|
+
if (count >= capacity) {
|
|
90
|
+
flush()
|
|
91
|
+
grow()
|
|
92
|
+
}
|
|
93
|
+
data.set(values, count * FLOATS_PER_INSTANCE)
|
|
94
|
+
count += 1
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function clipOf(op) {
|
|
98
|
+
const clip = op.clip
|
|
99
|
+
return clip ? [clip.x, clip.y, clip.w, clip.h] : NO_CLIP
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function fadeOf(op) {
|
|
103
|
+
const fade = op.fade
|
|
104
|
+
return fade ? [fade.topStart, fade.topEnd, fade.bottomStart, fade.bottomEnd] : NO_FADE
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function rect(op) {
|
|
108
|
+
const r = op.rect
|
|
109
|
+
const c = op.color
|
|
110
|
+
const rotation = op.rotation ?? 0
|
|
111
|
+
const expand = rotation ? (r.w + r.h) / 2 : 0
|
|
112
|
+
push(MODE_FILL, null, [
|
|
113
|
+
r.x, r.y, r.w, r.h,
|
|
114
|
+
op.radius ?? 0, op.roundness ?? 5, 0, 0,
|
|
115
|
+
c[0], c[1], c[2], c[3],
|
|
116
|
+
0, 0, 1, 1,
|
|
117
|
+
...clipOf(op),
|
|
118
|
+
0, 0, rotation, expand,
|
|
119
|
+
0, 0, 0, 0,
|
|
120
|
+
...fadeOf(op),
|
|
121
|
+
])
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function tintedFill(op) {
|
|
125
|
+
const r = op.rect
|
|
126
|
+
const c = op.color
|
|
127
|
+
push(MODE_TINTED_FILL, null, [
|
|
128
|
+
r.x, r.y, r.w, r.h,
|
|
129
|
+
op.radius ?? 0, op.roundness ?? 5, 0, 0,
|
|
130
|
+
c[0], c[1], c[2], c[3],
|
|
131
|
+
0, 0, 1, 1,
|
|
132
|
+
...clipOf(op),
|
|
133
|
+
op.tone ?? 0.5, 0, 0, 0,
|
|
134
|
+
0, 0, 0, 0,
|
|
135
|
+
...fadeOf(op),
|
|
136
|
+
])
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function gradient(op) {
|
|
140
|
+
const r = op.rect
|
|
141
|
+
const c = op.color
|
|
142
|
+
const d = op.color2
|
|
143
|
+
push(MODE_GRADIENT, null, [
|
|
144
|
+
r.x, r.y, r.w, r.h,
|
|
145
|
+
op.radius ?? 0, op.roundness ?? 5, 0, 0,
|
|
146
|
+
c[0], c[1], c[2], c[3],
|
|
147
|
+
0, 0, 1, 1,
|
|
148
|
+
...clipOf(op),
|
|
149
|
+
0, op.horizontal ? 1 : 0, 0, 0,
|
|
150
|
+
d[0], d[1], d[2], d[3],
|
|
151
|
+
...fadeOf(op),
|
|
152
|
+
])
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function stroke(op) {
|
|
156
|
+
const r = op.rect
|
|
157
|
+
const c = op.color
|
|
158
|
+
push(MODE_STROKE, null, [
|
|
159
|
+
r.x, r.y, r.w, r.h,
|
|
160
|
+
op.radius ?? 0, op.roundness ?? 5, 0, 0,
|
|
161
|
+
c[0], c[1], c[2], c[3],
|
|
162
|
+
0, 0, 1, 1,
|
|
163
|
+
...clipOf(op),
|
|
164
|
+
op.width ?? 1, 0, 0, 1,
|
|
165
|
+
0, 0, 0, 0,
|
|
166
|
+
...fadeOf(op),
|
|
167
|
+
])
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function image(op) {
|
|
171
|
+
const r = op.rect
|
|
172
|
+
const c = op.color ?? [1, 1, 1, 1]
|
|
173
|
+
const uv = op.uv ?? [0, 0, 1, 1]
|
|
174
|
+
push(MODE_IMAGE, op.image.texture, [
|
|
175
|
+
r.x, r.y, r.w, r.h,
|
|
176
|
+
op.radius ?? 0, op.roundness ?? 5, 0, 0,
|
|
177
|
+
c[0], c[1], c[2], c[3],
|
|
178
|
+
uv[0], uv[1], uv[2], uv[3],
|
|
179
|
+
...clipOf(op),
|
|
180
|
+
0, 0, 0, 0,
|
|
181
|
+
0, 0, 0, 0,
|
|
182
|
+
...fadeOf(op),
|
|
183
|
+
])
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function text(op) {
|
|
187
|
+
const dpr = frameContext.dpr
|
|
188
|
+
const scale = dpr
|
|
189
|
+
const layout = atlas.layoutText(op.text, op.font, scale)
|
|
190
|
+
const c = op.color
|
|
191
|
+
const clip = clipOf(op)
|
|
192
|
+
const fade = fadeOf(op)
|
|
193
|
+
const glyphMode = op.vibrancy > 0 ? MODE_VIBRANT_GLYPH : MODE_GLYPH
|
|
194
|
+
const k = op.scale ?? 1
|
|
195
|
+
const metrics = atlas.metrics(op.font)
|
|
196
|
+
const lineBox = op.font.size * (op.font.lineHeight ?? 1.25) * k
|
|
197
|
+
const baseline = op.y + (lineBox - (metrics.ascent + metrics.descent) * k) / 2 + metrics.ascent * k
|
|
198
|
+
const snap = (value) => (k === 1 ? Math.round(value * dpr) / dpr : value)
|
|
199
|
+
for (const item of layout.items) {
|
|
200
|
+
const g = item.glyph
|
|
201
|
+
const x = snap(op.x + (item.x + g.offsetX) * k)
|
|
202
|
+
const y = snap(baseline + g.offsetY * k)
|
|
203
|
+
push(glyphMode, null, [
|
|
204
|
+
x, y, g.w * k, g.h * k,
|
|
205
|
+
0, 2, 0, 0,
|
|
206
|
+
c[0], c[1], c[2], c[3],
|
|
207
|
+
g.u0, g.v0, g.u1, g.v1,
|
|
208
|
+
...clip,
|
|
209
|
+
op.vibrancy ?? 0, 0, 0, 0,
|
|
210
|
+
0, 0, 0, 0,
|
|
211
|
+
...fade,
|
|
212
|
+
])
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function draw(op) {
|
|
217
|
+
switch (op.type) {
|
|
218
|
+
case 'rect':
|
|
219
|
+
return rect(op)
|
|
220
|
+
case 'gradient':
|
|
221
|
+
return gradient(op)
|
|
222
|
+
case 'tintedFill':
|
|
223
|
+
return tintedFill(op)
|
|
224
|
+
case 'stroke':
|
|
225
|
+
return stroke(op)
|
|
226
|
+
case 'image':
|
|
227
|
+
return image(op)
|
|
228
|
+
case 'text':
|
|
229
|
+
return text(op)
|
|
230
|
+
default:
|
|
231
|
+
throw new Error(`unknown ui op ${op.type}`)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return { begin, draw, flush }
|
|
236
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
export function createContext(canvas) {
|
|
2
|
+
const gl = canvas.getContext('webgl2', {
|
|
3
|
+
alpha: false,
|
|
4
|
+
antialias: false,
|
|
5
|
+
depth: false,
|
|
6
|
+
stencil: false,
|
|
7
|
+
premultipliedAlpha: true,
|
|
8
|
+
preserveDrawingBuffer: false,
|
|
9
|
+
powerPreference: 'high-performance',
|
|
10
|
+
})
|
|
11
|
+
if (!gl) throw new Error('webgl2 is required')
|
|
12
|
+
return gl
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function compileProgram(gl, vertexSource, fragmentSource, name) {
|
|
16
|
+
const program = gl.createProgram()
|
|
17
|
+
gl.attachShader(program, compileShader(gl, gl.VERTEX_SHADER, vertexSource, name + '.vert'))
|
|
18
|
+
gl.attachShader(program, compileShader(gl, gl.FRAGMENT_SHADER, fragmentSource, name + '.frag'))
|
|
19
|
+
gl.linkProgram(program)
|
|
20
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
21
|
+
throw new Error(`link ${name}: ${gl.getProgramInfoLog(program)}`)
|
|
22
|
+
}
|
|
23
|
+
const uniforms = {}
|
|
24
|
+
const count = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS)
|
|
25
|
+
for (let i = 0; i < count; i++) {
|
|
26
|
+
const info = gl.getActiveUniform(program, i)
|
|
27
|
+
const key = info.name.replace(/\[0\]$/, '')
|
|
28
|
+
uniforms[key] = { location: gl.getUniformLocation(program, info.name), type: info.type, size: info.size }
|
|
29
|
+
}
|
|
30
|
+
return { program, uniforms }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function compileShader(gl, type, source, name) {
|
|
34
|
+
const shader = gl.createShader(type)
|
|
35
|
+
gl.shaderSource(shader, source)
|
|
36
|
+
gl.compileShader(shader)
|
|
37
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
38
|
+
const log = gl.getShaderInfoLog(shader)
|
|
39
|
+
const lines = source.split('\n').map((line, i) => `${i + 1}: ${line}`)
|
|
40
|
+
throw new Error(`compile ${name}: ${log}\n${lines.join('\n')}`)
|
|
41
|
+
}
|
|
42
|
+
return shader
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function setUniforms(gl, program, values) {
|
|
46
|
+
for (const [key, value] of Object.entries(values)) {
|
|
47
|
+
const uniform = program.uniforms[key]
|
|
48
|
+
if (!uniform) continue
|
|
49
|
+
applyUniform(gl, uniform, value)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function applyUniform(gl, uniform, value) {
|
|
54
|
+
const { location, type } = uniform
|
|
55
|
+
switch (type) {
|
|
56
|
+
case gl.FLOAT:
|
|
57
|
+
if (Array.isArray(value) || ArrayBuffer.isView(value)) gl.uniform1fv(location, value)
|
|
58
|
+
else gl.uniform1f(location, value)
|
|
59
|
+
break
|
|
60
|
+
case gl.FLOAT_VEC2:
|
|
61
|
+
gl.uniform2fv(location, value)
|
|
62
|
+
break
|
|
63
|
+
case gl.FLOAT_VEC3:
|
|
64
|
+
gl.uniform3fv(location, value)
|
|
65
|
+
break
|
|
66
|
+
case gl.FLOAT_VEC4:
|
|
67
|
+
gl.uniform4fv(location, value)
|
|
68
|
+
break
|
|
69
|
+
case gl.INT:
|
|
70
|
+
case gl.SAMPLER_2D:
|
|
71
|
+
case gl.BOOL:
|
|
72
|
+
gl.uniform1i(location, value)
|
|
73
|
+
break
|
|
74
|
+
case gl.INT_VEC2:
|
|
75
|
+
gl.uniform2iv(location, value)
|
|
76
|
+
break
|
|
77
|
+
default:
|
|
78
|
+
throw new Error(`unsupported uniform type ${type}`)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function createSurface(gl, width, height, levels) {
|
|
83
|
+
const texture = gl.createTexture()
|
|
84
|
+
gl.bindTexture(gl.TEXTURE_2D, texture)
|
|
85
|
+
gl.texStorage2D(gl.TEXTURE_2D, levels, gl.RGBA8, width, height)
|
|
86
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR)
|
|
87
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
|
88
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
|
89
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
|
90
|
+
const fbos = []
|
|
91
|
+
const sizes = []
|
|
92
|
+
for (let level = 0; level < levels; level++) {
|
|
93
|
+
const fbo = gl.createFramebuffer()
|
|
94
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)
|
|
95
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, level)
|
|
96
|
+
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER)
|
|
97
|
+
if (status !== gl.FRAMEBUFFER_COMPLETE) throw new Error(`framebuffer incomplete at level ${level}: ${status}`)
|
|
98
|
+
fbos.push(fbo)
|
|
99
|
+
sizes.push({ w: Math.max(1, width >> level), h: Math.max(1, height >> level) })
|
|
100
|
+
}
|
|
101
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
|
|
102
|
+
return { texture, width, height, levels, fbos, sizes }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function destroySurface(gl, surface) {
|
|
106
|
+
for (const fbo of surface.fbos) gl.deleteFramebuffer(fbo)
|
|
107
|
+
gl.deleteTexture(surface.texture)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function createImageTexture(gl, source, options = {}) {
|
|
111
|
+
const texture = gl.createTexture()
|
|
112
|
+
gl.bindTexture(gl.TEXTURE_2D, texture)
|
|
113
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true)
|
|
114
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false)
|
|
115
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source)
|
|
116
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false)
|
|
117
|
+
const mipmap = options.mipmap ?? true
|
|
118
|
+
if (mipmap) gl.generateMipmap(gl.TEXTURE_2D)
|
|
119
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, mipmap ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR)
|
|
120
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
|
121
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
|
122
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
|
123
|
+
return { texture, width: source.width ?? source.videoWidth, height: source.height ?? source.videoHeight, mipmap }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function updateImageTexture(gl, image, source) {
|
|
127
|
+
gl.bindTexture(gl.TEXTURE_2D, image.texture)
|
|
128
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true)
|
|
129
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source)
|
|
130
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false)
|
|
131
|
+
if (image.mipmap) gl.generateMipmap(gl.TEXTURE_2D)
|
|
132
|
+
image.width = source.width ?? source.videoWidth
|
|
133
|
+
image.height = source.height ?? source.videoHeight
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function mipLevelsFor(width, height) {
|
|
137
|
+
return Math.max(1, Math.min(8, Math.floor(Math.log2(Math.max(width, height))) - 1))
|
|
138
|
+
}
|