From 6a752a57e91b393bb9ecc503ba64f65928305731 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Sun, 30 Apr 2023 11:27:26 +0100 Subject: [PATCH] Add tiny UI --- assets/fonts/comfortaa-v40-latin-regular.woff | Bin 0 -> 15100 bytes .../fonts/comfortaa-v40-latin-regular.woff2 | Bin 0 -> 12028 bytes assets/fonts/open-sans-v34-latin-regular.woff | Bin 0 -> 20712 bytes .../fonts/open-sans-v34-latin-regular.woff2 | Bin 0 -> 16740 bytes assets/icons/info.svg | 6 + {static => assets/icons}/maximize.svg | 2 +- {static => assets/icons}/minimize.svg | 2 +- assets/icons/restart.svg | 5 + assets/no-change/404.html | 43 +++++ {static => assets}/no-change/robots.txt | 0 src/game-loop/game-loop.ts | 71 ++++---- src/index.html | 34 +++- src/index.scss | 92 ++++++++++ src/index.ts | 49 ++++-- src/pipelines/agents/agent-pipeline.ts | 7 +- src/pipelines/brush/brush-pipeline.ts | 11 +- src/pipelines/diffusion/diffuse.wgsl | 11 +- src/pipelines/diffusion/diffusion-pipeline.ts | 11 +- src/pipelines/render/render-pipeline.ts | 7 +- src/style/fonts.scss | 23 +++ src/style/mixins.scss | 160 ++++++++++++++++++ src/style/vars.scss | 44 +++++ src/styles/index.scss | 34 ---- src/styles/mixins.scss | 25 --- .../full-screen-quad/full-screen-quad.ts | 2 +- src/utils/graphics/noise/noise.ts | 116 +++++++------ src/utils/handle-full-screen.ts | 44 ++--- src/utils/webgpu/initialize-gpu.ts | 16 ++ src/utils/{ => webgpu}/smart-compile.ts | 0 webpack.config.js | 22 ++- 30 files changed, 627 insertions(+), 210 deletions(-) create mode 100644 assets/fonts/comfortaa-v40-latin-regular.woff create mode 100644 assets/fonts/comfortaa-v40-latin-regular.woff2 create mode 100644 assets/fonts/open-sans-v34-latin-regular.woff create mode 100644 assets/fonts/open-sans-v34-latin-regular.woff2 create mode 100644 assets/icons/info.svg rename {static => assets/icons}/maximize.svg (55%) rename {static => assets/icons}/minimize.svg (55%) create mode 100644 assets/icons/restart.svg create mode 100644 assets/no-change/404.html rename {static => assets}/no-change/robots.txt (100%) create mode 100644 src/index.scss create mode 100644 src/style/fonts.scss create mode 100644 src/style/mixins.scss create mode 100644 src/style/vars.scss delete mode 100644 src/styles/index.scss delete mode 100644 src/styles/mixins.scss create mode 100644 src/utils/webgpu/initialize-gpu.ts rename src/utils/{ => webgpu}/smart-compile.ts (100%) diff --git a/assets/fonts/comfortaa-v40-latin-regular.woff b/assets/fonts/comfortaa-v40-latin-regular.woff new file mode 100644 index 0000000000000000000000000000000000000000..c54393b0f232a808940e24118be9d4c56f29553c GIT binary patch literal 15100 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

