Skip to content

Refresh and audio rates on the Retroid Pocket Nova (SM8550)

RetroArch defaults to variable refresh from 80 to 120.198154 Hz on the built-in panel when it is the only connected display and no display mode is pinned. It uses the fastest mode for every system and times frames at the core's reported rate; the display driver repeats them to keep the panel within its range. See variable refresh for the launch conditions, rate limits and measurements.

Fixed-refresh modes

The mode selection and display-paced rendering described below apply to RetroArch with variable refresh disabled (global.vrr=0 in /storage/.config/system/configs/system.cfg). The preferred mode is 119.880120 Hz, twice NTSC's 59.94 Hz. For systems whose native rate is noticeably different, setsettings.sh (set_ra_refresh_rate) asks for a panel mode at exactly twice that rate, and RetroArch switches to it when the game starts.

Where each system's native rate comes from, crystal by crystal, and where an emulator's number differs from the console's, is derived in CONSOLE_CLOCKS.md. Which systems have their mode, and which are still open, is in PortareOS_Modelines.md.

Every mode uses the panel's own timing, 1302 × 998, and changes only the pixel clock (see below). Steam's 120.0004 Hz mode is Android's own 120 Hz mode exactly. Nothing else picks it: RetroArch asks for each system's exact rate, and mpv, ARMSX2, xemu and the launcher take the preferred mode. All of them keep the panel in its 120 Hz class. The modes are defined in the panel driver, projects/PortareOS/devices/SM8550/patches/linux/0105-drm-panel-Add-Retroid-Pocket-Nova-panel.patch.

Panel mode Pixel clock Off the console's rate Used by
119.88031 Hz 155772 kHz +1.6 ppm default (launcher, everything else)
119.45473 Hz 155219 kHz -2.2 ppm gambatte, mgba
120.198154 Hz 156185 kHz +4.4 ppm, rounded up snes9x, mesen2; variable refresh runs on it
119.65252 Hz 155476 kHz +2.6 ppm parallel_n64, swanstation, ymir
119.84568 Hz 155727 kHz +1.7 ppm genesis_plus_gx
118.36038 Hz 153797 kHz +3.2 ppm fbneo, for neogeo only
119.20000 Hz 154888 kHz +1.7 ppm neocd
120.00037 Hz 155928 kHz +3.1 ppm gamescope, for Steam

Why every mode has the panel's own timing

The panel's own timing, Android's for both of its rates, is 12 lines of vertical front porch, 2 of sync and 24 of back porch: 998 lines. Every mode here uses it and moves only the pixel clock, so none is exact to the console; all are within 3.2 ppm, which at fixed refresh RetroArch's audio rate control absorbs. Variable refresh runs on the fastest, the SNES mode, and that one is rounded up, +4.4 ppm: its refresh must be no longer than half the fastest content's frame, or that content falls a refresh behind about once an hour.

Two departures from it were tried, and the panel took neither under variable refresh.

The SNES mode was 1302 x 1116 at 174651 kHz: a 142-line back porch, cut to widen black frame insertion's flip window, and exact to 0.00 ppm. The panel does not lock to that frame at all. It scans out of its own memory at about 120.14 Hz, and the frames arriving on the link land in that memory at a point of its scan that drifts with the difference between the two rates. The DPU's CRC cannot see it, since every refresh leaves the controller whole; tools/tear-tap shows it as a bar that tears on a beat:

Panel mode Content, refresh Tears seen
120.198 Hz, vtotal 1116, variable refresh 59.94 fps, 119.88 Hz every 3.5 to 4.7 s
the same 60 fps, 120 Hz every 6.5 to 7.8 s
the same 50 fps, 100 Hz all the time
119.880 Hz, vtotal 1001, fixed refresh 59.94 fps none

Each interval is one over the gap between the panel's ~120.14 Hz and the refresh: 0.26 Hz, 0.14 Hz, 20 Hz.

Every other mode had 27 lines of back porch, three more than the panel's, so that 1302 x 1001 would make 119.88 Hz exactly 120000/1001. The panel locks to that, but holds the lock only while variable refresh stretches a frame by about 0.3 ms; past that it refreshes on its own and the frame arriving later tears. Measured with the bar on the 1001-line SNES mode, and through vrr-probe custom for Android's timing against ours at the same pacing:

Timing, variable refresh Content, refresh Stretch Tears seen
1302 x 1001, 156654 kHz 59.0 fps +0.16 ms 1 in 20 s
the same 58.0 fps +0.30 ms none in 20 s
the same 56.0 fps +0.61 ms 4 in 20 s
the same 54.0 fps +0.94 ms 6 in 20 s
the same 50 fps +1.68 ms many
1302 x 1001 against 1302 x 998, blind 50 fps +1.67 ms 2 against none
1302 x 998, 155928 kHz, again 50 fps +1.67 ms none in 30 s

