Skip to content

Commit

Permalink
Add TypedArray table (#175)
Browse files Browse the repository at this point in the history
  • Loading branch information
greggman authored Sep 20, 2024
1 parent f04a7a1 commit cecee47
Show file tree
Hide file tree
Showing 5 changed files with 326 additions and 3 deletions.
80 changes: 80 additions & 0 deletions webgpu/lessons/resources/jsonml.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* Implements JsonML *like* element creation (http://www.jsonml.org/)
*
* The major difference is this takes event handlers for `on` functions
* and supports nested attributes? Also allows elements.
*
* ```js
* document.body.appendChild(makeElem([
* 'style',
* '.bold { font-weight: bold; }',
* '.italic { font-style: italic; }',
* ]));
*
* document.body.appendChild(makeElem([
* 'div',
* 'This next word is ',
* ['span', {style: 'color: red'}, 'red'], // style is string
* ' and this next word is ',
* ['span', {style: {color: 'blue'}}, 'blue'], // style is object
* ' and this next word is ',
* ['span', {className: 'bold'}, 'bold'], // className works
* ' and this next word is ',
* ['span', {class: 'italic bold'}, 'italic-bold'], // class works too?
* ]));
*
* document.body.appendChild(makeElem([
* 'form',
* 'Enter name:',
* ['input', {type: 'text', placeholder: 'Jane Doe'}],
* [
* 'button',
* {
* type: 'button',
* onClick: (e) => {
* console.log('name:', e.target.previousElementSibling.value);
* },
* },
* 'submit',
* ],
* ]));
* ```
*/
export function makeElem(elemSpec) {
const tag = elemSpec[0];
if (tag instanceof Node) {
return tag;
}
const elem = document.createElement(tag);

let firstChildNdx = 1;
if (typeof elemSpec[1] !== Node && typeof elemSpec[1] !== 'string' && !Array.isArray(elemSpec[1])) {
firstChildNdx = 2;
for (const [key, value] of Object.entries(elemSpec[1])) {
if (typeof value === 'function' && key.startsWith('on')) {
const eventName = key.substring(2).toLowerCase();
elem.addEventListener(eventName, value, {passive: false});
} else if (typeof value === 'object') {
for (const [k, v] of Object.entries(value)) {
elem[key][k] = v;
}
} else if (elem[key] === undefined) {
elem.setAttribute(key, value);
} else {
elem[key] = value;
}
}
}

for (let ndx = firstChildNdx; ndx < elemSpec.length; ++ndx) {
const v = elemSpec[ndx];
if (typeof v === 'string') {
elem.appendChild(document.createTextNode(v));
} else if (v instanceof Node) {
elem.appendChild(v);
} else {
elem.appendChild(makeElem(v));
}
}
return elem;
}
12 changes: 12 additions & 0 deletions webgpu/lessons/resources/lesson.css
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,18 @@ pre.prettyprint.lighttheme .fun { color: #900; } /* function name */
}
}

@media (width < 540px) {
.byte-diagram {
--byte-grid-size: 20px;
font-size: 12px;
}
}
@media (width < 400px) {
.byte-diagram {
--byte-grid-size: 16px;
}
}

/* disqus */

#disqus_thread {
Expand Down
44 changes: 44 additions & 0 deletions webgpu/lessons/webgpu-memory-layout.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
:root {
--whl-th-bg-color: lightblue;
--whl-row-bg-color: linear-gradient(#FFF, #EEE);
}
@media (prefers-color-scheme: dark) {
:root {
--whl-th-bg-color: blue;
--whl-row-bg-color: linear-gradient(#333, #111);
}
}
div[data-diagram="typedArrays"] {
font-family: monospace;
font-size: small;
display: flex;
justify-content: center;
flex-direction: column;
overflow-x: auto;

label { display: flex; align-items: center;}
table { border-collapse: collapse; line-height: 1.5; width: max-content; }
thead { background-color: var(--whl-th-bg-color);}
th, td { border: 1px solid gray; position: relative; padding: 0.2em; margin: 0 }
input[type="text"] { border: none; font-family: monospace; background-color: inherit; width: 100%; padding: 0; margin: 0; text-align: right; }
.error { background-color: red; }

/*
tr:nth-child(even) { background-color: #333;}
tr:nth-child(odd) { background-color: #222;}
*/
tr { background: var(--whl-row-bg-color); }

td[colspan="1"] { width: 3em }
td[colspan="2"] { width: 6em }
td[colspan="4"] { width: 12em }
td[colspan="8"] { width: 24em }
}

@media (width < 740px) {
div[data-diagram="typedArrays"] {
font-size: x-small;
display: block;
}
}

160 changes: 160 additions & 0 deletions webgpu/lessons/webgpu-memory-layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import {
import {
makeTable,
} from './resources/elem.js';
import {
makeElem
} from './resources/jsonml.js';
import typeInfo from './resources/wgsl-data-types.js';

renderDiagrams({
Expand Down Expand Up @@ -93,4 +96,161 @@ renderDiagrams({
addRow([name, size, align]);
}
},

typedArrays(elem) {
const viewCtors = [
Int8Array,
Uint8Array,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array,
BigInt64Array,
BigUint64Array,
];

const numBytes = 16;

const range = (num, fn) => new Array(num).fill(0).map((_, i) => fn(i));

const arrayBuffer = new ArrayBuffer(numBytes);
const views = viewCtors.map(Ctor => new Ctor(arrayBuffer));
const f32 = new Float32Array(arrayBuffer);
f32.set([123, -456, 7.89, -0.123]);

const updateFns = [];
function updateAll() {
for (const fn of updateFns) {
fn();
}
}

let showAsHex = false;

function format(view, v) {
if (showAsHex && view.constructor.name.includes('nt')) {
return v.toString(16);//.padStart(view.BYTES_PER_ELEMENT * 2, '0');
} else {
return v.toString();
}
}

function parseBigInt16(v) {
v = v.trim();
const [start, sign] = v[0] === '-'
? [1, -1]
: [0, 1];
let result = BigInt(0);
for (let i = start; i < v.length; ++i) {
const c = v[i].toLowerCase().charCodeAt(0);
let digit;
if (c >= 0x30 && c <= 0x39) {
digit = c - 0x30;
} else if (c >= 0x61 && c <= 0x66) {
digit = c - 0x61 + 10;
} else {
throw new Error('not hex');
}
result = result * BigInt(0x10) + BigInt(digit);
}
return result * BigInt(sign);
}

function parseBigInt(v) {
return showAsHex ? parseBigInt16(v) : BigInt(v);
}

const intRE = /^-?\d+$/;
const hexRE = /^-?[0-9a-f]+$/i;
function parseIntNumber(v) {
if (showAsHex) {
if (!hexRE.test(v)) {
throw new Error('not hex');
}
} else {
if (!intRE.test(v)) {
throw new Error('not int');
}
}
return parseInt(v, showAsHex ? 16 : 10);
}

function parseFloatNumber(v) {
v = Number(v);
if (isNaN(v)) {
throw Error('NaN');
}
return v;
}

elem.appendChild(makeElem([
'table',
[
'thead',
['th', 'arrayBuffer'],
...range(numBytes, i => ['th', `${i}`]),
],
[
'tbody',
...views.map((view) => {
const numCellsPerElem = view.BYTES_PER_ELEMENT;
const numElems = numBytes / numCellsPerElem;
const parse = view instanceof BigInt64Array
? parseBigInt
: view instanceof BigUint64Array
? parseBigInt
: view.constructor.name.includes('Float')
? parseFloatNumber
: parseIntNumber;
return [
'tr',
['td', `as${view.constructor.name.substring(0, view.constructor.name.length - 5)}` ],
...range(numElems, i => {
const input = makeElem([
'input', {type: 'text', value: 123, onInput: function() {
let err = false;
try {
view[i] = parse(this.value);
} catch (error) {
//console.log('here', error.message);
err = true;
}
input.classList.toggle('error', err);
updateAll();
},
},
]);

updateFns.push(() => {
if (document.activeElement !== input) {
input.value = format(view, view[i]);
input.classList.remove('error');
}
});

return ['td', {colSpan: numCellsPerElem},
input,
];
}),
];
}),
],
]));

updateAll();

elem.appendChild(makeElem(['label',
elem.dataset.caption,
[
'input', { type: 'checkbox', onChange: function() {
showAsHex = this.checked;
updateAll();
},
},
],
]));

},
});
33 changes: 30 additions & 3 deletions webgpu/lessons/webgpu-memory-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ ourStructValuesAsU32[kFrameCountOffset] = 56; // an integer value

## <a id="a-typed-arrays"></a> `TypedArrays`

Note, like many things in programming there are multiple ways we could
do this. `TypedArray`s have a constructor that takes various forms. For example
Like many things in programming there are multiple ways we could
set the data for `OutStruct`. `TypedArray`s have a constructor that takes various forms. For example

* `new Float32Array(12)`

Expand Down Expand Up @@ -109,7 +109,7 @@ do this. `TypedArray`s have a constructor that takes various forms. For example
console.log(u32s); // produces 0, 0, 1, 1, 1
```

The reason is you can't put values like 0.8 and 1.2 into a `Uint32Array`
The reason is you can't put values like 0.8 and 1.2 into a `Uint32Array`. They get converted to unsigned integers
* `new Float32Array(someArrayBuffer)`
Expand Down Expand Up @@ -201,6 +201,32 @@ console.log(Array.from(u32).map(v => v.toString(16).padStart(8, '0')));

The values above are the 32bit hex representations of the floating point values for 1, 1000, -1000

For example: Let's create a 16 byte `ArrayBuffer`. Then we'll create different
`TypedArray` views of the same memory.

```js
const arrayBuffer = new ArrayBuffer(16);
const asInt8 = new Int8Array(arrayBuffer);
const asUint8 = new Uint8Array(arrayBuffer);
const asInt16 = new Int16Array(arrayBuffer);
const asUint16 = new Uint16Array(arrayBuffer);
const asInt32 = new Int32Array(arrayBuffer);
const asUint32 = new Uint32Array(arrayBuffer);
const asFloat32 = new Float32Array(arrayBuffer);
const asFloat64 = new Float64Array(arrayBuffer);
const asBigInt64 = new BigInt64Array(arrayBuffer);
const asBigUint64 = new BigInt64Array(arrayBuffer);
// Set some values to start.
asFloat32.set([123, -456, 7.8, -0.123]);
```

Here's a representation of all of those views, all viewing the same
memory. Below, edit any one number and the corresponding values that are
using the same memory will change.
<div data-diagram="typedArrays" data-caption="show integers as hex"></div>
## `map` issues
Be aware, the `map` function of a `TypedArray` makes a new typed array of the same type!
Expand Down Expand Up @@ -464,4 +490,5 @@ If you do want to do it manually though,
[here's a page that will compute the offsets for you](resources/wgsl-offset-computer.html)

<!-- keep this at the bottom of the article -->
<link rel="stylesheet" href="webgpu-memory-layout.css">
<script type="module" src="webgpu-memory-layout.js"></script>

0 comments on commit cecee47

Please sign in to comment.