diff --git a/index.html b/index.html
index fb27f5b..71ef44a 100644
--- a/index.html
+++ b/index.html
@@ -97,6 +97,12 @@
h2, td {
padding: 0 10px 0 10px;
}
+label {
+ user-select: none;
+ font-size: x-small;
+ display: flex;
+ align-items: center;
+}
.logo {
height: 1.5em;
}
@@ -149,6 +155,10 @@
.nowrap {
white-space: nowrap;
}
+.col-headings {
+ color: var(--header-text-color);
+ font-weight: bold;
+}
.different-better {
color: var(--different-text-color);
}
@@ -168,6 +178,13 @@
padding-left: 2em;
}
+table.show-default-limits .adapter-limit {
+ display: none;
+}
+table:not(.show-default-limits) .default-limit {
+ display: none;
+}
+
.bracketed-link {
font-size: x-small;
}
diff --git a/index.js b/index.js
index f58c795..76a06d2 100644
--- a/index.js
+++ b/index.js
@@ -112,12 +112,16 @@ function separateTextContentFromAttributes(attrs = {}) {
*/
function createHeading(tag, padChar, attrs = {}, children = []) {
const {textContent, attribs} = separateTextContentFromAttributes(attrs);
- return createElem(tag, attribs, [
+ const hidden = createHidden('');
+ const elem = createElem(tag, attribs, [
createHidden('\n\n'),
createElem('span', textContent),
- createHidden(`\n${''.padEnd(textContent.length, padChar)}`),
...children,
+ hidden,
]);
+
+ hidden.textContent = `\n${''.padEnd(elem.textContent.length, padChar)}`;
+ return elem;
}
const el = createElem;
@@ -146,13 +150,6 @@ const shortSize = (function() {
};
})();
-const shortSizeMem = (function() {
- const suffixes = ['b', 'k', 'mb', 'gb', 'tb', 'pb'];
- return function(size) {
- return shortSize(size, suffixes);
- };
-})();
-
const shortSizeByType = (function() {
const suffixesByType = {
'mem': ['b', 'k', 'mb', 'gb', 'tb', 'pb'],
@@ -164,11 +161,17 @@ const shortSizeByType = (function() {
};
})();
+/**
+ * Adds a row to table where first td is 'k'
+ */
function addValueRow(className, k, _v) {
const [v, attribs] = Array.isArray(_v) ? _v : [_v, {}];
return el('tr', {className}, [
el('td', {textContent: k}),
- el('td', {...attribs, textContent: v}),
+ ...(v instanceof HTMLElement
+ ? [el('td', {}, _v)]
+ : [el('td', {...attribs, textContent: v})]
+ )
]);
}
@@ -200,9 +203,8 @@ function mapLikeToKeyValueArray(obj) {
function expandMapLike(obj, sort = true) {
const entries = mapLikeToKeyValueArray(obj);
- const longestDesc = entries.reduce((longest, [description]) => Math.max(longest, description.length), 0);
const result = entries
- .map(([k, v]) => addValueRow('feature', k.padEnd(longestDesc + 1), v));
+ .map(([k, v]) => addValueRow('feature', k, v));
if (sort) {
result.sort(byFirstColumn);
}
@@ -223,7 +225,7 @@ function mapLikeToTableRows(values, sort = true) {
function makeBracketedLink(href, textContent, brackets = '()') {
return [
- createElem('span', {className: 'bracketed-link'}, [
+ createElem('span', {className: 'bracketed-link hide-on-copy'}, [
createElem('span', `${brackets[0]} `),
createElem('a', { target: '_blank', href, textContent }),
createElem('span', ` ${brackets[1]}`),
@@ -258,15 +260,20 @@ function markDifferencesInLimits(adapter, device) {
const diffClass = defaultLimit !== undefined
? (isDiff
? differenceWorse(k, defaultLimit, v) ? 'different-worse' : 'different-better'
- : '')
+ : 'different-none')
: 'unknown';
const shortSize = shortSizeByType(v, info?.type ?? 'count');
+ const defaultSize = shortSizeByType(defaultLimit, info?.type ?? 'count');
const value = v > 1024 ? `${v} (${shortSize})` : shortSize;
+ const defaultValue = defaultLimit > 1024 ? `${defaultLimit} (${defaultSize})` : defaultSize;
+ const defaultElem = el('span', {className: 'nowrap default-limit', textContent: defaultValue})
+ const title = isDiff
+ ? `default: ${defaultSize}\n${requestHint}`
+ : 'same as default'
+ const limitElem = el('span', {textContent: value, className: `${diffClass} nowrap adapter-limit`, title});
return [
k,
- isDiff
- ? [value, {className: `${diffClass} nowrap`, title: `default: ${shortSize}`}]
- : [value, {className: 'nowrap', title: 'same as default'}]
+ [defaultElem, limitElem],
];
})
);
@@ -302,6 +309,9 @@ function parseAdapterFlags(adapter) {
return flags;
}
+const requestLink = 'https://webgpufundamentals.org/webgpu/lessons/webgpu-limits-and-features.html';
+const requestHint = 'limits greater than default must be specified when requesting adapter';
+
async function adapterToElements(adapter) {
if (!adapter) {
return;
@@ -311,15 +321,28 @@ async function adapterToElements(adapter) {
const device = await adapter.requestDevice() || {}
const limitsSectionElem = el('tr', {className: 'section'}, [
- el('td', {colSpan: 2}, [
+ el('td', {}, [
createHeading('div', '-', {}, [
- createElem('span', {textContent: 'limits: '}),
- ...makeBracketedLink('https://webgpufundamentals.org/webgpu/lessons/webgpu-limits-and-features.html', 'must be requested'),
+ createElem('span', {textContent: 'limits: ', title: requestHint}),
+ ...makeBracketedLink(requestLink, 'must be requested'),
]),
]),
+ el('td', {}, [
+ el(
+ 'label',
+ {
+ className: 'nowrap hide-on-copy',
+ textContent: 'show defaults',
+ onInput: function() {
+ this.closest('table').classList.toggle('show-default-limits', this.checked);
+ },
+ }, [
+ el('input', {type: 'checkbox'}),
+ ]),
+ ]),
]);
- return el('table', {}, [
+ return el('table', {className: 'show-adapter-limits'}, [
el('tbody', {}, [
el('tr', {className: 'section'}, [
el('td', {colSpan: 2}, [createHeading('div', '-', 'adapter info:')]),
@@ -330,12 +353,14 @@ async function adapterToElements(adapter) {
]),
...mapLikeToTableRows(parseAdapterFlags(adapter)),
limitsSectionElem,
- ...mapLikeToTableRows(markDifferencesInLimits(adapter, device), true),
+ ...mapLikeToTableRows({
+ ...markDifferencesInLimits(adapter, device),
+ }, true),
el('tr', {className: 'section'}, [
el('td', {colSpan: 2}, [
createHeading('div', '-', {}, [
createElem('span', {textContent: 'features: '}),
- ...makeBracketedLink('https://webgpufundamentals.org/webgpu/lessons/webgpu-limits-and-features.html', 'must be requested'),
+ ...makeBracketedLink(requestLink, 'must be requested'),
]),
]),
]),
@@ -529,6 +554,7 @@ function getSelectionText(all) {
const dynamicStyle = document.querySelector('#dynamic-style');
dynamicStyle.textContent = `
body { white-space: pre !important; }
+ .nowrap { white-space: pre !important; }
.copy { display: initial; }
.hide-on-copy { display: none !important; }
`;
@@ -586,7 +612,72 @@ function getSelectionText(all) {
dynamicStyle.textContent = '';
return text;
+}
+
+function formatSectionForCopyPasteSave({head, rows}) {
+ // Get the width of each column
+ const longest = [];
+ for (const row of rows) {
+ for (let c = 0; c < row.cells.length; ++c) {
+ longest[c] = Math.max(longest[c] ?? 0, row.cells[c].textContent.length);
+ }
+ }
+
+ // check the first row to see if this is a 2 column section. If so,
+ // add space for a ':'
+ if (rows.length) {
+ const lastNonEmptyColumn = [...rows[0].cells].findLastIndex(e => e.textContent.trim().length > 0);
+ if (lastNonEmptyColumn >= 1) {
+ ++longest[0];
+ }
+ }
+
+ // padEnd all except the last column unless there is nothing in trailing columns
+ for (const row of rows) {
+ const lastNonEmptyColumn = [...row.cells].findLastIndex(e => e.textContent.trim().length > 0);
+
+ if (lastNonEmptyColumn >= 1) {
+ row.cells[0].append(createHidden(':'));
+ }
+
+ for (let c = 0; c < lastNonEmptyColumn; ++c) {
+ const cell = row.cells[c];
+ cell.appendChild(createHidden(''.padEnd(longest[c] - cell.textContent.length)));
+ }
+
+ if (lastNonEmptyColumn >= 0) {
+ row.cells[0].prepend(createHidden('* '));
+ }
}
+}
+
+function formatTableForCopyPasteSave(table) {
+ const sections = [];
+ let section = {
+ rows: [],
+ };
+
+ const addSection = () => {
+ if (section) {
+ sections.push(section);
+ }
+ };
+
+ for (const row of table.rows) {
+ if (row.classList.contains('section')) {
+ addSection();
+ section = {
+ head: row,
+ rows: [],
+ };
+ } else {
+ section.rows.push(row);
+ }
+ }
+ addSection();
+
+ sections.forEach(formatSectionForCopyPasteSave);
+}
async function main() {
if (!navigator.gpu?.requestAdapter) {
@@ -664,11 +755,18 @@ async function main() {
});
document.querySelector('#download').addEventListener('click', () => {
+ const showDefaults = [...document.querySelectorAll('table.show-default-limits')];
+ showDefaults.forEach(e => e.classList.remove('show-default-limits'));
+
const text = getSelectionText(true);
const blob = new Blob([text], {type: 'text/text'});
const filename = `webgpureport-${new Date().toISOString().replace(/[^a-z0-9-]/ig, '-')}.txt`;
saveData(blob, filename);
- });
+
+ showDefaults.forEach(e => e.classList.add('show-default-limits'));
+ });
+
+ document.querySelectorAll('table').forEach(formatTableForCopyPasteSave);
}
main();