The rolling line seen with BFI on the wide mode, below, has the same shape.

The DPU keeps the rate it is given. Measured with vblank-rate on the default mode, whose pixel clock the DSI PLL synthesises as 156239991 Hz against the 156240000 asked for: 119.88005 Hz over 20 s and 119.88009 over 60, -0.50 and -0.17 ppm from the 119.880113 those timings work out to: the pixel clock over htotal x vtotal. That is the link's rate; on its own porches the panel follows it, on the wide SNES mode it did not (above).

Measure against CLOCK_MONOTONIC_RAW, not CLOCK_MONOTONIC. DRM timestamps vblanks with ktime_get(), which NTP slews; the pixel clock is not slewed, so on a device running timesyncd the slew is measured as if it were panel error. Here that was +68 ppm drifting to +70, and the slew was separately seen swinging between -114 and -142 ppm between runs - orders of magnitude more than the mode errors this table is cut to. vblank-rate samples both clocks and reports both rates.

BFI was tested fairly afterwards and does not work here. The first attempt was not a fair test: setsettings gives BFI a swap interval of 1 so a frame reaches the display on every refresh, and the timed-present path was gated on an interval above 1, so BFI fell back to the repeated presents that patch exists to replace - visibly slowed, frames arriving a refresh late. #427 opened the gate. With it open, and on the wide-blanking mode cut for it, BFI still flickers and a line still rolls down the panel; more swapchain images help but do not remove it, at two or at three. That mode is the one the panel does not lock to (above), so the rolling line may be its own scan; BFI has not been measured on the panel's own porches.

Measured on the same session, in flips per second against the 120.198 the mode produces: 101.764 with the CRT shader on, 120.075 with it off, 120.195 with three swapchain images. So the band is GPU throughput and swapchain depth, not blanking - a frame that is not ready inverts the light/dark alternation however wide the window is. The 156 lines are not earning their bit clock, and were kept only because nothing measured them costing anything either; #623 measured it.

What the stock Android install says about this panel

The device still has its Android partitions, and the panel's qualified configuration is in dtbo_a, on the node named il97680a amoled panel without DSC. None of this was known here:

  • The panel is an AMOLED. Pixel response is microseconds, so there is no transition smear on a moving edge. What softens a left-to-right scroll is persistence: each SNES frame is held for two refreshes, 16.6 ms, and a tracked edge smears by however far it travels in that time. Black frame insertion is the remedy, and it does not work here - so the softness on a scrolling game is the display being sample-and-hold, and nothing in software reaches it.
  • Its own timings are 1302 x 998, at 60 and 120 Hz. This driver used 1001 lines, three more of back porch, until #623 showed what they cost (above).
  • The init sequence already matches the vendor's, command for command and byte for byte, for the first fourteen: the same register pages, the same 0x0d 0x75 0x00 0x00 brightness, the same 120 ms and 20 ms waits, and the same reset pulse once the active-low flag is accounted for.

The vendor sends three more after the display is on - page 0x22, register 0xe1 = 0x01, back to page 0x00 - which this driver does not. Whoever transcribed the other fourteen exactly had those in front of them and stopped, so the omission is more likely a decision than an oversight, and the reasoning did not survive: the driver reached this tree with the ROCKNIX rename. What 0xe1 does is undocumented, and a register written once after display-on in a vendor page is the shape of a compensation setting. That is the argument against sending it speculatively rather than for it - getting it wrong degrades the image subtly and attributes itself to nothing.

Why video mode, when the vendor uses command mode

That node drives the panel in command mode, with a hardware TE pin. This driver uses video mode, and that is deliberate.

A CRT has no memory: the host drives H and V sync and the beam paints what arrives, when it arrives. Video-mode DSI is the same contract - the DPU owns the timing, the panel holds no framebuffer, pixels stream. Command mode is the other arrangement: the panel keeps its own GRAM, refreshes itself at its own rate, and the host writes into it when TE allows.

Every mode in the table above exists because of that difference. The timings are ours to choose, so each console gets a panel mode at exactly twice its frame rate; in command mode the panel self-refreshes at a rate it decides and there is no modeline to cut. The strategy does not survive the switch.

It is also worse on both things this device is tuned for. TE gating puts a variable wait between a finished frame and the panel showing it, where video mode scans out whatever was latched at vblank. And writing into a buffer that is being scanned out is the one arrangement that tears for real, which tear-test shows does not happen here at all.

