q5 2.5.4 → 2.6.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 +31 -337
- package/package.json +2 -2
- package/q5.d.ts +81 -20
- package/q5.js +620 -530
- package/q5.min.js +2 -2
- package/src/q5-2d-canvas.js +2 -4
- package/src/q5-core.js +2 -2
- package/src/q5-util.js +1 -1
- package/src/q5-webgpu-canvas.js +195 -55
- package/src/q5-webgpu-drawing.js +298 -291
- package/src/q5-webgpu-image.js +25 -38
- package/src/q5-webgpu-text.js +95 -137
- package/src/readme.md +27 -59
package/src/q5-webgpu-image.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Q5.renderers.webgpu.image = ($, q) => {
|
|
2
2
|
$._textureBindGroups = [];
|
|
3
|
-
let
|
|
3
|
+
let vertexStack = [];
|
|
4
4
|
|
|
5
5
|
let vertexShader = Q5.device.createShaderModule({
|
|
6
6
|
label: 'imageVertexShader',
|
|
@@ -8,15 +8,14 @@ Q5.renderers.webgpu.image = ($, q) => {
|
|
|
8
8
|
struct VertexOutput {
|
|
9
9
|
@builtin(position) position: vec4f,
|
|
10
10
|
@location(0) texCoord: vec2f
|
|
11
|
-
}
|
|
12
|
-
|
|
11
|
+
}
|
|
13
12
|
struct Uniforms {
|
|
14
13
|
halfWidth: f32,
|
|
15
14
|
halfHeight: f32
|
|
16
|
-
}
|
|
15
|
+
}
|
|
17
16
|
|
|
18
17
|
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
19
|
-
@group(0) @binding(1) var<storage
|
|
18
|
+
@group(0) @binding(1) var<storage> transforms: array<mat4x4<f32>>;
|
|
20
19
|
|
|
21
20
|
@vertex
|
|
22
21
|
fn vertexMain(@location(0) pos: vec2f, @location(1) texCoord: vec2f, @location(2) transformIndex: f32) -> VertexOutput {
|
|
@@ -77,7 +76,7 @@ fn fragmentMain(@location(0) texCoord: vec2f) -> @location(0) vec4f {
|
|
|
77
76
|
bindGroupLayouts: [...$.bindGroupLayouts, textureLayout]
|
|
78
77
|
});
|
|
79
78
|
|
|
80
|
-
$.
|
|
79
|
+
$._pipelineConfigs[1] = {
|
|
81
80
|
label: 'imagePipeline',
|
|
82
81
|
layout: pipelineLayout,
|
|
83
82
|
vertex: {
|
|
@@ -88,28 +87,12 @@ fn fragmentMain(@location(0) texCoord: vec2f) -> @location(0) vec4f {
|
|
|
88
87
|
fragment: {
|
|
89
88
|
module: fragmentShader,
|
|
90
89
|
entryPoint: 'fragmentMain',
|
|
91
|
-
targets: [
|
|
92
|
-
{
|
|
93
|
-
format: 'bgra8unorm',
|
|
94
|
-
blend: $.blendConfigs?.normal || {
|
|
95
|
-
color: {
|
|
96
|
-
srcFactor: 'src-alpha',
|
|
97
|
-
dstFactor: 'one-minus-src-alpha',
|
|
98
|
-
operation: 'add'
|
|
99
|
-
},
|
|
100
|
-
alpha: {
|
|
101
|
-
srcFactor: 'src-alpha',
|
|
102
|
-
dstFactor: 'one-minus-src-alpha',
|
|
103
|
-
operation: 'add'
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
]
|
|
90
|
+
targets: [{ format: 'bgra8unorm', blend: $.blendConfigs.normal }]
|
|
108
91
|
},
|
|
109
|
-
primitive: {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
92
|
+
primitive: { topology: 'triangle-list' }
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
$._pipelines[1] = Q5.device.createRenderPipeline($._pipelineConfigs[1]);
|
|
113
96
|
|
|
114
97
|
let sampler = Q5.device.createSampler({
|
|
115
98
|
magFilter: 'linear',
|
|
@@ -134,7 +117,11 @@ fn fragmentMain(@location(0) texCoord: vec2f) -> @location(0) vec4f {
|
|
|
134
117
|
|
|
135
118
|
Q5.device.queue.copyExternalImageToTexture(
|
|
136
119
|
{ source: img },
|
|
137
|
-
{
|
|
120
|
+
{
|
|
121
|
+
texture,
|
|
122
|
+
colorSpace: $.canvas.colorSpace
|
|
123
|
+
// premultipliedAlpha: true
|
|
124
|
+
},
|
|
138
125
|
textureSize
|
|
139
126
|
);
|
|
140
127
|
|
|
@@ -187,7 +174,7 @@ fn fragmentMain(@location(0) texCoord: vec2f) -> @location(0) vec4f {
|
|
|
187
174
|
let [l, r, t, b] = $._calcBox(x, y, w, h, $._imageMode);
|
|
188
175
|
|
|
189
176
|
// prettier-ignore
|
|
190
|
-
|
|
177
|
+
vertexStack.push(
|
|
191
178
|
l, t, 0, 0, ti,
|
|
192
179
|
r, t, 1, 0, ti,
|
|
193
180
|
l, b, 0, 1, ti,
|
|
@@ -196,29 +183,29 @@ fn fragmentMain(@location(0) texCoord: vec2f) -> @location(0) vec4f {
|
|
|
196
183
|
r, b, 1, 1, ti
|
|
197
184
|
);
|
|
198
185
|
|
|
199
|
-
$.drawStack.push(1, img.textureIndex);
|
|
186
|
+
$.drawStack.push(1, img.textureIndex, 0);
|
|
200
187
|
};
|
|
201
188
|
|
|
202
189
|
$._hooks.preRender.push(() => {
|
|
203
190
|
if (!$._textureBindGroups.length) return;
|
|
204
191
|
|
|
205
192
|
// Switch to image pipeline
|
|
206
|
-
$.pass.setPipeline($.
|
|
207
|
-
|
|
208
|
-
// Create a vertex buffer for the image quads
|
|
209
|
-
const vertices = new Float32Array(verticesStack);
|
|
193
|
+
$.pass.setPipeline($._pipelines[1]);
|
|
210
194
|
|
|
211
195
|
const vertexBuffer = Q5.device.createBuffer({
|
|
212
|
-
size:
|
|
213
|
-
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
|
|
196
|
+
size: vertexStack.length * 4,
|
|
197
|
+
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
198
|
+
mappedAtCreation: true
|
|
214
199
|
});
|
|
215
200
|
|
|
216
|
-
|
|
201
|
+
new Float32Array(vertexBuffer.getMappedRange()).set(vertices);
|
|
202
|
+
vertexBuffer.unmap();
|
|
203
|
+
|
|
217
204
|
$.pass.setVertexBuffer(1, vertexBuffer);
|
|
218
205
|
});
|
|
219
206
|
|
|
220
207
|
$._hooks.postRender.push(() => {
|
|
221
|
-
|
|
208
|
+
vertexStack.length = 0;
|
|
222
209
|
});
|
|
223
210
|
};
|
|
224
211
|
|
package/src/q5-webgpu-text.js
CHANGED
|
@@ -7,35 +7,35 @@ const pos = array(vec2f(0, -1), vec2f(1, -1), vec2f(0, 0), vec2f(1, 0));
|
|
|
7
7
|
|
|
8
8
|
struct VertexInput {
|
|
9
9
|
@builtin(vertex_index) vertex : u32,
|
|
10
|
-
@builtin(instance_index) instance : u32
|
|
11
|
-
}
|
|
10
|
+
@builtin(instance_index) instance : u32
|
|
11
|
+
}
|
|
12
12
|
struct VertexOutput {
|
|
13
13
|
@builtin(position) position : vec4f,
|
|
14
|
-
@location(0)
|
|
15
|
-
@location(1)
|
|
16
|
-
}
|
|
14
|
+
@location(0) texCoord : vec2f,
|
|
15
|
+
@location(1) fillColor : vec4f
|
|
16
|
+
}
|
|
17
17
|
struct Char {
|
|
18
18
|
texOffset: vec2f,
|
|
19
19
|
texExtent: vec2f,
|
|
20
20
|
size: vec2f,
|
|
21
21
|
offset: vec2f,
|
|
22
|
-
}
|
|
22
|
+
}
|
|
23
23
|
struct Text {
|
|
24
24
|
pos: vec2f,
|
|
25
25
|
scale: f32,
|
|
26
26
|
transformIndex: f32,
|
|
27
27
|
fillIndex: f32,
|
|
28
28
|
strokeIndex: f32
|
|
29
|
-
}
|
|
29
|
+
}
|
|
30
30
|
struct Uniforms {
|
|
31
31
|
halfWidth: f32,
|
|
32
32
|
halfHeight: f32
|
|
33
|
-
}
|
|
33
|
+
}
|
|
34
34
|
|
|
35
35
|
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
36
|
-
@group(0) @binding(1) var<storage
|
|
36
|
+
@group(0) @binding(1) var<storage> transforms: array<mat4x4<f32>>;
|
|
37
37
|
|
|
38
|
-
@group(1) @binding(0) var<storage
|
|
38
|
+
@group(1) @binding(0) var<storage> colors : array<vec4f>;
|
|
39
39
|
|
|
40
40
|
@group(2) @binding(0) var fontTexture: texture_2d<f32>;
|
|
41
41
|
@group(2) @binding(1) var fontSampler: sampler;
|
|
@@ -61,13 +61,13 @@ fn vertexMain(input : VertexInput) -> VertexOutput {
|
|
|
61
61
|
|
|
62
62
|
var output : VertexOutput;
|
|
63
63
|
output.position = vert;
|
|
64
|
-
output.
|
|
65
|
-
output.
|
|
64
|
+
output.texCoord = (pos[input.vertex] * vec2f(1, -1)) * fontChar.texExtent + fontChar.texOffset;
|
|
65
|
+
output.fillColor = colors[i32(text.fillIndex)];
|
|
66
66
|
return output;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
fn sampleMsdf(
|
|
70
|
-
let c = textureSample(fontTexture, fontSampler,
|
|
69
|
+
fn sampleMsdf(texCoord: vec2f) -> f32 {
|
|
70
|
+
let c = textureSample(fontTexture, fontSampler, texCoord);
|
|
71
71
|
return max(min(c.r, c.g), min(max(c.r, c.g), c.b));
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -77,25 +77,83 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
|
|
|
77
77
|
// uses the default which is 4.
|
|
78
78
|
let pxRange = 4.0;
|
|
79
79
|
let sz = vec2f(textureDimensions(fontTexture, 0));
|
|
80
|
-
let dx = sz.x*length(vec2f(dpdxFine(input.
|
|
81
|
-
let dy = sz.y*length(vec2f(dpdxFine(input.
|
|
80
|
+
let dx = sz.x*length(vec2f(dpdxFine(input.texCoord.x), dpdyFine(input.texCoord.x)));
|
|
81
|
+
let dy = sz.y*length(vec2f(dpdxFine(input.texCoord.y), dpdyFine(input.texCoord.y)));
|
|
82
82
|
let toPixels = pxRange * inverseSqrt(dx * dx + dy * dy);
|
|
83
|
-
let sigDist = sampleMsdf(input.
|
|
83
|
+
let sigDist = sampleMsdf(input.texCoord) - 0.5;
|
|
84
84
|
let pxDist = sigDist * toPixels;
|
|
85
85
|
let edgeWidth = 0.5;
|
|
86
86
|
let alpha = smoothstep(-edgeWidth, edgeWidth, pxDist);
|
|
87
87
|
if (alpha < 0.001) {
|
|
88
88
|
discard;
|
|
89
89
|
}
|
|
90
|
-
|
|
91
|
-
return vec4f(fillColor.rgb, fillColor.a * alpha);
|
|
90
|
+
return vec4f(input.fillColor.rgb, input.fillColor.a * alpha);
|
|
92
91
|
}
|
|
93
92
|
`
|
|
94
93
|
});
|
|
95
94
|
|
|
95
|
+
let textBindGroupLayout = Q5.device.createBindGroupLayout({
|
|
96
|
+
label: 'MSDF text group layout',
|
|
97
|
+
entries: [
|
|
98
|
+
{
|
|
99
|
+
binding: 0,
|
|
100
|
+
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
101
|
+
buffer: { type: 'read-only-storage' }
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
binding: 1,
|
|
105
|
+
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
106
|
+
buffer: { type: 'read-only-storage' }
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
let fontSampler = Q5.device.createSampler({
|
|
112
|
+
minFilter: 'linear',
|
|
113
|
+
magFilter: 'linear',
|
|
114
|
+
mipmapFilter: 'linear',
|
|
115
|
+
maxAnisotropy: 16
|
|
116
|
+
});
|
|
117
|
+
let fontBindGroupLayout = Q5.device.createBindGroupLayout({
|
|
118
|
+
label: 'MSDF font group layout',
|
|
119
|
+
entries: [
|
|
120
|
+
{
|
|
121
|
+
binding: 0,
|
|
122
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
123
|
+
texture: {}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
binding: 1,
|
|
127
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
128
|
+
sampler: {}
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
binding: 2,
|
|
132
|
+
visibility: GPUShaderStage.VERTEX,
|
|
133
|
+
buffer: { type: 'read-only-storage' }
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
let fontPipelineLayout = Q5.device.createPipelineLayout({
|
|
139
|
+
bindGroupLayouts: [...$.bindGroupLayouts, fontBindGroupLayout, textBindGroupLayout]
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
$._pipelineConfigs[2] = {
|
|
143
|
+
label: 'msdf font pipeline',
|
|
144
|
+
layout: fontPipelineLayout,
|
|
145
|
+
vertex: { module: textShader, entryPoint: 'vertexMain' },
|
|
146
|
+
fragment: {
|
|
147
|
+
module: textShader,
|
|
148
|
+
entryPoint: 'fragmentMain',
|
|
149
|
+
targets: [{ format: 'bgra8unorm', blend: $.blendConfigs.normal }]
|
|
150
|
+
},
|
|
151
|
+
primitive: { topology: 'triangle-strip', stripIndexFormat: 'uint32' }
|
|
152
|
+
};
|
|
153
|
+
$._pipelines[2] = Q5.device.createRenderPipeline($._pipelineConfigs[2]);
|
|
154
|
+
|
|
96
155
|
class MsdfFont {
|
|
97
|
-
constructor(
|
|
98
|
-
this.pipeline = pipeline;
|
|
156
|
+
constructor(bindGroup, lineHeight, chars, kernings) {
|
|
99
157
|
this.bindGroup = bindGroup;
|
|
100
158
|
this.lineHeight = lineHeight;
|
|
101
159
|
this.chars = chars;
|
|
@@ -121,22 +179,7 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
|
|
|
121
179
|
}
|
|
122
180
|
}
|
|
123
181
|
|
|
124
|
-
|
|
125
|
-
label: 'MSDF text group layout',
|
|
126
|
-
entries: [
|
|
127
|
-
{
|
|
128
|
-
binding: 0,
|
|
129
|
-
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
130
|
-
buffer: { type: 'read-only-storage' }
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
binding: 1,
|
|
134
|
-
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
135
|
-
buffer: { type: 'read-only-storage' }
|
|
136
|
-
}
|
|
137
|
-
]
|
|
138
|
-
});
|
|
139
|
-
|
|
182
|
+
$._fonts = [];
|
|
140
183
|
let fonts = {};
|
|
141
184
|
|
|
142
185
|
let createFont = async (fontJsonUrl, fontName, cb) => {
|
|
@@ -199,74 +242,11 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
|
|
|
199
242
|
}
|
|
200
243
|
charsBuffer.unmap();
|
|
201
244
|
|
|
202
|
-
let fontSampler = Q5.device.createSampler({
|
|
203
|
-
minFilter: 'linear',
|
|
204
|
-
magFilter: 'linear',
|
|
205
|
-
mipmapFilter: 'linear',
|
|
206
|
-
maxAnisotropy: 16
|
|
207
|
-
});
|
|
208
|
-
let fontBindGroupLayout = Q5.device.createBindGroupLayout({
|
|
209
|
-
label: 'MSDF font group layout',
|
|
210
|
-
entries: [
|
|
211
|
-
{
|
|
212
|
-
binding: 0,
|
|
213
|
-
visibility: GPUShaderStage.FRAGMENT,
|
|
214
|
-
texture: {}
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
binding: 1,
|
|
218
|
-
visibility: GPUShaderStage.FRAGMENT,
|
|
219
|
-
sampler: {}
|
|
220
|
-
},
|
|
221
|
-
{
|
|
222
|
-
binding: 2,
|
|
223
|
-
visibility: GPUShaderStage.VERTEX,
|
|
224
|
-
buffer: { type: 'read-only-storage' }
|
|
225
|
-
}
|
|
226
|
-
]
|
|
227
|
-
});
|
|
228
|
-
let fontPipeline = Q5.device.createRenderPipeline({
|
|
229
|
-
label: 'msdf font pipeline',
|
|
230
|
-
layout: Q5.device.createPipelineLayout({
|
|
231
|
-
bindGroupLayouts: [...$.bindGroupLayouts, fontBindGroupLayout, textBindGroupLayout]
|
|
232
|
-
}),
|
|
233
|
-
vertex: {
|
|
234
|
-
module: textShader,
|
|
235
|
-
entryPoint: 'vertexMain'
|
|
236
|
-
},
|
|
237
|
-
fragment: {
|
|
238
|
-
module: textShader,
|
|
239
|
-
entryPoint: 'fragmentMain',
|
|
240
|
-
targets: [
|
|
241
|
-
{
|
|
242
|
-
format: 'bgra8unorm',
|
|
243
|
-
blend: {
|
|
244
|
-
color: {
|
|
245
|
-
srcFactor: 'src-alpha',
|
|
246
|
-
dstFactor: 'one-minus-src-alpha'
|
|
247
|
-
},
|
|
248
|
-
alpha: {
|
|
249
|
-
srcFactor: 'one',
|
|
250
|
-
dstFactor: 'one'
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
]
|
|
255
|
-
},
|
|
256
|
-
primitive: {
|
|
257
|
-
topology: 'triangle-strip',
|
|
258
|
-
stripIndexFormat: 'uint32'
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
|
|
262
245
|
let fontBindGroup = Q5.device.createBindGroup({
|
|
263
246
|
label: 'msdf font bind group',
|
|
264
247
|
layout: fontBindGroupLayout,
|
|
265
248
|
entries: [
|
|
266
|
-
{
|
|
267
|
-
binding: 0,
|
|
268
|
-
resource: texture.createView()
|
|
269
|
-
},
|
|
249
|
+
{ binding: 0, resource: texture.createView() },
|
|
270
250
|
{ binding: 1, resource: fontSampler },
|
|
271
251
|
{ binding: 2, resource: { buffer: charsBuffer } }
|
|
272
252
|
]
|
|
@@ -284,10 +264,11 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
|
|
|
284
264
|
}
|
|
285
265
|
}
|
|
286
266
|
|
|
287
|
-
$._font = new MsdfFont(
|
|
267
|
+
$._font = new MsdfFont(fontBindGroup, atlas.common.lineHeight, chars, kernings);
|
|
288
268
|
|
|
269
|
+
$._font.index = $._fonts.length;
|
|
270
|
+
$._fonts.push($._font);
|
|
289
271
|
fonts[fontName] = $._font;
|
|
290
|
-
$.pipelines[2] = $._font.pipeline;
|
|
291
272
|
|
|
292
273
|
q._preloadCount--;
|
|
293
274
|
|
|
@@ -316,12 +297,6 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
|
|
|
316
297
|
|
|
317
298
|
$.textFont = (fontName) => {
|
|
318
299
|
$._font = fonts[fontName];
|
|
319
|
-
|
|
320
|
-
// replay the change of font in the draw stack
|
|
321
|
-
$.drawStack.push(-1, () => {
|
|
322
|
-
$._font = fonts[fontName];
|
|
323
|
-
$.pipelines[2] = $._font.pipeline;
|
|
324
|
-
});
|
|
325
300
|
};
|
|
326
301
|
$.textSize = (size) => {
|
|
327
302
|
$._textSize = size;
|
|
@@ -434,7 +409,7 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
|
|
|
434
409
|
}
|
|
435
410
|
}
|
|
436
411
|
|
|
437
|
-
let charsData =
|
|
412
|
+
let charsData = [];
|
|
438
413
|
|
|
439
414
|
let ta = $._textAlign,
|
|
440
415
|
tb = $._textBaseline,
|
|
@@ -480,7 +455,7 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
|
|
|
480
455
|
}
|
|
481
456
|
$._charStack.push(charsData);
|
|
482
457
|
|
|
483
|
-
let text =
|
|
458
|
+
let text = [];
|
|
484
459
|
|
|
485
460
|
if ($._matrixDirty) $._saveMatrix();
|
|
486
461
|
|
|
@@ -488,11 +463,11 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
|
|
|
488
463
|
text[1] = -y;
|
|
489
464
|
text[2] = $._textSize / 44;
|
|
490
465
|
text[3] = $._transformIndex;
|
|
491
|
-
text[4] = $._fillIndex;
|
|
466
|
+
text[4] = $._fillSet ? $._fillIndex : 0;
|
|
492
467
|
text[5] = $._strokeIndex;
|
|
493
468
|
|
|
494
469
|
$._textStack.push(text);
|
|
495
|
-
$.drawStack.push(2, measurements.printedCharCount);
|
|
470
|
+
$.drawStack.push(2, measurements.printedCharCount, $._font.index);
|
|
496
471
|
};
|
|
497
472
|
|
|
498
473
|
$.textWidth = (str) => {
|
|
@@ -505,11 +480,11 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
|
|
|
505
480
|
|
|
506
481
|
if ($._doFill) {
|
|
507
482
|
let fi = $._fillIndex * 4;
|
|
508
|
-
g.fill(
|
|
483
|
+
g.fill(colorStack.slice(fi, fi + 4));
|
|
509
484
|
}
|
|
510
485
|
if ($._doStroke) {
|
|
511
486
|
let si = $._strokeIndex * 4;
|
|
512
|
-
g.stroke(
|
|
487
|
+
g.stroke(colorStack.slice(si, si + 4));
|
|
513
488
|
}
|
|
514
489
|
|
|
515
490
|
let img = g.createTextImage(str, w, h);
|
|
@@ -560,21 +535,15 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
|
|
|
560
535
|
totalTextSize += charsData.length * 4;
|
|
561
536
|
}
|
|
562
537
|
|
|
563
|
-
// Create a single buffer for all
|
|
538
|
+
// Create a single buffer for all char data
|
|
564
539
|
let charBuffer = Q5.device.createBuffer({
|
|
565
|
-
label: 'charBuffer',
|
|
566
540
|
size: totalTextSize,
|
|
567
541
|
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
568
542
|
mappedAtCreation: true
|
|
569
543
|
});
|
|
570
544
|
|
|
571
545
|
// Copy all text data into the buffer
|
|
572
|
-
|
|
573
|
-
let o = 0;
|
|
574
|
-
for (let array of $._charStack) {
|
|
575
|
-
textArray.set(array, o);
|
|
576
|
-
o += array.length;
|
|
577
|
-
}
|
|
546
|
+
new Float32Array(charBuffer.getMappedRange()).set($._charStack.flat());
|
|
578
547
|
charBuffer.unmap();
|
|
579
548
|
|
|
580
549
|
// Calculate total buffer size for metadata
|
|
@@ -589,12 +558,7 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
|
|
|
589
558
|
});
|
|
590
559
|
|
|
591
560
|
// Copy all metadata into the buffer
|
|
592
|
-
|
|
593
|
-
o = 0;
|
|
594
|
-
for (let array of $._textStack) {
|
|
595
|
-
metadataArray.set(array, o);
|
|
596
|
-
o += array.length;
|
|
597
|
-
}
|
|
561
|
+
new Float32Array(textBuffer.getMappedRange()).set($._textStack.flat());
|
|
598
562
|
textBuffer.unmap();
|
|
599
563
|
|
|
600
564
|
// Create a single bind group for the text buffer and metadata buffer
|
|
@@ -602,14 +566,8 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
|
|
|
602
566
|
label: 'msdf text bind group',
|
|
603
567
|
layout: textBindGroupLayout,
|
|
604
568
|
entries: [
|
|
605
|
-
{
|
|
606
|
-
|
|
607
|
-
resource: { buffer: charBuffer }
|
|
608
|
-
},
|
|
609
|
-
{
|
|
610
|
-
binding: 1,
|
|
611
|
-
resource: { buffer: textBuffer }
|
|
612
|
-
}
|
|
569
|
+
{ binding: 0, resource: { buffer: charBuffer } },
|
|
570
|
+
{ binding: 1, resource: { buffer: textBuffer } }
|
|
613
571
|
]
|
|
614
572
|
});
|
|
615
573
|
});
|
package/src/readme.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# q5.js Source Documentation
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
For modular use, the "q5-core.js" module must be loaded first.
|
|
4
4
|
|
|
5
5
|
```html
|
|
6
6
|
<script src="https://q5js.org/src/q5-core.js"></script>
|
|
@@ -9,6 +9,7 @@ To use q5 modules, "q5.js" (the default bundle) or the "q5-core.js" module must
|
|
|
9
9
|
These modules are included in the default "q5.js" bundle:
|
|
10
10
|
|
|
11
11
|
```html
|
|
12
|
+
<script src="https://q5js.org/src/q5-core.js"></script>
|
|
12
13
|
<script src="https://q5js.org/src/q5-2d-canvas.js"></script>
|
|
13
14
|
<script src="https://q5js.org/src/q5-2d-drawing.js"></script>
|
|
14
15
|
<script src="https://q5js.org/src/q5-2d-image.js"></script>
|
|
@@ -37,19 +38,21 @@ WebGPU rendering modules are in development:
|
|
|
37
38
|
```html
|
|
38
39
|
<script src="https://q5js.org/src/q5-webgpu-canvas.js"></script>
|
|
39
40
|
<script src="https://q5js.org/src/q5-webgpu-drawing.js"></script>
|
|
41
|
+
<script src="https://q5js.org/src/q5-webgpu-image.js"></script>
|
|
42
|
+
<script src="https://q5js.org/src/q5-webgpu-text.js"></script>
|
|
40
43
|
```
|
|
41
44
|
|
|
42
45
|
# Module Info
|
|
43
46
|
|
|
44
|
-
- [
|
|
47
|
+
- [q5.js Source Documentation](#q5js-source-documentation)
|
|
45
48
|
- [Module Info](#module-info)
|
|
46
|
-
- [core](#core)
|
|
47
|
-
- [canvas](#canvas)
|
|
49
|
+
- [q5-core](#q5-core)
|
|
50
|
+
- [q5-canvas](#q5-canvas)
|
|
48
51
|
- [q2d-canvas](#q2d-canvas)
|
|
49
|
-
- [q2d-drawing](#q2d-drawing)
|
|
50
|
-
- [q2d-image](#q2d-image)
|
|
51
|
-
- [q2d-soft-filters](#q2d-soft-filters)
|
|
52
|
-
- [q2d-text](#q2d-text)
|
|
52
|
+
- [q5-q2d-drawing](#q5-q2d-drawing)
|
|
53
|
+
- [q5-q2d-image](#q5-q2d-image)
|
|
54
|
+
- [q5-q2d-soft-filters](#q5-q2d-soft-filters)
|
|
55
|
+
- [q5-q2d-text](#q5-q2d-text)
|
|
53
56
|
- [webgpu-canvas](#webgpu-canvas)
|
|
54
57
|
- [webgpu-drawing](#webgpu-drawing)
|
|
55
58
|
- [webgpu-image](#webgpu-image)
|
|
@@ -59,19 +62,19 @@ WebGPU rendering modules are in development:
|
|
|
59
62
|
- [Load a MSDF font](#load-a-msdf-font)
|
|
60
63
|
- [Displaying Emojis](#displaying-emojis)
|
|
61
64
|
- [Lightweight Use](#lightweight-use)
|
|
62
|
-
- [
|
|
65
|
+
- [Limitations](#limitations)
|
|
63
66
|
- [math](#math)
|
|
64
67
|
- [noisier](#noisier)
|
|
65
68
|
|
|
66
|
-
## core
|
|
69
|
+
## q5-core
|
|
67
70
|
|
|
68
71
|
The core module provides the absolute basic functionality necessary to run q5.
|
|
69
72
|
|
|
70
73
|
It loads other modules by passing `$` (alias for `this`) and `q` (which in global mode is a proxy for `this` and `window` or `global`).
|
|
71
74
|
|
|
72
|
-
## canvas
|
|
75
|
+
## q5-canvas
|
|
73
76
|
|
|
74
|
-
The canvas module provides shared functionality for all canvas renderers, such as adding the canvas to the DOM, resizing the canvas, setting pixel density
|
|
77
|
+
The canvas module provides shared functionality for all canvas renderers, such as adding the canvas to the DOM, resizing the canvas, setting pixel density.
|
|
75
78
|
|
|
76
79
|
## q2d-canvas
|
|
77
80
|
|
|
@@ -81,17 +84,17 @@ All other 2D modules depend on this module.
|
|
|
81
84
|
|
|
82
85
|
Though loading q5-color is recommend, it's not required since `fill` and `stroke` can be set to a CSS color string.
|
|
83
86
|
|
|
84
|
-
## q2d-drawing
|
|
87
|
+
## q5-q2d-drawing
|
|
85
88
|
|
|
86
89
|
Adds Canvas2D drawing functions to q5.
|
|
87
90
|
|
|
88
|
-
## q2d-image
|
|
91
|
+
## q5-q2d-image
|
|
89
92
|
|
|
90
93
|
Adds Canvas2D image support to q5.
|
|
91
94
|
|
|
92
95
|
The filters in q5-image use the [CanvasRenderingContext2D.filter](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter) property to apply native hardware-accelerated filters to images.
|
|
93
96
|
|
|
94
|
-
## q2d-soft-filters
|
|
97
|
+
## q5-q2d-soft-filters
|
|
95
98
|
|
|
96
99
|
Software implementation of image filters.
|
|
97
100
|
|
|
@@ -101,23 +104,17 @@ These filters are slow. Real-time use of them is not recommended.
|
|
|
101
104
|
|
|
102
105
|
As of April 2024, Safari Technology Preview supports `ctx.filter` under a flag. Hopefully in the near future this module can be omitted from the default bundle.
|
|
103
106
|
|
|
104
|
-
## q2d-text
|
|
107
|
+
## q5-q2d-text
|
|
105
108
|
|
|
106
109
|
Adds Canvas2D text rendering support to q5.
|
|
107
110
|
|
|
108
111
|
Image based features in this module require the q5-2d-image module.
|
|
109
112
|
|
|
110
|
-
`createTextImage(str, w, h)` provides a simple way for users to create images from text.
|
|
111
|
-
|
|
112
|
-
`textImage(img, x, y)` displays text images, complying with the user's text position settings instead of their image position settings. The idea is that text will appear in the same place as it would if it were drawn with the `text` function.
|
|
113
|
-
|
|
114
|
-
`textCache(bool, maxSize)` enables or disables text caching.
|
|
115
|
-
|
|
116
113
|
## webgpu-canvas
|
|
117
114
|
|
|
118
115
|
> ⚠️ Experimental features! ⚠️
|
|
119
116
|
|
|
120
|
-
To use q5's WebGPU renderer, run `Q5.webgpu()` at the bottom of your sketch.
|
|
117
|
+
To use q5's WebGPU renderer, run `Q5.webgpu()` at the bottom of your sketch.
|
|
121
118
|
|
|
122
119
|
```js
|
|
123
120
|
function setup() {
|
|
@@ -135,53 +132,24 @@ Q5.webgpu();
|
|
|
135
132
|
|
|
136
133
|
WebGPU has different default settings compared to q5's q2d renderer and p5's P2D and WEBGL modes.
|
|
137
134
|
|
|
135
|
+
- Explicit use of `createCanvas` is required before anything can be drawn.
|
|
138
136
|
- The default color mode is RGB in 0-1 "float" format: `colorMode(RGB, 1)`.
|
|
139
137
|
- The origin of the canvas (0, 0) is in the center, not the top left.
|
|
140
|
-
- Mouse and touch coordinates correspond to canvas pixels.
|
|
141
|
-
- Use `textFill` and `textStroke` to set text colors.
|
|
142
|
-
- For now, strokes are only implemented for the `point`, `line`, and `text` functions.
|
|
138
|
+
- Mouse and touch coordinates correspond to canvas pixels (unlike in p5 WEBGL mode).
|
|
143
139
|
|
|
144
140
|
The sketches you create with the q5-webgpu renderer will still display properly if WebGPU is not supported on a viewer's browser. q5 will put a warning in the console and apply a compatibility layer to display sketches with the fallback q2d renderer.
|
|
145
141
|
|
|
146
|
-
Use of top level global mode with the WebGPU renderer requires that you make your sketch file a js module and await for `Q5.webgpu()` to return the Q5 instance (`q`), which you can then use to set q5 functions such as `draw`.
|
|
147
|
-
|
|
148
|
-
```html
|
|
149
|
-
<script type="module" src="sketch.js">
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
```js
|
|
153
|
-
let q = await Q5.webgpu();
|
|
154
|
-
|
|
155
|
-
createCanvas(200, 200);
|
|
156
|
-
noStroke();
|
|
157
|
-
|
|
158
|
-
q.draw = () => {
|
|
159
|
-
clear();
|
|
160
|
-
rect(50, 50, 100, 100);
|
|
161
|
-
};
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
Implemented functions:
|
|
165
|
-
|
|
166
|
-
`createCanvas`, `resizeCanvas`, `fill`, `clear`, `push`, `pop`, `resetMatrix`, `translate`, `rotate`, `scale`
|
|
167
|
-
|
|
168
142
|
## webgpu-drawing
|
|
169
143
|
|
|
170
144
|
> Uses `colorMode(RGB, 1)` by default. Changing it to 'oklch' is not supported yet for the webgpu renderer.
|
|
171
145
|
|
|
172
|
-
All basic shapes are drawn from their center. Strokes are not implemented yet.
|
|
173
|
-
|
|
174
146
|
q5's WebGPU renderer drawing functions like `rect` don't immediately draw on the canvas. Instead, they prepare vertex and color data to be sent to the GPU in bulk, which occurs after the user's `draw` function and any post-draw functions are run. This approach better utilizes the GPU, so it doesn't have to repeatedly wait for the CPU to send small chunks of data that describe each individual shape. It's the main reason why WebGPU is faster than Canvas2D.
|
|
175
147
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
`rect`, `circle`, `ellipse`, `triangle`, `beginShape`, `vertex`, `endShape`, `blendMode`
|
|
148
|
+
Rounded rectangles, stroke modes, and functions for drawing curves like `bezier` and `curve` are not implemented yet.
|
|
179
149
|
|
|
180
150
|
## webgpu-image
|
|
181
151
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
`loadImage`, `loadTexture`, `image`, `imageMode`
|
|
152
|
+
Using `image` to drawn a subsection of an image and most blending modes are not yet implemented.
|
|
185
153
|
|
|
186
154
|
## webgpu-text
|
|
187
155
|
|
|
@@ -260,9 +228,9 @@ For super lightweight use load <https://q5js.org/fonts/YaHei-256-msdf.json>, whi
|
|
|
260
228
|
!@'",-.0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
|
|
261
229
|
```
|
|
262
230
|
|
|
263
|
-
###
|
|
231
|
+
### Limitations
|
|
264
232
|
|
|
265
|
-
|
|
233
|
+
Text strokes are not supported yet, except with `textImage`.
|
|
266
234
|
|
|
267
235
|
## math
|
|
268
236
|
|