Skip to content

Composite background-mode plots with hardware acceleration; harden Redrawer - #138

Merged
halfhp merged 1 commit into
masterfrom
fix-96-background-render-perf
Sep 7, 2026
Merged

Composite background-mode plots with hardware acceleration; harden Redrawer#138
halfhp merged 1 commit into
masterfrom
fix-96-background-render-perf

Conversation

@halfhp

@halfhp halfhp commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Fixes #96

The size/performance correlation

The reporter found that a background-mode plot's cost scaled with its on-screen size, to the point that shrinking the view doubled their app's frame rate. That's inherent to how background mode composites, but one third of it was avoidable.

Every frame does three full-size pixel passes: the render thread clears and draws into an ARGB bitmap the size of the view; the UI thread copies that bitmap into a software layer; and the framework uploads the layer as a texture. All scale with width times height, and two of the three run on the UI thread.

The middle pass exists because Plot.isHwAccelerationSupported() returns false for every plot type, so onSizeChanged forces LAYER_TYPE_SOFTWARE. That's needed in main-thread mode, where the renderers draw paths and clips straight onto the view canvas and older hardware canvases didn't support some of those operations. In background mode those operations already happen on the offscreen bitmap, and the view's onDraw does nothing but drawBitmap, which hardware acceleration handles natively. This PR only forces the software layer in main-thread mode, removing a full-size CPU copy from the UI thread on every background-mode frame. Rendered output is identical.

Cost still scales with plot area; the release note says so and advises keeping high-rate plots small.

Redrawer crash

The crash in the issue comments is a library bug. Redrawer holds plots by weak reference so it doesn't keep them alive, but its loop called plotRef.get().redraw() unconditionally. When an activity is destroyed and the plot is collected before finish() is called, the thread dies with a NullPointerException, which on Android takes the process down. Now collected plots are skipped and dropped, the thread exits on its own once nothing remains, and the keepRunning / keepAlive flags are volatile since the thread reads them without synchronization.

Verification

  • PlotTest: with isHardwareAccelerated() stubbed true, a background-mode plot never calls setLayerType(SOFTWARE) and a main-thread plot still does. The background case fails on the old code.
  • RedrawerTest (new): unit cases for the collected-plot handling; a thread case verifying redraws at the configured rate; and a case that constructs a real plot, drops the reference, forces GC, and asserts the thread exits without an uncaught exception. On the old loop that last case fails with the reporter's exact NullPointerException, captured via an uncaught-exception handler.
  • Full suite: 224 tests, 0 failures. Lint, core release AAR and demoapp debug build pass. Threading tests rerun five times without flakes.

Needs a device check

I can't measure the frame-rate change from here. The ECG demo in background mode, sized to fill the screen, is the closest thing to the reporter's setup; GPU profiling before and after should show the UI-thread draw time drop.

…drawer (#96)

Plot forced a software layer on every plot type because the renderers
use canvas operations older hardware canvases didn't support. In
background mode those operations happen on an offscreen bitmap and
onDraw only copies that bitmap to the view, which hardware acceleration
handles natively, so the software layer just added a full-size CPU copy
of the view on the UI thread every frame. Only force it in main-thread
mode.

Redrawer holds plots weakly but dereferenced them without a null check,
crashing the process when a plot was collected before finish() was
called. Skip and drop collected plots, exit the thread once none
remain, and make the control flags volatile.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 91.66667% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 71.79%. Comparing base (6e1ad96) to head (3f82f28).

Files with missing lines Patch % Lines
...dplot-core/src/main/java/com/androidplot/Plot.java 0.00% 0 Missing and 1 partial ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@             Coverage Diff              @@
##             master     #138      +/-   ##
============================================
+ Coverage     71.02%   71.79%   +0.77%     
- Complexity     1244     1261      +17     
============================================
  Files           109      109              
  Lines          5194     5202       +8     
  Branches        544      547       +3     
============================================
+ Hits           3689     3735      +46     
+ Misses         1150     1111      -39     
- Partials        355      356       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@halfhp
halfhp merged commit 80094a2 into master Sep 7, 2026
1 check passed
@halfhp
halfhp deleted the fix-96-background-render-perf branch September 7, 2026 04:42
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.

Weird correlation between graph size and performance

2 participants