wmmAf(Ia5sSiLTs)4zmfsP_ibo1X7^{SoZPb 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 amd?$S(55001cYhm-k%A-FTxptyp(@(;K3V-x-d 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 literal 0 HcmV?d00001 diff --git a/assets/fonts/open-sans-v34-latin-regular.woff2 b/assets/fonts/open-sans-v34-latin-regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..15339ea9ccdd3bc54b25dfe54800f1c3bb730d26 GIT binary patch literal 16740 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 + + + + + diff --git a/static/maximize.svg b/assets/icons/maximize.svg similarity index 55% rename from static/maximize.svg rename to assets/icons/maximize.svg index 7118488..9c8558e 100644 --- a/static/maximize.svg +++ b/assets/icons/maximize.svg @@ -1,4 +1,4 @@ - + diff --git a/static/minimize.svg b/assets/icons/minimize.svg similarity index 55% rename from static/minimize.svg rename to assets/icons/minimize.svg index 93a212a..05d54a8 100644 --- a/static/minimize.svg +++ b/assets/icons/minimize.svg @@ -1,4 +1,4 @@ - + diff --git a/assets/icons/restart.svg b/assets/icons/restart.svg new file mode 100644 index 0000000..f87e22b --- /dev/null +++ b/assets/icons/restart.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/no-change/404.html b/assets/no-change/404.html new file mode 100644 index 0000000..7c62e5c --- /dev/null +++ b/assets/no-change/404.html @@ -0,0 +1,43 @@ + + + + + + Not found + + + + + + + + diff --git a/static/no-change/robots.txt b/assets/no-change/robots.txt similarity index 100% rename from static/no-change/robots.txt rename to assets/no-change/robots.txt diff --git a/src/game-loop/game-loop.ts b/src/game-loop/game-loop.ts index 0221917..833c632 100644 --- a/src/game-loop/game-loop.ts +++ b/src/game-loop/game-loop.ts @@ -6,36 +6,45 @@ import { RenderPipeline } from '../pipelines/render/render-pipeline'; import { settings } from '../settings'; import { DeltaTimeCalculator } from '../utils/delta-time-calculator'; import { Random } from '../utils/random'; -import { sleep } from '../utils/sleep'; import { vec2 } from 'gl-matrix'; export default class GameLoop { - private context: GPUCanvasContext; - private device: GPUDevice; + private readonly deltaTimeCalculator = new DeltaTimeCalculator(); - private agentPipeline: AgentPipeline; - private renderPipeline: RenderPipeline; - private brushPipeline: BrushPipeline; - private diffusionPipeline: DiffusionPipeline; + private readonly agentPipeline: AgentPipeline; + private readonly renderPipeline: RenderPipeline; + private readonly brushPipeline: BrushPipeline; + private readonly diffusionPipeline: DiffusionPipeline; private trailMapA?: GPUTexture; private trailMapB?: GPUTexture; + private hasFinished = false; + private readonly hasFinishedPromise: Promise = new Promise( + (resolve) => (this.resolveHasFinished = resolve) + ); + private resolveHasFinished: () => void; + private isSwipeActive = false; - private readonly deltaTimeCalculator = new DeltaTimeCalculator(); - public constructor(private canvas: HTMLCanvasElement) {} - - async start() { - await this.initializeDevice(); + public constructor( + private readonly canvas: HTMLCanvasElement, + private readonly device: GPUDevice + ) { + const context = this.canvas.getContext('webgpu') as any; + context.configure({ + device: this.device, + format: navigator.gpu.getPreferredCanvasFormat(), + alphaMode: 'premultiplied', + }); this.resize(); this.agentPipeline = new AgentPipeline(this.device, this.spawnAgents()); this.brushPipeline = new BrushPipeline(this.device); this.diffusionPipeline = new DiffusionPipeline(this.device); - this.renderPipeline = new RenderPipeline(this.context, this.device); + this.renderPipeline = new RenderPipeline(context, this.device); window.addEventListener('resize', this.resize.bind(this)); window.addEventListener('mousemove', this.onSwipe.bind(this)); @@ -44,8 +53,11 @@ export default class GameLoop { this.isSwipeActive = false; this.brushPipeline.clearSwipes(); }); + } + public async start(): Promise { requestAnimationFrame(this.render.bind(this)); + return this.hasFinishedPromise; } private onSwipe(event: MouseEvent) { @@ -112,24 +124,11 @@ export default class GameLoop { }); } - private async initializeDevice(): Promise { - const gpu = navigator.gpu; - if (!gpu) { - throw new Error('WebGPU is not supported'); + private render(time: DOMHighResTimeStamp) { + if (this.hasFinished) { + return; } - const adapter = await gpu.requestAdapter(); - this.device = await adapter.requestDevice(); // could request more resources - - this.context = this.canvas.getContext('webgpu') as any; - this.context.configure({ - device: this.device, - format: gpu.getPreferredCanvasFormat(), - alphaMode: 'premultiplied', - }); - } - - private async render(time: DOMHighResTimeStamp) { const deltaTime = this.deltaTimeCalculator.calculateDeltaTimeInSeconds(time); const params = { @@ -161,4 +160,18 @@ export default class GameLoop { // await sleep(200); requestAnimationFrame(this.render.bind(this)); } + + public destroy() { + this.hasFinished = true; + + this.agentPipeline?.destroy(); + this.brushPipeline?.destroy(); + this.diffusionPipeline?.destroy(); + this.renderPipeline?.destroy(); + + this.trailMapA?.destroy(); + this.trailMapB?.destroy(); + + this.resolveHasFinished(); + } } diff --git a/src/index.html b/src/index.html index ed9b4a4..45c0470 100644 --- a/src/index.html +++ b/src/index.html @@ -30,9 +30,37 @@ - - -
+
+ + +
+
+            
+          
+
+ + +
+ + diff --git a/src/index.scss b/src/index.scss new file mode 100644 index 0000000..4e588f1 --- /dev/null +++ b/src/index.scss @@ -0,0 +1,92 @@ +@use 'style/vars'; +@use 'style/fonts'; +@use 'style/mixins' as *; + +*, +*::before, +*::after { + margin: 0; + padding: 0; + box-sizing: border-box; + + @media (prefers-reduced-motion) { + transition: none !important; + animation: none !important; + } +} + +html { + height: 100%; + -webkit-font-smooth: antialiased; + + > body { + width: 100%; + height: 100%; + display: flex; + position: relative; + + > .canvas-container { + height: 100%; + display: flex; + width: 100%; + + > canvas { + height: 100%; + width: 100%; + } + + > button.minimize-full-screen { + @include image-button(url('../assets/icons/minimize.svg')); + position: absolute; + bottom: var(--small-margin); + right: var(--small-margin); + } + + > .errors-container { + color: red; + position: absolute; + top: 0; + left: 0; + display: none; + + pre { + font-size: 20px; + } + } + } + + > aside { + position: absolute; + top: 50%; + left: 0; + transform: translateY(-50%); + + @include blurred-background; + border-radius: var(--border-radius); + margin: var(--small-margin); + + > nav.buttons { + @include center-children; + flex-direction: column; + gap: var(--normal-margin); + margin: var(--small-margin); + + > button.info { + @include image-button(url('../assets/icons/info.svg')); + } + + > button.maximize-full-screen { + @include image-button(url('../assets/icons/maximize.svg')); + } + + > button.restart { + @include image-button(url('../assets/icons/restart.svg')); + } + } + + > main.pages { + display: none; + } + } + } +} diff --git a/src/index.ts b/src/index.ts index 7abbbcf..66da258 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,9 @@ +import '../assets/icons/info.svg'; import GameLoop from './game-loop/game-loop'; -import './styles/index.scss'; +import './index.scss'; import { applyArrayPlugins } from './utils/array'; +import { handleFullScreen } from './utils/handle-full-screen'; +import { initializeGPU } from './utils/webgpu/initialize-gpu'; declare global { interface Array { @@ -19,17 +22,43 @@ declare global { } } -applyArrayPlugins(); +const getElements = () => ({ + infoButton: document.querySelector('button.info') as HTMLButtonElement, + minimizeFullScreenButton: document.querySelector( + 'button.minimize-full-screen' + ) as HTMLButtonElement, + maximizeFullScreenButton: document.querySelector( + 'button.maximize-full-screen' + ) as HTMLButtonElement, + restartButton: document.querySelector('button.restart') as HTMLButtonElement, + canvas: document.querySelector('canvas') as HTMLCanvasElement, + canvasContainer: document.querySelector('main.canvas-container') as HTMLCanvasElement, + errorContainer: document.querySelector('.errors') as HTMLDivElement, +}); -const errorContainer = document.querySelector('.errors'); +const main = async () => { + applyArrayPlugins(); + const elements = getElements(); -const main = () => { - try { - const canvas = document.querySelector('canvas'); - const game = new GameLoop(canvas); - game.start(); - } catch (e) { - errorContainer.innerHTML = e.message; + handleFullScreen({ + minimizeButton: elements.minimizeFullScreenButton, + maximizeButton: elements.maximizeFullScreenButton, + target: elements.canvasContainer, + }); + + const gpu = await initializeGPU(); + + let game: GameLoop | null = null; + + elements.restartButton.addEventListener('click', () => game?.destroy()); + + while (true) { + try { + game = new GameLoop(elements.canvas, gpu); + await game.start(); + } catch (e) { + elements.errorContainer.innerHTML = e.message; + } } }; diff --git a/src/pipelines/agents/agent-pipeline.ts b/src/pipelines/agents/agent-pipeline.ts index 42e5d01..4b21465 100644 --- a/src/pipelines/agents/agent-pipeline.ts +++ b/src/pipelines/agents/agent-pipeline.ts @@ -1,4 +1,4 @@ -import { smartCompile } from '../../utils/smart-compile'; +import { smartCompile } from '../../utils/webgpu/smart-compile'; import { CommonParameters } from '../common-parameters'; import { AGENT_SIZE_IN_BYTES, Agent } from './agent'; import { AgentSettings } from './agent-settings'; @@ -133,4 +133,9 @@ export class AgentPipeline { this.previousTrailMapOut = trailMapOut; } } + + public destroy() { + this.uniforms.destroy(); + this.agentsBuffer.destroy(); + } } diff --git a/src/pipelines/brush/brush-pipeline.ts b/src/pipelines/brush/brush-pipeline.ts index ada27d0..9707485 100644 --- a/src/pipelines/brush/brush-pipeline.ts +++ b/src/pipelines/brush/brush-pipeline.ts @@ -1,5 +1,5 @@ import { generateNoise } from '../../utils/graphics/noise/noise'; -import { smartCompile } from '../../utils/smart-compile'; +import { smartCompile } from '../../utils/webgpu/smart-compile'; import { CommonParameters } from '../common-parameters'; import { BrushSettings } from './brush-settings'; import shader from './brush.wgsl'; @@ -15,7 +15,7 @@ export class BrushPipeline { private readonly pipeline: GPURenderPipeline; private readonly uniforms: GPUBuffer; private readonly vertexBuffer: GPUBuffer; - private readonly noise: GPUTexture; + private readonly noise: GPUTextureView; private linePoints: Array = []; private previousPoints: Array = []; private nextPoint: vec2 | null = null; @@ -116,7 +116,7 @@ export class BrushPipeline { }, { binding: 2, - resource: this.noise.createView(), + resource: this.noise, }, ], }); @@ -234,6 +234,11 @@ export class BrushPipeline { this.linePoints.splice(0, this.linePoints.length - 1); } + + public destroy() { + this.vertexBuffer.destroy(); + this.uniforms.destroy(); + } } const catmullRomInterpolation = ( diff --git a/src/pipelines/diffusion/diffuse.wgsl b/src/pipelines/diffusion/diffuse.wgsl index 154cd45..2ecb89f 100644 --- a/src/pipelines/diffusion/diffuse.wgsl +++ b/src/pipelines/diffusion/diffuse.wgsl @@ -29,15 +29,14 @@ fn fragment(@location(0) uv: vec2) -> @location(0) vec4 { let mixedTrails = mix( current.rgb, neighbours.rgb, - settings.diffusionRateTrails + (noise.rgb - vec3(0.5)) * 0.1 + settings.diffusionRateTrails ) * (1.0 - settings.decayRateTrails); let mixedBrush = mix( - current.a, - neighbours.a, - settings.diffusionRateBrush + (noise.a - 0.5) * 0.5 - ) * (1.0 - settings.decayRateBrush - (noise.a - 0.5) * 0.1); - + current.a + (noise.a - 0.5) * 0.1, + neighbours.a , + settings.diffusionRateBrush + ) * (1.0 - settings.decayRateBrush); return clamp(vec4(mixedTrails, mixedBrush), vec4(0), vec4(1)); } diff --git a/src/pipelines/diffusion/diffusion-pipeline.ts b/src/pipelines/diffusion/diffusion-pipeline.ts index fe1fe48..c2dda95 100644 --- a/src/pipelines/diffusion/diffusion-pipeline.ts +++ b/src/pipelines/diffusion/diffusion-pipeline.ts @@ -1,6 +1,6 @@ import { setUpFullScreenQuad } from '../../utils/graphics/full-screen-quad/full-screen-quad'; import { generateNoise } from '../../utils/graphics/noise/noise'; -import { smartCompile } from '../../utils/smart-compile'; +import { smartCompile } from '../../utils/webgpu/smart-compile'; import { CommonParameters } from '../common-parameters'; import shader from './diffuse.wgsl'; import { DiffusionSettings } from './diffusion-settings'; @@ -11,7 +11,7 @@ export class DiffusionPipeline { private readonly pipeline: GPURenderPipeline; private readonly uniforms: GPUBuffer; private readonly quadVertexBuffer: GPUBuffer; - private readonly noise: GPUTexture; + private readonly noise: GPUTextureView; private bindGroup?: GPUBindGroup; private previousTrailMapIn?: GPUTexture; @@ -128,7 +128,7 @@ export class DiffusionPipeline { }, { binding: 3, - resource: this.noise.createView(), + resource: this.noise, }, ], }); @@ -136,4 +136,9 @@ export class DiffusionPipeline { this.previousTrailMapIn = trailMapIn; } } + + public destroy() { + this.quadVertexBuffer.destroy(); + this.uniforms.destroy(); + } } diff --git a/src/pipelines/render/render-pipeline.ts b/src/pipelines/render/render-pipeline.ts index a0384d3..44b7be3 100644 --- a/src/pipelines/render/render-pipeline.ts +++ b/src/pipelines/render/render-pipeline.ts @@ -1,5 +1,5 @@ import { setUpFullScreenQuad } from '../../utils/graphics/full-screen-quad/full-screen-quad'; -import { smartCompile } from '../../utils/smart-compile'; +import { smartCompile } from '../../utils/webgpu/smart-compile'; import { CommonParameters } from '../common-parameters'; import { RenderSettings } from './render-settings'; import shader from './render.wgsl'; @@ -118,4 +118,9 @@ export class RenderPipeline { this.previousColorTexture = colorTexture; } } + + public destroy() { + this.quadVertexBuffer.destroy(); + this.uniforms.destroy(); + } } diff --git a/src/style/fonts.scss b/src/style/fonts.scss new file mode 100644 index 0000000..2b02a7b --- /dev/null +++ b/src/style/fonts.scss @@ -0,0 +1,23 @@ +/* comfortaa-regular - latin */ +@font-face { + font-family: 'Comfortaa'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: local(''), + url('../../assets/fonts/comfortaa-v40-latin-regular.woff2') format('woff2'), + /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../../assets/fonts/comfortaa-v40-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} + +/* open-sans-regular - latin */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: local(''), + url('../../assets/fonts/open-sans-v34-latin-regular.woff2') format('woff2'), + /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../../assets/fonts/open-sans-v34-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} diff --git a/src/style/mixins.scss b/src/style/mixins.scss new file mode 100644 index 0000000..1f18d08 --- /dev/null +++ b/src/style/mixins.scss @@ -0,0 +1,160 @@ +@use 'sass:math'; + +$breakpoint-width: 700px !default; + +@mixin on-small-screen() { + @media (max-width: ($breakpoint-width - 1px)) { + @content; + } +} + +@mixin on-large-screen() { + @media (min-width: $breakpoint-width) { + @content; + } +} + +@mixin in-dark-mode() { + html[theme='dark'] { + @content; + } +} + +@mixin title-fragment-link() { + position: relative; + + &:before { + content: '#'; + position: absolute; + left: -0.5ch; + top: 50%; + opacity: 0; + transform: translateX(-100%) translateY(-50%); + transition: opacity var(--transition-time); + } + + &:hover:before { + opacity: 0.5; + } +} + +@mixin image-button($background-image) { + @include square(var(--icon-size)); + border: none; + cursor: pointer; + + background-color: transparent; + background-image: $background-image; + background-size: contain; + background-repeat: no-repeat; + background-position: center; + + transition: transform var(--transition-time); + &:hover { + transform: scale(1.15); + } +} + +@mixin center-children() { + display: flex; + align-items: center; + justify-content: center; +} + +@mixin absolute-center() { + position: absolute; + left: 50%; + top: 50%; + transform: translateX(-50%) translateY(-50%); +} + +@mixin blurred-background() { + backdrop-filter: blur(var(--blur-radius)); + -webkit-backdrop-filter: blur(var(--blur-radius)); + + @supports not ( + (backdrop-filter: blur(var(--blur-radius))) or + (-webkit-backdrop-filter: blur(var(--blur-radius))) + ) { + background-color: var(--card-color); + } +} + +@mixin square($size) { + width: $size; + height: $size; +} + +@mixin title-font() { + font: 400 3rem 'Comfortaa', sans-serif; + color: var(--normal-text-color); + line-height: 1; + + @include on-small-screen { + font-size: 3rem; + line-height: 1.1; + } +} + +@mixin sub-title-font() { + font: 400 1.75rem 'Comfortaa', sans-serif; + color: var(--normal-text-color); + hyphens: auto; +} + +@mixin main-font() { + font: 400 1.1rem 'Open Sans', sans-serif; + color: var(--normal-text-color); + line-height: 1.8; + hyphens: auto; +} + +@mixin special-text-font() { + font: 400 1rem 'Open Sans', sans-serif; + color: var(--special-text-color); + hyphens: auto; + font-style: italic; +} + +@mixin link { + $border-shift: 10px; + $line-width: 2px; + + @include special-text-font(); + cursor: pointer; + position: relative; + display: inline-block; + overflow: hidden; + + padding: 0 3px $line-width 0; + + &:before, + &:after { + content: ''; + display: block; + position: absolute; + bottom: 0; + } + + &:before { + width: calc(100% + #{$border-shift}); + border-bottom: $line-width dashed var(--accent-color); + transition: transform var(--transition-time); + } + + &:after { + width: 100%; + height: $line-width; + background: linear-gradient( + 90deg, + var(--card-color) 0, + transparent 4px, + transparent calc(100% - 4px), + var(--card-color) 100% + ); + } + + &:hover:before { + transform: translateX(-$border-shift); + } +} diff --git a/src/style/vars.scss b/src/style/vars.scss new file mode 100644 index 0000000..2a4c1a0 --- /dev/null +++ b/src/style/vars.scss @@ -0,0 +1,44 @@ +@use 'mixins' as *; + +:root { + --transition-time: 200ms; + --transition-time-long: 350ms; + --line-width: 4px; + --line-height: 1.125rem; + --accent-color: #b7455e; + --sun-color: #f7f78c; + --very-light-text-color: #ffffff; + --background: #ffffff; + --normal-text-color: #31343f; + --card-color: #ffffff; + --blurred-card-color: transparent; + --blur-radius: 12px; + --special-text-color: var(--accent-color); + --inset-shadow: inset 0 0 4px 1px rgba(0, 0, 0, 0.05), inset 0 0 5px rgba(0, 0, 0, 0.2); + --border-radius: 0.85rem; + + --large-margin: 4.6rem; + --normal-margin: 2rem; + --small-margin: 1rem; + --shadow: 0 0 5px 2px rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.2); + --icon-size: 2.8rem; + --large-icon-size: 3.75rem; + --body-width: min(80%, 60rem); +} + +@include on-small-screen { + :root { + --body-width: 90%; + --large-margin: 2.8rem; + --normal-margin: 2rem; + } +} + +@include in-dark-mode { + --background: #242638; + --normal-text-color: #ffffff; + --card-color: #263551; + --blurred-card-color: #212f4a77; + --special-text-color: #ffffff; + --inset-shadow: inset 0 0 10px 2px rgba(0, 0, 0, 0.3), inset 0 0 4px rgba(0, 0, 0, 0.5); +} diff --git a/src/styles/index.scss b/src/styles/index.scss deleted file mode 100644 index 3a6e755..0000000 --- a/src/styles/index.scss +++ /dev/null @@ -1,34 +0,0 @@ -*, -*::before, -*::after { - margin: 0; - padding: 0; - box-sizing: border-box; - - @media (prefers-reduced-motion) { - transition: none !important; - animation: none !important; - } -} - -html { - height: 100%; - -webkit-font-smooth: antialiased; -} - -body { - height: 100%; - display: flex; -} - -canvas { - height: 100%; - width: 100%; -} - -.errors { - color: red; - position: absolute; - top: 0; - left: 0; -} diff --git a/src/styles/mixins.scss b/src/styles/mixins.scss deleted file mode 100644 index 207b151..0000000 --- a/src/styles/mixins.scss +++ /dev/null @@ -1,25 +0,0 @@ -@mixin card { - border: 2px solid white; - border-radius: 12px; - - backdrop-filter: blur(24px); - @supports not (backdrop-filter: blur(24px)) { - background-color: rgba(0, 0, 0, 0.15); - } - - &:focus { - outline: none; - border: 4px solid white; - } -} - -@mixin center-children { - display: flex; - justify-content: center; - align-items: center; -} - -@mixin square($size) { - width: $size; - height: $size; -} diff --git a/src/utils/graphics/full-screen-quad/full-screen-quad.ts b/src/utils/graphics/full-screen-quad/full-screen-quad.ts index 1afd862..0858a5a 100644 --- a/src/utils/graphics/full-screen-quad/full-screen-quad.ts +++ b/src/utils/graphics/full-screen-quad/full-screen-quad.ts @@ -1,4 +1,4 @@ -import { smartCompile } from '../../smart-compile'; +import { smartCompile } from '../../webgpu/smart-compile'; import shader from './full-screen-quad.wgsl'; export const setUpFullScreenQuad = ( diff --git a/src/utils/graphics/noise/noise.ts b/src/utils/graphics/noise/noise.ts index 05ece8b..656e83c 100644 --- a/src/utils/graphics/noise/noise.ts +++ b/src/utils/graphics/noise/noise.ts @@ -1,8 +1,10 @@ import { Random } from '../../random'; -import { smartCompile } from '../../smart-compile'; +import { smartCompile } from '../../webgpu/smart-compile'; import { setUpFullScreenQuad } from '../full-screen-quad/full-screen-quad'; import noise from './noise.wgsl'; +const textureCache = new Map(); + export const generateNoise = ({ device, width = 1024, @@ -19,67 +21,71 @@ export const generateNoise = ({ lacunarity?: number; amplitude?: number; gain?: number; -}) => { - const { buffer, vertex } = setUpFullScreenQuad(device); - const quadVertexBuffer = buffer; +}): GPUTextureView => { + const cacheKey = `${width}x${height}x${octaves}x${lacunarity}x${amplitude}x${gain}`; + if (!textureCache.has(cacheKey)) { + const { buffer, vertex } = setUpFullScreenQuad(device); + const quadVertexBuffer = buffer; - const pipeline = device.createRenderPipeline({ - layout: 'auto', - vertex, - fragment: { - module: smartCompile(device, noise), - entryPoint: 'fragment', - constants: { - octaves, - lacunarity, - amplitude, - gain, - seedR: Random.getRandom(), - seedG: Random.getRandom(), - seedB: Random.getRandom(), - seedA: Random.getRandom(), + const pipeline = device.createRenderPipeline({ + layout: 'auto', + vertex, + fragment: { + module: smartCompile(device, noise), + entryPoint: 'fragment', + constants: { + octaves, + lacunarity, + amplitude, + gain, + seedR: Random.getRandom(), + seedG: Random.getRandom(), + seedB: Random.getRandom(), + seedA: Random.getRandom(), + }, + targets: [ + { + format: 'rgba16float', + }, + ], }, - targets: [ + primitive: { + topology: 'triangle-strip', + }, + }); + + const colorTexture = device.createTexture({ + size: { + width, + height, + depthOrArrayLayers: 1, + }, + format: 'rgba16float', + usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT, + }); + + const renderPassDescriptor: GPURenderPassDescriptor = { + colorAttachments: [ { - format: 'rgba16float', + view: colorTexture.createView(), + clearValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }, + loadOp: 'clear', + storeOp: 'store', }, ], - }, - primitive: { - topology: 'triangle-strip', - }, - }); + }; - const colorTexture = device.createTexture({ - size: { - width, - height, - depthOrArrayLayers: 1, - }, - format: 'rgba16float', - usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT, - }); + const commandEncoder = device.createCommandEncoder(); - const renderPassDescriptor: GPURenderPassDescriptor = { - colorAttachments: [ - { - view: colorTexture.createView(), - clearValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }, - loadOp: 'clear', - storeOp: 'store', - }, - ], - }; + const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); + passEncoder.setPipeline(pipeline); + passEncoder.setVertexBuffer(0, quadVertexBuffer); + passEncoder.draw(4, 1); + passEncoder.end(); - const commandEncoder = device.createCommandEncoder(); + device.queue.submit([commandEncoder.finish()]); + textureCache.set(cacheKey, colorTexture); + } - const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); - passEncoder.setPipeline(pipeline); - passEncoder.setVertexBuffer(0, quadVertexBuffer); - passEncoder.draw(4, 1); - passEncoder.end(); - - device.queue.submit([commandEncoder.finish()]); - - return colorTexture; + return textureCache.get(cacheKey).createView(); }; diff --git a/src/utils/handle-full-screen.ts b/src/utils/handle-full-screen.ts index 8fc7fd1..db91163 100644 --- a/src/utils/handle-full-screen.ts +++ b/src/utils/handle-full-screen.ts @@ -1,8 +1,12 @@ -export const handleFullScreen = ( - minimizeButton: HTMLElement, - maximizeButton: HTMLElement, - target: HTMLElement -) => { +export const handleFullScreen = ({ + minimizeButton, + maximizeButton, + target, +}: { + minimizeButton: HTMLElement; + maximizeButton: HTMLElement; + target: HTMLElement; +}) => { if (!document.fullscreenEnabled) { minimizeButton.style.visibility = 'hidden'; maximizeButton.style.visibility = 'hidden'; @@ -10,39 +14,23 @@ export const handleFullScreen = ( } const isInFullScreen = (): boolean => document.fullscreenElement !== null; - - const showButtons = () => { + const updateButtons = () => { minimizeButton.style.visibility = isInFullScreen() ? 'visible' : 'hidden'; maximizeButton.style.visibility = isInFullScreen() ? 'hidden' : 'visible'; }; - showButtons(); - - let currentWindowHeight = innerHeight; - - const followToggle = () => { - showButtons(); - currentWindowHeight = innerHeight; - }; - - const triggerToggle = async () => { - await (isInFullScreen() ? document.exitFullscreen() : target.requestFullscreen()); - followToggle(); - }; + updateButtons(); addEventListener('keydown', (e) => { + // on full screen request, only apply it to the target if (e.key === 'F11') { - triggerToggle(); e.preventDefault(); + isInFullScreen() ? document.exitFullscreen() : target.requestFullscreen(); } }); - addEventListener('resize', () => { - if (isInFullScreen() && currentWindowHeight > innerHeight) { - followToggle(); - } - }); + addEventListener('fullscreenchange', updateButtons); - maximizeButton.addEventListener('click', triggerToggle); - minimizeButton.addEventListener('click', triggerToggle); + maximizeButton.addEventListener('click', target.requestFullscreen.bind(target)); + minimizeButton.addEventListener('click', document.exitFullscreen.bind(document)); }; diff --git a/src/utils/webgpu/initialize-gpu.ts b/src/utils/webgpu/initialize-gpu.ts new file mode 100644 index 0000000..54a63ef --- /dev/null +++ b/src/utils/webgpu/initialize-gpu.ts @@ -0,0 +1,16 @@ +export const initializeGPU = async (): Promise => { + const gpu = navigator.gpu; + if (!gpu) { + throw new Error('WebGPU is not supported'); + } + + const adapter = await gpu.requestAdapter({ + powerPreference: 'high-performance', + }); + + if (!adapter) { + throw new Error('Could not request adatper'); + } + + return await adapter.requestDevice(); // could request more resources +}; diff --git a/src/utils/smart-compile.ts b/src/utils/webgpu/smart-compile.ts similarity index 100% rename from src/utils/smart-compile.ts rename to src/utils/webgpu/smart-compile.ts diff --git a/webpack.config.js b/webpack.config.js index 55462d4..db7a51d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -39,13 +39,13 @@ module.exports = (env, argv) => ({ rules: [ { test: /\.svg$/i, - use: 'svg-inline-loader', + type: 'asset/inline', }, { - test: /\.wgsl$/i, - type: 'asset/source', + test: /\.woff2?$/i, + type: 'asset/resource', generator: { - filename: '[name][ext]', + filename: '[hash:8][ext]', }, }, { @@ -55,6 +55,13 @@ module.exports = (env, argv) => ({ filename: '[name][ext]', }, }, + { + test: /\.wgsl$/i, + type: 'asset/source', + generator: { + filename: '[name][ext]', + }, + }, { test: /\.scss$/i, use: [ @@ -70,16 +77,13 @@ module.exports = (env, argv) => ({ ], }, { - test: /\.ts$/, + test: /\.ts$/i, use: 'ts-loader', }, ], }, resolve: { - extensions: [ - '.ts', - '.js', // required for development - ], + extensions: ['.ts', '.js'], }, output: { clean: true,