diff options
Diffstat (limited to 'src/RaytraceDispatcher.ts')
| -rw-r--r-- | src/RaytraceDispatcher.ts | 154 |
1 files changed, 101 insertions, 53 deletions
diff --git a/src/RaytraceDispatcher.ts b/src/RaytraceDispatcher.ts index be36bb1..6d1bbd7 100644 --- a/src/RaytraceDispatcher.ts +++ b/src/RaytraceDispatcher.ts @@ -1,13 +1,20 @@ import {Framebuffer} from './Framebuffer'; -import {RaytraceContext} from './RaytraceContext'; +import {RaytraceContext} from './models/RaytraceContext'; import {instanceToPlain} from 'class-transformer'; import 'reflect-metadata'; import {Logger} from './Logger'; +import {FrameChunk} from "./models/FrameChunk"; +import {Colour} from "./models/Colour"; export class RaytraceDispatcher { - private renderStartMs: number; - private completedThreads = 0; - private processedResponses = 0; + private readonly renderStartMs: number; + private readonly contextJson: String; + private readonly chunks: FrameChunk[]; + private readonly raytraceWorkers: Worker[]; + private nextChunkIndex = 0; + private completedWorkers = 0; + private chunkBorderWidth = 1; + constructor( readonly framebuffer: Framebuffer, readonly context: RaytraceContext, @@ -15,74 +22,115 @@ export class RaytraceDispatcher { readonly onComplete: Function ) { this.renderStartMs = new Date().getTime(); + this.contextJson = JSON.stringify(instanceToPlain(context)) + this.chunks = []; + this.raytraceWorkers = []; } requestRender() { - // Assumes height%threads = 0 - const rowBatchSize = this.context.height / this.context.options.numThreads; + let chunkHeight, chunkWidth; - for (let y = 0; y < this.context.height; y += rowBatchSize) { - const rowStartIndex = y; - const rowEndIndex = y + rowBatchSize - 1; - this.dispatchRaytraceWorker(rowStartIndex, rowEndIndex, this.context); + if (this.context.height <= 720) { + chunkHeight = 64; + chunkWidth = 64; + this.chunkBorderWidth = 2; + } else { + chunkHeight = 128; + chunkWidth = 128; + this.chunkBorderWidth = 5; } - } - private dispatchRaytraceWorker( - rowStartIndex: number, - rowEndIndex: number, - context: RaytraceContext - ) { - this.logger.log(`Dispatching worker: rows ${rowStartIndex}-${rowEndIndex}`); + // Process scene into chunks + for (let y = 0; y < this.context.height; y+= chunkHeight) { + for (let x = 0 ; x < this.context.width; x+= chunkWidth) { + this.chunks.push(new FrameChunk(x, y, chunkWidth, chunkHeight)); + } + } + this.logger.log(`Scene split into ${this.chunks.length} chunks of ${chunkWidth}x${chunkHeight}`); - const raytracer = new Worker(new URL('./Raytracer.ts', import.meta.url)); + // Spawn worker threads + for (let n = 0; n < this.context.options.numThreads; n++) { + const worker = new Worker(new URL('./Raytracer.ts', import.meta.url)); + worker.onmessage = (event) => { this.onMessageHandler(worker, event) }; + this.raytraceWorkers.push(worker); + } + this.logger.log(`Spawned ${this.context.options.numThreads} render threads`); - raytracer.postMessage({ + // Start raytrace + for (let worker of this.raytraceWorkers) { + this.raytraceNextChunk(worker); + } + } + + private raytraceNextChunk(worker: Worker) { + const chunkIndex = this.nextChunkIndex++; + let chunk = this.chunks[chunkIndex]; + this.drawChunkBorder(chunk, this.chunkBorderWidth); + worker.postMessage({ type: 'raytraceStart', - rowStartIndex: rowStartIndex, - rowEndIndex: rowEndIndex, - context: JSON.stringify(instanceToPlain(context)), + chunk: chunk, + chunkIndex: chunkIndex, + context: this.contextJson, }); - - raytracer.onmessage = ({data}) => { - if (data.type === 'raytraceResultRow') { - this.processResponse(data.rowIndex, data.resultBuffer); - } - if (data.type === 'raytraceComplete') { - this.handleWorkerComplete(); - } - }; } - private handleWorkerComplete() { - if (++this.completedThreads === this.context.options.numThreads) { - this.framebuffer.flush(); - this.logger.log( - `Raytrace completed in ${new Date().getTime() - this.renderStartMs}ms` - ); + private onMessageHandler(worker: Worker, message: MessageEvent) { + if (message.data.type === 'raytraceComplete') { + this.writeChunkToFramebuffer(message.data.chunkIndex, message.data.resultBuffer); + } + + // Queue next work if available + if (this.nextChunkIndex < this.chunks.length) { + this.raytraceNextChunk(worker); + } else { + worker.terminate(); + this.completedWorkers++; + } + + if (this.completedWorkers == this.context.options.numThreads) { this.onComplete(); + this.logger.log(`Raytrace completed in ${new Date().getTime() - this.renderStartMs}ms\n`); } } - private processResponse(rowIndex: number, rowData: ArrayBuffer) { - const clampedRowData = new Uint8ClampedArray(rowData); + private writeChunkToFramebuffer(chunkIndex: number, data: ArrayBuffer) { + const clampedRowData = new Uint8ClampedArray(data); + const chunk = this.chunks[chunkIndex]; + + for (let y = 0; y < chunk.height; y++) { + for (let x = 0; x < chunk.width; x++) { + const idx = (x * 3) + ((chunk.width * 3) * y); + const r = clampedRowData[idx]; + const g = clampedRowData[idx + 1]; + const b = clampedRowData[idx + 2]; + const colour = new Colour(r, g, b); - 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); + this.framebuffer.writePixelAt( (x+chunk.xStart), (y+chunk.yStart), colour); + } } - 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(); + } + + private drawChunkBorder(chunk: FrameChunk, borderWidth: number) { + const width = chunk.width; + const height = chunk.height; + + const borderColour = new Colour(220, 128, 128); + const unrenderedAreaColour = new Colour(160, 160, 160); + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + + if (y < borderWidth || y >= (height-borderWidth) || x < borderWidth || x >= (width-borderWidth)) { + this.framebuffer.writePixelAt(chunk.xStart+x, chunk.yStart+y, borderColour); + } else { + this.framebuffer.writePixelAt(chunk.xStart+x, chunk.yStart+y, unrenderedAreaColour); + } + + } } + + this.framebuffer.flush(); } } |