What command mode buys is panel self-refresh while nothing moves, a power saving on a static screen and the opposite of this workload. Considered and declined.

Fixed-refresh pacing: how a frame lands on a frame

A mode at twice the console's rate is only half of it. The other half is RetroArch holding the core to that mode, one frame for every two refreshes, with nothing else setting the pace.

RetroArch reaches the panel through Vulkan's VK_KHR_display, with no compositor. Its swap interval is left on automatic, which resolves to 2 when the mode is within the audio timing skew of twice the core's rate; that is what the modes are chosen for. Vulkan has no swap interval, so stock RetroArch keeps one by presenting the same frame twice into the FIFO swapchain. With two swapchain images, which the image uses for latency, the second present's acquire returns at the vblank between the two, and the core has one refresh period, 8.36 ms, to produce the next frame. A frame that takes longer costs a whole refresh. SwanStation at 4x with a CRT shader missed often enough for 55.65 fps against 59.83, with the audio buffer running dry every 50 to 100 ms and 5 to 8 ms gaps in the sound 7.5% of the time. That was the "slowed down" audio after threaded video was turned off.

Patch 0014-vulkan-khr-display-timed-presents.patch presents each frame once instead, with a desired present time through VK_GOOGLE_display_timing: two refresh periods after the last present the driver has timed, counted per present issued since it. Mesa's KMS backend flips on exactly that vblank. The acquire then returns the moment the previous frame reaches the display, the core has the whole frame period, and the loop runs at the mode's rate, which is the console's. The last present's actual time is the anchor, so a late frame moves the cadence by a refresh and the next one is placed from where the display is, not from a schedule that has drifted. The repeat stays for contexts that cannot time a present. Two things in RetroArch had to be fixed for the extension to be usable at all: its entry points were only loaded on Windows, and a device negotiated by a hardware core, which is how SwanStation and the other Vulkan cores work, never had the extension enabled or recorded.

Measured on Tekken 3, threaded video off:

Presents Core fps Audio gaps in 15 s
The same frame twice 55.65 161 gaps of 5 to 8 ms, 7.5% of the time
One, timed 59.64 none

The dropped-frame count was about ten in every run, all in the first seconds, while the core loads and the pipelines compile.

