Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
WebGPU Fundamentals
=====================
===================

This is [a series of lessons or tutorials about webgpu](http://webgpufundamentals.org/).

Expand Down
3 changes: 3 additions & 0 deletions toc.hanson
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
],
'post-processing': [
'webgpu-post-processing.md',
'webgpu-image-adjustments.md',
'webgpu-1dlut.md',
'webgpu-3dlut.md',
],
},
'compute-shaders': [
Expand Down
21 changes: 21 additions & 0 deletions webgpu/lessons/webgpu-1dlut.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* intentionally empty at the moment */

.img-grid[data-diagram] {
display:grid;
grid-template-columns:repeat(auto-fit,250px); /* same as column width */
gap:10px; /* same as column gap */
}
.img-grid-cols {
columns: 250px;
column-gap: 1em;
grid-column: 1/-1; /* take all the columns of the grid*/
}
.img-grid-item {
display: inline-flex;
flex-direction: column;
margin-bottom: 0.5em;
}
.img-grid img {
width: 100%;
border: 1px solid #888;
}
53 changes: 53 additions & 0 deletions webgpu/lessons/webgpu-1dlut.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
renderDiagrams
} from './resources/diagrams.js';
import {
createElem as el
} from './resources/elem.js';

async function luts(elem) {
const luts = [
{ name: 'monochrome', url: 'resources/images/lut/monochrome-s8.png' },
{ name: 'sepia', url: 'resources/images/lut/sepia-s8.png' },
{ name: 'saturated', url: 'resources/images/lut/saturated-s8.png', },
{ name: 'posterize', url: 'resources/images/lut/posterize-s8n.png', },
{ name: 'posterize-3-rgb', url: 'resources/images/lut/posterize-3-rgb-s8n.png', },
{ name: 'posterize-3-lab', url: 'resources/images/lut/posterize-3-lab-s8n.png', },
{ name: 'posterize-4-lab', url: 'resources/images/lut/posterize-4-lab-s8n.png', },
{ name: 'posterize-more', url: 'resources/images/lut/posterize-more-s8n.png', },
{ name: 'inverse', url: 'resources/images/lut/inverse-s8.png', },
{ name: 'color negative', url: 'resources/images/lut/color-negative-s8.png', },
{ name: 'high contrast', url: 'resources/images/lut/high-contrast-bw-s8.png', },
{ name: 'funky contrast', url: 'resources/images/lut/funky-contrast-s8.png', },
{ name: 'nightvision', url: 'resources/images/lut/nightvision-s8.png', },
{ name: 'thermal', url: 'resources/images/lut/thermal-s8.png', },
{ name: 'b/w', url: 'resources/images/lut/black-white-s8n.png', },
{ name: 'hue +60', url: 'resources/images/lut/hue-plus-60-s8.png', },
{ name: 'hue +180', url: 'resources/images/lut/hue-plus-180-s8.png', },
{ name: 'hue -60', url: 'resources/images/lut/hue-minus-60-s8.png', },
{ name: 'red to cyan', url: 'resources/images/lut/red-to-cyan-s8.png' },
{ name: 'blues', url: 'resources/images/lut/blues-s8.png' },
{ name: 'infrared', url: 'resources/images/lut/infrared-s8.png' },
{ name: 'radioactive', url: 'resources/images/lut/radioactive-s8.png' },
{ name: 'goolgey', url: 'resources/images/lut/googley-s8.png' },
{ name: 'bgy', url: 'resources/images/lut/bgy-s8.png' },
];

elem.append(
el('div', { className: 'img-grid-cols' }, luts.map(({ name, url}) =>
el('div', { className: 'img-grid-item' }, [
el('img', { src: `/webgpu/${url}`, alt: name }),
el('div', { textContent: name }),
]))
)
);
}


async function main() {
renderDiagrams({
luts,
});
}

main();
18 changes: 18 additions & 0 deletions webgpu/lessons/webgpu-1dlut.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Title: WebGPU Post Processing - 1D Lookup Tables (1D-LUT)
Description: 1D Lookup Tables (LUT)
TOC: 1D Lookup Table (LUT)

ddd

<div class="webgpu_center">
<div class="img-grid" data-diagram="luts"></div></div>
</div>



{{{example url="../webgpu-post-processing-3d-lookup-table(lut).html"}}}


<!-- keep this at the bottom of the article -->
<link href="webgpu-1dlut.css" rel="stylesheet">
<script type="module" src="webgpu-1dlut.js"></script>
21 changes: 21 additions & 0 deletions webgpu/lessons/webgpu-3dlut.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* intentionally empty at the moment */

.img-grid[data-diagram] {
display:grid;
grid-template-columns:repeat(auto-fit,250px); /* same as column width */
gap:10px; /* same as column gap */
}
.img-grid-cols {
columns: 250px;
column-gap: 1em;
grid-column: 1/-1; /* take all the columns of the grid*/
}
.img-grid-item {
display: inline-flex;
flex-direction: column;
margin-bottom: 0.5em;
}
.img-grid img {
width: 100%;
border: 1px solid #888;
}
53 changes: 53 additions & 0 deletions webgpu/lessons/webgpu-3dlut.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
renderDiagrams
} from './resources/diagrams.js';
import {
createElem as el
} from './resources/elem.js';

