Rework of CEF integration to support broker-mode and accelerated copy#334
Rework of CEF integration to support broker-mode and accelerated copy#334RyeMutt wants to merge 10 commits into
Conversation
Add ALCefDaemonEnabled / ALCefSandbox / ALCefAcceleratedPaint settings
(all off by default, persisted) to gate the CEF media rework, and wrap
the per-surface GL allocate + CPU->GPU upload in doMediaTexUpdate() in a
named "media texUpload" Tracy zone. That upload is exactly the cost the
accelerated-paint shared-texture path removes, so isolating it gives a
before/after baseline.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CEF daemon Phase 1: split dullahan into shared runtime + per-browser impl
Update dullahan submodule to 961ed95: hoist the process-global CEF
runtime (CefApp, CefInitialize/Shutdown, command-line flags, message
pump) into a reference-counted dullahan_runtime shared by every browser
in the process, leaving dullahan_impl as one offscreen browser. This
lets multiple browsers share a single CEF runtime - the foundation for
the shared tab-manager daemon. Public dullahan API unchanged; dullahan +
media_plugin_cef build and link on Windows.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CEF daemon Phase 1: opengl-example two-browser proof harness
Update dullahan submodule to 004132a: the opengl-example now opens two
browser tabs that share a single CEF runtime (switchable via Tabs menu /
Ctrl+1/2), exercising the dullahan_runtime split at runtime - the
standalone proof that multiple CEF browsers coexist in one process.
Builds and links on Windows.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CEF daemon Phase 2a: static plugin entry-point path in LLPluginInstance
Add the mechanism a dedicated single-plugin host needs to call its
statically-linked plugin directly instead of dlopen()ing a plugin
library. LLPluginInstance::setStaticInitFunction() registers a
LLPluginInitEntryPoint; when set, load() calls it and skips the
boost::dll dlopen/dlsym (and the Windows cwd hack) - ignoring the
plugin dir/file. This avoids dlopen of large TLS-using libraries (CEF on
Linux, where dlopen exhausts the static TLS block) and is a prerequisite
for the Windows sandbox, which requires the host and its sub-processes
to be a single executable image.
slplugin's main now registers ll_get_static_plugin_init(); the generic
SLPlugin links slplugin_generic.cpp which returns NULL, so its behaviour
is unchanged (still dlopen the plugin named in load_plugin). A dedicated
host (SLPluginCEF, next) will link a definition returning its plugin's
&LLPluginInitEntryPoint.
No behaviour change for the existing SLPlugin. Builds and links on
Windows (llplugin + SLPlugin).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CEF daemon Phase 2b: SLPluginCEF dedicated static-link host
Build a dedicated host executable that statically links the CEF media
plugin (and CEF itself) instead of dlopen()ing media_plugin_cef:
- compile the plugin sources once into an object library
(media_plugin_cef_objs) consumed by both the existing loadable
media_plugin_cef module (legacy dlopen path, unchanged) and the new
host
- SLPluginCEF = the generic slplugin host driver + slplugin_cef.cpp
(returns &LLPluginInitEntryPoint) + the plugin objects, linking
media_plugin_base/dullahan/ll::cef/llplugin/llmessage/llcommon. Via
the Phase 2a static path, LLPluginInstance::load() calls the linked
entry point directly - no dlopen of libcef.
On Windows the host is staged into newview/.../llplugin next to
libcef.dll so the statically-referenced CEF runtime resolves from its
own directory. This removes the Linux static-TLS-block crash (libcef is
now link-time, not dlopen'd) and gives the Windows sandbox the
single-image host it needs.
Still one process per media instance; only the host shape changes. The
viewer does not launch SLPluginCEF yet (launcher selection is the next
step). Builds and links on Windows (media_plugin_cef DLL + SLPluginCEF).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CEF daemon Phase 2b: launch SLPluginCEF for CEF media when enabled
Add ALCefDedicatedHost (Boolean, off) and wire LLViewerMediaImpl::
newSourceFromMediaType to launch the SLPluginCEF host instead of the
generic SLPlugin for media_plugin_cef when it is set. The dedicated host
statically links the CEF plugin, so it avoids dlopen of libcef (the
Linux static-TLS crash) and is the single-image host the Windows sandbox
needs. plugin_name is still passed and validated; the static host
ignores it. Falls back to the generic launcher (with a warning) if
SLPluginCEF is not present, so enabling the flag without the host built
degrades gracefully rather than breaking media.
Still one process per media instance. Opt-in and restart-gated so the
existing dlopen path stays the default until verified. llviewermedia.cpp
compiles (single-file).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the Windows SLPluginCEF host a CEF bootstrap client so the Chromium
sandbox can be enabled for browser sub-processes.
- slplugin.cpp: factor the plugin<->parent host message loop out of the
platform entry points into slplugin_run(port) so the bootstrap entry
can reuse it.
- media_plugins/cef/slplugin_cef_bootstrap.cpp: RunWinMain (exported
via CEF_BOOTSTRAP_EXPORT) - CefExecuteProcess for sub-processes (same
image, as the sandbox requires), then dullahan::setSandboxInfo() +
slplugin_run() for the browser process. SLPLUGIN_CEF_NO_SANDBOX env
var disables the sandbox for debugging.
- CMake: on Windows SLPluginCEF is now a DLL (RunWinMain); ship CEF's
bootstrap.exe renamed to SLPluginCEF.exe (matching base name loads
SLPluginCEF.dll). Stays an executable on Linux/macOS.
- llviewermedia: ALCefSandbox also routes CEF media to SLPluginCEF (the
sandbox requires the dedicated host).
- Bumps dullahan submodule to 5bfd90b (sandbox_info plumbing + the
example bootstrap client).
cef_sandbox.lib is not linked (M138+ ships it only inside the bootstrap
executables), so no static-CRT requirement. Builds on Windows:
SLPluginCEF.dll (RunWinMain exported) + SLPluginCEF.exe, media_plugin_cef
DLL, llviewermedia. Sandbox actually engaging needs runtime verification.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the shared tab-manager daemon's host side: one process serving N
plugin tabs. slplugin_daemon_run() listens on a control port (written to
a rendezvous file for discover-or-spawn), serves the first tab from the
launch-arg port, and spawns an LLPluginProcessChild for each later parent
that registers its listen port on the control channel. Every tab lives
in this one process, so they transparently share a single CEF runtime
(dullahan_runtime, Phase 1) - the point of the daemon. Exits after
DAEMON_IDLE_TIMEOUT with no live tabs (keeps the runtime warm across
brief gaps).
SLPluginCEF's bootstrap entry now parses "<port> --daemon <rendezvous>"
and routes to slplugin_daemon_run vs the single-tab slplugin_run. The
daemon driver is plugin-agnostic (lives in llplugin/slplugin) and is
linked only into SLPluginCEF for now.
Builds and links on Windows. This is the daemon SIDE only; the
viewer-side discover-or-spawn that drives it (LLPluginProcessParent
connect-or-launch + liveness/crash recovery) is the next step and needs
runtime iteration - see the commit body of the follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CEF daemon Phase 4b step 1: mUseDaemon plumb + daemon-aware liveness
Lay the inert groundwork for the viewer-side discover-or-spawn without
touching the launch path yet.
- LLPluginProcessParent: setUseDaemon()/getUseDaemon() + mUseDaemon
flag. pluginLockedUpOrQuit() no longer treats a null mProcess as
"child exited" when mUseDaemon is set - in daemon mode this parent
talks to a shared host it does not own, so liveness comes from the
socket/heartbeat (pluginLockedUp) instead. (STATE_EXITING reaching
CLEANUP on a null process is already correct - nothing to wait on.)
- LLPluginClassMedia: setUseDaemon() forwarding to the parent at
create time.
- llviewermedia: set it for CEF media when ALCefDaemonEnabled.
This is deliberately inert: the launch path still creates a process, so
mProcess stays non-null and the new branch never fires. With the flag
off it is a no-op. It only becomes live once step 2 (discover-or-spawn)
can leave mProcess null. Builds: llplugin + llviewermedia compile.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CEF daemon Phase 4b step 2: viewer-side discover-or-spawn
Wire LLPluginProcessParent to the shared daemon. At STATE_LISTENING,
when mUseDaemon is set, instead of launching a private process it:
- reads the daemon control port from the per-plugin-dir rendezvous
file and, if a daemon is reachable, registers this tab by connecting
to the control port and sending its listen port (the daemon connects
back -> the existing accept() path, mProcess stays null);
- else takes an atomic spawn lock (stealing a stale one) and launches
the daemon DETACHED (autokill=false, attached=false) with
"<port> --daemon <rendezvous>", so it outlives this parent and is
shared by later tabs - no parent owns it (otherwise closing one
tab's media would kill the whole daemon). It serves the spawner as
its first tab, then connects back like any registration;
- else (another parent is launching) waits and retries next idle.
The daemon removes the spawn lock once it has published the rendezvous.
llviewermedia routes CEF media to SLPluginCEF when ALCefDaemonEnabled
too (the daemon requires the dedicated host; the generic SLPlugin can't
parse --daemon).
All gated by mUseDaemon/ALCefDaemonEnabled - the default path is
untouched. Builds: llplugin + SLPluginCEF + llviewermedia compile/link.
The concurrent lifecycle (spawn race, register-vs-spawn timing, daemon
liveness) needs runtime verification.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CEF daemon: rendezvous path must be caller-supplied + user-writable
daemonRendezvousPath() derived the rendezvous/lock path from mPluginDir
(the install/plugin dir), which is read-only on a packaged build - the
daemon could not publish its control port and no spawn lock could be
taken. The low-level llplugin layer also has no business inventing user
paths.
Make it an explicit, caller-supplied path: setUseDaemon() now takes a
rendezvous_path; LLPluginProcessParent stores it and the daemon branch
is skipped if it is empty. The viewer supplies a user-writable path in
the logs dir (LL_PATH_LOGS) carrying the viewer PID, so it is writable on
an installed build and separate viewer instances do not share a daemon.
Plumbed viewer -> LLPluginClassMedia -> LLPluginProcessParent.
Builds: llplugin + llviewermedia compile.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CEF daemon: dedicated host never falls back to dullahan_host
Fix daemon (and single-tab) SLPluginCEF launching dullahan_host.exe for
CEF sub-processes when the sandbox is not active. The dedicated host
dispatches its own sub-processes (CEF re-launches SLPluginCEF.exe ->
RunWinMain -> CefExecuteProcess), so dullahan_host is never needed - but
dullahan only skipped it when sandboxed.
The bootstrap entry now calls dullahan::setHostHandlesSubprocesses(true)
alongside setSandboxInfo, so CEF re-launches the SLPluginCEF image for
sub-processes whether or not the sandbox engaged. Bumps dullahan
submodule to e9bc2a8 (the decoupling + example). Builds on Windows.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CEF daemon: launch daemon in the viewer job object (fixes sandbox)
The Windows CEF sandbox worked for the per-process SLPluginCEF host but
failed for the daemon. The only launch difference was the job object:
the daemon was spawned with autokill=false, putting it OUTSIDE the
viewer's job object that the working per-process host (autokill=true)
runs in - and the sandbox broker requires it.
autokill (the APR job-object association) and attached (kill the child
when this LLProcess handle is destroyed) had been conflated. Keep
autokill at its default (true) so the daemon joins the job and dies with
the viewer, and set only attached=false so discarding the fire-and-forget
handle does not kill the daemon (it must outlive the spawning tab).
Builds: llplugin.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fix cef daemon build-link to alchemy-bin and packaging
CEF daemon: register static plugin in daemon host (fixes sandbox)
The daemon ran unsandboxed (and spawned dullahan_host) because its tabs
dlopen()ed media_plugin_cef.dll instead of using the statically-linked
plugin. dullahan is statically linked, so the dlopen'd DLL carries a
SECOND dullahan_runtime - a different instance than the one the bootstrap
host set the sandbox info / host-handles-subprocesses flags on. That tab
runtime saw mSandboxInfo=NULL, so it disabled the sandbox and used the
dullahan_host helper.
slplugin_run() (the single-tab host) calls
LLPluginInstance::setStaticInitFunction() so load() uses the linked
plugin entry directly; slplugin_daemon_run() did not. Add the same call
so daemon tabs run in the host's own dullahan_runtime - the one with the
sandbox info.
Builds: SLPluginCEF.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CEF daemon: clean up the rendezvous file on viewer shutdown
The daemon runs in the viewer's job object, so on viewer exit it is
force-killed and never reaches the std::remove(rendezvous) at the end of
slplugin_daemon_run (that only runs on the idle-timeout path). Because
the rendezvous filename carries the viewer PID, each run left a distinct
orphan in the logs dir.
Delete it (and any stale spawn lock) from ~LLViewerMedia(), which runs at
shutdown while gDirUtilp is still valid - before the daemon is killed.
Factor the path into a shared helper so the create site
(newSourceFromMediaType) and the cleanup agree. No-op when daemon mode
was unused.
Builds: llviewermedia compiles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… daemon In daemon mode the parent owns no host process (mProcess is null), so STATE_EXITING fell straight through to STATE_CLEANUP and slammed the socket shut while the tab's browser was still live in the shared CEF runtime. The tab then lost its parent socket before processing shutdown_plugin, dropped into STATE_ERROR (which never nulls mInstance), and was reaped by the daemon - whose ~LLPluginProcessChild calls exit(0) when mInstance is set, taking down every other tab. Net effect: open two web instances, close one, the whole SLPluginCEF.exe disappears. Fix the teardown end to end: - Parent: in daemon mode STATE_EXITING now waits for the tab to finish its graceful unload and drop its end of the socket (EOF/error) or for the lockup timeout, instead of tearing the socket down immediately. - Child: a lost parent socket in daemon mode routes to the normal graceful unload (STATE_SHUTDOWNREQ) so the browser closes cleanly in the shared runtime before the tab is reaped. - Child: ~LLPluginProcessChild no longer exit(0)s in daemon mode - daemon plugins are statically linked (no DSO unload to lock up), so it deletes the instance directly and lets the other tabs live. - Daemon marks every tab with setDaemonMode(true). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> CEF daemon: recover media after a daemon crash instead of failing permanently A shared-daemon crash drops every tab's socket at once, so each media's LLPluginProcessParent reports the plugin dead (MEDIA_EVENT_PLUGIN_FAILED) and the impl latched mMediaSourceFailed = true -> isForcedUnloaded() pins it to PRIORITY_UNLOADED forever. Net effect of one daemon crash: all CEF media go permanently dark, a regression from per-process hosting where a crash only loses one media. For daemon-mode CEF media, schedule a bounded, backed-off re-init on failure (DAEMON_RECOVERY_MAX_ATTEMPTS, base*attempt up to a ceiling) rather than failing permanently. When the backoff elapses, update() clears the failure latch and the normal load path recreates the source - the first impl to do so wins the spawn lock and respawns the daemon (discover-or-spawn), the rest reconnect as fresh tabs. A completed navigate resets the attempt counter, so the cap only trips on a daemon that keeps crashing on launch (no respawn storm). Non-daemon plugins are untouched (gated on LLPluginClassMedia::getUseDaemon()). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> CEF daemon: hard tab ceiling as a runaway backstop PluginInstancesTotal -> mMaxIntances is the primary tab cap (excess media stay PRIORITY_UNLOADED, so they never create a source or a tab). Add a generous daemon-side ceiling (DAEMON_MAX_TABS) so a buggy or hostile parent can't drive one process to spawn unbounded browsers regardless of the viewer-side accounting; over the ceiling, registrations are dropped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> CEF daemon: cut idle CPU by driving the external pump from CEF's schedule Bump dullahan submodule: dullahan_runtime now implements OnScheduleMessagePumpWork so CefDoMessageLoopWork() runs only when CEF asks for it, instead of on every host idle tick. Removes the constant idle CPU draw of the fixed-cadence pump and collapses N daemon tabs' per-frame pumps into at most one shared pump. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> CEF daemon: fix resize blanking via external-pump watchdog Bump dullahan submodule: the OnScheduleMessagePumpWork-gated pump could let a needed repaint go unscheduled after a resize (CEF coalesces its external-pump notifications), leaving the media surface blank with no event to recover it. dullahan_runtime now watchdogs the pump so it can never stay idle longer than 100ms, bounding recovery while keeping the idle-CPU win. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> CEF daemon: stop per-tab 50ms stall from the browser-init settle hack Bump dullahan submodule: the post-CefInitialize "settle" pump (a 50ms blocking sleep that let the shared global request context finish initializing) ran in every browser's init(). In the daemon that froze the host thread - and so every other tab - for 50ms on each new tab. It now runs once per process in dullahan_runtime::acquire(); later daemon tabs skip it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> CEF daemon: fix login-time re-CefInitialize crash (persistent runtime) When the login-screen web surface transitioned away, the daemon's CEF browser count hit zero, release() called CefShutdown(), and the next web surface crashed in CefInitialize() (CEF cannot be re-initialized in a process). Bump dullahan submodule for the persistent-runtime mode, and have the SLPluginCEF bootstrap opt in (setPersistentRuntime(true)) and shut CEF down once (shutdownRuntime()) after the host loop returns. CEF now stays up across zero-browser gaps and is torn down exactly once at process exit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump dullahan submodule: adds the accelerated_paint setting + the OnAcceleratedPaint -> onAcceleratedPaint callback path (GPU shared-texture handle, format, coded size), shared_texture_enabled on the window, and GPU-compositing-on in that mode. Default off; no behavior change yet. Foundation for zero-copy media textures. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> CEF daemon: Phase 5b - opengl-example zero-copy paint proof (Win) Bump dullahan submodule: the opengl-example now drives OnAcceleratedPaint through a D3D11 + WGL_NV_DX_interop2 helper to alias CEF's GPU shared texture into the GL quad with no CPU copy, with a CPU-path fallback (DULLAHAN_FORCE_CPU_PAINT) for A/B. Standalone proof before wiring the cross-process transport + viewer media-texture import. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> CEF daemon: Phase 5b - diagnostics for white-screen accelerated paint Bump dullahan submodule: debugger-visible logging in the opengl-example interop to locate why the accelerated path renders white (callback firing? shared-resource open? register? lock?). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> CEF daemon: Phase 5b - fix accelerated paint NT-handle register failure Bump dullahan submodule: WGL_NV_DX_interop2 can't register Chromium's NT-handle shared texture directly (wglDXRegisterObjectNV ERROR_OPEN_FAILED -> white quad). Open it, GPU-copy (CopyResource) into an own-device intermediate texture that registers cleanly, and sample that. Still no CPU readback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> CEF daemon: Phase 5b - fix accelerated-paint page-change flicker Bump dullahan submodule: the OnAcceleratedPaint shared-texture pool (no keyed mutex) was being read stale - cached opens could point at a remapped resource and the GPU copy wasn't waited on before CEF recycled the source. Open fresh each frame + wait on an event query after CopyResource. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> CEF daemon: Phase 5c - carry the GPU shared-texture handle to the viewer The media IPC only transported CPU pixels via shared memory. Add a parallel path that hands the viewer a GPU shared-texture handle for zero-copy paint. - LLPluginClassMedia: setUseAcceleratedPaint() + the viewer's pid go in the media "init" message; a new "accelerated_paint" message delivers the duplicated handle (decimal string so the full 64-bit value survives) plus cef_color_type format and coded size, exposed via getAcceleratedPaint*/ takeAcceleratedPaintHandle(). - media_plugin_cef: when accelerated_paint is requested, register setOnAcceleratedPaintCallback; on each frame OpenProcess(viewer pid) once and DuplicateHandle the CEF shared texture into the viewer (the handle is only valid during the callback), then send accelerated_paint. The browser host is unsandboxed (broker), so the cross-process dup is allowed. - llviewermedia: enable it from ALCefAcceleratedPaint for CEF media; update() logs the first arriving handle (transport checkpoint) and closes each so the per-frame duplicates don't leak. Transport only - the viewer does not yet import/bind the texture (5d). Builds on Windows; runtime checkpoint is the "accelerated paint frame received" log. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> CEF daemon: Phase 5d (producer) - keyed-mutex stable texture, drop per-frame dup 5c duplicated a CEF shared-texture handle into the viewer EVERY frame (plus a per-frame OpenSharedResource on the viewer). Replace that with a producer-side stable texture so the handle crosses the boundary only once per size. media_plugin_cef gains CefAccelProducer (Windows): a D3D11 device + one persistent shared texture created with NT-handle + keyed mutex. Each accelerated-paint frame it opens CEF's pooled texture and CopyResources it into the stable texture under the mutex (single key 0 = mutual exclusion, which also gives the cross-process/cross-device GPU sync). The stable texture's NT handle is DuplicateHandle'd into the viewer only when it is (re)created; per-frame messages carry handle "0" = "same texture, new frame". Single-key (not 0/1 ping-pong) so the producer never deadlocks before the consumer exists. LLPluginClassMedia keeps the last real handle (a "0" ping no longer clears it) and always marks the frame dirty. CMake links d3d11/dxgi into media_plugin_cef + SLPluginCEF. Producer only; the viewer still just logs+closes the handle (5c checkpoint) - it opens/binds the stable texture next. Builds on Windows. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> CEF daemon: Phase 5d (consumer) - zero-copy CEF media in-world The viewer now imports the plugin's shared GPU texture and blits it into the media texture with no CPU upload (replacing the setSubImage path for accelerated CEF media). - new LLCEFAccelInterop (newview, Windows): D3D11 device + WGL_NV_DX_interop2. Opens the plugin's keyed-mutex stable texture (delivered once per size), copies it into an own-device intermediate under the mutex (single key 0 = mutual exclusion + cross-process sync), GL-registers that intermediate (the opened cross-device NT-handle texture can't be registered directly), and blits it into the media GL texture. - the blit uses glBlitFramebuffer with an inverted destination rectangle: a framebuffer read samples the interop texture in the correct channel order (no BGRA swizzle needed) and the flip turns CEF's top-down texture bottom-up for the viewer. - LLViewerMediaImpl::update() runs the accelerated path on the main thread and skips the shm/upload path; the interop is created lazily and torn down with the impl. - media_plugin_cef requests an RGBA8 media texture in accelerated mode. Builds on Windows (new files + d3d11/dxgi wired into newview). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> CEF daemon: Phase 5d - don't lose the stable handle if the consumer isn't ready The plugin sends its stable shared-texture handle only once per size, but the consumer consumed (zeroed) it before confirming the interop was ready - so if interop init failed on that frame the handle was lost and the media stuck until a resize. Make the handle persistent on the viewer side (getAcceleratedPaintHandle / clearAcceleratedPaintDirty replace takeAcceleratedPaintHandle). The consumer brings up the interop first, then compares the persistent handle against the one it last bound and only advances its bound-handle on a SUCCESSFUL setStableTexture - so a transient failure retries with the same handle next frame. Reset the bound handle on destroyMediaSource so a recreated source rebinds fresh even if the handle value is reused. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> CEF daemon: Phase 5e - dullahan macOS/Linux accelerated-paint plumbing Bump dullahan submodule: OnAcceleratedPaint now yields the macOS IOSurfaceRef (via the existing void* callback) and the Linux dma-buf (new fd-based callback), alongside the Windows handle. Foundation for the macOS/Linux zero-copy paths; written blind from the CEF API (no Linux/mac CEF headers or build here), unverified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> CEF daemon: Phase 5e - macOS zero-copy paint (IOSurface), untested macOS path end-to-end. The accelerated frame is an IOSurface, which is shareable by its global IOSurfaceID (an integer) - no handle duplication or producer texture needed: - media_plugin_cef sends IOSurfaceGetID over the existing accelerated_paint message each frame (the surface is pooled, so the id can change). - LLCEFAccelInterop gains a macOS impl: IOSurfaceLookup -> bind to a GL_TEXTURE_RECTANGLE via CGLTexImageIOSurface2D, then the same flipped glBlitFramebuffer into the media texture as the Windows path. Windows still builds (the macOS branch is #elif-guarded). The macOS code is written blind (no macOS build/test here) and unverified. Linux dma-buf transport + import still to come. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> CEF daemon: Phase 5e - Linux zero-copy paint (dma-buf + EGL), untested Linux path end-to-end. CEF delivers the accelerated frame as a dma-buf, so: - media_plugin_cef registers the dma-buf callback on Linux; per frame it dup()s the fd (CEF's is valid only during the callback), keeps a small ring alive, and sends the fd number + its pid + plane layout (stride/offset/DRM modifier/format) over the accelerated_paint message. The viewer re-opens the fd via /proc/<pid>/fd - no SCM_RIGHTS side channel needed. - LLPluginClassMedia carries the dma-buf layout fields (0 on Windows/macOS); LLCEFAccelInterop::setStableTexture gained them as trailing args. - LLCEFAccelInterop Linux impl: open /proc/<src_pid>/fd/<fd>, import it with eglCreateImageKHR(EGL_LINUX_DMA_BUF_EXT) -> glEGLImageTargetTexture2DOES, then the same flipped glBlitFramebuffer into the media texture. Windows still builds (the Linux branch is #elif-guarded). Written blind (no Linux build/test here) and unverified - notably it assumes the viewer's GL context is EGL (not GLX) for eglGetCurrentDisplay to work. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> CEF media: share one D3D11<->GL interop device instead of one per media impl Each LLCEFAccelInterop created its own D3D11 device + wglDXOpenDeviceNV and ad-hoc wglGetProcAddress'd the WGL_NV_DX_interop2 entry points, so N web surfaces meant N D3D devices and N interop devices. Centralize it: - the wglDX* entry points are now loaded in LLGLManager::initWGL alongside the other WGL extensions (declared in llglheaders.h, defined in llgl.cpp, gated on WGL_NV_DX_interop2). - LLDXHardware owns one D3D11 device + one wglDXOpenDeviceNV interop device for the whole process (initGLDXInterop / cleanupGLDXInterop). It is brought up once in LLWindowWin32::switchContext - main thread, render context current, WGL loaded - and torn down with the context. - LLCEFAccelInterop drops its private device/loader and uses the shared device, context and interop handle plus the global wglDX* pointers; per surface it still owns only its opened texture, intermediate, registration and FBOs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Build slplugin_daemon.cpp into the non-Windows SLPluginCEF and route the platform main() through a per-host ll_run_slplugin_host() hook: the generic SLPlugin serves a single connection, while the CEF host marks the runtime persistent and dispatches slplugin_daemon_run() when launched with --daemon (mirroring the Windows bootstrap). Make SLPlugin and SLPluginCEF proper macOS app bundles with their own Info.plist and bundle identifiers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump the example's glad feature from gl-api-21 to gl-api-41 for the 4.1 Core context, and advance the dullahan submodule to the 4.1 Core / macOS IOSurface zero-copy example. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Update dullahan: drop default framerate to 30 to reduce render load Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The viewer rendered media grey under accelerated paint on macOS: the plugin sent only the global IOSurfaceID and the viewer did IOSurfaceLookup, but CEF shares its accelerated-paint IOSurfaces between its GPU and browser processes via mach ports, not global ids, so a cross-process id lookup returns NULL and nothing is ever bound. Hand the surface over a mach channel instead: the viewer registers a bootstrap receive port named from its pid (LLCEFSurfaceReceiver); the plugin looks it up (host_pid is already in the init handshake) and mach_msg's an IOSurfaceCreateMachPort() right per frame, tagged with a new per-media accel_id so one receiver can demux many tabs. The viewer resolves it with IOSurfaceLookupFromMachPort and binds it as before. The receiver registers up front (not gated on a frame) since the plugin only produces once the port exists. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 49 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (35)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| if (LLFile::stat(lock_path, &st) == 0 && | ||
| (time(nullptr) - st.st_mtime) > LOCK_STALE_SECONDS) | ||
| { | ||
| LLFile::remove(lock_path); |
Description
Related Issues
Issue Link:
Checklist
Please ensure the following before requesting review:
Additional Notes