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
38 changes: 28 additions & 10 deletions js/src/45_lod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,14 @@ export function lodWriteGridTexture(
}
}
}
lodUploadGridBytes(gl, tex, data, w, h, !!rgba, filter);
}

function lodUploadGridBytes(gl, tex, data, w, h, isRgba, filter) {
gl.bindTexture(gl.TEXTURE_2D, tex);
const align = gl.getParameter(gl.UNPACK_ALIGNMENT);
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
if (rgba) {
if (isRgba) {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
} else {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, w, h, 0, gl.RED, gl.UNSIGNED_BYTE, data);
Expand All @@ -136,6 +140,12 @@ export function lodWriteGridTexture(
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
}

export function lodUploadWireGrid(gl, u8Wire, w, h, filter) {
const tex = gl.createTexture();
lodUploadGridBytes(gl, tex, u8Wire, w, h, false, filter);
return tex;
}

// Treat the color scale like exposure: brighten slowly on drill-in so a
// smaller aggregate tile does not suddenly go hot, but recover faster when
// the incoming tile needs more headroom to avoid clipping.
Expand Down Expand Up @@ -1220,14 +1230,19 @@ export function lodApplyDensityUpdate(view, g, upd, buffers) {
}
}
}
const grid = d.enc === "log-u8"
? lodDecodeLogU8(buffers[d.buf], d.max)
: lodCopyGrid(view._asF32(buffers[d.buf]));
// Mean point color plane (LOD doc §2), copied because exposure easing
// re-reads it on every norm step, after the wire buffer may be gone.
// Mean point color plane (LOD doc §2), copied because legend-hover dimming
// and home-restore re-uploads re-read it after the wire buffer may be gone.
const rgba = d.rgba !== undefined ? new Uint8Array(view._asU8(buffers[d.rgba])) : null;
const normStart = lodNormMax(g, d.max);
const normMax = view._prefersReducedMotion() ? d.max : normStart;
const reducedMotion = view._prefersReducedMotion();
const normMax = reducedMotion ? d.max : normStart;
const directWire = d.enc === "log-u8" && !rgba && reducedMotion;
const wire = directWire ? new Uint8Array(view._asU8(buffers[d.buf])) : null;
const grid = directWire
? null
: d.enc === "log-u8"
? lodDecodeLogU8(buffers[d.buf], d.max)
: lodCopyGrid(view._asF32(buffers[d.buf]));
g.densityNormMax = normMax;
g.prevDensity = g.density;
g._densityFadeStart = view._now();
Expand All @@ -1241,12 +1256,15 @@ export function lodApplyDensityUpdate(view, g, upd, buffers) {
// enough to points territory to be worth a round-trip at all.
visible: upd.visible,
grid,
wire,
rgba,
filter,
_filterKey: replyFilterKey,
tex: view._uploadGrid(
grid, d.w, d.h, normMax, rgba, filter, view._fillOpacity(g.trace.style),
),
tex: directWire
? lodUploadWireGrid(view.gl, wire, d.w, d.h, filter)
: view._uploadGrid(
grid, d.w, d.h, normMax, rgba, filter, view._fillOpacity(g.trace.style),
),
lut: g.density.lut,
};
// Exact scans include a view-specific sample and replace the overlay.
Expand Down
11 changes: 7 additions & 4 deletions js/src/54_kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { parseColor } from "./20_theme";
import {
lodAggregateStands, lodAggregateStepWindow, lodApplyDensityUpdate, lodApplyDrill,
lodDrillServesView, lodDropDrill, lodFilterKey, lodPromoteCachedDrill, lodRememberDensity,
lodUploadWireGrid,
} from "./45_lod";
import { xyCreateRebinWorker } from "./46_worker";
import { ChartView } from "./50_chartview";
Expand Down Expand Up @@ -280,10 +281,12 @@ Object.assign(ChartView.prototype, {
const hd = g._homeDensity;
this._applySampleRebinGrid(g, {
...hd,
tex: this._uploadGrid(
hd.grid, hd.w, hd.h, hd.normMax || hd.max || 1, hd.rgba, hd.filter,
this._fillOpacity(g.trace.style),
),
tex: hd.grid
? this._uploadGrid(
hd.grid, hd.w, hd.h, hd.normMax || hd.max || 1, hd.rgba, hd.filter,
this._fillOpacity(g.trace.style),
)
: lodUploadWireGrid(this.gl, hd.wire, hd.w, hd.h, hd.filter),
}, false);
}
return;
Expand Down
43 changes: 42 additions & 1 deletion scripts/render_smoke_nonumpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,35 @@ def main() -> None:
if(c>0&&Math.abs(qg[i]-c)/c>0.08)qok=0;}}
}}
const qwire=(qok && Math.abs(gd.density.max-qmax)<1e-9)?1:0;
// --- Direct-wire density (reduced motion): a count-only log-u8 reply
// uploads the wire bytes verbatim (grid null, normMax pinned to max) and
// retains a copy on density.wire so re-uploads survive without a decoded
// grid. Then the home-extent restore in _requestSampleRebin — the path
// that dereferenced the null grid — must re-upload from the wire copy.
const rmPrev=v._prefersReducedMotion.bind(v);
v._prefersReducedMotion=()=>true;
const rmax=9, renc=new Uint8Array(64);
for(let i=0;i<64;i++){{const c=(i%4===0)?9:(i%7===0?1:0);
renc[i]=c>0?Math.max(1,Math.min(255,Math.round(255*Math.log1p(c)/Math.log1p(rmax)))):0;}}
v._onKernelMsg({{type:"density_update",traces:[{{id:gd.trace.id,mode:"density",visible:12345,
density:{{buf:0,w:8,h:8,max:rmax,enc:"log-u8",x_range:[0,100],y_range:[0,100]}}}}]}},[renc.buffer.slice(0)]);
let dwire=(gd.density.grid===null && gd.density.wire instanceof Uint8Array &&
gd.density.wire.length===64 && gd.density.normMax===rmax &&
v.gl.isTexture(gd.density.tex))?1:0;
if(dwire){{for(let i=0;i<64;i++) if(gd.density.wire[i]!==renc[i]) dwire=0;}}
let drestore=0;
try{{
gd._homeDensity=gd.density;
v._onKernelMsg({{type:"density_update",traces:[{{id:gd.trace.id,mode:"density",visible:2345,
density:{{buf:0,w:8,h:8,max:rmax,enc:"log-u8",x_range:[40,60],y_range:[40,60]}}}}]}},[renc.buffer.slice(0)]);
const zoomed=gd.density!==gd._homeDensity;
if(!v.view0) v.view0={{...v.view}};
v._requestSampleRebin(gd, v.view0, 0);
drestore=(zoomed && gd.density.grid===null && gd.density.wire instanceof Uint8Array &&
gd.density.xRange[1]===100 && v.gl.isTexture(gd.density.tex))?1:0;
}}catch(e){{ drestore=0; }}
gd._homeDensity=null;
v._prefersReducedMotion=rmPrev;
// --- Rapid zoom in/out torture (drill thrash): the marks/density alphas
// must be CONTINUOUS across window-boundary crossings, dying-drill
// revives, and kernel replies landing mid-transition. Runs on a virtual
Expand Down Expand Up @@ -1169,7 +1198,7 @@ def main() -> None:
gMc.readPixels(Math.round(WM*0.8),Math.round(HM*0.5),1,1,gMc.RGBA,gMc.UNSIGNED_BYTE,rpx);
const meancolor=(lpx[0]>60 && lpx[0]>lpx[2]*3 && rpx[2]>60 && rpx[2]>rpx[0]*3)?1:0;
vMc.destroy();holderMc.remove();
const base=`XY_OK lit=${{lit}} total=${{w*h}} labels=${{labels}} pick=${{hits}} row=${{hasXY}} selAll=${{selAll}} selSome=${{selSome}} active=${{active}} btns=${{btns}} modebarHidden=${{modebarHiddenAtRest}} modebarTopLeft=${{modebarTopLeft}} modebarHover=${{modebarHoverReveal}} modebarNoCollapse=${{modebarNoCollapse}} modebarMenu=${{modebarMenu}} modebarDrag=${{modebarDrag}} modebarSelect=${{modebarSelect}} lassoEdit=${{lassoEdit}} modebarExport=${{modebarExport}} panToggle=${{panToggle}} zin=${{zin}} smooth=${{smooth}} labelThrottle=${{labelThrottle}} hoverSkip=${{hoverSkip}} zanch=${{zanch}} retarget=${{retarget}} nosnap=${{nosnap}} prefetch=${{prefetch}} maxwait=${{maxwait}} box=${{boxOk}} xonly=${{xonly}} zmode=${{zmode}} densityLit=${{densityLit}} drill=${{drilled}} pending=${{pending}} dblend=${{dblend}} dseq=${{dseq}} hov=${{hov}} sstale=${{sstale}} sfresh=${{sfresh}} srestore=${{srestore}} plut=${{plut}} reg=${{reg}} refresh=${{refresh}} dpick=${{dpick}} hold=${{hold}} zoomout=${{zoomout}} broad=${{broadfallback}} dying=${{dying}} dback=${{dback}} dnorm=${{dnorm}} dnormDone=${{dnormDone}} stale=${{stale}} thrash=${{thrash}} qwire=${{qwire}} stream=${{stream}} tj=${{Math.round(maxJump*100)}} td=${{Math.round(reviveDip*100)}} malformed=${{malformed}} pixdet=${{pixdet}} splitbuf=${{splitbuf}} barBase=${{barBase}} histBase=${{histBase}} edgepad=${{edgepad}} mgrad=${{mgrad}} axisontop=${{axisontop}} mtipbase=${{mtipbase}} mcorner=${{mcorner}} mstroke=${{mstroke}} bgrad=${{bgrad}} bcorner=${{bcorner}} msmooth=${{msmooth}} bgocc=${{bgocc}} meancolor=${{meancolor}} dretire=${{dretire}}`;
const base=`XY_OK lit=${{lit}} total=${{w*h}} labels=${{labels}} pick=${{hits}} row=${{hasXY}} selAll=${{selAll}} selSome=${{selSome}} active=${{active}} btns=${{btns}} modebarHidden=${{modebarHiddenAtRest}} modebarTopLeft=${{modebarTopLeft}} modebarHover=${{modebarHoverReveal}} modebarNoCollapse=${{modebarNoCollapse}} modebarMenu=${{modebarMenu}} modebarDrag=${{modebarDrag}} modebarSelect=${{modebarSelect}} lassoEdit=${{lassoEdit}} modebarExport=${{modebarExport}} panToggle=${{panToggle}} zin=${{zin}} smooth=${{smooth}} labelThrottle=${{labelThrottle}} hoverSkip=${{hoverSkip}} zanch=${{zanch}} retarget=${{retarget}} nosnap=${{nosnap}} prefetch=${{prefetch}} maxwait=${{maxwait}} box=${{boxOk}} xonly=${{xonly}} zmode=${{zmode}} densityLit=${{densityLit}} drill=${{drilled}} pending=${{pending}} dblend=${{dblend}} dseq=${{dseq}} hov=${{hov}} sstale=${{sstale}} sfresh=${{sfresh}} srestore=${{srestore}} plut=${{plut}} reg=${{reg}} refresh=${{refresh}} dpick=${{dpick}} hold=${{hold}} zoomout=${{zoomout}} broad=${{broadfallback}} dying=${{dying}} dback=${{dback}} dnorm=${{dnorm}} dnormDone=${{dnormDone}} stale=${{stale}} thrash=${{thrash}} qwire=${{qwire}} dwire=${{dwire}} drestore=${{drestore}} stream=${{stream}} tj=${{Math.round(maxJump*100)}} td=${{Math.round(reviveDip*100)}} malformed=${{malformed}} pixdet=${{pixdet}} splitbuf=${{splitbuf}} barBase=${{barBase}} histBase=${{histBase}} edgepad=${{edgepad}} mgrad=${{mgrad}} axisontop=${{axisontop}} mtipbase=${{mtipbase}} mcorner=${{mcorner}} mstroke=${{mstroke}} bgrad=${{bgrad}} bcorner=${{bcorner}} msmooth=${{msmooth}} bgocc=${{bgocc}} meancolor=${{meancolor}} dretire=${{dretire}}`;
const baseWithStyle=`${{base}} vstyle=${{vstyle}}`;
// Responsive: 100%-by-100% chart in a 400x300 container tracks its parent;
// growing the container must fire the ResizeObserver and re-render bigger.
Expand Down Expand Up @@ -1398,6 +1427,8 @@ def main() -> None:
stale = int(re.search(r"stale=(\d+)", title).group(1))
thrash = int(re.search(r"thrash=(\d+)", title).group(1))
qwire = int(re.search(r"qwire=(\d+)", title).group(1))
dwire = int(re.search(r"dwire=(\d+)", title).group(1))
drestore = int(re.search(r"drestore=(\d+)", title).group(1))
stream = int(re.search(r"stream=(\d+)", title).group(1))
malformed = int(re.search(r"malformed=(\d+)", title).group(1))
pixdet = int(re.search(r"pixdet=(\d+)", title).group(1))
Expand Down Expand Up @@ -1564,6 +1595,16 @@ def main() -> None:
)
if qwire != 1:
raise SystemExit("log-u8 density decode failed (quantized wire)")
if dwire != 1:
raise SystemExit(
"direct-wire density failed (reduced motion must upload the "
"log-u8 wire bytes verbatim and retain them on density.wire)"
)
if drestore != 1:
raise SystemExit(
"direct-wire home restore failed (null-grid density must "
"re-upload from the retained wire bytes, not throw)"
)
if meancolor != 1:
raise SystemExit(
"mean-color density failed (surface must wear the per-cell mean "
Expand Down
7 changes: 7 additions & 0 deletions spec/design/lod-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,13 @@ invariants so future kinds don't regress them:
- **T4 — normalization is eased, never stepped** (exposure-style normMax) —
count-only surfaces; a mean-color texture's physical alpha is
max-independent, so it has no normalization to ease.
When there is nothing to ease, the wire bytes ARE the texture: a
count-only `log-u8` reply under `prefers-reduced-motion` pins normMax at
the reply's own `max`, so decoding to floats and re-encoding against that
same max is the identity. Those replies upload `buf` straight to R8
(`lodUploadWireGrid`) and keep `grid` null, retaining the wire bytes so the
`_homeDensity` restore (T7) can re-upload after the buffer is gone. Every
other reply still decodes through `_uploadGrid`.
- **T5 — stale replies die:** seq on view updates, drill_seq on subsets,
pending-view hold for prefetched drills.
- **T6 — invalid requests do not mutate:** malformed viewport/screen requests
Expand Down
Loading