Skip to content

feat(video,platform): declare explicit capture pixel formats - #5568

Open
luanweslley77 wants to merge 15 commits into
LizardByte:masterfrom
luanweslley77:feat/explicit-capture-pixel-format
Open

luanweslley77 wants to merge 15 commits into
LizardByte:masterfrom
luanweslley77:feat/explicit-capture-pixel-format

Conversation

@luanweslley77

@luanweslley77 luanweslley77 commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

Description

Capture backends now declare the negotiated pixel format on platf::img_t (pix_fmt_e pixel_format), and the RAM-path consumers (software encoder via libswscale, VAAPI/CUDA RAM) respect it instead of guessing from pixel_pitch. This continues the direction of #5495, which made the pitch information explicit.

With this, 10-bit HDR captures (PipeWire ARGB_2101010/ABGR_2101010, KMS BGRA1010102/RGBA1010102) reach the software encoder and the VAAPI/CUDA RAM converters correctly instead of being misread as 8-bit BGR0 with wrong colors. It also unblocks software HEVC Main10, which the encoder already advertises (AV_PIX_FMT_YUV420P10) but could never receive. Declared formats that a converter cannot represent now fail loudly instead of producing corrupt frames. The 10-bit shift formats are mapped unconditionally and therefore require the FFmpeg patch from build-deps #762, which the last commit pins in third-party/build-deps.

The last commit bumps third-party/build-deps to include the FFmpeg patch that registers BGRA1010102/RGBA1010102 (LizardByte/build-deps PR: LizardByte/build-deps#762). Without it, the branch still works but falls back to the previous behavior for those two formats.

Screenshot

Issues Fixed or Closed

Roadmap Issues

Type of Change

  • feat: New feature (non-breaking change which adds functionality)
  • fix: Bug fix (non-breaking change which fixes an issue)
  • docs: Documentation only changes
  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semicolons, etc.)
  • refactor: Code change that neither fixes a bug nor adds a feature
  • perf: Code change that improves performance
  • test: Adding missing tests or correcting existing tests
  • build: Changes to affect the build system or external dependencies
  • ci: Changes to CI configuration files and scripts
  • chore: Other changes that don't modify src or test files
  • revert: Reverts a previous commit
  • BREAKING CHANGE: Introduces a breaking change (can be combined with any type above)

Checklist

  • Code follows the style guidelines of this project
  • Code has been self-reviewed
  • Code has been commented, particularly in hard-to-understand areas
  • Code docstring/documentation-blocks for new or existing methods/components have been added or updated
  • Unit tests have been added or updated for any new or modified functionality

AI Usage

See our AI usage policy.

  • None: No AI tools were used in creating this PR
  • Light: AI provided minor assistance (formatting, simple suggestions)
  • Moderate: AI helped with code generation or debugging specific parts
  • Heavy: AI generated most or all of the code changes

@github-actions

github-actions Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Build artifacts

No artifacts matched the configured patterns in workflow run 33539186965.

@luanweslley77
luanweslley77 force-pushed the feat/explicit-capture-pixel-format branch from 3c8ab99 to 29e068c Compare September 1, 2026 23:10
@sonarqubecloud

sonarqubecloud Bot commented Sep 1, 2026

Copy link
Copy Markdown

@ReenigneArcher ReenigneArcher left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed head 29e068ce9f98dea70d2ed2ce6226232598bdf4cb against master at 9f7daa5e5a804b8eb09586d39b00e3e9bc176200. The explicit format mappings and software conversions look sound, but the newly advertised NV12 format exposes two capture-path defects, and the build-deps pin must point to a tagged release.

Validation: git diff --check and clang-format --dry-run --Werror passed for the changed native files. Three relevant PR tests passed in a focused C++/gtest harness using the unchanged PR conversion methods and the FFmpeg binaries from v2026.1004.34232. An additional test comparing nonzero colors across all five packed 10-bit formats with padded input strides also passed. A synthetic test of the unchanged PipeWire on_process callback passed for contiguous NV12 but failed for separate Y/UV planes: only 76,800 of the required 115,200 bytes were copied for a 320x240 frame. These are focused tests, not a complete Sunshine build or a Linux hardware streaming run.

Merge status: GitHub confirms conflicts with the target branch. Please rebase and resolve the conflicts before merging.

