aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJames Barnett <noreply@jamesbarnett.xyz>2022-01-03 17:02:22 +0000
committerJames Barnett <noreply@jamesbarnett.xyz>2022-01-03 17:02:22 +0000
commit026a006b410a0132c2cb573edff4352b4333b857 (patch)
tree2a641595671a44a2e66619dd6f98dad118d30f0f /src
parentdde729e31a3aa9b5dadd0c0f07b3cb1f497067bb (diff)
downloadjs-raytracer-026a006b410a0132c2cb573edff4352b4333b857.tar.xz
js-raytracer-026a006b410a0132c2cb573edff4352b4333b857.zip
Performance optimisations
Diffstat (limited to 'src')
-rw-r--r--src/Framebuffer.ts10
-rw-r--r--src/RaytraceContext.ts2
-rw-r--r--src/RaytraceDispatcher.ts32
-rw-r--r--src/Raytracer.ts25
-rw-r--r--src/index.ts29
5 files changed, 74 insertions, 24 deletions
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 = `<img src=${canvas.toDataURL()}>`;
+ });
+
const threadsSlider = getInputElement('threads');
threadsSlider.addEventListener('input', () => {
getInputElement('threads-value').textContent = threadsSlider.value;