Skip to content

Instantly share code, notes, and snippets.

@CodyJasonBennett
Created May 28, 2025 07:09
Show Gist options
  • Save CodyJasonBennett/ecefd27980900f8a375dfbfb727642df to your computer and use it in GitHub Desktop.
Save CodyJasonBennett/ecefd27980900f8a375dfbfb727642df to your computer and use it in GitHub Desktop.
WebGL2 OffscreenCanvas in a Worker
function createWorker(fn: Function): Worker {
const blob = new Blob([`(${fn})()`], { type: 'application/javascript' })
const uri = URL.createObjectURL(blob)
return new Worker(uri)
}
const worker = createWorker(() => {
function clientWaitAsync(
gl: WebGL2RenderingContext,
sync: WebGLSync,
flags: GLbitfield,
interval_ms: number,
): Promise<void> {
return new Promise((resolve, reject) => {
function test() {
const res = gl.clientWaitSync(sync, flags, 0)
if (res === gl.WAIT_FAILED) {
reject()
return
}
if (res === gl.TIMEOUT_EXPIRED) {
setTimeout(test, interval_ms)
return
}
resolve()
}
test()
})
}
async function getBufferSubDataAsync(
gl: WebGL2RenderingContext,
target: number,
buffer: WebGLBuffer,
srcByteOffset: number,
dstBuffer: ArrayBufferView,
dstOffset?: number,
length?: number,
): Promise<void> {
const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0)!
gl.flush()
await clientWaitAsync(gl, sync, 0, 10)
gl.deleteSync(sync)
gl.bindBuffer(target, buffer)
gl.getBufferSubData(target, srcByteOffset, dstBuffer, dstOffset, length)
gl.bindBuffer(target, null)
}
const canvas = new OffscreenCanvas(0, 0)
const gl = canvas.getContext('webgl2')!
const vertex = gl.createShader(gl.VERTEX_SHADER)!
gl.shaderSource(
vertex,
/* glsl */ `#version 300 es
out float test;
void main() {
test = 2.0;
}
`,
)
gl.compileShader(vertex)
const fragment = gl.createShader(gl.FRAGMENT_SHADER)!
gl.shaderSource(fragment, '#version 300 es\nout lowp vec4 c;void main(){c=vec4(0);}')
gl.compileShader(fragment)
const program = gl.createProgram()!
gl.attachShader(program, vertex)
gl.attachShader(program, fragment)
gl.transformFeedbackVaryings(program, ['test'], gl.SEPARATE_ATTRIBS)
gl.linkProgram(program)
gl.useProgram(program)
const transformFeedback = gl.createTransformFeedback()
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, transformFeedback)
gl.enable(gl.RASTERIZER_DISCARD)
const buffers: WebGLBuffer[] = []
onmessage = async (event) => {
const array = new Float32Array(event.data as SharedArrayBuffer)
let buffer = buffers.shift()
if (!buffer) {
buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bufferData(gl.ARRAY_BUFFER, array.byteLength, gl.DYNAMIC_READ)
gl.bindBuffer(gl.ARRAY_BUFFER, null)
}
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, buffer)
gl.beginTransformFeedback(gl.POINTS)
gl.drawArrays(gl.POINTS, 0, 1)
gl.endTransformFeedback()
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null)
await getBufferSubDataAsync(gl, gl.ARRAY_BUFFER, buffer, 0, array, 0, 1)
buffers.push(buffer)
postMessage(1)
}
})
const buffer = new SharedArrayBuffer(4)
worker.onmessage = () => console.log(new Float32Array(buffer)[0])
worker.postMessage(buffer)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment