Skip to content

[Android] Reduce RAM hogging and OOM crashes#1354

Open
waxm134 wants to merge 5 commits into
CyberTimon:mainfrom
waxm134:oom_fix2
Open

[Android] Reduce RAM hogging and OOM crashes#1354
waxm134 wants to merge 5 commits into
CyberTimon:mainfrom
waxm134:oom_fix2

Conversation

@waxm134

@waxm134 waxm134 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Description

Android is much more resource constraint than Desktop. These fixes ensure that memory is freed up whenever possible on Android. All changes only take effect on Android platform. Export pipeline is untouched, all fixes target the preview path

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Performance improvement
  • Code refactoring
  • Documentation update
  • UI/UX improvement
  • Build/CI or Dependency update

Changes Made

1. Downscale-first preview path (lib.rs:process_preview_job)

Android goes through a different code path than desktop. Instead of transforming at full resolution then downscaling, it:

  • Downscales the original image to preview_dim first
  • Runs all CPU transformations (warp, crop, rotate, flip, color, exposure, etc.) at preview_dim

RAM impact: The largest intermediate buffer is ~preview_dim^2 × 3 × 4 bytes instead of original_res^2 × 3 × 4. For a 24MP image at 1920px preview: ~42 MB vs ~288 MB.

Preview quality: Identical — color/exposure/crop transformations produce the same result regardless of whether they run at 1920px or 6000px, because they're per-pixel operations.

Export quality: Untouched — export uses get_cached_full_warped_image (full-res, no downscale-first).

2. Preview cache at max(editorPreviewResolution, 2560) (image_loader.rs)

When an image is loaded, a downscaled copy of the pristine image is created at max(editorPreviewResolution, 2560) (default: 2560) and cached in state.preview_cache. The full-resolution DynamicImage in original_image can be freed by the Rust allocator.

RAM impact: The full original image (e.g. 24 MP, ~280 MB as Rgb32F) can be deallocated, leaving only the ~30 MB 2560px cache.

Preview quality: At normal zoom levels (preview_dim ≤ 2560), the 2560 cache is used directly — no quality loss. At high zoom (preview_dim > 2560), the cache is bypassed and the full original is used.

Export quality: Unaffected — export always reads the full original.

3. ImageRgba32FImageRgb8 conversion on Android (image_loader.rs)

After decoding a raw file that produces a 32-bit float image on Android, it's immediately converted to 8-bit RGB.

RAM impact: 4× less memory per pixel (1 byte per channel instead of 4).

Preview quality: The conversion happens before the preview pipeline. Since the preview pipeline works in f32 internally anyway (the downscale produces Rgb32F), the quality loss is just the initial precision loss from the raw developer → in practice invisible.

Export quality: Unaffected — export re-reads the raw file at full precision.

4. Throttled flush pipeline (useImageProcessing.ts)

The flushPipeline (which sends the apply_adjustments invoke to the backend) is throttled to at most once per 33ms on Android, with a 200ms backup timer.

RAM impact: Prevents job queue buildup. Without this, multiple high-resolution renders could be in-flight simultaneously, causing 2-3× peak memory.

Preview quality: No change — throttling delays the 2nd+ frame by at most 200ms (shorter than a single render at high zoom).

Export quality: Unaffected.

5. Ref-based hi-fi zoom dispatch (useImageProcessing.ts)

On Android, requestHiFiZoom and requestHiFiOriginalZoom store the target resolution in a ref and call themselves debounced without arguments. Non-Android passes the target directly.

RAM impact: Ensures only the highest pending resolution is actually rendered — no unnecessary intermediate renders at non-peak zoom.

Preview quality: No change — you get the final resolution, not intermediate ones.

6. isSliderDragging guard on crop preview (useImageProcessing.ts)

The uncropped (crop menu) preview generation is blocked while the slider is being dragged.

RAM impact: Prevents concurrent heavy renders during slider changes.

7. Sequence guard + work mutex on uncropped preview (lib.rs:generate_uncropped_preview)

Uses uncropped_preview_seq (atomic counter) and uncropped_preview_work_mutex to ensure only the latest uncropped preview job runs. A 500ms emit throttle prevents flooding the frontend with JPEG base64 data.

RAM impact: Prevents multiple uncropped preview jobs stacking up on the thread pool.

Preview quality: No change.

8. GPU processor + image cache cleared on image switch (image_loader.rs)

state.gpu_processor and state.gpu_image_cache are set to None when a new image is loaded.

RAM impact: Drops all GPU textures (output, working, blur, tonal, clarity, tile, geometry staging) — can be hundreds of MB on a large image.

9. Resolution refs reset on image switch (useImageProcessing.ts)

currentResRef.current = 0 and currentOriginalResRef.current = 0 when selectedImage?.path changes.

RAM impact: Ensures the new image renders at base resolution immediately rather than comparing against the old image's high-res target.

10. Zoom-out memory freeing (useImageProcessing.ts)

When finalRes < currentResRef.current * 0.7, a new render is forced at the lower resolution.

RAM impact: Overwrites the backend's cached_preview (192+ MB for a 4000px RGBA f32 blob) with the lower-resolution version, freeing that RAM. Also replaces the frontend blob URL.

Screenshots/Videos

Testing

  • I have tested these changes locally and confirmed that they work as expected without issues

Test Configuration:

  • OS: (Ubuntu 24.04, Android 16)
  • Hardware: (e.g. Intel i5, Xiaomi Pad 7)

Checklist

  • My code follows the project's code style
  • I haven't added unnecessary AI-generated code comments
  • My changes generate no new warnings or errors

Additional Notes

AI Disclaimer:

Please state the involvement of AI in this PR:

  • This PR is entirely AI-generated
  • This PR is AI-generated but guided by a human
  • This PR was handwritten with AI assistance (spell check, logic suggestions, error resolving)
  • This PR contains only blood, sweat, and coffee (AI-free)

@waxm134
waxm134 requested a review from CyberTimon as a code owner July 10, 2026 17:19
@waxm134 waxm134 mentioned this pull request Jul 13, 2026
19 tasks
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.

1 participant