Skip to content
Merged
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 docs/_includes/mesh-band.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
data-distortion="{{ include.distortion | default: 0.8 }}"
data-swirl="{{ include.swirl | default: 0.55 }}">
<canvas aria-hidden="true" focusable="false"></canvas>
<noscript><img src="{{ include.still | relative_url }}" alt="{{ include.alt | default: '' }}" width="1480" height="440"></noscript>
<noscript><img src="{{ include.still | relative_url }}" alt="{{ include.alt | default: '' }}" width="923" height="213"></noscript>
<figcaption>
{{ include.caption }}
<span class="mesh-band-credit">Field: <a href="https://github.com/paper-design/shaders" rel="noopener">Paper Shaders</a> mesh gradient (Apache-2.0), palette and weights from this note.</span>
Expand Down
Binary file modified docs/assets/figures/mage-004/mesh-band.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/figures/mage-007/mesh-band.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 39 additions & 3 deletions docs/assets/mesh-band.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ uniform float u_grainMixer;
uniform float u_grainOverlay;
uniform float u_intensity;
uniform float u_opacityGain;
uniform float u_saturation;
uniform vec2 u_pointer;

in vec2 v_objectUV;
out vec4 fragColor;
Expand Down Expand Up @@ -105,7 +107,8 @@ void main() {
for (int i = 0; i < 10; i++) {
if (i >= int(u_colorsCount)) break;

vec2 pos = getPosition(i, t) + mixerGrain;
// The field leans toward the pointer: the nearer spot leads, the far ones trail.
vec2 pos = getPosition(i, t) + mixerGrain + u_pointer * (0.18 / (1.0 + float(i)));
vec3 colorFraction = u_colors[i].rgb * u_colors[i].a;
float opacityFraction = u_colors[i].a;

Expand All @@ -127,6 +130,11 @@ void main() {
color *= u_intensity;
opacity *= u_opacityGain;

// Paper's shader averages the spots, which on a page of two or three colours
// drags everything toward grey. Push the result back away from its own luma so
// the band reads as colour rather than as a wash.
color = mix(vec3(dot(color, vec3(0.2126, 0.7152, 0.0722))), color, u_saturation);

if (u_grainOverlay > 0.) {
float grainOverlay = valueNoise(rotate(grainUV, 1.) + vec2(3.));
grainOverlay = mix(grainOverlay, valueNoise(rotate(grainUV, 2.) + vec2(-1.)), .5);
Expand Down Expand Up @@ -254,6 +262,8 @@ void main() {
grainOverlay: gl.getUniformLocation(prog, "u_grainOverlay"),
intensity: gl.getUniformLocation(prog, "u_intensity"),
opacityGain: gl.getUniformLocation(prog, "u_opacityGain"),
saturation: gl.getUniformLocation(prog, "u_saturation"),
pointer: gl.getUniformLocation(prog, "u_pointer"),
};

gl.uniform4fv(u.colors, spots);
Expand All @@ -262,12 +272,18 @@ void main() {
gl.uniform1f(u.swirl, Number(band.dataset.swirl ?? 0.55));
gl.uniform1f(u.grainMixer, Number(band.dataset.grainMixer ?? 0.05));
gl.uniform1f(u.grainOverlay, Number(band.dataset.grainOverlay ?? 0.04));
gl.uniform1f(u.intensity, Number(band.dataset.intensity ?? 0.8));
gl.uniform1f(u.opacityGain, Number(band.dataset.opacity ?? 0.6));
gl.uniform1f(u.intensity, Number(band.dataset.intensity ?? 0.5));
gl.uniform1f(u.opacityGain, Number(band.dataset.opacity ?? 0.85));
gl.uniform1f(u.saturation, Number(band.dataset.saturation ?? 2.9));
gl.uniform2f(u.pointer, 0, 0);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.clearColor(...BACKDROP);

// Pointer state: a direction the spots lean toward, eased into and out of.
const pointer = [0, 0];
const pointerTarget = [0, 0];

function resize() {
const dpr = Math.min(window.devicePixelRatio || 1, 1.5);
const width = Math.max(1, Math.round(canvas.clientWidth * dpr));
Expand All @@ -283,6 +299,10 @@ void main() {

function frame(seconds) {
resize();
// The pointer pulls the field, then the field settles back on its own.
pointer[0] += (pointerTarget[0] - pointer[0]) * 0.07;
pointer[1] += (pointerTarget[1] - pointer[1]) * 0.07;
gl.uniform2f(u.pointer, pointer[0], pointer[1]);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.uniform1f(u.time, seconds);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
Expand Down Expand Up @@ -333,6 +353,22 @@ void main() {
window.addEventListener("resize", () => {
if (reduced.matches) frame(0);
});

// A mouse leans the field; lifts of the pointer let it settle back. Touch is
// left alone so the band never competes with scrolling.
if (!reduced.matches) {
band.addEventListener("pointermove", (event) => {
if (event.pointerType && event.pointerType !== "mouse") return;
const rect = canvas.getBoundingClientRect();
if (!rect.width || !rect.height) return;
pointerTarget[0] = ((event.clientX - rect.left) / rect.width - 0.5) * 2;
pointerTarget[1] = -((event.clientY - rect.top) / rect.height - 0.5) * 2;
});
band.addEventListener("pointerleave", () => {
pointerTarget[0] = 0;
pointerTarget[1] = 0;
});
}
}

function boot() {
Expand Down
2 changes: 1 addition & 1 deletion docs/assets/notebook.css
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ mjx-container[display] { overflow-x: auto; overflow-y: hidden; padding-block: 8p
radial-gradient(90% 130% at 52% 92%, rgba(147, 202, 255, .16), transparent 58%),
var(--paper);
}
.mesh-band canvas { display: block; width: 100%; height: auto; aspect-ratio: 740 / 220; opacity: 0; transition: opacity .7s ease; }
.mesh-band canvas { display: block; width: 100%; height: auto; aspect-ratio: 740 / 220; max-height: 170px; opacity: 0; transition: opacity .7s ease; }
.mesh-band[data-ready] canvas { opacity: 1; }
.mesh-band noscript img { display: block; width: 100%; height: auto; }
.mesh-band figcaption { padding: 10px 14px 12px; border-top: 1px solid var(--rule); background: var(--surface); font-size: 12px; line-height: 1.7; color: var(--muted); }
Expand Down
2 changes: 1 addition & 1 deletion docs/experiments/mage-004.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ mesh_band: true
weights="0.74,1,0.62,0.90"
still="/assets/figures/mage-004/mesh-band.png"
alt="A dark field with four soft spots of colour — blue, green, lavender and warm sand — scaled by how competitive each implementation is."
caption="The four spots are the four implementations in the table below, opacity set by the geometric mean of their kernel times relative to the best implementation on each operation: Triton brightest, cuTile Rust dimmest." %}**The claim.** Give a compiler the job of deciding how a GPU kernel places its data, and it will do
caption="The four spots are the four implementations, opacity set by how competitive each is." %}**The claim.** Give a compiler the job of deciding how a GPU kernel places its data, and it will do
a good job where the reuse is low and a worse one where the reuse is high. On five FP32 operations,
the [cuTile Rust](https://github.com/NVlabs/cutile-rs) tile kernels beat the hand-written ones on
bias + GELU ($8.19\ \mu s$ against $11.0$), draw on layer normalization ($10.76$ against $10.05$),
Expand Down
2 changes: 1 addition & 1 deletion docs/experiments/mage-007.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ mesh_band: true
weights="1.34,1.37,0.38"
still="/assets/figures/mage-007/mesh-band.png"
alt="A dark field with three soft spots of colour — green, blue and muted red — the red one much dimmer than the other two."
caption="The three spots are this note's three changes, opacity set by the speed-up each measured: neighbour aggregation (1.34×, green), LayerNorm (1.37×, blue) and the rejected GELU variant (0.38×, red)." %}**The claim.** Making each thread load four values at once instead of one is a memory decision, not
caption="The three spots are this note's three changes, opacity set by the speed-up each measured." %}**The claim.** Making each thread load four values at once instead of one is a memory decision, not
an optimisation: it cuts the number of load instructions by four and puts $4\times$ the bytes in
flight per instruction. It pays when a kernel is short of instructions to issue. It costs when the
grid already fills the machine and the only thing hiding memory latency is how many threads are
Expand Down
Loading