p3nGu1nZz commited on
Commit
4313cb9
1 Parent(s): 8ab0d15
Files changed (2) hide show
  1. index.html +6 -38
  2. utility.js +15 -0
index.html CHANGED
@@ -4,31 +4,16 @@
4
  <head>
5
  <meta charset="utf-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Plasma-Arc: WebGPU Experiment</title>
8
  </head>
9
 
10
  <body>
11
  <canvas></canvas>
12
  <script type="module">
13
  import { mat4 } from 'https://webgpufundamentals.org/3rdparty/wgpu-matrix.module.js';
14
- import { fetchShaderCode } from './utility.js';
15
  import { CONFIG } from './config.js';
16
  import { CANVAS, CTX, COLORS, RENDER_PASS_DESCRIPTOR } from './constants.js';
17
-
18
- function generateGlyphTextureAtlas(canvas, ctx, config) {
19
- canvas.width = config.canvas.width;
20
- canvas.height = config.canvas.height;
21
- ctx.font = config.context.font
22
- ctx.textBaseline = config.context.textBaseline;
23
- ctx.textAlign = config.context.textAlign;
24
- ctx.fillStyle = config.context.fillStyle;
25
- for (let c = 33, x = 0, y = 0; c < 128; ++c) {
26
- ctx.fillText(String.fromCodePoint(c), x + config.glyphWidth / 2, y + config.glyphHeight / 2);
27
- x = (x + config.glyphWidth) % canvas.width;
28
- if (x === 0) y += config.glyphHeight;
29
- }
30
- return canvas;
31
- }
32
  async function main() {
33
  const adapter = await navigator.gpu?.requestAdapter();
34
  const device = await adapter?.requestDevice();
@@ -36,7 +21,6 @@
36
  alert('need a browser that supports WebGPU');
37
  return;
38
  }
39
-
40
  const canvas = document.querySelector('canvas');
41
  const context = canvas.getContext('webgpu');
42
  const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
@@ -44,13 +28,11 @@
44
  device,
45
  format: presentationFormat,
46
  });
47
-
48
  const shaderCode = await fetchShaderCode('shaders.wgsl');
49
  const module = device.createShaderModule({
50
  label: 'textured quad shaders',
51
  code: shaderCode,
52
  });
53
-
54
  const glyphCanvas = generateGlyphTextureAtlas(CANVAS, CTX, CONFIG);
55
  document.body.appendChild(glyphCanvas);
56
  glyphCanvas.style.backgroundColor = '#222';
@@ -66,25 +48,21 @@
66
  size: CONFIG.maxGlyphs * CONFIG.vertsPerGlyph * 4,
67
  usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
68
  });
69
-
70
  const indices = Array.from({ length: CONFIG.maxGlyphs * 6 }, (_, i) => {
71
  const ndx = Math.floor(i / 6) * 4;
72
  return (i % 6 < 3 ? [ndx, ndx + 1, ndx + 2] : [ndx + 2, ndx + 1, ndx + 3])[i % 3];
73
  });
74
  device.queue.writeBuffer(indexBuffer, 0, new Uint32Array(indices));
75
-
76
  function generateGlyphVerticesForText(s, COLORS = [[1, 1, 1, 1]]) {
77
  const vertexData = new Float32Array(CONFIG.maxGlyphs * CONFIG.floatsPerVertex * CONFIG.vertsPerGlyph);
78
  const glyphUVWidth = CONFIG.glyphWidth / glyphCanvas.width;
79
  const glyphUVHeight = CONFIG.glyphHeight / glyphCanvas.height;
80
  let offset = 0, x0 = 0, y0 = 0, x1 = 1, y1 = 1, width = 0;
81
  let colorNdx = 0;
82
-
83
  const addVertex = (x, y, u, v, color) => {
84
  vertexData.set([x, y, u, v, ...color], offset);
85
  offset += 8;
86
  };
87
-
88
  for (let i = 0; i < s.length; ++i) {
89
  const c = s.charCodeAt(i);
90
  if (c >= 33) {
@@ -96,14 +74,13 @@
96
  const u1 = u0 + glyphUVWidth;
97
  const v0 = v1 + glyphUVHeight;
98
  width = Math.max(x1, width);
99
-
100
  addVertex(x0, y0, u0, v0, COLORS[colorNdx]);
101
  addVertex(x1, y0, u1, v0, COLORS[colorNdx]);
102
  addVertex(x0, y1, u0, v1, COLORS[colorNdx]);
103
  addVertex(x1, y1, u1, v1, COLORS[colorNdx]);
104
  } else {
105
  colorNdx = (colorNdx + 1) % COLORS.length;
106
- if (c === 10) { // Newline character
107
  x0 = 0; x1 = 1; y0--; y1 = y0 + 1;
108
  continue;
109
  }
@@ -112,10 +89,8 @@
112
  }
113
  return { vertexData, numGlyphs: offset / CONFIG.floatsPerVertex, width, height: y1 };
114
  }
115
-
116
  const { vertexData, numGlyphs, width, height } = generateGlyphVerticesForText('Hello\nworld!\nText in\nWebGPU!', COLORS);
117
  device.queue.writeBuffer(vertexBuffer, 0, vertexData);
118
-
119
  const pipeline = device.createRenderPipeline({
120
  label: 'textured quad pipeline',
121
  layout: 'auto',
@@ -126,9 +101,9 @@
126
  {
127
  arrayStride: vertexSize,
128
  attributes: [
129
- { shaderLocation: 0, offset: 0, format: 'float32x2' }, // position
130
- { shaderLocation: 1, offset: 8, format: 'float32x2' }, // texcoord
131
- { shaderLocation: 2, offset: 16, format: 'float32x4' } // color
132
  ],
133
  },
134
  ],
@@ -145,29 +120,24 @@
145
  }],
