Spaces:
Running
Running
import { mat4 } from 'https://webgpufundamentals.org/3rdparty/wgpu-matrix.module.js'; | |
import { initializeWebGPU } from './wgpu-device.js'; | |
import { createState } from './wgpu-state.js'; | |
import { generateGlyphTextureAtlas, createTextureFromSource } from './wgpu-utility.js'; | |
import { createPipeline } from './wgpu-pipeline.js'; | |
import { fetchShaderCode } from './wgpu-shader.js'; | |
import { GenerateVertexDataAndTexture } from './wgpu-texture.js'; | |
import { generateGlyphVerticesForText } from './wgpu-text.js'; | |
import { config } from './wgpu-config.js'; | |
import { CANVAS, CTX, COLORS, RENDER_PASS_DESCRIPTOR } from './wgpu-constants.js'; | |
// Canvas element for rendering | |
const canvas = document.querySelector('canvas'); | |
// State initialization | |
const state = createState(config); | |
const FIXED_DELTA_TIME = 1 / 60; // Fixed time step of 60 FPS for physics and game logic | |
const MAX_FRAME_TIME = 0.25; // Maximum time to simulate per frame to prevent spiral of death | |
const TARGET_FPS = 60; // Target frames per second | |
const FRAME_DURATION = 1000 / TARGET_FPS; // Duration of a single frame in milliseconds | |
async function Main() { | |
const adapter = await navigator.gpu?.requestAdapter(); | |
const { device, context, presentationFormat } = await initializeWebGPU(navigator, adapter, canvas); | |
if (!device) return; | |
state.device = device; | |
// Initialize Resources | |
await InitializeResources(presentationFormat); | |
// Start the game loop | |
GameLoop(context); | |
} | |
// Initialize shaders, pipeline, textures, and buffers | |
async function InitializeResources(presentationFormat) { | |
// Load shader code | |
const shaderCode = await fetchShaderCode('shaders.wgsl'); | |
const vertexSize = config.floatsPerVertex * 4; | |
// Create rendering pipeline | |
state.pipeline = await createPipeline(state.device, presentationFormat, vertexSize, shaderCode); | |
// Generate glyph texture atlas | |
const glyphCanvas = generateGlyphTextureAtlas(CANVAS, CTX, config); | |
document.body.appendChild(glyphCanvas); | |
glyphCanvas.style.backgroundColor = '#222'; | |
// Create vertex and index buffers | |
CreateBuffers(); | |
// Generate vertex buffer data and texture | |
GenerateVertexDataAndTexture(state, glyphCanvas, generateGlyphVerticesForText, COLORS, config, createTextureFromSource); | |
} | |
// Function to create vertex and index buffers | |
function CreateBuffers() { | |
const vertexBufferSize = config.maxGlyphs * config.vertsPerGlyph * config.floatsPerVertex * 4; | |
state.vertexBuffer = state.device.createBuffer({ | |
label: 'vertices', | |
size: vertexBufferSize, | |
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, | |
}); | |
state.indexBuffer = state.device.createBuffer({ | |
label: 'indices', | |
size: config.maxGlyphs * config.vertsPerGlyph * 4, | |
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST, | |
}); | |
const indices = GenerateIndices(config.maxGlyphs); | |
state.device.queue.writeBuffer(state.indexBuffer, 0, new Uint32Array(indices)); | |
} | |
// Function to generate indices for glyphs | |
function GenerateIndices(maxGlyphs) { | |
// Generate index array for glyphs | |
return Array.from({ length: maxGlyphs * 6 }, (_, i) => { | |
const ndx = Math.floor(i / 6) * 4; | |
return (i % 6 < 3 ? [ndx, ndx + 1, ndx + 2] : [ndx + 2, ndx + 1, ndx + 3])[i % 3]; | |
}); | |
} | |
// Game loop function | |
function GameLoop(context) { | |
let lastTime = performance.now(); | |
let accumulator = 0; | |
function Tick() { | |
const currentTime = performance.now(); | |
const frameTime = (currentTime - lastTime) / 1000; | |
lastTime = currentTime; | |
const deltaTime = Math.min(frameTime, MAX_FRAME_TIME); | |
accumulator += deltaTime; | |
// Fixed time step updates for game logic | |
while (accumulator >= FIXED_DELTA_TIME) { | |
FixedUpdate(FIXED_DELTA_TIME); | |
accumulator -= FIXED_DELTA_TIME; | |
} | |
// Variable time step update for rendering | |
const alpha = accumulator / FIXED_DELTA_TIME; | |
Render(alpha, context); | |
// Schedule the next frame update | |
setTimeout(Tick, FRAME_DURATION); | |
} | |
Tick(); | |
} | |
// Fixed update function for game logic | |
function FixedUpdate(deltaTime) { | |
state.time += deltaTime; | |
// Perform game logic updates here, such as physics and AI | |
} | |
// Render function | |
function Render(alpha, context) { | |
// Set up projection and view matrices | |
const fov = 60 * Math.PI / 180; | |
const aspect = canvas.clientWidth / canvas.clientHeight; | |
const projectionMatrix = mat4.perspective(fov, aspect, config.render.zNear, config.render.zFar); | |
const viewMatrix = mat4.lookAt([0, 0, 5], [0, 0, 0], [0, 1, 0]); | |
const viewProjectionMatrix = mat4.multiply(projectionMatrix, viewMatrix); | |
RENDER_PASS_DESCRIPTOR.colorAttachments[0].view = context.getCurrentTexture().createView(); | |
const encoder = state.device.createCommandEncoder(); | |
const pass = encoder.beginRenderPass(RENDER_PASS_DESCRIPTOR); | |
pass.setPipeline(state.pipeline); | |
mat4.rotateY(viewProjectionMatrix, state.time, state.matrix); | |
mat4.translate(state.matrix, [-state.width / 2, -state.height / 2, 0], state.matrix); | |
state.device.queue.writeBuffer(state.uniformBuffer, 0, state.uniformValues); | |
pass.setBindGroup(0, state.bindGroup); | |
pass.setVertexBuffer(0, state.vertexBuffer); | |
pass.setIndexBuffer(state.indexBuffer, 'uint32'); | |
pass.drawIndexed(state.numGlyphs * 6); | |
pass.end(); | |
state.device.queue.submit([encoder.finish()]); | |
} | |
// Initialize application | |
Main(); | |