feat(browser): comprehensive performance + GPU acceleration#91
feat(browser): comprehensive performance + GPU acceleration#91AndrewAltimit merged 27 commits intomainfrom
Conversation
Introduce a display list intermediate representation for the browser paint layer. Instead of issuing backend draw calls immediately during the layout tree walk, the paint pass now records DisplayItems into a cached DisplayList that is replayed against the backend. This enables: - Frame-to-frame caching (skip rebuild when layout hasn't changed) - Future dirty-rect culling (replay_dirty filters by intersection) - Draw call batching via begin_batch/flush_batch - Scroll offset adjustment without full rebuild New backend trait extensions (standalone, not yet in SdiBackend): - SdiRenderTarget: offscreen render targets for compositing/tiling - SdiGeometry: raw triangle submission (SDL_RenderGeometry) - SdiBlendMode: alpha blend mode control for layer compositing All 1796 tests pass. No breaking changes to existing backends. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…yleCache Three performance optimizations for the browser paint and layout engine: - Skip paint_background when bg is transparent with no image/texture - Skip paint_borders when all four border widths are zero - Skip paint_outline when outline_width is zero - Push overflow:hidden clip rects to the GPU via backend.set_clip_rect() so hardware clipping supplements the existing software culling - Replace StyleCache HashMap<NodeId, CachedEdges> with Vec<Option<>> for O(1) lookup by NodeId (dense usize index, no hashing overhead) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Pre-allocate fragment, line, and children Vecs in inline layout to reduce per-line heap allocation churn (lines estimate from total fragment width / available width) - Early-break child iteration for Y-sorted containers (Block/Anonymous) when a child is entirely below the overflow clip bottom, avoiding unnecessary clip-rect checks for offscreen children - Pre-allocate normal_children Vec with child count capacity Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-size children Vec in build_children() and wrap_anonymous() based on the known DOM child count, avoiding repeated heap reallocations during recursive layout tree construction. Item 3.3 (incremental style cache) analyzed and found already optimized: restyle_hover_affected() only recomputes styles for ancestor chains of changed nodes, with geometry-equality checks to skip unnecessary relayout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move image decoding off the main thread to avoid blocking the UI
during page loads with many images. A lazy-spawned daemon thread
("img-decode") receives raw bytes and returns DecodedImages via
mpsc channels.
- Main thread fetches raw bytes, sends to decode thread
- poll_decoded_images() drains completed decodes each tick
- recv_timeout() within batch budget for test compatibility
- WASM retains synchronous decode (no thread support)
- In-flight counter tracks pending decodes for loading state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add fill_shadow() to SdiShapes trait with a concentric-rect software fallback matching existing behavior. GPU backends can override with a Gaussian blur shader for higher quality and single-call performance. - Outer shadows in paint/shadow.rs now delegate to fill_shadow() - Display list gains Shadow variant for recorded outer shadows - Inset shadows unchanged (different edge-strip rendering pattern) - Default impl produces identical output to the previous code Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pack images <= 128x128 into shared 2048x2048 atlas textures using row-based packing, reducing GPU texture bind switches on pages with many small icons/avatars. - New ImageAtlas with AtlasPage/AtlasRegion row-packing system - Small images routed through atlas in ensure_image_textures() - blit_sub() used for atlas images, regular blit() for large ones - Display list gains BlitSub variant for atlas-packed images - Atlas cleared on navigation without destroying GPU textures - 7 new unit tests for atlas packing, dedup, and row wrapping Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add DisplayList::compact() pass that runs after recording: - Remove zero-size items (w=0 or h=0) that waste draw calls - Merge consecutive same-color FillRects with abutting horizontal edges into single wider rects (common for border edges, shadows) Compaction preserves paint order and never affects visual output. Combined with begin_batch/flush_batch wrapping already in replay(), this reduces draw call count for typical pages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add glyph_metrics_ascii() for O(1) ASCII byte lookup without binary search over Unicode extended tables - bitmap_measure_text() uses fast ASCII path when text.is_ascii(), iterating bytes directly instead of decoding chars - Fix diagonal gradient rendering to use horizontal band interpolation (O(32) draw calls with correct gradient projection) instead of axis-snapping approximation - Fix clippy clamp-like pattern in gradient band count Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reduce maximum concentric fill_rounded_rect calls per radial gradient from 128 to 48. Since bands overlap concentrically, 48 bands produces visually indistinguishable results with ~62% fewer draw calls. Applied to both immediate-mode paint and display-list recording paths. Item 3.4 (Bloom filter for selector matching) analyzed and skipped: SelectorIndex already pre-filters by ID/class/tag, descendant combinator matching is rare after index filtering, and DOM trees are shallow (10-20 levels). Bloom filter maintenance cost would exceed its benefit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three major browser engine improvements: **Dirty rectangle tracking (4.1 + 6.1):** - Track dirty regions from hover/focus visual-only changes - Use replay_dirty() to skip draw calls outside dirty rects - mark_hover_focus_dirty() computes affected element screen rects - Full repaint on layout/scroll changes, partial on hover **Offscreen compositing via display list layers (2.7):** - PushLayer/PopLayer display items bracket opacity stacking contexts - Opacity stack in replay() multiplies alpha across nested layers - Correctly handles nested opacity (0.5 * 0.5 = 0.25 effective) - GPU backends can override with real offscreen render targets - 8 new compositing tests **Parallel style computation (3.2):** - Optional rayon dependency behind "parallel-style" feature flag - ParallelStyles wrapper enables concurrent writes to disjoint indices - style_subtree_parallel() uses rayon::par_iter for >= 4 siblings - Sequential fallback for small child counts (avoid scheduling overhead) - Compile-time dispatch between parallel and sequential paths Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Phase 5b) **GPU blur filter approximation (2.4):** - filter: blur() now applies brightness/saturation reduction as visual hint instead of being a no-op (3-line software approximation) - BlurHint display item emitted for GPU backends to override with actual Gaussian blur via render targets - 2 new blur filter tests **Tile-based rendering infrastructure (4.2):** - TileGrid with TILE_SIZE=256, visibility range queries, dirty tracking - Grid created/resized on layout rebuild, queried on scroll - Infrastructure for future GPU tile caching (render-to-texture) - 8 new tiling tests **Sticky element compositing (6.2):** - position:sticky elements wrapped in PushLayer/PopLayer - Isolates sticky content for future GPU translation during scroll **Scroll prediction buffer (6.3):** - ScrollState.buffer_zone extends viewport by 1x height - PaintViewport height includes buffer zone for pre-rendering - Small scroll increments find items already in the display list Items 5.3 (progressive JPEG) and 5.4 (WebP GPU) skipped: jpeg-decoder has no streaming API, and GPU decode needs platform-specific APIs not available on PSP/desktop/WASM targets. Background decode thread already handles latency. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The display list replay used reset_clip_rect() on PopClip, which removed the browser window clip entirely. With the scroll buffer zone extending the recorded area beyond the viewport, content in the buffer zone would briefly render outside the browser window. Fix: maintain a clip stack in replay() and replay_dirty(). PopClip now restores to the parent clip or the base clip (browser window content area) instead of resetting to full screen. The base clip is passed as a parameter from widget_paint.rs. This fixes the temporary content leak reported on Wikipedia's main page where elements would flash outside the browser window area. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Gemini AI Code ReviewIssues (if any)
Previous Issues (for incremental reviews)(none) Suggestions (if any)
Notes
Generated by Gemini AI (gemini-3.1-pro-preview). Supplementary to human reviews. |
Automated fix by Claude in response to Gemini/Codex review. Iteration: 1/5 Co-Authored-By: AI Review Agent <noreply@anthropic.com>
Review Response Agent (Iteration 1)Status: Changes committed, pushing... Commit: Fixed Issues
Ignored Issues
Deferred to Human
Notes
Automated summary of agent fixes. |
Gemini AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues (if any)
Previous Issues (for incremental reviews)
Suggestions (if any)(none) Notes
Generated by Gemini AI (gemini-3.1-pro-preview). Supplementary to human reviews. |
Review Response Agent (Iteration 2)Status: No changes needed
Fixed Issues
Ignored Issues
Deferred to Human
Notes
The agent reviewed feedback but no file modifications were detected. |
Automated fix by Claude in response to pipeline failures. Failures addressed: - format - lint - test-suite Actions taken: - Ran autoformat (ruff format, cargo fmt) - Fixed remaining lint issues Iteration: 1/5 Co-Authored-By: AI Pipeline Agent <noreply@anthropic.com>
Failure Handler Agent (Iteration 1)Status: Changes committed, pushing... Commit: Failures addressed:
Automated fix in response to CI pipeline failures. |
**PushLayer leak on offscreen culling (CRITICAL):** Early returns in record_box() now emit PopLayer for sticky elements before returning, preventing permanent layer stack imbalance. **Double opacity application (BUG):** When PushLayer handles opacity during replay, recording functions now use effective_opacity=1.0 to avoid squaring the opacity (e.g. 0.5 recorded * 0.5 replayed = 0.25 instead of intended 0.5). Text and text shadow opacity also fixed to use 1.0 since layers handle it. **text-overflow ellipsis width calculation (BUG):** Changed from flawed screen-space math to layout-space calculation: `(clip.x + clip.width - x).max(0.0) as u32` which correctly computes available width regardless of scroll position or viewport offset. **Missing sync decode fallback (BUG):** When the background decode thread channel is unavailable (spawn failed or channel disconnected), images now fall back to synchronous decode instead of being silently dropped. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Gemini AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues (if any)
Previous Issues (for incremental reviews)
Suggestions (if any)(none) Notes
Generated by Gemini AI (gemini-3.1-pro-preview). Supplementary to human reviews. |
Gemini AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues (if any)
Previous Issues (for incremental reviews)
Suggestions (if any)
Notes
Generated by Gemini AI (gemini-3.1-pro-preview). Supplementary to human reviews. |
Automated fix by Claude in response to Gemini/Codex review. Iteration: 3/5 Co-Authored-By: AI Review Agent <noreply@anthropic.com>
Review Response Agent (Iteration 3)Status: Changes committed, pushing... Commit: Fixed Issues
Ignored Issues
Deferred to Human
Notes
Automated summary of agent fixes. |
Gemini AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues (if any)
Previous Issues (for incremental reviews)
Suggestions (if any)
Notes
Generated by Gemini AI (gemini-3.1-pro-preview). Supplementary to human reviews. |
Automated fix by Claude in response to Gemini/Codex review. Iteration: 4/5 Co-Authored-By: AI Review Agent <noreply@anthropic.com>
Review Response Agent (Iteration 4)Status: Changes committed, pushing... Commit: Fixed Issues
Ignored Issues
Deferred to Human
Notes
Automated summary of agent fixes. |
Gemini AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues (if any)
Previous Issues (for incremental reviews)
Suggestions (if any)
Notes
Generated by Gemini AI (gemini-3.1-pro-preview). Supplementary to human reviews. |
Automated fix by Claude in response to Gemini/Codex review. Iteration: 5/5 Co-Authored-By: AI Review Agent <noreply@anthropic.com>
Review Response Agent (Iteration 5)Status: Changes committed, pushing... Commit: Fixed Issues
Ignored Issues
Deferred to Human
Notes
Automated summary of agent fixes. |
Gemini AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues (if any)
Previous Issues
Suggestions(none) Notes
Generated by Gemini AI (gemini-3.1-pro-preview). Supplementary to human reviews. |
Automated fix by Claude in response to pipeline failures. Failures addressed: - format - lint - test-suite Actions taken: - Ran autoformat (ruff format, cargo fmt) - Fixed remaining lint issues Iteration: 2/5 Co-Authored-By: AI Pipeline Agent <noreply@anthropic.com>
Failure Handler Agent (Iteration 2)Status: Changes committed, pushing... Commit: Failures addressed:
Automated fix in response to CI pipeline failures. |
Gemini AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues (if any)
Previous Issues
Suggestions (if any)(none) Notes
Generated by Gemini AI (gemini-3.1-pro-preview). Supplementary to human reviews. |
…ent bands **SVG/Canvas elements silently dropped (CRITICAL):** Added paint_svg_canvas_elements() second pass after display list replay that walks the layout tree for Svg/Canvas replaced elements and paints them via immediate-mode backend calls. These elements have complex internal drawing pipelines that can't be represented as display items. **Decode thread panic hangs loading state (BUG):** poll_decoded_images() now detects TryRecvError::Disconnected (thread panicked or channel closed) and resets image_decode_in_flight to 0, clearing the tx/rx channels to prevent permanent LoadingState::Loading. **Diagonal gradient band overlap (BUG):** Replaced ceil()-based band height with integer interval calculation (end_y - start_y) that produces non-overlapping bands, preventing alpha double-blending artifacts on semi-transparent gradients. Remaining review items assessed as low-impact/false-positive: - mark_hover_focus_dirty ignoring transforms: falls back to full repaint which is always correct; transform-aware dirty rects are a future optimization - screen_y using content.y: margin_box().height covers full extent including padding/border/margin from the content origin Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review fixes applied (commit 5a13770)Fixed (3 issues)[CRITICAL] SVG/Canvas elements silently dropped -- Added [BUG] Decode thread panic hangs loading state -- [BUG] Diagonal gradient band overlap -- Replaced Assessed as low-impact / false-positive (2 items)mark_hover_focus_dirty ignores transforms/sticky -- True, but the fallback is always a full repaint (correct behavior). Transform-aware dirty rects would be a future optimization. No visible bug since the full repaint path handles it. screen_y using content.y for culling -- The culling computes |
Gemini AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues (if any)
Previous Issues (for incremental reviews)
Suggestions (if any)(none) Notes
Generated by Gemini AI (gemini-3.1-pro-preview). Supplementary to human reviews. |
…fsets The paint_svg_canvas_elements() walk now computes sticky_dy and transform offsets matching record_box/paint_box logic, so SVG/Canvas elements inside transformed or sticky containers render at correct screen coordinates. Z-index ordering remains a known limitation of the post-pass approach (SVG/Canvas render after display list content). Correct z-ordering would require dedicated DisplayItem variants (future work). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review fixes applied (commit 13a41d7)Fixed (1 issue)SVG/Canvas post-pass ignores transforms and sticky -- Z-index ordering is a known limitation of the post-pass approach -- SVG/Canvas render after all display list content. Correct z-ordering would require dedicated `DisplayItem::Svg`/`DisplayItem::Canvas` variants that embed immediate-mode painting within the display list replay order. Noted as future work. Still-unresolved items assessedcontent.y culling -- This is pre-existing behavior in the original immediate-mode `paint_box()` (paint/mod.rs line 230 uses the same `content.y` check). Not introduced by this PR. The check is conservative -- it may fail to cull some offscreen elements with large top padding, but never incorrectly culls visible ones (the content.y + margin_box().height sum always extends beyond the true bottom). mark_hover_focus_dirty ignoring transforms -- When the dirty rect misses the actual visual position, the display list is still fully rebuilt (colors re-baked) -- the replay_dirty filter just doesn't cull efficiently. The fallback full_repaint_needed path handles layout-affecting hover changes. This is a performance issue for hover on transformed elements, not a correctness bug. |
Gemini AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues (if any)
Previous Issues (for incremental reviews)
Suggestions (if any)(none) Notes
Generated by Gemini AI (gemini-3.1-pro-preview). Supplementary to human reviews. |
…rty rects **Diagonal gradient display list regression (BUG):** The band-based diagonal gradient fix was only in background.rs (immediate mode) but the display list recorder still used the old axis-snapping fallback. Ported the horizontal band interpolation to record_linear_gradient so diagonal gradients render correctly via the display list path. **Bottom sticky in SVG/Canvas walker (BUG):** paint_svg_canvas_elements() now handles Dimension::Px(bottom) sticky positioning, matching the full compute_sticky_dy logic. **Hover dirty rects for transformed/sticky elements (BUG):** mark_hover_focus_dirty() now checks if affected nodes have CSS transforms or sticky positioning. If so, falls back to full_repaint_needed since the static layout rect doesn't reflect the actual screen position after transform/sticky offsets are applied. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review fixes applied (commit 55fc85f)Fixed (3 issues)Diagonal gradient display list regression -- The band-based diagonal gradient was only in `background.rs` (immediate-mode path, now unused). The display list recorder in `record_linear_gradient` still used the old axis-snapping fallback. Ported the horizontal band interpolation so diagonal gradients render correctly via the display list path. Bottom sticky in SVG/Canvas walker -- `paint_svg_canvas_elements()` now handles `Dimension::Px(bottom)` sticky positioning, matching the full `compute_sticky_dy` logic. Hover dirty rects for transformed/sticky elements -- `mark_hover_focus_dirty()` now checks if affected nodes have CSS transforms or sticky positioning. If so, falls back to `full_repaint_needed` since the static layout rect doesn't reflect the actual screen position. Remaining assessed itemcontent.y culling -- Pre-existing in the original `paint_box()` (paint/mod.rs line 230). The display list recorder copies this behavior. This is a potential performance issue (failing to cull elements with massive top padding) but not a correctness regression since `content.y + margin_box().height` always extends beyond the true bottom, meaning visible elements are never culled. A future fix could use `border_box().y` for the top check. |
Gemini AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues (if any)
Previous Issues (for incremental reviews)
Suggestions (if any)(none) Notes
Generated by Gemini AI (gemini-3.1-pro-preview). Supplementary to human reviews. |
…ositioning **Buffer zone inflating sticky calculations (BUG):** Added visible_height to PaintViewport and visible_viewport_height to RecordContext. Sticky bottom positioning now uses the true visible viewport height instead of the inflated culling boundary (which includes the scroll buffer zone), so elements stick to the actual visible area. **Hardcoded 272px in SVG/Canvas walker (BUG):** paint_svg_canvas_elements() now receives the actual viewport height as a parameter instead of hardcoding the PSP native resolution. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review fixes applied (commit 12ed62a)Fixed (2 issues)Buffer zone inflating sticky calculations -- Added `visible_height` to `PaintViewport` and `visible_viewport_height` to `RecordContext`. `compute_sticky_dy` now uses the true visible viewport height for bottom sticky positioning instead of the inflated culling boundary (which includes the scroll buffer zone). Hardcoded 272px in SVG/Canvas walker -- `paint_svg_canvas_elements()` now receives the actual `content_h` as a parameter instead of hardcoding the PSP native resolution. Works correctly on all skins/viewports. Remaining assessed itemscontent.y culling -- Pre-existing in the original `paint_box()` (paint/mod.rs line 230), not introduced by this PR. Conservative check that may fail to cull elements with massive top padding (performance issue, not correctness). Ancestor transforms in hover dirty rects -- The current fix checks the affected node's own transforms/sticky. Ancestor transforms are rarer for hover targets and would require walking the DOM parent chain. Falls back to full repaint for the affected node's own transforms, which handles the common case. |
Gemini AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues (if any)
Previous Issues (for incremental reviews)
Suggestions (if any)
Notes
Generated by Gemini AI (gemini-3.1-pro-preview). Supplementary to human reviews. |
DisplayItem::DrawText now carries a pre-computed `width` field that includes letter-spacing. This fixes two issues: 1. bounds() used bitmap_measure_text which ignored letter-spacing, causing text with letter-spacing to be incorrectly culled by replay_dirty() (bounding box too small) 2. Removes expensive string re-measurement from the hot bounds() path during dirty-rect intersection testing Also filters empty/zero-width DrawText items in compact() to save replay iterations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review fixes applied (commit bee69cf)Fixed (1 bug + 1 suggestion)DrawText bounds ignore letter spacing -- `DisplayItem::DrawText` now carries a pre-computed `width: u32` field that includes letter-spacing. `bounds()` uses this cached value instead of calling `bitmap_measure_text()`, fixing incorrect dirty-rect culling for text with letter-spacing and removing expensive string re-measurement from the hot path. compact() empty DrawText filtering -- Empty-string or zero-width DrawText items are now removed during compaction. Remaining assessed items (unchanged)
|
Gemini AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues (if any)
Previous Issues (for incremental reviews)
Suggestions (if any)
Notes
Generated by Gemini AI (gemini-3.1-pro-preview). Supplementary to human reviews. |
… rebuild **Background images trapped in texture atlas (BUG):** Background images were pushed into the same pending list as <img> URLs, causing small background images to be atlas-packed. But assign_textures only checked individual textures for background-image assignment (not the atlas), so they'd never render. Fix: separate bg_pending list that always creates individual textures, since background images are tiled/stretched to arbitrary sizes. **Redundant layout rebuild (WARNING):** rebuild_layout_with_images() was called twice when pending_images was empty and any_decoded was true (once in the in_flight==0 block, once unconditionally). Removed the duplicate from the in_flight block. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review fixes applied (commit a1bca8d)Fixed (1 bug + 1 warning)Background images trapped in texture atlas -- Background-image URLs were mixed into the same pending list as ` Redundant layout rebuild -- `rebuild_layout_with_images()` was called twice when `pending_images` was empty and `any_decoded` was true. Removed the duplicate call from inside the `in_flight == 0` block. Suggestion noted (dirty rect union)Unioning intersecting dirty rects before the replay loop would reduce overdraw. Good future optimization -- for now the common case is 1-2 small dirty rects from hover changes, so the overhead is minimal. |
Gemini AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues (if any)
Previous Issues (for incremental reviews)
Suggestions (if any)
Notes
Generated by Gemini AI (gemini-3.1-pro-preview). Supplementary to human reviews. |
…case **WASM LoadingState stall (BUG):** On WASM, the LoadingState::Idle transition was gated behind the non-WASM image_decode_in_flight check. Pages with no images would permanently stall in Loading state. Added WASM-specific Idle transition when pending_images is empty. **Shadow compaction on 0x0 elements (BUG):** compact() removed Shadow items with w=0 or h=0, but a shadow with large spread/blur remains visible even on a zero-size source element. Shadows are now always retained during compaction. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review fixes applied (commit 0234366)Fixed (2 bugs)WASM LoadingState stall -- Added WASM-specific `LoadingState::Idle` transition when `pending_images` is empty. Previously the transition was gated behind the non-WASM `image_decode_in_flight` check, so WASM pages with no images would stall in Loading state permanently. Shadow compaction on 0x0 elements -- `compact()` no longer removes `Shadow` items with zero base width/height. A shadow with large `spread` or `blur` produces visible pixels even on a 0x0 source element. Suggestion noted (ParallelStyles transmute)`UnsafeCell` is `#[repr(transparent)]` so the transmute would be sound, but `into_inner()` is a one-time operation at the end of style computation -- not a hot path. Keeping the safe iterate-and-collect avoids adding unsafe code for negligible benefit. |