146
  },
147
  });
148
-
149
  function createTextureFromSource(device, source, options = {}) {
150
  const texture = device.createTexture({
151
  format: 'rgba8unorm',
152
  size: [source.width, source.height],
153
  usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
154
  });
155
-
156
  device.queue.copyExternalImageToTexture(
157
  { source, flipY: options.flipY },
158
  { texture, premultipliedAlpha: true },
159
  { width: source.width, height: source.height }
160
  );
161
-
162
  return texture;
163
  }
164
-
165
  const texture = createTextureFromSource(device, glyphCanvas, { mips: true });
166
  const sampler = device.createSampler({
167
  minFilter: 'linear',
168
  magFilter: 'linear'
169
  });
170
-
171
  const uniformBuffer = device.createBuffer({
172
  label: 'uniforms for quad',
173
  size: CONFIG.uniformBufferSize,
@@ -175,7 +145,6 @@
175
  });
176
  const uniformValues = new Float32Array(CONFIG.uniformBufferSize / 4);
177
  const matrix = uniformValues.subarray(0, 16);
178
-
179
  const bindGroup = device.createBindGroup({
180
  layout: pipeline.getBindGroupLayout(0),
181
  entries: [
@@ -184,7 +153,6 @@
184
  { binding: 2, resource: { buffer: uniformBuffer } },
185
  ],
186
  });
187
-
188
  function render(time) {
189
  time *= CONFIG.time.phase;
190
  const fov = 60 * Math.PI / 180;
 
4
  <head>
5
  <meta charset="utf-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Plasma-Arc: WGPU Experiment</title>
8
  </head>
9
 
10
  <body>
11
  <canvas></canvas>
12
  <script type="module">
13
  import { mat4 } from 'https://webgpufundamentals.org/3rdparty/wgpu-matrix.module.js';
14
+ import { fetchShaderCode, generateGlyphTextureAtlas } from './utility.js';
15
  import { CONFIG } from './config.js';
16
  import { CANVAS, CTX, COLORS, RENDER_PASS_DESCRIPTOR } from './constants.js';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  async function main() {
18
  const adapter = await navigator.gpu?.requestAdapter();
19
  const device = await adapter?.requestDevice();
 
21
  alert('need a browser that supports WebGPU');
22
  return;
23
  }
 
24
  const canvas = document.querySelector('canvas');
25
  const context = canvas.getContext('webgpu');
26
  const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
 
28
  device,
29
  format: presentationFormat,
30
  });
 
31
  const shaderCode = await fetchShaderCode('shaders.wgsl');
32
  const module = device.createShaderModule({
33
  label: 'textured quad shaders',
34
  code: shaderCode,
35
  });
 
36
  const glyphCanvas = generateGlyphTextureAtlas(CANVAS, CTX, CONFIG);
37
  document.body.appendChild(glyphCanvas);
38
  glyphCanvas.style.backgroundColor = '#222';
 
48
  size: CONFIG.maxGlyphs * CONFIG.vertsPerGlyph * 4,
49
  usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
50
  });
 
51
  const indices = Array.from({ length: CONFIG.maxGlyphs * 6 }, (_, i) => {
52
  const ndx = Math.floor(i / 6) * 4;
53
  return (i % 6 < 3 ? [ndx, ndx + 1, ndx + 2] : [ndx + 2, ndx + 1, ndx + 3])[i % 3];
54
  });
55
  device.queue.writeBuffer(indexBuffer, 0, new Uint32Array(indices));
 
