Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
0177dd1
feat(effects): add intra refresh params to DatamoshEffect
fand Oct 17, 2026
2baff6d
docs(examples): add Datamosh Scroll demo
fand Oct 17, 2026
2719670
feat(effects): add heal param to DatamoshEffect
fand Oct 17, 2026
334cf66
refactor(effects): rename intraPatchSize to intraClusterSize
fand Oct 17, 2026
3926482
docs(examples): pin magazine layer to the screen in Datamosh Scroll
fand Oct 17, 2026
d896155
docs(examples): mosh the old scene before swapping in Datamosh Scroll
fand Oct 17, 2026
9e8948f
Revert "docs(examples): pin magazine layer to the screen in Datamosh …
fand Oct 17, 2026
2ae4e71
docs(examples): fade chroma gain back to 1 over the heal in Datamosh …
fand Oct 17, 2026
0eb412e
docs(examples): vary the magazine layout per section in Datamosh Scroll
fand Oct 17, 2026
9c5fa7a
docs(examples): move Drop and Flip headings in Datamosh Scroll
fand Oct 17, 2026
857f44b
docs(examples): move Flip heading to the top right in Datamosh Scroll
fand Oct 17, 2026
cdf450d
docs(examples): mosh only video and headings in Datamosh Scroll
fand Oct 17, 2026
42f921f
docs(examples): halve body copy in Datamosh Scroll
fand Oct 17, 2026
5a34a65
docs(examples): compact bottom-left layout for Flip in Datamosh Scroll
fand Oct 17, 2026
a0f2121
docs(examples): use smaller magazine type in all sections of Datamosh…
fand Oct 17, 2026
5e334c2
docs(examples): shrink section numbers in Datamosh Scroll
fand Oct 17, 2026
49e7b3a
docs(examples): fix mobile layout in Datamosh Scroll
fand Oct 17, 2026
33e774c
docs(examples): enlarge main headings on mobile in Datamosh Scroll
fand Oct 17, 2026
4d63a2a
feat(effects): rename intraRate/intraThreshold to healRate/healThreshold
fand Oct 18, 2026
0018e88
feat(effects): rename intraClusterSize to healClusterSize
fand Oct 18, 2026
559a8e7
feat(effects): remove heal param from DatamoshEffect
fand Oct 18, 2026
5869214
docs(examples): drop the cut frame's residual in Datamosh Scroll
fand Oct 18, 2026
cb3c04f
docs(examples): swap one frame after enable in Datamosh Scroll
fand Oct 18, 2026
c052f28
docs(examples): hold the mosh before healing in Datamosh Scroll
fand Oct 18, 2026
bad823c
docs(examples): expose zoom time in Datamosh Scroll pane
fand Oct 18, 2026
05f637e
docs(examples): derive healRate from healMs in Datamosh Scroll
fand Oct 18, 2026
ffa1fc2
docs(examples): tune Datamosh Scroll defaults
fand Oct 18, 2026
b4bce6e
docs(examples): remove zoom kick from Datamosh Scroll
fand Oct 18, 2026
6a250bf
fix: address review findings in Datamosh Scroll
fand Oct 18, 2026
6a76cf7
feat(effects): remove healClusterSize from DatamoshEffect
fand Oct 19, 2026
f82a89a
docs(examples): toggle heading mosh in Datamosh Scroll pane
fand Oct 19, 2026
bcf039f
docs(examples): toggle magazine text mosh instead of headings
fand Oct 19, 2026
1e159ed
docs(examples): swap last video in Datamosh Scroll
fand Oct 19, 2026
4d4e575
docs(examples): pin headings to the screen in Datamosh Scroll
fand Oct 19, 2026
b9ce409
docs(examples): use a fixed VFX canvas in Datamosh Scroll
fand Oct 19, 2026
671aa07
docs(examples): pin magazine text and add scroll progress in Datamosh…
fand Oct 19, 2026
f467789
docs(examples): add page rulers to Datamosh Scroll
fand Oct 19, 2026
fc75a47
docs(examples): unpin magazine text and enlarge headings in Datamosh …
fand Oct 19, 2026
610ce62
docs(examples): keep the new heading clean over the mosh in Datamosh …
fand Oct 19, 2026
83fbf33
docs(examples): tune Datamosh Scroll defaults and drop text mosh toggle
fand Oct 19, 2026
f4dafce
docs(examples): host re-encoded webm videos for Datamosh Scroll
fand Oct 19, 2026
877e562
docs(effects): clarify DatamoshEffect heal param docs
fand Oct 19, 2026
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
7 changes: 7 additions & 0 deletions .changeset/datamosh-intra-refresh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@vfx-js/effects": minor
---

