|
| 1 | +# OpenSpec: issue-224-cbuffer-export |
| 2 | + |
| 3 | +## Summary |
| 4 | + |
| 5 | +Expose `rdc cbuffer` as a first-class CLI command with decoded JSON output and optional |
| 6 | +raw binary export (`--raw`). |
| 7 | + |
| 8 | +## Context and Motivation |
| 9 | + |
| 10 | +OpenSpec phase2-buffer-decode (archived 2026-02-19) originally planned three CLI commands: |
| 11 | +`rdc cbuffer`, `rdc vbuffer`, and `rdc ibuffer`. The daemon handler `cbuffer_decode` and its |
| 12 | +VFS route shipped in that phase. The CLI half — `rdc cbuffer` — was never implemented. |
| 13 | +This change completes that unshipped work. |
| 14 | + |
| 15 | +GitHub issue #224 (part 2) tracks the gap. Users who rely on `rdc buffer --raw` for raw bytes |
| 16 | +have no ergonomic path to decoded constant-buffer variables without constructing VFS paths |
| 17 | +manually. |
| 18 | + |
| 19 | +**Scope note:** The archived phase2-buffer-decode plan envisioned `rdc cbuffer` as a thin VFS |
| 20 | +`cat` wrapper; this change implements a richer direct command with decoded JSON output, a new |
| 21 | +`cbuffer_raw` handler, and a new VFS `leaf_bin` route — reviewers should not expect a pure VFS |
| 22 | +wrapper. |
| 23 | + |
| 24 | +## Design |
| 25 | + |
| 26 | +### New command: `rdc cbuffer` |
| 27 | + |
| 28 | +``` |
| 29 | +rdc cbuffer [EID] --stage [vs|hs|ds|gs|ps|cs] --set N --binding N [--json] [--raw -o file.bin] |
| 30 | +``` |
| 31 | + |
| 32 | +- `EID`: optional; resolved via `complete_eid` if omitted (matches existing `rdc buffer` pattern). |
| 33 | +- `--stage`: default `ps`. |
| 34 | +- `--set`: default `0`. |
| 35 | +- `--binding`: default `0`. |
| 36 | +- `--json`: emit decoded variables as JSON (default output mode). |
| 37 | +- `--raw -o file.bin`: export the raw constant-buffer bytes to a file. |
| 38 | + |
| 39 | +### Decoded path |
| 40 | + |
| 41 | +Calls the existing `cbuffer_decode` daemon handler unchanged. Returns |
| 42 | +`{"eid", "set", "binding", "variables": [{name, type, value}, ...]}` and writes it via |
| 43 | +`write_json`. No daemon changes required for this path. |
| 44 | + |
| 45 | +### Raw path |
| 46 | + |
| 47 | +Adds a new `cbuffer_raw` daemon handler in `handlers/buffer.py`. The handler repeats the |
| 48 | +reflection lookup (`fixedBindSetOrSpace` / `fixedBindNumber`), obtains |
| 49 | +`GetConstantBlock(...).descriptor`, calls `controller.GetBufferData(resource, byteOffset, |
| 50 | +byteSize)`, writes the bytes to `state.temp_dir/cbuffer_<eid>_<set>_<binding>.bin`, and |
| 51 | +returns `{"path", "size"}`. |
| 52 | + |
| 53 | +The handler is exposed as a VFS `leaf_bin` route at |
| 54 | +`/draws/<eid>/cbuffer/<set>/<binding>/data` in `vfs/router.py`, mirroring the way `buf_raw` |
| 55 | +is wired at `/buffers/<id>/data`. The CLI calls |
| 56 | +`_export_vfs_path(f"/draws/{eid}/cbuffer/{set}/{binding}/data", output, raw)` (from |
| 57 | +`commands/export.py`), which follows the same `vfs_ls` + `resolve_path` → `_deliver_binary` |
| 58 | +flow used by `rdc buffer --raw`. `_deliver_binary` (vfs.py:216) is the final delivery |
| 59 | +step — it calls `call(match.handler, match.args)` through the VFS resolve layer; it is NOT |
| 60 | +a standalone "call handler, get path, write bytes" helper invoked directly. |
| 61 | + |
| 62 | +The `hasattr(pipe_state, "GetConstantBlock")` guard present in `cbuffer_decode` is preserved |
| 63 | +in `cbuffer_raw` for RenderDoc version drift safety. |
| 64 | + |
| 65 | +### New source file |
| 66 | + |
| 67 | +`src/rdc/commands/cbuffer.py` — registered in `src/rdc/cli.py` adjacent to `buffer_cmd` |
| 68 | +(~line 138). |
| 69 | + |
| 70 | +## Risks |
| 71 | + |
| 72 | +### Non-buffer-backed constant buffers |
| 73 | + |
| 74 | +`ConstantBlock.bufferBacked == False` for push constants (Vulkan) and D3D12 root constants. |
| 75 | +These have no backing buffer resource; `GetBufferData` would operate on a null resource. |
| 76 | + |
| 77 | +Defined behavior: `--raw` MUST return a JSON-RPC error with a descriptive message |
| 78 | +(e.g. `"cbuffer is not buffer-backed (push constant or root constant)"`) rather than crash |
| 79 | +or silently return zero bytes. Decoded `--json` output is unaffected and continues to work |
| 80 | +via `GetCBufferVariableContents`. |
| 81 | + |
| 82 | +### D3D12 root constants / register spaces |
| 83 | + |
| 84 | +The `fixedBindSetOrSpace` / `fixedBindNumber` mapping for D3D12 root constants cannot be |
| 85 | +verified on this Linux development machine. The Vulkan (vkcube) integration test exercises |
| 86 | +the buffer-backed path only. Behavior on D3D12 root-constant captures is verified by |
| 87 | +reporter @Misaka-Mikoto-Tech on real D3D12 hardware after the PR ships. |
| 88 | + |
| 89 | +### `_extract_value` type coverage (optional improvement) |
| 90 | + |
| 91 | +The existing `_extract_value` helper (`buffer.py:163-173`) handles only `f32v` members; |
| 92 | +integer and unsigned-integer shader variables degrade silently. `_flatten_shader_var` |
| 93 | +in `handlers/_helpers.py` already handles `u32v`/`s32v` and is used by `shader_constants`. |
| 94 | +Switching `cbuffer_decode` to use `_flatten_shader_var` is an optional polish step; it does |
| 95 | +not block this change but is tracked as a separate optional task. |
0 commit comments