diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Logger.ts | 7 | ||||
| -rw-r--r-- | src/RaytraceDispatcher.ts | 9 | ||||
| -rw-r--r-- | src/index.ts | 232 | ||||
| -rw-r--r-- | src/models/Scene.ts | 92 | ||||
| -rw-r--r-- | src/ui/benchmarkCharts.ts | 90 | ||||
| -rw-r--r-- | src/ui/benchmarkView.ts | 111 | ||||
| -rw-r--r-- | src/ui/demoView.ts | 141 | ||||
| -rw-r--r-- | src/ui/index.ts | 51 |
8 files changed, 495 insertions, 238 deletions
diff --git a/src/Logger.ts b/src/Logger.ts index 4087e91..5a825d0 100644 --- a/src/Logger.ts +++ b/src/Logger.ts @@ -1,10 +1,9 @@ export class Logger { - constructor() {} + constructor(readonly element: HTMLElement) {} log(message: string) { - const elem = document.getElementById('console')!; - elem.innerText += `${message}\n`; - elem.scrollTop = elem.scrollHeight; + this.element.innerText += `${message}\n`; + this.element.scrollTop = this.element.scrollHeight; console.log(message); } } diff --git a/src/RaytraceDispatcher.ts b/src/RaytraceDispatcher.ts index eeae5ce..12f2502 100644 --- a/src/RaytraceDispatcher.ts +++ b/src/RaytraceDispatcher.ts @@ -90,8 +90,13 @@ export class RaytraceDispatcher { } if (this.completedWorkers == this.context.options.numThreads) { - this.onComplete(); - this.logger.log(`Raytrace completed in ${new Date().getTime() - this.renderStartMs}ms\n`); + const renderTimeMs = (new Date().getTime() - this.renderStartMs); + + this.logger.log(`Raytrace completed in ${renderTimeMs}ms\n`); + + const pixels = this.context.width * this.context.height; + const score = Math.round(pixels/renderTimeMs); + this.onComplete(score); } } diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 3dab80f..0000000 --- a/src/index.ts +++ /dev/null @@ -1,232 +0,0 @@ -import {Colour} from './models/Colour'; -import {Framebuffer} from './Framebuffer'; -import {Plane, Sphere} from './models/Geometry'; -import {Light} from './models/Light'; -import {Albedo, Material} from './models/Material'; -import {RaytraceDispatcher} from './RaytraceDispatcher'; -import {ChunkAllocationMode, RaytraceContext, RaytracerOptions} from './models/RaytraceContext'; -import {Vector} from './models/Vector'; -import {Logger} from './Logger'; - -let dispatcher: RaytraceDispatcher; - -function render() { - getRenderButton().classList.add('loading'); - getStopRenderButton().classList.remove('d-hide'); - getViewFullButton().classList.add('d-hide'); - dispatcher = initDispatcher(parseOptions()); - dispatcher.requestRender(); -} - -function stopRender() { - dispatcher.stopRender(); - onRenderComplete(); -} - -function initDispatcher(options: RaytracerOptions): RaytraceDispatcher { - const {width, height} = parseResolution(); - - const fov = Math.PI / 3; - - const framebuffer = new Framebuffer(width, height); - - const materialMirror = new Material( - new Colour(220, 220, 220), - new Albedo(0.1, 1, 0.8, 0.0), - 2500, - 1 - ); - - const materialGlass = new Material( - new Colour(153, 179, 204), - new Albedo(0.0, 0.5, 0.1, 0.8), - 125, - 1.5 - ); - - const spheres: Sphere[] = [ - new Sphere(1, new Vector(-6.5, -3, -26), matteMaterial(27, 118, 255)), - new Sphere(1, new Vector(-14, -3, -25), matteMaterial(146, 80, 188)), - new Sphere(1, new Vector(-10, -3, -25), matteMaterial(0, 146, 178)), - new Sphere(1, new Vector(-10, -3, -20), matteMaterial(185, 18, 27)), - new Sphere(1, new Vector(-2.5, -3, -20), matteMaterial(115, 45, 217)), - new Sphere(2, new Vector(-10.5, -2, -16), materialMirror), - new Sphere(1, new Vector(-3, -3, -16), matteMaterial(247, 178, 173)), - new Sphere(1, new Vector(-6, -3, -18), matteMaterial(154, 188, 167)), - new Sphere(1, new Vector(-6, -3, -12), matteMaterial(96, 125, 139)), - new Sphere(1, new Vector(-9.5, -3, -12), matteMaterial(122, 186, 242)), - new Sphere(1, new Vector(0, -3, -14), matteMaterial(250, 91, 15)), - new Sphere(1, new Vector(-3, -3, -11), materialGlass), - new Sphere(1, new Vector(-1, -3, -10), matteMaterial(54, 95, 182)), - new Sphere(1, new Vector(-4.5, -3, -8), matteMaterial(139, 195, 74)), - - new Sphere(4, new Vector(4, 0, -18), materialMirror), - new Sphere(1, new Vector(4, -3, -12), matteMaterial(115, 45, 217)), - new Sphere(1.5, new Vector(8.5, -2.5, -10), materialMirror), - new Sphere(1, new Vector(1, -3, -11.5), matteMaterial(255, 200, 0)), - new Sphere(1, new Vector(1.2, -3, -8.2), materialGlass), - new Sphere(1, new Vector(4, -3, -7), matteMaterial(244, 67, 54)), - new Sphere(1, new Vector(5.5, -3, -9.5), matteMaterial(150, 237, 137)), - new Sphere(1, new Vector(6.5, -3, -15), matteMaterial(14, 234, 255)), - new Sphere(1, new Vector(10, -3, -16), matteMaterial(171, 71, 188)), - new Sphere(1, new Vector(11, -3, -20), matteMaterial(190, 189, 191)), - ]; - - const planes = [ - new Plane( - -4, - 50, - 40, - -45, - 1.5, - new Colour(116, 101, 87), - new Colour(92, 78, 70) - ), - ]; - - const lights = [ - new Light(new Vector(30, 50, 40), 2.5), - new Light(new Vector(-20, 50, -25), 0.5), - ]; - - const context = new RaytraceContext( - height, - width, - fov, - spheres, - planes, - lights, - new Colour(221, 221, 221), - options - ); - - return new RaytraceDispatcher( - framebuffer, - context, - new Logger(), - onRenderComplete - ); -} - -function parseOptions(): RaytracerOptions { - return { - numThreads: getDesiredThreadCount(), - shadows: getInputElement('shadows-toggle').checked, - diffuseLighting: getInputElement('diffuse-toggle').checked, - specularLighting: getInputElement('specular-toggle').checked, - reflections: getInputElement('reflections-toggle').checked, - refractions: getInputElement('refractions-toggle').checked, - maxRecurseDepth: 5, - maxDrawDistance: 1000, - directMemoryTransfer: getInputElement('direct-transfer').checked, - chunkSize: parseInt(getInputElement('chunk-size').value, 10), - chunkAllocationMode: getChunkAllocationMode() - }; -} - -function parseResolution(): {width: number; height: number} { - switch (getInputElement('res').value) { - case '360p': - return {width: 640, height: 360}; - case '480p': - return {width: 832, height: 480}; - case '720p': - default: - return {width: 1280, height: 720}; - case '1080p': - return {width: 1920, height: 1080}; - case '1440p': - return {width: 2560, height: 1440}; - case '4k': - return {width: 3840, height: 2160}; - } -} - -function getChunkAllocationMode(): ChunkAllocationMode { - switch (getInputElement('chunk-allocation-mode').value) { - case 'SEQUENTIAL': - default: - return ChunkAllocationMode.SEQUENTIAL; - case 'RANDOM': - return ChunkAllocationMode.RANDOM - case 'CENTER_TO_EDGE': - return ChunkAllocationMode.CENTER_TO_EDGE - case 'EDGE_TO_CENTER': - return ChunkAllocationMode.EDGE_TO_CENTER - } -} - -function registerEventListeners() { - getRenderButton().addEventListener('click', render); - getStopRenderButton().addEventListener('click', stopRender); - - getViewFullButton().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; - }); - - const threadToggle = getInputElement('enable-threads-toggle'); - threadToggle.addEventListener('change', () => { - threadsSlider.disabled = !threadToggle.checked; - }); -} - -function getDesiredThreadCount(): number { - if (getInputElement('enable-threads-toggle').checked) { - return Number.parseInt(getInputElement('threads').value); - } else { - return 1; - } -} - -function onRenderComplete() { - getRenderButton().classList.remove('loading'); - getStopRenderButton().classList.add('d-hide'); - getViewFullButton().classList.remove('d-hide'); -} - -function getRenderButton(): HTMLElement { - return document.getElementById('render')!; -} - -function getStopRenderButton(): HTMLElement { - return document.getElementById('stop-render')!; -} - -function getViewFullButton(): HTMLElement { - return document.getElementById('view-full')!; -} - -function getInputElement(elementId: string) { - return document.getElementById(elementId) as HTMLInputElement; -} - -function matteMaterial(r: number, g: number, b: number): Material { - return new Material( - new Colour(r, g, b), - new Albedo(0.4, 0.0, 0.0, 0.0), - 0, - 1 - ); -} - -function glossMaterial(r: number, g: number, b: number): Material { - return new Material( - new Colour(r, g, b), - new Albedo(0.4, 0.1, 0.05, 0), - 250, - 1 - ); -} - -document.addEventListener('DOMContentLoaded', () => { - registerEventListeners(); - render(); -}); diff --git a/src/models/Scene.ts b/src/models/Scene.ts new file mode 100644 index 0000000..31e84bf --- /dev/null +++ b/src/models/Scene.ts @@ -0,0 +1,92 @@ +import {Plane, Sphere} from "./Geometry"; +import {Light} from "./Light"; +import {Albedo, Material} from "./Material"; +import {Colour} from "./Colour"; +import {Vector} from "./Vector"; + +export function getScene(): { + spheres: Sphere[]; + planes: Plane[]; + lights: Light[]; +} { + return { + spheres: [ + new Sphere(1, new Vector(-6.5, -3, -26), matteMaterial(27, 118, 255)), + new Sphere(1, new Vector(-14, -3, -25), matteMaterial(146, 80, 188)), + new Sphere(1, new Vector(-10, -3, -25), matteMaterial(0, 146, 178)), + new Sphere(1, new Vector(-10, -3, -20), matteMaterial(185, 18, 27)), + new Sphere(1, new Vector(-2.5, -3, -20), matteMaterial(115, 45, 217)), + new Sphere(2, new Vector(-10.5, -2, -16), mirrorMaterial()), + new Sphere(1, new Vector(-3, -3, -16), matteMaterial(247, 178, 173)), + new Sphere(1, new Vector(-6, -3, -18), matteMaterial(154, 188, 167)), + new Sphere(1, new Vector(-6, -3, -12), matteMaterial(96, 125, 139)), + new Sphere(1, new Vector(-9.5, -3, -12), matteMaterial(122, 186, 242)), + new Sphere(1, new Vector(0, -3, -14), matteMaterial(250, 91, 15)), + new Sphere(1, new Vector(-3, -3, -11), glassMaterial()), + new Sphere(1, new Vector(-1, -3, -10), matteMaterial(54, 95, 182)), + new Sphere(1, new Vector(-4.5, -3, -8), matteMaterial(139, 195, 74)), + new Sphere(4, new Vector(4, 0, -18), mirrorMaterial()), + new Sphere(1, new Vector(4, -3, -12), matteMaterial(115, 45, 217)), + new Sphere(1.5, new Vector(8.5, -2.5, -10), mirrorMaterial()), + new Sphere(1, new Vector(1, -3, -11.5), matteMaterial(255, 200, 0)), + new Sphere(1, new Vector(1.2, -3, -8.2), glassMaterial()), + new Sphere(1, new Vector(4, -3, -7), matteMaterial(244, 67, 54)), + new Sphere(1, new Vector(5.5, -3, -9.5), matteMaterial(150, 237, 137)), + new Sphere(1, new Vector(6.5, -3, -15), matteMaterial(14, 234, 255)), + new Sphere(1, new Vector(10, -3, -16), matteMaterial(171, 71, 188)), + new Sphere(1, new Vector(11, -3, -20), matteMaterial(190, 189, 191)), + ], + planes: [ + new Plane( + -4, + 50, + 40, + -45, + 1.5, + new Colour(116, 101, 87), + new Colour(92, 78, 70) + ), + ], + lights: [ + new Light(new Vector(30, 50, 40), 2.5), + new Light(new Vector(-20, 50, -25), 0.5), + ] + } +} + +function mirrorMaterial(): Material { + return new Material( + new Colour(220, 220, 220), + new Albedo(0.1, 1, 0.8, 0.0), + 2500, + 1 + ); +} + +function glassMaterial(): Material { + return new Material( + new Colour(153, 179, 204), + new Albedo(0.0, 0.5, 0.1, 0.8), + 125, + 1.5 + ); +} + +function matteMaterial(r: number, g: number, b: number): Material { + return new Material( + new Colour(r, g, b), + new Albedo(0.4, 0.0, 0.0, 0.0), + 0, + 1 + ); +} + +function glossMaterial(r: number, g: number, b: number): Material { + return new Material( + new Colour(r, g, b), + new Albedo(0.4, 0.1, 0.05, 0), + 250, + 1 + ); +} + diff --git a/src/ui/benchmarkCharts.ts b/src/ui/benchmarkCharts.ts new file mode 100644 index 0000000..85513c2 --- /dev/null +++ b/src/ui/benchmarkCharts.ts @@ -0,0 +1,90 @@ +import * as echarts from 'echarts'; +import {EChartsOption, EChartsType} from "echarts"; + +let scoreChart: EChartsType; +const userDeviceLabel = 'Your device'; + +let deviceScores = [ + { device: 'AMD Ryzen 7\n3700X\n(8c/16t)', multiCoreScore: 416, singleCoreScore: 74 }, + { device: 'Intel 11th Gen\ni5-1145G7\n(4c/8t)', multiCoreScore: 294, singleCoreScore: 73 }, + { device: 'Intel 8th Gen\ni7-8550U\n(4c/8t)', multiCoreScore: 221, singleCoreScore: 53 }, + { device: 'iPhone XS\n(6c/6t)\n(*WebKit)', multiCoreScore: 71, singleCoreScore: 28 }, + { device: 'Raspberry Pi4\nARM Cortex-A72\n(4c/4t)', multiCoreScore: 25, singleCoreScore: 9 } +] + +let scoreDataset = { + dimensions: ['device', 'multiCoreScore', 'singleCoreScore'], + source: deviceScores +} + +const option: EChartsOption = { + legend: {}, + dataset: scoreDataset, + xAxis: { type: 'value' }, + yAxis: { + type: 'category', + inverse: true, + axisLabel: { + formatter: function (label) { + if (label == userDeviceLabel) { + return `{usersDevice|${label}}`; + } + return label; + }, + rich: { + usersDevice: { + color: "#5755d9", + fontWeight: "bold", + fontSize: "14px" + }, + }, + }, + }, + series: [ + { + name: 'Multi-core', + type: 'bar', + label: { + show: true + }, + }, + { + name: 'Single core', + type: 'bar', + label: { + show: true + }, + } + ], + grid: { + left: '0%', + top: '5%', + containLabel: true + }, + color: [ + '#5755d9', + '#f1f1fc' + ], +} + +export function initScoreChart(dom: HTMLElement) { + scoreChart = echarts.init(dom); + scoreChart.setOption(option); + + window.addEventListener('resize', function() { + scoreChart.resize(); + }); +} + +export function addDeviceResult(multiCoreScore: number, singleCoreScore: number) { + // Remove previous result if present + const index = deviceScores.findIndex(d => d.device === userDeviceLabel); + if (index >= 0) { + deviceScores.splice(index, 1) + } + + deviceScores.push({device: userDeviceLabel, multiCoreScore: multiCoreScore, singleCoreScore: singleCoreScore}); + deviceScores.sort((a, b) => b.multiCoreScore - a.multiCoreScore); + + scoreChart.setOption(option); +}
\ No newline at end of file diff --git a/src/ui/benchmarkView.ts b/src/ui/benchmarkView.ts new file mode 100644 index 0000000..487ec63 --- /dev/null +++ b/src/ui/benchmarkView.ts @@ -0,0 +1,111 @@ +import {ChunkAllocationMode, RaytraceContext, RaytracerOptions} from "../models/RaytraceContext"; +import {RaytraceDispatcher} from "../RaytraceDispatcher"; +import {Framebuffer} from "../Framebuffer"; +import {getScene} from "../models/Scene"; +import {Colour} from "../models/Colour"; +import {Logger} from "../Logger"; +import * as BenchmarkChart from "./benchmarkCharts"; + +let multiCoreDispatcher: RaytraceDispatcher; +let singleCoreDispatcher: RaytraceDispatcher; + +let multiCoreScore = 0; +let singleCoreScore = 0; + +const deviceAvailableThreadCount = navigator.hardwareConcurrency; + +const logger = new Logger(document.getElementById('console-benchmark')!); + +const benchmarkButton = document.getElementById('benchmark')!; +const multiCoreScoreElement = document.getElementById('multi-core-score')!; +const singleCoreScoreElement = document.getElementById('single-core-score')!; + +export function registerEventListeners() { + benchmarkButton.addEventListener('click', startBenchmark); + logger.log(`Detected ${deviceAvailableThreadCount} available CPU threads, ready to run`); +} + +export function initChart() { + BenchmarkChart.initScoreChart(document.getElementById('benchmark-results-graph')!); +} + +function startBenchmark() { + benchmarkButton.classList.add('loading'); + multiCoreScoreElement.classList.add('loading'); + document.getElementById('single-core-score')!.classList.add('loading'); + + multiCoreDispatcher = initDispatcher( + 1920, + 1080, + getBenchmarkRenderOptions(true), + onMultiCoreBenchmarkComplete + ) + + singleCoreDispatcher = initDispatcher( + 1920, + 1080, + getBenchmarkRenderOptions(false), + onSingleCoreBenchmarkComplete + ) + + logger.log("Started multi-core benchmark"); + multiCoreDispatcher.requestRender(); +} + +function onMultiCoreBenchmarkComplete(score: number) { + multiCoreScore = score; + multiCoreScoreElement.textContent = `${multiCoreScore}`; + multiCoreScoreElement.classList.remove('loading'); + logger.log("Started single core benchmark"); + singleCoreDispatcher.requestRender(); +} + +function onSingleCoreBenchmarkComplete(score: number) { + singleCoreScore = score; + benchmarkButton.classList.remove('loading'); + singleCoreScoreElement.textContent = `${singleCoreScore}`; + singleCoreScoreElement.classList.remove('loading'); + + BenchmarkChart.addDeviceResult(multiCoreScore, singleCoreScore); +} + +function initDispatcher(width: number, height: number, options: RaytracerOptions, onComplete: Function): RaytraceDispatcher { + const fov = Math.PI / 3; + const framebuffer = new Framebuffer(width, height); + + const {spheres, planes, lights} = getScene(); + + const context = new RaytraceContext( + height, + width, + fov, + spheres, + planes, + lights, + new Colour(221, 221, 221), + options + ); + + return new RaytraceDispatcher( + framebuffer, + context, + logger, + onComplete + ); +} + +function getBenchmarkRenderOptions(multiCore: boolean) { + return { + numThreads: multiCore ? deviceAvailableThreadCount: 1, + shadows: true, + diffuseLighting: true, + specularLighting: true, + reflections: true, + refractions: true, + maxRecurseDepth: 5, + maxDrawDistance: 1000, + directMemoryTransfer: true, + chunkSize: 0, + chunkAllocationMode: ChunkAllocationMode.SEQUENTIAL + } +} diff --git a/src/ui/demoView.ts b/src/ui/demoView.ts new file mode 100644 index 0000000..e42696b --- /dev/null +++ b/src/ui/demoView.ts @@ -0,0 +1,141 @@ +import {ChunkAllocationMode, RaytraceContext, RaytracerOptions} from "../models/RaytraceContext"; +import {RaytraceDispatcher} from "../RaytraceDispatcher"; +import {Framebuffer} from "../Framebuffer"; +import {getScene} from "../models/Scene"; +import {Colour} from "../models/Colour"; +import {Logger} from "../Logger"; + +let dispatcher: RaytraceDispatcher; + +const renderButton = document.getElementById('render')!; +const stopRenderButton = document.getElementById('stop-render')!; +const viewFullButton = document.getElementById('view-full')!; + +export function registerEventListeners() { + renderButton.addEventListener('click', render); + stopRenderButton.addEventListener('click', stopRender); + + viewFullButton.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; + }); + + const threadToggle = getInputElement('enable-threads-toggle'); + threadToggle.addEventListener('change', () => { + threadsSlider.disabled = !threadToggle.checked; + }); +} + +export function render() { + renderButton.classList.add('loading'); + stopRenderButton.classList.remove('d-hide'); + viewFullButton.classList.add('d-hide'); + + const {width, height} = parseResolution(); + const options = parseOptions(); + dispatcher = initDispatcher(width, height, options, onRenderComplete); + dispatcher.requestRender(); +} + +function stopRender() { + dispatcher.stopRender(); + onRenderComplete(); +} + +function onRenderComplete() { + renderButton.classList.remove('loading'); + stopRenderButton.classList.add('d-hide'); + viewFullButton.classList.remove('d-hide'); +} + +function initDispatcher(width: number, height: number, options: RaytracerOptions, onComplete: Function): RaytraceDispatcher { + const fov = Math.PI / 3; + const framebuffer = new Framebuffer(width, height); + + const {spheres, planes, lights} = getScene(); + + const context = new RaytraceContext( + height, + width, + fov, + spheres, + planes, + lights, + new Colour(221, 221, 221), + options + ); + + return new RaytraceDispatcher( + framebuffer, + context, + new Logger(document.getElementById('console-demo')!), + onComplete + ); +} + +function parseOptions(): RaytracerOptions { + return { + numThreads: getDesiredThreadCount(), + shadows: getInputElement('shadows-toggle').checked, + diffuseLighting: getInputElement('diffuse-toggle').checked, + specularLighting: getInputElement('specular-toggle').checked, + reflections: getInputElement('reflections-toggle').checked, + refractions: getInputElement('refractions-toggle').checked, + maxRecurseDepth: 5, + maxDrawDistance: 1000, + directMemoryTransfer: getInputElement('direct-transfer').checked, + chunkSize: parseInt(getInputElement('chunk-size').value, 10), + chunkAllocationMode: getChunkAllocationMode() + }; +} + +function parseResolution(): {width: number; height: number} { + switch (getInputElement('res').value) { + case '360p': + return {width: 640, height: 360}; + case '480p': + return {width: 832, height: 480}; + case '720p': + default: + return {width: 1280, height: 720}; + case '1080p': + return {width: 1920, height: 1080}; + case '1440p': + return {width: 2560, height: 1440}; + case '4k': + return {width: 3840, height: 2160}; + } +} + +function getChunkAllocationMode(): ChunkAllocationMode { + switch (getInputElement('chunk-allocation-mode').value) { + case 'SEQUENTIAL': + default: + return ChunkAllocationMode.SEQUENTIAL; + case 'RANDOM': + return ChunkAllocationMode.RANDOM + case 'CENTER_TO_EDGE': + return ChunkAllocationMode.CENTER_TO_EDGE + case 'EDGE_TO_CENTER': + return ChunkAllocationMode.EDGE_TO_CENTER + } +} + +function getDesiredThreadCount(): number { + if (getInputElement('enable-threads-toggle').checked) { + return Number.parseInt(getInputElement('threads').value); + } else { + return 1; + } +} + +function getInputElement(elementId: string) { + return document.getElementById(elementId) as HTMLInputElement; +}
\ No newline at end of file diff --git a/src/ui/index.ts b/src/ui/index.ts new file mode 100644 index 0000000..683d114 --- /dev/null +++ b/src/ui/index.ts @@ -0,0 +1,51 @@ +import * as DemoView from "./demoView"; +import * as BenchmarkView from "./benchmarkView"; + +let chartInitialised = false; + +function registerEventListeners() { + document.getElementById('demo-tab')!.addEventListener('click', (ev) => { + ev.preventDefault(); + setActiveTab('demo'); + }); + + document.getElementById('benchmark-tab')!.addEventListener('click', (ev) => { + ev.preventDefault(); + setActiveTab('benchmark'); + }); + + DemoView.registerEventListeners(); + BenchmarkView.registerEventListeners(); +} + +function setActiveTab(tab: string) { + if (tab==='demo') { + document.getElementById('demo-mode')!.classList.remove('d-hide'); + document.getElementById('benchmark-mode')!.classList.add('d-hide'); + + document.getElementById('demo-tab')!.classList.add('active'); + document.getElementById('benchmark-tab')!.classList.remove('active'); + } else { + // benchmark + document.getElementById('demo-mode')!.classList.add('d-hide'); + document.getElementById('benchmark-mode')!.classList.remove('d-hide'); + + document.getElementById('demo-tab')!.classList.remove('active'); + document.getElementById('benchmark-tab')!.classList.add('active'); + + if (!chartInitialised) { + // Init on tab click rather than window load so DOM element has correct size and animations play + BenchmarkView.initChart(); + chartInitialised = true; + } + } +} + +document.addEventListener('DOMContentLoaded', () => { + registerEventListeners(); + if (window.location.hash === '#benchmark') { + setActiveTab('benchmark'); + } else { + DemoView.render(); + } +}); |