From 026a006b410a0132c2cb573edff4352b4333b857 Mon Sep 17 00:00:00 2001 From: James Barnett Date: Mon, 3 Jan 2022 17:02:22 +0000 Subject: Performance optimisations --- src/Framebuffer.ts | 10 ++++------ src/RaytraceContext.ts | 2 ++ src/RaytraceDispatcher.ts | 32 ++++++++++++++++++++++++-------- src/Raytracer.ts | 25 +++++++++++++++++-------- src/index.ts | 29 +++++++++++++++++++++++++++-- 5 files changed, 74 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/Framebuffer.ts b/src/Framebuffer.ts index b9926d2..a6ad5fc 100644 --- a/src/Framebuffer.ts +++ b/src/Framebuffer.ts @@ -1,5 +1,3 @@ -import {Colour} from './Colour'; - export class Framebuffer { readonly width: number; readonly height: number; @@ -24,11 +22,11 @@ export class Framebuffer { ); } - writePixelAt(x: number, y: number, colour: Colour) { + writePixelAt(x: number, y: number, r: number, g: number, b: number) { const startIdx = (y * this.width + x) * 4; - this.canvasImageData.data[startIdx] = colour.r; - this.canvasImageData.data[startIdx + 1] = colour.g; - this.canvasImageData.data[startIdx + 2] = colour.b; + this.canvasImageData.data[startIdx] = r; + this.canvasImageData.data[startIdx + 1] = g; + this.canvasImageData.data[startIdx + 2] = b; this.canvasImageData.data[startIdx + 3] = 255; // No A } diff --git a/src/RaytraceContext.ts b/src/RaytraceContext.ts index 3629f5a..34f20dd 100644 --- a/src/RaytraceContext.ts +++ b/src/RaytraceContext.ts @@ -32,4 +32,6 @@ export interface RaytracerOptions { reflections: boolean; maxRecurseDepth: number; maxDrawDistance: number; + bufferDrawCalls: boolean; + directMemoryTransfer: boolean; } diff --git a/src/RaytraceDispatcher.ts b/src/RaytraceDispatcher.ts index 1ce412f..be36bb1 100644 --- a/src/RaytraceDispatcher.ts +++ b/src/RaytraceDispatcher.ts @@ -1,4 +1,3 @@ -import {Colour} from './Colour'; import {Framebuffer} from './Framebuffer'; import {RaytraceContext} from './RaytraceContext'; import {instanceToPlain} from 'class-transformer'; @@ -7,7 +6,8 @@ import {Logger} from './Logger'; export class RaytraceDispatcher { private renderStartMs: number; - private responsesReceived = 0; + private completedThreads = 0; + private processedResponses = 0; constructor( readonly framebuffer: Framebuffer, readonly context: RaytraceContext, @@ -18,7 +18,7 @@ export class RaytraceDispatcher { } requestRender() { - // Assumes height and threads are always even + // Assumes height%threads = 0 const rowBatchSize = this.context.height / this.context.options.numThreads; for (let y = 0; y < this.context.height; y += rowBatchSize) { @@ -55,7 +55,8 @@ export class RaytraceDispatcher { } private handleWorkerComplete() { - if (++this.responsesReceived === this.context.options.numThreads) { + if (++this.completedThreads === this.context.options.numThreads) { + this.framebuffer.flush(); this.logger.log( `Raytrace completed in ${new Date().getTime() - this.renderStartMs}ms` ); @@ -63,10 +64,25 @@ export class RaytraceDispatcher { } } - private processResponse(rowIndex: number, rowData: Colour[]) { - for (let x = 0; x < this.framebuffer.width; x++) { - this.framebuffer.writePixelAt(x, rowIndex, rowData[x]); + private processResponse(rowIndex: number, rowData: ArrayBuffer) { + const clampedRowData = new Uint8ClampedArray(rowData); + + for (let x = 0; x < this.context.width; x++) { + const idx = x * 3; + const r = clampedRowData[idx]; + const g = clampedRowData[idx + 1]; + const b = clampedRowData[idx + 2]; + this.framebuffer.writePixelAt(x, rowIndex, r, g, b); + } + + if ( + this.context.options.bufferDrawCalls && + ++this.processedResponses >= this.context.height * 0.05 + ) { + this.framebuffer.flush(); + this.processedResponses = 0; + } else if (!this.context.options.bufferDrawCalls) { + this.framebuffer.flush(); } - this.framebuffer.flush(); } } diff --git a/src/Raytracer.ts b/src/Raytracer.ts index 68d5138..673977b 100644 --- a/src/Raytracer.ts +++ b/src/Raytracer.ts @@ -49,7 +49,7 @@ class Raytracer { process() { for (let y = this.rowStartIndex; y <= this.rowEndIndex; y++) { - const rowResultBuffer: Colour[] = []; + const resultBuffer = new Uint8ClampedArray(3 * this.context.width); for (let x = 0; x < this.context.width; x++) { const rayX = x + 0.5 - this.context.width / 2; @@ -61,14 +61,24 @@ class Raytracer { const pixelValue = this.raytrace(ray); - rowResultBuffer.push(pixelValue); + const buffIdx = x * 3; + resultBuffer[buffIdx] = pixelValue.r; + resultBuffer[buffIdx + 1] = pixelValue.g; + resultBuffer[buffIdx + 2] = pixelValue.b; } - self.postMessage({ - type: 'raytraceResultRow', - resultBuffer: rowResultBuffer, - rowIndex: y, - }); + if (this.context.options.directMemoryTransfer) { + // prettier-ignore + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + self.postMessage({type: 'raytraceResultRow', rowIndex: y, resultBuffer: resultBuffer.buffer}, [resultBuffer.buffer]); + } else { + self.postMessage({ + type: 'raytraceResultRow', + resultBuffer: resultBuffer.buffer, + rowIndex: y, + }); + } } self.postMessage({type: 'raytraceComplete'}); @@ -205,7 +215,6 @@ class Raytracer { incidentAngle: Vector, surfaceNormal: Vector ): Vector { - // I - N*2.f*(I*N); // v2 = v1 – 2(v1.n)n https://bocilmania.com/2018/04/21/how-to-get-reflection-vector/ return incidentAngle.subtract( surfaceNormal diff --git a/src/index.ts b/src/index.ts index 3736c92..6b86f57 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,8 +15,8 @@ function render() { } function initDispatcher(options: RaytracerOptions): RaytraceDispatcher { - const width = 960; - const height = 720; + const {width, height} = parseResolution(); + const fov = Math.PI / 3; const framebuffer = new Framebuffer(width, height); @@ -68,12 +68,37 @@ function parseOptions(): RaytracerOptions { reflections: getInputElement('reflections-toggle').checked, maxRecurseDepth: 5, maxDrawDistance: 1000, + bufferDrawCalls: getInputElement('buffer-draw').checked, + directMemoryTransfer: getInputElement('direct-transfer').checked, }; } +function parseResolution(): {width: number; height: number} { + switch (getInputElement('res').value) { + case '720p': + default: + return {width: 960, height: 720}; + case '1080p': + return {width: 1440, height: 1080}; + case '1440p': + return {width: 1920, height: 1440}; + case '4k': + return {width: 2880, height: 2160}; + case '8k': + return {width: 5760, height: 4320}; + } +} + function registerEventListeners() { getRenderButton().addEventListener('click', render); + document.getElementById('view-full')!.addEventListener('click', () => { + const canvas = document.getElementById( + 'render-output' + ) as HTMLCanvasElement; + window.open()!.document.body.innerHTML = ``; + }); + const threadsSlider = getInputElement('threads'); threadsSlider.addEventListener('input', () => { getInputElement('threads-value').textContent = threadsSlider.value; -- cgit v1.2.3