Skip to content

feat(input): present selected non-vulkan surface via compositor dmabuf#69

Merged
K1ngst0m merged 2 commits intomainfrom
dev/non-vulkan-present
Jan 25, 2026
Merged

feat(input): present selected non-vulkan surface via compositor dmabuf#69
K1ngst0m merged 2 commits intomainfrom
dev/non-vulkan-present

Conversation

@K1ngst0m
Copy link
Copy Markdown
Collaborator

@K1ngst0m K1ngst0m commented Jan 25, 2026

User description

  • add compositor swapchain render + dma-buf export for selected surface
  • expose presented frames through InputForwarder for viewer fallback
  • map DRM FourCC formats to Vulkan formats for import path
  • reset cached surface frames on selection changes or teardown
  • add OpenSpec proposal/design/spec/tasks for compositor capture

PR Type

Enhancement


Description

  • Add compositor swapchain rendering and DMA-BUF export for non-Vulkan surfaces

  • Expose presented frames through InputForwarder for fallback rendering path

  • Map DRM FourCC formats to Vulkan formats for seamless import

  • Reset cached surface frames on selection changes or teardown

  • Include OpenSpec proposal, design, specification, and task documentation


Diagram Walkthrough

flowchart LR
  A["Non-Vulkan Client"] -->|commit| B["Compositor Server"]
  B -->|render to swapchain| C["DMA-BUF Buffer"]
  C -->|export frame| D["InputForwarder"]
  D -->|get_presented_frame| E["Application"]
  E -->|drm_to_vk_format| F["Vulkan Backend"]
  F -->|render_frame_with_ui| G["Viewer Output"]
Loading

File Walkthrough

Relevant files
Enhancement
8 files
application.hpp
Add surface frame tracking and state members                         
+4/-0     
application.cpp
Integrate compositor frames into render pipeline                 
+66/-8   
compositor_server.hpp
Define SurfaceFrame struct and presentation API                   
+19/-0   
compositor_server.cpp
Implement swapchain rendering and DMA-BUF export                 
+198/-3 
input_forwarder.hpp
Add get_presented_frame method to public API                         
+4/-1     
input_forwarder.cpp
Forward get_presented_frame to compositor server                 
+5/-0     
drm_fourcc.hpp
Define DRM FourCC format constants and helpers                     
+25/-0   
drm_format.hpp
Map DRM formats to Vulkan format enums                                     
+30/-0   
Documentation
4 files
proposal.md
Document feature proposal and design rationale                     
+43/-0   
design.md
Detail implementation decisions and trade-offs                     
+40/-0   
spec.md
Define requirements and acceptance scenarios                         
+31/-0   
tasks.md
Track implementation tasks and verification steps               
+18/-0   

Summary by CodeRabbit

  • New Features

    • Non‑Vulkan surface presentation: Wayland/XWayland clients can be rendered and exported for viewer consumption, with automatic fallback to input forwarding when compositor presentation is unavailable.
    • Viewer now accepts compositor-presented frames for unified rendering, including DRM→Vulkan format translation and zero‑copy DMA‑BUF export.
  • Documentation

    • Added design, proposal, spec, and task docs describing the compositor capture and DMA‑BUF export workflow.

✏️ Tip: You can customize this high-level summary in your review settings.

- add compositor swapchain render + dma-buf export for selected surface
- expose presented frames through InputForwarder for viewer fallback
- map DRM FourCC formats to Vulkan formats for import path
- reset cached surface frames on selection changes or teardown
- add OpenSpec proposal/design/spec/tasks for compositor capture
@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Jan 25, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Silent error paths: The new compositor presentation path contains multiple silent early-returns and ignored
syscall results (e.g., write() in request_present_reset() and several failure points in
render_surface_to_frame()), reducing debuggability and making failures hard to diagnose.

Referred Code
void CompositorServer::Impl::request_present_reset() {
    present_reset_requested.store(true, std::memory_order_release);
    if (event_fd.valid()) {
        uint64_t val = 1;
        static_cast<void>(write(event_fd.get(), &val, sizeof(val)));
    }
}

void CompositorServer::Impl::update_presented_frame(wlr_surface* surface) {
    auto target = get_input_target();
    if (target.surface != surface) {
        return;
    }

    render_surface_to_frame(surface);
}

