optimize drawing with webgl

still has color selection issue on windows
post/wasm-gol-2
Ashelyn Dawn 1 year ago
parent b9a7ca4aef
commit 6708c73571

@ -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 if (!script) return null
return ( return (
@ -68,7 +89,7 @@ export default async function ScriptServer({ src, wasm, ...rest }: Props) {
{...(wasmCompiled ? { {...(wasmCompiled ? {
wasmSrc: wasmCompiled.path wasmSrc: wasmCompiled.path
} : {})} } : {})}
{...rest} {...processedArgs}
/> />
) )
} }

@ -4,6 +4,8 @@ subtitle: You know I couldn't leave it alone
resources: resources:
- wasm-life-2/controller.js - wasm-life-2/controller.js
- wasm-life-2/game.wat - 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 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
<ScriptLoader <ScriptLoader
src="/wasm-life-2/controller.js" src="/wasm-life-2/controller.js"
wasm="/wasm-life-2/game.wat" wasm="/wasm-life-2/game.wat"
vertexSrc="/wasm-life-2/vertex.glsl"
fragmentSrc="/wasm-life-2/fragment.glsl"
canvas="#game" canvas="#game"
/> />

@ -19,7 +19,7 @@ export async function setup(params, wasmModule) {
const canvas = gameState.canvas = document.querySelector(canvasSelector) const canvas = gameState.canvas = document.querySelector(canvasSelector)
gameState.ctx = gameState.canvas.getContext("2d") 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') const pixelSize = gameState.pixelSize = parseInt(canvas.getAttribute('data-pixelsize') || '2')
gameState.width = Math.floor(parseInt(canvas.width) / pixelSize) gameState.width = Math.floor(parseInt(canvas.width) / pixelSize)
@ -58,8 +58,8 @@ export async function setup(params, wasmModule) {
} }
export async function onThemeChange() { export async function onThemeChange() {
gameState.ctx.fillStyle = getComputedStyle(gameState.canvas).getPropertyValue('color')
drawBoard() drawBoard()
gameState.ctx.fillStyle = getComputedStyle(gameState.canvas).getPropertyValue('--foreground')
} }
export async function cleanup() { export async function cleanup() {

@ -1,31 +1,54 @@
let gameState = { let gameState = {
running: false, running: false,
pixelSize: 0,
lastReported: null, lastReported: null,
frameTimes: [], frameTimes: [],
frames: 0, frames: 0,
width: 0, width: 0,
height: 0, height: 0,
canvas: null, canvas: null,
ctx: null, gl: null,
gameExports: null shader: null,
gameExports: null,
} }
const initialMessage = "Click the board above to start simulation" const initialMessage = "Click the board above to start simulation"
export async function setup(params, wasmModule) { export async function setup(params, wasmModule) {
const { canvas: canvasSelector } = params; const { canvas: canvasSelector, fragmentSrc, vertexSrc } = params;
gameState.gameExports = wasmModule.exports gameState.gameExports = wasmModule.exports
const canvas = gameState.canvas = document.querySelector(canvasSelector) const canvas = gameState.canvas = document.querySelector(canvasSelector)
gameState.ctx = gameState.canvas.getContext("2d") const gl = gameState.gl = gameState.canvas.getContext("webgl2")
gameState.ctx.fillStyle = getComputedStyle(gameState.canvas).getPropertyValue('--foreground')
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.width = Math.floor(parseInt(canvas.width) / pixelSize)
gameState.height = Math.floor(parseInt(canvas.height) / pixelSize) gameState.height = Math.floor(parseInt(canvas.height) / pixelSize)
initialize() initialize()
onThemeChange()
drawBoard() drawBoard()
const frameTimesElem = document.getElementById('frameTimes') const frameTimesElem = document.getElementById('frameTimes')
@ -58,8 +81,16 @@ export async function setup(params, wasmModule) {
} }
export async function onThemeChange() { 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() drawBoard()
gameState.ctx.fillStyle = getComputedStyle(gameState.canvas).getPropertyValue('--foreground')
} }
export async function cleanup() { export async function cleanup() {
@ -122,25 +153,26 @@ function frameLoop() {
} }
function drawBoard() { function drawBoard() {
const { gameExports, width, height, pixelSize, ctx, canvas } = gameState 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)
ctx.clearRect(0, 0, canvas.width, canvas.height) const texture = gl.createTexture()
ctx.beginPath() gl.activeTexture(gl.TEXTURE0)
for (let row = 0; row < height; row++) { gl.bindTexture(gl.TEXTURE_2D, texture)
for (let column = 0; column < width; column++) { gl.pixelStorei( gl.UNPACK_ALIGNMENT, 1 )
const alive = gameExports.getValueAtPosition(row, column) gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, width, height, 0, gl.ALPHA, gl.UNSIGNED_BYTE, boardData)
if (!alive) continue 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);
const x = column * pixelSize
const y = row * pixelSize
ctx.moveTo(x, y)
ctx.lineTo(x + pixelSize, y) gl.clearColor(1, 1, 1, 0)
ctx.lineTo(x + pixelSize, y + pixelSize) gl.clear(gl.COLOR_BUFFER_BIT)
ctx.lineTo(x, y + pixelSize) gl.drawArrays(gl.TRIANGLES, 0, 6)
ctx.lineTo(x, y)
}
}
ctx.fill()
} }

@ -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);
}

@ -59,12 +59,16 @@
;; perhaps we should have a way to report errors back to JS next time ;; 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 $currentBuffer
global.get $boardBufferLength global.get $boardBufferLength
i32.mul i32.mul
) )
(func (export "getBoardLength") (result i32)
global.get $boardBufferLength
)
(func $swapBoards (func $swapBoards
global.get $currentBuffer global.get $currentBuffer
i32.eqz i32.eqz

@ -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);
}
Loading…
Cancel
Save