Follow-up to #178, which lands multi-GPU (PRIME) support but skips blur on outputs driven by a secondary GPU. That's graceful degradation (secondary-output windows render without blur), documented in #178 as though smithay can't do it — but it can. This is a not-yet-implemented feature, not a structural limitation. niri implements exactly this today.
Mechanism
Effects run on the primary render GlesRenderer — obtained via MultiRenderer::as_mut(), exposed in-tree as as_gles_renderer() — the result is wrapped as a primary-GPU texture element, and MultiFrame::finish() PRIME-copies the composited frame to the scanout GPU. The target/scanout GPU's renderer is never touched.
Already wired in #178
- Cross-GPU frames composite on the primary via
gpu_manager.renderer(primary, target, fmt).
- The Kawase blur passes already run on the primary through
renderer.as_gles_renderer().
- The blurred result is already inserted as
GlesBridge(blur_elem), drawing via as_gles_frame() + record_bridged_damage — the same shape as niri's PrimaryGpuTextureRenderElement.
What remains
process_blur_requests renders the blur source (behind-content → bg_tex) through the frame renderer R, which on a cross-GPU frame binds on the target GPU while the blur passes read on the primary — two contexts that can't share the texture. That's why process_blur_requests early-returns on frame_is_cross_gpu. Route the source render onto the primary GlesRenderer consistently, then drop the gate (and reword the "not yet implemented" note in dev/docs/multi-gpu.md / blur.rs / CHANGELOG once it works).
Reference blueprint (niri)
src/render_helpers/{blur,effect_buffer,primary_gpu_texture,renderer}.rs, src/backend/tty.rs. Verified: niri ships dual-Kawase blur and runs it on secondary-GPU outputs with no primary/secondary branch; MultiRenderer::as_mut() returns the render (primary) GPU.
Follow-up to #178, which lands multi-GPU (PRIME) support but skips blur on outputs driven by a secondary GPU. That's graceful degradation (secondary-output windows render without blur), documented in #178 as though smithay can't do it — but it can. This is a not-yet-implemented feature, not a structural limitation. niri implements exactly this today.
Mechanism
Effects run on the primary render
GlesRenderer— obtained viaMultiRenderer::as_mut(), exposed in-tree asas_gles_renderer()— the result is wrapped as a primary-GPU texture element, andMultiFrame::finish()PRIME-copies the composited frame to the scanout GPU. The target/scanout GPU's renderer is never touched.Already wired in #178
gpu_manager.renderer(primary, target, fmt).renderer.as_gles_renderer().GlesBridge(blur_elem), drawing viaas_gles_frame()+record_bridged_damage— the same shape as niri'sPrimaryGpuTextureRenderElement.What remains
process_blur_requestsrenders the blur source (behind-content →bg_tex) through the frame rendererR, which on a cross-GPU frame binds on the target GPU while the blur passes read on the primary — two contexts that can't share the texture. That's whyprocess_blur_requestsearly-returns onframe_is_cross_gpu. Route the source render onto the primaryGlesRendererconsistently, then drop the gate (and reword the "not yet implemented" note indev/docs/multi-gpu.md/blur.rs/ CHANGELOG once it works).Reference blueprint (niri)
src/render_helpers/{blur,effect_buffer,primary_gpu_texture,renderer}.rs,src/backend/tty.rs. Verified: niri ships dual-Kawase blur and runs it on secondary-GPU outputs with no primary/secondary branch;MultiRenderer::as_mut()returns the render (primary) GPU.