async function luts(elem) {
const luts = [
{ name: 'monochrome', url: 'resources/images/lut/monochrome-s8.png' },
{ name: 'sepia', url: 'resources/images/lut/sepia-s8.png' },
{ name: 'saturated', url: 'resources/images/lut/saturated-s8.png', },
{ name: 'posterize', url: 'resources/images/lut/posterize-s8n.png', },
{ name: 'posterize-3-rgb', url: 'resources/images/lut/posterize-3-rgb-s8n.png', },
{ name: 'posterize-3-lab', url: 'resources/images/lut/posterize-3-lab-s8n.png', },
{ name: 'posterize-4-lab', url: 'resources/images/lut/posterize-4-lab-s8n.png', },
{ name: 'posterize-more', url: 'resources/images/lut/posterize-more-s8n.png', },
{ name: 'inverse', url: 'resources/images/lut/inverse-s8.png', },
{ name: 'color negative', url: 'resources/images/lut/color-negative-s8.png', },
{ name: 'high contrast', url: 'resources/images/lut/high-contrast-bw-s8.png', },
{ name: 'funky contrast', url: 'resources/images/lut/funky-contrast-s8.png', },
{ name: 'nightvision', url: 'resources/images/lut/nightvision-s8.png', },
{ name: 'thermal', url: 'resources/images/lut/thermal-s8.png', },
{ name: 'b/w', url: 'resources/images/lut/black-white-s8n.png', },
{ name: 'hue +60', url: 'resources/images/lut/hue-plus-60-s8.png', },
{ name: 'hue +180', url: 'resources/images/lut/hue-plus-180-s8.png', },
{ name: 'hue -60', url: 'resources/images/lut/hue-minus-60-s8.png', },
{ name: 'red to cyan', url: 'resources/images/lut/red-to-cyan-s8.png' },
{ name: 'blues', url: 'resources/images/lut/blues-s8.png' },
{ name: 'infrared', url: 'resources/images/lut/infrared-s8.png' },
{ name: 'radioactive', url: 'resources/images/lut/radioactive-s8.png' },
{ name: 'goolgey', url: 'resources/images/lut/googley-s8.png' },
{ name: 'bgy', url: 'resources/images/lut/bgy-s8.png' },
];

elem.append(
el('div', { className: 'img-grid-cols' }, luts.map(({ name, url}) =>
el('div', { className: 'img-grid-item' }, [
el('img', { src: `/webgpu/${url}`, alt: name }),
el('div', { textContent: name }),
]))
)
);
}


async function main() {
renderDiagrams({
luts,
});
}

main();
18 changes: 18 additions & 0 deletions webgpu/lessons/webgpu-3dlut.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Title: WebGPU Post Processing - 3d lookup table (LUT)
Description: 3D lookup table (LUT)
TOC: 3D Lookup Table (LUT)

ddd

<div class="webgpu_center">
<div class="img-grid" data-diagram="luts"></div></div>
</div>



{{{example url="../webgpu-post-processing-3d-lookup-table(lut).html"}}}


<!-- keep this at the bottom of the article -->
<link href="webgpu-3dlut.css" rel="stylesheet">
<script type="module" src="webgpu-3dlut.js"></script>
21 changes: 21 additions & 0 deletions webgpu/lessons/webgpu-image-adjustments.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* intentionally empty at the moment */

.img-grid[data-diagram] {
display:grid;
grid-template-columns:repeat(auto-fit,250px); /* same as column width */
gap:10px; /* same as column gap */
}
.img-grid-cols {
columns: 250px;
column-gap: 1em;
grid-column: 1/-1; /* take all the columns of the grid*/
}
.img-grid-item {
display: inline-flex;
flex-direction: column;
margin-bottom: 0.5em;
}
.img-grid img {
width: 100%;
border: 1px solid #888;
}
53 changes: 53 additions & 0 deletions webgpu/lessons/webgpu-image-adjustments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
renderDiagrams
} from './resources/diagrams.js';
import {
createElem as el
} from './resources/elem.js';