void CompositorServer::Impl::render_surface_to_frame(wlr_surface* surface) {
    if (!present_swapchain || !surface) {
        return;
    }


 ... (clipped 60 lines)

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 25, 2026

📝 Walkthrough

Walkthrough

Adds a compositor-based presentation path for non‑Vulkan Wayland/XWayland surfaces: render the selected surface into a headless swapchain, export frames as DMA‑BUF with metadata, cache the last presented frame, and expose it via InputForwarder for the viewer; falls back to input-only forwarding if presentation fails.

Changes

Cohort / File(s) Summary
Design Documentation
openspec/changes/add-non-vulkan-surface-present/proposal.md, openspec/changes/add-non-vulkan-surface-present/design.md, openspec/changes/add-non-vulkan-surface-present/specs/compositor-capture/spec.md, openspec/changes/add-non-vulkan-surface-present/tasks.md
New design, spec, proposal and task list describing compositor capture path, DMA‑BUF export, surface selection rules, fallback behavior, and verification steps.
DRM Format Utilities
src/util/drm_fourcc.hpp, src/util/drm_format.hpp
Add FourCC helper, DRM format constants/modifiers, and drm_to_vk_format() mapping to translate DRM fourcc to Vulkan vk::Format.
Compositor Presentation Infrastructure
src/input/compositor_server.hpp, src/input/compositor_server.cpp
Add SurfaceFrame type and present-swapchain state; implement render-to-swapchain, DMA‑BUF export, presented-frame caching, thread-safe get_presented_frame(after_frame_number), clear/reset semantics, and integrate with surface commit/focus lifecycle.
Input Forwarder API
src/input/input_forwarder.hpp, src/input/input_forwarder.cpp
Expose get_presented_frame(uint64_t after_frame_number) on InputForwarder delegating to CompositorServer.
Application Rendering Integration
src/app/application.hpp, src/app/application.cpp
Store optional m_surface_frame and tracking; update tick/render path to prefer capture receiver or presented surface frame, translate DRM→Vulkan formats, duplicate DMA‑BUF FD for viewer import, and adjust UI sync and frame-number tracking.

Sequence Diagram

sequenceDiagram
    participant Surface as Wayland/XWayland<br/>Surface
    participant Comp as CompositorServer<br/>(Compositor thread)
    participant Swap as wlr_swapchain<br/>(Headless output)
    participant Forwarder as InputForwarder
    participant App as Application<br/>(Main thread)
    participant Viewer as Viewer Renderer<br/>(Vulkan)

    Surface->>Comp: surface commit
    Comp->>Comp: check selected/input target
    Comp->>Swap: render_surface_to_frame()
    Swap->>Swap: acquire buffer
    Comp->>Comp: render via wlr_render_pass
    Comp->>Comp: export DMA-BUF attrs (format,stride,modifier)
    Comp->>Comp: duplicate FD, cache SurfaceFrame (frame_number)
    
    App->>Forwarder: tick_frame()
    Forwarder->>Comp: get_presented_frame(last_seen)
    Comp-->>Forwarder: SurfaceFrame (if newer)
    Forwarder-->>App: std::optional<SurfaceFrame>
    
    App->>App: drm_to_vk_format() and decide rebuild
    App->>Viewer: import DMA-BUF + metadata, render
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

Review effort 4/5

Poem

🐰 I nibbled code in moonlit night,

Headless frames now catch the light,
DMA‑BUF hops without a fuss,
Viewer greets each chosen us,
If swapchain sleeps, input still takes flight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding non-Vulkan surface presentation via compositor DMA-BUF, which is the core feature of this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Jan 25, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Consider a more robust frame selection

The current frame selection logic strictly prioritizes Vulkan frames, which can
cause a stale frame to be displayed even if a newer compositor frame is
available. This should be changed to a timestamp-based mechanism to always
display the most recent frame from any source.

Examples:

src/app/application.cpp [485-491]
    if ((!m_capture_receiver || !m_capture_receiver->has_frame()) && m_input_forwarder) {
        auto surface_frame = m_input_forwarder->get_presented_frame(m_last_surface_frame_number);
        if (surface_frame) {
            m_surface_frame = std::move(*surface_frame);
            m_last_surface_frame_number = m_surface_frame->frame_number;
        }
    }
src/app/application.cpp [552-576]
        if (m_capture_receiver && m_capture_receiver->has_frame()) {
            source_frame = &m_capture_receiver->get_frame();
            using_capture_receiver = true;
            source_frame_number = source_frame->frame_number;
        } else if (m_surface_frame) {
            auto vk_format = util::drm_to_vk_format(m_surface_frame->format);
            if (vk_format == VK_FORMAT_UNDEFINED) {
                GOGGLES_LOG_DEBUG("Skipping surface frame with unsupported DRM format");
            } else if (m_surface_frame->modifier == util::DRM_FORMAT_MOD_INVALID) {
                GOGGLES_LOG_DEBUG("Skipping surface frame with invalid DMA-BUF modifier");

 ... (clipped 15 lines)

Solution Walkthrough:

Before:

// In Application::tick_frame()

// Only get a compositor frame if no Vulkan frame is available
if (!vulkan_capture_has_frame()) {
    get_new_compositor_frame();
}

// Always prioritize the Vulkan frame if it exists
if (vulkan_capture_has_frame()) {
    render(vulkan_frame);
} else if (compositor_frame_exists()) {
    render(compositor_frame);
} else {
    render_clear_screen();
}

After:

// In Application::tick_frame()

// Always try to get the latest frames from both sources
vulkan_frame = get_new_vulkan_frame();
compositor_frame = get_new_compositor_frame();

// Compare timestamps to decide which frame is newer
if (vulkan_frame && (!compositor_frame || vulkan_frame.timestamp > compositor_frame.timestamp)) {
    render(vulkan_frame);
} else if (compositor_frame) {
    render(compositor_frame);
} else {
    render_clear_screen();
}
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a design flaw in the frame selection logic where a stale Vulkan frame can block newer compositor frames, leading to a frozen display and poor user experience.

Medium
  • Update

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@openspec/changes/add-non-vulkan-surface-present/proposal.md`:
- Around line 9-21: Add the missing spec delta to the "What Changes" section:
insert a bullet describing the new `compositor-capture` spec (mark as "new" and
"breaking" if applicable) directly under the existing bullets in the "What
Changes" list so it appears alongside the other changes rather than only in the
"Impact" section; reference the exact spec name `compositor-capture` in that
bullet to match the Impact entry.

In
`@openspec/changes/add-non-vulkan-surface-present/specs/compositor-capture/spec.md`:
- Around line 6-23: Update each scenario to use the single-line "- **WHEN** ...
- **THEN** ..." step format instead of separate GIVEN/AND/WHEN/THEN bullets: for
"Scenario: Present selected surface" replace the four bullets with one WHEN/THEN
line (e.g. WHEN a non-Vulkan client surface is connected and selected via the
surface selector and the compositor produces a new frame - THEN the viewer
presents the selected surface), for "Scenario: Presentation unavailable" combine
the GIVEN/WHEN/THEN into one WHEN/THEN line reflecting that compositor
presentation cannot be initialized and input forwarding continues without
presenting non-Vulkan frames, and for "Scenario: Export compositor frame via
DMA-BUF" make a single WHEN/THEN line that states when the compositor renders a
frame for the selected surface it exports a DMA-BUF with the required metadata
and the viewer imports/presents it without CPU readback; apply the same
single-line WHEN/THEN pattern to the other scenarios referenced (lines 28-31).

In `@src/app/application.cpp`:
- Around line 563-571: The CaptureFrame's frame_number is being hardcoded to 0
causing timeline semaphore sync issues; update the assignment in the block that
fills surface_capture so that surface_capture.frame_number is set from the
source frame (use m_surface_frame->frame_number), ensuring any needed type
conversion to match surface_capture.frame_number's type; modify the code around
the surface_capture population (the lines that set
width/height/stride/offset/format/modifier/frame_number/dmabuf_fd) to propagate
the real frame number instead of 0 so Vulkan backend timeline semaphore
synchronization remains correct.

In `@src/util/drm_format.hpp`:
- Around line 1-28: Replace the C Vulkan API usage in drm_to_vk_format with
vulkan-hpp types: change the include from <vulkan/vulkan.h> to
<vulkan/vulkan.hpp>, change the function return type from VkFormat to vk::Format
(function drm_to_vk_format), and return the corresponding vk::Format enum values
(e.g., vk::Format::eB8G8R8A8Unorm, vk::Format::eR8G8B8A8Unorm,
vk::Format::eA2R10G10B10UnormPack32, vk::Format::eA2B10G10R10UnormPack32,
vk::Format::eR5G6B5UnormPack16, vk::Format::eUndefined) instead of VK_FORMAT_*
constants; keep the DRM switch cases (DRM_FORMAT_*) unchanged.

Comment on lines +9 to +21
## What Changes

- Render the selected non-Vulkan surface into a headless compositor output and export a DMA-BUF.
- Expose the latest compositor-presented frame via `InputForwarder` without altering input routing.
- When no Vulkan capture frame is available, feed compositor DMA-BUF frames into the existing
Vulkan viewer render path.
- Keep the Vulkan layer capture pipeline unchanged.
- Fall back to input-only mode if compositor presentation is unavailable.

## Impact

- Affected specs: `compositor-capture` (new)
- Affected code:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

List spec deltas under “What Changes.”

Guidelines require spec deltas to be listed in the “What Changes” section. Please add a bullet there (and mark breaking if applicable), rather than only under Impact.

✅ Example addition
 ## What Changes
 
+- Spec deltas: compositor-capture (ADDED)
 - Render the selected non-Vulkan surface into a headless compositor output and export a DMA-BUF.

As per coding guidelines, spec deltas must appear under “What Changes.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## What Changes
- Render the selected non-Vulkan surface into a headless compositor output and export a DMA-BUF.
- Expose the latest compositor-presented frame via `InputForwarder` without altering input routing.
- When no Vulkan capture frame is available, feed compositor DMA-BUF frames into the existing
Vulkan viewer render path.
- Keep the Vulkan layer capture pipeline unchanged.
- Fall back to input-only mode if compositor presentation is unavailable.
## Impact
- Affected specs: `compositor-capture` (new)
- Affected code:
## What Changes
- Spec deltas: compositor-capture (ADDED)
- Render the selected non-Vulkan surface into a headless compositor output and export a DMA-BUF.
- Expose the latest compositor-presented frame via `InputForwarder` without altering input routing.
- When no Vulkan capture frame is available, feed compositor DMA-BUF frames into the existing
Vulkan viewer render path.
- Keep the Vulkan layer capture pipeline unchanged.
- Fall back to input-only mode if compositor presentation is unavailable.
## Impact
- Affected specs: `compositor-capture` (new)
- Affected code:
🤖 Prompt for AI Agents
In `@openspec/changes/add-non-vulkan-surface-present/proposal.md` around lines 9 -
21, Add the missing spec delta to the "What Changes" section: insert a bullet
describing the new `compositor-capture` spec (mark as "new" and "breaking" if
applicable) directly under the existing bullets in the "What Changes" list so it
appears alongside the other changes rather than only in the "Impact" section;
reference the exact spec name `compositor-capture` in that bullet to match the
Impact entry.

Comment on lines +6 to +23
#### Scenario: Present selected surface
- **GIVEN** a non-Vulkan client surface is connected to the compositor
- **AND** the surface is selected via the existing surface selector
- **WHEN** the compositor produces a new frame
- **THEN** the viewer presents the selected surface

#### Scenario: Presentation unavailable
- **GIVEN** compositor presentation cannot be initialized
- **WHEN** non-Vulkan clients connect for input
- **THEN** input forwarding continues without presenting non-Vulkan frames

### Requirement: DMA-BUF Export for Compositor Frames
The compositor capture path SHALL export frames using DMA-BUF for zero-copy presentation.

#### Scenario: Export compositor frame via DMA-BUF
- **WHEN** the compositor renders a frame for the selected surface
- **THEN** it exports a DMA-BUF with width, height, format, stride, and modifier metadata
- **AND** the viewer imports and presents the frame without CPU readback
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Scenario steps must use the WHEN/THEN single-line format.

The scenarios currently use GIVEN/AND/WHEN/THEN bullets, but the spec format requires each step to be a single - **WHEN** ... - **THEN** ... line. Please update all scenarios accordingly.

✅ Example rewrite (apply similar pattern to all scenarios)
 #### Scenario: Present selected surface
-- **GIVEN** a non-Vulkan client surface is connected to the compositor
-- **AND** the surface is selected via the existing surface selector
-- **WHEN** the compositor produces a new frame
-- **THEN** the viewer presents the selected surface
+- **WHEN** a non-Vulkan client surface is connected to the compositor, the surface is selected via the existing surface selector, and the compositor produces a new frame - **THEN** the viewer presents the selected surface

Please apply the same WHEN/THEN step format to the remaining scenarios. As per coding guidelines, please use the required scenario step format.

Also applies to: 28-31

🤖 Prompt for AI Agents
In
`@openspec/changes/add-non-vulkan-surface-present/specs/compositor-capture/spec.md`
around lines 6 - 23, Update each scenario to use the single-line "- **WHEN** ...
- **THEN** ..." step format instead of separate GIVEN/AND/WHEN/THEN bullets: for
"Scenario: Present selected surface" replace the four bullets with one WHEN/THEN
line (e.g. WHEN a non-Vulkan client surface is connected and selected via the
surface selector and the compositor produces a new frame - THEN the viewer
presents the selected surface), for "Scenario: Presentation unavailable" combine
the GIVEN/WHEN/THEN into one WHEN/THEN line reflecting that compositor
presentation cannot be initialized and input forwarding continues without
presenting non-Vulkan frames, and for "Scenario: Export compositor frame via
DMA-BUF" make a single WHEN/THEN line that states when the compositor renders a
frame for the selected surface it exports a DMA-BUF with the required metadata
and the viewer imports/presents it without CPU readback; apply the same
single-line WHEN/THEN pattern to the other scenarios referenced (lines 28-31).

@K1ngst0m K1ngst0m force-pushed the dev/non-vulkan-present branch from 2dc4135 to 94a1d67 Compare January 25, 2026 11:46
@K1ngst0m K1ngst0m merged commit 56cf582 into main Jan 25, 2026
4 checks passed
@K1ngst0m K1ngst0m deleted the dev/non-vulkan-present branch January 25, 2026 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant