From 6708c735717796cb881637ca1fa327587ae1c78b Mon Sep 17 00:00:00 2001 From: Ashelyn Rose Date: Sat, 2 Sep 2023 04:01:08 -0600 Subject: [PATCH] optimize drawing with webgl still has color selection issue on windows --- components/ScriptLoaderServer.tsx | 23 ++++++- posts/2023-08-25_wasm-game-of-life-2.md | 4 ++ scripts/wasm-life-1/controller.js | 4 +- scripts/wasm-life-2/controller.js | 90 +++++++++++++++++-------- scripts/wasm-life-2/fragment.glsl | 10 +++ scripts/wasm-life-2/game.wat | 6 +- scripts/wasm-life-2/vertex.glsl | 8 +++ 7 files changed, 112 insertions(+), 33 deletions(-) create mode 100644 scripts/wasm-life-2/fragment.glsl create mode 100644 scripts/wasm-life-2/vertex.glsl diff --git a/components/ScriptLoaderServer.tsx b/components/ScriptLoaderServer.tsx index 15110b1..4dd5273 100644 --- a/components/ScriptLoaderServer.tsx +++ b/components/ScriptLoaderServer.tsx @@ -60,6 +60,27 @@ export default async function ScriptServer({ src, wasm, ...rest }: Props) { } } + let processedArgs = {} + for (const argName in rest) { + const argValue = rest[argName] + + try { + const argPath = path.join(process.cwd(), 'scripts', argValue) + const argContents = await fs.readFile(argPath) + const argFileName = path.basename(argValue) + const argDest = path.join(process.cwd(), '.next/static/scripts', argValue) + const argDir = path.dirname(argDest) + await fs.mkdir(argDir, { recursive: true }) + await fs.writeFile(argDest, argContents) + processedArgs[argName] = { + name: argFileName, + path: path.join('/_next/static/scripts', argValue) + } + } catch { + processedArgs[argName] = argValue + } + } + if (!script) return null return ( @@ -68,7 +89,7 @@ export default async function ScriptServer({ src, wasm, ...rest }: Props) { {...(wasmCompiled ? { wasmSrc: wasmCompiled.path } : {})} - {...rest} + {...processedArgs} /> ) } diff --git a/posts/2023-08-25_wasm-game-of-life-2.md b/posts/2023-08-25_wasm-game-of-life-2.md index 7ef9f8b..94c57f8 100644 --- a/posts/2023-08-25_wasm-game-of-life-2.md +++ b/posts/2023-08-25_wasm-game-of-life-2.md @@ -4,6 +4,8 @@ subtitle: You know I couldn't leave it alone resources: - wasm-life-2/controller.js - wasm-life-2/game.wat +- wasm-life-2/vertex.glsl +- wasm-life-2/fragment.glsl --- This post is part 2 of my Webassembly Game of Life series, read part 1 @@ -24,6 +26,8 @@ This post is part 2 of my Webassembly Game of Life series, read part 1 diff --git a/scripts/wasm-life-1/controller.js b/scripts/wasm-life-1/controller.js index 5c97bb5..41bfe59 100644 --- a/scripts/wasm-life-1/controller.js +++ b/scripts/wasm-life-1/controller.js @@ -19,7 +19,7 @@ export async function setup(params, wasmModule) { const canvas = gameState.canvas = document.querySelector(canvasSelector) gameState.ctx = gameState.canvas.getContext("2d") - gameState.ctx.fillStyle = getComputedStyle(gameState.canvas).getPropertyValue('--foreground') + gameState.ctx.fillStyle = getComputedStyle(gameState.canvas).getPropertyValue('color') const pixelSize = gameState.pixelSize = parseInt(canvas.getAttribute('data-pixelsize') || '2') gameState.width = Math.floor(parseInt(canvas.width) / pixelSize) @@ -58,8 +58,8 @@ export async function setup(params, wasmModule) { } export async function onThemeChange() { + gameState.ctx.fillStyle = getComputedStyle(gameState.canvas).getPropertyValue('color') drawBoard() - gameState.ctx.fillStyle = getComputedStyle(gameState.canvas).getPropertyValue('--foreground') } export async function cleanup() { diff --git a/scripts/wasm-life-2/controller.js b/scripts/wasm-life-2/controller.js index 7c310c2..09b2993 100644 --- a/scripts/wasm-life-2/controller.js +++ b/scripts/wasm-life-2/controller.js @@ -1,31 +1,54 @@ let gameState = { running: false, - pixelSize: 0, lastReported: null, frameTimes: [], frames: 0, width: 0, height: 0, canvas: null, - ctx: null, - gameExports: null + gl: null, + shader: null, + gameExports: null, } const initialMessage = "Click the board above to start simulation" export async function setup(params, wasmModule) { - const { canvas: canvasSelector } = params; + const { canvas: canvasSelector, fragmentSrc, vertexSrc } = params; gameState.gameExports = wasmModule.exports const canvas = gameState.canvas = document.querySelector(canvasSelector) - gameState.ctx = gameState.canvas.getContext("2d") - gameState.ctx.fillStyle = getComputedStyle(gameState.canvas).getPropertyValue('--foreground') + const gl = gameState.gl = gameState.canvas.getContext("webgl2") - const pixelSize = gameState.pixelSize = parseInt(canvas.getAttribute('data-pixelsize') || '2') + const vertexShader = gl.createShader(gl.VERTEX_SHADER) + gl.shaderSource(vertexShader, await fetch(vertexSrc.path).then(res => res.text())) + gl.compileShader(vertexShader) + + const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER) + gl.shaderSource(fragmentShader, await fetch(fragmentSrc.path).then(res => res.text())) + gl.compileShader(fragmentShader) + + const program = gameState.shader = gl.createProgram() + gl.attachShader(program, vertexShader) + gl.attachShader(program, fragmentShader) + gl.linkProgram(program) + gl.useProgram(program) + + const renderQuad = new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]) + const vertexBuffer = gl.createBuffer() + gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer) + gl.bufferData(gl.ARRAY_BUFFER, renderQuad, gl.STATIC_DRAW) + + const positionLocation = gl.getAttribLocation(program, "position") + gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0) + gl.enableVertexAttribArray(positionLocation); + + const pixelSize = parseInt(canvas.getAttribute('data-pixelsize') || '2') gameState.width = Math.floor(parseInt(canvas.width) / pixelSize) gameState.height = Math.floor(parseInt(canvas.height) / pixelSize) initialize() + onThemeChange() drawBoard() const frameTimesElem = document.getElementById('frameTimes') @@ -58,8 +81,16 @@ export async function setup(params, wasmModule) { } export async function onThemeChange() { + const {gl, shader} = gameState + + const drawColorString = getComputedStyle(gameState.canvas).getPropertyValue('color') + const [_match, ...colorValues] = /rgb\(([0-9]+), ([0-9]+), ([0-9]+)\)/.exec(drawColorString) + const drawColor = colorValues.map(n => parseFloat(n) / 255) + + const drawColorLocation = gl.getUniformLocation(shader, "drawColor") + gl.uniform3fv(drawColorLocation, drawColor) + drawBoard() - gameState.ctx.fillStyle = getComputedStyle(gameState.canvas).getPropertyValue('--foreground') } export async function cleanup() { @@ -122,25 +153,26 @@ function frameLoop() { } function drawBoard() { - const { gameExports, width, height, pixelSize, ctx, canvas } = gameState - - ctx.clearRect(0, 0, canvas.width, canvas.height) - ctx.beginPath() - for (let row = 0; row < height; row++) { - for (let column = 0; column < width; column++) { - const alive = gameExports.getValueAtPosition(row, column) - - if (!alive) continue - - const x = column * pixelSize - const y = row * pixelSize - - ctx.moveTo(x, y) - ctx.lineTo(x + pixelSize, y) - ctx.lineTo(x + pixelSize, y + pixelSize) - ctx.lineTo(x, y + pixelSize) - ctx.lineTo(x, y) - } - } - ctx.fill() + const { gameExports, width, height, gl } = gameState + const wasmBuffer = gameExports.shared_memory.buffer + const boardPointer = gameExports.getBoardPointer() + const boardLength = gameExports.getBoardLength() + const boardData = (new Uint8Array(wasmBuffer)).slice(boardPointer, boardPointer + boardLength) + + const texture = gl.createTexture() + gl.activeTexture(gl.TEXTURE0) + gl.bindTexture(gl.TEXTURE_2D, texture) + gl.pixelStorei( gl.UNPACK_ALIGNMENT, 1 ) + gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, width, height, 0, gl.ALPHA, gl.UNSIGNED_BYTE, boardData) + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + + + + gl.clearColor(1, 1, 1, 0) + gl.clear(gl.COLOR_BUFFER_BIT) + gl.drawArrays(gl.TRIANGLES, 0, 6) } diff --git a/scripts/wasm-life-2/fragment.glsl b/scripts/wasm-life-2/fragment.glsl new file mode 100644 index 0000000..d61f11b --- /dev/null +++ b/scripts/wasm-life-2/fragment.glsl @@ -0,0 +1,10 @@ +precision highp float; +varying vec2 texCoords; +uniform sampler2D textureSampler; +uniform vec3 drawColor; + +void main() { + vec4 textureColor = texture2D(textureSampler, texCoords); + + gl_FragColor = vec4(drawColor, textureColor.a * 255.0); +} diff --git a/scripts/wasm-life-2/game.wat b/scripts/wasm-life-2/game.wat index 7c5f003..c0a8813 100644 --- a/scripts/wasm-life-2/game.wat +++ b/scripts/wasm-life-2/game.wat @@ -59,12 +59,16 @@ ;; perhaps we should have a way to report errors back to JS next time ) - (func $getBoardPtr (result i32) + (func $getBoardPtr (export "getBoardPointer") (result i32) global.get $currentBuffer global.get $boardBufferLength i32.mul ) + (func (export "getBoardLength") (result i32) + global.get $boardBufferLength + ) + (func $swapBoards global.get $currentBuffer i32.eqz diff --git a/scripts/wasm-life-2/vertex.glsl b/scripts/wasm-life-2/vertex.glsl new file mode 100644 index 0000000..494cb15 --- /dev/null +++ b/scripts/wasm-life-2/vertex.glsl @@ -0,0 +1,8 @@ +attribute vec2 position; +varying vec2 texCoords; + +void main() { + texCoords = (position + 1.0) / 2.0; + gl_Position = vec4(position, 0, 1.0); +} +