Wayland: Implement game embedding#107435
Conversation
4de4f77 to
8852b2e
Compare
|
Thank you, seems to be working great! I'm using Cosmic Desktop on Arch. A few warning/error messages seen so far: `WARNING: FIFO protocol not found! Frame pacing will be degraded. ERROR: Condition "p_window_id != MAIN_WINDOW_ID" is true. Returning: INVALID_SCREEN When stopping game: ERROR: Condition "((size_t)head_rec) != vec.iov_len" is true. Returning: false I can upload full logs if any of these is interesting. |
|
Interestingly I can't seem to embed at all on KDE Plasma, NixOS. There aren't any relevant errors opening the window and closing the window only says the client disconnected. |
|
Hi people, thank you for testing! @cg9999, the first two errors are unrelated to this PR, so that's fine. One is for the new FIFO support which is not yet implemented in most compositors and the other is a new bug which is getting discussed. I think I have a solution for the latter. Regarding the last two errors, those are fine really. Those happen when the game is disconnecting and it just complains that it's not getting enough data, which is fine. I'll make sure to silence those during disconnection when I wrap up the thing. @ArchercatNEO, that's unfortunate :( What's the pic showing? I see a window with something embedded in it i guess? Does the thing look wonky? Note that you need to set both backends (editor and project) to Wayland or it will not work properly. I'll first do a quick test on a KDE VM I have lying around. Hopefully the issue is trivial.
The index out of bounds is weird, but I wouldn't worry about it too much since there's no cleanup logic still. |
|
@ArchercatNEO I think I figured out what's happening. What version of NixOS are you running?
Edit: oof it's so old that it doesn't have a destructor and thus triggers a failsafe... Since it reports some data on bind I need to account for that. I'm onto it. Edit 2: I managed to replicate this on my workstation running the editor on top of LLVMpipe. This will speed up iteration a lot :D Edit 3: From my testing, it looks like the issue was not wl_drm. Oops. |
|
Aight @ArchercatNEO the issue should be fixed. Looks like That said I'm currently keeping it in the code despite being untested in case I eventually do actually get everything through the proxy. It has its own advantages actually. The fix was actually related to Now I can run it on an emulated Fedora KDE 42 using LLVMpipe, and on an updated copy of Arch with LLVMpipe and a modded sway without the dmabuf protocol. On KDE though for some reason the game surface is actually slightly out of place, which is weird. I still haven't investigated that though, but I suppose it might be related to libdecor or something. Please tell me if this fixed your issue :D If anyone's curious, I'll take advantage of this message to explain a special hurdle I had to overcome to get this proxy working: indestructible objects. This proxy tries as hard as possible to offload stuff to the real compositor, 1) because I'm lazy 2) because it'd be tedious to do by hand, but not everything can be blindly passed. One of those is global binding for when the object in question does not have a destructor method. It might sound crazy, but originally most core globals (including infamously Because of this we need to take a bit more extra responsibility: if the object does not have a Luckily this thing only applies basically to core objects and other very old protocols (like wl_drm). All new protocols have destructors and even new eligible versions are automatically included in the check so as time goes on we should be seeing this mechanism being hit less and less, with everything eventually passed through and handled by the compositor natively. In this case, the issue we had is that a few globals (like There's still a few objects I need to track but hopefully they won't give too many issues in the meantime. (Sorry for the rambliness but It's quite late so I don't have much time to explain it in a cleaner way) |
|
@Riteo unfortunately it seems there are more issues and I still can't embed There was nothing wrong with the window it just wasn't embedding (I hadn't set up the images to actually fit in the screen) Here's what I mean by the window not embedding, there's nothing wrong with the content but it just isn't in the game tab Also a MUCH weirder bug, despite not having libdecor both windows have decorations but this warning is still raised somehow. This didn't happen last time I tried it but it does now. |
|
Random crash with this log. The embeded window wasn't even open so this is something with the main window itself |
|
Got a crash using the latest commits: Full log available if needed |
At least we're moving forwards :)
It looks like it's embedding fine to me ;) You're using the floating game workspace. That bar with the various modes and selections is the editor, which in turn wraps the game window. Kinda weird, ik, but that's why you can't click "Game", that's the actual game tab, detached from the main window. All you need to fix it (as you might have accidentally done already) is to untick "Make Game Workspace Floating on Next Play" from the 3 dots in the "debug bar" (no idea how it's actually called): So, in conclusion, it's a feature, not a bug :D @ArchercatNEO and @cg9999:
Unfortunately those kind of crashes can only be debugged by reading the whole log. Please take in consideration that these will contain hexdumps of all data sent to the editor/game including keystrokes so if you have no idea what might be in them, you can send contact me via email at |
|
Full log for #107435 (comment) Nothing secret here, just trying things out on wayland |
|
@cg9999 thank you for the log file! Apparently it's related to the pointer constraints logic, which is also used for pointer warping (a new protocol just came out but we don't have it hooked up yet). I can replicate it by simply triggering a pointer warp in the editor by using infinite panning. We need to redirect requests only from the embedded window but it looks like I always skipped the generic handler (and thus the creation of an object) by returning the wrong value 😅 Hopefully the latest changes I just pushed will fix your issue! :D |
|
@Riteo
Yep that's what it was and looking back it looks like embedding worked even before the wl_display fix it was just in its own window (I really should've noticed the debug tabs on the window oops) As for fixing changes, ever since pulling I have gotten no crashes while developing a game and no significant errors either. |
|
Looking through the protocols used I noticed you added the |
|
@ArchercatNEO great catch! Yea, we need to add that too, along with FIFO actually.
Yes, exactly. We need to have all most used protocols in there for compatibility, as any good compositor would do. That's also why I turned the embedder off for anything but the editor, so that we don't need to worry about this stuff for shipped projects. |
|
Sorry to do this in 2 parts but I looked to mesa's master branch to see which protocols they use in https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/loader/meson.build#L8-16 (Transcripted) I am guessing that these are the protocols we need to relay down to the embedded process since wsi/egl will attempt to use protocols even if we don't. It also seems that regardless of egl/vulkan these are all the protocols we need to worry about. Of these it seems we are missing |
|
Thank you @Riteo for the incredible work ! Question:
In any case it seems really a lot of work and i will test as soon as possible ! |
|
Yes, from the footnote in the first post
|
Hi @AndreaMonzini, thank you!
As @ArchercatNEO pointed out, I considered that, yes. Though neither the de facto standard approach of an embedded compositor nor the buffer-sharing approach used on MacOS really convinced me, given Wayland's architecture. That's how I came up with this experiment.
To be clear, this PR does not make use of the same windowing hacks as on X11 and Windows. The end result is a single window just like on MacOS and is hopefully just as sturdy. The main difference between MacOS' approach and this is that the MacOS embedder has to explicitly (de)serialize all state (buffer data, input, etc.) using Godot's custom IPC. This PR instead skips that (de)serialization step by working directly with Wayland's IPC, allowing us to move all custom logic to the editor and even embed unmodified clients. Since we interact directly with Wayland objects, it should be easier to integrate with more advanced features (e.g. popups). We also get a lot of features "for free"; All we have to do is broadcast to the client whatever message the compositor is already sending us. Don't get me wrong, the MacOS approach is awesome! The only reason that I can do something like this is because the Wayland protocol allows me to do that. Even the official documentation notes that it serves well for embedding so I suppose that it was probably designed with that in mind. I wouldn't be surprised if no other platform could do something like this. I hope that it answers your question :D
Thank you! Don't hesitate to ask if you need further info or if you find an issue :D |
@ArchercatNEO sorry for the late response, I forgot to answer 😅 Thank you for looking into all remaining WSI protocols! So, let's take a look. We definitely need to also add I'm not really sure that we need to add Regarding |
|
I had a question about the additional boilerplate we're adding. The real compositor will report all interfaces it supports with an interface name, id and a version; even those we do not make use of. Since it is up to the client to have the full interface to check against shouldn't it be possible to just keep track of all protocols supported by the real compositor in an array and simply relay all of them? This way we intercept the few protocols we care about and can just forward all the ones we don't to the client. |
Thank you for the detailed answer ! |
|
Thanks! Fantastic work! |
|
Thank you @deralmas ! a lot of work ! |
|
Yay yay yay! Super excited to try the new changes! Thanks so much for all the amazing work! |
|
Holy shit we're merged! Congrats and thanks a bunch @deralmas! |
| struct xdg_toplevel *toplevel = ws->xdg_toplevel; | ||
|
|
||
| if (toplevel == nullptr && ws->libdecor_frame) { | ||
| toplevel = libdecor_frame_get_xdg_toplevel(ws->libdecor_frame); | ||
| } | ||
|
|
||
| ERR_FAIL_NULL_V(toplevel, ERR_CANT_CREATE); |
There was a problem hiding this comment.
We're using libdecor without an #ifdef LIBDECOR_ENABLED flag here.
There was a problem hiding this comment.
Oops, a classic mistake of mine, thank you for catching that 😅
|
Hi @ndbn thank you for reporting this compile errror. Dunno why no-one caught this warning before, warnings in general seem somewhat flaky, in my experience 😅 It makes sense, as both branches only contain the debug macro, which gets stubbed out with an I'll make a PR soon. |
|
This was raised in RC as well after the PR was merged |
|
Oh, sorry about that. I completely missed it, or maybe just forgot about it 😅 |
screenrecording-2025-12-01_23-18-16.mp4(Note: The video shows the correct behavoir first with X11, then I switch over to Wayland to show what's happening.) Unfortunately I'm getting some really strange menu behavior with the latest dev5 build that just went live when switching to Wayland. The dropdown menus are offset awkwardly. screenrecording-2025-12-01_23-19-47.mp4After playing around, it looks like after you click to show the menu, if you then click again to close the menu, it shifts back into place momentarily (although that might just be purely visual; I tried to see if I could quickly hover over it but no go). When things are awkwardly offset like this, my immediate thought goes to the fact that I'm using fractional scaling for my monitors (running 1440p monitors at 1.25 scaling). Not sure if it's related at all, but just throwing that out there since it's often the case when I see things offset like this. (But in good news, the embedded editor for Wayland is working!) Running the CachyOS with Wayland + Hyprland + Nvidia RTX 5090. |
|
Sorry if this is the wrong place for this comment, but from a user perspective I find it weird that this requires "single window mode" to be off when it appears to not use a second window (unlike what I've been forced to use on Linux for the last year where the game opens in a new window). I find this problematic not just because it's illogical (from a UX perspective) but because window sizes seem to be all over the place in Godot with Wayland and display scaling (whereas in single window mode, this was not an issue). |
|
Hi @eobet. Nothing stops us from embedding in single-window mode, technically. Actually, nothing technically stops us from embedding in single-window mode on X11/Windows too. Originally (before Wayland embedding), it was enabled in single-window mode too, but it got disabled in #101936. The issue is, in both cases, that the embedded game covers popups and the like. While we don't use a different window, we use a different window "layer" ( We use a subsurface to avoid reading and re-compositing everything, which would be a lot more complex, as mentioned in this PR description.
This is a bug, and something that will be fixed. There are various interconnected things at play, but I've got a workaround ready, and improvements to the engine API are in the works. You can track #110643 and perhaps give its linked PR a shot. Please note that the Wayland backend is marked as experimental and is still not polished as we'd like. That's why it's currently hidden behind an opt-in switch. Feel free to ask if you've got further questions, I'm happy to help. |
|
@deralmas: FYI I've taken your idea and turned it into a rust crate: https://lore.freedesktop.org/wayland-devel/CAHijbEUuMG9-dCPFrDRMrEzB60YgCbjdeRDgUZaZV3JyMfJ-0w@mail.gmail.com/T/#u Just wanted to let you know that I thought this was pretty neat. |
|
@mahkoh That's wonderful! Thank you for letting me know, I'll check it out. It sounds very nifty, especially with all the utilities you built around it.
Thank you, I'm really glad :D |
|
Hey did you ever get this feature to work on Hyprland, Niri or other tiling compositors? I already set both my editor and game to Wayland, but I still can't get this embedded window feature to work. |
|
@musjj Working quite fine for me on v4.6.1-rc1. This is on Niri. Be sure to uncheck the option I have highlighted.
|
|
Oh wow thanks that works!!! |
|
I'm curious if @deralmas would be interested in splitting off the Wayland interception code into a separate library? I'd like to use the work to do almost this exact thing for my own use cases, and having a C/C++ implementation (Rust is too annoying for me to use with this) would help a lot. |
|
Hi @orowith2os, yes, I'm definitely interested in turning this into a library! Please note that currently it's pretty hacky (see the PR description for more context), uses a lot of internal (but public) libwayland types ( If you're working on something right now, it might be easier to bundle the required Godot templates and use Please feel free to contact me directly if you need anything :D |







As per tradition, I summarized this description and updated it with all the new developments ever since I opened this PR. If you want to see the original, check out the edit history.
This PR implements game embedding on Wayland, using a novel experimental approach.
The code is basically feature complete, with the exception of tablet support, due to various quirks.
Testing
Obviously, the editor must be running with the
Prefer Waylandoption on in the editor settings.(Previous iterations of this PR required the project to specify the Wayland driver too, this is not the case anymore)
When debugging, you can uncomment
#define WAYLAND_EMBED_DEBUG_LOGS_ENABLED, which will completely blast your terminal with a lot of information, ranging from hex dumps to various debug notes. Because of this, whenever it is defined I recommendteeing the whole thing to a file. I usually run it like this:These logs are extremely verbose on purpose and, if you need to trim them down when sharing, please keep a full copy as I might need more data later.
That said, please be aware that: as with any protocol dump, these logs contain every event, including key presses sent to the editor/game (not the whole OS), just like
WAYLAND_DEBUG=1. I'm open to receiving logs privately if that makes you feel safer.How it works
This code acts as a shim between the user's compositor and the editor (templates and the project managers skip it for stability). It intercepts all Wayland messages and manipulates them to achieve things not normally possible such as redirecting a window to a subsurface, all without modifying the embedded client. This shim is referred as the "embedder" in the code.
The embedding client can access a "fake" Wayland global offered by the shim with various methods to handle all embedded clients.
In practice it "merges" multiple clients into one single connection, which has various pro and cons. I'll explain below why I chose this approach.
Additionally I tried to make the code as "automatic" as possible; In most instances all it takes is to make the embedder aware of an interface and it takes care of any requests through a generic handler.
I'll document the approach properly at least after this PR gets ready but here's a very quick and rambly summary of what comes to mind:
Details
This is basically a compositor without all the actual logic (except when we need to emulate stuff for stuff like fake windows obviously). The first client to connect is designated as the "main" client, which creates actual windows, while all new clients get embedded as soon as they make a new toplevel by intercepting the request. Everything else still gets created (buffers, etc.), which means that we can simply take the buffer that was meant for a toplevel and place it into a subsurface. We need to emulate just enough of a toplevel to keep the client happy and both the compositor and the client will do all the heavy lifting for us.
The most basic building block in this project is that there are various "ID spaces": since we're handling multiple clients, and the Wayland protocol enforces reusing IDs (with an assertion in libwayland), we need a "global" ID space (with all the "real" objects from the actual compositor) and a per-client "local" ID space, with relative mappings between both.
I made sure to add plenty of utility methods and some wrappers to hopefully make handling them both easier.
We also need to manage the real compositor's global objects (what I call "registry objects" to disambiguate) since we offer the control object (we already have an IPC after all, why reinvent the wheel? :P ).
The most annoying part was a certain mechanism I had to implement for compatibility with older compositors because it looks like you couldn't cleanup certain objects (mainly core registry globals) until relatively recently.
Thus a compatibility mechanism was born: "registry globals instances", basically if we detect that an object does not have a destructor method we instance it once and reuse it by creating fake objects. It works surprisingly well although it requires some extra state tracking for some specific objects.
It's not implemented perfectly (there are some missing edge cases) but as all globals now implement destructor methods hopefully it should kick in less and less over time.
It definitely adds complexity and in all honestly I should've considered just disabling the feature on old compositors but since I was still experimenting I did not understand fully the issue yet 😅
How it's written
It started from a standalone C prototype which got grafted into the Godot codebase. I refactored it extensively since then to follow Godot's style and data structures. If something is weird, that's probably why.
This code went through lots of iterations due to its experimental nature. I'm not super proud about how the data structures are laid out and the names are kinda eh. I'll keep improving it as much as possible but I don't expect it to become perfect by any means. There are a lot of moving parts and stuff works, so I'm not going to break it if I'm not forced to :P
I plan to eventually create a third-party library that implements this approach or at the very least to fully document the current architecture. Hopefully that will give further insights into simplifying it further.
There are also some naively written parts and dumb things in general but luckily performance seems to not be an issue: #107435 (comment)
Why this approach
The "canonical" approach for Wayland embedding would be to implement a more or less full fledged compositor1, usually with a library like wlroots.
I initially looked into it but it looks like all compositor libraries (understandably) expect that you will be running them under less capable environments (DRM/libinput, X11, older compositors...). Thus we would need to normally re-implement basically every single event handler as a pass-through, so I started considering handling the issue differently.
The advantages overall are very interesting:
Maintainability: the idea is that most Wayland protocols can simply "pass through" so, by introspecting into the generated headers, all you need to do to "implement" a protocol is adding its static interface pointer into a list.
Size and simplicity: the actual embedder is substantially smaller than your standard compositor, comments and all. By doing our trick, we get a lot of stuff "for free".
Integration: at least wlroots renders everything on a single buffer (I assume all compositor libraries do). We instead simply "trick" the secondary clients into rendering onto a subsurface. This means that we don't need a renderer and most importantly that popups and other extra surfaces can go outside of the embedded window, which I think is a huge plus.
Rendering performance?: I'm not sure how to verify this but I suppose that because we're not doing an extra step(?) of rendering and simply redirecting the embedded client to different surfaces that there should be minimal overhead.
There are also disadvantages I should point out:
Everything runs under the embedder, editor included. This means that, if the embedded game does "naughty things" and the real compositor disconnects it, it will actually kick both the editor and the game. This is not critical as we trust the client and could do the kicking ourselves but that's note-worthy. Also note that the embedder is enabled only with the editor, the project manager and non-embedded projects will run "on bare metal" right away.
This only works for Wayland clients. Honestly, this is not that big of a deal because there are drop-in solutions like xwayland-satellite that can handle xwayland for us if we ever feel the need. Some compositors like niri went with the same approach: https://github.com/YaLTeR/niri/wiki/Xwayland Update: This is even less of an issue now as we simply pass
--display-driver waylandto the embedded game.You need to register every protocol used, even low level ones. We thus need to add and track extra XML definitions for stuff used by the WSI. It's one line in the actual code but still, worthy of note.
You need to register every protocol used. Yes, this makes the barrier of entry to contribution ever so slightly high but if the protocol does not touch the few things the embedder "overrides" it should be just a line in the embedder code.
Yea.
Old conclusion (It felt wrong to change it)
The original conclusion is outdated in some ways but I'm keeping it because it sums exactly the feelings I had while working on this thing. I don't want to get personal so I'll limit myself by saying that those annoying times for unrelated reasons and I went pretty darn emotional for a PR description. I can't write normal PRs for the life of me, sorry xD
That said, as per the original text, this was way more complex than I thought but I still genuinely believe that this makes more sense than using a compositor library like wlroots. In both cases you're writing a compositor and that's a hard thing to do, there's no way around that.
Hopefully I'm right :P
Footnotes
To be completely fair, another option mentioned in the Wayland docs themselves would be to pass the game buffer out-of-band like with the MacOS embedder. I personally think that in our particular case, given Wayland's architecture, the approach I'm implementing is the right choice. ↩