56
  function generateGlyphVerticesForText(s, COLORS = [[1, 1, 1, 1]]) {
57
  const vertexData = new Float32Array(CONFIG.maxGlyphs * CONFIG.floatsPerVertex * CONFIG.vertsPerGlyph);
58
  const glyphUVWidth = CONFIG.glyphWidth / glyphCanvas.width;
59
  const glyphUVHeight = CONFIG.glyphHeight / glyphCanvas.height;
60
  let offset = 0, x0 = 0, y0 = 0, x1 = 1, y1 = 1, width = 0;
61
  let colorNdx = 0;
 
62
  const addVertex = (x, y, u, v, color) => {
63
  vertexData.set([x, y, u, v, ...color], offset);
64
  offset += 8;
65
  };
 
66
  for (let i = 0; i < s.length; ++i) {
67
  const c = s.charCodeAt(i);
68
  if (c >= 33) {
 
74
  const u1 = u0 + glyphUVWidth;
75
  const v0 = v1 + glyphUVHeight;
76
  width = Math.max(x1, width);
 
77
  addVertex(x0, y0, u0, v0, COLORS[colorNdx]);
78
  addVertex(x1, y0, u1, v0, COLORS[colorNdx]);
79
  addVertex(x0, y1, u0, v1, COLORS[colorNdx]);
80
  addVertex(x1, y1, u1, v1, COLORS[colorNdx]);
81
  } else {
82
  colorNdx = (colorNdx + 1) % COLORS.length;
83
+ if (c === 10) { // Newline
84
  x0 = 0; x1 = 1; y0--; y1 = y0 + 1;
85
  continue;
86
  }
 
89
  }
90
  return { vertexData, numGlyphs: offset / CONFIG.floatsPerVertex, width, height: y1 };
91
  }
 
92
  const { vertexData, numGlyphs, width, height } = generateGlyphVerticesForText('Hello\nworld!\nText in\nWebGPU!', COLORS);
93
  device.queue.writeBuffer(vertexBuffer, 0, vertexData);
 
94
  const pipeline = device.createRenderPipeline({
95
  label: 'textured quad pipeline',
96
  layout: 'auto',
 
101
  {
102
  arrayStride: vertexSize,
103
  attributes: [
104
+ { shaderLocation: 0, offset: 0, format: 'float32x2' }, // pos
105
+ { shaderLocation: 1, offset: 8, format: 'float32x2' }, // tex
106
+ { shaderLocation: 2, offset: 16, format: 'float32x4' } // col
107
  ],
108
  },
109
  ],
 
120
  }],
121
  },
122
  });
 
123
  function createTextureFromSource(device, source, options = {}) {
124
  const texture = device.createTexture({
125
  format: 'rgba8unorm',
126
  size: [source.width, source.height],
127
  usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
128
  });
 
129
  device.queue.copyExternalImageToTexture(
130
  { source, flipY: options.flipY },
131
  { texture, premultipliedAlpha: true },
132
  { width: source.width, height: source.height }
133
  );
 
134
  return texture;
135
  }
 
136
  const texture = createTextureFromSource(device, glyphCanvas, { mips: true });
137
  const sampler = device.createSampler({
138
  minFilter: 'linear',
139
  magFilter: 'linear'
140
  });
 
141
  const uniformBuffer = device.createBuffer({
142
  label: 'uniforms for quad',
143
  size: CONFIG.uniformBufferSize,
 
145
  });
146
  const uniformValues = new Float32Array(CONFIG.uniformBufferSize / 4);
147
  const matrix = uniformValues.subarray(0, 16);
 
148
  const bindGroup = device.createBindGroup({
149
  layout: pipeline.getBindGroupLayout(0),
150
  entries: [
 
153
  { binding: 2, resource: { buffer: uniformBuffer } },
154
  ],
155
  });
 
156
  function render(time) {
157
  time *= CONFIG.time.phase;
158
  const fov = 60 * Math.PI / 180;
utility.js CHANGED
@@ -3,4 +3,19 @@
3
  export async function fetchShaderCode(url) {
4
  const response = await fetch(url);
5
  return await response.text();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  }
 
3
  export async function fetchShaderCode(url) {
4
  const response = await fetch(url);
5
  return await response.text();
6
+ }
7
+
8
+ export function generateGlyphTextureAtlas(canvas, ctx, config) {
9
+ canvas.width = config.canvas.width;
10
+ canvas.height = config.canvas.height;
11
+ ctx.font = config.context.font
12
+ ctx.textBaseline = config.context.textBaseline;
13
+ ctx.textAlign = config.context.textAlign;
14
+ ctx.fillStyle = config.context.fillStyle;
15
+ for (let c = 33, x = 0, y = 0; c < 128; ++c) {
16
+ ctx.fillText(String.fromCodePoint(c), x + config.glyphWidth / 2, y + config.glyphHeight / 2);
17
+ x = (x + config.glyphWidth) % canvas.width;
18
+ if (x === 0) y += config.glyphHeight;
19
+ }
20
+ return canvas;
21
  }