From b3ba157cb4f1e9af897986a3374019261dc87b0b Mon Sep 17 00:00:00 2001 From: "Claude (noether)" Date: Fri, 8 May 2026 10:28:08 +0000 Subject: [PATCH] =?UTF-8?q?iter2=20Phase=202:=20situation=20analysis=20?= =?UTF-8?q?=E2=80=94=20six=20bugs=20in=20HEVC=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 2 source-read of the HEVC path post-iter1-close (fork master 229d6d1). Six bugs identified, all in libva backend; kernel + driver path proven for HEVC in Phase 0 cross-validator sweep. Substrate timing caveat: Phase 2 conducted against fresnel kernel 6.19.9-99. Operator-scheduled rolling pacman -Syyuu to linux-7 imminent. Phase 2 source-read findings are kernel-agnostic (fork code + UAPI + FFmpeg reference); they carry forward across the kernel jump unchanged. Phase 3 baselines will run on linux-7. Bug 1 — src/config.c:64-69 HEVCMain falls through to default, returns VA_STATUS_ERROR_UNSUPPORTED_PROFILE. Verbatim match for iter1 Bug 1 pattern; fix is 3-line break addition. Bug 2 — src/picture.c:204-206 explicit case VAProfileHEVCMain: return UNSUPPORTED_PROFILE with stale comment "Fourier-local: HEVC stripped, no HW support on RK3566." (RK3566 is ohm context; fresnel is RK3399 where rkvdec DOES support HEVC.) Fix: replace explicit reject with dispatch to h265_set_controls() (mirrors MPEG-2 dispatch at picture.c:186-191). Bug 3 — src/h265.c uses staging-era CIDs: V4L2_CID_MPEG_VIDEO_HEVC_PPS / _SPS / _SLICE_PARAMS These don't exist on fresnel's 6.19 kernel headers (verified via test-compile: gcc reports undeclared identifiers, suggests V4L2_CID_MPEG_VIDEO_DEC_PTS as nearest match). Mainline kernel UAPI splits HEVC stateless into 7 controls: V4L2_CID_STATELESS_HEVC_{SPS,PPS,SLICE_PARAMS,SCALING_MATRIX, DECODE_PARAMS,DECODE_MODE,START_CODE} + ENTRY_POINT_OFFSETS, EXT_SPS_ST_RPS, EXT_SPS_LT_RPS (0xa40a90..0xa40a96 + extensions, V4L2_CID_CODEC_STATELESS_BASE + 400..407+). Fix shape: rewrite h265.c against new split API. Substantially larger than iter1's mpeg2.c rewrite (HEVC has 7 controls vs MPEG-2 3, + slice_params dynamic-array, + per-slice accumulation logic needed). Bug 4 — h265.c uses single-slice_params shape; new API is dynamic-array. Fresnel rkvdec advertises: hevc_slice_parameters 0xa40a92 elems=1 dims=[600] dynamic-array Up to 600 slice_params entries per submission. Current codec_store_buffer:115-135 OVERWRITES previous slice on VASliceParameterBufferType arrival. Multi-slice frames need APPEND-not-overwrite. FFmpeg reference v4l2_request_hevc.c:540-547 shows the pattern. Fix shape: extend params.h265 to hold slice_params array (or pointer+count); codec_store_buffer appends; h265_set_controls flushes the array at end_picture as a single dynamic-array S_EXT_CTRLS entry. Bug 5 — h265.c missing controls: doesn't submit DECODE_PARAMS (per-frame DPB info; new in modern API), SCALING_MATRIX (conditional on iqmatrix_set + sps.scaling_list_enabled), DECODE_MODE+START_CODE (device-wide menus, set once per context init). Fix shape: add h265_fill_decode_params() (DPB ordering from VAAPI ReferenceFrames[15] — preserve current extraction logic from h265_fill_slice_params:269-315, route to new struct). Conditional SCALING_MATRIX from VAIQMatrixBufferHEVC. Device-wide DECODE_MODE+START_CODE either at first h265_set_controls call or in extended context.c device-init block. Bug 6 — src/meson.build comments out 'h265.c' (line 50) and 'h265.h' (line 73). Fix: uncomment both. Trivial. Bug 7 (verify only) — include/hevc-ctrls.h is a 9-line shim that just #include . Comment dates the modernization to "linux-media 6.6+". Adds zero value; harmless. Leave in place per iter1 Phase 5 Nit 6 lower-risk path. Bug 8 (latent) — picture.c:287 params.h264.matrix_set=false writes union byte 240. For HEVC: byte 240 lands inside h265.picture (range [0..604), size 604) — different field than MPEG-2's chroma_intra_quantiser_matrix. ffmpeg-vaapi's per-frame VAPictureParameterBufferHEVC re-send overwrites the corrupted byte before h265_set_controls reads. Latent for clients that reuse a surface without re-sending picture params. iter2+ Phase 4 cross-cutting backlog candidate; not iter2 scope. Things verified NOT bugs: - h265_fill_pps/sps/slice_params field extraction from VAAPI structs is sound (just routes to wrong destination structs) - NAL header parsing (data_bit_offset bit-search) is preserved in new API — slice_params still has bit_size + data_bit_offset - v4l2_set_controls batching API in place (used by H.264 + iter1 MPEG-2; iter2 uses same) Substrate / kernel observation: - Linux mainline 7.1.0-rc2 reference checkout has drivers/staging/media/rkvdec/ with rkvdec.c, rkvdec-h264.c, rkvdec-vp9.c — NO rkvdec_hevc.c. fresnel's HEVC support is out-of-tree (Christian Hewitt patches per phase0_findings.md external references). May land in stable 7.x. - Phase 4 contract-before-code therefore can't cite kernel-side HEVC handler source until/unless rkvdec_hevc.c lands in mainline. UAPI doc + FFmpeg reference + Phase 3 cross-validator bytes are the contract anchor. Open questions tabled for Phase 3 (post-linux-7-upgrade): 1. iter1 + T4 references on linux-7 (regression check of closed iter1 work) 2. SDDM watchpoint on linux-7 3. Cross-validator HEVC re-anchor (Baseline C equivalent for HEVC) — verbatim payload bytes for SPS, PPS, DECODE_PARAMS, SLICE_PARAMS array, SCALING_MATRIX 4. Pre-fix scratch test (Bug 1 + Bug 2 only, h265.c kept commented out) — confirm collateral safe 5. Slice-count for bbb_720p10s_hevc.mp4 fixture 6. Whether linux-7 brings rkvdec_hevc.c into mainline Predicted iter2 close shape: trivial Bugs 1+2+6 fixes + sizable h265.c rewrite (~250-400 lines, ~3x iter1's mpeg2.c) + new codec_store_buffer slice accumulation logic. If Phase 7 fails: likely struct-size mismatch (run pahole), DPB ordering, or slice_params array size encoding. Co-Authored-By: Claude Opus 4.7 (1M context) --- phase2_iter2_situation.md | 317 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 phase2_iter2_situation.md diff --git a/phase2_iter2_situation.md b/phase2_iter2_situation.md new file mode 100644 index 0000000..2241805 --- /dev/null +++ b/phase2_iter2_situation.md @@ -0,0 +1,317 @@ +# Iteration 2 — Phase 2 (situation analysis) + +Phase 2 source-read of the HEVC path in libva-v4l2-request-fourier post-iter1-close (master tip `229d6d1`). Six bugs identified, all in the libva backend. Kernel + driver path was already proven for HEVC in Phase 0 cross-validator sweep. + +**Caveat on substrate timing**: this Phase 2 was conducted against fresnel running `linux-eos-arm 6.19.9-99`. Phase 3 baselines + Phase 6 implementation + Phase 7 verification will run against an imminent linux-7 rolling upgrade (operator-scheduled). Phase 2 source-read findings are kernel-agnostic (fork code + kernel UAPI + FFmpeg reference + VAAPI buffers); they carry forward across the kernel jump unchanged. + +## Reset context + +``` +$ git -C /home/mfritsche/src/fresnel-fourier status -sb +## master +$ git -C /home/mfritsche/src/fresnel-fourier log --oneline -3 +6e8c970 iter2 Phase 0 + Phase 1 lock: HEVC Main on rkvdec +dc69378 iter1 Phase 8 close: 2/5 codecs passing +ec9133a iter1 Phase 7: verification — all 5 criteria GREEN + +$ git -C /home/mfritsche/src/libva-multiplanar/libva-v4l2-request-fourier log --oneline -1 +229d6d1 fresnel-fourier iter1 Phase 6 commit D: drop missed mpeg2-ctrls.h include from context.c +$ ssh fresnel 'uname -r' +6.19.9-99-eos-arm +``` + +Source files inspected: + +| File | Lines | +|---|---| +| `src/h265.c` | 407 (currently excluded from build) | +| `src/picture.c` | 403 (HEVCMain reject at 204-206) | +| `src/config.c` | 255 (HEVCMain fall-through at 67) | +| `src/surface.h` | 182 (params.h265 union member) | +| `src/meson.build` | 98 (h265.c + h265.h commented out) | +| `include/hevc-ctrls.h` | 9 (thin shim, NOT a duplication header) | + +Reference checkouts: + +- Linux mainline at `~/src/libva-multiplanar/references/linux-mainline/` (7.1.0-rc2). **Note**: rkvdec_hevc.c does not exist in this checkout (rkvdec only has h264, vp9). The HEVC stateless API is in UAPI but the rkvdec driver-side HEVC handler is out-of-tree on fresnel's 6.19 (Christian Hewitt patch series). May land in stable 7.x. +- FFmpeg Kwiboo `v4l2-request-n8.1` branch — `libavcodec/v4l2_request_hevc.c` (752 lines, modern API). +- VAAPI headers on fresnel — `` defines `VAPictureParameterBufferHEVC`, `VASliceParameterBufferHEVC`, `VAIQMatrixBufferHEVC`. +- Kernel UAPI on fresnel — `` lines 2090+ define new HEVC stateless controls. + +## Bug 1 — `RequestCreateConfig` HEVCMain fall-through to default + +`src/config.c:55-71`: + +```c +case VAProfileMPEG2Simple: +case VAProfileMPEG2Main: + // fresnel-fourier iter1: MPEG-2 enabled. ... + break; +case VAProfileHEVCMain: +default: + return VA_STATUS_ERROR_UNSUPPORTED_PROFILE; +``` + +iter1 added the `break;` for MPEG-2 cases. HEVCMain still falls through to `default:` and returns `VA_STATUS_ERROR_UNSUPPORTED_PROFILE` (= 12). **Verbatim match for iter1 Bug 1's pattern**, applied to a different profile. + +**Fix shape**: 3-line `break;` addition (analogous to iter1 Commit A pattern). Matches existing H.264 + MPEG-2 case structure. No profile-specific validation logic in `RequestCreateConfig` (validation happens at vaCreateContext / control submission time). + +## Bug 2 — `picture.c::codec_set_controls` explicit HEVCMain reject + +`src/picture.c:204-206`: + +```c +case VAProfileHEVCMain: + /* Fourier-local: HEVC stripped, no HW support on RK3566. */ + return VA_STATUS_ERROR_UNSUPPORTED_PROFILE; +``` + +Comment is stale — references RK3566 (PineTab2 / ohm-side context); fresnel-fourier targets RK3399 where rkvdec DOES support HEVC. Phase 0 cross-validator sweep confirmed `ffmpeg -hwaccel v4l2request` decodes the BBB HEVC fixture exit 0 on fresnel. + +**Fix shape**: replace the explicit reject with a dispatch to `h265_set_controls()` (mirroring the MPEG-2 dispatch at `picture.c:186-191`). 5-line change. Comment updated to remove the stale RK3566 reference. + +## Bug 3 — `src/h265.c` uses staging-era control IDs + +`src/h265.c` calls `v4l2_set_control` with three control IDs: + +- `V4L2_CID_MPEG_VIDEO_HEVC_PPS` (line 386) +- `V4L2_CID_MPEG_VIDEO_HEVC_SPS` (line 393) +- `V4L2_CID_MPEG_VIDEO_HEVC_SLICE_PARAMS` (line 401) + +These are staging-era — they don't exist on fresnel's 6.19 kernel headers. Empirical test: + +``` +$ gcc -c /tmp/test_compile_h265.c -I include +/tmp/test_compile_h265.c:3:29: error: 'V4L2_CID_MPEG_VIDEO_HEVC_PPS' undeclared (first use in this function); + did you mean 'V4L2_CID_MPEG_VIDEO_DEC_PTS'? +... (similar for _SPS and _SLICE_PARAMS) +``` + +So `h265.c` won't compile against the modern kernel UAPI. That's why `src/meson.build` comments it out (sources line 50, headers line 73). + +The mainline kernel UAPI defines (`:2096-2100` and following): + +```c +#define V4L2_CID_STATELESS_HEVC_SPS (V4L2_CID_CODEC_STATELESS_BASE + 400) /* 0xa40a90 */ +#define V4L2_CID_STATELESS_HEVC_PPS (V4L2_CID_CODEC_STATELESS_BASE + 401) /* 0xa40a91 */ +#define V4L2_CID_STATELESS_HEVC_SLICE_PARAMS (V4L2_CID_CODEC_STATELESS_BASE + 402) /* 0xa40a92 */ +#define V4L2_CID_STATELESS_HEVC_SCALING_MATRIX (V4L2_CID_CODEC_STATELESS_BASE + 403) /* 0xa40a93 */ +#define V4L2_CID_STATELESS_HEVC_DECODE_PARAMS (V4L2_CID_CODEC_STATELESS_BASE + 404) /* 0xa40a94 */ +#define V4L2_CID_STATELESS_HEVC_DECODE_MODE (V4L2_CID_CODEC_STATELESS_BASE + 405) /* 0xa40a95 */ +#define V4L2_CID_STATELESS_HEVC_START_CODE (V4L2_CID_CODEC_STATELESS_BASE + 406) /* 0xa40a96 */ +#define V4L2_CID_STATELESS_HEVC_ENTRY_POINT_OFFSETS (V4L2_CID_CODEC_STATELESS_BASE + 407) +``` + +Plus EXT_SPS_ST_RPS, EXT_SPS_LT_RPS for HEVC range extensions (likely not needed on fresnel for Main profile). + +The fresnel V4L2 inventory ([`phase0_evidence/2026-05-07/v4l2_inventory.txt`](phase0_evidence/2026-05-07/v4l2_inventory.txt)) confirms `/dev/video` exposes the new IDs `0xa40a90/91/92/93/94/95/96` (matches `mpeg_2_*` style for MPEG-2 in iter1; here `hevc_*`). + +**Fix shape**: rewrite `h265.c` against the new split API. Significantly larger than iter1's mpeg2.c rewrite — see Bug 4-6 for additional structural changes. + +## Bug 4 — h265.c uses old struct shape (slice_params is now dynamic-array) + +The current `src/h265.c` submits **one** slice_params per frame (one `v4l2_set_control` call with `&slice_params, sizeof(slice_params)`). The new mainline API treats slice_params as a **dynamic array** — one entry per slice in the frame. + +Per Phase 0 inventory, fresnel's rkvdec advertises: + +``` +hevc_slice_parameters 0x00a40a92 (hevc-slice-params): elems=1 dims=[600] flags=has-payload, dynamic-array +``` + +So the kernel accepts up to 600 slice_params entries per submission. FFmpeg `libavcodec/v4l2_request_hevc.c:540-547`: + +```c +if (ctx->max_slice_params && controls->num_slice_params) { + control[count++] = (struct v4l2_ext_control) { + .id = V4L2_CID_STATELESS_HEVC_SLICE_PARAMS, + .ptr = controls->frame_slice_params, + .size = sizeof(*controls->frame_slice_params) * + FFMIN(controls->num_slice_params, ctx->max_slice_params), + }; +} +``` + +FFmpeg accumulates slice_params across multiple `decode_slice` callbacks and submits them all together at end_frame. + +**Fix shape**: `h265_set_controls()` needs to iterate over all slices in the frame (the libva backend currently sees them as multiple `VARenderPicture(VASliceParameterBufferType)` calls — `codec_store_buffer:115-135` for HEVC currently does `memcpy(&surface->params.h265.slice, …)` which **overwrites** previous slice params with the latest one). For multi-slice frames, the libva backend must accumulate slice params into an array, not overwrite. + +This is the largest behavioral change vs iter1 — the existing `codec_store_buffer` doesn't support per-slice accumulation. Phase 4 plan needs to extend `surface_object->params.h265` to hold a slice_params array (or use a separate accumulator), and `codec_store_buffer` needs to append rather than overwrite. + +For the iter2 fixture (`bbb_720p10s_hevc.mp4`, libx265 ultrafast 1280×720), the typical slices-per-frame count is small (probably 1-4 depending on x265's slice configuration). Phase 3 baseline will measure the actual count via cross-validator strace. + +## Bug 5 — h265.c missing controls (DECODE_PARAMS, SCALING_MATRIX, DECODE_MODE, START_CODE) + +Current `h265.c` submits only PPS, SPS, SLICE_PARAMS. The new API has: + +| Control | Required | Frequency | Source data | +|---|---|---|---| +| `STATELESS_HEVC_SPS` | yes | per-frame (or once per stream) | VAPictureParameterBufferHEVC | +| `STATELESS_HEVC_PPS` | yes | per-frame | VAPictureParameterBufferHEVC | +| `STATELESS_HEVC_SLICE_PARAMS` | yes | per-frame, dynamic-array | VASliceParameterBufferHEVC × N | +| `STATELESS_HEVC_SCALING_MATRIX` | conditional on `sps_scaling_list_data_present_flag` | per-frame | VAIQMatrixBufferHEVC | +| `STATELESS_HEVC_DECODE_PARAMS` | yes | per-frame | DPB info + POC, derived from VAPictureParameterBufferHEVC | +| `STATELESS_HEVC_DECODE_MODE` | once | device-wide at init | menu (FRAME_BASED on rkvdec) | +| `STATELESS_HEVC_START_CODE` | once | device-wide at init | menu (ANNEX_B on rkvdec) | + +Per FFmpeg `v4l2_request_hevc.c:512-565` `v4l2_request_hevc_queue_decode`, the per-frame batch contains 3 mandatory + 3 conditional = up to 6 controls (no DECODE_MODE/START_CODE — those are device-wide menus set once at init). + +The new **DECODE_PARAMS** control carries: +- `dpb[]` array of DPB entries (timestamp + flags per ref pic) — was inside slice_params in old API; moved to a per-frame DECODE_PARAMS struct in new API. +- `num_active_dpb_entries`, `num_poc_st_curr_before/after`, `num_poc_lt_curr` — same; moved to DECODE_PARAMS. +- POC info per frame. + +**Fix shape**: `h265_fill_decode_params()` extracts DPB+POC info from VAAPI's ReferenceFrames[15] — the existing `h265_fill_slice_params` lines 269-315 already does this work, just into the wrong destination struct. New code populates `v4l2_ctrl_hevc_decode_params.dpb[]` instead of `slice_params.dpb[]`. + +The old `dpb[]` location in the staging API was inside slice_params; new API moves it to decode_params (per-frame, sensible since DPB is shared across all slices in a frame). Phase 4 plan should preserve the existing extraction logic and route it into the new struct. + +**SCALING_MATRIX**: only needed when `sps->scaling_list_enabled_flag` is set AND `pps->pps_scaling_list_data_present_flag` or SPS provides explicit lists. For the BBB-720p10s fixture, most likely default (flat) scaling — `iqmatrix_set` will be false, and we omit SCALING_MATRIX from the batch (kernel uses spec defaults). + +**DECODE_MODE + START_CODE**: device-wide controls set once at context init, not per frame. Currently `src/context.c:142-155` sets H.264-specific device controls; iter2 should NOT add HEVC device init there because HEVC decoding shares the rkvdec device with H.264 (and possibly future VP9). The H.264 device-init currently in context.c works because the device is rkvdec; for HEVC we need to set HEVC's `_DECODE_MODE` and `_START_CODE` on the same device. Either extend context.c's device-init block or set the HEVC-specific controls inside `h265_set_controls()` once-per-context (with a flag to skip on subsequent frames). + +## Bug 6 — `src/meson.build` excludes h265.c + h265.h + +`src/meson.build:50` and `:73`: + +``` +sources = [ + ... +# 'h265.c' +] + +headers = [ + ... +# 'h265.h' +] +``` + +Both commented out. iter2 fix: uncomment both. Trivial 2-line change in meson.build. + +## Bug 7 (verify-only) — include/hevc-ctrls.h is a thin shim + +Unlike `mpeg2-ctrls.h` (which iter1 deleted because it was a duplication header that masked kernel UAPI), `include/hevc-ctrls.h` is already a 9-line shim: + +```c +/* Fourier-local override: HEVC controls are upstream since linux-media + * 6.6+, so defer to the kernel's linux/v4l2-controls.h instead of + * duplicating the struct definitions (duplication causes redefinition + * errors on newer linux-api-headers). */ +#ifndef _LIBVA_V4L2_REQUEST_HEVC_CTRLS_H +#define _LIBVA_V4L2_REQUEST_HEVC_CTRLS_H +#include +#endif +``` + +The shim adds zero value — `` is already pulled transitively via `` from any `.c` file that uses HEVC controls. + +**Decision (defer to Phase 4)**: leave the shim in place (lower-risk path; iter1 Phase 5 Nit 6 deferral). Deletion is vestigial-cleanup, not iter2-scope. Verify in Phase 4 plan whether `src/h265.c` or any other file uniquely depends on `` being a separate include; if not, deletion is a 1-line follow-up cleanup but not blocking. + +## Bug 8 (NEW, latent) — `picture.c:287` `params.h264.matrix_set = false` corrupts h265.picture + +Per offsetof verification on fresnel via gcc + libva: + +``` +h264.matrix_set offset = 240 +h265.picture range = [0 .. 604) size=604 +h265.slice range = [604 .. 868) size=264 +h265.iqmatrix range = [868 .. 1884) size=1016 +h265.iqmatrix_set offset = 1884 +``` + +The unconditional `params.h264.matrix_set = false` write at `picture.c:287` lands at union byte 240, which falls **inside h265.picture** (range 0..604). Specifically byte 240 of `VAPictureParameterBufferHEVC` — checking the va.h struct layout, byte 240 is somewhere in the middle (likely inside one of the bitfield sub-structs `pic_fields` or `slice_parsing_fields` or near `ReferenceFrames[]`). + +For iter2's binding cells: same masking-by-RenderPicture-overwrite mechanism as iter1's MPEG-2 case. ffmpeg-vaapi sends `VAPictureParameterBufferHEVC` every frame via `vaRenderPicture(VAPictureParameterBufferType)`, which `codec_store_buffer:104-108` copies wholesale into `surface->params.h265.picture` (overwriting the corrupted byte 240). Net safe for iter2. + +**Latent bug confirmed for HEVC too**: a VAAPI client that reuses a surface in BeginPicture without re-sending VAPictureParameterBufferType (legal VAAPI for surfaces with prior picture-param state) would see byte 240 corrupted. iter2 doesn't fix this; iter2+ Phase 4 cross-cutting backlog item B3 (BeginPicture profile-aware reset) covers it. + +## Things verified NOT bugs + +### h265.c field extraction logic from VAAPI is sound + +The 407-line h265.c code: + +- `h265_fill_pps()` (lines 48-102): extracts PPS fields from VAAPI `picture` (`VAPictureParameterBufferHEVC`) and `slice` (`VASliceParameterBufferHEVC`). Field-by-field source reads are correct against VAAPI's struct shape. +- `h265_fill_sps()` (lines 104-158): extracts SPS from VAAPI `picture`. Same shape; correct against VAAPI struct. +- `h265_fill_slice_params()` (lines 160-365): extracts slice params + DPB info + ref index lists + pred weight tables. The DPB extraction (lines 269-315) reads VAPictureHEVC ReferenceFrames[15] correctly. +- The data extraction targets the OLD struct layout (where DPB is in slice_params, where slice_params is single not dynamic-array, etc.). Phase 4 rewrite preserves the extraction logic and re-routes the destination to the new struct layout. + +### h265.c parses NAL header from bitstream + +Lines 184-209: extracts `nal_unit_type`, `nuh_temporal_id_plus1`, and `data_bit_offset` from the slice data buffer. The `data_bit_offset` calculation searches for the slice-segment-header start-code-prefix bit. **This logic is preserved** — the new V4L2 API still requires `slice_params.bit_size` and `slice_params.data_bit_offset` per slice. Field locations differ; computation is the same. + +### v4l2.c batching API is in place + +`src/v4l2.c:475-489` `v4l2_set_controls` accepts an `v4l2_ext_control[]` array. iter2's batched 6-control-per-frame submission uses this same API (single `VIDIOC_S_EXT_CTRLS` call), matching iter1's pattern. + +## Open questions for Phase 3 baseline + +Before Phase 4 plan, Phase 3 should capture (after the linux-7 kernel upgrade to anchor against the shipping kernel): + +1. **iter1 + T4 references on linux-7** — re-run iter1 Phase 7 criterion 5 (T4 H.264) and criterion 4 (MPEG-2 +02s). If hashes differ, that's a substrate regression to investigate before iter2 proceeds. Criterion 1 (vainfo) regression is also worth checking. +2. **fresnel SDDM watchpoint** on linux-7 — verify the greeter still works after the kernel jump. If regression fires, run `~/.claude/plans/dynamic-forging-piglet.md` qFatal capture. +3. **Cross-validator HEVC re-anchor** — re-capture `ffmpeg -hwaccel v4l2request -i bbb_720p10s_hevc.mp4 -frames:v 5 -f null -` strace + ftrace on linux-7. Capture the VERBATIM payload bytes for SPS, PPS, DECODE_PARAMS, SLICE_PARAMS array entries, SCALING_MATRIX (if present). Phase 4 transcription anchor (per [`memory/feedback_review_empirical_over_theoretical.md`](../../.claude/projects/-home-mfritsche-src-fresnel-fourier/memory/feedback_review_empirical_over_theoretical.md)). +4. **Pre-fix scratch test** — apply Bug 1 (config break) + Bug 2 (picture.c reject removal) as a scratch patch on a throwaway branch; rebuild WITHOUT touching h265.c (keep it commented out). Verify mpv `--hwdec=vaapi` MPEG-2 + H.264 still pass (no collateral). Then attempt to compile h265.c uncommented to confirm the staging-era CID errors. Document the exact compile errors as Phase 3 baseline. +5. **Verify the slice-count for bbb_720p10s_hevc.mp4** via FFmpeg's slice count or by counting `decode_slice` entries — small (1-4 per frame) vs large affects whether iter2 needs to allocate slice_params arrays statically or dynamically. +6. **Determine if linux-7 brings rkvdec_hevc.c into mainline** — if yes, read the kernel-side HEVC handler for additional contract verification (especially around DECODE_PARAMS and slice_params dynamic-array semantics). + +## Phase 4 plan inputs + +Once Phase 3 baseline confirms the situation on linux-7, Phase 4 plan should specify: + +1. **Diff scope**: + - `src/config.c:64-69` — add `break;` for VAProfileHEVCMain case (3-line change, mirrors iter1 Bug 1 fix shape). + - `src/picture.c:204-206` — replace explicit reject with dispatch to `h265_set_controls()`. + - `src/h265.c` — substantial rewrite (~250-400 lines): + - Replace V4L2_CID_MPEG_VIDEO_HEVC_* with V4L2_CID_STATELESS_HEVC_*. + - Restructure for slice_params dynamic-array (per-slice append in codec_store_buffer + flush at frame end). + - Add DECODE_PARAMS struct fill (split from slice_params). + - Add SCALING_MATRIX (conditional on iqmatrix_set + sps.scaling_list_enabled). + - Add device-wide DECODE_MODE + START_CODE at h265_set_controls first-call (or context init). + - Update flag bitmasks: collapse boolean SPS/PPS fields into u64 flags (analogous to MPEG-2 picture.flags collapse in iter1). + - `src/picture.c::codec_store_buffer` — add HEVC slice_params accumulation logic (append-not-overwrite for `VASliceParameterBufferType`). + - `src/surface.h::params.h265` — add a slice_params array (or pointer + count) to hold accumulated slice params. + - `src/meson.build` — uncomment `h265.c` (line 50) and `h265.h` (line 73). + - `include/hevc-ctrls.h` — keep as-is (not a duplication header; lower-risk path). + - `src/context.c` — possibly extend H.264 device-init block to also set HEVC's device-wide DECODE_MODE + START_CODE if h265_set_controls doesn't handle that itself. + +2. **Contract anchor** (per `feedback_dev_process.md` Phase 6 contract-before-code): + - Cite verbatim from `:2090+` — the 7 stateless HEVC control struct definitions and flag constants. + - Cite verbatim from FFmpeg `libavcodec/v4l2_request_hevc.c:512-565` — the queue_decode batched submission shape. + - Cite Phase 3 Baseline C HEVC anchor's verbatim payload (re-captured on linux-7). + - If linux-7 has rkvdec_hevc.c in mainline, cite the driver's slice_params handling, DECODE_PARAMS handling. + +3. **Phase 7 verification harness**: re-use iter1's 5-criterion shape with HEVC fixture substituted. Add bonus byte-compare of post-fix VIDIOC_S_EXT_CTRLS payload vs Baseline C HEVC anchor (per [`memory/feedback_review_empirical_over_theoretical.md`](../../.claude/projects/-home-mfritsche-src-fresnel-fourier/memory/feedback_review_empirical_over_theoretical.md) — empirical wins). + +4. **Header deletion completeness check**: per [`memory/feedback_header_deletion_check.md`](../../.claude/projects/-home-mfritsche-src-fresnel-fourier/memory/feedback_header_deletion_check.md). For iter2: include/hevc-ctrls.h is preserved as the lower-risk path, so this rule mostly doesn't apply. But: any `` reference iter1 might have missed (defensive grep in Phase 6 — even though iter1's Commit D fixed the one we found in context.c, do another sweep). + +## Predicted iter2 outcome + +The bugs are well-bounded but the fix is structurally larger than iter1. Phase 4 plan will produce: + +- Trivial diffs for Bugs 1 + 2 + 6 (config.c break, picture.c dispatch, meson uncomment). +- Substantial rewrite of h265.c for Bugs 3-5 (~350 lines, ~3× iter1's mpeg2.c rewrite). +- New per-slice accumulation logic in picture.c::codec_store_buffer for the slice_params dynamic-array semantics. + +If Phase 7 misses a check, most likely culprits in priority order: + +1. **`VIDIOC_S_EXT_CTRLS` returns EINVAL** because of a struct-size mismatch (HEVC structs are large; `v4l2_ctrl_hevc_sps` is ~70 bytes, `_pps` ~80 bytes, `_decode_params` ~100+ bytes, `_slice_params` ~150 bytes per slice). Mitigation: `pahole` / `sizeof` against kernel UAPI; compare to Baseline C verbatim sizes. + +2. **Slice_params dynamic-array submission shape wrong** — kernel may reject if size is not a multiple of `sizeof(struct v4l2_ctrl_hevc_slice_params)`, or if num_slices doesn't match an internal kernel expectation. Phase 6 should test single-slice + multi-slice cases. + +3. **DECODE_PARAMS DPB ordering** — VAAPI's ReferenceFrames[] uses POC-sorted order; kernel's DECODE_PARAMS.dpb[] may want a specific order (long-term first, then short-term-curr-before, etc.). FFmpeg reference shows the canonical population. + +4. **SCALING_MATRIX presence/absence** — if BBB has flat scaling and we omit SCALING_MATRIX from the batch, kernel may default OK or may EINVAL if `pps.scaling_list_data_present_flag` is set. Phase 6 should test both branches if possible. + +5. **Pixel hash mismatch HW vs SW** — many possible causes (DPB ref ordering, slice_params bit_offset wrong, NAL unit type mis-extracted). Mitigation: byte-compare per-control payload against cross-validator before debugging pixels. + +If Phase 7 → Phase 4 loopback fires for any reason, the loopback target is bounded: contract is well-cited, mismatches localize to specific control struct fields. + +## What this leaves Phase 3 with + +Phase 3 baseline measurements should: + +1. Wait for linux-7 kernel upgrade (substrate update) and re-verify iter1 + T4 + SDDM. +2. Re-capture the cross-validator HEVC anchor on linux-7 (Baseline C-equivalent for HEVC). +3. Apply Bug 1 + Bug 2 as scratch patches to confirm the next failure mode is the h265.c compile error (not some new surprise). +4. Capture VAAPI VAPicture/VASlice/VAIQMatrix HEVC payload via LIBVA_TRACE for one frame's decode (to confirm field values our backend reads from VAAPI match what we expect to populate into V4L2 controls). + +After Phase 3 closes, Phase 4 plan can lock the implementation diff against the captured contract.