Add heal params to `DatamoshEffect` so the mosh can heal block by
block like a real codec: `healRate` (random refresh chance per block per
frame) and `healThreshold` (refresh blocks with bad motion matches).
45 changes: 39 additions & 6 deletions packages/effects/src/datamosh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
//
// - disabled: every frame decoded as intra -> straight passthrough.
// - enabled: frames decoded as inter -> motion-compensate the held
// accumulator + residual, never refreshing it from a clean frame, so
// motion keeps dragging stale texture around (the mosh).
// accumulator + residual, so motion keeps dragging stale texture around
// (the mosh). Only heal-refreshed blocks go back to the clean frame.
//
// Pipeline per frame (RGB path):
// capture ctx.src -> curRT
// ME cur vs prev -> mvRT (one fragment per block)
// residual cur - mc(prev) -> resRT
// decode intra ? cur : mc(accum, mv) + res -> accRT (persistent)
// (per-block intra refresh heals the mosh back to the source)
// display accRT -> ctx.target
// blit cur -> prevRT (persistent; next frame's reference)
//
Expand All @@ -40,6 +41,8 @@ void main() {
// (px) that best matches the current block, into .rg of a float RT.
// A small length bias breaks ties toward (0,0) so flat blocks don't latch
// onto spurious large vectors.
// Also picks the block mode into .b (1 = intra): random refresh, or a
// poor best match, like an encoder falling back to intra.
const FRAG_ME = `#version 300 es
precision highp float;
out vec4 outMV;
Expand All @@ -48,6 +51,13 @@ uniform vec2 uResolution; // [w, h] in px
uniform float uBlock; // block size in px
uniform float uSearch; // search radius, in steps
uniform float uStep; // px per step (coverage = uSearch * uStep px)
uniform float uHealRate;
uniform float uHealThreshold;
uniform float uFrame;
float hash(vec3 p) {
uvec3 q = uvec3(p) * uvec3(1597334673u, 3812015801u, 2798796415u);
return float((q.x ^ q.y ^ q.z) * 1597334673u) / 4294967295.0;
}
void main() {
vec2 block = floor(gl_FragCoord.xy);
vec2 base = block * uBlock; // top-left pixel of this block
Expand All @@ -66,6 +76,7 @@ void main() {

vec2 move = vec2(0.0);
float bestSad = 1e9;
float bestRaw = 0.0;
for (float by = -uSearch; by <= uSearch; by++) {
for (float bx = -uSearch; bx <= uSearch; bx++) {
vec2 cand = vec2(bx, by) * uStep; // candidate displacement, px
Expand All @@ -74,15 +85,20 @@ void main() {
vec3 cRef = texture(uRef, (base + off[k] + cand + 0.5) / uResolution).rgb;
sad += dot(abs(cs[k] - cRef), vec3(1.0));
}
float raw = sad;
sad += length(cand) * 0.0025; // prefer small motion on ties
if (sad < bestSad) {
bestSad = sad;
bestRaw = raw;
move = cand;
}
}
}

outMV = vec4(move, 0.0, 1.0);
// Mean abs diff per channel, 0..1.
float err = bestRaw / 27.0;
bool intra = hash(vec3(block, uFrame)) < uHealRate || err > uHealThreshold;
outMV = vec4(move, intra ? 1.0 : 0.0, 1.0);
}
`;

Expand Down Expand Up @@ -112,7 +128,7 @@ uniform vec2 uResolution;
uniform bool uIntra;
uniform bool uUseResidual;
void main() {
if (uIntra) {
if (uIntra || texture(uMV, uv).b > 0.5) {
outColor = vec4(texture(uVideo, uv).rgb, 1.0);
return;
}
Expand Down Expand Up @@ -195,7 +211,7 @@ uniform bool uIntra;
uniform bool uUseResidual;
void main() {
// I-frame: passthrough the input
if (uIntra) {
if (uIntra || texture(uMV, uv).b > 0.5) {
outColor = vec4(luma(texture(uVideo, uv).rgb), 0.0, 0.0, 1.0);
return;
}
Expand Down Expand Up @@ -225,7 +241,7 @@ uniform bool uIntra;
uniform bool uUseResidual;
void main() {
// I-frame: passthrough the input
if (uIntra) {
if (uIntra || texture(uMV, uv).b > 0.5) {
outColor = vec4(chroma(texture(uVideo, uv).rgb), 0.0, 1.0);
return;
}
Expand Down Expand Up @@ -294,6 +310,17 @@ export type DatamoshParams = {
colorSpace: DatamoshColorSpace;
/** Chroma saturation boost (YCbCr output). 1 = unchanged. */
chromaGain: number;
/**
* Chance per block per frame to refresh from the clean frame (0..1).
* Ramp up to heal the mosh gradually.
* Works with `healThreshold`: a block refreshes if either one hits.
*/
healRate: number;
/**
* Refresh blocks whose best motion match is worse than this.
* Mean abs diff per channel (0..1); 1 = never.
*/
healThreshold: number;
/** Stage shown on the canvas. */
view: DatamoshView;
};
Expand All @@ -306,6 +333,8 @@ const DEFAULT_PARAMS: DatamoshParams = {
dup: 0,
colorSpace: "ycbcr",
chromaGain: 1,
healRate: 0,
healThreshold: 1,
view: "output",
};

Expand Down Expand Up @@ -336,6 +365,7 @@ export class DatamoshEffect implements Effect {
#cw = 0;
#ch = 0;
#block = 0;
#frame = 0;
// Re-seed when the color model flips (accumulators change meaning).
#lastColorSpace: DatamoshColorSpace;

Expand Down Expand Up @@ -472,6 +502,9 @@ export class DatamoshEffect implements Effect {
uBlock: this.#block,
uSearch: this.params.searchRange,
uStep: this.params.searchStep,
uHealRate: this.params.healRate,
uHealThreshold: this.params.healThreshold,
uFrame: this.#frame++ % 65536,
},
target: mv,
});
Expand Down
11 changes: 11 additions & 0 deletions packages/examples/src/works.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,15 @@ export const works: Work[] = [
"A playground for the Figma shader effect ports: warp, slice shift, pixel stretch, pattern refraction, pixelate, dither, and colored edges. Pick an image and tweak every parameter live with Tweakpane.",
url: "./works/figma-effects.html",
},
{
id: "datamosh-scroll",
index: "16",
title: "Datamosh Scroll",
tags: ["effect-api", "Glitch", "Transition"],
year: "2026",
author: "AMAGI",
description:
"A day of skateboarding, told as a one-page scroll site. Every section change drops the keyframe: the next scene's motion drags the last one along until intra refresh heals it block by block.",
url: "./works/datamosh-scroll.html",
},
];
Loading
Loading