Published
Edited
Aug 10, 2021
Importers
2 stars
Insert cell
Insert cell
shader({ width: 640, height: 100 })`
fn main(uv: vec2<f32>) -> vec4<f32> {
return vec4<f32>(uv, 0.0, 1.0);
}`
Insert cell
shader({ width: 640, height: 100 })`
let size = 25.0;

fn main(uv: vec2<f32>) -> vec4<f32> {
var p = uv * u.resolution.xy;
var q = p.x % size * 2.0 < size == p.y % size * 2.0 < size;
var o = f32(q);
return vec4<f32>(o,o,o,1.0);
}`
Insert cell
shader({
width: 640,
height: 100,
iTime: true
})`
fn rotate2d(a: f32) -> mat2x2<f32> {
let c = cos(a);
let s = sin(a);
return mat2x2<f32>(
vec2<f32>(c, -s),
vec2<f32>(s, c)
);
}

let size = 15.0;

fn main(uv: vec2<f32>) -> vec4<f32> {
var p = (uv - 0.5) * (u.resolution.xy) * rotate2d(u.time / 10.0);
if (p.x < 0.0) {p.x = p.x - size;}
if (p.y < 0.0) {p.y = p.y - size;}
p = abs(p);
let q = p.x % (size * 2.0) < size == p.y % (size * 2.0) < size;
let o = f32(q);
return vec4<f32>(o,o,o,1.0);
}
`
Insert cell
canvas = shader({
width: 640,
height: 100,
uniforms: { angle: 0 }
})`
fn rotate2d(a: f32) -> mat2x2<f32> {
let c = cos(a);
let s = sin(a);
return mat2x2<f32>(
vec2<f32>(c, -s),
vec2<f32>(s, c)
);
}

let size = 15.0;

fn main(uv: vec2<f32>) -> vec4<f32> {
var p = (uv - 0.5) * (u.resolution.xy) * rotate2d(u.angle);
if (p.x < 0.0) {p.x = p.x - size;}
if (p.y < 0.0) {p.y = p.y - size;}
p = abs(p);
let q = p.x % (size * 2.0) < size == p.y % (size * 2.0) < size;
let o = f32(q);
return vec4<f32>(o,o,o,1.0);
}
`
Insert cell
canvas.update({ angle: (now / 10000.0) % (2 * Math.PI) })
Insert cell
Insert cell
Insert cell
shader({
height: 100,
inputs: { angle: viewof angle, size: viewof size },
visibility
})`
fn rotate2d(a: f32) -> mat2x2<f32> {
let c = cos(a);
let s = sin(a);
return mat2x2<f32>(
vec2<f32>(c, -s),
vec2<f32>(s, c)
);
}


fn main(uv: vec2<f32>) -> vec4<f32> {
var p = (uv - 0.5) * (u.resolution.xy) * rotate2d(u.angle);
if (p.x < 0.0) {p.x = p.x - u.size;}
if (p.y < 0.0) {p.y = p.y - u.size;}
p = abs(p);
let q = p.x % (u.size * 2.0) < u.size == p.y % (u.size * 2.0) < u.size;
let o = f32(q);
return vec4<f32>(o,o,o,1.0);
}
`
Insert cell
Insert cell
function shader({
width = 640,
height = 480,
devicePixelRatio = window.devicePixelRatio,
invalidation,
visibility,
uniforms = {},
inputs = {},
iTime = false,
sources = [],
preserveDrawingBuffer = false
} = {}) {
if (visibility !== undefined && typeof visibility !== "function")
throw new Error("invalid visibility");
return async function () {
const source = String.raw.apply(String, arguments);
const canvas = DOM.canvas(
width * devicePixelRatio,
height * devicePixelRatio
);
canvas.style = `max-width: 100%; width: ${width}px; height: auto;`;
const ctx = canvas.getContext("webgpu");
if (!navigator.gpu) {
throw new Error(
"`gpupresent` context could not be created. Is WebGPU enabled on your device?"
);
}
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
ctx.configure({
device,
format: "bgra8unorm"
});
const ondispose =
invalidation === undefined ? Inputs.disposal(canvas) : invalidation;
let frame;
let disposed = false;
ondispose.then(() => (disposed = true));

const shader = device.createShaderModule({
code: `
[[block]] struct Uniforms {
resolution: vec3<f32>;
time: f32;
${Object.keys(uniforms)
.map((name) => `${name}: f32;`)
.join("\n")}
${Object.keys(inputs)
.map((name) => `${name}: f32;`)
.join("\n")}
};

[[group(0), binding(0)]] var<uniform> u: Uniforms;

struct VertexInput {
[[location(0)]] pos: vec2<f32>;
};

struct VertexOutput {
[[builtin(position)]] pos: vec4<f32>;
[[location(0)]] uv: vec2<f32>;
};

[[stage(vertex)]]
fn main_vertex(input: VertexInput) -> VertexOutput {
var output: VertexOutput;
var pos: vec2<f32> = input.pos * 2.0 - 1.0;
output.pos = vec4<f32>(pos, 0.0, 1.0);
output.uv = input.pos;
return output;
}

${[...sources].join("\n")}
${source}

[[stage(fragment)]]
fn main_fragment(in: VertexOutput) -> [[location(0)]] vec4<f32> {
let x = u.resolution; // need to use all inputs
return main(in.uv);
}
`
});
const pipeline = device.createRenderPipeline({
vertex: {
module: shader,
entryPoint: "main_vertex",
buffers: [
{
arrayStride: Float32Array.BYTES_PER_ELEMENT * 2,
attributes: [
{
offset: 0,
shaderLocation: 0,
format: "float32x2"
}
]
}
]
},
fragment: {
module: shader,
entryPoint: "main_fragment",
targets: [{ format: "bgra8unorm" }]
},
primitives: {
topology: "triangle-list"
}
});
const textureView = ctx.getCurrentTexture().createView();
const renderPassDescriptor = {
colorAttachments: [
{
view: textureView,
loadValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
storeOp: "store"
}
]
};
const attribs = new Float32Array([0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1]);
const attribsBuffer = createBuffer(device, attribs, GPUBufferUsage.VERTEX);

let uniformsArray = new Float32Array([
width, // res.x
height, // res.y
devicePixelRatio, // res.z
0, // time
...Object.values(uniforms),
...Array.from(Object.values(inputs), (input) => input.value)
]);

let uniformsBuffer = createBuffer(
device,
uniformsArray,
GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
);

async function render() {
const commandEncoder = device.createCommandEncoder();
const textureView = ctx.getCurrentTexture().createView();
renderPassDescriptor.colorAttachments[0].view = textureView;
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
passEncoder.setPipeline(pipeline);
const bindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [
{
binding: 0,
resource: {
buffer: uniformsBuffer
}
}
]
});

passEncoder.setBindGroup(0, bindGroup);
passEncoder.setVertexBuffer(0, attribsBuffer);
passEncoder.draw(6, 1, 0, 0);
passEncoder.endPass();
device.queue.submit([commandEncoder.finish()]);
}

