Skip to content

Ship heatmaps in texture-native byte formats#190

Draft
Alek99 wants to merge 1 commit into
mainfrom
agent/quantize-heatmap-wire
Draft

Ship heatmaps in texture-native byte formats#190
Alek99 wants to merge 1 commit into
mainfrom
agent/quantize-heatmap-wire

Conversation

@Alek99

@Alek99 Alek99 commented Jul 22, 2026

Copy link
Copy Markdown
Member

Summary

  • quantize scalar heatmaps kernel-side to the exact R8 bytes the browser already generated, with byte zero reserved for missing cells
  • replace four planar f32 truecolor buffers with one interleaved RGBA8 column
  • remove the browser's per-texel conversion/interleave loop while retaining protocol-4 decode paths as parity oracles
  • decode explicit heatmap encoding metadata in standalone hover/CSV, SVG, and raster paths
  • keep live picks and native scalar PNG export on canonical f64 data
  • advance the renderer/spec protocol to 5 so stale clients fail loudly

Truecolor normalization now owns its working buffer and preserves both conventional 0–1 and 0–255 alpha without mutating caller data.

Verification

  • repository-wide run: 2216 passed, 66 skipped in 83.37s
  • sole failure: the pre-existing test_client_source_is_the_installed_bundle assertion looks for unminified local symbol spellings in the minified production bundle
  • that exact failure was reproduced independently on untouched main; its semantic-export-marker correction is already in draft PR Avoid full payload reships for restyles and unchanged columns #189
  • focused heatmap payload/export/Chromium suite: 23 passed
  • real-Chromium compact-vs-legacy render: pixel-identical
  • packed/split byte parity, exact legacy JavaScript half-step rounding, missing cells, standalone decode/error bound, SVG/raster readback, truecolor alpha, and non-mutation are covered
  • ruff check .: passed
  • ruff format --check .: 342 files already formatted
  • tsc -p js/tsconfig.json --noEmit: passed
  • node js/build.mjs --check: shipped bundles fresh
  • git diff --check: passed

Local first-paint benchmark

Fixture construction excluded; three payload builds per row:

Kind Grid Previous wire Compact wire Reduction Payload median
scalar 1000×1000 3.8 MiB 976.6 KiB 4.0× 2.22 ms
truecolor 1000×1000 15.3 MiB 3.8 MiB 4.0× 17.64 ms
scalar 2000×2000 15.3 MiB 3.8 MiB 4.0× 7.32 ms
truecolor 2000×2000 61.0 MiB 15.3 MiB 4.0× 67.24 ms

Closes #164

@codspeed-hq

codspeed-hq Bot commented Jul 22, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 28.63%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 1 regressed benchmark
✅ 96 untouched benchmarks
⏩ 1 skipped benchmark1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
test_first_payload_heatmap_core_2d 1.6 ms 2.2 ms -28.63%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing agent/quantize-heatmap-wire (3de3171) with main (2a7d898)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR moves heatmap payloads to texture-native byte formats. The main changes are:

  • Scalar heatmaps ship quantized R8 values with explicit missing-value metadata.
  • Truecolor heatmaps ship one interleaved RGBA8 column.
  • Browser, tooltip, CSV, SVG, and raster paths decode the new formats.
  • Native scalar export retains canonical values, and the renderer protocol advances to version 5.

Confidence Score: 4/5

The uint8 alpha normalization path needs a fix before merging.

Scalar encoding and its browser and static decoders are consistent. Packed and split buffer handling account for u8 widths and padding.

A valid uint8 RGBA image with alpha values limited to 0 and 1 renders with incorrect opacity in python/xy/marks.py.

T-Rex T-Rex Logs

What T-Rex did

  • The team reproduced the low-byte alpha opacity issue using a native payload fixture and verified the behavior with a Chromium render and pixel-readback harness.
  • T-Rex confirmed pixel parity between compact and legacy Chromium renders, including zero RGBA channel differences and identical PNG hashes, with nonblank truecolor counts and per-payload chart and canvas confirmations from Playwright recordings.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
python/xy/marks.py Adds owned RGB(A) normalization, but low-valued uint8 alpha channels are interpreted as normalized alpha.
python/xy/_payload.py Adds scalar and truecolor byte quantization, u8 payload metadata, and canonical scalar borrowing for native export.
js/src/50_chartview.ts Adds packed RGBA8 and unit-u8 texture uploads while retaining legacy planar-f32 decoding.
js/src/52_tooltip.ts Decodes compact scalar heatmap values for standalone and immediate tooltip output.
js/src/53_interaction.ts Decodes compact scalar heatmap values during CSV export.
python/xy/_svg.py Adds compact scalar and packed truecolor decoding for SVG export.
python/xy/_scene.py Shares the new heatmap decoders with raster scene generation.
python/xy/_raster.py Renders compact wire heatmaps while preserving canonical scalar rendering for native export.

Reviews (1): Last reviewed commit: "Ship heatmaps in texture-native byte for..." | Re-trigger Greptile

Comment thread python/xy/marks.py
Comment on lines +2358 to +2361
if rgba.shape[-1] == 4:
finite_alpha = rgba[..., 3][np.isfinite(rgba[..., 3])]
if finite_alpha.size and finite_alpha.max() > 1.0:
rgba[..., 3] /= 255.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Low Byte Alpha Becomes Opaque

A uint8 RGBA image whose alpha channel contains only 0 and 1 skips byte normalization because its maximum is not greater than one. Alpha 1 is then packed as 255, so nearly transparent pixels render fully opaque instead of preserving their RGBA8 opacity.

Suggested change
if rgba.shape[-1] == 4:
finite_alpha = rgba[..., 3][np.isfinite(rgba[..., 3])]
if finite_alpha.size and finite_alpha.max() > 1.0:
rgba[..., 3] /= 255.0
if rgba.shape[-1] == 4:
finite_alpha = rgba[..., 3][np.isfinite(rgba[..., 3])]
alpha_is_byte = np.issubdtype(arr.dtype, np.integer)
if finite_alpha.size and (alpha_is_byte or finite_alpha.max() > 1.0):
rgba[..., 3] /= 255.0
Artifacts

Repro: native payload and standalone application fixture

  • Contains supporting evidence from the run (text/x-python; charset=utf-8).

Repro: Playwright Chromium capture harness

  • Contains supporting evidence from the run (text/javascript; charset=utf-8).

Repro: rendered screenshot pixel-readback harness

  • Contains supporting evidence from the run (text/x-python; charset=utf-8).

Repro: native alpha decoding and browser pixel-readback output

  • Keeps the command output available without making the summary code-heavy.

Repro: successful Chromium application rendering and capture output

  • Keeps the command output available without making the summary code-heavy.

Low Byte Alpha Render

  • Shows the rendered state that T-Rex checked.

▶ Screen recording from the T-Rex run

  • Shows the flow or interaction that T-Rex exercised.

View artifacts

T-Rex Ran code and verified through T-Rex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Heatmaps ship 4-16 bytes/cell for what the client uploads as 8-bit textures

1 participant