Skip to content

Improve framebuffer transitions and 640x480 handling - #1940

Open
robcodedev wants to merge 3 commits into
OnionUI:v4.5-devfrom
robcodedev:framebuffer-transitions
Open

robcodedev wants to merge 3 commits into
OnionUI:v4.5-devfrom
robcodedev:framebuffer-transitions

Conversation

@robcodedev

@robcodedev robcodedev commented Sep 8, 2026

Copy link
Copy Markdown

This PR improves framebuffer transitions on 752x560-capable devices, adds an optional 640x480 mode, and fixes the installer starting before a suitable framebuffer geometry has been established.

The changes are split into three commits so each part can still be reviewed independently:

  1. use fbmode for verified runtime framebuffer transitions
  2. add the optional .force640Res override
  3. clear and establish 640x480 before the installer UI starts

1. Verified framebuffer transitions

Runtime currently uses fbset for resolution changes. It requests a geometry but does not verify when the driver has actually latched it, always requests two virtual pages, and does not clear framebuffer memory whose page boundaries have just moved.

That last point is what produces the sheared frames seen around 752x560 handoffs. The 640x480 and 752x560 layouts do not divide framebuffer memory at the same offsets, so bytes written using the 752x560 stride can briefly be scanned out using the 640x480 stride.

This adds fbmode, a small framebuffer helper that:

  • waits for the requested visible and virtual geometry to be reported
  • uses the driver's actual post-switch line_length
  • supports pre-clearing before a pitch change
  • leaves scanout parked on page 0
  • exposes --probe so redundant mode changes can be skipped
  • reports failure back to runtime so existing fbset fallbacks can be used

GameSwitcher handoff

launch_switcher becomes the single owner of GameSwitcher's 752x560 requirement.

A game exiting toward GameSwitcher leaves the framebuffer alone instead of dropping to 640x480 only for the next stage to switch it straight back.

Before launching GameSwitcher, runtime probes the current framebuffer and leaves an already-correct 752x560 two-page layout untouched, avoiding a forced same-mode relatch when arriving from a 752x560 game.

MainUI handoff

On 752x560-capable devices, runtime establishes MainUI's 640x480 three-page layout in init_system, before the boot screen is drawn.

On a Miyoo Mini Flip the firmware leaves the framebuffer at 752x560 with three virtual pages before runtime starts:

mode "752x560-77"
    geometry 752 560 752 1680 32
endmode

This was captured immediately after init_lcd, before any runtime mode change.

The boot-time transition is therefore a real pitch change, from 3008 bytes per row to 2560. The new 640x480 pages overlap memory previously used by the 752x560 layout, so this path preclears before changing geometry rather than allowing framebuffer contents written at the old stride to be scanned at the new one.

launch_main_ui then probes the framebuffer. If it is still 640x480/3, the layout is preserved and the boot screen remains visible until MainUI paints over it.

If the layout differs, runtime preclears before changing it so a pitch change cannot expose stale pixels written using the previous stride.

640-only devices do not use these extra preparation paths.

Shutdown

keymon can draw the End_Save screen while the game is still exiting. The screen is rendered using the framebuffer geometry it observed when it started, so changing the mode underneath it can shear the shutdown screen.

Runtime now preserves the framebuffer when /tmp/.offOrder is set. A normal return-to-MainUI transition is unnecessary once shutdown is already pending, so the current layout is left untouched.

2. Optional forced 640x480 mode

Adds an opt-in flag:

$sysdir/config/.force640Res

When present, runtime pins screen_resolution to 640x480, removes /tmp/new_res_available, and establishes the 640x480 three-page framebuffer layout.

Removing /tmp/new_res_available is intentional: downstream code then takes the same paths it would on hardware that never exposed 560p, rather than every resolution-related call site needing a separate override case.

/tmp/screen_resolution records 640x480, so components that read it use the same geometry runtime is using. DraStic's launch.sh uses this file to decide whether to select its 752x560 mode, and display_getResolution() feeds DISPLAY_WIDTH/DISPLAY_HEIGHT for screenshot capture and GameSwitcher thumbnail scaling.

No flag file means no behavior change.

3. Installer framebuffer geometry

The installer starts outside Onion's normal runtime framebuffer setup.

install.sh runs init_lcd but does not establish a framebuffer geometry before its UI is used. installUI itself has a fixed 640x480 layout.

On a Flip, the framebuffer immediately after init_lcd was observed as:

mode "752x560-77"
    geometry 752 560 752 1680 32
endmode

The installer now clears /dev/fb0 and establishes a 640x480 two-page layout immediately after init_lcd, before any installer UI is drawn.

The clear deliberately happens first. fbset changes the framebuffer geometry but does not clear its contents, so pixels written using the firmware's 752x560 stride could otherwise be scanned using the new 640x480 stride until installUI produces its first frame.

Clearing the framebuffer allocation first means the geometry change operates on zeroed memory, applying the same preclear-before-pitch-change principle used by fbmode during runtime transitions.

This path uses fbset rather than fbmode. On a first install, fbmode is still inside onion.pak; on an update, the installer stub replaces the old .tmp_update contents before install.sh runs. The firmware-provided fbset is available during bootstrap, so the installer can establish its known 640x480 layout without depending on the Onion runtime helper.