const uniformIndex = new Map(
Object.entries(Object.keys(uniforms)).map(([idx, key]) => [key, +idx])
);

Object.assign(canvas, {
update(values = {}) {
if (disposed) return false;
for (const [name, value] of Object.entries(values)) {
if (uniformIndex.get(name) == undefined)
throw new Error(`Could not find uniform ${name}`);
uniformsArray.set([value], 4 + uniformIndex.get(name));
}
uniformsBuffer = createBuffer(
device,
uniformsArray,
GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
);
frame || requestAnimationFrame(render);
return true;
}
});

const uniformLen = Object.keys(uniforms).length;
for (const [i, input] of Object.entries(Object.values(inputs))) {
const update = () => {
uniformsArray.set([input.value], +i + uniformLen + 4);
uniformsBuffer = createBuffer(
device,
uniformsArray,
GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
);
frame || requestAnimationFrame(render);
};
input.addEventListener("input", update);
ondispose.then(() => input.removeEventListener("input", update));
}

if (iTime) {
frame = true;
let timeframe;
(async function tick() {
if (visibility !== undefined) await visibility();
uniformsArray.set([performance.now() / 1000], 3);
uniformsBuffer = createBuffer(
device,
uniformsArray,
GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
);
render();
return (timeframe = requestAnimationFrame(tick));
})();
ondispose.then(() => cancelAnimationFrame(timeframe));
} else {
render();
}
return canvas;
};
}
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more