Threaded video is off (video_threaded = "false"). With it on, a worker thread presents whatever frame is latest and the core is paced by the audio sink instead: full speed and clean audio, but a thread hop of latency, frames reaching the panel unevenly, the core drifting against the panel by the difference of two clocks, and nothing for the automatic frame delay to measure. The exact modes buy nothing in that arrangement. It was the stopgap (#337) between finding the cause and fixing the driver (#339). A core that cannot make a frame inside 16.7 ms can still have it on in its own override file, config/<core>/<core>.cfg; better to find out why it is that slow.

With the core paced by the panel, audio rate control only absorbs the residual between the mode and the panel's crystal, a few parts per million, rather than the difference between the audio and video clocks. Where the mode is not exactly twice the core's rate, the arcade boards for instance, the skew is larger and rate control bends the pitch accordingly, as on any other device.

To see it working: the log has Timed presents: swap interval 2, one present a frame, 16.683 ms apart. when the path is in effect. A hardware-rendered core that creates its own Vulkan device has to enable VK_GOOGLE_display_timing for this, because RetroArch cannot add it afterwards. Flycast ignored the list it was given until its patch 002, and ran on the repeated presents. A core that still does gets VK_GOOGLE_display_timing: not enabled on the device, presents are not timed. in the log and falls back. RetroArch's statistics overlay (statistics_show) reports the core's frame rate, the loop deviation and the audio buffer's underruns. vblank-rate measures the panel's real rate without DRM master, from a queued DRM vblank event; /sys/kernel/debug/dri/0/crtc-0/status also counts vblanks but its counter only advances while the vblank IRQ is enabled, so it cannot be sampled against wall clock - an idle panel reads about 27 Hz on a 120 Hz mode. A capture of the PipeWire sink (pw-record --target <sink> -P '{ stream.capture.sink = true }') shows audio gaps as runs of zero samples.

Systems and fixed-refresh modes

The rates are the original NTSC hardware's frame rate, or for handhelds the hardware's own rate.

Where an emulator runs at a different rate from the hardware, the table says so. On the N64, the hardware rate follows from the video chip's 48.681812 MHz clock: a 3094-clock line and a 263-line frame in 240p, or 262.5 lines in 480i. ParaLLEl N64 as shipped upstream does not emulate the line length: it derives the frame period from a nominal 60 Hz and the frame height the game sets, so it runs at about 60.02 Hz. PortareOS patches it (003-vi-frame-period-from-h-sync.patch) to take the period from the V_SYNC and H_SYNC registers, which gives the console's 59.826 Hz, and matches a panel mode to it.

The PlayStation's line is 3412.5 GPU clocks, the broadcast line. SwanStation rounds it to 3413 and so runs at 59.8173 Hz; PortareOS patches it (001-ntsc-line-is-3412-5-ticks.patch) to alternate 3413 and 3412 like the console, which gives 59.826 Hz, the same as the N64, so the two share a mode. SwanStation keeps 263 lines in 480i as well, where the console has 262.5, so a 480i game runs at 59.826 rather than the console's 59.94: 0.19 % slow, the same compromise as the N64's.

Systems (default emulator) Native rate (Hz) Panel mode (Hz)
gb, gbh, gbc, gbch (Gambatte) 59.7275 119.455
gba, gbah, gbav (mGBA) 59.7275 119.455
snes, snesh, sfc, satellaview, snesmsu1 (snes9x) 60.0988 120.198
nes, famicom, fds (MesenCE) 60.0988 120.198
psx (SwanStation) 59.826 (480i too, see above) 119.652
saturn (Ymir) 59.826 (455 x 4 master clocks a line, 263 lines; the core reports it exactly with our patch) 119.652
mastersystem, sg-1000, gamegear, ggh (Genesis Plus GX) 59.9227 119.846
megadrive, megadrive-japan, megadriveh, genesis, genh (Genesis Plus GX) 59.9227 119.846
segacd, megacd (Genesis Plus GX) 59.9227 119.846
sega32x (PicoDrive) 59.9227 (the emulator reports 60) 119.880
n64, n64dd (ParaLLEl N64) 59.826 in 240p, 59.94 in 480i 119.652
neogeo (FBNeo) 59.1856 (the emulator keeps hundredths: 59.18) 118.360
neocd (NeoCD) 59.5999 (the CD's 24.168 MHz crystal) 119.200
arcade (FBNeo) depends on the game's board (about 54–61) 119.880
dreamcast, naomi, atomiswave (Flycast) 59.94 119.880 (exactly 2×)
gamecube, triforce, wii, wiiware (Dolphin) 59.94 119.880 (exactly 2×)
ps2 (ARMSX2) 59.94 119.880 (exactly 2×)
xbox (xemu) 59.94 119.880 (exactly 2×)
psp, pspminis (PPSSPP) 59.94 119.880 (exactly 2×)
scummvm, steam, ports no fixed rate (PC games) 119.880
movies (mpv) the video's frame rate (23.976, 25, 29.97 …) 119.880; mpv syncs video to audio
music, tools, imageviewer – 119.880

Audio

The Nova's speaker and headphone link runs at 48 kHz, 44.1 kHz or 32 kHz. Three things had to allow that: the AudioReach topology blob, which caps the playback PCMs at 48 kHz until extra-firmware's tplg-playback-rates.py widens it; kernel patches 1052–1055, which open the DSP ports and the machine driver's fixup to 44.1 and 32 kHz; and the I2S bit clock, which follows the stream (1054). PipeWire allows all three rates (default.clock.allowed-rates = [ 48000 44100 32000 ]). AUDIO_SAMPLE_RATES.md describes the whole procedure, for carrying it to another distribution. No rate is a resampling stage in itself: the link switches to the rate of the stream that opens it.

RetroArch picks the link rate from the core's (audio_out_rate = "0", RetroArch patch 0015): the smallest of 32, 44.1 and 48 kHz that the core's rate divides into within 0.5 % (32,040 → 32,000; 22,050 → 44,100; 44,100 → 44,100), else the smallest above it so nothing is cut (32,768 → 44,100), else 48 kHz (65,536 → 48,000). The pick is made every time the audio driver initialises, so a core that changes its rate through SET_SYSTEM_AV_INFO, as ParaLLEl N64 does once the game has programmed its DAC, reopens the device at the new game's rate. A non-zero audio_out_rate in a per-core config (config/<core>/<core>.cfg) still overrides the pick; the shipped ones for SwanStation, Flycast, PPSSPP, NeoCD, Genesis Plus GX, PicoDrive (44.1 kHz) and Snes9x (32 kHz, 44.1 kHz for snesmsu1) name the rate the pick would make anyway. RetroArch resamples each core's audio to that rate with its sinc resampler, and dynamic rate control keeps the stream in step with the display. So every core is resampled at least a little, even one whose rate matches the output, because rate control adjusts the ratio by up to 0.5 %. mpv plays a file at its own rate.

The first rate column is the console's own: the rate its sound hardware produces samples at, or "analog" where the chip's channels are mixed as analog signals and there is no sample rate to speak of. The second is what the emulator hands RetroArch, taken from its source code at the pinned commit with PortareOS's options.

Systems (default emulator) Console's audio rate (Hz) Emulator outputs (Hz) Played at (Hz)
gb, gbh, gbc, gbch (Gambatte) analog (the APU runs at 1,048,576) 32,768 48,000
gba, gbah, gbav (mGBA) 32,768 by default; a game can pick up to 262,144 65,536 48,000
snes, snesh, sfc, satellaview (snes9x) 32,000 nominal (about 32,040 on real consoles) 32,040 (the core's own) 32,000
snesmsu1 (snes9x) 32,000, plus the MSU-1's 44,100 44,100 with MSU-1 enhanced audio, the core's default 44,100
nes, famicom, fds (MesenCE) analog (the APU's channels are mixed as analog signals) 32,000 (mixed at the APU clock, lowpassed and resampled; our patch) 32,000
psx (SwanStation) 44,100 44,100 44,100
saturn (Ymir) 44,100 (SCSP) 44,100 44,100
mastersystem, sg-1000, gamegear, ggh (Genesis Plus GX) analog (SN76489, 223,722 per channel step) 44,100 44,100
megadrive, megadrive-japan, megadriveh, genesis, genh (Genesis Plus GX) 53,267 (YM2612), plus the SN76489 44,100 44,100
segacd, megacd (Genesis Plus GX) as the Mega Drive, plus 32,552 (RF5C164 PCM) and 44,100 (CD audio) 44,100 44,100
sega32x (PicoDrive) as the Mega Drive, plus the 32X's PWM at a rate the game sets 44,100 (native would give 53,267) 44,100
n64, n64dd (ParaLLEl N64) set by the game (commonly 22,050 to 44,100) the game's rate, exactly, e.g. 22,037.94; 32,040 until the game sets one the link rate picked for the game's: 32,000 for ~32 kHz games, 44,100 for ~22 kHz and 44.1 kHz ones
neogeo (FBNeo) 55,555 (YM2610) about 48,000 (47,990 at 59.18 fps) 48,000
neocd (NeoCD) 55,555 (YM2610), plus 44,100 (CD audio) 44,100 44,100
arcade (FBNeo) depends on the board about 48,000, depending on the game's frame rate 48,000
dreamcast, naomi, atomiswave (Flycast) 44,100 (AICA) 44,100 44,100
gamecube, triforce (Dolphin) 32,029 (DSP), 48,000 (streamed disc audio) 48,000 (asks RetroArch for its rate) 48,000
wii, wiiware (Dolphin) 32,000 (DSP), 48,000 (streamed disc audio) 48,000 (asks RetroArch for its rate) 48,000
ps2 (ARMSX2) 48,000 (SPU2) 48,000 (44,100 in PS1 mode) 48,000
xbox (xemu) 48,000 (AC'97) 48,000 48,000
psp, pspminis (PPSSPP) 44,100 44,100 44,100
movies, music (mpv, PORTAMP) the file's rate the file's rate the file's rate

In short:

  • No resampling needed: the 44.1 kHz cores play at 44.1 kHz: PS1, Saturn, Dreamcast, NAOMI, Atomiswave, PSP, NeoCD and every other Sega system. Xbox, PS2 and Dolphin already produce 48 kHz, and the NES 32 kHz.
  • Nearly native: the SNES plays at 32 kHz. The 32,040 → 32,000 conversion (0.125 %) is smaller than what rate control adjusts anyway.
  • Resampled as on any other device: Game Boy, GBA and Neo Geo. Their rates match none of the link's rates, so they need resampling either way. The N64 is per game: ~32 kHz games get the 32 kHz link, ~22 kHz games an exact 2:1 into 44.1 kHz, 44.1 kHz games play native.

Adding a mode

  1. Find the system's exact native rate f, preferably from the rate the emulator logs at startup.
  2. Compute the pixel clock round(2 × f × 1302 × 998 / 1000) in kHz, add the mode to the driver patch next to the others, and update the hunk's line count.
  3. Add the emulator and the rate 2 × f to set_ra_refresh_rate in setsettings.sh. RetroArch accepts a mode within 1 Hz of the requested rate, and setsettings.sh only asks for a mode that the panel lists within 0.002 Hz.
  4. Test the mode on the device: modetest -c lists it, and the picture stays stable while a game runs.

Open work is tracked in #284.