Spaces:
Running
Running
state to state
Browse files- index.js +49 -63
- wgpu-state.js +29 -28
- wgpu-texture.js +13 -15
index.js
CHANGED
@@ -4,8 +4,8 @@ import { createState } from './wgpu-state.js';
|
|
4 |
import { generateGlyphTextureAtlas, createTextureFromSource } from './wgpu-utility.js';
|
5 |
import { createPipeline } from './wgpu-pipeline.js';
|
6 |
import { fetchShaderCode } from './wgpu-shader.js';
|
7 |
-
import { GenerateVertexDataAndTexture } from './wgpu-texture.js';
|
8 |
-
import { generateGlyphVerticesForText } from './wgpu-text.js';
|
9 |
import { config } from './wgpu-config.js';
|
10 |
import { CANVAS, CTX, COLORS, RENDER_PASS_DESCRIPTOR } from './wgpu-constants.js';
|
11 |
|
@@ -14,140 +14,126 @@ const canvas = document.querySelector('canvas');
|
|
14 |
|
15 |
// State initialization
|
16 |
const state = createState(config);
|
17 |
-
state.canvas = canvas;
|
18 |
|
19 |
-
const FIXED_DELTA_TIME = 1 / 60;
|
20 |
-
const MAX_FRAME_TIME = 0.25;
|
21 |
-
const TARGET_FPS = 60;
|
22 |
-
const FRAME_DURATION = 1000 / TARGET_FPS;
|
23 |
|
24 |
-
|
25 |
-
state.
|
26 |
-
state.
|
27 |
-
state.
|
|
|
|
|
28 |
|
29 |
async function Main() {
|
30 |
const adapter = await navigator.gpu?.requestAdapter();
|
31 |
-
const { device, context, presentationFormat } = await initializeWebGPU(navigator, adapter, state.canvas);
|
32 |
if (!device) return;
|
33 |
|
34 |
-
state.device = device;
|
|
|
|
|
35 |
|
36 |
// Initialize Resources
|
37 |
-
await InitializeResources(
|
38 |
|
39 |
// Start the game loop
|
40 |
-
GameLoop(
|
41 |
}
|
42 |
|
43 |
-
|
44 |
-
async function InitializeResources(presentationFormat) {
|
45 |
-
// Load shader code
|
46 |
const shaderCode = await fetchShaderCode('shaders.wgsl');
|
47 |
const vertexSize = config.floatsPerVertex * 4;
|
48 |
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
// Generate glyph texture atlas
|
53 |
const glyphCanvas = generateGlyphTextureAtlas(CANVAS, CTX, config);
|
54 |
document.body.appendChild(glyphCanvas);
|
55 |
glyphCanvas.style.backgroundColor = '#222';
|
56 |
|
57 |
-
// Create vertex and index buffers
|
58 |
CreateBuffers();
|
59 |
|
60 |
-
// Generate vertex buffer data and texture
|
61 |
GenerateVertexDataAndTexture(state, glyphCanvas, generateGlyphVerticesForText, COLORS, config, createTextureFromSource);
|
62 |
}
|
63 |
|
64 |
-
// Function to create vertex and index buffers
|
65 |
function CreateBuffers() {
|
66 |
const vertexBufferSize = config.maxGlyphs * config.vertsPerGlyph * config.floatsPerVertex * 4;
|
67 |
-
state.vertexBuffer = state.device.createBuffer({
|
68 |
label: 'vertices',
|
69 |
size: vertexBufferSize,
|
70 |
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
71 |
});
|
72 |
|
73 |
-
state.indexBuffer = state.device.createBuffer({
|
74 |
label: 'indices',
|
75 |
size: config.maxGlyphs * config.vertsPerGlyph * 4,
|
76 |
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
77 |
});
|
78 |
|
79 |
const indices = GenerateIndices(config.maxGlyphs);
|
80 |
-
state.device.queue.writeBuffer(state.indexBuffer, 0, new Uint32Array(indices));
|
81 |
}
|
82 |
|
83 |
-
// Function to generate indices for glyphs
|
84 |
function GenerateIndices(maxGlyphs) {
|
85 |
-
// Generate index array for glyphs
|
86 |
return Array.from({ length: maxGlyphs * 6 }, (_, i) => {
|
87 |
const ndx = Math.floor(i / 6) * 4;
|
88 |
return (i % 6 < 3 ? [ndx, ndx + 1, ndx + 2] : [ndx + 2, ndx + 1, ndx + 3])[i % 3];
|
89 |
});
|
90 |
}
|
91 |
|
92 |
-
|
93 |
-
function GameLoop(context) {
|
94 |
let lastTime = performance.now();
|
95 |
-
state.accumulator = 0;
|
96 |
|
97 |
function Tick() {
|
98 |
const currentTime = performance.now();
|
99 |
const frameTime = (currentTime - lastTime) / 1000;
|
100 |
lastTime = currentTime;
|
101 |
|
102 |
-
const deltaTime = Math.min(frameTime, state.maxFrameTime);
|
103 |
-
state.accumulator += deltaTime;
|
104 |
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
state.accumulator -= state.fixedDeltaTime;
|
109 |
}
|
110 |
|
111 |
-
|
112 |
-
|
113 |
-
Render(alpha, context);
|
114 |
|
115 |
-
|
116 |
-
setTimeout(Tick, state.frameDuration);
|
117 |
}
|
118 |
|
119 |
Tick();
|
120 |
}
|
121 |
|
122 |
-
// Fixed update function for game logic
|
123 |
function FixedUpdate(deltaTime) {
|
124 |
-
state.time += deltaTime;
|
125 |
-
// Perform game logic updates here, such as physics and AI
|
126 |
}
|
127 |
|
128 |
-
|
129 |
-
function Render(alpha, context) {
|
130 |
-
// Set up projection and view matrices
|
131 |
const fov = 60 * Math.PI / 180;
|
132 |
-
const aspect = state.canvas.clientWidth / state.canvas.clientHeight;
|
133 |
const projectionMatrix = mat4.perspective(fov, aspect, config.render.zNear, config.render.zFar);
|
134 |
const viewMatrix = mat4.lookAt([0, 0, 5], [0, 0, 0], [0, 1, 0]);
|
135 |
const viewProjectionMatrix = mat4.multiply(projectionMatrix, viewMatrix);
|
136 |
-
RENDER_PASS_DESCRIPTOR.colorAttachments[0].view = context.getCurrentTexture().createView();
|
137 |
|
138 |
-
const encoder = state.device.createCommandEncoder();
|
139 |
const pass = encoder.beginRenderPass(RENDER_PASS_DESCRIPTOR);
|
140 |
-
pass.setPipeline(state.pipeline);
|
141 |
-
mat4.rotateY(viewProjectionMatrix, state.time, state.matrix);
|
142 |
-
mat4.translate(state.matrix, [-state.width / 2, -state.height / 2, 0], state.matrix);
|
143 |
-
state.device.queue.writeBuffer(state.uniformBuffer, 0, state.uniformValues);
|
144 |
-
pass.setBindGroup(0, state.bindGroup);
|
145 |
-
pass.setVertexBuffer(0, state.vertexBuffer);
|
146 |
-
pass.setIndexBuffer(state.indexBuffer, 'uint32');
|
147 |
-
pass.drawIndexed(state.numGlyphs * 6);
|
148 |
pass.end();
|
149 |
-
state.device.queue.submit([encoder.finish()]);
|
150 |
}
|
151 |
|
152 |
-
// Initialize application
|
153 |
Main();
|
|
|
4 |
import { generateGlyphTextureAtlas, createTextureFromSource } from './wgpu-utility.js';
|
5 |
import { createPipeline } from './wgpu-pipeline.js';
|
6 |
import { fetchShaderCode } from './wgpu-shader.js';
|
7 |
+
import { GenerateVertexDataAndTexture } from './wgpu-texture.js';
|
8 |
+
import { generateGlyphVerticesForText } from './wgpu-text.js';
|
9 |
import { config } from './wgpu-config.js';
|
10 |
import { CANVAS, CTX, COLORS, RENDER_PASS_DESCRIPTOR } from './wgpu-constants.js';
|
11 |
|
|
|
14 |
|
15 |
// State initialization
|
16 |
const state = createState(config);
|
17 |
+
state.canvas = canvas;
|
18 |
|
19 |
+
const FIXED_DELTA_TIME = 1 / 60;
|
20 |
+
const MAX_FRAME_TIME = 0.25;
|
21 |
+
const TARGET_FPS = 60;
|
22 |
+
const FRAME_DURATION = 1000 / TARGET_FPS;
|
23 |
|
24 |
+
// Ensure timing object exists within state
|
25 |
+
state.timing = state.timing || {};
|
26 |
+
state.timing.fixedDeltaTime = FIXED_DELTA_TIME;
|
27 |
+
state.timing.maxFrameTime = MAX_FRAME_TIME;
|
28 |
+
state.timing.targetFps = TARGET_FPS;
|
29 |
+
state.timing.frameDuration = FRAME_DURATION;
|
30 |
|
31 |
async function Main() {
|
32 |
const adapter = await navigator.gpu?.requestAdapter();
|
33 |
+
const { device, context, presentationFormat } = await initializeWebGPU(navigator, adapter, state.canvas);
|
34 |
if (!device) return;
|
35 |
|
36 |
+
state.webgpu.device = device;
|
37 |
+
state.webgpu.context = context;
|
38 |
+
state.webgpu.presentationFormat = presentationFormat;
|
39 |
|
40 |
// Initialize Resources
|
41 |
+
await InitializeResources();
|
42 |
|
43 |
// Start the game loop
|
44 |
+
GameLoop();
|
45 |
}
|
46 |
|
47 |
+
async function InitializeResources() {
|
|
|
|
|
48 |
const shaderCode = await fetchShaderCode('shaders.wgsl');
|
49 |
const vertexSize = config.floatsPerVertex * 4;
|
50 |
|
51 |
+
state.webgpu.pipeline = await createPipeline(state.webgpu.device, state.webgpu.presentationFormat, vertexSize, shaderCode);
|
52 |
+
|
|
|
|
|
53 |
const glyphCanvas = generateGlyphTextureAtlas(CANVAS, CTX, config);
|
54 |
document.body.appendChild(glyphCanvas);
|
55 |
glyphCanvas.style.backgroundColor = '#222';
|
56 |
|
|
|
57 |
CreateBuffers();
|
58 |
|
|
|
59 |
GenerateVertexDataAndTexture(state, glyphCanvas, generateGlyphVerticesForText, COLORS, config, createTextureFromSource);
|
60 |
}
|
61 |
|
|
|
62 |
function CreateBuffers() {
|
63 |
const vertexBufferSize = config.maxGlyphs * config.vertsPerGlyph * config.floatsPerVertex * 4;
|
64 |
+
state.webgpu.vertexBuffer = state.webgpu.device.createBuffer({
|
65 |
label: 'vertices',
|
66 |
size: vertexBufferSize,
|
67 |
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
68 |
});
|
69 |
|
70 |
+
state.webgpu.indexBuffer = state.webgpu.device.createBuffer({
|
71 |
label: 'indices',
|
72 |
size: config.maxGlyphs * config.vertsPerGlyph * 4,
|
73 |
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
74 |
});
|
75 |
|
76 |
const indices = GenerateIndices(config.maxGlyphs);
|
77 |
+
state.webgpu.device.queue.writeBuffer(state.webgpu.indexBuffer, 0, new Uint32Array(indices));
|
78 |
}
|
79 |
|
|
|
80 |
function GenerateIndices(maxGlyphs) {
|
|
|
81 |
return Array.from({ length: maxGlyphs * 6 }, (_, i) => {
|
82 |
const ndx = Math.floor(i / 6) * 4;
|
83 |
return (i % 6 < 3 ? [ndx, ndx + 1, ndx + 2] : [ndx + 2, ndx + 1, ndx + 3])[i % 3];
|
84 |
});
|
85 |
}
|
86 |
|
87 |
+
function GameLoop() {
|
|
|
88 |
let lastTime = performance.now();
|
89 |
+
state.timing.accumulator = 0;
|
90 |
|
91 |
function Tick() {
|
92 |
const currentTime = performance.now();
|
93 |
const frameTime = (currentTime - lastTime) / 1000;
|
94 |
lastTime = currentTime;
|
95 |
|
96 |
+
const deltaTime = Math.min(frameTime, state.timing.maxFrameTime);
|
97 |
+
state.timing.accumulator += deltaTime;
|
98 |
|
99 |
+
while (state.timing.accumulator >= state.timing.fixedDeltaTime) {
|
100 |
+
FixedUpdate(state.timing.fixedDeltaTime);
|
101 |
+
state.timing.accumulator -= state.timing.fixedDeltaTime;
|
|
|
102 |
}
|
103 |
|
104 |
+
const alpha = state.timing.accumulator / state.timing.fixedDeltaTime;
|
105 |
+
Render(alpha);
|
|
|
106 |
|
107 |
+
setTimeout(Tick, state.timing.frameDuration);
|
|
|
108 |
}
|
109 |
|
110 |
Tick();
|
111 |
}
|
112 |
|
|
|
113 |
function FixedUpdate(deltaTime) {
|
114 |
+
state.timing.time += deltaTime;
|
|
|
115 |
}
|
116 |
|
117 |
+
function Render(alpha) {
|
|
|
|
|
118 |
const fov = 60 * Math.PI / 180;
|
119 |
+
const aspect = state.canvas.clientWidth / state.canvas.clientHeight;
|
120 |
const projectionMatrix = mat4.perspective(fov, aspect, config.render.zNear, config.render.zFar);
|
121 |
const viewMatrix = mat4.lookAt([0, 0, 5], [0, 0, 0], [0, 1, 0]);
|
122 |
const viewProjectionMatrix = mat4.multiply(projectionMatrix, viewMatrix);
|
123 |
+
RENDER_PASS_DESCRIPTOR.colorAttachments[0].view = state.webgpu.context.getCurrentTexture().createView();
|
124 |
|
125 |
+
const encoder = state.webgpu.device.createCommandEncoder();
|
126 |
const pass = encoder.beginRenderPass(RENDER_PASS_DESCRIPTOR);
|
127 |
+
pass.setPipeline(state.webgpu.pipeline);
|
128 |
+
mat4.rotateY(viewProjectionMatrix, state.timing.time, state.matrices.matrix);
|
129 |
+
mat4.translate(state.matrices.matrix, [-state.glyphs.width / 2, -state.glyphs.height / 2, 0], state.matrices.matrix);
|
130 |
+
state.webgpu.device.queue.writeBuffer(state.webgpu.uniformBuffer, 0, state.matrices.uniformValues);
|
131 |
+
pass.setBindGroup(0, state.webgpu.bindGroup);
|
132 |
+
pass.setVertexBuffer(0, state.webgpu.vertexBuffer);
|
133 |
+
pass.setIndexBuffer(state.webgpu.indexBuffer, 'uint32');
|
134 |
+
pass.drawIndexed(state.glyphs.numGlyphs * 6);
|
135 |
pass.end();
|
136 |
+
state.webgpu.device.queue.submit([encoder.finish()]);
|
137 |
}
|
138 |
|
|
|
139 |
Main();
|
wgpu-state.js
CHANGED
@@ -1,33 +1,34 @@
|
|
1 |
export function createState(config) {
|
2 |
return {
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
|
|
23 |
canvas: null,
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
};
|
33 |
}
|
|
|
1 |
export function createState(config) {
|
2 |
return {
|
3 |
+
webgpu: {
|
4 |
+
device: null,
|
5 |
+
pipeline: null,
|
6 |
+
vertexBuffer: null,
|
7 |
+
indexBuffer: null,
|
8 |
+
uniformBuffer: null,
|
9 |
+
texture: null,
|
10 |
+
sampler: null,
|
11 |
+
bindGroup: null,
|
12 |
+
context: null,
|
13 |
+
presentationFormat: null,
|
14 |
+
},
|
15 |
+
matrices: {
|
16 |
+
uniformValues: new Float32Array(config.uniformBufferSize / 4),
|
17 |
+
matrix: null,
|
18 |
+
},
|
19 |
+
glyphs: {
|
20 |
+
numGlyphs: 0,
|
21 |
+
width: 0,
|
22 |
+
height: 0,
|
23 |
+
},
|
24 |
canvas: null,
|
25 |
+
timing: {
|
26 |
+
time: 0,
|
27 |
+
fixedDeltaTime: 1 / 60,
|
28 |
+
maxFrameTime: 0.25,
|
29 |
+
targetFps: 60,
|
30 |
+
frameDuration: 1000 / 60,
|
31 |
+
accumulator: 0,
|
32 |
+
}
|
33 |
};
|
34 |
}
|
wgpu-texture.js
CHANGED
@@ -1,34 +1,32 @@
|
|
1 |
-
// wgpu-texture.js
|
2 |
-
|
3 |
export function GenerateVertexDataAndTexture(state, glyphCanvas, generateGlyphVerticesForText, COLORS, config, createTextureFromSource) {
|
4 |
const glyphData = generateGlyphVerticesForText('Hello\nworld!\nText in\nWebGPU!', COLORS, config, glyphCanvas);
|
5 |
-
state.device.queue.writeBuffer(state.vertexBuffer, 0, glyphData.vertexData);
|
6 |
|
7 |
-
state.texture = createTextureFromSource(state.device, glyphCanvas, { mips: true });
|
8 |
-
state.sampler = state.device.createSampler({
|
9 |
minFilter: 'linear',
|
10 |
magFilter: 'linear',
|
11 |
});
|
12 |
|
13 |
-
state.uniformBuffer = state.device.createBuffer({
|
14 |
label: 'uniforms for quad',
|
15 |
size: config.uniformBufferSize,
|
16 |
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
17 |
});
|
18 |
|
19 |
-
state.matrix = state.uniformValues.subarray(0, 16);
|
20 |
|
21 |
-
state.bindGroup = state.device.createBindGroup({
|
22 |
-
layout: state.pipeline.getBindGroupLayout(0),
|
23 |
entries: [
|
24 |
-
{ binding: 0, resource: state.sampler },
|
25 |
-
{ binding: 1, resource: state.texture.createView() },
|
26 |
-
{ binding: 2, resource: { buffer: state.uniformBuffer } },
|
27 |
],
|
28 |
});
|
29 |
|
30 |
// Update state with glyph details
|
31 |
-
state.numGlyphs = glyphData.numGlyphs;
|
32 |
-
state.width = glyphData.width;
|
33 |
-
state.height = glyphData.height;
|
34 |
}
|
|
|
|
|
|
|
1 |
export function GenerateVertexDataAndTexture(state, glyphCanvas, generateGlyphVerticesForText, COLORS, config, createTextureFromSource) {
|
2 |
const glyphData = generateGlyphVerticesForText('Hello\nworld!\nText in\nWebGPU!', COLORS, config, glyphCanvas);
|
3 |
+
state.webgpu.device.queue.writeBuffer(state.webgpu.vertexBuffer, 0, glyphData.vertexData);
|
4 |
|
5 |
+
state.webgpu.texture = createTextureFromSource(state.webgpu.device, glyphCanvas, { mips: true });
|
6 |
+
state.webgpu.sampler = state.webgpu.device.createSampler({
|
7 |
minFilter: 'linear',
|
8 |
magFilter: 'linear',
|
9 |
});
|
10 |
|
11 |
+
state.webgpu.uniformBuffer = state.webgpu.device.createBuffer({
|
12 |
label: 'uniforms for quad',
|
13 |
size: config.uniformBufferSize,
|
14 |
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
15 |
});
|
16 |
|
17 |
+
state.matrices.matrix = state.matrices.uniformValues.subarray(0, 16);
|
18 |
|
19 |
+
state.webgpu.bindGroup = state.webgpu.device.createBindGroup({
|
20 |
+
layout: state.webgpu.pipeline.getBindGroupLayout(0),
|
21 |
entries: [
|
22 |
+
{ binding: 0, resource: state.webgpu.sampler },
|
23 |
+
{ binding: 1, resource: state.webgpu.texture.createView() },
|
24 |
+
{ binding: 2, resource: { buffer: state.webgpu.uniformBuffer } },
|
25 |
],
|
26 |
});
|
27 |
|
28 |
// Update state with glyph details
|
29 |
+
state.glyphs.numGlyphs = glyphData.numGlyphs;
|
30 |
+
state.glyphs.width = glyphData.width;
|
31 |
+
state.glyphs.height = glyphData.height;
|
32 |
}
|