Summary
Changes by category
Display List & Batching (Phase 1)
DisplayListwith 16DisplayItemvariants;record()from layout tree,replay()with scroll offsets,replay_dirty()with rect cullingSdiRenderTarget,SdiGeometry,SdiBlendMode(standalone, with software fallbacks)GPU-Accelerated Rendering (Phase 2-3)
SdiShapes::fill_shadow()with concentric-rect fallback; backends can override with Gaussian blur shaderBlurHintdisplay item for GPU overridePushLayer/PopLayerwith opacity stack for correct nested opacityset_clip_rect()Layout Engine Performance (Phase 3)
HashMap<NodeId>toVec<Option<>>for O(1) lookupVec::with_capacity(child_count)in tree builderrayonbehindparallel-stylefeature flagImage Pipeline (Phase 4)
recv_timeoutfor test compatblit_sub()renderingglyph_metrics_ascii()byte-path avoiding Unicode binary searchScroll & Viewport (Phase 5)
replay_dirty()position:stickywrapped inPushLayer/PopLayerTileGridwith visibility queries and dirty trackingPaint Optimizations
paint_background/paint_borders/paint_outlinewhen nothing to drawPopCliprestores to parent or base clip instead of full-screen resetAnalyzed & Skipped (with justification)
Vec::new()already zero-alloc for leaf LayoutBox childrenjpeg-decoderhas no streaming API; background thread handles latencyTest plan
cargo clippy --workspace -- -D warningscleancargo build -p oasis-app --release)cargo check -p oasis-backend-wasm)cargo +nightly psp --release-> EBOOT.PBP)cargo check -p oasis-browser --features parallel-stylecompilesGenerated with Claude Code