Hosted verification is limited: the native workflow runs report failure with no jobs or retrievable logs, the screenshot publisher fails because no CI screenshots were produced, and the failed GH-Pages documentation build's logs were inaccessible. I am not attributing those workflow statuses to these code changes. Sonar analysis and the main documentation status passed.

Comment thread src/platform/linux/pipewire.cpp Outdated
Comment thread src/platform/linux/pipewire.cpp
Comment thread third-party/build-deps Outdated
@luanweslley77
luanweslley77 force-pushed the feat/explicit-capture-pixel-format branch from 29e068c to fe9cab1 Compare October 15, 2026 08:50
Comment thread tests/integration/xvfb_server.h Fixed
@luanweslley77
luanweslley77 force-pushed the feat/explicit-capture-pixel-format branch from fe9cab1 to 05659b2 Compare October 15, 2026 10:16
Comment thread tests/integration/xvfb_server.h Fixed
Comment thread tests/integration/xvfb_server.h Fixed
@luanweslley77
luanweslley77 force-pushed the feat/explicit-capture-pixel-format branch 3 times, most recently from 094abf0 to 05e70c9 Compare October 15, 2026 13:18

@ReenigneArcher ReenigneArcher left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Re-reviewed 05e70c96c46efacdbc0f903202a41bbb21a6739f, including the full PR diff, changes since the earlier review, and the live master tip 1582fffb47eaf80c91dfa282d7346fb7cd6ceb09.

The three earlier findings are addressed: NV12 negotiation is filtered by converter capability, separate Y/UV planes are repacked, and build-deps now uses the published v2026.1004.34232 commit. The explicit format support still adds behavior absent from current master. Two new P2 findings remain: some rejected NV12 frames bypass the new recovery accounting, and the color-pattern regression uses uninitialized colorimetry.

Validation: whitespace and clang-format checks passed. A focused C++/gtest harness using unchanged PR conversion methods, headers, and the tagged Darwin ARM64 FFmpeg binaries ran 43 relevant PR tests: 41 passed, the new color-pattern comparison failed, and the SPA-dependent format-map test skipped. Initializing and applying the test colorspace as suggested produced 42 passes and the same one skip. Separate tests of the extracted NV12 copy methods reproduced both missing drop-accounting cases; adding the suggested calls made all three callback checks pass. These are focused tests, not a full Sunshine build or Linux capture/GL/CUDA execution.

Merge status: GitHub confirms conflicts. Please rebase onto current master and resolve them before merging, preserving the recent PipeWire teardown and capture-device fixes.

Hosted verification: native workflows await maintainer approval. Sonar's failed gate reports three mutable-global declarations in src/video.h that are unchanged on the target branch; I am not treating them as introduced findings. The main documentation check passed, while the GH-Pages documentation check failed and its logs were inaccessible.

Review completed by Codex | Model: gpt-6.1-sol | Thinking: xhigh