async function luts(elem) {
const luts = [
{ name: 'monochrome', url: 'resources/images/lut/monochrome-s8.png' },
{ name: 'sepia', url: 'resources/images/lut/sepia-s8.png' },
{ name: 'saturated', url: 'resources/images/lut/saturated-s8.png', },
{ name: 'posterize', url: 'resources/images/lut/posterize-s8n.png', },
{ name: 'posterize-3-rgb', url: 'resources/images/lut/posterize-3-rgb-s8n.png', },
{ name: 'posterize-3-lab', url: 'resources/images/lut/posterize-3-lab-s8n.png', },
{ name: 'posterize-4-lab', url: 'resources/images/lut/posterize-4-lab-s8n.png', },
{ name: 'posterize-more', url: 'resources/images/lut/posterize-more-s8n.png', },
{ name: 'inverse', url: 'resources/images/lut/inverse-s8.png', },
{ name: 'color negative', url: 'resources/images/lut/color-negative-s8.png', },
{ name: 'high contrast', url: 'resources/images/lut/high-contrast-bw-s8.png', },
{ name: 'funky contrast', url: 'resources/images/lut/funky-contrast-s8.png', },
{ name: 'nightvision', url: 'resources/images/lut/nightvision-s8.png', },
{ name: 'thermal', url: 'resources/images/lut/thermal-s8.png', },
{ name: 'b/w', url: 'resources/images/lut/black-white-s8n.png', },
{ name: 'hue +60', url: 'resources/images/lut/hue-plus-60-s8.png', },
{ name: 'hue +180', url: 'resources/images/lut/hue-plus-180-s8.png', },
{ name: 'hue -60', url: 'resources/images/lut/hue-minus-60-s8.png', },
{ name: 'red to cyan', url: 'resources/images/lut/red-to-cyan-s8.png' },
{ name: 'blues', url: 'resources/images/lut/blues-s8.png' },
{ name: 'infrared', url: 'resources/images/lut/infrared-s8.png' },
{ name: 'radioactive', url: 'resources/images/lut/radioactive-s8.png' },
{ name: 'goolgey', url: 'resources/images/lut/googley-s8.png' },
{ name: 'bgy', url: 'resources/images/lut/bgy-s8.png' },
];

elem.append(
el('div', { className: 'img-grid-cols' }, luts.map(({ name, url}) =>
el('div', { className: 'img-grid-item' }, [
el('img', { src: `/webgpu/${url}`, alt: name }),
el('div', { textContent: name }),
]))
)
);
}


async function main() {
renderDiagrams({
luts,
});
}

main();
26 changes: 26 additions & 0 deletions webgpu/lessons/webgpu-image-adjustments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Title: WebGPU Post Processing - Image Adjustments
Description: Image Adjustments
TOC: Image Adjustments

In previous article we covered how to do [post processing](webgpu-post-processing.html). Some common operations to
want to do are often called, image adjustments as seen in
image editing programs like Photoshop, gIMP, Affinity Photo, etc...

In preparation, lets make an example that load an image and has
a post processing step. This wil be effectively the first part
of [the previous article](webgpu-post-processing.html) merged
with our example of loading an image from
[the article on loading images into textures](webgpu-importing-textures.html).







{{{example url="../webgpu-post-processing-3d-lookup-table(lut).html"}}}


<!-- keep this at the bottom of the article -->
<link href="webgpu-image-adjustments.css" rel="stylesheet">
<script type="module" src="webgpu-image-adjustments.js"></script>
9 changes: 7 additions & 2 deletions webgpu/lessons/webgpu-post-processing.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ TOC: Basic CRT Effect
Post Processing just means to do some processing after you've created the "original" image.
Post processing can apply to a photo, a video, a 2d scene, a 3d scene. It just generally
means you have an image and you apply some effects to that image, like choosing a filter
in Instagram.
on Instagram.

In almost every example on this site we render to the canvas texture. To do post processing
we instead render to a different texture. Then render that texture to the canvas while
Expand Down Expand Up @@ -720,7 +720,6 @@ This works

This is much faster! But, unfortunately, on some GPUs it is still slower than using a render pass.


<div class="webgpu_center data-table">
<table>
<thead>
Expand Down Expand Up @@ -750,6 +749,12 @@ benefit from the shared data of workgroups and or subgroups. GPUs have been rend
to textures for much longer than they've been running compute shaders so many things
about that process are highly optimized.

---

This article introduced the concept of *post processing*.
In the next article we'll cover some
[common post processing image adjustments](webgpu-image-adjustments.html).

<!-- keep this at the bottom of the article -->
<link href="webgpu-post-processing.css" rel="stylesheet">
<script type="module" src="webgpu-post-processing.js"></script>
13 changes: 13 additions & 0 deletions webgpu/resources/js/on-paste-image.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default function onPasteImage(fn) {
document.addEventListener('paste', (e) => {
e.preventDefault();
const items = (event.clipboardData || event.originalEvent.clipboardData).items;
for (let i = 0; i < items.length; i++) {
if (items[i].kind === 'file' && items[i].type.startsWith('image/')) {
const blob = items[i].getAsFile(); // Get the image as a Blob (File object)
fn(blob);
break;
}
}
});
}
28 changes: 28 additions & 0 deletions webgpu/resources/js/pick-image.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const fileElem = document.createElement('input');
fileElem.type = 'file';
fileElem.accept = 'image/*';

let resolve;

const finish = (file) => {
console.log('finish:', file);
fileElem.removeEventListener('change', onChange);
fileElem.removeEventListener('cancel', onCancel);
const r = resolve;
resolve = undefined;
r(file);
};

const onChange = fileElem.addEventListener('change', e => {
finish(e.target.files[0]);
});
const onCancel = () => finish();

const pickImage = () => new Promise(_resolve => {
resolve = _resolve;
fileElem.addEventListener('change', onChange);
fileElem.addEventListener('cancel', onCancel);
fileElem.click();
});

export default pickImage;
Loading
Loading