- Replace Buffer.concat on IPC rx path with a growing ring buffer (rxAppend/rxConsume)
- Replace setTimeout(flushSurfaceBuffers, 10) with setImmediate() to cut 10ms per frame
- Use OSR paint dirty rect for partial DamageBuffer updates instead of full-frame copy
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rendering: Skip the full bind/clear/draw/finish/submit cycle when
nothing visual has changed (no new Electron frames, no dirty Wayland
surfaces, no cursor movement). In the DRM path, also skip when no
screencopy or portal streams are active. Frame callbacks are always
sent unconditionally to prevent Wayland client stalls.
Screencopy: Remove the incorrect row-flip after glReadPixels in both
the wlr-screencopy fulfillment and portal stream readback paths.
Smithay already applies a Y-correction when rendering to GBM-backed
FBOs, so glReadPixels returns top-down data. The manual flip was
inverting an already-correct image, producing upside-down output.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The DRM event loop only woke for registered calloop sources (VBlank,
libinput, session). When the VBlank rendering cycle broke (transient
page-flip failure, timing edge), nothing woke the loop to restart it —
only input events (mouse movement) would un-stall rendering.
Three changes:
1. Register the Wayland display poll_fd as a calloop source so client
surface commits immediately wake the event loop and trigger
render_frame via the main callback.
2. Register the listening socket fd so new Wayland client connections
also wake the loop promptly.
3. Add a periodic timer (fires every frame_time ≈ 16ms) as a safety net
to restart stalled VBlank cycles even with no input or client
activity.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two critical fixes:
1. **Flickering (GL frame not finalized)**: render_frame was missing
the frame.finish() call that nested.rs has (line 688). Without it,
GL rendering commands are never flushed to the GBM BO, so the DRM
page flip scans out a buffer with stale/incomplete pixel data,
alternating between rendered and unrendered content every frame.
2. **IPC protocol corruption (EPIPE/ECONNRESET)**: Node's socket.write()
is async and buffers data in userspace. The native sendFd() call
(sendmsg with SCM_RIGHTS) goes directly to the kernel, racing ahead
of the buffered header+plane bytes. The compositor then reads the
1-byte SCM_RIGHTS dummy as the next message magic, hits 'unknown
magic', and the IPC reader thread exits — closing the socket (EPIPE).
Fix: use fs.writeSync() on the raw fd for header+plane data so it
reaches the kernel buffer before sendFd's sendmsg.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Three fixes:
1. **Flickering (every-other-frame)**: Swapchain slots were dropped
immediately after page_flip, setting acquired=false so the next
acquire() could return the same GBM BO currently being scanned out
by the CRTC. Added current_slot field to EventLoopData that holds
the submitted slot until the next frame replaces it, preventing
buffer reuse during scanout.
2. **Missing keyboard_key action**: The DRM codepath was missing the
'keyboard_key' custom action handler that nested.rs has. Added it
with evdev→XKB keycode conversion and keyboard.input() dispatch.
3. **Maximize alignment**: DRM maximize was clamping to size hints and
centering horizontally, while nested.rs uses full output width.
Aligned to match nested.rs behavior.
4. **z_order sorting**: Electron window rendering now sorts by z_order
to match nested.rs's chain+sort approach.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two critical bugs causing a black/off screen:
1. [[root]] window configs were spawned as Electron processes but never
added to config.windows. The render loop, window_mapped state, and
respawn logic all iterate config.windows — so root windows were
invisible. Fix: merge root entries (with fullscreen geometry and low
z_order) into config.windows before WoState::new(), so all subsystems
see them uniformly.
2. When the display manager (Ly) leaves the CRTC in a state Smithay
considers 'already committed' (matching mode/connectors),
commit_pending() returns false and only page_flip() is used.
page_flip without a prior modeset can 'succeed' but produce no
visible output because the CRTC was never properly activated with
a framebuffer. Fix: force a full modeset commit (ALLOW_MODESET) on
the very first frame (total_flips==0) regardless of commit_pending.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Three bugs addressed:
1. SO_RCVTIMEO(5s) on the IPC socket was never cleared after the HELLO
handshake. When the web page becomes static (no paint events for >5s),
the blocking read_exact in recv_message times out, the IPC reader thread
exits, and the socket is closed — causing ECONNRESET on the Electron
side and permanently stalling frame delivery. Fix: clear SO_RCVTIMEO
to zero (infinite) immediately after successful HELLO exchange.
2. If a VBlank event is lost or delayed (e.g. during DRM master transitions),
vblank_pending stays true and all subsequent render_frame calls bail out
permanently. Fix: if vblank_pending has been stuck for >500ms, force-clear
it with a warning so rendering can resume.
making it impossible to diagnose the black screen from logs. Fix: add
INFO-level logging for first render, first page flip, first texture
import, swapchain exhaustion on first frame, and vblank recovery events.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Three root causes prevented Electron frames from reaching the compositor:
1. --disable-gpu killed Chromium's rendering pipeline, preventing the
'paint' event from firing in offscreen mode. Replaced with
--use-gl=swiftshader so Chromium can composite internally while the
native wo_dmabuf module handles GPU buffer allocation separately.
2. DISPLAY=:0 with no X server caused Electron to fail silently on
pure Wayland sessions (Ly/logind). Replaced with
--ozone-platform=headless which lets Chromium's OSR pipeline work
without any display server connection.
3. WO_DRM_RENDER_NODE was never set, so the native wo_dmabuf.node
module always defaulted to /dev/dri/renderD128 regardless of which
GPU the compositor actually opened. Now resolved via sysfs
(card1 -> renderD128, card2 -> renderD129, etc.) and passed through
to Electron.
Also: retry EACCES/EPERM as transient during DRM device open (logind
session activation can lag behind libseat's is_active() flag on Ly).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>