This installer preparation runs on every device. On hardware already using 640x480 it clears the framebuffer and reapplies the geometry that is already active.

Fallback

Runtime resolution-change paths retain their existing fbset fallback.

The runtime framebuffer preparation is gated on the presence of fbmode, and failed fbmode transitions fall back to fbset where a mode change is required. The installer uses firmware-provided fbset directly because fbmode is not available during bootstrap.

Testing

Thanks @Amiga500 and "another.alvin" for testing and coming with feedback.

Tested on Miyoo Mini V4 / Miyoo Flip 752x560 hardware:

  • repeated cold boots
  • boot with and without .force640Res
  • MainUI startup and return from games
  • 640x480 and 752x560 game launches
  • GameSwitcher handoffs and quick switching
  • shutdown by power button from a 752x560 game
  • repeated forced-640 boots to check for intermittent sheared output
  • captured the firmware framebuffer geometry immediately after init_lcd
  • first install on a 752x560 device
  • first install on a 640x480 device
  • verified behavior remains unchanged on Miyoo Mini Plus (640x480)

fbset requests a geometry and returns without verifying that the driver
has latched it. Runtime also always asked for two virtual pages, even
though MainUI uses three, and stale framebuffer contents were left in
memory when page boundaries moved.

That last part causes the sheared frames seen around 752x560 handoffs.
A 640 page can start inside a 752 page, so pixels written at a
3008-byte stride can briefly be scanned out at 2560 bytes per row.

Add fbmode, which verifies both visible and virtual geometry, clears
using the driver's real post-switch line_length, can preclear before a
pitch change, and leaves scanout parked on page 0.

Make launch_switcher the single owner of GameSwitcher's 752x560
requirement. A game exiting toward GameSwitcher leaves the framebuffer
alone, and launch_switcher probes first so an already-correct 752x560
two-page layout is left untouched.

Leave the framebuffer alone when a shutdown is pending too. keymon draws
the End_Save screen from deepsleep() and backgrounds it, so it is
usually still rendering when the game exits and runtime reaches the
post-game resolution change. bootScreen reads the geometry once at
startup and writes rows at that pitch, so changing the mode underneath
it shears the shutdown screen. Nothing after that point needs 640x480.

On 752x560-capable devices, establish MainUI's 640x480 three-page
layout in init_system before the boot screen is drawn. launch_main_ui
probes that layout and preserves it when already correct; if a real
change is needed it preclears before committing the new geometry.

Existing resolution-change paths retain fbset fallback. The new
preparation paths only run when fbmode is available, and failed mode
changes fall back to fbset where a transition is required.
Not everyone wants 560p on a 752x560 panel. Drop a .force640Res file in
$sysdir/config and runtime keeps the framebuffer at 640x480.

The override is applied after resolution detection. When set, it pins
screen_resolution to 640x480, removes /tmp/new_res_available, and
establishes the 640x480 three-page layout through fbmode.

Removing /tmp/new_res_available means the rest of runtime takes the same
paths it would on a device that never exposed 560p, rather than every
call site needing to know about the override.

The actual mode used by Onion is written to /tmp/screen_resolution.

No flag, no behaviour change.
Copilot AI added a commit to Amiga500/Onion that referenced this pull request Sep 9, 2026
…and previews, GameSwitcher favorites + crash fixes, fbmode framebuffer transitions

Co-authored-by: Amiga500 <16525337+Amiga500@users.noreply.github.com>
@robcodedev

Copy link
Copy Markdown
Author

Thanks @Amiga500 for testing and coming with feedback. I will dropping the detection commit rather than patching it. Force-pushing later so this is just the first 2 safe commits.

install.sh initializes the LCD but never establishes a framebuffer
geometry, so installUI starts on whatever mode the firmware left
active. On a Miyoo Mini Flip that is 752x560 with three virtual pages,
while installUI's layout is a fixed 640x480, so the installer UI is
drawn wrong.

Clear the framebuffer and set 640x480 with two pages right after
init_lcd, before anything can draw. The clear comes first: fbset does
not touch framebuffer memory, so the firmware's boot contents would be
scanned out at the new stride until installUI paints over them, which
is not until after SDL and asset loading.

Clearing /dev/fb0 to end of device covers the framebuffer allocation
exposed by the driver, a superset of both layouts, and zeroed bytes are
safe to scan at either pitch.

Use fbset rather than fbmode because the installer runs before
onion.pak is extracted on a first install, and the stub clears
.tmp_update before install.sh runs on an update, so bin/fbmode is not
available on either path. Apply the same preclear-before-pitch-change
principle directly here with the tools available during bootstrap.

fbset is supplied by the firmware at /usr/sbin/fbset and is on PATH
on both paths.
@robcodedev
robcodedev force-pushed the framebuffer-transitions branch from b3d9186 to 5b95c76 Compare September 10, 2026 19:12
@robcodedev robcodedev changed the title Improve framebuffer transitions and screen initialization Improve framebuffer transitions and 640x480 handling Sep 10, 2026
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