Mandelbrot Set 2019-05-12 This example is rendered with eight WebWorkers, and uses ArrayBuffers with transferred ownership to pass data from the worker back to the main script. mandelbrot_set.ts interface WorkerJob { width: number; height: number; y: number; } interface WorkerResponse { y: number; row: ArrayBuffer; } const TASK_COUNT = 8 function createCanvas() { const canvas = document.createElement('canvas'); canvas.id = "mandelbrot_canvas"; canvas.setAttribute("style", "width:100%;") canvas.style.border = "1px solid #ccc"; const root = document.getElementById("mandelbrot"); root.appendChild(canvas); canvas.style.height = Math.floor(canvas.offsetWidth) + 'px'; canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetWidth; return canvas; } function showMessage(msg: string) { let hello = document.createElement('div'); let textNode = document.createTextNode(msg) hello.appendChild(textNode) let entry = document.getElementById("mandelbrot") entry.appendChild(hello) } function spawnWorkers(mandelbrot_canvas: HTMLCanvasElement) { let startTime: number let endTime: number function start() { startTime = new Date().getTime(); }; function end(width: number, height: number) { endTime = new Date().getTime(); let timeDiff = endTime - startTime; // get milliseconds let milliseconds = Math.round(timeDiff); showMessage(width + " x " + height + " " + milliseconds + " ms") } start() const ctx = mandelbrot_canvas.getContext("2d"); const width = mandelbrot_canvas.width * 4 const height = mandelbrot_canvas.height * 4 let remaining = height mandelbrot_canvas.width = width mandelbrot_canvas.height = height mandelbrot_canvas.style.width = (width / 4) + 'px'; mandelbrot_canvas.style.height = (height / 4) + 'px'; let tasks: Array<Worker> = [] for (let count = 0; count < TASK_COUNT; count++) { const worker = new Worker("/assets/js/mandelbrot_worker_renderer.bundle.js"); worker.onmessage = function(msg: MessageEvent) { const image = ctx.getImageData(0, 0, mandelbrot_canvas.width * 2, 1); const data = image.data const messageData: WorkerResponse = msg.data const y = messageData["y"] const rowBuffer = messageData["row"] const row = new Uint32Array(rowBuffer) for (let x = 0; x < width; x ++) { const offset = (x * 4); const xx = (x / width) * 4.0 - 2.0 - 0.5; let gray = 0; if (row[x] % 2 == 0) { gray = 255; } data[offset + 0] = gray; //red data[offset + 1] = gray; //green data[offset + 2] = gray; //blue data[offset + 3] = 255; //alpha } ctx.putImageData(image, 0, y); remaining -= 1 if (remaining == 0) { end(width, height) } } tasks.push(worker) } let task_counter = 0 for (let y = 0; y < height; y++) { const msg: WorkerJob = { width: width, height: height, y: y } let worker = tasks[task_counter] worker.postMessage(msg) task_counter += 1 if (task_counter >= tasks.length) { task_counter = 0 } } } const mandelbrot_canvas = createCanvas() spawnWorkers(mandelbrot_canvas) mandelbrot_worker_renderer.ts declare function postMessage(message: any, transfer?: Transferable[]):void let Mandelbrot = { ITERATIONS: 1024, BAILOUT: 16, iterate: function (xx: number, yy: number) { const iterations = this.ITERATIONS const bailout = this.BAILOUT const cr = xx const ci = yy let zi = 0.0 let zr = 0.0 let i = 0 while (i++ < iterations) { const temp = zr * zi const zr2 = zr * zr const zi2 = zi * zi zr = zr2 - zi2 + cr zi = 2.0 * temp + ci if (zi2 + zr2 > bailout) { return i } } return i }, } function render(messageData: WorkerJob) { const width = messageData["width"] const height = messageData["height"] const y = messageData["y"] const arrayBuffer = new ArrayBuffer(width * 32) const rowIterations = new Uint32Array(arrayBuffer) const yy = ((y / height) * 4.0 - 2.0) for (let x = 0; x < width; x++) { const xx = (x / width) * 4.0 - 2.0 - 0.5 rowIterations[x] = Mandelbrot.iterate(xx, yy) } const result: WorkerResponse = { y: y, row: arrayBuffer } postMessage(result, [result.row]) } self.onmessage = function (msg: MessageEvent) { const messageData:WorkerJob = msg.data render(messageData) } mandelbrot javascript typescript