├── .gitignore
├── LICENSE
├── README.md
├── assets
├── syne-mono-subset.ttf
└── syne-mono-subset.woff
├── cover.jpg
├── css
├── base.css
└── demo.css
├── favicon.ico
├── index.html
├── js
├── composite.js
├── libs
│ ├── float16.js
│ └── webgpu-utils.module.js
├── main.js
├── rd-compute.js
└── shader
│ ├── composite-shader.js
│ └── rd-compute-shader.js
└── performance-comparison
├── css
└── demo.css
├── index.html
└── js
├── composite.js
├── libs
├── float16.js
└── webgpu-utils.module.js
├── main.js
├── rd-compute.js
├── rd-fragment.js
├── shader
├── composite-shader.js
├── rd-compute-shader.js
└── rd-fragment-shader.js
└── utils
├── rolling-average.js
└── timing-helper.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache
3 | .parcel-cache
4 | package-lock.json
5 | .idea
6 | .DS_Store
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2009 - 2024 [Codrops](https://tympanus.net/codrops)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WebGPU Reaction-Diffusion Compute Shader Demo
2 |
3 | Exemplary implementation of the reaction-diffusion algorithm using WebGPU compute shader.
4 |
5 | 
6 |
7 | [Article on Codrops](https://tympanus.net/codrops/2024/05/01/reaction-diffusion-compute-shader-in-webgpu/)
8 |
9 | [Demo](https://tympanus.net/Tutorials/WebGPUReactionDiffusion/)
10 |
11 | ## Installation
12 |
13 | Run this demo on a [local server](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Tools_and_setup/set_up_a_local_testing_server).
14 |
15 | ## Credits
16 |
17 | - [webgpu-utils](https://github.com/greggman/webgpu-utils) library by Gregg Tavares
18 | - [float16](https://github.com/petamoriken/float16) library by Kenta Moriuchi
19 |
20 | ## Misc
21 |
22 | Follow Robert Leitl: [Website](https://robert.leitl.dev/), [GitHub](https://github.com/robert-leitl)
23 |
24 | Follow Codrops: [X](http://www.X.com/codrops), [Facebook](http://www.facebook.com/codrops), [GitHub](https://github.com/codrops), [Instagram](https://www.instagram.com/codropsss/)
25 |
26 | [Support us](https://www.buymeacoffee.com/codrops)
27 |
28 | ## License
29 | [MIT](LICENSE)
30 |
31 | Made with :blue_heart: by [Codrops](http://www.codrops.com)
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/assets/syne-mono-subset.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-leitl/webgpu-reaction-diffusion/e331a2ca38c59f686f8b45a42590895bfd054aa4/assets/syne-mono-subset.ttf
--------------------------------------------------------------------------------
/assets/syne-mono-subset.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-leitl/webgpu-reaction-diffusion/e331a2ca38c59f686f8b45a42590895bfd054aa4/assets/syne-mono-subset.woff
--------------------------------------------------------------------------------
/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-leitl/webgpu-reaction-diffusion/e331a2ca38c59f686f8b45a42590895bfd054aa4/cover.jpg
--------------------------------------------------------------------------------
/css/base.css:
--------------------------------------------------------------------------------
1 | *,
2 | *::after,
3 | *::before {
4 | box-sizing: border-box;
5 | }
6 |
7 | :root {
8 | font-size: 12px;
9 | --color-text: #737c94;
10 | --color-bg: #000;
11 | --color-link: #a4aec8;
12 | --color-link-hover: #737c94;
13 | --page-padding: 1.5rem;
14 | }
15 |
16 | body {
17 | margin: 0;
18 | color: var(--color-text);
19 | background-color: var(--color-bg);
20 | font-family: ui-monospace,SFMono-Regular,Menlo,Roboto Mono,monospace;
21 | -webkit-font-smoothing: antialiased;
22 | -moz-osx-font-smoothing: grayscale;
23 | }
24 |
25 | /* Page Loader */
26 | .js .loading::before,
27 | .js .loading::after {
28 | content: '';
29 | position: fixed;
30 | z-index: 1000;
31 | }
32 |
33 | .js .loading::before {
34 | top: 0;
35 | left: 0;
36 | width: 100%;
37 | height: 100%;
38 | background: var(--color-bg);
39 | }
40 |
41 | .js .loading::after {
42 | top: 50%;
43 | left: 50%;
44 | width: 60px;
45 | height: 60px;
46 | margin: -30px 0 0 -30px;
47 | border-radius: 50%;
48 | opacity: 0.4;
49 | background: var(--color-link);
50 | animation: loaderAnim 0.7s linear infinite alternate forwards;
51 |
52 | }
53 |
54 | @keyframes loaderAnim {
55 | to {
56 | opacity: 1;
57 | transform: scale3d(0.5,0.5,1);
58 | }
59 | }
60 |
61 | a {
62 | text-decoration: none;
63 | color: var(--color-link);
64 | outline: none;
65 | cursor: pointer;
66 | }
67 |
68 | a:hover {
69 | text-decoration: underline;
70 | color: var(--color-link-hover);
71 | outline: none;
72 | }
73 |
74 | /* Better focus styles from https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible */
75 | a:focus {
76 | /* Provide a fallback style for browsers
77 | that don't support :focus-visible */
78 | outline: none;
79 | background: lightgrey;
80 | }
81 |
82 | a:focus:not(:focus-visible) {
83 | /* Remove the focus indicator on mouse-focus for browsers
84 | that do support :focus-visible */
85 | background: transparent;
86 | }
87 |
88 | a:focus-visible {
89 | /* Draw a very noticeable focus style for
90 | keyboard-focus on browsers that do support
91 | :focus-visible */
92 | outline: 2px solid red;
93 | background: transparent;
94 | }
95 |
96 | .unbutton {
97 | background: none;
98 | border: 0;
99 | padding: 0;
100 | margin: 0;
101 | font: inherit;
102 | cursor: pointer;
103 | }
104 |
105 | .unbutton:focus {
106 | outline: none;
107 | }
108 |
109 | .frame {
110 | padding: var(--page-padding);
111 | position: relative;
112 | display: grid;
113 | z-index: 1000;
114 | width: 100%;
115 | height: 100%;
116 | grid-row-gap: 1rem;
117 | grid-column-gap: 2rem;
118 | pointer-events: none;
119 | justify-items: start;
120 | grid-template-columns: auto auto;
121 | grid-template-areas: 'title' 'archive' 'back' 'sub' 'sponsor' 'github';
122 | }
123 |
124 | .frame #cdawrap {
125 | justify-self: start;
126 | }
127 |
128 | .frame a {
129 | pointer-events: auto;
130 | }
131 |
132 | .frame__title {
133 | grid-area: title;
134 | font-size: inherit;
135 | margin: 0;
136 | }
137 |
138 | .frame__back {
139 | grid-area: back;
140 | justify-self: start;
141 | }
142 |
143 | .frame__archive {
144 | grid-area: archive;
145 | justify-self: start;
146 | }
147 |
148 | .frame__github {
149 | grid-area: github;
150 | }
151 |
152 | .frame__sub {
153 | grid-area: sub;
154 | }
155 |
156 | .frame__tags {
157 | grid-area: tags;
158 | }
159 |
160 | .frame__hire {
161 | grid-area: hire;
162 | }
163 |
164 | .frame__demos {
165 | grid-area: demos;
166 | display: flex;
167 | gap: 1rem;
168 | }
169 |
170 | .content {
171 | padding: var(--page-padding);
172 | display: flex;
173 | flex-direction: column;
174 | width: 100vw;
175 | position: relative;
176 | }
177 |
178 | @media screen and (min-width: 53em) {
179 | body {
180 | --page-padding: 2rem;
181 | }
182 | .frame {
183 | position: fixed;
184 | top: 0;
185 | left: 0;
186 | width: 100%;
187 | height: 100%;
188 | grid-template-columns: auto auto auto 1fr;
189 | grid-template-rows: auto auto;
190 | align-content: space-between;
191 | grid-template-areas: 'title title sponsor sponsor' 'back archive github sub';
192 | }
193 | .frame #cdawrap, .frame__sub {
194 | justify-self: end;
195 | }
196 | .content {
197 | min-height: 100vh;
198 | justify-content: center;
199 | align-items: center;
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/css/demo.css:
--------------------------------------------------------------------------------
1 | #viewport {
2 | position: fixed;
3 | top: 0;
4 | left: 0;
5 | width: 100%;
6 | height: 100%;
7 | touch-action: none;
8 | font-family: 'Syne Mono', sans-serif;
9 | }
10 |
11 | html, body {
12 | overflow: hidden;
13 | width: 100%;
14 | height: 100%;
15 | }
16 |
17 | #no-webgpu {
18 | width: 100%;
19 | height: 100%;
20 | display: flex;
21 | align-items: center;
22 | justify-content: center;
23 | padding: 2em;
24 | line-height: 1.4;
25 | }
26 |
27 | #no-webgpu p {
28 | display: inline-block;
29 | max-width: 32em;
30 | }
31 |
32 | @font-face {
33 | font-family: "Syne Mono";
34 | src:
35 | url("/assets/font.ttf") format("truetype"),
36 | url("/assets/font.woff") format("woff");
37 | }
38 |
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-leitl/webgpu-reaction-diffusion/e331a2ca38c59f686f8b45a42590895bfd054aa4/favicon.ico
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | WebGPU Reaction Diffusion | Codrops
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
24 |
25 |
26 |
27 | You are using a browser that does not support WebGPU.
28 | Read more about the required steps to run WebGPU here.
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/js/composite.js:
--------------------------------------------------------------------------------
1 | import {CompositeShader} from './shader/composite-shader.js';
2 | import * as wgh from './libs/webgpu-utils.module.js';
3 |
4 | export class Composite {
5 |
6 | constructor(device, reactionDiffusion) {
7 | this.device = device;
8 | this.reactionDiffusion = reactionDiffusion;
9 |
10 | const module = device.createShaderModule({ code: CompositeShader });
11 | const defs = wgh.makeShaderDataDefinitions(CompositeShader);
12 | const pipelineLayout = {
13 | vertex: {
14 | module: module,
15 | entryPoint: 'vertex_main',
16 | },
17 | fragment: {
18 | module,
19 | entryPoint:'frag_main',
20 | targets: [
21 | { format: navigator.gpu.getPreferredCanvasFormat() }
22 | ]
23 | },
24 | primitive: {
25 | topology: 'triangle-list',
26 | },
27 | }
28 | const descriptors = wgh.makeBindGroupLayoutDescriptors(defs, pipelineLayout);
29 | const bindGroupLayout = device.createBindGroupLayout(descriptors[0]);
30 |
31 | // create the animation uniforms view and buffer
32 | const animationUniformView = wgh.makeStructuredView(defs.uniforms.animationUniforms);
33 | this.animationUniform = {
34 | view: animationUniformView,
35 | buffer: this.device.createBuffer({
36 | size: animationUniformView.arrayBuffer.byteLength,
37 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
38 | })
39 | };
40 |
41 | // the sampler for the input texture
42 | this.sampler = device.createSampler({
43 | minFilter: 'linear',
44 | magFilter: 'linear'
45 | });
46 |
47 | // create the pipeline
48 | this.bindGroupLayouts = [bindGroupLayout];
49 | this.pipeline = device.createRenderPipeline({
50 | label: 'composite pipeline',
51 | layout: device.createPipelineLayout({
52 | bindGroupLayouts: [bindGroupLayout],
53 | }),
54 | ...pipelineLayout
55 | });
56 |
57 | // initial resize
58 | this.resize();
59 | }
60 |
61 | resize() {
62 | this.createBindGroups();
63 | }
64 |
65 | render(renderPassEncoder, pulse) {
66 | this.animationUniform.view.set({ pulse });
67 | this.device.queue.writeBuffer(this.animationUniform.buffer, 0, this.animationUniform.view.arrayBuffer);
68 |
69 | renderPassEncoder.setPipeline(this.pipeline);
70 | renderPassEncoder.setBindGroup(0, this.bindGroup);
71 | renderPassEncoder.draw(3);
72 | }
73 |
74 | createBindGroups() {
75 | this.bindGroup = this.device.createBindGroup({
76 | layout: this.bindGroupLayouts[0],
77 | entries: [
78 | { binding: 0, resource: { buffer: this.animationUniform.buffer } },
79 | { binding: 1, resource: this.sampler },
80 | { binding: 2, resource: this.reactionDiffusion.resultStorageTexture.createView() },
81 | ]
82 | });
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/js/libs/float16.js:
--------------------------------------------------------------------------------
1 | /*! @petamoriken/float16 v3.8.6 | MIT License - https://github.com/petamoriken/float16 */
2 |
3 | const THIS_IS_NOT_AN_OBJECT = "This is not an object";
4 | const THIS_IS_NOT_A_FLOAT16ARRAY_OBJECT = "This is not a Float16Array object";
5 | const THIS_CONSTRUCTOR_IS_NOT_A_SUBCLASS_OF_FLOAT16ARRAY =
6 | "This constructor is not a subclass of Float16Array";
7 | const THE_CONSTRUCTOR_PROPERTY_VALUE_IS_NOT_AN_OBJECT =
8 | "The constructor property value is not an object";
9 | const SPECIES_CONSTRUCTOR_DIDNT_RETURN_TYPEDARRAY_OBJECT =
10 | "Species constructor didn't return TypedArray object";
11 | const DERIVED_CONSTRUCTOR_CREATED_TYPEDARRAY_OBJECT_WHICH_WAS_TOO_SMALL_LENGTH =
12 | "Derived constructor created TypedArray object which was too small length";
13 | const ATTEMPTING_TO_ACCESS_DETACHED_ARRAYBUFFER =
14 | "Attempting to access detached ArrayBuffer";
15 | const CANNOT_CONVERT_UNDEFINED_OR_NULL_TO_OBJECT =
16 | "Cannot convert undefined or null to object";
17 | const CANNOT_MIX_BIGINT_AND_OTHER_TYPES =
18 | "Cannot mix BigInt and other types, use explicit conversions";
19 | const ITERATOR_PROPERTY_IS_NOT_CALLABLE = "@@iterator property is not callable";
20 | const REDUCE_OF_EMPTY_ARRAY_WITH_NO_INITIAL_VALUE =
21 | "Reduce of empty array with no initial value";
22 | const THE_COMPARISON_FUNCTION_MUST_BE_EITHER_A_FUNCTION_OR_UNDEFINED =
23 | "The comparison function must be either a function or undefined";
24 | const OFFSET_IS_OUT_OF_BOUNDS = "Offset is out of bounds";
25 |
26 | function uncurryThis(target) {
27 | return (thisArg, ...args) => {
28 | return ReflectApply(target, thisArg, args);
29 | };
30 | }
31 | function uncurryThisGetter(target, key) {
32 | return uncurryThis(
33 | ReflectGetOwnPropertyDescriptor(
34 | target,
35 | key
36 | ).get
37 | );
38 | }
39 | const {
40 | apply: ReflectApply,
41 | construct: ReflectConstruct,
42 | defineProperty: ReflectDefineProperty,
43 | get: ReflectGet,
44 | getOwnPropertyDescriptor: ReflectGetOwnPropertyDescriptor,
45 | getPrototypeOf: ReflectGetPrototypeOf,
46 | has: ReflectHas,
47 | ownKeys: ReflectOwnKeys,
48 | set: ReflectSet,
49 | setPrototypeOf: ReflectSetPrototypeOf,
50 | } = Reflect;
51 | const NativeProxy = Proxy;
52 | const {
53 | EPSILON,
54 | MAX_SAFE_INTEGER,
55 | isFinite: NumberIsFinite,
56 | isNaN: NumberIsNaN,
57 | } = Number;
58 | const {
59 | iterator: SymbolIterator,
60 | species: SymbolSpecies,
61 | toStringTag: SymbolToStringTag,
62 | for: SymbolFor,
63 | } = Symbol;
64 | const NativeObject = Object;
65 | const {
66 | create: ObjectCreate,
67 | defineProperty: ObjectDefineProperty,
68 | freeze: ObjectFreeze,
69 | is: ObjectIs,
70 | } = NativeObject;
71 | const ObjectPrototype = NativeObject.prototype;
72 | const ObjectPrototype__lookupGetter__ = (ObjectPrototype).__lookupGetter__
73 | ? uncurryThis( (ObjectPrototype).__lookupGetter__)
74 | : (object, key) => {
75 | if (object == null) {
76 | throw NativeTypeError(
77 | CANNOT_CONVERT_UNDEFINED_OR_NULL_TO_OBJECT
78 | );
79 | }
80 | let target = NativeObject(object);
81 | do {
82 | const descriptor = ReflectGetOwnPropertyDescriptor(target, key);
83 | if (descriptor !== undefined) {
84 | if (ObjectHasOwn(descriptor, "get")) {
85 | return descriptor.get;
86 | }
87 | return;
88 | }
89 | } while ((target = ReflectGetPrototypeOf(target)) !== null);
90 | };
91 | const ObjectHasOwn = (NativeObject).hasOwn ||
92 | uncurryThis(ObjectPrototype.hasOwnProperty);
93 | const NativeArray = Array;
94 | const ArrayIsArray = NativeArray.isArray;
95 | const ArrayPrototype = NativeArray.prototype;
96 | const ArrayPrototypeJoin = uncurryThis(ArrayPrototype.join);
97 | const ArrayPrototypePush = uncurryThis(ArrayPrototype.push);
98 | const ArrayPrototypeToLocaleString = uncurryThis(
99 | ArrayPrototype.toLocaleString
100 | );
101 | const NativeArrayPrototypeSymbolIterator = ArrayPrototype[SymbolIterator];
102 | const ArrayPrototypeSymbolIterator = uncurryThis(NativeArrayPrototypeSymbolIterator);
103 | const {
104 | abs: MathAbs,
105 | trunc: MathTrunc,
106 | } = Math;
107 | const NativeArrayBuffer = ArrayBuffer;
108 | const ArrayBufferIsView = NativeArrayBuffer.isView;
109 | const ArrayBufferPrototype = NativeArrayBuffer.prototype;
110 | const ArrayBufferPrototypeSlice = uncurryThis(ArrayBufferPrototype.slice);
111 | const ArrayBufferPrototypeGetByteLength = uncurryThisGetter(ArrayBufferPrototype, "byteLength");
112 | const NativeSharedArrayBuffer = typeof SharedArrayBuffer !== "undefined" ? SharedArrayBuffer : null;
113 | const SharedArrayBufferPrototypeGetByteLength = NativeSharedArrayBuffer
114 | && uncurryThisGetter(NativeSharedArrayBuffer.prototype, "byteLength");
115 | const TypedArray = ReflectGetPrototypeOf(Uint8Array);
116 | const TypedArrayFrom = TypedArray.from;
117 | const TypedArrayPrototype = TypedArray.prototype;
118 | const NativeTypedArrayPrototypeSymbolIterator = TypedArrayPrototype[SymbolIterator];
119 | const TypedArrayPrototypeKeys = uncurryThis(TypedArrayPrototype.keys);
120 | const TypedArrayPrototypeValues = uncurryThis(
121 | TypedArrayPrototype.values
122 | );
123 | const TypedArrayPrototypeEntries = uncurryThis(
124 | TypedArrayPrototype.entries
125 | );
126 | const TypedArrayPrototypeSet = uncurryThis(TypedArrayPrototype.set);
127 | const TypedArrayPrototypeReverse = uncurryThis(
128 | TypedArrayPrototype.reverse
129 | );
130 | const TypedArrayPrototypeFill = uncurryThis(TypedArrayPrototype.fill);
131 | const TypedArrayPrototypeCopyWithin = uncurryThis(
132 | TypedArrayPrototype.copyWithin
133 | );
134 | const TypedArrayPrototypeSort = uncurryThis(TypedArrayPrototype.sort);
135 | const TypedArrayPrototypeSlice = uncurryThis(TypedArrayPrototype.slice);
136 | const TypedArrayPrototypeSubarray = uncurryThis(
137 | TypedArrayPrototype.subarray
138 | );
139 | const TypedArrayPrototypeGetBuffer = uncurryThisGetter(
140 | TypedArrayPrototype,
141 | "buffer"
142 | );
143 | const TypedArrayPrototypeGetByteOffset = uncurryThisGetter(
144 | TypedArrayPrototype,
145 | "byteOffset"
146 | );
147 | const TypedArrayPrototypeGetLength = uncurryThisGetter(
148 | TypedArrayPrototype,
149 | "length"
150 | );
151 | const TypedArrayPrototypeGetSymbolToStringTag = uncurryThisGetter(
152 | TypedArrayPrototype,
153 | SymbolToStringTag
154 | );
155 | const NativeUint8Array = Uint8Array;
156 | const NativeUint16Array = Uint16Array;
157 | const Uint16ArrayFrom = (...args) => {
158 | return ReflectApply(TypedArrayFrom, NativeUint16Array, args);
159 | };
160 | const NativeUint32Array = Uint32Array;
161 | const NativeFloat32Array = Float32Array;
162 | const ArrayIteratorPrototype = ReflectGetPrototypeOf([][SymbolIterator]());
163 | const ArrayIteratorPrototypeNext = uncurryThis(ArrayIteratorPrototype.next);
164 | const GeneratorPrototypeNext = uncurryThis((function* () {})().next);
165 | const IteratorPrototype = ReflectGetPrototypeOf(ArrayIteratorPrototype);
166 | const DataViewPrototype = DataView.prototype;
167 | const DataViewPrototypeGetUint16 = uncurryThis(
168 | DataViewPrototype.getUint16
169 | );
170 | const DataViewPrototypeSetUint16 = uncurryThis(
171 | DataViewPrototype.setUint16
172 | );
173 | const NativeTypeError = TypeError;
174 | const NativeRangeError = RangeError;
175 | const NativeWeakSet = WeakSet;
176 | const WeakSetPrototype = NativeWeakSet.prototype;
177 | const WeakSetPrototypeAdd = uncurryThis(WeakSetPrototype.add);
178 | const WeakSetPrototypeHas = uncurryThis(WeakSetPrototype.has);
179 | const NativeWeakMap = WeakMap;
180 | const WeakMapPrototype = NativeWeakMap.prototype;
181 | const WeakMapPrototypeGet = uncurryThis(WeakMapPrototype.get);
182 | const WeakMapPrototypeHas = uncurryThis(WeakMapPrototype.has);
183 | const WeakMapPrototypeSet = uncurryThis(WeakMapPrototype.set);
184 |
185 | const arrayIterators = new NativeWeakMap();
186 | const SafeIteratorPrototype = ObjectCreate(null, {
187 | next: {
188 | value: function next() {
189 | const arrayIterator = WeakMapPrototypeGet(arrayIterators, this);
190 | return ArrayIteratorPrototypeNext(arrayIterator);
191 | },
192 | },
193 | [SymbolIterator]: {
194 | value: function values() {
195 | return this;
196 | },
197 | },
198 | });
199 | function safeIfNeeded(array) {
200 | if (
201 | array[SymbolIterator] === NativeArrayPrototypeSymbolIterator &&
202 | ArrayIteratorPrototype.next === ArrayIteratorPrototypeNext
203 | ) {
204 | return array;
205 | }
206 | const safe = ObjectCreate(SafeIteratorPrototype);
207 | WeakMapPrototypeSet(arrayIterators, safe, ArrayPrototypeSymbolIterator(array));
208 | return safe;
209 | }
210 | const generators = new NativeWeakMap();
211 | const DummyArrayIteratorPrototype = ObjectCreate(IteratorPrototype, {
212 | next: {
213 | value: function next() {
214 | const generator = WeakMapPrototypeGet(generators, this);
215 | return GeneratorPrototypeNext(generator);
216 | },
217 | writable: true,
218 | configurable: true,
219 | },
220 | });
221 | for (const key of ReflectOwnKeys(ArrayIteratorPrototype)) {
222 | if (key === "next") {
223 | continue;
224 | }
225 | ObjectDefineProperty(DummyArrayIteratorPrototype, key, ReflectGetOwnPropertyDescriptor(ArrayIteratorPrototype, key));
226 | }
227 | function wrap(generator) {
228 | const dummy = ObjectCreate(DummyArrayIteratorPrototype);
229 | WeakMapPrototypeSet(generators, dummy, generator);
230 | return dummy;
231 | }
232 |
233 | function isObject(value) {
234 | return (
235 | (value !== null && typeof value === "object") ||
236 | typeof value === "function"
237 | );
238 | }
239 | function isObjectLike(value) {
240 | return value !== null && typeof value === "object";
241 | }
242 | function isNativeTypedArray(value) {
243 | return TypedArrayPrototypeGetSymbolToStringTag(value) !== undefined;
244 | }
245 | function isNativeBigIntTypedArray(value) {
246 | const typedArrayName = TypedArrayPrototypeGetSymbolToStringTag(value);
247 | return (
248 | typedArrayName === "BigInt64Array" ||
249 | typedArrayName === "BigUint64Array"
250 | );
251 | }
252 | function isArrayBuffer(value) {
253 | try {
254 | if (ArrayIsArray(value)) {
255 | return false;
256 | }
257 | ArrayBufferPrototypeGetByteLength( (value));
258 | return true;
259 | } catch (e) {
260 | return false;
261 | }
262 | }
263 | function isSharedArrayBuffer(value) {
264 | if (NativeSharedArrayBuffer === null) {
265 | return false;
266 | }
267 | try {
268 | SharedArrayBufferPrototypeGetByteLength( (value));
269 | return true;
270 | } catch (e) {
271 | return false;
272 | }
273 | }
274 | function isAnyArrayBuffer(value) {
275 | return isArrayBuffer(value) || isSharedArrayBuffer(value);
276 | }
277 | function isOrdinaryArray(value) {
278 | if (!ArrayIsArray(value)) {
279 | return false;
280 | }
281 | return (
282 | value[SymbolIterator] === NativeArrayPrototypeSymbolIterator &&
283 | ArrayIteratorPrototype.next === ArrayIteratorPrototypeNext
284 | );
285 | }
286 | function isOrdinaryNativeTypedArray(value) {
287 | if (!isNativeTypedArray(value)) {
288 | return false;
289 | }
290 | return (
291 | value[SymbolIterator] === NativeTypedArrayPrototypeSymbolIterator &&
292 | ArrayIteratorPrototype.next === ArrayIteratorPrototypeNext
293 | );
294 | }
295 | function isCanonicalIntegerIndexString(value) {
296 | if (typeof value !== "string") {
297 | return false;
298 | }
299 | const number = +value;
300 | if (value !== number + "") {
301 | return false;
302 | }
303 | if (!NumberIsFinite(number)) {
304 | return false;
305 | }
306 | return number === MathTrunc(number);
307 | }
308 |
309 | const brand = SymbolFor("__Float16Array__");
310 | function hasFloat16ArrayBrand(target) {
311 | if (!isObjectLike(target)) {
312 | return false;
313 | }
314 | const prototype = ReflectGetPrototypeOf(target);
315 | if (!isObjectLike(prototype)) {
316 | return false;
317 | }
318 | const constructor = prototype.constructor;
319 | if (constructor === undefined) {
320 | return false;
321 | }
322 | if (!isObject(constructor)) {
323 | throw NativeTypeError(THE_CONSTRUCTOR_PROPERTY_VALUE_IS_NOT_AN_OBJECT);
324 | }
325 | return ReflectHas(constructor, brand);
326 | }
327 |
328 | const INVERSE_OF_EPSILON = 1 / EPSILON;
329 | function roundTiesToEven(num) {
330 | return (num + INVERSE_OF_EPSILON) - INVERSE_OF_EPSILON;
331 | }
332 | const FLOAT16_MIN_VALUE = 6.103515625e-05;
333 | const FLOAT16_MAX_VALUE = 65504;
334 | const FLOAT16_EPSILON = 0.0009765625;
335 | const FLOAT16_EPSILON_MULTIPLIED_BY_FLOAT16_MIN_VALUE = FLOAT16_EPSILON * FLOAT16_MIN_VALUE;
336 | const FLOAT16_EPSILON_DEVIDED_BY_EPSILON = FLOAT16_EPSILON * INVERSE_OF_EPSILON;
337 | function roundToFloat16(num) {
338 | const number = +num;
339 | if (!NumberIsFinite(number) || number === 0) {
340 | return number;
341 | }
342 | const sign = number > 0 ? 1 : -1;
343 | const absolute = MathAbs(number);
344 | if (absolute < FLOAT16_MIN_VALUE) {
345 | return sign * roundTiesToEven(absolute / FLOAT16_EPSILON_MULTIPLIED_BY_FLOAT16_MIN_VALUE) * FLOAT16_EPSILON_MULTIPLIED_BY_FLOAT16_MIN_VALUE;
346 | }
347 | const temp = (1 + FLOAT16_EPSILON_DEVIDED_BY_EPSILON) * absolute;
348 | const result = temp - (temp - absolute);
349 | if (result > FLOAT16_MAX_VALUE || NumberIsNaN(result)) {
350 | return sign * Infinity;
351 | }
352 | return sign * result;
353 | }
354 | const buffer = new NativeArrayBuffer(4);
355 | const floatView = new NativeFloat32Array(buffer);
356 | const uint32View = new NativeUint32Array(buffer);
357 | const baseTable = new NativeUint16Array(512);
358 | const shiftTable = new NativeUint8Array(512);
359 | for (let i = 0; i < 256; ++i) {
360 | const e = i - 127;
361 | if (e < -27) {
362 | baseTable[i] = 0x0000;
363 | baseTable[i | 0x100] = 0x8000;
364 | shiftTable[i] = 24;
365 | shiftTable[i | 0x100] = 24;
366 | } else if (e < -14) {
367 | baseTable[i] = 0x0400 >> (-e - 14);
368 | baseTable[i | 0x100] = (0x0400 >> (-e - 14)) | 0x8000;
369 | shiftTable[i] = -e - 1;
370 | shiftTable[i | 0x100] = -e - 1;
371 | } else if (e <= 15) {
372 | baseTable[i] = (e + 15) << 10;
373 | baseTable[i | 0x100] = ((e + 15) << 10) | 0x8000;
374 | shiftTable[i] = 13;
375 | shiftTable[i | 0x100] = 13;
376 | } else if (e < 128) {
377 | baseTable[i] = 0x7c00;
378 | baseTable[i | 0x100] = 0xfc00;
379 | shiftTable[i] = 24;
380 | shiftTable[i | 0x100] = 24;
381 | } else {
382 | baseTable[i] = 0x7c00;
383 | baseTable[i | 0x100] = 0xfc00;
384 | shiftTable[i] = 13;
385 | shiftTable[i | 0x100] = 13;
386 | }
387 | }
388 | function roundToFloat16Bits(num) {
389 | floatView[0] = roundToFloat16(num);
390 | const f = uint32View[0];
391 | const e = (f >> 23) & 0x1ff;
392 | return baseTable[e] + ((f & 0x007fffff) >> shiftTable[e]);
393 | }
394 | const mantissaTable = new NativeUint32Array(2048);
395 | for (let i = 1; i < 1024; ++i) {
396 | let m = i << 13;
397 | let e = 0;
398 | while ((m & 0x00800000) === 0) {
399 | m <<= 1;
400 | e -= 0x00800000;
401 | }
402 | m &= ~0x00800000;
403 | e += 0x38800000;
404 | mantissaTable[i] = m | e;
405 | }
406 | for (let i = 1024; i < 2048; ++i) {
407 | mantissaTable[i] = 0x38000000 + ((i - 1024) << 13);
408 | }
409 | const exponentTable = new NativeUint32Array(64);
410 | for (let i = 1; i < 31; ++i) {
411 | exponentTable[i] = i << 23;
412 | }
413 | exponentTable[31] = 0x47800000;
414 | exponentTable[32] = 0x80000000;
415 | for (let i = 33; i < 63; ++i) {
416 | exponentTable[i] = 0x80000000 + ((i - 32) << 23);
417 | }
418 | exponentTable[63] = 0xc7800000;
419 | const offsetTable = new NativeUint16Array(64);
420 | for (let i = 1; i < 64; ++i) {
421 | if (i !== 32) {
422 | offsetTable[i] = 1024;
423 | }
424 | }
425 | function convertToNumber(float16bits) {
426 | const i = float16bits >> 10;
427 | uint32View[0] = mantissaTable[offsetTable[i] + (float16bits & 0x3ff)] + exponentTable[i];
428 | return floatView[0];
429 | }
430 |
431 | function ToIntegerOrInfinity(target) {
432 | const number = +target;
433 | if (NumberIsNaN(number) || number === 0) {
434 | return 0;
435 | }
436 | return MathTrunc(number);
437 | }
438 | function ToLength(target) {
439 | const length = ToIntegerOrInfinity(target);
440 | if (length < 0) {
441 | return 0;
442 | }
443 | return length < MAX_SAFE_INTEGER
444 | ? length
445 | : MAX_SAFE_INTEGER;
446 | }
447 | function SpeciesConstructor(target, defaultConstructor) {
448 | if (!isObject(target)) {
449 | throw NativeTypeError(THIS_IS_NOT_AN_OBJECT);
450 | }
451 | const constructor = target.constructor;
452 | if (constructor === undefined) {
453 | return defaultConstructor;
454 | }
455 | if (!isObject(constructor)) {
456 | throw NativeTypeError(THE_CONSTRUCTOR_PROPERTY_VALUE_IS_NOT_AN_OBJECT);
457 | }
458 | const species = constructor[SymbolSpecies];
459 | if (species == null) {
460 | return defaultConstructor;
461 | }
462 | return species;
463 | }
464 | function IsDetachedBuffer(buffer) {
465 | if (isSharedArrayBuffer(buffer)) {
466 | return false;
467 | }
468 | try {
469 | ArrayBufferPrototypeSlice(buffer, 0, 0);
470 | return false;
471 | } catch (e) {}
472 | return true;
473 | }
474 | function defaultCompare(x, y) {
475 | const isXNaN = NumberIsNaN(x);
476 | const isYNaN = NumberIsNaN(y);
477 | if (isXNaN && isYNaN) {
478 | return 0;
479 | }
480 | if (isXNaN) {
481 | return 1;
482 | }
483 | if (isYNaN) {
484 | return -1;
485 | }
486 | if (x < y) {
487 | return -1;
488 | }
489 | if (x > y) {
490 | return 1;
491 | }
492 | if (x === 0 && y === 0) {
493 | const isXPlusZero = ObjectIs(x, 0);
494 | const isYPlusZero = ObjectIs(y, 0);
495 | if (!isXPlusZero && isYPlusZero) {
496 | return -1;
497 | }
498 | if (isXPlusZero && !isYPlusZero) {
499 | return 1;
500 | }
501 | }
502 | return 0;
503 | }
504 |
505 | const BYTES_PER_ELEMENT = 2;
506 | const float16bitsArrays = new NativeWeakMap();
507 | function isFloat16Array(target) {
508 | return WeakMapPrototypeHas(float16bitsArrays, target) ||
509 | (!ArrayBufferIsView(target) && hasFloat16ArrayBrand(target));
510 | }
511 | function assertFloat16Array(target) {
512 | if (!isFloat16Array(target)) {
513 | throw NativeTypeError(THIS_IS_NOT_A_FLOAT16ARRAY_OBJECT);
514 | }
515 | }
516 | function assertSpeciesTypedArray(target, count) {
517 | const isTargetFloat16Array = isFloat16Array(target);
518 | const isTargetTypedArray = isNativeTypedArray(target);
519 | if (!isTargetFloat16Array && !isTargetTypedArray) {
520 | throw NativeTypeError(SPECIES_CONSTRUCTOR_DIDNT_RETURN_TYPEDARRAY_OBJECT);
521 | }
522 | if (typeof count === "number") {
523 | let length;
524 | if (isTargetFloat16Array) {
525 | const float16bitsArray = getFloat16BitsArray(target);
526 | length = TypedArrayPrototypeGetLength(float16bitsArray);
527 | } else {
528 | length = TypedArrayPrototypeGetLength(target);
529 | }
530 | if (length < count) {
531 | throw NativeTypeError(
532 | DERIVED_CONSTRUCTOR_CREATED_TYPEDARRAY_OBJECT_WHICH_WAS_TOO_SMALL_LENGTH
533 | );
534 | }
535 | }
536 | if (isNativeBigIntTypedArray(target)) {
537 | throw NativeTypeError(CANNOT_MIX_BIGINT_AND_OTHER_TYPES);
538 | }
539 | }
540 | function getFloat16BitsArray(float16) {
541 | const float16bitsArray = WeakMapPrototypeGet(float16bitsArrays, float16);
542 | if (float16bitsArray !== undefined) {
543 | const buffer = TypedArrayPrototypeGetBuffer(float16bitsArray);
544 | if (IsDetachedBuffer(buffer)) {
545 | throw NativeTypeError(ATTEMPTING_TO_ACCESS_DETACHED_ARRAYBUFFER);
546 | }
547 | return float16bitsArray;
548 | }
549 | const buffer = (float16).buffer;
550 | if (IsDetachedBuffer(buffer)) {
551 | throw NativeTypeError(ATTEMPTING_TO_ACCESS_DETACHED_ARRAYBUFFER);
552 | }
553 | const cloned = ReflectConstruct(Float16Array, [
554 | buffer,
555 | (float16).byteOffset,
556 | (float16).length,
557 | ], float16.constructor);
558 | return WeakMapPrototypeGet(float16bitsArrays, cloned);
559 | }
560 | function copyToArray(float16bitsArray) {
561 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
562 | const array = [];
563 | for (let i = 0; i < length; ++i) {
564 | array[i] = convertToNumber(float16bitsArray[i]);
565 | }
566 | return array;
567 | }
568 | const TypedArrayPrototypeGetters = new NativeWeakSet();
569 | for (const key of ReflectOwnKeys(TypedArrayPrototype)) {
570 | if (key === SymbolToStringTag) {
571 | continue;
572 | }
573 | const descriptor = ReflectGetOwnPropertyDescriptor(TypedArrayPrototype, key);
574 | if (ObjectHasOwn(descriptor, "get") && typeof descriptor.get === "function") {
575 | WeakSetPrototypeAdd(TypedArrayPrototypeGetters, descriptor.get);
576 | }
577 | }
578 | const handler = ObjectFreeze( ({
579 | get(target, key, receiver) {
580 | if (isCanonicalIntegerIndexString(key) && ObjectHasOwn(target, key)) {
581 | return convertToNumber(ReflectGet(target, key));
582 | }
583 | if (WeakSetPrototypeHas(TypedArrayPrototypeGetters, ObjectPrototype__lookupGetter__(target, key))) {
584 | return ReflectGet(target, key);
585 | }
586 | return ReflectGet(target, key, receiver);
587 | },
588 | set(target, key, value, receiver) {
589 | if (isCanonicalIntegerIndexString(key) && ObjectHasOwn(target, key)) {
590 | return ReflectSet(target, key, roundToFloat16Bits(value));
591 | }
592 | return ReflectSet(target, key, value, receiver);
593 | },
594 | getOwnPropertyDescriptor(target, key) {
595 | if (isCanonicalIntegerIndexString(key) && ObjectHasOwn(target, key)) {
596 | const descriptor = ReflectGetOwnPropertyDescriptor(target, key);
597 | descriptor.value = convertToNumber(descriptor.value);
598 | return descriptor;
599 | }
600 | return ReflectGetOwnPropertyDescriptor(target, key);
601 | },
602 | defineProperty(target, key, descriptor) {
603 | if (
604 | isCanonicalIntegerIndexString(key) &&
605 | ObjectHasOwn(target, key) &&
606 | ObjectHasOwn(descriptor, "value")
607 | ) {
608 | descriptor.value = roundToFloat16Bits(descriptor.value);
609 | return ReflectDefineProperty(target, key, descriptor);
610 | }
611 | return ReflectDefineProperty(target, key, descriptor);
612 | },
613 | }));
614 | class Float16Array {
615 | constructor(input, _byteOffset, _length) {
616 | let float16bitsArray;
617 | if (isFloat16Array(input)) {
618 | float16bitsArray = ReflectConstruct(NativeUint16Array, [getFloat16BitsArray(input)], new.target);
619 | } else if (isObject(input) && !isAnyArrayBuffer(input)) {
620 | let list;
621 | let length;
622 | if (isNativeTypedArray(input)) {
623 | list = input;
624 | length = TypedArrayPrototypeGetLength(input);
625 | const buffer = TypedArrayPrototypeGetBuffer(input);
626 | if (IsDetachedBuffer(buffer)) {
627 | throw NativeTypeError(ATTEMPTING_TO_ACCESS_DETACHED_ARRAYBUFFER);
628 | }
629 | if (isNativeBigIntTypedArray(input)) {
630 | throw NativeTypeError(CANNOT_MIX_BIGINT_AND_OTHER_TYPES);
631 | }
632 | const data = new NativeArrayBuffer(
633 | length * BYTES_PER_ELEMENT
634 | );
635 | float16bitsArray = ReflectConstruct(NativeUint16Array, [data], new.target);
636 | } else {
637 | const iterator = input[SymbolIterator];
638 | if (iterator != null && typeof iterator !== "function") {
639 | throw NativeTypeError(ITERATOR_PROPERTY_IS_NOT_CALLABLE);
640 | }
641 | if (iterator != null) {
642 | if (isOrdinaryArray(input)) {
643 | list = input;
644 | length = input.length;
645 | } else {
646 | list = [... (input)];
647 | length = list.length;
648 | }
649 | } else {
650 | list = (input);
651 | length = ToLength(list.length);
652 | }
653 | float16bitsArray = ReflectConstruct(NativeUint16Array, [length], new.target);
654 | }
655 | for (let i = 0; i < length; ++i) {
656 | float16bitsArray[i] = roundToFloat16Bits(list[i]);
657 | }
658 | } else {
659 | float16bitsArray = ReflectConstruct(NativeUint16Array, arguments, new.target);
660 | }
661 | const proxy = (new NativeProxy(float16bitsArray, handler));
662 | WeakMapPrototypeSet(float16bitsArrays, proxy, float16bitsArray);
663 | return proxy;
664 | }
665 | static from(src, ...opts) {
666 | const Constructor = this;
667 | if (!ReflectHas(Constructor, brand)) {
668 | throw NativeTypeError(
669 | THIS_CONSTRUCTOR_IS_NOT_A_SUBCLASS_OF_FLOAT16ARRAY
670 | );
671 | }
672 | if (Constructor === Float16Array) {
673 | if (isFloat16Array(src) && opts.length === 0) {
674 | const float16bitsArray = getFloat16BitsArray(src);
675 | const uint16 = new NativeUint16Array(
676 | TypedArrayPrototypeGetBuffer(float16bitsArray),
677 | TypedArrayPrototypeGetByteOffset(float16bitsArray),
678 | TypedArrayPrototypeGetLength(float16bitsArray)
679 | );
680 | return new Float16Array(
681 | TypedArrayPrototypeGetBuffer(TypedArrayPrototypeSlice(uint16))
682 | );
683 | }
684 | if (opts.length === 0) {
685 | return new Float16Array(
686 | TypedArrayPrototypeGetBuffer(
687 | Uint16ArrayFrom(src, roundToFloat16Bits)
688 | )
689 | );
690 | }
691 | const mapFunc = opts[0];
692 | const thisArg = opts[1];
693 | return new Float16Array(
694 | TypedArrayPrototypeGetBuffer(
695 | Uint16ArrayFrom(src, function (val, ...args) {
696 | return roundToFloat16Bits(
697 | ReflectApply(mapFunc, this, [val, ...safeIfNeeded(args)])
698 | );
699 | }, thisArg)
700 | )
701 | );
702 | }
703 | let list;
704 | let length;
705 | const iterator = src[SymbolIterator];
706 | if (iterator != null && typeof iterator !== "function") {
707 | throw NativeTypeError(ITERATOR_PROPERTY_IS_NOT_CALLABLE);
708 | }
709 | if (iterator != null) {
710 | if (isOrdinaryArray(src)) {
711 | list = src;
712 | length = src.length;
713 | } else if (isOrdinaryNativeTypedArray(src)) {
714 | list = src;
715 | length = TypedArrayPrototypeGetLength(src);
716 | } else {
717 | list = [...src];
718 | length = list.length;
719 | }
720 | } else {
721 | if (src == null) {
722 | throw NativeTypeError(
723 | CANNOT_CONVERT_UNDEFINED_OR_NULL_TO_OBJECT
724 | );
725 | }
726 | list = NativeObject(src);
727 | length = ToLength(list.length);
728 | }
729 | const array = new Constructor(length);
730 | if (opts.length === 0) {
731 | for (let i = 0; i < length; ++i) {
732 | array[i] = (list[i]);
733 | }
734 | } else {
735 | const mapFunc = opts[0];
736 | const thisArg = opts[1];
737 | for (let i = 0; i < length; ++i) {
738 | array[i] = ReflectApply(mapFunc, thisArg, [list[i], i]);
739 | }
740 | }
741 | return array;
742 | }
743 | static of(...items) {
744 | const Constructor = this;
745 | if (!ReflectHas(Constructor, brand)) {
746 | throw NativeTypeError(
747 | THIS_CONSTRUCTOR_IS_NOT_A_SUBCLASS_OF_FLOAT16ARRAY
748 | );
749 | }
750 | const length = items.length;
751 | if (Constructor === Float16Array) {
752 | const proxy = new Float16Array(length);
753 | const float16bitsArray = getFloat16BitsArray(proxy);
754 | for (let i = 0; i < length; ++i) {
755 | float16bitsArray[i] = roundToFloat16Bits(items[i]);
756 | }
757 | return proxy;
758 | }
759 | const array = new Constructor(length);
760 | for (let i = 0; i < length; ++i) {
761 | array[i] = items[i];
762 | }
763 | return array;
764 | }
765 | keys() {
766 | assertFloat16Array(this);
767 | const float16bitsArray = getFloat16BitsArray(this);
768 | return TypedArrayPrototypeKeys(float16bitsArray);
769 | }
770 | values() {
771 | assertFloat16Array(this);
772 | const float16bitsArray = getFloat16BitsArray(this);
773 | return wrap((function* () {
774 | for (const val of TypedArrayPrototypeValues(float16bitsArray)) {
775 | yield convertToNumber(val);
776 | }
777 | })());
778 | }
779 | entries() {
780 | assertFloat16Array(this);
781 | const float16bitsArray = getFloat16BitsArray(this);
782 | return wrap((function* () {
783 | for (const [i, val] of TypedArrayPrototypeEntries(float16bitsArray)) {
784 | yield ([i, convertToNumber(val)]);
785 | }
786 | })());
787 | }
788 | at(index) {
789 | assertFloat16Array(this);
790 | const float16bitsArray = getFloat16BitsArray(this);
791 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
792 | const relativeIndex = ToIntegerOrInfinity(index);
793 | const k = relativeIndex >= 0 ? relativeIndex : length + relativeIndex;
794 | if (k < 0 || k >= length) {
795 | return;
796 | }
797 | return convertToNumber(float16bitsArray[k]);
798 | }
799 | with(index, value) {
800 | assertFloat16Array(this);
801 | const float16bitsArray = getFloat16BitsArray(this);
802 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
803 | const relativeIndex = ToIntegerOrInfinity(index);
804 | const k = relativeIndex >= 0 ? relativeIndex : length + relativeIndex;
805 | const number = +value;
806 | if (k < 0 || k >= length) {
807 | throw NativeRangeError(OFFSET_IS_OUT_OF_BOUNDS);
808 | }
809 | const uint16 = new NativeUint16Array(
810 | TypedArrayPrototypeGetBuffer(float16bitsArray),
811 | TypedArrayPrototypeGetByteOffset(float16bitsArray),
812 | TypedArrayPrototypeGetLength(float16bitsArray)
813 | );
814 | const cloned = new Float16Array(
815 | TypedArrayPrototypeGetBuffer(
816 | TypedArrayPrototypeSlice(uint16)
817 | )
818 | );
819 | const array = getFloat16BitsArray(cloned);
820 | array[k] = roundToFloat16Bits(number);
821 | return cloned;
822 | }
823 | map(callback, ...opts) {
824 | assertFloat16Array(this);
825 | const float16bitsArray = getFloat16BitsArray(this);
826 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
827 | const thisArg = opts[0];
828 | const Constructor = SpeciesConstructor(float16bitsArray, Float16Array);
829 | if (Constructor === Float16Array) {
830 | const proxy = new Float16Array(length);
831 | const array = getFloat16BitsArray(proxy);
832 | for (let i = 0; i < length; ++i) {
833 | const val = convertToNumber(float16bitsArray[i]);
834 | array[i] = roundToFloat16Bits(
835 | ReflectApply(callback, thisArg, [val, i, this])
836 | );
837 | }
838 | return proxy;
839 | }
840 | const array = new Constructor(length);
841 | assertSpeciesTypedArray(array, length);
842 | for (let i = 0; i < length; ++i) {
843 | const val = convertToNumber(float16bitsArray[i]);
844 | array[i] = ReflectApply(callback, thisArg, [val, i, this]);
845 | }
846 | return (array);
847 | }
848 | filter(callback, ...opts) {
849 | assertFloat16Array(this);
850 | const float16bitsArray = getFloat16BitsArray(this);
851 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
852 | const thisArg = opts[0];
853 | const kept = [];
854 | for (let i = 0; i < length; ++i) {
855 | const val = convertToNumber(float16bitsArray[i]);
856 | if (ReflectApply(callback, thisArg, [val, i, this])) {
857 | ArrayPrototypePush(kept, val);
858 | }
859 | }
860 | const Constructor = SpeciesConstructor(float16bitsArray, Float16Array);
861 | const array = new Constructor(kept);
862 | assertSpeciesTypedArray(array);
863 | return (array);
864 | }
865 | reduce(callback, ...opts) {
866 | assertFloat16Array(this);
867 | const float16bitsArray = getFloat16BitsArray(this);
868 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
869 | if (length === 0 && opts.length === 0) {
870 | throw NativeTypeError(REDUCE_OF_EMPTY_ARRAY_WITH_NO_INITIAL_VALUE);
871 | }
872 | let accumulator, start;
873 | if (opts.length === 0) {
874 | accumulator = convertToNumber(float16bitsArray[0]);
875 | start = 1;
876 | } else {
877 | accumulator = opts[0];
878 | start = 0;
879 | }
880 | for (let i = start; i < length; ++i) {
881 | accumulator = callback(
882 | accumulator,
883 | convertToNumber(float16bitsArray[i]),
884 | i,
885 | this
886 | );
887 | }
888 | return accumulator;
889 | }
890 | reduceRight(callback, ...opts) {
891 | assertFloat16Array(this);
892 | const float16bitsArray = getFloat16BitsArray(this);
893 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
894 | if (length === 0 && opts.length === 0) {
895 | throw NativeTypeError(REDUCE_OF_EMPTY_ARRAY_WITH_NO_INITIAL_VALUE);
896 | }
897 | let accumulator, start;
898 | if (opts.length === 0) {
899 | accumulator = convertToNumber(float16bitsArray[length - 1]);
900 | start = length - 2;
901 | } else {
902 | accumulator = opts[0];
903 | start = length - 1;
904 | }
905 | for (let i = start; i >= 0; --i) {
906 | accumulator = callback(
907 | accumulator,
908 | convertToNumber(float16bitsArray[i]),
909 | i,
910 | this
911 | );
912 | }
913 | return accumulator;
914 | }
915 | forEach(callback, ...opts) {
916 | assertFloat16Array(this);
917 | const float16bitsArray = getFloat16BitsArray(this);
918 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
919 | const thisArg = opts[0];
920 | for (let i = 0; i < length; ++i) {
921 | ReflectApply(callback, thisArg, [
922 | convertToNumber(float16bitsArray[i]),
923 | i,
924 | this,
925 | ]);
926 | }
927 | }
928 | find(callback, ...opts) {
929 | assertFloat16Array(this);
930 | const float16bitsArray = getFloat16BitsArray(this);
931 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
932 | const thisArg = opts[0];
933 | for (let i = 0; i < length; ++i) {
934 | const value = convertToNumber(float16bitsArray[i]);
935 | if (ReflectApply(callback, thisArg, [value, i, this])) {
936 | return value;
937 | }
938 | }
939 | }
940 | findIndex(callback, ...opts) {
941 | assertFloat16Array(this);
942 | const float16bitsArray = getFloat16BitsArray(this);
943 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
944 | const thisArg = opts[0];
945 | for (let i = 0; i < length; ++i) {
946 | const value = convertToNumber(float16bitsArray[i]);
947 | if (ReflectApply(callback, thisArg, [value, i, this])) {
948 | return i;
949 | }
950 | }
951 | return -1;
952 | }
953 | findLast(callback, ...opts) {
954 | assertFloat16Array(this);
955 | const float16bitsArray = getFloat16BitsArray(this);
956 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
957 | const thisArg = opts[0];
958 | for (let i = length - 1; i >= 0; --i) {
959 | const value = convertToNumber(float16bitsArray[i]);
960 | if (ReflectApply(callback, thisArg, [value, i, this])) {
961 | return value;
962 | }
963 | }
964 | }
965 | findLastIndex(callback, ...opts) {
966 | assertFloat16Array(this);
967 | const float16bitsArray = getFloat16BitsArray(this);
968 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
969 | const thisArg = opts[0];
970 | for (let i = length - 1; i >= 0; --i) {
971 | const value = convertToNumber(float16bitsArray[i]);
972 | if (ReflectApply(callback, thisArg, [value, i, this])) {
973 | return i;
974 | }
975 | }
976 | return -1;
977 | }
978 | every(callback, ...opts) {
979 | assertFloat16Array(this);
980 | const float16bitsArray = getFloat16BitsArray(this);
981 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
982 | const thisArg = opts[0];
983 | for (let i = 0; i < length; ++i) {
984 | if (
985 | !ReflectApply(callback, thisArg, [
986 | convertToNumber(float16bitsArray[i]),
987 | i,
988 | this,
989 | ])
990 | ) {
991 | return false;
992 | }
993 | }
994 | return true;
995 | }
996 | some(callback, ...opts) {
997 | assertFloat16Array(this);
998 | const float16bitsArray = getFloat16BitsArray(this);
999 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
1000 | const thisArg = opts[0];
1001 | for (let i = 0; i < length; ++i) {
1002 | if (
1003 | ReflectApply(callback, thisArg, [
1004 | convertToNumber(float16bitsArray[i]),
1005 | i,
1006 | this,
1007 | ])
1008 | ) {
1009 | return true;
1010 | }
1011 | }
1012 | return false;
1013 | }
1014 | set(input, ...opts) {
1015 | assertFloat16Array(this);
1016 | const float16bitsArray = getFloat16BitsArray(this);
1017 | const targetOffset = ToIntegerOrInfinity(opts[0]);
1018 | if (targetOffset < 0) {
1019 | throw NativeRangeError(OFFSET_IS_OUT_OF_BOUNDS);
1020 | }
1021 | if (input == null) {
1022 | throw NativeTypeError(
1023 | CANNOT_CONVERT_UNDEFINED_OR_NULL_TO_OBJECT
1024 | );
1025 | }
1026 | if (isNativeBigIntTypedArray(input)) {
1027 | throw NativeTypeError(
1028 | CANNOT_MIX_BIGINT_AND_OTHER_TYPES
1029 | );
1030 | }
1031 | if (isFloat16Array(input)) {
1032 | return TypedArrayPrototypeSet(
1033 | getFloat16BitsArray(this),
1034 | getFloat16BitsArray(input),
1035 | targetOffset
1036 | );
1037 | }
1038 | if (isNativeTypedArray(input)) {
1039 | const buffer = TypedArrayPrototypeGetBuffer(input);
1040 | if (IsDetachedBuffer(buffer)) {
1041 | throw NativeTypeError(ATTEMPTING_TO_ACCESS_DETACHED_ARRAYBUFFER);
1042 | }
1043 | }
1044 | const targetLength = TypedArrayPrototypeGetLength(float16bitsArray);
1045 | const src = NativeObject(input);
1046 | const srcLength = ToLength(src.length);
1047 | if (targetOffset === Infinity || srcLength + targetOffset > targetLength) {
1048 | throw NativeRangeError(OFFSET_IS_OUT_OF_BOUNDS);
1049 | }
1050 | for (let i = 0; i < srcLength; ++i) {
1051 | float16bitsArray[i + targetOffset] = roundToFloat16Bits(src[i]);
1052 | }
1053 | }
1054 | reverse() {
1055 | assertFloat16Array(this);
1056 | const float16bitsArray = getFloat16BitsArray(this);
1057 | TypedArrayPrototypeReverse(float16bitsArray);
1058 | return this;
1059 | }
1060 | toReversed() {
1061 | assertFloat16Array(this);
1062 | const float16bitsArray = getFloat16BitsArray(this);
1063 | const uint16 = new NativeUint16Array(
1064 | TypedArrayPrototypeGetBuffer(float16bitsArray),
1065 | TypedArrayPrototypeGetByteOffset(float16bitsArray),
1066 | TypedArrayPrototypeGetLength(float16bitsArray)
1067 | );
1068 | const cloned = new Float16Array(
1069 | TypedArrayPrototypeGetBuffer(
1070 | TypedArrayPrototypeSlice(uint16)
1071 | )
1072 | );
1073 | const clonedFloat16bitsArray = getFloat16BitsArray(cloned);
1074 | TypedArrayPrototypeReverse(clonedFloat16bitsArray);
1075 | return cloned;
1076 | }
1077 | fill(value, ...opts) {
1078 | assertFloat16Array(this);
1079 | const float16bitsArray = getFloat16BitsArray(this);
1080 | TypedArrayPrototypeFill(
1081 | float16bitsArray,
1082 | roundToFloat16Bits(value),
1083 | ...safeIfNeeded(opts)
1084 | );
1085 | return this;
1086 | }
1087 | copyWithin(target, start, ...opts) {
1088 | assertFloat16Array(this);
1089 | const float16bitsArray = getFloat16BitsArray(this);
1090 | TypedArrayPrototypeCopyWithin(float16bitsArray, target, start, ...safeIfNeeded(opts));
1091 | return this;
1092 | }
1093 | sort(compareFn) {
1094 | assertFloat16Array(this);
1095 | const float16bitsArray = getFloat16BitsArray(this);
1096 | const sortCompare = compareFn !== undefined ? compareFn : defaultCompare;
1097 | TypedArrayPrototypeSort(float16bitsArray, (x, y) => {
1098 | return sortCompare(convertToNumber(x), convertToNumber(y));
1099 | });
1100 | return this;
1101 | }
1102 | toSorted(compareFn) {
1103 | assertFloat16Array(this);
1104 | const float16bitsArray = getFloat16BitsArray(this);
1105 | if (compareFn !== undefined && typeof compareFn !== "function") {
1106 | throw new NativeTypeError(THE_COMPARISON_FUNCTION_MUST_BE_EITHER_A_FUNCTION_OR_UNDEFINED);
1107 | }
1108 | const sortCompare = compareFn !== undefined ? compareFn : defaultCompare;
1109 | const uint16 = new NativeUint16Array(
1110 | TypedArrayPrototypeGetBuffer(float16bitsArray),
1111 | TypedArrayPrototypeGetByteOffset(float16bitsArray),
1112 | TypedArrayPrototypeGetLength(float16bitsArray)
1113 | );
1114 | const cloned = new Float16Array(
1115 | TypedArrayPrototypeGetBuffer(
1116 | TypedArrayPrototypeSlice(uint16)
1117 | )
1118 | );
1119 | const clonedFloat16bitsArray = getFloat16BitsArray(cloned);
1120 | TypedArrayPrototypeSort(clonedFloat16bitsArray, (x, y) => {
1121 | return sortCompare(convertToNumber(x), convertToNumber(y));
1122 | });
1123 | return cloned;
1124 | }
1125 | slice(start, end) {
1126 | assertFloat16Array(this);
1127 | const float16bitsArray = getFloat16BitsArray(this);
1128 | const Constructor = SpeciesConstructor(float16bitsArray, Float16Array);
1129 | if (Constructor === Float16Array) {
1130 | const uint16 = new NativeUint16Array(
1131 | TypedArrayPrototypeGetBuffer(float16bitsArray),
1132 | TypedArrayPrototypeGetByteOffset(float16bitsArray),
1133 | TypedArrayPrototypeGetLength(float16bitsArray)
1134 | );
1135 | return new Float16Array(
1136 | TypedArrayPrototypeGetBuffer(
1137 | TypedArrayPrototypeSlice(uint16, start, end)
1138 | )
1139 | );
1140 | }
1141 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
1142 | const relativeStart = ToIntegerOrInfinity(start);
1143 | const relativeEnd = end === undefined ? length : ToIntegerOrInfinity(end);
1144 | let k;
1145 | if (relativeStart === -Infinity) {
1146 | k = 0;
1147 | } else if (relativeStart < 0) {
1148 | k = length + relativeStart > 0 ? length + relativeStart : 0;
1149 | } else {
1150 | k = length < relativeStart ? length : relativeStart;
1151 | }
1152 | let final;
1153 | if (relativeEnd === -Infinity) {
1154 | final = 0;
1155 | } else if (relativeEnd < 0) {
1156 | final = length + relativeEnd > 0 ? length + relativeEnd : 0;
1157 | } else {
1158 | final = length < relativeEnd ? length : relativeEnd;
1159 | }
1160 | const count = final - k > 0 ? final - k : 0;
1161 | const array = new Constructor(count);
1162 | assertSpeciesTypedArray(array, count);
1163 | if (count === 0) {
1164 | return array;
1165 | }
1166 | const buffer = TypedArrayPrototypeGetBuffer(float16bitsArray);
1167 | if (IsDetachedBuffer(buffer)) {
1168 | throw NativeTypeError(ATTEMPTING_TO_ACCESS_DETACHED_ARRAYBUFFER);
1169 | }
1170 | let n = 0;
1171 | while (k < final) {
1172 | array[n] = convertToNumber(float16bitsArray[k]);
1173 | ++k;
1174 | ++n;
1175 | }
1176 | return (array);
1177 | }
1178 | subarray(begin, end) {
1179 | assertFloat16Array(this);
1180 | const float16bitsArray = getFloat16BitsArray(this);
1181 | const Constructor = SpeciesConstructor(float16bitsArray, Float16Array);
1182 | const uint16 = new NativeUint16Array(
1183 | TypedArrayPrototypeGetBuffer(float16bitsArray),
1184 | TypedArrayPrototypeGetByteOffset(float16bitsArray),
1185 | TypedArrayPrototypeGetLength(float16bitsArray)
1186 | );
1187 | const uint16Subarray = TypedArrayPrototypeSubarray(uint16, begin, end);
1188 | const array = new Constructor(
1189 | TypedArrayPrototypeGetBuffer(uint16Subarray),
1190 | TypedArrayPrototypeGetByteOffset(uint16Subarray),
1191 | TypedArrayPrototypeGetLength(uint16Subarray)
1192 | );
1193 | assertSpeciesTypedArray(array);
1194 | return (array);
1195 | }
1196 | indexOf(element, ...opts) {
1197 | assertFloat16Array(this);
1198 | const float16bitsArray = getFloat16BitsArray(this);
1199 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
1200 | let from = ToIntegerOrInfinity(opts[0]);
1201 | if (from === Infinity) {
1202 | return -1;
1203 | }
1204 | if (from < 0) {
1205 | from += length;
1206 | if (from < 0) {
1207 | from = 0;
1208 | }
1209 | }
1210 | for (let i = from; i < length; ++i) {
1211 | if (
1212 | ObjectHasOwn(float16bitsArray, i) &&
1213 | convertToNumber(float16bitsArray[i]) === element
1214 | ) {
1215 | return i;
1216 | }
1217 | }
1218 | return -1;
1219 | }
1220 | lastIndexOf(element, ...opts) {
1221 | assertFloat16Array(this);
1222 | const float16bitsArray = getFloat16BitsArray(this);
1223 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
1224 | let from = opts.length >= 1 ? ToIntegerOrInfinity(opts[0]) : length - 1;
1225 | if (from === -Infinity) {
1226 | return -1;
1227 | }
1228 | if (from >= 0) {
1229 | from = from < length - 1 ? from : length - 1;
1230 | } else {
1231 | from += length;
1232 | }
1233 | for (let i = from; i >= 0; --i) {
1234 | if (
1235 | ObjectHasOwn(float16bitsArray, i) &&
1236 | convertToNumber(float16bitsArray[i]) === element
1237 | ) {
1238 | return i;
1239 | }
1240 | }
1241 | return -1;
1242 | }
1243 | includes(element, ...opts) {
1244 | assertFloat16Array(this);
1245 | const float16bitsArray = getFloat16BitsArray(this);
1246 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
1247 | let from = ToIntegerOrInfinity(opts[0]);
1248 | if (from === Infinity) {
1249 | return false;
1250 | }
1251 | if (from < 0) {
1252 | from += length;
1253 | if (from < 0) {
1254 | from = 0;
1255 | }
1256 | }
1257 | const isNaN = NumberIsNaN(element);
1258 | for (let i = from; i < length; ++i) {
1259 | const value = convertToNumber(float16bitsArray[i]);
1260 | if (isNaN && NumberIsNaN(value)) {
1261 | return true;
1262 | }
1263 | if (value === element) {
1264 | return true;
1265 | }
1266 | }
1267 | return false;
1268 | }
1269 | join(separator) {
1270 | assertFloat16Array(this);
1271 | const float16bitsArray = getFloat16BitsArray(this);
1272 | const array = copyToArray(float16bitsArray);
1273 | return ArrayPrototypeJoin(array, separator);
1274 | }
1275 | toLocaleString(...opts) {
1276 | assertFloat16Array(this);
1277 | const float16bitsArray = getFloat16BitsArray(this);
1278 | const array = copyToArray(float16bitsArray);
1279 | return ArrayPrototypeToLocaleString(array, ...safeIfNeeded(opts));
1280 | }
1281 | get [SymbolToStringTag]() {
1282 | if (isFloat16Array(this)) {
1283 | return ("Float16Array");
1284 | }
1285 | }
1286 | }
1287 | ObjectDefineProperty(Float16Array, "BYTES_PER_ELEMENT", {
1288 | value: BYTES_PER_ELEMENT,
1289 | });
1290 | ObjectDefineProperty(Float16Array, brand, {});
1291 | ReflectSetPrototypeOf(Float16Array, TypedArray);
1292 | const Float16ArrayPrototype = Float16Array.prototype;
1293 | ObjectDefineProperty(Float16ArrayPrototype, "BYTES_PER_ELEMENT", {
1294 | value: BYTES_PER_ELEMENT,
1295 | });
1296 | ObjectDefineProperty(Float16ArrayPrototype, SymbolIterator, {
1297 | value: Float16ArrayPrototype.values,
1298 | writable: true,
1299 | configurable: true,
1300 | });
1301 | ReflectSetPrototypeOf(Float16ArrayPrototype, TypedArrayPrototype);
1302 |
1303 | function isTypedArray(target) {
1304 | return isNativeTypedArray(target) || isFloat16Array(target);
1305 | }
1306 |
1307 | function getFloat16(dataView, byteOffset, ...opts) {
1308 | return convertToNumber(
1309 | DataViewPrototypeGetUint16(dataView, byteOffset, ...safeIfNeeded(opts))
1310 | );
1311 | }
1312 | function setFloat16(dataView, byteOffset, value, ...opts) {
1313 | return DataViewPrototypeSetUint16(
1314 | dataView,
1315 | byteOffset,
1316 | roundToFloat16Bits(value),
1317 | ...safeIfNeeded(opts)
1318 | );
1319 | }
1320 |
1321 | function f16round(x) {
1322 | return roundToFloat16(x);
1323 | }
1324 |
1325 | export { Float16Array, f16round, getFloat16, f16round as hfround, isFloat16Array, isTypedArray, setFloat16 };
1326 |
--------------------------------------------------------------------------------
/js/main.js:
--------------------------------------------------------------------------------
1 | import {ReactionDiffusionCompute} from './rd-compute.js';
2 | import {Composite} from './composite.js';
3 |
4 | const hasWebGPU = !!navigator.gpu;
5 | let canvas, device, webGPUContext, viewportSize, timeMS = 0, reactionDiffusion, composite;
6 |
7 | async function init() {
8 | if (!hasWebGPU) {
9 | const noWebGPUMessage = document.querySelector('#no-webgpu');
10 | noWebGPUMessage.style.display = '';
11 | return;
12 | }
13 |
14 | const format = navigator.gpu.getPreferredCanvasFormat();
15 | const adapter = await navigator.gpu?.requestAdapter();
16 | device = await adapter?.requestDevice();
17 |
18 | canvas = document.getElementById('viewport');
19 | webGPUContext = canvas.getContext('webgpu');
20 | webGPUContext.configure({ device, format });
21 |
22 | updateViewportSize();
23 |
24 | reactionDiffusion = new ReactionDiffusionCompute(device, viewportSize);
25 | composite = new Composite(device, reactionDiffusion);
26 |
27 | const observer = new ResizeObserver(() => resize());
28 | observer.observe(canvas);
29 |
30 | run(0);
31 | }
32 |
33 | function updateViewportSize() {
34 | const pixelRatio = Math.min(2, window.devicePixelRatio);
35 | viewportSize = [
36 | Math.max(1, Math.min(window.innerWidth * pixelRatio, device.limits.maxTextureDimension2D)),
37 | Math.max(1, Math.min(window.innerHeight * pixelRatio, device.limits.maxTextureDimension2D))
38 | ];
39 | }
40 |
41 | function resize() {
42 | updateViewportSize();
43 |
44 | canvas.width = viewportSize[0];
45 | canvas.height = viewportSize[1];
46 |
47 | reactionDiffusion.resize(viewportSize[0], viewportSize[1]);
48 | composite.resize();
49 | }
50 |
51 | function run(t) {
52 | timeMS += Math.min(32, t);
53 |
54 | // create the global pulse animation value: sin with one cycle per second
55 | const dateTimeMS = new Date().getTime() + 800;
56 | const pulse = Math.sin(2 * Math.PI * dateTimeMS * .001);
57 |
58 | const commandEncoder = device.createCommandEncoder();
59 |
60 | // update the reaction diffusion compute
61 | const computePassEncoder = commandEncoder.beginComputePass(commandEncoder);
62 | reactionDiffusion.compute(computePassEncoder, pulse);
63 | computePassEncoder.end();
64 |
65 | // render the composite result
66 | const compositePassEncoder = commandEncoder.beginRenderPass({
67 | colorAttachments: [{
68 | view: webGPUContext.getCurrentTexture().createView(),
69 | clearValue: { r: 1, g: 1, b: 1, a: 1},
70 | loadOp: 'clear',
71 | storeOp: 'store'
72 | }],
73 | });
74 | composite.render(compositePassEncoder, pulse);
75 | compositePassEncoder.end();
76 |
77 | device.queue.submit([commandEncoder.finish()]);
78 |
79 | requestAnimationFrame(t => run(t));
80 | }
81 |
82 | // wait until the webfont is loaded
83 | const font = new FontFace('Syne Mono', 'url(assets/syne-mono-subset.woff), url(assets/syne-mono-subset.ttf)');
84 | document.fonts.add(font);
85 | font.load().then(() => document.fonts.ready.then(async () => await init()));
86 |
87 |
--------------------------------------------------------------------------------
/js/rd-compute.js:
--------------------------------------------------------------------------------
1 | import * as wgh from './libs/webgpu-utils.module.js';
2 | import { Float16Array } from './libs/float16.js';
3 | import { ReactionDiffusionComputeShader, ReactionDiffusionShaderDispatchSize } from './shader/rd-compute-shader.js';
4 |
5 | export class ReactionDiffusionCompute {
6 |
7 | // these are the iterations of the simulation during one frame (the more iterations, the faster the simulation)
8 | ITERATIONS = 10;
9 |
10 | // this is the scaling factor for the simulation textures (one quarter the size of the canvas)
11 | SCALE = .25;
12 |
13 | // interaction data
14 | pointer = {
15 | position: [0, 0],
16 | followerPosition: undefined,
17 | followerVelocity: [0, 0]
18 | };
19 |
20 | constructor(device, viewportSize) {
21 | this.device = device;
22 |
23 | // create pipeline and bind group layouts
24 | const module = this.device.createShaderModule({ code: ReactionDiffusionComputeShader });
25 | const defs = wgh.makeShaderDataDefinitions(ReactionDiffusionComputeShader);
26 | const pipelineDescriptor = {
27 | compute: {
28 | module,
29 | entryPoint: 'compute_main',
30 | }
31 | };
32 | const descriptors = wgh.makeBindGroupLayoutDescriptors(defs, pipelineDescriptor);
33 | // the storage texture descriptor has to be created manually
34 | descriptors[0].entries.push({
35 | binding: 1,
36 | storageTexture: { access: 'write-only', format: 'rgba16float' },
37 | visibility: GPUShaderStage.COMPUTE
38 | });
39 | this.bindGroupLayout = this.device.createBindGroupLayout(descriptors[0]);
40 |
41 | // create the compute pipeline
42 | const pipelineLayout = this.device.createPipelineLayout({
43 | bindGroupLayouts: [this.bindGroupLayout]
44 | });
45 | this.pipeline = this.device.createComputePipeline({
46 | label: 'reaction diffusion compute pipeline',
47 | layout: pipelineLayout,
48 | ...pipelineDescriptor
49 | });
50 |
51 | // create the animation uniform view and buffer
52 | const animationUniformView = wgh.makeStructuredView(defs.uniforms.animationUniforms);
53 | this.animationUniform = {
54 | view: animationUniformView,
55 | buffer: this.device.createBuffer({
56 | size: animationUniformView.arrayBuffer.byteLength,
57 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
58 | })
59 | };
60 |
61 | this.initEvents();
62 |
63 | // initial resize (this also creates the textures and bind groups)
64 | this.resize(viewportSize[0], viewportSize[1]);
65 | }
66 |
67 | initEvents() {
68 | document.body.addEventListener('pointerdown', e => {
69 | this.pointer.position = this.getNormalizedPointerCoords(e.clientX, e.clientY);
70 | this.pointer.followerPosition = [...this.pointer.position];
71 | });
72 |
73 | document.body.addEventListener('pointermove', e => {
74 | this.pointer.position = this.getNormalizedPointerCoords(e.clientX, e.clientY);
75 | if (!this.pointer.followerPosition) this.pointer.followerPosition = [...this.pointer.position];
76 | });
77 | }
78 |
79 | resize(width, height) {
80 | this.width = Math.round(width * this.SCALE);
81 | this.height = Math.round(height * this.SCALE);
82 | this.aspect = this.width / this.height;
83 |
84 | this.inputCanvas = document.createElement('canvas');
85 | this.inputCanvas.width = this.width;
86 | this.inputCanvas.height = this.height;
87 | this.fontSize = Math.max(this.aspect > 1 ? 70 : 80, Math.min(this.inputCanvas.width, this.inputCanvas.height) / 3.75);
88 | this.inputContext = this.inputCanvas.getContext("2d", { willReadFrequently: true });
89 | this.inputContext.font = `${this.fontSize}px "Syne Mono"`;
90 | this.letterWidth = this.inputContext.measureText('0').width;
91 | this.letterHeight = this.inputContext.measureText('0').actualBoundingBoxAscent;
92 |
93 | this.createTextures(this.width, this.height);
94 | this.createBindGroups();
95 | }
96 |
97 | get resultStorageTexture() {
98 | return this.swapTextures[0];
99 | }
100 |
101 | createTextures(width, height) {
102 | // dispose existing textures
103 | if (this.swapTextures) {
104 | this.swapTextures.forEach(texture => texture.destroy());
105 | this.seedTexture.destroy();
106 | }
107 |
108 | // the seed texture to copy the canvas image to
109 | this.seedTexture = this.device.createTexture({
110 | size: { width, height },
111 | format: 'rgba8unorm',
112 | usage:
113 | GPUTextureUsage.COPY_DST |
114 | GPUTextureUsage.TEXTURE_BINDING
115 | });
116 |
117 | // the textures for the actual reaction diffusion ping-pong computation
118 | this.swapTextures = new Array(2).fill(null).map((v, ndx) => {
119 | const texture = this.device.createTexture({
120 | size: { width, height },
121 | format: 'rgba16float',
122 | usage:
123 | GPUTextureUsage.COPY_DST |
124 | GPUTextureUsage.STORAGE_BINDING |
125 | GPUTextureUsage.TEXTURE_BINDING |
126 | GPUTextureUsage.RENDER_ATTACHMENT,
127 | });
128 |
129 | if (ndx === 0) {
130 | // initially add the clock letters as chemical A
131 | const imgData = this.drawTime(true);
132 | const imgNormRGBA = Array.from(imgData.data).map(v => v / 255);
133 | const data = new Float16Array(imgNormRGBA);
134 | this.device.queue.writeTexture({ texture }, data.buffer, { bytesPerRow: width * 8 }, { width, height });
135 | } else {
136 | const data = new Float16Array(new Array(width * height * 4).fill(0));
137 | this.device.queue.writeTexture({ texture }, data.buffer, { bytesPerRow: width * 8 }, { width, height });
138 | }
139 |
140 | return texture;
141 | });
142 |
143 | this.dispatches = [
144 | Math.ceil(width / ReactionDiffusionShaderDispatchSize[0]),
145 | Math.ceil(height / ReactionDiffusionShaderDispatchSize[1])
146 | ];
147 | }
148 |
149 | drawTime(init = false) {
150 | const ctx = this.inputContext;
151 | ctx.resetTransform();
152 | ctx.clearRect(0, 0, this.width, this.height);
153 |
154 | ctx.scale(1, 1);
155 | ctx.rect(0, 0, this.width, this.height);
156 | ctx.fillStyle = init ? '#0f0' : 'rgba(0, 0, 0, 0)';
157 | ctx.fill();
158 |
159 | ctx.translate(this.width / 2, this.height / 2);
160 | ctx.scale(1, -1);
161 | ctx.fillStyle = '#f00';
162 | const now = new Date();
163 |
164 | if (this.aspect > 1.4) {
165 | ctx.fillText(`${now.getHours().toString(10).padStart(2, '0')}:${now.getMinutes().toString(10).padStart(2, '0')}:${now.getSeconds().toString(10).padStart(2, '0')}`,
166 | - this.letterWidth * 4,
167 | + this.letterHeight * .5);
168 | } else {
169 | const lineHeight = 1.25;
170 | const x = - this.letterWidth;
171 | const y = - this.letterHeight * .5 + (this.letterHeight * (1 - lineHeight));
172 | const rowHeight = this.letterHeight * lineHeight;
173 | ctx.fillText(`${now.getHours().toString(10).padStart(2, '0')}`,
174 | x,
175 | y);
176 | ctx.fillText(`${now.getMinutes().toString(10).padStart(2, '0')}`,
177 | x,
178 | y + rowHeight);
179 | ctx.fillText(`${now.getSeconds().toString(10).padStart(2, '0')}`,
180 | x,
181 | y + rowHeight * 2);
182 | }
183 |
184 | this.lastTime = init ? undefined : now;
185 | return this.inputContext.getImageData(0, 0, this.width, this.height);
186 | }
187 |
188 | createBindGroups() {
189 | this.swapBindGroups = [
190 | this.device.createBindGroup({
191 | layout: this.bindGroupLayout,
192 | entries: [
193 | { binding: 0, resource: this.swapTextures[0].createView() },
194 | { binding: 1, resource: this.swapTextures[1].createView() },
195 | { binding: 2, resource: this.seedTexture.createView() },
196 | { binding: 3, resource: { buffer: this.animationUniform.buffer }},
197 | ]
198 | }),
199 | this.device.createBindGroup({
200 | layout: this.bindGroupLayout,
201 | entries: [
202 | { binding: 0, resource: this.swapTextures[1].createView() },
203 | { binding: 1, resource: this.swapTextures[0].createView() },
204 | { binding: 2, resource: this.seedTexture.createView() },
205 | { binding: 3, resource: { buffer: this.animationUniform.buffer }},
206 | ]
207 | })
208 | ];
209 | }
210 |
211 | compute(computePassEncoder, pulse) {
212 | this.animatePointer();
213 |
214 | // update animation uniforms
215 | this.animationUniform.view.set({ pulse });
216 | this.animationUniform.view.set({ pointerVelocity: this.pointer.followerVelocity });
217 | this.animationUniform.view.set({ pointerPos: this.pointer.followerPosition ? this.pointer.followerPosition : [0, 0] });
218 | this.device.queue.writeBuffer(this.animationUniform.buffer, 0, this.animationUniform.view.arrayBuffer);
219 |
220 | computePassEncoder.setPipeline(this.pipeline);
221 |
222 | // redraw the clock only if needed
223 | if (!this.lastTime || this.lastTime.getSeconds() !== new Date().getSeconds()) {
224 | const imgData = this.drawTime();
225 | const seedData = new Uint8Array(Array.from(imgData.data));
226 | this.device.queue.writeTexture({ texture: this.seedTexture }, seedData.buffer, { bytesPerRow: this.width * 4 }, { width: this.width, height: this.height });
227 | }
228 |
229 | // reaction diffusion ping-pong loop ;)
230 | for(let i = 0; i < this.ITERATIONS; i++) {
231 | computePassEncoder.setBindGroup(0, this.swapBindGroups[0]);
232 | computePassEncoder.dispatchWorkgroups(this.dispatches[0], this.dispatches[1]);
233 |
234 | computePassEncoder.setBindGroup(0, this.swapBindGroups[1]);
235 | computePassEncoder.dispatchWorkgroups(this.dispatches[0], this.dispatches[1]);
236 | }
237 | }
238 |
239 | animatePointer() {
240 | if (!this.pointer.followerPosition) return;
241 |
242 | const prevPointerFollower = [...this.pointer.followerPosition];
243 | this.pointer.followerPosition[0] += (this.pointer.position[0] - this.pointer.followerPosition[0]) / 18;
244 | this.pointer.followerPosition[1] += (this.pointer.position[1] - this.pointer.followerPosition[1]) / 18;
245 | this.pointer.followerVelocity[0] = (this.pointer.followerPosition[0] - prevPointerFollower[0]);
246 | this.pointer.followerVelocity[1] = (this.pointer.followerPosition[1] - prevPointerFollower[1]);
247 | }
248 |
249 | getNormalizedPointerCoords(clientX, clientY) {
250 | return [
251 | (clientX / window.innerWidth) * 2 - 1,
252 | (1 - clientY / window.innerHeight) * 2 - 1
253 | ];
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/js/shader/composite-shader.js:
--------------------------------------------------------------------------------
1 | // language=C
2 | export const CompositeShader = `
3 | struct VertexOutput {
4 | @builtin(position) position: vec4f,
5 | @location(0) uv: vec2f
6 | }
7 |
8 | struct AnimationUniforms {
9 | pulse: f32,
10 | };
11 |
12 | @group(0) @binding(0) var animationUniforms: AnimationUniforms;
13 |
14 | @vertex
15 | fn vertex_main(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {
16 | const pos : array = array(
17 | vec2f(-1, 3),
18 | vec2f(-1, -1),
19 | vec2f(3, -1)
20 | );
21 | const uv : array = array(
22 | vec2f(0, 2),
23 | vec2f(0, 0),
24 | vec2f(2, 0)
25 | );
26 | var output : VertexOutput;
27 | output.position = vec4f(pos[vertexIndex], 0., 1.);
28 | output.uv = uv[vertexIndex];
29 | return output;
30 | }
31 |
32 | @group(0) @binding(1) var inputTexSampler : sampler;
33 | @group(0) @binding(2) var inputTex : texture_2d;
34 |
35 | // https://iquilezles.org/articles/palettes/
36 | fn pal(t: f32, a: vec3, b: vec3, c: vec3, d: vec3) -> vec3 {
37 | return a + b * cos(6.28318 * (c * t + d));
38 | }
39 |
40 | // Bulge distortion based on: https://www.shadertoy.com/view/ldBfRV
41 | fn distort(r: vec2, alpha: f32) -> vec2 {
42 | return r + r * -alpha * (1. - dot(r, r) * 1.25);
43 | }
44 |
45 | fn emboss(
46 | p: vec2f,
47 | channel: vec4f,
48 | center: vec4f,
49 | tex: texture_2d,
50 | texSampler: sampler,
51 | texelSize: vec2f,
52 | scale: f32,
53 | shift: f32) -> vec4f
54 | {
55 | let tlColor: vec4f = textureSample(tex, texSampler, p + vec2(-texelSize.x, texelSize.y) * scale);
56 | let brColor: vec4f = textureSample(tex, texSampler, p + vec2(texelSize.x, -texelSize.y) * scale);
57 | let c: f32 = smoothstep(0., shift, dot(center, channel));
58 | let tl: f32 = smoothstep(0., shift, dot(tlColor, channel));
59 | let br: f32 = smoothstep(0., shift, dot(brColor, channel));
60 | return vec4f(tl, c, br, clamp(2.0 * br - c - tl, 0.0, 1.0));
61 | }
62 |
63 | @fragment
64 | fn frag_main(@location(0) uv : vec2f) -> @location(0) vec4f {
65 |
66 | // add bulge distortion to the uv coords
67 | let p = distort(uv * 2. - 1., -0.15) * .5 + .5;
68 |
69 | // get input data
70 | let inputTexSize : vec2f = vec2f(textureDimensions(inputTex));
71 | let inputTexelSize = 1. / inputTexSize;
72 | let input: vec4f = textureSample(inputTex, inputTexSampler, p);
73 |
74 | // use the chemical B distribution as the base color value
75 | let value = smoothstep(0.225, .8, input.g);
76 | var base: vec3f = pal(value * .4 + 0.4, vec3(.5,0.5,0.5), vec3(0.5,0.5,.5), vec3(1.,1.0,1.0), vec3(0.05,0.1,0.2));
77 | base *= 1.5 * ((animationUniforms.pulse) * .15 + .85);
78 |
79 | // get centered uv coords and distance to center
80 | let st = uv * 2. - 1.;
81 | let dist = length(st);
82 |
83 | // inner emboss effect
84 | var emboss1: vec4f = emboss(p, vec4(1., 0., 0., 0.), input, inputTex, inputTexSampler, inputTexelSize, .5, .4 + dist * .3);
85 | emboss1.w = emboss1.w * .3 * (animationUniforms.pulse * .2 + .8);
86 |
87 | // inner specular from emboss data
88 | let specular = smoothstep(0.2, 0.3, 2.0 * emboss1.x - emboss1.y - emboss1.z) * .5 * (1. - dist) * ((1. - animationUniforms.pulse) * .15 + .85);
89 |
90 | // outer emboss for iridescence
91 | var emboss2: vec4f = emboss(p, vec4(0., 1., 0., 0.), input, inputTex, inputTexSampler, inputTexelSize, .8, .1);
92 | var iridescence = pal(input.r * 5. + .2, vec3(.5,0.5,0.5), vec3(0.5,0.5,.5), vec3(1.,1.0,1.0),vec3(0.0,0.33,0.67));
93 | iridescence = mix(iridescence, vec3f(0.), smoothstep(0., .4, max(input.g, emboss2.w)));
94 | iridescence *= .07 * ((1. - animationUniforms.pulse) * .2 + .8);
95 |
96 | // simple centered vignette
97 | let vignette = dist * .075;
98 |
99 | var color: vec4f = vec4f(base + vec3(emboss1.w) + specular + iridescence - vignette, 1.);
100 |
101 | return color;
102 | }
103 | `;
104 |
--------------------------------------------------------------------------------
/js/shader/rd-compute-shader.js:
--------------------------------------------------------------------------------
1 | // reaction diffusion requires a 3x3 kernel
2 | const kernelSize = 3;
3 |
4 | // keep the number of threads to the recommended threshold (64)
5 | const workgroupSize = [8, 8];
6 |
7 | // each thread handles a tile of pixels
8 | const tileSize = [2, 2];
9 |
10 | // holds all the pixels needed for one workgroup
11 | const cacheSize = [
12 | tileSize[0] * workgroupSize[0], // 16
13 | tileSize[1] * workgroupSize[1] // 16
14 | ];
15 |
16 | // the cache has to include the boundary pixels needed for a
17 | // valid evaluation of the kernel within the dispatch area
18 | const dispatchSize = [
19 | cacheSize[0] - (kernelSize - 1), // 14
20 | cacheSize[1] - (kernelSize - 1), // 14
21 | ];
22 |
23 | export const ReactionDiffusionShaderDispatchSize = dispatchSize;
24 |
25 | // language=C
26 | export const ReactionDiffusionComputeShader = `
27 |
28 | const kernelSize = ${kernelSize};
29 | const dispatchSize = vec2u(${dispatchSize[0]},${dispatchSize[1]});
30 | const tileSize = vec2u(${tileSize[0]},${tileSize[1]});
31 | const laplacian: array = array(
32 | 0.05, 0.20, 0.05,
33 | 0.20, -1.0, 0.20,
34 | 0.05, 0.20, 0.05,
35 | );
36 |
37 | struct AnimationUniforms {
38 | pulse: f32,
39 | pointerVelocity: vec2f,
40 | pointerPos: vec2f
41 | };
42 |
43 | @group(0) @binding(0) var inputTex: texture_2d;
44 | @group(0) @binding(1) var outputTex: texture_storage_2d;
45 | @group(0) @binding(2) var seedTex: texture_2d;
46 | @group(0) @binding(3) var animationUniforms: AnimationUniforms;
47 |
48 | // based on: https://community.khronos.org/t/manual-bilinear-filter/58504
49 | fn texture2D_bilinear(t: texture_2d, coord: vec2f, dims: vec2u) -> vec4f {
50 | let f: vec2f = fract(coord);
51 | let sample: vec2u = vec2u(coord + (0.5 - f));
52 | let tl: vec4f = textureLoad(t, clamp(sample, vec2u(1, 1), dims), 0);
53 | let tr: vec4f = textureLoad(t, clamp(sample + vec2u(1, 0), vec2u(1, 1), dims), 0);
54 | let bl: vec4f = textureLoad(t, clamp(sample + vec2u(0, 1), vec2u(1, 1), dims), 0);
55 | let br: vec4f = textureLoad(t, clamp(sample + vec2u(1, 1), vec2u(1, 1), dims), 0);
56 | let tA: vec4f = mix(tl, tr, f.x);
57 | let tB: vec4f = mix(bl, br, f.x);
58 | return mix(tA, tB, f.y);
59 | }
60 |
61 | // the cache for the texture lookups (tileSize * workgroupSize).
62 | // each thread adds a tile of pixels to the workgroups shared memory
63 | var cache: array, ${cacheSize[1]}>;
64 |
65 | @compute @workgroup_size(${workgroupSize[0]}, ${workgroupSize[1]}, 1)
66 | fn compute_main(
67 | @builtin(workgroup_id) workGroupID : vec3,
68 | @builtin(local_invocation_id) localInvocationID : vec3,
69 | @builtin(global_invocation_id) globalInvocationID : vec3
70 | ) {
71 | // the kernel offset (number of pixels next to the center of the kernel) defines
72 | // the border area next to the dispatched (=work) area that has to be included
73 | // within the pixel cache
74 | let kernelOffset: vec2u = vec2((kernelSize - 1) / 2);
75 |
76 | // the local pixel offset of this threads tile (the tile inside the workgroup)
77 | let tileOffset: vec2u = localInvocationID.xy * tileSize;
78 |
79 | // the global pixel offset of the workgroup
80 | let dispatchOffset: vec2u = workGroupID.xy * dispatchSize;
81 |
82 | // get texture dimensions
83 | let dims: vec2u = vec2(textureDimensions(inputTex, 0));
84 | let aspectFactor: vec2f = vec2f(dims) / f32(max(dims.x, dims.y));
85 |
86 | // add the pixels of this thread's tiles to the cache
87 | for (var c=0u; c= bounds.xy) && all(sample < bounds.zw)) {
141 |
142 | // get centered uv coords
143 | let uv: vec2f = (2. * vec2f(sample) / vec2f(dims)) - 1.;
144 |
145 | // convolution with laplacian kernel
146 | var lap = vec2f(0);
147 | let ks: i32 = i32(kernelSize);
148 | for (var x = 0; x < ks; x++) {
149 | for (var y = 0; y < ks; y++) {
150 | var i = vec2i(local) + vec2(x, y) - vec2i(kernelOffset);
151 | lap += cache[i.y][i.x].xy * laplacian[y * ks + x];
152 | }
153 | }
154 |
155 | // create a pointer mask value to influence the dB value
156 | var st = (uv * aspectFactor) * .5 + .5;
157 | let pointerPos = (animationUniforms.pointerPos * aspectFactor) * .5 + .5;
158 | var pointerMask = smoothstep(0.6, 1., 1. - min(1., (distance(st, pointerPos))));
159 | pointerMask = pointerMask * length(animationUniforms.pointerVelocity) * 30.;
160 |
161 | // the dA and dB values are also influenced by the horizontal or vertical distance from the center
162 | let dist = mix(dot(uv.xx, uv.xx), dot(uv.yy, uv.yy), step(1.4, f32(dims.x) / f32(dims.y)));
163 |
164 | // reaction diffusion parameters
165 | let cacheValue: vec4f = cache[local.y][local.x];
166 | let dA = 1. - dist * .15;
167 | var dB = .25 + dist * 0.1;
168 | dB = dB + 0.1 * (animationUniforms.pulse * .5 + .5); // apply pulse motion
169 | dB = dB - min(0.15, 0.2 * pointerMask); // apply pointer mask
170 | dB = max(0.1, dB); // prevent instable values
171 | let feed = 0.065;
172 | var kill = 0.06;
173 | // increase the kill param in the areas covered by the clock letters
174 | kill = kill + cacheValue.b * .05 * (animationUniforms.pulse * .3 + .7);
175 |
176 | // reaction diffusion calculation
177 | let rd0 = cacheValue.xy;
178 | let A = rd0.x;
179 | let B = rd0.y;
180 | let reaction = A * B * B;
181 | let rd = vec2f(
182 | A + (dA * lap.x - reaction + feed * (1. - A)),
183 | B + (dB * lap.y + reaction - (kill + feed) * B),
184 | );
185 |
186 | textureStore(outputTex, sample, vec4(rd, 0., 1.0));
187 | }
188 | }
189 | }
190 | }
191 | `;
192 |
193 |
--------------------------------------------------------------------------------
/performance-comparison/css/demo.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | overflow: hidden;
3 | width: 100%;
4 | height: 100%;
5 | font-family: sans-serif;
6 | }
7 |
8 | #viewport {
9 | position: absolute;
10 | top: 0;
11 | left: 0;
12 | width: 100%;
13 | height: 100%;
14 | }
15 |
--------------------------------------------------------------------------------
/performance-comparison/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | WebGPU Reaction Diffusion | Performance
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/performance-comparison/js/composite.js:
--------------------------------------------------------------------------------
1 | import {CompositeShader} from './shader/composite-shader.js';
2 | import * as wgh from './libs/webgpu-utils.module.js';
3 |
4 | export class Composite {
5 |
6 | constructor(device, reactionDiffusion) {
7 | this.device = device;
8 | this.reactionDiffusion = reactionDiffusion;
9 |
10 | const module = device.createShaderModule({ code: CompositeShader });
11 | const defs = wgh.makeShaderDataDefinitions(CompositeShader);
12 | const pipelineLayout = {
13 | vertex: {
14 | module: module,
15 | entryPoint: 'vertex_main',
16 | },
17 | fragment: {
18 | module,
19 | entryPoint:'frag_main',
20 | targets: [
21 | { format: navigator.gpu.getPreferredCanvasFormat() }
22 | ]
23 | },
24 | primitive: {
25 | topology: 'triangle-list',
26 | },
27 | }
28 | const descriptors = wgh.makeBindGroupLayoutDescriptors(defs, pipelineLayout);
29 | const bindGroupLayout = device.createBindGroupLayout(descriptors[0]);
30 |
31 | this.sampler = device.createSampler({
32 | minFilter: 'linear',
33 | magFilter: 'linear'
34 | });
35 | this.bindGroupLayouts = [bindGroupLayout];
36 |
37 | this.pipeline = device.createRenderPipeline({
38 | label: 'composite pipeline',
39 | layout: device.createPipelineLayout({
40 | bindGroupLayouts: [bindGroupLayout],
41 | }),
42 | ...pipelineLayout
43 | });
44 |
45 | this.createBindGroups();
46 | }
47 |
48 | render(renderPassEncoder) {
49 | renderPassEncoder.setPipeline(this.pipeline);
50 | renderPassEncoder.setBindGroup(0, this.bindGroup);
51 | renderPassEncoder.draw(3);
52 | }
53 |
54 | createBindGroups() {
55 | this.bindGroup = this.device.createBindGroup({
56 | layout: this.bindGroupLayouts[0],
57 | entries: [
58 | { binding: 0, resource: this.sampler },
59 | { binding: 1, resource: this.reactionDiffusion.resultStorageTexture.createView() },
60 | ]
61 | });
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/performance-comparison/js/libs/float16.js:
--------------------------------------------------------------------------------
1 | /*! @petamoriken/float16 v3.8.6 | MIT License - https://github.com/petamoriken/float16 */
2 |
3 | const THIS_IS_NOT_AN_OBJECT = "This is not an object";
4 | const THIS_IS_NOT_A_FLOAT16ARRAY_OBJECT = "This is not a Float16Array object";
5 | const THIS_CONSTRUCTOR_IS_NOT_A_SUBCLASS_OF_FLOAT16ARRAY =
6 | "This constructor is not a subclass of Float16Array";
7 | const THE_CONSTRUCTOR_PROPERTY_VALUE_IS_NOT_AN_OBJECT =
8 | "The constructor property value is not an object";
9 | const SPECIES_CONSTRUCTOR_DIDNT_RETURN_TYPEDARRAY_OBJECT =
10 | "Species constructor didn't return TypedArray object";
11 | const DERIVED_CONSTRUCTOR_CREATED_TYPEDARRAY_OBJECT_WHICH_WAS_TOO_SMALL_LENGTH =
12 | "Derived constructor created TypedArray object which was too small length";
13 | const ATTEMPTING_TO_ACCESS_DETACHED_ARRAYBUFFER =
14 | "Attempting to access detached ArrayBuffer";
15 | const CANNOT_CONVERT_UNDEFINED_OR_NULL_TO_OBJECT =
16 | "Cannot convert undefined or null to object";
17 | const CANNOT_MIX_BIGINT_AND_OTHER_TYPES =
18 | "Cannot mix BigInt and other types, use explicit conversions";
19 | const ITERATOR_PROPERTY_IS_NOT_CALLABLE = "@@iterator property is not callable";
20 | const REDUCE_OF_EMPTY_ARRAY_WITH_NO_INITIAL_VALUE =
21 | "Reduce of empty array with no initial value";
22 | const THE_COMPARISON_FUNCTION_MUST_BE_EITHER_A_FUNCTION_OR_UNDEFINED =
23 | "The comparison function must be either a function or undefined";
24 | const OFFSET_IS_OUT_OF_BOUNDS = "Offset is out of bounds";
25 |
26 | function uncurryThis(target) {
27 | return (thisArg, ...args) => {
28 | return ReflectApply(target, thisArg, args);
29 | };
30 | }
31 | function uncurryThisGetter(target, key) {
32 | return uncurryThis(
33 | ReflectGetOwnPropertyDescriptor(
34 | target,
35 | key
36 | ).get
37 | );
38 | }
39 | const {
40 | apply: ReflectApply,
41 | construct: ReflectConstruct,
42 | defineProperty: ReflectDefineProperty,
43 | get: ReflectGet,
44 | getOwnPropertyDescriptor: ReflectGetOwnPropertyDescriptor,
45 | getPrototypeOf: ReflectGetPrototypeOf,
46 | has: ReflectHas,
47 | ownKeys: ReflectOwnKeys,
48 | set: ReflectSet,
49 | setPrototypeOf: ReflectSetPrototypeOf,
50 | } = Reflect;
51 | const NativeProxy = Proxy;
52 | const {
53 | EPSILON,
54 | MAX_SAFE_INTEGER,
55 | isFinite: NumberIsFinite,
56 | isNaN: NumberIsNaN,
57 | } = Number;
58 | const {
59 | iterator: SymbolIterator,
60 | species: SymbolSpecies,
61 | toStringTag: SymbolToStringTag,
62 | for: SymbolFor,
63 | } = Symbol;
64 | const NativeObject = Object;
65 | const {
66 | create: ObjectCreate,
67 | defineProperty: ObjectDefineProperty,
68 | freeze: ObjectFreeze,
69 | is: ObjectIs,
70 | } = NativeObject;
71 | const ObjectPrototype = NativeObject.prototype;
72 | const ObjectPrototype__lookupGetter__ = (ObjectPrototype).__lookupGetter__
73 | ? uncurryThis( (ObjectPrototype).__lookupGetter__)
74 | : (object, key) => {
75 | if (object == null) {
76 | throw NativeTypeError(
77 | CANNOT_CONVERT_UNDEFINED_OR_NULL_TO_OBJECT
78 | );
79 | }
80 | let target = NativeObject(object);
81 | do {
82 | const descriptor = ReflectGetOwnPropertyDescriptor(target, key);
83 | if (descriptor !== undefined) {
84 | if (ObjectHasOwn(descriptor, "get")) {
85 | return descriptor.get;
86 | }
87 | return;
88 | }
89 | } while ((target = ReflectGetPrototypeOf(target)) !== null);
90 | };
91 | const ObjectHasOwn = (NativeObject).hasOwn ||
92 | uncurryThis(ObjectPrototype.hasOwnProperty);
93 | const NativeArray = Array;
94 | const ArrayIsArray = NativeArray.isArray;
95 | const ArrayPrototype = NativeArray.prototype;
96 | const ArrayPrototypeJoin = uncurryThis(ArrayPrototype.join);
97 | const ArrayPrototypePush = uncurryThis(ArrayPrototype.push);
98 | const ArrayPrototypeToLocaleString = uncurryThis(
99 | ArrayPrototype.toLocaleString
100 | );
101 | const NativeArrayPrototypeSymbolIterator = ArrayPrototype[SymbolIterator];
102 | const ArrayPrototypeSymbolIterator = uncurryThis(NativeArrayPrototypeSymbolIterator);
103 | const {
104 | abs: MathAbs,
105 | trunc: MathTrunc,
106 | } = Math;
107 | const NativeArrayBuffer = ArrayBuffer;
108 | const ArrayBufferIsView = NativeArrayBuffer.isView;
109 | const ArrayBufferPrototype = NativeArrayBuffer.prototype;
110 | const ArrayBufferPrototypeSlice = uncurryThis(ArrayBufferPrototype.slice);
111 | const ArrayBufferPrototypeGetByteLength = uncurryThisGetter(ArrayBufferPrototype, "byteLength");
112 | const NativeSharedArrayBuffer = typeof SharedArrayBuffer !== "undefined" ? SharedArrayBuffer : null;
113 | const SharedArrayBufferPrototypeGetByteLength = NativeSharedArrayBuffer
114 | && uncurryThisGetter(NativeSharedArrayBuffer.prototype, "byteLength");
115 | const TypedArray = ReflectGetPrototypeOf(Uint8Array);
116 | const TypedArrayFrom = TypedArray.from;
117 | const TypedArrayPrototype = TypedArray.prototype;
118 | const NativeTypedArrayPrototypeSymbolIterator = TypedArrayPrototype[SymbolIterator];
119 | const TypedArrayPrototypeKeys = uncurryThis(TypedArrayPrototype.keys);
120 | const TypedArrayPrototypeValues = uncurryThis(
121 | TypedArrayPrototype.values
122 | );
123 | const TypedArrayPrototypeEntries = uncurryThis(
124 | TypedArrayPrototype.entries
125 | );
126 | const TypedArrayPrototypeSet = uncurryThis(TypedArrayPrototype.set);
127 | const TypedArrayPrototypeReverse = uncurryThis(
128 | TypedArrayPrototype.reverse
129 | );
130 | const TypedArrayPrototypeFill = uncurryThis(TypedArrayPrototype.fill);
131 | const TypedArrayPrototypeCopyWithin = uncurryThis(
132 | TypedArrayPrototype.copyWithin
133 | );
134 | const TypedArrayPrototypeSort = uncurryThis(TypedArrayPrototype.sort);
135 | const TypedArrayPrototypeSlice = uncurryThis(TypedArrayPrototype.slice);
136 | const TypedArrayPrototypeSubarray = uncurryThis(
137 | TypedArrayPrototype.subarray
138 | );
139 | const TypedArrayPrototypeGetBuffer = uncurryThisGetter(
140 | TypedArrayPrototype,
141 | "buffer"
142 | );
143 | const TypedArrayPrototypeGetByteOffset = uncurryThisGetter(
144 | TypedArrayPrototype,
145 | "byteOffset"
146 | );
147 | const TypedArrayPrototypeGetLength = uncurryThisGetter(
148 | TypedArrayPrototype,
149 | "length"
150 | );
151 | const TypedArrayPrototypeGetSymbolToStringTag = uncurryThisGetter(
152 | TypedArrayPrototype,
153 | SymbolToStringTag
154 | );
155 | const NativeUint8Array = Uint8Array;
156 | const NativeUint16Array = Uint16Array;
157 | const Uint16ArrayFrom = (...args) => {
158 | return ReflectApply(TypedArrayFrom, NativeUint16Array, args);
159 | };
160 | const NativeUint32Array = Uint32Array;
161 | const NativeFloat32Array = Float32Array;
162 | const ArrayIteratorPrototype = ReflectGetPrototypeOf([][SymbolIterator]());
163 | const ArrayIteratorPrototypeNext = uncurryThis(ArrayIteratorPrototype.next);
164 | const GeneratorPrototypeNext = uncurryThis((function* () {})().next);
165 | const IteratorPrototype = ReflectGetPrototypeOf(ArrayIteratorPrototype);
166 | const DataViewPrototype = DataView.prototype;
167 | const DataViewPrototypeGetUint16 = uncurryThis(
168 | DataViewPrototype.getUint16
169 | );
170 | const DataViewPrototypeSetUint16 = uncurryThis(
171 | DataViewPrototype.setUint16
172 | );
173 | const NativeTypeError = TypeError;
174 | const NativeRangeError = RangeError;
175 | const NativeWeakSet = WeakSet;
176 | const WeakSetPrototype = NativeWeakSet.prototype;
177 | const WeakSetPrototypeAdd = uncurryThis(WeakSetPrototype.add);
178 | const WeakSetPrototypeHas = uncurryThis(WeakSetPrototype.has);
179 | const NativeWeakMap = WeakMap;
180 | const WeakMapPrototype = NativeWeakMap.prototype;
181 | const WeakMapPrototypeGet = uncurryThis(WeakMapPrototype.get);
182 | const WeakMapPrototypeHas = uncurryThis(WeakMapPrototype.has);
183 | const WeakMapPrototypeSet = uncurryThis(WeakMapPrototype.set);
184 |
185 | const arrayIterators = new NativeWeakMap();
186 | const SafeIteratorPrototype = ObjectCreate(null, {
187 | next: {
188 | value: function next() {
189 | const arrayIterator = WeakMapPrototypeGet(arrayIterators, this);
190 | return ArrayIteratorPrototypeNext(arrayIterator);
191 | },
192 | },
193 | [SymbolIterator]: {
194 | value: function values() {
195 | return this;
196 | },
197 | },
198 | });
199 | function safeIfNeeded(array) {
200 | if (
201 | array[SymbolIterator] === NativeArrayPrototypeSymbolIterator &&
202 | ArrayIteratorPrototype.next === ArrayIteratorPrototypeNext
203 | ) {
204 | return array;
205 | }
206 | const safe = ObjectCreate(SafeIteratorPrototype);
207 | WeakMapPrototypeSet(arrayIterators, safe, ArrayPrototypeSymbolIterator(array));
208 | return safe;
209 | }
210 | const generators = new NativeWeakMap();
211 | const DummyArrayIteratorPrototype = ObjectCreate(IteratorPrototype, {
212 | next: {
213 | value: function next() {
214 | const generator = WeakMapPrototypeGet(generators, this);
215 | return GeneratorPrototypeNext(generator);
216 | },
217 | writable: true,
218 | configurable: true,
219 | },
220 | });
221 | for (const key of ReflectOwnKeys(ArrayIteratorPrototype)) {
222 | if (key === "next") {
223 | continue;
224 | }
225 | ObjectDefineProperty(DummyArrayIteratorPrototype, key, ReflectGetOwnPropertyDescriptor(ArrayIteratorPrototype, key));
226 | }
227 | function wrap(generator) {
228 | const dummy = ObjectCreate(DummyArrayIteratorPrototype);
229 | WeakMapPrototypeSet(generators, dummy, generator);
230 | return dummy;
231 | }
232 |
233 | function isObject(value) {
234 | return (
235 | (value !== null && typeof value === "object") ||
236 | typeof value === "function"
237 | );
238 | }
239 | function isObjectLike(value) {
240 | return value !== null && typeof value === "object";
241 | }
242 | function isNativeTypedArray(value) {
243 | return TypedArrayPrototypeGetSymbolToStringTag(value) !== undefined;
244 | }
245 | function isNativeBigIntTypedArray(value) {
246 | const typedArrayName = TypedArrayPrototypeGetSymbolToStringTag(value);
247 | return (
248 | typedArrayName === "BigInt64Array" ||
249 | typedArrayName === "BigUint64Array"
250 | );
251 | }
252 | function isArrayBuffer(value) {
253 | try {
254 | if (ArrayIsArray(value)) {
255 | return false;
256 | }
257 | ArrayBufferPrototypeGetByteLength( (value));
258 | return true;
259 | } catch (e) {
260 | return false;
261 | }
262 | }
263 | function isSharedArrayBuffer(value) {
264 | if (NativeSharedArrayBuffer === null) {
265 | return false;
266 | }
267 | try {
268 | SharedArrayBufferPrototypeGetByteLength( (value));
269 | return true;
270 | } catch (e) {
271 | return false;
272 | }
273 | }
274 | function isAnyArrayBuffer(value) {
275 | return isArrayBuffer(value) || isSharedArrayBuffer(value);
276 | }
277 | function isOrdinaryArray(value) {
278 | if (!ArrayIsArray(value)) {
279 | return false;
280 | }
281 | return (
282 | value[SymbolIterator] === NativeArrayPrototypeSymbolIterator &&
283 | ArrayIteratorPrototype.next === ArrayIteratorPrototypeNext
284 | );
285 | }
286 | function isOrdinaryNativeTypedArray(value) {
287 | if (!isNativeTypedArray(value)) {
288 | return false;
289 | }
290 | return (
291 | value[SymbolIterator] === NativeTypedArrayPrototypeSymbolIterator &&
292 | ArrayIteratorPrototype.next === ArrayIteratorPrototypeNext
293 | );
294 | }
295 | function isCanonicalIntegerIndexString(value) {
296 | if (typeof value !== "string") {
297 | return false;
298 | }
299 | const number = +value;
300 | if (value !== number + "") {
301 | return false;
302 | }
303 | if (!NumberIsFinite(number)) {
304 | return false;
305 | }
306 | return number === MathTrunc(number);
307 | }
308 |
309 | const brand = SymbolFor("__Float16Array__");
310 | function hasFloat16ArrayBrand(target) {
311 | if (!isObjectLike(target)) {
312 | return false;
313 | }
314 | const prototype = ReflectGetPrototypeOf(target);
315 | if (!isObjectLike(prototype)) {
316 | return false;
317 | }
318 | const constructor = prototype.constructor;
319 | if (constructor === undefined) {
320 | return false;
321 | }
322 | if (!isObject(constructor)) {
323 | throw NativeTypeError(THE_CONSTRUCTOR_PROPERTY_VALUE_IS_NOT_AN_OBJECT);
324 | }
325 | return ReflectHas(constructor, brand);
326 | }
327 |
328 | const INVERSE_OF_EPSILON = 1 / EPSILON;
329 | function roundTiesToEven(num) {
330 | return (num + INVERSE_OF_EPSILON) - INVERSE_OF_EPSILON;
331 | }
332 | const FLOAT16_MIN_VALUE = 6.103515625e-05;
333 | const FLOAT16_MAX_VALUE = 65504;
334 | const FLOAT16_EPSILON = 0.0009765625;
335 | const FLOAT16_EPSILON_MULTIPLIED_BY_FLOAT16_MIN_VALUE = FLOAT16_EPSILON * FLOAT16_MIN_VALUE;
336 | const FLOAT16_EPSILON_DEVIDED_BY_EPSILON = FLOAT16_EPSILON * INVERSE_OF_EPSILON;
337 | function roundToFloat16(num) {
338 | const number = +num;
339 | if (!NumberIsFinite(number) || number === 0) {
340 | return number;
341 | }
342 | const sign = number > 0 ? 1 : -1;
343 | const absolute = MathAbs(number);
344 | if (absolute < FLOAT16_MIN_VALUE) {
345 | return sign * roundTiesToEven(absolute / FLOAT16_EPSILON_MULTIPLIED_BY_FLOAT16_MIN_VALUE) * FLOAT16_EPSILON_MULTIPLIED_BY_FLOAT16_MIN_VALUE;
346 | }
347 | const temp = (1 + FLOAT16_EPSILON_DEVIDED_BY_EPSILON) * absolute;
348 | const result = temp - (temp - absolute);
349 | if (result > FLOAT16_MAX_VALUE || NumberIsNaN(result)) {
350 | return sign * Infinity;
351 | }
352 | return sign * result;
353 | }
354 | const buffer = new NativeArrayBuffer(4);
355 | const floatView = new NativeFloat32Array(buffer);
356 | const uint32View = new NativeUint32Array(buffer);
357 | const baseTable = new NativeUint16Array(512);
358 | const shiftTable = new NativeUint8Array(512);
359 | for (let i = 0; i < 256; ++i) {
360 | const e = i - 127;
361 | if (e < -27) {
362 | baseTable[i] = 0x0000;
363 | baseTable[i | 0x100] = 0x8000;
364 | shiftTable[i] = 24;
365 | shiftTable[i | 0x100] = 24;
366 | } else if (e < -14) {
367 | baseTable[i] = 0x0400 >> (-e - 14);
368 | baseTable[i | 0x100] = (0x0400 >> (-e - 14)) | 0x8000;
369 | shiftTable[i] = -e - 1;
370 | shiftTable[i | 0x100] = -e - 1;
371 | } else if (e <= 15) {
372 | baseTable[i] = (e + 15) << 10;
373 | baseTable[i | 0x100] = ((e + 15) << 10) | 0x8000;
374 | shiftTable[i] = 13;
375 | shiftTable[i | 0x100] = 13;
376 | } else if (e < 128) {
377 | baseTable[i] = 0x7c00;
378 | baseTable[i | 0x100] = 0xfc00;
379 | shiftTable[i] = 24;
380 | shiftTable[i | 0x100] = 24;
381 | } else {
382 | baseTable[i] = 0x7c00;
383 | baseTable[i | 0x100] = 0xfc00;
384 | shiftTable[i] = 13;
385 | shiftTable[i | 0x100] = 13;
386 | }
387 | }
388 | function roundToFloat16Bits(num) {
389 | floatView[0] = roundToFloat16(num);
390 | const f = uint32View[0];
391 | const e = (f >> 23) & 0x1ff;
392 | return baseTable[e] + ((f & 0x007fffff) >> shiftTable[e]);
393 | }
394 | const mantissaTable = new NativeUint32Array(2048);
395 | for (let i = 1; i < 1024; ++i) {
396 | let m = i << 13;
397 | let e = 0;
398 | while ((m & 0x00800000) === 0) {
399 | m <<= 1;
400 | e -= 0x00800000;
401 | }
402 | m &= ~0x00800000;
403 | e += 0x38800000;
404 | mantissaTable[i] = m | e;
405 | }
406 | for (let i = 1024; i < 2048; ++i) {
407 | mantissaTable[i] = 0x38000000 + ((i - 1024) << 13);
408 | }
409 | const exponentTable = new NativeUint32Array(64);
410 | for (let i = 1; i < 31; ++i) {
411 | exponentTable[i] = i << 23;
412 | }
413 | exponentTable[31] = 0x47800000;
414 | exponentTable[32] = 0x80000000;
415 | for (let i = 33; i < 63; ++i) {
416 | exponentTable[i] = 0x80000000 + ((i - 32) << 23);
417 | }
418 | exponentTable[63] = 0xc7800000;
419 | const offsetTable = new NativeUint16Array(64);
420 | for (let i = 1; i < 64; ++i) {
421 | if (i !== 32) {
422 | offsetTable[i] = 1024;
423 | }
424 | }
425 | function convertToNumber(float16bits) {
426 | const i = float16bits >> 10;
427 | uint32View[0] = mantissaTable[offsetTable[i] + (float16bits & 0x3ff)] + exponentTable[i];
428 | return floatView[0];
429 | }
430 |
431 | function ToIntegerOrInfinity(target) {
432 | const number = +target;
433 | if (NumberIsNaN(number) || number === 0) {
434 | return 0;
435 | }
436 | return MathTrunc(number);
437 | }
438 | function ToLength(target) {
439 | const length = ToIntegerOrInfinity(target);
440 | if (length < 0) {
441 | return 0;
442 | }
443 | return length < MAX_SAFE_INTEGER
444 | ? length
445 | : MAX_SAFE_INTEGER;
446 | }
447 | function SpeciesConstructor(target, defaultConstructor) {
448 | if (!isObject(target)) {
449 | throw NativeTypeError(THIS_IS_NOT_AN_OBJECT);
450 | }
451 | const constructor = target.constructor;
452 | if (constructor === undefined) {
453 | return defaultConstructor;
454 | }
455 | if (!isObject(constructor)) {
456 | throw NativeTypeError(THE_CONSTRUCTOR_PROPERTY_VALUE_IS_NOT_AN_OBJECT);
457 | }
458 | const species = constructor[SymbolSpecies];
459 | if (species == null) {
460 | return defaultConstructor;
461 | }
462 | return species;
463 | }
464 | function IsDetachedBuffer(buffer) {
465 | if (isSharedArrayBuffer(buffer)) {
466 | return false;
467 | }
468 | try {
469 | ArrayBufferPrototypeSlice(buffer, 0, 0);
470 | return false;
471 | } catch (e) {}
472 | return true;
473 | }
474 | function defaultCompare(x, y) {
475 | const isXNaN = NumberIsNaN(x);
476 | const isYNaN = NumberIsNaN(y);
477 | if (isXNaN && isYNaN) {
478 | return 0;
479 | }
480 | if (isXNaN) {
481 | return 1;
482 | }
483 | if (isYNaN) {
484 | return -1;
485 | }
486 | if (x < y) {
487 | return -1;
488 | }
489 | if (x > y) {
490 | return 1;
491 | }
492 | if (x === 0 && y === 0) {
493 | const isXPlusZero = ObjectIs(x, 0);
494 | const isYPlusZero = ObjectIs(y, 0);
495 | if (!isXPlusZero && isYPlusZero) {
496 | return -1;
497 | }
498 | if (isXPlusZero && !isYPlusZero) {
499 | return 1;
500 | }
501 | }
502 | return 0;
503 | }
504 |
505 | const BYTES_PER_ELEMENT = 2;
506 | const float16bitsArrays = new NativeWeakMap();
507 | function isFloat16Array(target) {
508 | return WeakMapPrototypeHas(float16bitsArrays, target) ||
509 | (!ArrayBufferIsView(target) && hasFloat16ArrayBrand(target));
510 | }
511 | function assertFloat16Array(target) {
512 | if (!isFloat16Array(target)) {
513 | throw NativeTypeError(THIS_IS_NOT_A_FLOAT16ARRAY_OBJECT);
514 | }
515 | }
516 | function assertSpeciesTypedArray(target, count) {
517 | const isTargetFloat16Array = isFloat16Array(target);
518 | const isTargetTypedArray = isNativeTypedArray(target);
519 | if (!isTargetFloat16Array && !isTargetTypedArray) {
520 | throw NativeTypeError(SPECIES_CONSTRUCTOR_DIDNT_RETURN_TYPEDARRAY_OBJECT);
521 | }
522 | if (typeof count === "number") {
523 | let length;
524 | if (isTargetFloat16Array) {
525 | const float16bitsArray = getFloat16BitsArray(target);
526 | length = TypedArrayPrototypeGetLength(float16bitsArray);
527 | } else {
528 | length = TypedArrayPrototypeGetLength(target);
529 | }
530 | if (length < count) {
531 | throw NativeTypeError(
532 | DERIVED_CONSTRUCTOR_CREATED_TYPEDARRAY_OBJECT_WHICH_WAS_TOO_SMALL_LENGTH
533 | );
534 | }
535 | }
536 | if (isNativeBigIntTypedArray(target)) {
537 | throw NativeTypeError(CANNOT_MIX_BIGINT_AND_OTHER_TYPES);
538 | }
539 | }
540 | function getFloat16BitsArray(float16) {
541 | const float16bitsArray = WeakMapPrototypeGet(float16bitsArrays, float16);
542 | if (float16bitsArray !== undefined) {
543 | const buffer = TypedArrayPrototypeGetBuffer(float16bitsArray);
544 | if (IsDetachedBuffer(buffer)) {
545 | throw NativeTypeError(ATTEMPTING_TO_ACCESS_DETACHED_ARRAYBUFFER);
546 | }
547 | return float16bitsArray;
548 | }
549 | const buffer = (float16).buffer;
550 | if (IsDetachedBuffer(buffer)) {
551 | throw NativeTypeError(ATTEMPTING_TO_ACCESS_DETACHED_ARRAYBUFFER);
552 | }
553 | const cloned = ReflectConstruct(Float16Array, [
554 | buffer,
555 | (float16).byteOffset,
556 | (float16).length,
557 | ], float16.constructor);
558 | return WeakMapPrototypeGet(float16bitsArrays, cloned);
559 | }
560 | function copyToArray(float16bitsArray) {
561 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
562 | const array = [];
563 | for (let i = 0; i < length; ++i) {
564 | array[i] = convertToNumber(float16bitsArray[i]);
565 | }
566 | return array;
567 | }
568 | const TypedArrayPrototypeGetters = new NativeWeakSet();
569 | for (const key of ReflectOwnKeys(TypedArrayPrototype)) {
570 | if (key === SymbolToStringTag) {
571 | continue;
572 | }
573 | const descriptor = ReflectGetOwnPropertyDescriptor(TypedArrayPrototype, key);
574 | if (ObjectHasOwn(descriptor, "get") && typeof descriptor.get === "function") {
575 | WeakSetPrototypeAdd(TypedArrayPrototypeGetters, descriptor.get);
576 | }
577 | }
578 | const handler = ObjectFreeze( ({
579 | get(target, key, receiver) {
580 | if (isCanonicalIntegerIndexString(key) && ObjectHasOwn(target, key)) {
581 | return convertToNumber(ReflectGet(target, key));
582 | }
583 | if (WeakSetPrototypeHas(TypedArrayPrototypeGetters, ObjectPrototype__lookupGetter__(target, key))) {
584 | return ReflectGet(target, key);
585 | }
586 | return ReflectGet(target, key, receiver);
587 | },
588 | set(target, key, value, receiver) {
589 | if (isCanonicalIntegerIndexString(key) && ObjectHasOwn(target, key)) {
590 | return ReflectSet(target, key, roundToFloat16Bits(value));
591 | }
592 | return ReflectSet(target, key, value, receiver);
593 | },
594 | getOwnPropertyDescriptor(target, key) {
595 | if (isCanonicalIntegerIndexString(key) && ObjectHasOwn(target, key)) {
596 | const descriptor = ReflectGetOwnPropertyDescriptor(target, key);
597 | descriptor.value = convertToNumber(descriptor.value);
598 | return descriptor;
599 | }
600 | return ReflectGetOwnPropertyDescriptor(target, key);
601 | },
602 | defineProperty(target, key, descriptor) {
603 | if (
604 | isCanonicalIntegerIndexString(key) &&
605 | ObjectHasOwn(target, key) &&
606 | ObjectHasOwn(descriptor, "value")
607 | ) {
608 | descriptor.value = roundToFloat16Bits(descriptor.value);
609 | return ReflectDefineProperty(target, key, descriptor);
610 | }
611 | return ReflectDefineProperty(target, key, descriptor);
612 | },
613 | }));
614 | class Float16Array {
615 | constructor(input, _byteOffset, _length) {
616 | let float16bitsArray;
617 | if (isFloat16Array(input)) {
618 | float16bitsArray = ReflectConstruct(NativeUint16Array, [getFloat16BitsArray(input)], new.target);
619 | } else if (isObject(input) && !isAnyArrayBuffer(input)) {
620 | let list;
621 | let length;
622 | if (isNativeTypedArray(input)) {
623 | list = input;
624 | length = TypedArrayPrototypeGetLength(input);
625 | const buffer = TypedArrayPrototypeGetBuffer(input);
626 | if (IsDetachedBuffer(buffer)) {
627 | throw NativeTypeError(ATTEMPTING_TO_ACCESS_DETACHED_ARRAYBUFFER);
628 | }
629 | if (isNativeBigIntTypedArray(input)) {
630 | throw NativeTypeError(CANNOT_MIX_BIGINT_AND_OTHER_TYPES);
631 | }
632 | const data = new NativeArrayBuffer(
633 | length * BYTES_PER_ELEMENT
634 | );
635 | float16bitsArray = ReflectConstruct(NativeUint16Array, [data], new.target);
636 | } else {
637 | const iterator = input[SymbolIterator];
638 | if (iterator != null && typeof iterator !== "function") {
639 | throw NativeTypeError(ITERATOR_PROPERTY_IS_NOT_CALLABLE);
640 | }
641 | if (iterator != null) {
642 | if (isOrdinaryArray(input)) {
643 | list = input;
644 | length = input.length;
645 | } else {
646 | list = [... (input)];
647 | length = list.length;
648 | }
649 | } else {
650 | list = (input);
651 | length = ToLength(list.length);
652 | }
653 | float16bitsArray = ReflectConstruct(NativeUint16Array, [length], new.target);
654 | }
655 | for (let i = 0; i < length; ++i) {
656 | float16bitsArray[i] = roundToFloat16Bits(list[i]);
657 | }
658 | } else {
659 | float16bitsArray = ReflectConstruct(NativeUint16Array, arguments, new.target);
660 | }
661 | const proxy = (new NativeProxy(float16bitsArray, handler));
662 | WeakMapPrototypeSet(float16bitsArrays, proxy, float16bitsArray);
663 | return proxy;
664 | }
665 | static from(src, ...opts) {
666 | const Constructor = this;
667 | if (!ReflectHas(Constructor, brand)) {
668 | throw NativeTypeError(
669 | THIS_CONSTRUCTOR_IS_NOT_A_SUBCLASS_OF_FLOAT16ARRAY
670 | );
671 | }
672 | if (Constructor === Float16Array) {
673 | if (isFloat16Array(src) && opts.length === 0) {
674 | const float16bitsArray = getFloat16BitsArray(src);
675 | const uint16 = new NativeUint16Array(
676 | TypedArrayPrototypeGetBuffer(float16bitsArray),
677 | TypedArrayPrototypeGetByteOffset(float16bitsArray),
678 | TypedArrayPrototypeGetLength(float16bitsArray)
679 | );
680 | return new Float16Array(
681 | TypedArrayPrototypeGetBuffer(TypedArrayPrototypeSlice(uint16))
682 | );
683 | }
684 | if (opts.length === 0) {
685 | return new Float16Array(
686 | TypedArrayPrototypeGetBuffer(
687 | Uint16ArrayFrom(src, roundToFloat16Bits)
688 | )
689 | );
690 | }
691 | const mapFunc = opts[0];
692 | const thisArg = opts[1];
693 | return new Float16Array(
694 | TypedArrayPrototypeGetBuffer(
695 | Uint16ArrayFrom(src, function (val, ...args) {
696 | return roundToFloat16Bits(
697 | ReflectApply(mapFunc, this, [val, ...safeIfNeeded(args)])
698 | );
699 | }, thisArg)
700 | )
701 | );
702 | }
703 | let list;
704 | let length;
705 | const iterator = src[SymbolIterator];
706 | if (iterator != null && typeof iterator !== "function") {
707 | throw NativeTypeError(ITERATOR_PROPERTY_IS_NOT_CALLABLE);
708 | }
709 | if (iterator != null) {
710 | if (isOrdinaryArray(src)) {
711 | list = src;
712 | length = src.length;
713 | } else if (isOrdinaryNativeTypedArray(src)) {
714 | list = src;
715 | length = TypedArrayPrototypeGetLength(src);
716 | } else {
717 | list = [...src];
718 | length = list.length;
719 | }
720 | } else {
721 | if (src == null) {
722 | throw NativeTypeError(
723 | CANNOT_CONVERT_UNDEFINED_OR_NULL_TO_OBJECT
724 | );
725 | }
726 | list = NativeObject(src);
727 | length = ToLength(list.length);
728 | }
729 | const array = new Constructor(length);
730 | if (opts.length === 0) {
731 | for (let i = 0; i < length; ++i) {
732 | array[i] = (list[i]);
733 | }
734 | } else {
735 | const mapFunc = opts[0];
736 | const thisArg = opts[1];
737 | for (let i = 0; i < length; ++i) {
738 | array[i] = ReflectApply(mapFunc, thisArg, [list[i], i]);
739 | }
740 | }
741 | return array;
742 | }
743 | static of(...items) {
744 | const Constructor = this;
745 | if (!ReflectHas(Constructor, brand)) {
746 | throw NativeTypeError(
747 | THIS_CONSTRUCTOR_IS_NOT_A_SUBCLASS_OF_FLOAT16ARRAY
748 | );
749 | }
750 | const length = items.length;
751 | if (Constructor === Float16Array) {
752 | const proxy = new Float16Array(length);
753 | const float16bitsArray = getFloat16BitsArray(proxy);
754 | for (let i = 0; i < length; ++i) {
755 | float16bitsArray[i] = roundToFloat16Bits(items[i]);
756 | }
757 | return proxy;
758 | }
759 | const array = new Constructor(length);
760 | for (let i = 0; i < length; ++i) {
761 | array[i] = items[i];
762 | }
763 | return array;
764 | }
765 | keys() {
766 | assertFloat16Array(this);
767 | const float16bitsArray = getFloat16BitsArray(this);
768 | return TypedArrayPrototypeKeys(float16bitsArray);
769 | }
770 | values() {
771 | assertFloat16Array(this);
772 | const float16bitsArray = getFloat16BitsArray(this);
773 | return wrap((function* () {
774 | for (const val of TypedArrayPrototypeValues(float16bitsArray)) {
775 | yield convertToNumber(val);
776 | }
777 | })());
778 | }
779 | entries() {
780 | assertFloat16Array(this);
781 | const float16bitsArray = getFloat16BitsArray(this);
782 | return wrap((function* () {
783 | for (const [i, val] of TypedArrayPrototypeEntries(float16bitsArray)) {
784 | yield ([i, convertToNumber(val)]);
785 | }
786 | })());
787 | }
788 | at(index) {
789 | assertFloat16Array(this);
790 | const float16bitsArray = getFloat16BitsArray(this);
791 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
792 | const relativeIndex = ToIntegerOrInfinity(index);
793 | const k = relativeIndex >= 0 ? relativeIndex : length + relativeIndex;
794 | if (k < 0 || k >= length) {
795 | return;
796 | }
797 | return convertToNumber(float16bitsArray[k]);
798 | }
799 | with(index, value) {
800 | assertFloat16Array(this);
801 | const float16bitsArray = getFloat16BitsArray(this);
802 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
803 | const relativeIndex = ToIntegerOrInfinity(index);
804 | const k = relativeIndex >= 0 ? relativeIndex : length + relativeIndex;
805 | const number = +value;
806 | if (k < 0 || k >= length) {
807 | throw NativeRangeError(OFFSET_IS_OUT_OF_BOUNDS);
808 | }
809 | const uint16 = new NativeUint16Array(
810 | TypedArrayPrototypeGetBuffer(float16bitsArray),
811 | TypedArrayPrototypeGetByteOffset(float16bitsArray),
812 | TypedArrayPrototypeGetLength(float16bitsArray)
813 | );
814 | const cloned = new Float16Array(
815 | TypedArrayPrototypeGetBuffer(
816 | TypedArrayPrototypeSlice(uint16)
817 | )
818 | );
819 | const array = getFloat16BitsArray(cloned);
820 | array[k] = roundToFloat16Bits(number);
821 | return cloned;
822 | }
823 | map(callback, ...opts) {
824 | assertFloat16Array(this);
825 | const float16bitsArray = getFloat16BitsArray(this);
826 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
827 | const thisArg = opts[0];
828 | const Constructor = SpeciesConstructor(float16bitsArray, Float16Array);
829 | if (Constructor === Float16Array) {
830 | const proxy = new Float16Array(length);
831 | const array = getFloat16BitsArray(proxy);
832 | for (let i = 0; i < length; ++i) {
833 | const val = convertToNumber(float16bitsArray[i]);
834 | array[i] = roundToFloat16Bits(
835 | ReflectApply(callback, thisArg, [val, i, this])
836 | );
837 | }
838 | return proxy;
839 | }
840 | const array = new Constructor(length);
841 | assertSpeciesTypedArray(array, length);
842 | for (let i = 0; i < length; ++i) {
843 | const val = convertToNumber(float16bitsArray[i]);
844 | array[i] = ReflectApply(callback, thisArg, [val, i, this]);
845 | }
846 | return (array);
847 | }
848 | filter(callback, ...opts) {
849 | assertFloat16Array(this);
850 | const float16bitsArray = getFloat16BitsArray(this);
851 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
852 | const thisArg = opts[0];
853 | const kept = [];
854 | for (let i = 0; i < length; ++i) {
855 | const val = convertToNumber(float16bitsArray[i]);
856 | if (ReflectApply(callback, thisArg, [val, i, this])) {
857 | ArrayPrototypePush(kept, val);
858 | }
859 | }
860 | const Constructor = SpeciesConstructor(float16bitsArray, Float16Array);
861 | const array = new Constructor(kept);
862 | assertSpeciesTypedArray(array);
863 | return (array);
864 | }
865 | reduce(callback, ...opts) {
866 | assertFloat16Array(this);
867 | const float16bitsArray = getFloat16BitsArray(this);
868 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
869 | if (length === 0 && opts.length === 0) {
870 | throw NativeTypeError(REDUCE_OF_EMPTY_ARRAY_WITH_NO_INITIAL_VALUE);
871 | }
872 | let accumulator, start;
873 | if (opts.length === 0) {
874 | accumulator = convertToNumber(float16bitsArray[0]);
875 | start = 1;
876 | } else {
877 | accumulator = opts[0];
878 | start = 0;
879 | }
880 | for (let i = start; i < length; ++i) {
881 | accumulator = callback(
882 | accumulator,
883 | convertToNumber(float16bitsArray[i]),
884 | i,
885 | this
886 | );
887 | }
888 | return accumulator;
889 | }
890 | reduceRight(callback, ...opts) {
891 | assertFloat16Array(this);
892 | const float16bitsArray = getFloat16BitsArray(this);
893 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
894 | if (length === 0 && opts.length === 0) {
895 | throw NativeTypeError(REDUCE_OF_EMPTY_ARRAY_WITH_NO_INITIAL_VALUE);
896 | }
897 | let accumulator, start;
898 | if (opts.length === 0) {
899 | accumulator = convertToNumber(float16bitsArray[length - 1]);
900 | start = length - 2;
901 | } else {
902 | accumulator = opts[0];
903 | start = length - 1;
904 | }
905 | for (let i = start; i >= 0; --i) {
906 | accumulator = callback(
907 | accumulator,
908 | convertToNumber(float16bitsArray[i]),
909 | i,
910 | this
911 | );
912 | }
913 | return accumulator;
914 | }
915 | forEach(callback, ...opts) {
916 | assertFloat16Array(this);
917 | const float16bitsArray = getFloat16BitsArray(this);
918 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
919 | const thisArg = opts[0];
920 | for (let i = 0; i < length; ++i) {
921 | ReflectApply(callback, thisArg, [
922 | convertToNumber(float16bitsArray[i]),
923 | i,
924 | this,
925 | ]);
926 | }
927 | }
928 | find(callback, ...opts) {
929 | assertFloat16Array(this);
930 | const float16bitsArray = getFloat16BitsArray(this);
931 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
932 | const thisArg = opts[0];
933 | for (let i = 0; i < length; ++i) {
934 | const value = convertToNumber(float16bitsArray[i]);
935 | if (ReflectApply(callback, thisArg, [value, i, this])) {
936 | return value;
937 | }
938 | }
939 | }
940 | findIndex(callback, ...opts) {
941 | assertFloat16Array(this);
942 | const float16bitsArray = getFloat16BitsArray(this);
943 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
944 | const thisArg = opts[0];
945 | for (let i = 0; i < length; ++i) {
946 | const value = convertToNumber(float16bitsArray[i]);
947 | if (ReflectApply(callback, thisArg, [value, i, this])) {
948 | return i;
949 | }
950 | }
951 | return -1;
952 | }
953 | findLast(callback, ...opts) {
954 | assertFloat16Array(this);
955 | const float16bitsArray = getFloat16BitsArray(this);
956 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
957 | const thisArg = opts[0];
958 | for (let i = length - 1; i >= 0; --i) {
959 | const value = convertToNumber(float16bitsArray[i]);
960 | if (ReflectApply(callback, thisArg, [value, i, this])) {
961 | return value;
962 | }
963 | }
964 | }
965 | findLastIndex(callback, ...opts) {
966 | assertFloat16Array(this);
967 | const float16bitsArray = getFloat16BitsArray(this);
968 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
969 | const thisArg = opts[0];
970 | for (let i = length - 1; i >= 0; --i) {
971 | const value = convertToNumber(float16bitsArray[i]);
972 | if (ReflectApply(callback, thisArg, [value, i, this])) {
973 | return i;
974 | }
975 | }
976 | return -1;
977 | }
978 | every(callback, ...opts) {
979 | assertFloat16Array(this);
980 | const float16bitsArray = getFloat16BitsArray(this);
981 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
982 | const thisArg = opts[0];
983 | for (let i = 0; i < length; ++i) {
984 | if (
985 | !ReflectApply(callback, thisArg, [
986 | convertToNumber(float16bitsArray[i]),
987 | i,
988 | this,
989 | ])
990 | ) {
991 | return false;
992 | }
993 | }
994 | return true;
995 | }
996 | some(callback, ...opts) {
997 | assertFloat16Array(this);
998 | const float16bitsArray = getFloat16BitsArray(this);
999 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
1000 | const thisArg = opts[0];
1001 | for (let i = 0; i < length; ++i) {
1002 | if (
1003 | ReflectApply(callback, thisArg, [
1004 | convertToNumber(float16bitsArray[i]),
1005 | i,
1006 | this,
1007 | ])
1008 | ) {
1009 | return true;
1010 | }
1011 | }
1012 | return false;
1013 | }
1014 | set(input, ...opts) {
1015 | assertFloat16Array(this);
1016 | const float16bitsArray = getFloat16BitsArray(this);
1017 | const targetOffset = ToIntegerOrInfinity(opts[0]);
1018 | if (targetOffset < 0) {
1019 | throw NativeRangeError(OFFSET_IS_OUT_OF_BOUNDS);
1020 | }
1021 | if (input == null) {
1022 | throw NativeTypeError(
1023 | CANNOT_CONVERT_UNDEFINED_OR_NULL_TO_OBJECT
1024 | );
1025 | }
1026 | if (isNativeBigIntTypedArray(input)) {
1027 | throw NativeTypeError(
1028 | CANNOT_MIX_BIGINT_AND_OTHER_TYPES
1029 | );
1030 | }
1031 | if (isFloat16Array(input)) {
1032 | return TypedArrayPrototypeSet(
1033 | getFloat16BitsArray(this),
1034 | getFloat16BitsArray(input),
1035 | targetOffset
1036 | );
1037 | }
1038 | if (isNativeTypedArray(input)) {
1039 | const buffer = TypedArrayPrototypeGetBuffer(input);
1040 | if (IsDetachedBuffer(buffer)) {
1041 | throw NativeTypeError(ATTEMPTING_TO_ACCESS_DETACHED_ARRAYBUFFER);
1042 | }
1043 | }
1044 | const targetLength = TypedArrayPrototypeGetLength(float16bitsArray);
1045 | const src = NativeObject(input);
1046 | const srcLength = ToLength(src.length);
1047 | if (targetOffset === Infinity || srcLength + targetOffset > targetLength) {
1048 | throw NativeRangeError(OFFSET_IS_OUT_OF_BOUNDS);
1049 | }
1050 | for (let i = 0; i < srcLength; ++i) {
1051 | float16bitsArray[i + targetOffset] = roundToFloat16Bits(src[i]);
1052 | }
1053 | }
1054 | reverse() {
1055 | assertFloat16Array(this);
1056 | const float16bitsArray = getFloat16BitsArray(this);
1057 | TypedArrayPrototypeReverse(float16bitsArray);
1058 | return this;
1059 | }
1060 | toReversed() {
1061 | assertFloat16Array(this);
1062 | const float16bitsArray = getFloat16BitsArray(this);
1063 | const uint16 = new NativeUint16Array(
1064 | TypedArrayPrototypeGetBuffer(float16bitsArray),
1065 | TypedArrayPrototypeGetByteOffset(float16bitsArray),
1066 | TypedArrayPrototypeGetLength(float16bitsArray)
1067 | );
1068 | const cloned = new Float16Array(
1069 | TypedArrayPrototypeGetBuffer(
1070 | TypedArrayPrototypeSlice(uint16)
1071 | )
1072 | );
1073 | const clonedFloat16bitsArray = getFloat16BitsArray(cloned);
1074 | TypedArrayPrototypeReverse(clonedFloat16bitsArray);
1075 | return cloned;
1076 | }
1077 | fill(value, ...opts) {
1078 | assertFloat16Array(this);
1079 | const float16bitsArray = getFloat16BitsArray(this);
1080 | TypedArrayPrototypeFill(
1081 | float16bitsArray,
1082 | roundToFloat16Bits(value),
1083 | ...safeIfNeeded(opts)
1084 | );
1085 | return this;
1086 | }
1087 | copyWithin(target, start, ...opts) {
1088 | assertFloat16Array(this);
1089 | const float16bitsArray = getFloat16BitsArray(this);
1090 | TypedArrayPrototypeCopyWithin(float16bitsArray, target, start, ...safeIfNeeded(opts));
1091 | return this;
1092 | }
1093 | sort(compareFn) {
1094 | assertFloat16Array(this);
1095 | const float16bitsArray = getFloat16BitsArray(this);
1096 | const sortCompare = compareFn !== undefined ? compareFn : defaultCompare;
1097 | TypedArrayPrototypeSort(float16bitsArray, (x, y) => {
1098 | return sortCompare(convertToNumber(x), convertToNumber(y));
1099 | });
1100 | return this;
1101 | }
1102 | toSorted(compareFn) {
1103 | assertFloat16Array(this);
1104 | const float16bitsArray = getFloat16BitsArray(this);
1105 | if (compareFn !== undefined && typeof compareFn !== "function") {
1106 | throw new NativeTypeError(THE_COMPARISON_FUNCTION_MUST_BE_EITHER_A_FUNCTION_OR_UNDEFINED);
1107 | }
1108 | const sortCompare = compareFn !== undefined ? compareFn : defaultCompare;
1109 | const uint16 = new NativeUint16Array(
1110 | TypedArrayPrototypeGetBuffer(float16bitsArray),
1111 | TypedArrayPrototypeGetByteOffset(float16bitsArray),
1112 | TypedArrayPrototypeGetLength(float16bitsArray)
1113 | );
1114 | const cloned = new Float16Array(
1115 | TypedArrayPrototypeGetBuffer(
1116 | TypedArrayPrototypeSlice(uint16)
1117 | )
1118 | );
1119 | const clonedFloat16bitsArray = getFloat16BitsArray(cloned);
1120 | TypedArrayPrototypeSort(clonedFloat16bitsArray, (x, y) => {
1121 | return sortCompare(convertToNumber(x), convertToNumber(y));
1122 | });
1123 | return cloned;
1124 | }
1125 | slice(start, end) {
1126 | assertFloat16Array(this);
1127 | const float16bitsArray = getFloat16BitsArray(this);
1128 | const Constructor = SpeciesConstructor(float16bitsArray, Float16Array);
1129 | if (Constructor === Float16Array) {
1130 | const uint16 = new NativeUint16Array(
1131 | TypedArrayPrototypeGetBuffer(float16bitsArray),
1132 | TypedArrayPrototypeGetByteOffset(float16bitsArray),
1133 | TypedArrayPrototypeGetLength(float16bitsArray)
1134 | );
1135 | return new Float16Array(
1136 | TypedArrayPrototypeGetBuffer(
1137 | TypedArrayPrototypeSlice(uint16, start, end)
1138 | )
1139 | );
1140 | }
1141 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
1142 | const relativeStart = ToIntegerOrInfinity(start);
1143 | const relativeEnd = end === undefined ? length : ToIntegerOrInfinity(end);
1144 | let k;
1145 | if (relativeStart === -Infinity) {
1146 | k = 0;
1147 | } else if (relativeStart < 0) {
1148 | k = length + relativeStart > 0 ? length + relativeStart : 0;
1149 | } else {
1150 | k = length < relativeStart ? length : relativeStart;
1151 | }
1152 | let final;
1153 | if (relativeEnd === -Infinity) {
1154 | final = 0;
1155 | } else if (relativeEnd < 0) {
1156 | final = length + relativeEnd > 0 ? length + relativeEnd : 0;
1157 | } else {
1158 | final = length < relativeEnd ? length : relativeEnd;
1159 | }
1160 | const count = final - k > 0 ? final - k : 0;
1161 | const array = new Constructor(count);
1162 | assertSpeciesTypedArray(array, count);
1163 | if (count === 0) {
1164 | return array;
1165 | }
1166 | const buffer = TypedArrayPrototypeGetBuffer(float16bitsArray);
1167 | if (IsDetachedBuffer(buffer)) {
1168 | throw NativeTypeError(ATTEMPTING_TO_ACCESS_DETACHED_ARRAYBUFFER);
1169 | }
1170 | let n = 0;
1171 | while (k < final) {
1172 | array[n] = convertToNumber(float16bitsArray[k]);
1173 | ++k;
1174 | ++n;
1175 | }
1176 | return (array);
1177 | }
1178 | subarray(begin, end) {
1179 | assertFloat16Array(this);
1180 | const float16bitsArray = getFloat16BitsArray(this);
1181 | const Constructor = SpeciesConstructor(float16bitsArray, Float16Array);
1182 | const uint16 = new NativeUint16Array(
1183 | TypedArrayPrototypeGetBuffer(float16bitsArray),
1184 | TypedArrayPrototypeGetByteOffset(float16bitsArray),
1185 | TypedArrayPrototypeGetLength(float16bitsArray)
1186 | );
1187 | const uint16Subarray = TypedArrayPrototypeSubarray(uint16, begin, end);
1188 | const array = new Constructor(
1189 | TypedArrayPrototypeGetBuffer(uint16Subarray),
1190 | TypedArrayPrototypeGetByteOffset(uint16Subarray),
1191 | TypedArrayPrototypeGetLength(uint16Subarray)
1192 | );
1193 | assertSpeciesTypedArray(array);
1194 | return (array);
1195 | }
1196 | indexOf(element, ...opts) {
1197 | assertFloat16Array(this);
1198 | const float16bitsArray = getFloat16BitsArray(this);
1199 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
1200 | let from = ToIntegerOrInfinity(opts[0]);
1201 | if (from === Infinity) {
1202 | return -1;
1203 | }
1204 | if (from < 0) {
1205 | from += length;
1206 | if (from < 0) {
1207 | from = 0;
1208 | }
1209 | }
1210 | for (let i = from; i < length; ++i) {
1211 | if (
1212 | ObjectHasOwn(float16bitsArray, i) &&
1213 | convertToNumber(float16bitsArray[i]) === element
1214 | ) {
1215 | return i;
1216 | }
1217 | }
1218 | return -1;
1219 | }
1220 | lastIndexOf(element, ...opts) {
1221 | assertFloat16Array(this);
1222 | const float16bitsArray = getFloat16BitsArray(this);
1223 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
1224 | let from = opts.length >= 1 ? ToIntegerOrInfinity(opts[0]) : length - 1;
1225 | if (from === -Infinity) {
1226 | return -1;
1227 | }
1228 | if (from >= 0) {
1229 | from = from < length - 1 ? from : length - 1;
1230 | } else {
1231 | from += length;
1232 | }
1233 | for (let i = from; i >= 0; --i) {
1234 | if (
1235 | ObjectHasOwn(float16bitsArray, i) &&
1236 | convertToNumber(float16bitsArray[i]) === element
1237 | ) {
1238 | return i;
1239 | }
1240 | }
1241 | return -1;
1242 | }
1243 | includes(element, ...opts) {
1244 | assertFloat16Array(this);
1245 | const float16bitsArray = getFloat16BitsArray(this);
1246 | const length = TypedArrayPrototypeGetLength(float16bitsArray);
1247 | let from = ToIntegerOrInfinity(opts[0]);
1248 | if (from === Infinity) {
1249 | return false;
1250 | }
1251 | if (from < 0) {
1252 | from += length;
1253 | if (from < 0) {
1254 | from = 0;
1255 | }
1256 | }
1257 | const isNaN = NumberIsNaN(element);
1258 | for (let i = from; i < length; ++i) {
1259 | const value = convertToNumber(float16bitsArray[i]);
1260 | if (isNaN && NumberIsNaN(value)) {
1261 | return true;
1262 | }
1263 | if (value === element) {
1264 | return true;
1265 | }
1266 | }
1267 | return false;
1268 | }
1269 | join(separator) {
1270 | assertFloat16Array(this);
1271 | const float16bitsArray = getFloat16BitsArray(this);
1272 | const array = copyToArray(float16bitsArray);
1273 | return ArrayPrototypeJoin(array, separator);
1274 | }
1275 | toLocaleString(...opts) {
1276 | assertFloat16Array(this);
1277 | const float16bitsArray = getFloat16BitsArray(this);
1278 | const array = copyToArray(float16bitsArray);
1279 | return ArrayPrototypeToLocaleString(array, ...safeIfNeeded(opts));
1280 | }
1281 | get [SymbolToStringTag]() {
1282 | if (isFloat16Array(this)) {
1283 | return ("Float16Array");
1284 | }
1285 | }
1286 | }
1287 | ObjectDefineProperty(Float16Array, "BYTES_PER_ELEMENT", {
1288 | value: BYTES_PER_ELEMENT,
1289 | });
1290 | ObjectDefineProperty(Float16Array, brand, {});
1291 | ReflectSetPrototypeOf(Float16Array, TypedArray);
1292 | const Float16ArrayPrototype = Float16Array.prototype;
1293 | ObjectDefineProperty(Float16ArrayPrototype, "BYTES_PER_ELEMENT", {
1294 | value: BYTES_PER_ELEMENT,
1295 | });
1296 | ObjectDefineProperty(Float16ArrayPrototype, SymbolIterator, {
1297 | value: Float16ArrayPrototype.values,
1298 | writable: true,
1299 | configurable: true,
1300 | });
1301 | ReflectSetPrototypeOf(Float16ArrayPrototype, TypedArrayPrototype);
1302 |
1303 | function isTypedArray(target) {
1304 | return isNativeTypedArray(target) || isFloat16Array(target);
1305 | }
1306 |
1307 | function getFloat16(dataView, byteOffset, ...opts) {
1308 | return convertToNumber(
1309 | DataViewPrototypeGetUint16(dataView, byteOffset, ...safeIfNeeded(opts))
1310 | );
1311 | }
1312 | function setFloat16(dataView, byteOffset, value, ...opts) {
1313 | return DataViewPrototypeSetUint16(
1314 | dataView,
1315 | byteOffset,
1316 | roundToFloat16Bits(value),
1317 | ...safeIfNeeded(opts)
1318 | );
1319 | }
1320 |
1321 | function f16round(x) {
1322 | return roundToFloat16(x);
1323 | }
1324 |
1325 | export { Float16Array, f16round, getFloat16, f16round as hfround, isFloat16Array, isTypedArray, setFloat16 };
1326 |
--------------------------------------------------------------------------------
/performance-comparison/js/main.js:
--------------------------------------------------------------------------------
1 | import {ReactionDiffusionCompute} from './rd-compute.js';
2 | import {ReactionDiffusionFragment} from './rd-fragment.js';
3 | import {Composite} from './composite.js';
4 | import {TimingHelper} from './utils/timing-helper.js';
5 | import {RollingAverage} from './utils/rolling-average.js';
6 |
7 | const canvas = document.getElementById('viewport');
8 | const adapter = await navigator.gpu?.requestAdapter();
9 | const canTimestamp = adapter.features.has('timestamp-query');
10 | const device = await adapter?.requestDevice({
11 | requiredFeatures: [ ...(canTimestamp ? ['timestamp-query'] : [])],
12 | });
13 | const context = canvas.getContext('webgpu');
14 | const format = navigator.gpu.getPreferredCanvasFormat();
15 | context.configure({ device, format, alphaMode: 'premultiplied' });
16 | const viewportSize = [
17 | Math.max(1, Math.min(window.innerWidth, device.limits.maxTextureDimension2D)),
18 | Math.max(1, Math.min(window.innerHeight, device.limits.maxTextureDimension2D))
19 | ];
20 | canvas.width = viewportSize[0];
21 | canvas.height = viewportSize[1];
22 |
23 | const useCompute = false;
24 |
25 | const timingHelper = useCompute ? new TimingHelper(device) : new TimingHelper(device, ReactionDiffusionFragment.ITERATIONS * 2);
26 | const rdTime = new RollingAverage(500);
27 | const timeDisplay = document.createElement('span');
28 | timeDisplay.style.position = 'absolute';
29 | timeDisplay.style.top = '0';
30 | timeDisplay.style.left = '0';
31 | timeDisplay.style.fontSize = '1.4em';
32 | timeDisplay.style.color = '#fff';
33 | document.body.appendChild(timeDisplay);
34 |
35 | const reactionDiffusion = useCompute ? new ReactionDiffusionCompute(device, viewportSize) : new ReactionDiffusionFragment(device, viewportSize);
36 | const composite = new Composite(device, reactionDiffusion);
37 |
38 | const run = t => {
39 |
40 | const commandEncoder = device.createCommandEncoder();
41 |
42 | if (useCompute) {
43 | const computePassEncoder = timingHelper.beginComputePass(commandEncoder);
44 | reactionDiffusion.compute(computePassEncoder);
45 | computePassEncoder.end();
46 | } else {
47 | reactionDiffusion.render(commandEncoder, timingHelper);
48 | }
49 |
50 | const compositePassEncoder = commandEncoder.beginRenderPass({
51 | colorAttachments: [{
52 | view: context.getCurrentTexture().createView(),
53 | clearValue: { r: 1, g: 1, b: 1, a: 1},
54 | loadOp: 'clear',
55 | storeOp: 'store'
56 | }],
57 | });
58 | composite.render(compositePassEncoder);
59 | compositePassEncoder.end();
60 |
61 | device.queue.submit([commandEncoder.finish()]);
62 |
63 | timingHelper.getResult().then(gpuTime => rdTime.addSample(gpuTime / 1000));
64 | timeDisplay.innerText = `GPU time ${useCompute ? 'compute shader' : 'fragment shader'}: ${Math.round(rdTime.value)} µs`;
65 |
66 | requestAnimationFrame(t => run(t));
67 | }
68 |
69 | run(0);
70 |
--------------------------------------------------------------------------------
/performance-comparison/js/rd-compute.js:
--------------------------------------------------------------------------------
1 | import * as wgh from './libs/webgpu-utils.module.js';
2 | import { Float16Array } from './libs/float16.js';
3 | import { ReactionDiffusionComputeShader, ReactionDiffusionShaderDispatchSize } from './shader/rd-compute-shader.js';
4 |
5 | export class ReactionDiffusionCompute {
6 |
7 | ITERATIONS = 15;
8 |
9 | SCALE = .5;
10 |
11 | constructor(device, viewportSize) {
12 | this.device = device;
13 |
14 | // create pipeline and bind group layouts
15 | const module = this.device.createShaderModule({ code: ReactionDiffusionComputeShader });
16 | const defs = wgh.makeShaderDataDefinitions(ReactionDiffusionComputeShader);
17 | const pipelineDescriptor = {
18 | compute: {
19 | module,
20 | entryPoint: 'compute_main',
21 | }
22 | };
23 | const descriptors = wgh.makeBindGroupLayoutDescriptors(defs, pipelineDescriptor);
24 | descriptors[0].entries.push({
25 | binding: 1,
26 | storageTexture: { access: 'write-only', format: 'rgba16float' },
27 | visibility: GPUShaderStage.COMPUTE
28 | });
29 | this.bindGroupLayout = this.device.createBindGroupLayout(descriptors[0]);
30 |
31 | const pipelineLayout = this.device.createPipelineLayout({
32 | bindGroupLayouts: [this.bindGroupLayout]
33 | });
34 | this.pipeline = this.device.createComputePipeline({
35 | label: 'reaction diffusion compute pipeline',
36 | layout: pipelineLayout,
37 | ...pipelineDescriptor
38 | });
39 |
40 | this.init(viewportSize[0] * this.SCALE, viewportSize[1] * this.SCALE);
41 | }
42 |
43 | init(width, height) {
44 | this.createTextures(Math.round(width), Math.round(height));
45 | this.createBindGroups();
46 | }
47 |
48 | get resultStorageTexture() {
49 | return this.swapTextures[0];
50 | }
51 |
52 | createTextures(width, height) {
53 | if (this.swapTextures) {
54 | this.swapTextures.forEach(texture => texture.destroy());
55 | }
56 |
57 | this.swapTextures = new Array(2).fill(null).map((v, ndx) => {
58 | const texture = this.device.createTexture({
59 | size: { width, height },
60 | format: 'rgba16float',
61 | usage:
62 | GPUTextureUsage.COPY_DST |
63 | GPUTextureUsage.STORAGE_BINDING |
64 | GPUTextureUsage.TEXTURE_BINDING |
65 | GPUTextureUsage.RENDER_ATTACHMENT,
66 | });
67 |
68 | const w = width;
69 | const h = height;
70 | let data;
71 | const rgba = new Array(w * h * 4).fill(0);
72 | const s = 20;
73 | const bx = [w / 2 - s, w / 2 + s];
74 | const by = [h / 2 - s, h / 2 + s];
75 | for(let x=0; x bx[0] && x < bx[1] && y > by[0] && y < by[1];
78 | rgba[(x + y * w) * 4 + 0] = ndx === 0 && !v ? 1 : 0;
79 | rgba[(x + y * w) * 4 + 1] = ndx === 0 && v ? 1 : 0;
80 | rgba[(x + y * w) * 4 + 2] = 0;
81 | rgba[(x + y * w) * 4 + 3] = 1;
82 | }
83 | }
84 | data = new Float16Array(rgba);
85 |
86 | this.device.queue.writeTexture({ texture }, data.buffer, { bytesPerRow: width * 8 }, { width, height });
87 |
88 | return texture;
89 | });
90 |
91 | this.dispatches = [
92 | Math.ceil(width / ReactionDiffusionShaderDispatchSize[0]),
93 | Math.ceil(height / ReactionDiffusionShaderDispatchSize[1])
94 | ];
95 | }
96 |
97 | createBindGroups() {
98 | this.swapBindGroups = [
99 | this.device.createBindGroup({
100 | layout: this.bindGroupLayout,
101 | entries: [
102 | { binding: 0, resource: this.swapTextures[0].createView() },
103 | { binding: 1, resource: this.swapTextures[1].createView() },
104 | ]
105 | }),
106 | this.device.createBindGroup({
107 | layout: this.bindGroupLayout,
108 | entries: [
109 | { binding: 0, resource: this.swapTextures[1].createView() },
110 | { binding: 1, resource: this.swapTextures[0].createView() },
111 | ]
112 | })
113 | ];
114 | }
115 |
116 | compute(computePassEncoder) {
117 | computePassEncoder.setPipeline(this.pipeline);
118 |
119 | for(let i = 0; i < this.ITERATIONS; i++) {
120 | computePassEncoder.setBindGroup(0, this.swapBindGroups[0]);
121 | computePassEncoder.dispatchWorkgroups(this.dispatches[0], this.dispatches[1]);
122 |
123 | computePassEncoder.setBindGroup(0, this.swapBindGroups[1]);
124 | computePassEncoder.dispatchWorkgroups(this.dispatches[0], this.dispatches[1]);
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/performance-comparison/js/rd-fragment.js:
--------------------------------------------------------------------------------
1 | import * as wgh from './libs/webgpu-utils.module.js';
2 | import { Float16Array } from './libs/float16.js';
3 | import { ReactionDiffusionFragmentShader } from './shader/rd-fragment-shader.js';
4 |
5 | export class ReactionDiffusionFragment {
6 |
7 | static ITERATIONS = 15;
8 |
9 | SCALE = .5;
10 |
11 | constructor(device, viewportSize) {
12 | this.device = device;
13 |
14 | // create pipeline and bind group layouts
15 | const module = this.device.createShaderModule({ code: ReactionDiffusionFragmentShader });
16 | const defs = wgh.makeShaderDataDefinitions(ReactionDiffusionFragmentShader);
17 | const pipelineDescriptor = {
18 | vertex: {
19 | module: module,
20 | entryPoint: 'vertex_main',
21 | },
22 | fragment: {
23 | module,
24 | entryPoint:'frag_main',
25 | targets: [{ format: 'rgba16float' }]
26 | },
27 | primitive: {
28 | topology: 'triangle-list',
29 | },
30 | };
31 | const descriptors = wgh.makeBindGroupLayoutDescriptors(defs, pipelineDescriptor);
32 | this.bindGroupLayout = this.device.createBindGroupLayout(descriptors[0]);
33 |
34 | const pipelineLayout = this.device.createPipelineLayout({
35 | bindGroupLayouts: [this.bindGroupLayout]
36 | });
37 | this.pipeline = this.device.createRenderPipeline({
38 | label: 'reaction diffusion fragment pipeline',
39 | layout: pipelineLayout,
40 | ...pipelineDescriptor
41 | });
42 |
43 | this.init(viewportSize[0] * this.SCALE, viewportSize[1] * this.SCALE);
44 | }
45 |
46 | init(width, height) {
47 | this.createTextures(Math.round(width), Math.round(height));
48 | this.createBindGroups();
49 | }
50 |
51 | get resultStorageTexture() {
52 | return this.swapTextures[0];
53 | }
54 |
55 | createTextures(width, height) {
56 | if (this.swapTextures) {
57 | this.swapTextures.forEach(texture => texture.destroy());
58 | }
59 |
60 | this.swapTextures = new Array(2).fill(null).map((v, ndx) => {
61 | const texture = this.device.createTexture({
62 | size: { width, height },
63 | format: 'rgba16float',
64 | usage:
65 | GPUTextureUsage.COPY_DST |
66 | GPUTextureUsage.TEXTURE_BINDING |
67 | GPUTextureUsage.RENDER_ATTACHMENT,
68 | });
69 |
70 | const w = width;
71 | const h = height;
72 | let data;
73 | const rgba = new Array(w * h * 4).fill(0);
74 | const s = 20;
75 | const bx = [w / 2 - s, w / 2 + s];
76 | const by = [h / 2 - s, h / 2 + s];
77 | for(let x=0; x bx[0] && x < bx[1] && y > by[0] && y < by[1];
80 | rgba[(x + y * w) * 4 + 0] = ndx === 0 && !v ? 1 : 0;
81 | rgba[(x + y * w) * 4 + 1] = ndx === 0 && v ? 1 : 0;
82 | rgba[(x + y * w) * 4 + 2] = 0;
83 | rgba[(x + y * w) * 4 + 3] = 1;
84 | }
85 | }
86 | data = new Float16Array(rgba);
87 |
88 | this.device.queue.writeTexture({ texture }, data.buffer, { bytesPerRow: width * 8 }, { width, height });
89 |
90 | return texture;
91 | });
92 |
93 | this.swapTextureViews = this.swapTextures.map(texture => texture.createView());
94 | }
95 |
96 | createBindGroups() {
97 | this.swapBindGroups = [
98 | this.device.createBindGroup({
99 | layout: this.bindGroupLayout,
100 | entries: [
101 | { binding: 0, resource: this.swapTextures[0].createView() },
102 | ]
103 | }),
104 | this.device.createBindGroup({
105 | layout: this.bindGroupLayout,
106 | entries: [
107 | { binding: 0, resource: this.swapTextures[1].createView() },
108 | ]
109 | })
110 | ];
111 | }
112 |
113 | render(commandEncoder, timingHelper) {
114 | for(let i = 0; i < ReactionDiffusionFragment.ITERATIONS; i++) {
115 | let renderPassEncoder = timingHelper.beginRenderPass(commandEncoder, {
116 | colorAttachments: [{
117 | view: this.swapTextureViews[1],
118 | loadOp: 'load',
119 | storeOp: 'store'
120 | }],
121 | }, i * 2);
122 | renderPassEncoder.setPipeline(this.pipeline);
123 | renderPassEncoder.setBindGroup(0, this.swapBindGroups[0]);
124 | renderPassEncoder.draw(3);
125 | renderPassEncoder.end();
126 |
127 | renderPassEncoder = timingHelper.beginRenderPass(commandEncoder, {
128 | colorAttachments: [{
129 | view: this.swapTextureViews[0],
130 | loadOp: 'load',
131 | storeOp: 'store'
132 | }],
133 | }, i * 2 + 1);
134 | renderPassEncoder.setPipeline(this.pipeline);
135 | renderPassEncoder.setBindGroup(0, this.swapBindGroups[1]);
136 | renderPassEncoder.draw(3);
137 | renderPassEncoder.end();
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/performance-comparison/js/shader/composite-shader.js:
--------------------------------------------------------------------------------
1 | // language=C
2 | export const CompositeShader = `
3 | struct VertexOutput {
4 | @builtin(position) position: vec4f,
5 | @location(0) uv: vec2f
6 | }
7 |
8 | @vertex
9 | fn vertex_main(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {
10 | const pos : array = array(
11 | vec2f(-1, 3),
12 | vec2f(-1, -1),
13 | vec2f(3, -1)
14 | );
15 | const uv : array = array(
16 | vec2f(0, 2),
17 | vec2f(0, 0),
18 | vec2f(2, 0)
19 | );
20 | var output : VertexOutput;
21 | output.position = vec4f(pos[vertexIndex], 0., 1.);
22 | output.uv = uv[vertexIndex];
23 | return output;
24 | }
25 |
26 | @group(0) @binding(0) var colorTexSampler : sampler;
27 | @group(0) @binding(1) var colorTex : texture_2d;
28 |
29 | @fragment
30 | fn frag_main(@location(0) uv : vec2f) -> @location(0) vec4f {
31 | let colorTexSize : vec2f = vec2f(textureDimensions(colorTex));
32 | let color : vec4f = textureSample(colorTex, colorTexSampler, uv);
33 | return color;
34 | }
35 | `;
36 |
--------------------------------------------------------------------------------
/performance-comparison/js/shader/rd-compute-shader.js:
--------------------------------------------------------------------------------
1 | const kernelSize = 3;
2 | const workgroupSize = [8, 8];
3 | // each thread handles a tile of pixels
4 | const tileSize = [2, 2];
5 | // holds all the pixels needed for one workgroup
6 | const cacheSize = [
7 | tileSize[0] * workgroupSize[0],
8 | tileSize[1] * workgroupSize[1]
9 | ];
10 | // the cache has to include the boundary pixels needed for a
11 | // valid evaluation of the kernel within the dispatch area
12 | const dispatchSize = [
13 | cacheSize[0] - Math.max(0, (kernelSize - 1)),
14 | cacheSize[1] - Math.max(0, (kernelSize - 1)),
15 | ];
16 |
17 | export const ReactionDiffusionShaderDispatchSize = dispatchSize;
18 |
19 | // language=C
20 | export const ReactionDiffusionComputeShader = `
21 |
22 | const kernelSize = ${kernelSize};
23 | const dispatchSize = vec2u(${dispatchSize[0]},${dispatchSize[1]});
24 | const tileSize = vec2u(${tileSize[0]},${tileSize[1]});
25 |
26 | // based on: https://community.khronos.org/t/manual-bilinear-filter/58504
27 | fn texture2D_bilinear(t: texture_2d, coord: vec2f, dims: vec2u) -> vec4f {
28 | let f: vec2f = fract(coord);
29 | let sample: vec2u = vec2u(coord + (0.5 - f));
30 | let tl: vec4f = textureLoad(t, clamp(sample, vec2u(1, 1), dims), 0);
31 | let tr: vec4f = textureLoad(t, clamp(sample + vec2u(1, 0), vec2u(1, 1), dims), 0);
32 | let bl: vec4f = textureLoad(t, clamp(sample + vec2u(0, 1), vec2u(1, 1), dims), 0);
33 | let br: vec4f = textureLoad(t, clamp(sample + vec2u(1, 1), vec2u(1, 1), dims), 0);
34 | let tA: vec4f = mix(tl, tr, f.x);
35 | let tB: vec4f = mix(bl, br, f.x);
36 | return mix(tA, tB, f.y);
37 | }
38 |
39 | @group(0) @binding(0) var inputTex: texture_2d;
40 | @group(0) @binding(1) var outputTex: texture_storage_2d;
41 |
42 | // the cache for the texture lookups (tileSize * workgroupSize)
43 | var cache: array, ${cacheSize[1]}>;
44 |
45 | @compute @workgroup_size(${workgroupSize[0]}, ${workgroupSize[1]}, 1)
46 | fn compute_main(
47 | @builtin(workgroup_id) workGroupID : vec3,
48 | @builtin(local_invocation_id) localInvocationID : vec3,
49 | @builtin(global_invocation_id) globalInvocationID : vec3
50 | ) {
51 | // each thread adds a tile of pixels to the workgroups shared memory
52 |
53 | let kernelArea: u32 = kernelSize * kernelSize;
54 |
55 | // the kernel offset (number of pixels next to the center of the kernel) defines
56 | // the border area next to the dispatched (=work) area that has to be included
57 | // within the pixel cache
58 | let kernelOffset: vec2u = vec2((kernelSize - 1) / 2);
59 |
60 | // the local pixel offset of this threads tile
61 | let tileOffset: vec2u = localInvocationID.xy * tileSize;
62 |
63 | // the global pixel offset of the workgroup
64 | let dispatchOffset: vec2u = workGroupID.xy * dispatchSize;
65 |
66 | // get texture dimensions
67 | let dims: vec2u = vec2(textureDimensions(inputTex, 0));
68 |
69 | // add this threads tiles pixels to the cache
70 | for (var c=0u; c = array(
95 | 0.05, 0.20, 0.05,
96 | 0.20, -1.0, 0.20,
97 | 0.05, 0.20, 0.05,
98 | );
99 |
100 | // run through the whole cache area
101 | for (var c=0u; c= bounds.xy) && all(sample < bounds.zw)) {
109 |
110 | let uv: vec2f = vec2f(sample) / vec2f(dims);
111 |
112 | // convolution with laplacian kernel
113 | var lap = vec2f(0);
114 | let ks: i32 = i32(kernelSize);
115 | for (var x = 0; x < ks; x++) {
116 | for (var y = 0; y < ks; y++) {
117 | let i = vec2i(local) + vec2(x, y) - vec2i(kernelOffset);
118 | lap += cache[i.y][i.x].xy * laplacian[y * ks + x];
119 | }
120 | }
121 |
122 | // reaction diffusion calculation
123 | let cacheValue: vec4f = cache[local.y][local.x];
124 | let rd0 = cacheValue.xy;
125 | let dA = .5;
126 | let dB = .2;
127 | let feed = 0.063;
128 | let kill = 0.062;
129 | // calculate result
130 | let A = rd0.x;
131 | let B = rd0.y;
132 | let reaction = A * B * B;
133 | let rd = vec2f(
134 | A + (dA * lap.x - reaction + feed * (1. - A)),
135 | B + (dB * lap.y + reaction - (kill + feed) * B),
136 | );
137 |
138 | textureStore(outputTex, sample, vec4(rd, 0., 1.0));
139 |
140 | // debug code
141 | //textureStore(outputTex, sample, vec4(cache[local.y][local.x], 1.0));
142 | //textureStore(outputTex, sample, vec4(uv, 0., 1.0));
143 | }
144 | }
145 | }
146 | }
147 |
148 | `;
149 |
150 |
--------------------------------------------------------------------------------
/performance-comparison/js/shader/rd-fragment-shader.js:
--------------------------------------------------------------------------------
1 | // language=C
2 | export const ReactionDiffusionFragmentShader = `
3 | struct VertexOutput {
4 | @builtin(position) position: vec4f,
5 | @location(0) uv: vec2f
6 | }
7 |
8 | @vertex
9 | fn vertex_main(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {
10 | const pos : array = array(
11 | vec2f(-1, 3),
12 | vec2f(-1, -1),
13 | vec2f(3, -1)
14 | );
15 | const uv : array = array(
16 | vec2f(0, 2),
17 | vec2f(0, 0),
18 | vec2f(2, 0)
19 | );
20 | var output : VertexOutput;
21 | output.position = vec4f(pos[vertexIndex], 0., 1.);
22 | output.uv = uv[vertexIndex];
23 | return output;
24 | }
25 |
26 | @group(0) @binding(0) var inputTex : texture_2d;
27 |
28 | @fragment
29 | fn frag_main(@location(0) uv : vec2f) -> @location(0) vec4f {
30 | let texSize : vec2f = vec2f(textureDimensions(inputTex));
31 | let sample: vec2u = vec2u(floor(uv * texSize));
32 |
33 | let laplacian: array = array(
34 | 0.05, 0.20, 0.05,
35 | 0.20, -1.0, 0.20,
36 | 0.05, 0.20, 0.05,
37 | );
38 |
39 | // convolution with laplacian kernel
40 | var lap = vec2f(0);
41 | for (var x = 0; x < 3; x++) {
42 | for (var y = 0; y < 3; y++) {
43 | let i = vec2i(sample) + vec2i(x - 1, y - 1);
44 | lap += textureLoad(inputTex, i, 0).xy * laplacian[y * 3 + x];
45 | }
46 | }
47 |
48 | // reaction diffusion calculation
49 | let rd0 = textureLoad(inputTex, sample, 0);
50 | let dA = .5;
51 | let dB = .2;
52 | let feed = 0.063;
53 | let kill = 0.062;
54 | // calculate result
55 | let A = rd0.x;
56 | let B = rd0.y;
57 | let reaction = A * B * B;
58 | let rd = vec2f(
59 | A + (dA * lap.x - reaction + feed * (1. - A)),
60 | B + (dB * lap.y + reaction - (kill + feed) * B),
61 | );
62 |
63 | let color = vec4(rd, 0., 1.0);
64 |
65 | return color;
66 | }
67 | `;
68 |
--------------------------------------------------------------------------------
/performance-comparison/js/utils/rolling-average.js:
--------------------------------------------------------------------------------
1 | // Credits: https://webgpufundamentals.org/webgpu/lessons/webgpu-timing.html
2 | export class RollingAverage {
3 | #total = 0;
4 | #samples = [];
5 | #cursor = 0;
6 | #numSamples;
7 | constructor(numSamples = 30) {
8 | this.#numSamples = numSamples;
9 | }
10 | addSample(v) {
11 | this.#total += v - (this.#samples[this.#cursor] || 0);
12 | this.#samples[this.#cursor] = v;
13 | this.#cursor = (this.#cursor + 1) % this.#numSamples;
14 | }
15 | get value() {
16 | return this.#total / this.#samples.length;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/performance-comparison/js/utils/timing-helper.js:
--------------------------------------------------------------------------------
1 | // Credits: https://webgpufundamentals.org/webgpu/lessons/webgpu-timing.html
2 | function assert(cond, msg = '') {
3 | if (!cond) {
4 | throw new Error(msg);
5 | }
6 | }
7 |
8 | export class TimingHelper {
9 | #canTimestamp;
10 | #device;
11 | #querySet;
12 | #resolveBuffer;
13 | #resultBuffer;
14 | #resultBuffers = [];
15 | // state can be 'free', 'need resolve', 'wait for result'
16 | #state = 'free';
17 | #passCount = 1;
18 |
19 | constructor(device, passCount = 1) {
20 | this.#device = device;
21 | this.#passCount = passCount;
22 | this.#canTimestamp = device.features.has('timestamp-query');
23 | this.#querySet = device.createQuerySet({
24 | type: 'timestamp',
25 | count: this.#passCount * 2,
26 | });
27 | this.#resolveBuffer = device.createBuffer({
28 | size: this.#querySet.count * 8,
29 | usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC,
30 | });
31 | }
32 |
33 | #beginTimestampPass(encoder, fnName, descriptor, passIndex) {
34 | if (this.#canTimestamp) {
35 | assert(this.#state === 'free', 'state not free');
36 | this.#state = passIndex !== this.#passCount - 1 ? 'free' : 'need resolve';
37 |
38 | const pass = encoder[fnName]({
39 | ...descriptor,
40 | ...{
41 | timestampWrites: {
42 | querySet: this.#querySet,
43 | beginningOfPassWriteIndex: passIndex * 2,
44 | endOfPassWriteIndex: passIndex * 2 + 1,
45 | },
46 | },
47 | });
48 |
49 | const resolve = () => this.#resolveTiming(encoder, passIndex);
50 | pass.end = (function(origFn) {
51 | return function() {
52 | origFn.call(this);
53 | resolve();
54 | };
55 | })(pass.end);
56 |
57 | return pass;
58 | } else {
59 | return encoder[fnName](descriptor);
60 | }
61 | }
62 |
63 | beginRenderPass(encoder, descriptor = {}, passIndex = 0) {
64 | return this.#beginTimestampPass(encoder, 'beginRenderPass', descriptor, passIndex);
65 | }
66 |
67 | beginComputePass(encoder, descriptor = {}, passIndex = 0) {
68 | return this.#beginTimestampPass(encoder, 'beginComputePass', descriptor, passIndex);
69 | }
70 |
71 | #resolveTiming(encoder, passIndex) {
72 | if (!this.#canTimestamp || passIndex !== this.#passCount - 1) {
73 | return;
74 | }
75 | assert(this.#state === 'need resolve', 'must call addTimestampToPass');
76 | this.#state = 'wait for result';
77 |
78 | this.#resultBuffer = this.#resultBuffers.pop() || this.#device.createBuffer({
79 | size: this.#resolveBuffer.size,
80 | usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
81 | });
82 |
83 | encoder.resolveQuerySet(this.#querySet, 0, this.#querySet.count, this.#resolveBuffer, 0);
84 | encoder.copyBufferToBuffer(this.#resolveBuffer, 0, this.#resultBuffer, 0, this.#resultBuffer.size);
85 | }
86 |
87 | async getResult() {
88 | if (!this.#canTimestamp) {
89 | return 0;
90 | }
91 | assert(this.#state === 'wait for result', 'must call resolveTiming');
92 | this.#state = 'free';
93 |
94 | const resultBuffer = this.#resultBuffer;
95 | await resultBuffer.mapAsync(GPUMapMode.READ);
96 | const times = new BigInt64Array(resultBuffer.getMappedRange());
97 |
98 | let durationSum = 0;
99 | for(let i=0; i