Comment on lines +735 to +741
if (!luma_stride) {
return false;
}
const auto luma_region = resolve_memptr_region(static_cast<const std::uint8_t *>(luma_data.data), luma_data.maxsize, luma_data.chunk->offset, luma_data.chunk->size, luma_data.chunk->flags);
if (!luma_region) {
return false;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[P2] Account for NV12 luma failures before returning. A negative/missing luma stride or a corrupted/out-of-bounds luma chunk returns here without calling report_drop(). on_process() then just queues the buffer and returns, so a producer that keeps sending these unusable NV12 frames never advances drop_burst or sets stream_error. Capture keeps timing out and showing a stale/black frame instead of taking the new two-second reinit/abort path. The packed-frame path already accounts for equivalent failures. Please account for both early exits and add callback-level coverage; the existing helper tests do not exercise this wiring.

The following change passed isolated tests of these copy methods for invalid luma stride, corrupted luma, and missing chroma; Linux runtime capture was not run.

Suggested change
if (!luma_stride) {
return false;
}
const auto luma_region = resolve_memptr_region(static_cast<const std::uint8_t *>(luma_data.data), luma_data.maxsize, luma_data.chunk->offset, luma_data.chunk->size, luma_data.chunk->flags);
if (!luma_region) {
return false;
}
if (!luma_stride) {
report_drop(d);
return false;
}
const auto luma_region = resolve_memptr_region(static_cast<const std::uint8_t *>(luma_data.data), luma_data.maxsize, luma_data.chunk->offset, luma_data.chunk->size, luma_data.chunk->flags);
if (!luma_region) {
report_drop(d);
return false;
}

Review completed by Codex | Model: gpt-6.1-sol | Thinking: xhigh

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed, thanks. All NV12 rejections go through one helper now, so the luma failures count as drops too and the abandonment path actually runs. I added tests that call the copy helpers directly. They fail on the previous code because stream_error never gets set. While testing this I also hit a deadlock (fill_img holds frame_mutex and report_drop re-locked it), and fixed that as well.

Comment thread tests/unit/test_video.cpp
frame.release();
ASSERT_NE(device.frame, nullptr);
ASSERT_NE(device.frame->data[0], nullptr);
ASSERT_NE(device.frame->data[2], nullptr);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[P2] Initialize and apply the colorspace in the conversion test setup. These tests default-initialize the device, leaving encode_device_t::colorspace indeterminate. Switching the input format calls apply_colorspace(), which reads that state; the initial BGR0 reference also needs the same matrix/range as later formats. With the published Darwin ARM64 FFmpeg, SoftwareEncoderConversion.ComparesEquivalentColorPatterns fails its comparisons in the unchanged test harness. Initializing the colorimetry and applying it before the first conversion makes all 42 executable focused tests pass (one SPA-dependent test remains skipped), so the test does not depend on stack contents or compare different color ranges.

This suggestion was tested in the focused harness, not a full platform build.

Suggested change
ASSERT_NE(device.frame->data[2], nullptr);
ASSERT_NE(device.frame->data[2], nullptr);
device.colorspace = {video::colorspace_e::rec601, false, static_cast<unsigned>(av_pix_fmt_desc_get(format)->comp[0].depth)};
device.apply_colorspace();

Review completed by Codex | Model: gpt-6.1-sol | Thinking: xhigh

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed as suggested. setup_conversion sets rec601/limited/depth and applies it before the first conversion, so the reference and the later conversions use the same matrix and range. Skipping the apply reproduces the mismatch, so the test does not depend on the environment anymore.

@luanweslley77
luanweslley77 force-pushed the feat/explicit-capture-pixel-format branch 2 times, most recently from d07f46a to d21bb7c Compare October 17, 2026 15:10
Add the shared pix_fmt_e header, the img_t pixel format field, and the helpers that describe or validate a declared capture layout, keeping the historical 4-byte BGR0 fallback for undeclared captures.
The CUDA translation unit derives its captured image from the host layout instead of duplicating the field declarations.
Desktop capture formats are translated to the declared capture format, and unsupported HDR formats stay undeclared so conversion fails closed.
The DXGI backends stamp the mapped pixel format on each captured image instead of relying on the converter to infer it.
The macOS backends map the negotiated Core Video format to the declared capture format instead of exposing an undeclared image.
KMS output and cursor capture declare the format described by the DRM fourcc, and unmapped 10-bit or foreign layouts remain undeclared so conversion fails closed.
wlroots images carry the format produced by the texture download instead of leaving the converter to infer it from the pixel pitch.
@luanweslley77
luanweslley77 force-pushed the feat/explicit-capture-pixel-format branch 2 times, most recently from 6d72620 to 01296ff Compare October 17, 2026 19:57
Map the SPA formats explicitly, advertise only formats the selected encoder path can consume, validate and pack the memory buffers, and abandon a stream that keeps producing invalid frames.
Map declared capture formats to FFmpeg, keep the historical fallback only for undeclared captures, support P010, copy aspect-ratio padding on the chroma grid, and fail closed for inconsistent layouts.
Classify the actual visual layout, reject unsupported visuals at init, take ownership of the XImage, recover from transient grabs, and declare XFixes cursors as BGRA.
Upload padded frames with GL_UNPACK_ROW_LENGTH, restore the unpack state, and reject capture formats, pixel pitches or row strides that the RAM converters cannot represent.
Tests can start an isolated X server, probe a live socket instead of stale lock files, and restore the previous DISPLAY environment.
X11 capture has integration coverage for the declared BGR0 layout, content changes, and rejection of an unsupported 16-bit visual.
The EGL upload path runs against a real GL context, including padded rows, unpack-state restoration, and fail-closed layouts.
A capture is pushed through the software converter, encoded as H.264, decoded, and checked against the captured pixels.
@luanweslley77
luanweslley77 force-pushed the feat/explicit-capture-pixel-format branch from 01296ff to 1c02f40 Compare October 17, 2026 21:12
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
7 New issues
7 New Code Smells (required ≤ 0)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants