q5 2.5.5 → 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/package.json +2 -2
- package/q5.js +600 -518
- package/q5.min.js +2 -2
- package/src/q5-2d-canvas.js +2 -4
- package/src/q5-core.js +2 -2
- package/src/q5-webgpu-canvas.js +176 -44
- 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 +8 -18
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
|
@@ -62,7 +62,7 @@ WebGPU rendering modules are in development:
|
|
|
62
62
|
- [Load a MSDF font](#load-a-msdf-font)
|
|
63
63
|
- [Displaying Emojis](#displaying-emojis)
|
|
64
64
|
- [Lightweight Use](#lightweight-use)
|
|
65
|
-
- [
|
|
65
|
+
- [Limitations](#limitations)
|
|
66
66
|
- [math](#math)
|
|
67
67
|
- [noisier](#noisier)
|
|
68
68
|
|
|
@@ -114,7 +114,7 @@ Image based features in this module require the q5-2d-image module.
|
|
|
114
114
|
|
|
115
115
|
> ⚠️ Experimental features! ⚠️
|
|
116
116
|
|
|
117
|
-
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.
|
|
118
118
|
|
|
119
119
|
```js
|
|
120
120
|
function setup() {
|
|
@@ -132,34 +132,24 @@ Q5.webgpu();
|
|
|
132
132
|
|
|
133
133
|
WebGPU has different default settings compared to q5's q2d renderer and p5's P2D and WEBGL modes.
|
|
134
134
|
|
|
135
|
+
- Explicit use of `createCanvas` is required before anything can be drawn.
|
|
135
136
|
- The default color mode is RGB in 0-1 "float" format: `colorMode(RGB, 1)`.
|
|
136
137
|
- The origin of the canvas (0, 0) is in the center, not the top left.
|
|
137
|
-
- Mouse and touch coordinates correspond to canvas pixels.
|
|
138
|
-
- For now, strokes are only implemented for the `point`, `line`, and `textImage` functions.
|
|
138
|
+
- Mouse and touch coordinates correspond to canvas pixels (unlike in p5 WEBGL mode).
|
|
139
139
|
|
|
140
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.
|
|
141
141
|
|
|
142
|
-
Implemented functions:
|
|
143
|
-
|
|
144
|
-
`createCanvas`, `resizeCanvas`, `fill`, `clear`, `push`, `pop`, `resetMatrix`, `translate`, `rotate`, `scale`
|
|
145
|
-
|
|
146
142
|
## webgpu-drawing
|
|
147
143
|
|
|
148
144
|
> Uses `colorMode(RGB, 1)` by default. Changing it to 'oklch' is not supported yet for the webgpu renderer.
|
|
149
145
|
|
|
150
|
-
All basic shapes are drawn from their center. Strokes are not implemented yet.
|
|
151
|
-
|
|
152
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.
|
|
153
147
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
`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.
|
|
157
149
|
|
|
158
150
|
## webgpu-image
|
|
159
151
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
`loadImage`, `loadTexture`, `image`, `imageMode`
|
|
152
|
+
Using `image` to drawn a subsection of an image and most blending modes are not yet implemented.
|
|
163
153
|
|
|
164
154
|
## webgpu-text
|
|
165
155
|
|
|
@@ -238,9 +228,9 @@ For super lightweight use load <https://q5js.org/fonts/YaHei-256-msdf.json>, whi
|
|
|
238
228
|
!@'",-.0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
|
|
239
229
|
```
|
|
240
230
|
|
|
241
|
-
###
|
|
231
|
+
### Limitations
|
|
242
232
|
|
|
243
|
-
|
|
233
|
+
Text strokes are not supported yet, except with `textImage`.
|
|
244
234
|
|
|
245
235
|
## math
|
|
246
236
|
|