Repository navigation
Fix text labels packed past TEXTURE_HEIGHT (clipped/stretched rendering) - #1314
Conversation
The new-texture overflow check (y + height > TEXTURE_HEIGHT) only ran inside the row-break branch. A label that fit horizontally into an existing row near the texture bottom was packed with its cell extending past TEXTURE_HEIGHT, so drawText silently clipped its glyphs at the bitmap's bottom edge while the quad still mapped the full cell. The clipped glyphs plus the CLAMP_TO_EDGE smear of the last bitmap row rendered the label vertically distorted on screen. Run the overflow check for every label so oversized cells start a new texture.
|
Thank you, we need to check if this affects the performance on Android. Regarding your first note: |
|
Hi, True, good point, we should ensure that it doesn't effect the performance. I can't use my laptop the next days, maybe for a week, I'm sitting in mud. When I'm back in civilization, I can try to measure the performance. Proposal: we can design it as opt-in. Maybe as a static property. Similar to
I agree. |
|
Hi, I sent some AI agent friends to make the feature opt-in and implement and run tests. That's the result: 1. Opt-in flagThe fix is now opt-in via a new static flag 2. TestsAdded Note: # uncomment `include ':vtm-tests'` in settings.gradle
./gradlew :vtm-tests:test --tests org.oscim.renderer.bucket.TextBucketPackingTest
3. Results
JVM benchmark (300 labels per bucket, both flag states, deterministic seeds):
scenario flag off flag on
small labels — atlas overflow none none
small labels — textures 2 2
small labels — prepare() ~224 µs ~187 µs
tall captions (12/67 px) — atlas overflow yes (2572 > 2048, bug) none (2044 ≤ 2048)
tall captions — textures 32 33
tall captions — prepare() ~539 µs ~403 µs
prepare() runs only at bucket compile time (never per frame), so these sub-millisecond costs are negligible; the invariant is what matters.
On-device (Android, 120 Hz display, static buggy viewport with a visibly stretched label):
flag off flag on
texture binds/frame (20 samples) 13 14
overflow packing yes (label visibly stretched) 0 events
frame pacing 120 fps sustained 120 fps sustained
The only rendering cost is exactly one extra texture bind (+1 draw call) per frame, only when an overflow would have occurred — viewports without an overflowing cell are unchanged. Frame pacing unaffected. (Debug build with forced continuous rendering; the relative A/B is what counts.) |
|
Thanks! |
Fixes #1313
Problem
Large captions occasionally rendered vertically clipped/stretched (flat cut at the bottom plus a halo smear), because
TextBucket.prepare()only checkedy + height > TEXTURE_HEIGHTinside thex + width > TEXTURE_WIDTHrow-break branch. A label that fit horizontally into an existing bottom row was packed with its cell extending past the 256 px texture height: the glyphs were rasterized clipped at the bitmap's bottom edge, while the quad's texture coordinates still covered the full cell (out-of-bounds rows sample the last row viaCLAMP_TO_EDGE).Only the largest font styles were affected (smaller cells never exceed
TEXTURE_HEIGHT), and the artifact came and went with panning/zooming because it depends on the atlas packing order for the visible label set — see the issue for screenshots and repro details.Fix
Run the new-texture overflow check for every label instead of only on row breaks, so any cell that would exceed
TEXTURE_HEIGHTstarts a new texture:One file changed (
TextBucket.java, 16 insertions / 11 deletions). No API changes.Verification
Notes
TextStyle.fontHeightwhile the width is measured live viapaint.measureText(). We considered (and tested) switching the height to live paint metrics as hardening — it's not required for this bug, but a tested reference implementation is available on my fork if maintainers are interested:jhotadhari/vtmbranchkeep/live-paint-text-metricsProposed CHANGELOG entry
(placed under
## Next versionindocs/Changelog.md)