RetroPlayer: hardware rendering for game clients (OpenGL and OpenGL ES) - #151
RetroPlayer: hardware rendering for game clients (OpenGL and OpenGL ES)#151sunlollyking wants to merge 1 commit into
Conversation
|
Garbear - aware you've got a lot on - this is super exciting functionality i think you'd be interested in. I'm wanting lots of feedback from a wide base of users on code structure and all the errors it probably contains. Ive added as much defensive programming as i can but aware i've not consulted anyone really on what is quite a complicated architecture. Also wanting to open this up to the libreelec team for review too. Happy to take any feedback on approach curious for you to test but it genuinely works and gets us a long way to full compatibility. Edit: I'll also eventually squash commits keeping them all visible at the moment in case everyone seems happy and we can look at incremental cherry pick merge. |
792c120 to
d83c9ac
Compare
|
This is so cool! Only problem is, it's hard for me to test atm as my branch has a lot of unfinished work for B2 that conflicts with the patchset here. If we can start getting everything else merged, we'll get a master branch we can rebase on that I can easily test out. |
|
No stress ! We'll get it all in and Q is going to hopefully be a huge RP release. |
|
B2 will freeze most code, so it'll be a stable point where we can start developing for Q*. Now I just gotta bet B2 finished and shipped 🙂 |
|
Tested end to end today on both renderers. The flickering that made hardware-rendered games unplayable is fixed, and video filters, scaling and rotation now work on OpenGL ES for the first time. Root cause of the flickering. A hardware-rendering client was handed one framebuffer and drew into it every frame, and that same framebuffer was published to the rendering thread. So the thread sampled a frame while the client was already drawing the next one into it, and what came out missing was whatever the game drew last. In Crazy Taxi the sky held still while the cars and the HUD flickered, because the sky is drawn first and they are drawn last. Two approaches that do not work, both tried:
What works is copying instead. When the client signals a finished frame the pool blits it into a buffer of its own and publishes the copy, so the client keeps the single stable framebuffer it expects and the rendering thread samples a surface nobody is drawing into. The copy is taken on the client's thread, between its frames, where the frame is whole. Two targets are used in turn because the rendering thread may still be sampling the one published last frame. Worth noting for anyone debugging something similar: enabling a video filter does not mask this. The filter path copies too, but on the rendering thread at render time, so it faithfully copies a half-drawn frame. A second fix was needed for desktop GL specifically. A core profile has no default vertex array object, so the draw that puts the game on screen was rejected outright with Test results
OpenGL was tested on a desktop Wayland build (Mesa, Intel), OpenGL ES on a LibreELEC Generic image on real hardware — One measured cost: an extra blit per frame. No dropped frames and no change in audio pacing on either renderer. Two known issues, neither a regression here: the GBC and PSP overlay filter presets clip out of bounds (2 of 616 presets, both upstream libretro presets, cosmetic), and a client that fails to boot a disc image can take the Kodi process down with it, which wants an error dialog rather than a hard exit. I'll push the two commits onto this branch next. |
d83c9ac to
2b01a14
Compare
|
I've replaced this branch with the line of work that is actually tested, so it can be tried on either renderer. The previous branch is preserved at Why it was replaced rather than added to. The old branch carried three approaches to the flickering that are now known not to work, and leaving them in the history would invite reviewers to reason about them:
What works is copying the finished frame on the client's thread and publishing the copy, so the client keeps the single stable framebuffer it expects and the rendering thread samples a surface nobody is drawing into. Testing. Both renderers, on real hardware:
OpenGL on a desktop Wayland build (Mesa, Intel). OpenGL ES on LibreELEC, on two machines — Intel, and an AMD Radeon 780M — reporting For anyone testing: a LibreELEC One thing to be aware of: this branch is stacked on the RetroAchievements callbacks from #150, which is one commit of the 65 here. Everything else is renderer work. Happy to rebase it off if you would rather review the two independently. Two known issues, neither introduced here: the GBC and PSP overlay filter presets clip out of bounds (2 of 616, both upstream libretro presets, cosmetic), and a client that fails to boot a disc image can take the Kodi process down rather than reporting an error. |
2b01a14 to
e76f6f6
Compare
|
Withdrawing one of the two "known issues" I listed earlier: the claim that a client failing to boot a disc image can take the Kodi process down. That came from scripted launches on my side, and those runs turned out to be unreliable for reasons I never got to the bottom of — the same crash appeared on both renderers, and on a fully reverted tree where my changes could not have caused it. Booting the same image by hand, repeatedly, does not reproduce it. So it is far more likely an artefact of how I was driving Kodi than a real fault, and it should not be in front of reviewers as a defect. The other known issue stands: the GBC and PSP overlay filter presets clip out of bounds (2 of 616, both upstream libretro presets, cosmetic). |
DO NOT MERGE. Build kodi from garbear/xbmc#151 (RetroPlayer GL/GLES hardware rendering, head sunlollyking:retroplayer-opengl-hw) and game.libretro from kodi-game/game.libretro#164 (Game API 7.0.0, head sunlollyking:opengl-hw-rendering), with the libretro-common and rcheevos pins that tree expects. All game addons rebuild against the new Game API automatically. Verified at runtime on x86_64 (AMD Radeon 880M, Mesa GLES 3.2, Wayland): dolphin negotiates an OpenGL ES 3.2 context and GameCube games are fully playable. This branch exists to exercise the same stack on aarch64 CI.
|
Tested this on the tv.kodi.Kodi flatpak (freedesktop 25.08 runtime, Result: working, and genuinely playable. A GameCube title ( Three observations from the debug log, in case they're useful:
Two issues hit along the way turned out to be unrelated to these PRs, noted for anyone reproducing: the generated game.libretro.dolphin addon requires An aarch64 build of the same stack is being exercised via flathub/tv.kodi.Kodi#775. Happy to run further tests on this hardware (AMD/Mesa/GLES/Wayland flatpak) if useful. |
DO NOT MERGE. Build kodi from garbear/xbmc#151 (RetroPlayer GL/GLES hardware rendering, head sunlollyking:retroplayer-opengl-hw) and game.libretro from kodi-game/game.libretro#164 (Game API 7.0.0, head sunlollyking:opengl-hw-rendering), with the libretro-common and rcheevos pins that tree expects. All game addons rebuild against the new Game API automatically. Verified at runtime on x86_64 (AMD Radeon 880M, Mesa GLES 3.2, Wayland): dolphin negotiates an OpenGL ES 3.2 context and GameCube games are fully playable. This branch exists to exercise the same stack on aarch64 CI.
DO NOT MERGE. Build kodi from garbear/xbmc#151 (RetroPlayer GL/GLES hardware rendering, head sunlollyking:retroplayer-opengl-hw) and game.libretro from kodi-game/game.libretro#164 (Game API 7.0.0, head sunlollyking:opengl-hw-rendering), with the libretro-common and rcheevos pins that tree expects. All game addons rebuild against the new Game API automatically. Verified at runtime on x86_64 (AMD Radeon 880M, Mesa GLES 3.2, Wayland): dolphin negotiates an OpenGL ES 3.2 context and GameCube games are fully playable. This branch exists to exercise the same stack on aarch64 CI.
|
@kel-mo thank you, that is the first time anyone but me has run this and it is on a stack with nothing in common with mine — different GPU, different Mesa, flatpak rather than a distro or LibreELEC build. Worth saying that your log exercised the refusal path in the negotiation ladder, where a GL core context is turned down on a GLES build before ES 3.2 is accepted. Nobody had tested that but me, and it is the part I was least sure of. All three observations chased down. Two fixes pushed, one is not ours. 1. The handshake is not contradictory, but the logging deserved the confusion. Two different meanings of "shared" sitting next to each other. Kodi's line means the client's context shares Kodi's objects. Dolphin's warning is about There were also two log lines announcing the same context, one of which only added the word "shared". Dropped the duplicate, said what the survivor actually shares, and added a line on the add-on side explaining the refusal (kodi-game/game.libretro#164, 2. Not ours, and not fixable from here. Dolphin's libretro build uses 3. This one was real, and it is the interesting one. The client's context is deliberately made current with no surface — the client draws into its own framebuffer and never needs one. A stream opens across two threads, which is where the mismatch comes from, and the picture recovers on the following frame. That gives exactly the signature you describe: one incomplete-framebuffer error, at Being straight about what that last one is worth: I found it by reading, not by watching it happen, and I have not confirmed the error is gone. Scripted launches on my machine die at game load for reasons I never got to the bottom of — the same unreliability behind the crash claim I withdrew earlier in this thread — so I could not reproduce your log locally. The defect is real and wrong on its own terms regardless. Whether it is your error is unproven. If you get the chance to re-run on The GameCube input problem is ours, not a kodi-game one to report. On the aarch64 builds in flathub/tv.kodi.Kodi#775 — nothing to do with this stack. The runs are failing in |
The pipeline still cloned retroplayer-achievements-api, which predates this work and carries neither the hardware rendering entry points nor AudioAvailable. Every Windows job failed compiling client.h against it. Point it at retroplayer-opengl-hw, the head of garbear/xbmc#151, which is the Kodi side this add-on declares a dependency on.
d520e43 to
f94e7b5
Compare
|
Rebased and out of draft. Off the achievements base. You said back in August that this was hard to test because it was stacked on unfinished work, and that once things were merged there would be a master to rebase on. There is now, so this sits on current The ABI break is gone. That was the other thing making it awkward — 7.0.0 with Rewritten as eight commits meant to be read in order: the API, the windowing accessors, the buffer and pool, the renderer, the end-to-end path, then three that are not hardware rendering at all and were only ever tangled in with it. Happy to split those three out if you would rather. Two things dropped along the way. The One thing I want to be straight about: all the hardware testing in the description happened before this rebase. It builds clean since — full GLES build, no errors — but I have not re-run games on it. I would rather say that than have you find out by trying it. Re-running the set is my next job and I will report back either way. The paired add-on change, kodi-game/game.libretro#164, now builds against stock master too, so it is not waiting on this. |
f94e7b5 to
c404346
Compare
|
Can you rebase on |
9a8e175 to
e4c734d
Compare
|
I merged xbmc#28965, good time to rebase that out. |
e4c734d to
2d6d67d
Compare
|
Rebased onto Down from 9 commits to 7, because two are upstream now:
Three conflicts, all resolved by taking both sides rather than choosing:
One thing worth flagging because skipping the emulator commit nearly hid it. That commit also defined Builds clean against current |
|
|
||
|
|
| bool RequiresGameLoop() const { return m_bRequiresGameLoop; } | ||
| bool IsPlaying() const { return m_bIsPlaying; } | ||
| size_t GetSerializeSize() const { return m_serializeSize; } | ||
| size_t GetSerializeSize() const { return SerializeSize(); } |
There was a problem hiding this comment.
I'd say remove this and rename below to GetSerializeSize()
There was a problem hiding this comment.
Done — GetSerializeSize() is now the only accessor, declared where the wrapper used to be, and the five ReversiblePlayback callers follow it. No wrapper left.
|
Some review:
I went through the rest of the code and it looks good. I noticed earlier that some commits touched code from previous commits. We might want the rendering stuff squashed into 1 atomic commit, unless you can break it up in a way that makes sense, and compiles between each commit. |
2d6d67d to
174ffec
Compare
|
All four done, force-pushed. Branch is 0 behind strings.po — two problems, not one. The two graphics-API messages had taken 35302/35303 while the last defined string is 35298, leaving 35299–35301 unaccounted for with no empty-range marker. And the removed flicker setting had left an orphan behind: a comment and a source reference with no Game.h — dropped. Checking what it actually contained: the whole diff was the Version — 7.3.0, minimum 7.3.0. Worth recording why it isn't 7.2.0: Commits — squashed the rendering work into one, and you were right that it had to be: they genuinely did not stand alone. The history is now: I kept the API bump separate because it is the one commit with an ABI consequence, and that seemed worth being able to point at on its own. One thing to flag: the bottom two are already open as xbmc#29032 and xbmc#29031. I have left them here so the branch still builds and runs as a unit, but they are not rendering work and can come out the moment either lands — say the word if you would rather they went now. I also caught a self-inflicted one while restructuring: the first pass swept some untracked scratch files of mine into the squash. They are out, and the tree is exactly the previous content plus the four fixes above — I diffed it against the pre-restructure branch to be sure nothing else moved. |
|
Yes, bottom 2 are upstreamed, we'll continue discussion there, so nix them from here. 2 commits is fine, but the strings.po changes bled into one and screwed up the second. Lets just combine them. |
|
Both done — force-pushed, one commit now, builds clean and 0 behind Rumble and audio are out. Dropped rather than reverted, so nothing of them remains in the tree; they live on in xbmc#29032 and xbmc#29031 as you say. The two commits are combined. You were right about the bleed and it was worse than cosmetic — Final shape: I checked the result against the pre-change branch rather than assuming: the only difference is the four rumble and audio files, nothing else moved. That leaves this PR as exactly the rendering work plus the API bump it depends on, which I think is what you were after. |
174ffec to
1dc9b04
Compare
A game client that renders on the GPU is handed a framebuffer of its own to draw into rather than passing frames back as pixels. Everything here arrives together because none of it stands alone: the render layer needs the EGL display and context the windowing system owns, a render buffer has to be backed by a framebuffer object, a renderer has to draw what the client left in it, and the stream has to negotiate a context both the client and the build can provide -- refusing honestly, and saying why, where they cannot. The client's finished frame is copied on its own thread between frames, and the copy is what the rendering thread samples. The client keeps the single stable framebuffer it expects, and no frame is read while it is half drawn. The Game API goes to 7.3.0, minimum 7.3.0. game_stream_hw_framebuffer_properties grows from a placeholder byte to carry the maximum width and height, because a client had no way to say how large its framebuffer needs to be and the frontend was sizing it from the geometry of a frame rather than the largest frame the core will ever draw. The frame size travels with each presented frame so the renderer samples the part that was drawn rather than the whole buffer. That moves the offsets of everything after it, so the minimum moves with the version: a client built against 7.2.0 is refused rather than loaded against a table it would read wrongly. 7.3.0 rather than 7.2.0 because master's 7.2.0 is already taken by "Allow setting game FPS/samplerate", which leaves this structure alone -- two different layouts must not share one number.
1dc9b04 to
1a8ab51
Compare
Game clients that render with OpenGL — Flycast, Mupen64Plus-Next, melonDS, PPSSPP, YabaSanshiro, Dolphin and others — could not run in Kodi at all. RetroPlayer only accepted frames a client had drawn in software and handed over as pixels. This adds the path that lets a client render on the GPU instead: Kodi gives it a framebuffer, the client draws into it, and Kodi draws the texture that framebuffer is backed by. The frame never leaves the GPU.
Builds on Lukas Rusak's WIP FBO buffer, pool and renderer, preserved with their original authorship.
Rebased onto master, off the achievements base
This used to be stacked on the RetroAchievements callbacks, which made it hard to test independently — @garbear said as much on this PR. It is now rebased onto current
xbmc/xbmcmaster and carries nothing but this work, in eight commits meant to be read in order.No ABI break any more
Earlier revisions moved the Game API to 7.0.0 with
_MINto match, because the hardware framebuffer structures grow. That is no longer necessary. Measured against master:game_stream_propertiesgame_stream_buffergame_stream_packetAddonToKodiFuncTable_GameKodiToAddonFuncTable_GameThe two structures that grow sit inside unions whose largest members are the video and software-framebuffer ones, so nothing an existing add-on was built against moves. 7.2.0 with
_MINleft at 7.1.0 — clients built for 7.1.0 keep loading and simply never use the new fields.GL and GLES
Both, from shared code. The GLES path is a thin delta rather than a second implementation — the differences are a handful of guarded lines, because a GL core profile has no default vertex array object and GLES does not care.
GLES is where this has actually been run: LibreELEC is GLES, and the Piers Linux flatpak has moved to GLES too.
What works
Video filters, scaling, view modes and rotation all work on hardware-rendered games, which they never did before. Rewind and savestates work alongside it. Quitting returns to a working GUI.
Independently reproduced by @kel-mo on the tv.kodi.Kodi flatpak (AMD Radeon 880M, Mesa, GLES 3.2, Wayland), including the negotiation ladder correctly refusing a desktop-GL context on a GLES build before settling on GLES 3.2.
The frame race, since it shaped the design
A client is given one framebuffer and draws into it every frame. Publishing that same framebuffer to the rendering thread means sampling a frame the client is already drawing the next one into, and what goes missing is whatever it drew last — in most games the HUD. Fences do not fix it: a fence orders the writes before it, and the racing writes belong to the next frame. Handing the client alternating buffers is worse, because clients cache the framebuffer they are given.
What works is copying on the client's thread, between its frames, where the frame is whole, and publishing the copy.
Four commits that are not hardware rendering
These rode along on the old branch and are each proposed against
xbmc/xbmcmaster on their own, so this PR can be reviewed as rendering work alone:Games: remember which emulator to open a game withRetroPlayer: don't offer the whole filesystem as savestatesRetroPlayer: slow the game down when the audio sink is full, don't flushGames: say when a rumble request goes nowhereThey stay here so the branch is testable as one piece, and will drop out as each lands.
Paired add-on change
kodi-game/game.libretro#164. That one now builds against stock
xbmc/xbmcmaster as well, detecting the new fields at configure time, so it is not blocked on this landing.Testing status, stated plainly
Everything above was verified on hardware before this was rebased onto master. Since the rebase it builds clean (full GLES build, zero errors) but has not been re-run on hardware. I would not want that taken on trust — I am putting it up for review now rather than sitting on it, and will re-run the full set and report back.