aboutsummaryrefslogtreecommitdiff
path: root/src/RaytraceDispatcher.ts
blob: be36bb1f9c08cbf822e832b64b49c8e813a17d16 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import {Framebuffer} from './Framebuffer';
import {RaytraceContext} from './RaytraceContext';
import {instanceToPlain} from 'class-transformer';
import 'reflect-metadata';
import {Logger} from './Logger';

export class RaytraceDispatcher {
  private renderStartMs: number;
  private completedThreads = 0;
  private processedResponses = 0;
  constructor(
    readonly framebuffer: Framebuffer,
    readonly context: RaytraceContext,
    readonly logger: Logger,
    readonly onComplete: Function
  ) {
    this.renderStartMs = new Date().getTime();
  }

  requestRender() {
    // Assumes height%threads = 0
    const rowBatchSize = this.context.height / this.context.options.numThreads;

    for (let y = 0; y < this.context.height; y += rowBatchSize) {
      const rowStartIndex = y;
      const rowEndIndex = y + rowBatchSize - 1;
      this.dispatchRaytraceWorker(rowStartIndex, rowEndIndex, this.context);
    }
  }

  private dispatchRaytraceWorker(
    rowStartIndex: number,
    rowEndIndex: number,
    context: RaytraceContext
  ) {
    this.logger.log(`Dispatching worker: rows ${rowStartIndex}-${rowEndIndex}`);

    const raytracer = new Worker(new URL('./Raytracer.ts', import.meta.url));

    raytracer.postMessage({
      type: 'raytraceStart',
      rowStartIndex: rowStartIndex,
      rowEndIndex: rowEndIndex,
      context: JSON.stringify(instanceToPlain(context)),
    });

    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`
      );
      this.onComplete();
    }
  }

  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();
    }
  }
}