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 |
|
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
1dc9b04 to
1a8ab51
Compare
4402aff to
2a77569
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 8.1.0, minimum unchanged at 8.0.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. Both structures are members of a union rather than fields in sequence, and the video members are the larger ones, so neither addition moves an offset. Measured against master's headers and this branch's: game_stream_properties 28 bytes either way, game_stream_buffer 24, game_stream_packet 36. A client built against 8.0.0 therefore still reads the table correctly, and the minimum stays where it is rather than forcing every game add-on to be rebuilt.
2a77569 to
6e5ba08
Compare
|
@garbear what's your view now on including this in test builds and opening up against master. |
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, squashed to a single commit.Game API 7.3.0
The hardware framebuffer structures gain a size: the largest frame a client will
draw, which the frontend needs before the stream opens to allocate with, and the
size of each frame actually drawn, without which a client that draws a smaller
frame into a larger framebuffer is shown in the corner of a black field.
Nothing else in the API moves. Measured against master, every public structure
is the size it already was:
game_stream_propertiesgame_stream_buffergame_stream_packetAddonToKodiFuncTable_GameKodiToAddonFuncTable_GameBoth 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.
_MINis therefore left at 8.0.0: every add-on built against the currentAPI keeps loading, and only clients that want a hardware framebuffer need
rebuilding. The version itself goes to 8.1.0.
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
PlayStation 2 is the newest and was the one expected to be out of reach. It renders through this path on desktop GL with the stock core, and on GLES with a port of LRPS2 written for it — verified on Intel and on AMD (Radeon 780M) under Mesa, booting the PS2 BIOS and running a real disc. The GLES side of that lives in LibreELEC/LibreELEC.tv#11701 as a patch series against the core, not here.
PlayStation was added on @KOPRajs's suggestion, and it earned its place: it is the only system tested here that changes resolution while a game runs, and it found a bug none of the others could. SwanStation moves between 256x239, 320x240, 640x239 and 640x472 between screens, and the frame copy the rendering thread samples was being allocated once, at whatever the first frame happened to be. Every later frame blitted into a texture cut to the size of the Sony logo, and the renderer, told the frame's real size, sampled up to 2.5x past the end of it. The copy is now sized to the client's framebuffer, which is what the renderer already assumed. Fixed here rather than worked around in the core.
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.
Split out of this branch
Work that rode along on the old branch is proposed separately, so this is
rendering only.
Games: remember which emulator to open a game with(xbmc#28965) and
RetroPlayer: don't offer the whole filesystem as savestates(xbmc#29017) have merged.Games: say when a rumble request goes nowhereis xbmc#29032. The audio pacing change was withdrawn afteryour review and replaced by xbmc#29036 and xbmc#29037.
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, and the branch is currently behind master.
I would rather say so than have it taken on trust. Re-running the full set is
the next job.