From dbb9203c5553201142a64c0de3e1f885c417766a Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Wed, 11 Dec 2024 22:14:39 -0800 Subject: [PATCH] Speed up initial PoW calculation by caching the canvas and context when worker is loaded. Add a bunch of logging for testing. Reverse pixel checking loop to work backwards from the end which is faster. --- src/lib/workers/pow.ts | 109 ++++++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 50 deletions(-) diff --git a/src/lib/workers/pow.ts b/src/lib/workers/pow.ts index e5534bd..ce89a60 100644 --- a/src/lib/workers/pow.ts +++ b/src/lib/workers/pow.ts @@ -29,8 +29,9 @@ export class Pow { // Both width and height must be multiple of 256, (one byte) // but do not need to be the same, // matching GPU capabilities is the aim - static webglWidth = 256 * 8 - static webglHeight = 256 * 8 + static webglWidth = 256 * 4 + static webglHeight = 256 * 4 + static gl = new OffscreenCanvas(this.webglWidth, this.webglHeight).getContext('webgl2') static hexify (arr: number[] | Uint8Array): string { let out = '' @@ -49,15 +50,10 @@ export class Pow { reverseHex += hashHex.slice(i - 2, i) } - const canvas = new OffscreenCanvas(this.webglWidth, this.webglHeight) - const gl = canvas.getContext('webgl2') - - if (!gl) + if (this.gl == null) throw new Error('webgl2_required') - gl.clearColor(0, 0, 0, 1) - - console.log(`color cleared: ${performance.now()}`) + this.gl.clearColor(0, 0, 0, 1) // Vertext Shader const vsSource = `#version 300 es @@ -186,8 +182,8 @@ export class Pow { void main() { int i; - uint uv_x = uint(uv_pos.x * ${canvas.width - 1}.); - uint uv_y = uint(uv_pos.y * ${canvas.height - 1}.); + uint uv_x = uint(uv_pos.x * ${this.webglWidth - 1}.); + uint uv_y = uint(uv_pos.y * ${this.webglHeight - 1}.); uint x_pos = uv_x % 256u; uint y_pos = uv_y % 256u; uint x_index = (uv_x - x_pos) / 256u; @@ -235,87 +231,100 @@ export class Pow { } }` - const vertexShader = gl.createShader(gl.VERTEX_SHADER) + console.log(`shaders start: ${performance.now()}`) + const vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER) if (vertexShader == null) throw 'error creating vertex shader' - gl.shaderSource(vertexShader, vsSource) - gl.compileShader(vertexShader) + this.gl.shaderSource(vertexShader, vsSource) + this.gl.compileShader(vertexShader) - if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) - throw gl.getShaderInfoLog(vertexShader) + if (!this.gl.getShaderParameter(vertexShader, this.gl.COMPILE_STATUS)) + throw this.gl.getShaderInfoLog(vertexShader) - const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER) + const fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER) if (fragmentShader == null) throw 'error creating fragment shader' - gl.shaderSource(fragmentShader, fsSource) - gl.compileShader(fragmentShader) + this.gl.shaderSource(fragmentShader, fsSource) + this.gl.compileShader(fragmentShader) - if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) - throw gl.getShaderInfoLog(fragmentShader) + if (!this.gl.getShaderParameter(fragmentShader, this.gl.COMPILE_STATUS)) + throw this.gl.getShaderInfoLog(fragmentShader) - const program = gl.createProgram() + const program = this.gl.createProgram() if (program == null) throw 'error creating program' - gl.attachShader(program, vertexShader) - gl.attachShader(program, fragmentShader) - gl.linkProgram(program) + this.gl.attachShader(program, vertexShader) + this.gl.attachShader(program, fragmentShader) + this.gl.linkProgram(program) - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - throw gl.getProgramInfoLog(program) + if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) + throw this.gl.getProgramInfoLog(program) - gl.useProgram(program) + this.gl.useProgram(program) // Construct simple 2D geometry - const triangleArray = gl.createVertexArray() - gl.bindVertexArray(triangleArray) + const triangleArray = this.gl.createVertexArray() + this.gl.bindVertexArray(triangleArray) // Vertex Positions, 2 triangles const positions = new Float32Array([ -1, -1, 0, -1, 1, 0, 1, 1, 0, 1, -1, 0, 1, 1, 0, -1, -1, 0 ]) - const positionBuffer = gl.createBuffer() - gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer) - gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW) - gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0) - gl.enableVertexAttribArray(0) + const positionBuffer = this.gl.createBuffer() + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, positionBuffer) + this.gl.bufferData(this.gl.ARRAY_BUFFER, positions, this.gl.STATIC_DRAW) + this.gl.vertexAttribPointer(0, 3, this.gl.FLOAT, false, 0, 0) + this.gl.enableVertexAttribArray(0) // Texture Positions const uvPosArray = new Float32Array([ 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1 ]) - const uvBuffer = gl.createBuffer() - gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer) - gl.bufferData(gl.ARRAY_BUFFER, uvPosArray, gl.STATIC_DRAW) - gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0) - gl.enableVertexAttribArray(1) + const uvBuffer = this.gl.createBuffer() + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, uvBuffer) + this.gl.bufferData(this.gl.ARRAY_BUFFER, uvPosArray, this.gl.STATIC_DRAW) + this.gl.vertexAttribPointer(1, 2, this.gl.FLOAT, false, 0, 0) + this.gl.enableVertexAttribArray(1) - const work0Location = gl.getUniformLocation(program, 'u_work0') - const work1Location = gl.getUniformLocation(program, 'u_work1') + const work0Location = this.gl.getUniformLocation(program, 'u_work0') + const work1Location = this.gl.getUniformLocation(program, 'u_work1') // Draw output until success or progressCallback says to stop const work0 = new Uint8Array(4) const work1 = new Uint8Array(4) + console.log(`shaders end: ${performance.now()}`) const draw = (): void => { + if (this.gl == null) throw new Error('webgl2_required') + const start = performance.now() + console.log(`crypto start: ${performance.now()}`) crypto.getRandomValues(work0) crypto.getRandomValues(work1) + console.log(`crypto end: ${performance.now()}`) - gl.uniform4uiv(work0Location, work0) - gl.uniform4uiv(work1Location, work1) + console.log(`uiv start: ${performance.now()}`) + this.gl.uniform4uiv(work0Location, work0) + this.gl.uniform4uiv(work1Location, work1) + console.log(`uiv end: ${performance.now()}`) - gl.clear(gl.COLOR_BUFFER_BIT) - gl.drawArrays(gl.TRIANGLES, 0, 6) - const pixels = new Uint8Array(gl.drawingBufferWidth * gl.drawingBufferHeight * 4) + console.log(`clear start: ${performance.now()}`) + this.gl.clear(this.gl.COLOR_BUFFER_BIT) + console.log(`clear end: ${performance.now()}`) + console.log(`draw start: ${performance.now()}`) + this.gl.drawArrays(this.gl.TRIANGLES, 0, 6) + console.log(`draw end: ${performance.now()}`) + const pixels = new Uint8Array(this.gl.drawingBufferWidth * this.gl.drawingBufferHeight * 4) console.log(`start readPixels: ${performance.now()}`) - gl.readPixels(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels) + this.gl.readPixels(0, 0, this.gl.drawingBufferWidth, this.gl.drawingBufferHeight, this.gl.RGBA, this.gl.UNSIGNED_BYTE, pixels) console.log(`end readPixels: ${performance.now()}`) // Check the pixels for any success - for (let i = 0; i < pixels.length; i += 4) { + for (let i = pixels.length - 4; i >= 0; i -= 4) { if (pixels[i] !== 0) { console.log(`found: ${performance.now()}`) + console.log(`frame time: ${performance.now() - start}`) // Return the work value with the custom bits typeof callback === 'function' && callback(this.hexify(work1) + this.hexify([ @@ -327,11 +336,11 @@ export class Pow { return } } + console.log(`frame time: ${performance.now() - start}`) // Nothing found yet, try again self.requestAnimationFrame(draw) } - console.log(`starting first draw: ${performance.now()}`) // Begin generation self.requestAnimationFrame(draw) } -- 2.34.1