Phase 8 skeleton: public C API + first end-to-end smoke test

include/daedalus.h: stable C API surface exposing the 5 cycles
(VP9 IDCT 8x8, LPF wd=4, MC 8h, LPF wd=8; AV1 CDEF). Per-kernel
recipe-dispatch helpers default to the cycle 1-5 verdict
substrate (QPU for cycles 1+2+4, CPU for cycles 3+5); explicit
override available for benchmarking and runtime-aware scheduling.

src/daedalus_core.c: NEON-path implementation of all 5 kernels
wrapped behind the public API. QPU path stubbed out (returns -1)
since wiring v3d_runner into daedalus_ctx is the next Phase 8
sub-step; with has_qpu=0 the recipe falls back to CPU cleanly.

tests/test_api_idct.c: 64-block IDCT through the public recipe
dispatch, bit-exact vs C ref. PASS 4096/4096 bytes — proves the
API surface compiles, library links, dispatch routing works, and
NEON fallback delivers correct results.

docs/phase8_scoping.md: architecture options (A=userspace V4L2,
B=kernel V4L2 shim, C=direct libva); pick A for v1; explicitly
out-of-scope work tracked.

Next Phase 8 sub-step: wire v3d_runner into daedalus_ctx so
has_qpu=1 and QPU dispatch goes through the API too. After that:
V4L2 ioctl glue, bitstream parser, superblock loop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 13:54:43 +00:00
parent 5223d3cb3f
commit 760f6a4060
5 changed files with 733 additions and 0 deletions
+26
View File
@@ -276,6 +276,32 @@ if (DAEDALUS_BUILD_VULKAN)
add_dependencies(bench_v3d_cdef daedalus_shaders) add_dependencies(bench_v3d_cdef daedalus_shaders)
target_link_libraries(bench_v3d_cdef PRIVATE v3d_runner Vulkan::Vulkan) target_link_libraries(bench_v3d_cdef PRIVATE v3d_runner Vulkan::Vulkan)
target_compile_options(bench_v3d_cdef PRIVATE -O2) target_compile_options(bench_v3d_cdef PRIVATE -O2)
endif()
# ---- Phase 8 — public C API library + smoke test ---------------------------
add_library(daedalus_core STATIC
src/daedalus_core.c
${FFASM_SOURCES}
${FFASM_LPF_SOURCES}
${FFASM_MC_SOURCES}
${FFC_MC_SOURCES}
${DAV1D_CDEF_ASM_SOURCES}
${DAV1D_CDEF_C_SOURCES}
)
target_include_directories(daedalus_core PUBLIC include)
target_compile_options(daedalus_core PRIVATE -O2)
add_executable(test_api_idct
tests/test_api_idct.c
tests/vp9_idct8_ref.c
)
target_link_libraries(test_api_idct PRIVATE daedalus_core)
target_compile_options(test_api_idct PRIVATE -O2)
if (DAEDALUS_BUILD_VULKAN)
# (re-open the conditional so the closing endif() below balances)
# M4 — concurrent CPU(NEON) + QPU bench. Links the FFmpeg NEON # M4 — concurrent CPU(NEON) + QPU bench. Links the FFmpeg NEON
# snapshot so we can run real NEON kernels on pinned CPU cores # snapshot so we can run real NEON kernels on pinned CPU cores
+142
View File
@@ -0,0 +1,142 @@
---
phase: 8
status: scoping (architecture options + tractable-first-step picked)
date_opened: 2026-05-18
prereqs: cycles 1-5 closed (IDCT, LPF wd=4, MC, LPF wd=8, CDEF)
consumer_target: libva-v4l2-request-fourier → firefox/chromium-fourier
---
# Phase 8 — V4L2 deployment scoping
## What Phase 8 is
The "deliver the work" phase. Cycles 1-5 produced 5 individually-
measured per-block kernels (3 deployed on QPU, 2 on CPU per the
deployment recipe). Phase 8 makes those kernels add up to a
decoded video at the user's display.
Per `project_consumer_target.md`, the integration target is
**libva-v4l2-request-fourier**: a V4L2 stateless decoder node
exposing a VP9 (later AV1) contract, bridged via VA-API to
browser-fourier builds. Same plumbing mfritsche already runs for
HEVC/RK3588, different decoder backend.
## Architecture stack
```
+-------------------------------------------------------+
| firefox-fourier / chromium-fourier (already builds) |
+-------------------------------------------------------+
| VA-API |
+-------------------------------------------------------+
| libva-v4l2-request-fourier (already runs for HEVC) |
+-------------------------------------------------------+
| V4L2 stateless ioctl interface (kernel uAPI) |
+-------------------------------------------------------+
| daedalus-fourier V4L2 shim (NEW — Phase 8 work) |
| ↳ Parses bitstream control structs (V4L2_CID_STATELESS_VP9_*)
| ↳ Drives per-superblock decode loop
| ↳ Dispatches per-kernel to CPU NEON or V3D QPU (recipe)
+-------------------------------------------------------+
| daedalus-fourier core library (NEW Phase 8 — wraps |
| ↳ kernels from cycles 1-5) |
+-------------------------------------------------------+
| V3D 7.1 Mesa userspace + ARM NEON |
+-------------------------------------------------------+
```
## Three architecture options
### Option A — Userspace V4L2 emulation (recommended for v1)
Implement a userspace `videodev2`-compatible loopback device
(via `v4l2loopback` or a custom UIO-style approach) that exposes
`/dev/videoNN` with the VP9 stateless contract. libva-v4l2-
request-fourier talks to this normally.
**Pros**: stays entirely in userspace; no kernel module work; can
iterate quickly; isolation from kernel crash domain. The
daedalus-fourier daemon runs as a regular Linux process, taking
V4L2 ioctls (via the loopback shim) and emitting decoded frames.
**Cons**: v4l2loopback is loosely maintained; userspace V4L2 has
some semantic quirks (DRM/PRIME buffer sharing is harder than in
a real kernel driver).
### Option B — Tiny kernel V4L2 shim
A small kernel module that registers as a V4L2 device, takes the
ioctls, and forwards bitstream blobs + control structs to a
userspace daemon (the actual decoder) over a UNIX socket or
character-device chardev. Daemon decodes and posts frames back.
**Pros**: a real `/dev/videoNN` with proper VFL_TYPE_VIDEO
semantics. DRM PRIME buffer sharing works correctly.
**Cons**: kernel module work. Cross-process buffer marshaling
adds latency. Out-of-tree maintenance burden.
### Option C — Direct libva integration (not recommended)
Skip V4L2 entirely; implement a libva backend module directly.
**Pros**: avoids the V4L2 wrapper layer entirely.
**Cons**: contradicts `project_consumer_target.md` (decision to
use V4L2 path locked in). libva backend maintenance burden is
roughly equivalent to V4L2 shim with no portability gain.
**Pick A** for v1; revisit if userspace V4L2 semantics block
DRM PRIME / dmabuf for browser zero-copy.
## What's tractable this session
Phase 8 in full is **days of work** (V4L2 ioctl glue, bitstream
parser, superblock loop, frame buffer management, dmabuf handling,
end-to-end test against a real VP9 clip). Out of scope for a
single session continuation.
What IS tractable now:
1. **Public C API header** (`include/daedalus.h`): declare the
library's stable function surface for the 5 kernels +
substrate selection + init/teardown. Future Phase 8 V4L2 shim
consumes this header. This:
- Locks the API shape so V4L2 work doesn't need to plumb
through internal types.
- Documents which kernels deploy where (recipe encoded in API).
- Forces a clean separation between "kernel work" (cycles 1-5)
and "decoder pipeline" (Phase 8).
2. **A minimal core library** (`src/daedalus_core.{h,c}`):
skeleton that compiles, has the right typedefs and dispatch
tables, but body of each function is `assert(0 && "TODO")`.
Builds against existing kernel implementations.
3. **One integration test** (`tests/test_idct_through_api.c`):
exercise the public API for ONE kernel end-to-end. Proves the
API can connect to existing benches.
This commit gives the integration target something concrete to
hook into without prejudging V4L2 architecture (A/B/C).
## Out of scope for this session
- v4l2loopback setup (Option A specifics).
- VP9 bitstream parser (huge — borrow from FFmpeg / VP9 reference).
- Superblock-level decode loop.
- Frame buffer / dmabuf integration.
- libva-v4l2-request-fourier modifications (separate sibling repo).
These are tracked as future phases / issues.
## Acceptance for this Phase 8 scoping deliverable
- `include/daedalus.h` exists and is documented.
- `src/daedalus_core.{h,c}` skeleton compiles + links into the
existing CMake build.
- One pass-through test (`test_idct_through_api`) runs and
exercises the public API path for at least one kernel,
producing the same M1 bit-exact result the cycle 1 bench did.
- Recipe table (which kernel runs where) is documented in the
header and the docs/k* phase7 docs cross-reference it.
+210
View File
@@ -0,0 +1,210 @@
/*
* daedalus-fourier — public C API.
*
* Stable surface for the integration layer (Phase 8 V4L2 shim,
* libva-v4l2-request-fourier consumer, or any future skin) to
* dispatch per-kernel work to the right substrate per the
* cycle 1-5 deployment recipe.
*
* Recipe (verdict at end of cycles 1-5, see docs/k*_phase7.md):
*
* VP9 IDCT 8x8 → V3D QPU (R=0.92 GREEN; M4 +7.2 %)
* VP9 LPF wd=4 inner → V3D QPU (R=0.41 ORANGE; M4 +6.9 %)
* VP9 MC 8-tap horiz → CPU NEON (R=0.067 RED; M4 -19.5 %)
* VP9 LPF wd=8 inner → V3D QPU (R=0.34 ORANGE; M4 +4.1 %)
* AV1 CDEF 8x8 luma → CPU NEON (R=0.116 ORANGE; QPU = opportunistic helper at 0.4 Mblock/s)
*
* The API exposes BOTH substrates for every kernel — the
* integration layer can override the recipe at runtime if it
* has scheduler knowledge the kernel-level R-band measurement
* didn't capture. The recommended path is to use
* `daedalus_recipe_dispatch_*` which picks the recipe substrate
* automatically.
*
* License: BSD-2-Clause. This header is part of the library API
* boundary; the implementation links against vendored
* LGPL-2.1+ FFmpeg snapshot and BSD-2-Clause dav1d snapshot.
*
* Threading: a `daedalus_ctx *` owns Vulkan + V3D state. A
* context is single-threaded; use one per worker thread if you
* need parallelism on the QPU side. NEON-side dispatch is
* stateless and re-entrant.
*
* ABI: pre-1.0 — no stability guarantees yet. The function names
* and signatures will become ABI-stable at v1.0; until then the
* integration layer should rebuild against the headers it links
* with.
*/
#ifndef DAEDALUS_FOURIER_H
#define DAEDALUS_FOURIER_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* -------------------------------------------------------------------
* Substrate selection
*
* Most callers should NOT specify a substrate — use the
* `daedalus_recipe_dispatch_*` family below, which picks the
* substrate per the cycles-1-5 verdict. Explicit substrate
* selection is for benchmarking, debugging, and future
* runtime-aware schedulers.
* ----------------------------------------------------------------- */
typedef enum {
DAEDALUS_SUBSTRATE_AUTO = 0, /* per recipe table */
DAEDALUS_SUBSTRATE_CPU = 1, /* force ARM NEON */
DAEDALUS_SUBSTRATE_QPU = 2, /* force V3D compute */
} daedalus_substrate;
/* -------------------------------------------------------------------
* Context lifecycle
* ----------------------------------------------------------------- */
typedef struct daedalus_ctx daedalus_ctx;
/* Create a context. Initialises V3D Vulkan device if available;
* NEON-only fallback OK if V3D init fails. Returns NULL on alloc
* failure. */
daedalus_ctx *daedalus_ctx_create(void);
/* Returns 1 if QPU dispatch is available on this context, 0 if
* NEON-only. Useful for the integration layer to short-circuit
* QPU dispatch attempts. */
int daedalus_ctx_has_qpu(const daedalus_ctx *ctx);
void daedalus_ctx_destroy(daedalus_ctx *ctx);
/* -------------------------------------------------------------------
* VP9 IDCT 8x8 add — cycle 1 (QPU by recipe)
*
* For each of n_blocks: take 64 int16 coefficients, perform 8x8
* inverse DCT, add to dst[r,c] = clamp(dst[r,c] + ((q + 16)>>5)).
*
* `meta` is an array of (dst_byte_offset, block_x, block_y) for
* each block, where dst_byte_offset is byte offset into dst.
*
* Returns 0 on success, negative errno-like on failure.
* ----------------------------------------------------------------- */
typedef struct {
uint32_t dst_off; /* byte offset into dst */
uint32_t block_x; /* used only by QPU path for placement */
uint32_t block_y;
uint32_t _pad;
} daedalus_idct8_meta;
int daedalus_recipe_dispatch_vp9_idct8(
daedalus_ctx *ctx,
uint8_t *dst, size_t dst_stride,
const int16_t *coeffs, size_t n_blocks,
const daedalus_idct8_meta *meta);
int daedalus_dispatch_vp9_idct8(
daedalus_ctx *ctx,
daedalus_substrate sub,
uint8_t *dst, size_t dst_stride,
const int16_t *coeffs, size_t n_blocks,
const daedalus_idct8_meta *meta);
/* -------------------------------------------------------------------
* VP9 LPF wd=4 / wd=8 — cycles 2 and 4 (QPU by recipe)
*
* Loop filter at horizontal edge crossing pixel column 4 of an
* 8x8 block. Per-edge thresholds (E, I, H).
* ----------------------------------------------------------------- */
typedef struct {
uint32_t dst_off; /* byte offset into dst, at col 4 of edge */
int32_t E, I, H;
} daedalus_lpf_meta;
int daedalus_recipe_dispatch_vp9_lpf4(
daedalus_ctx *ctx,
uint8_t *dst, size_t dst_stride,
size_t n_edges, const daedalus_lpf_meta *meta);
int daedalus_recipe_dispatch_vp9_lpf8(
daedalus_ctx *ctx,
uint8_t *dst, size_t dst_stride,
size_t n_edges, const daedalus_lpf_meta *meta);
int daedalus_dispatch_vp9_lpf4(daedalus_ctx *ctx, daedalus_substrate sub,
uint8_t *dst, size_t dst_stride,
size_t n_edges, const daedalus_lpf_meta *meta);
int daedalus_dispatch_vp9_lpf8(daedalus_ctx *ctx, daedalus_substrate sub,
uint8_t *dst, size_t dst_stride,
size_t n_edges, const daedalus_lpf_meta *meta);
/* -------------------------------------------------------------------
* VP9 MC 8-tap horizontal — cycle 3 (CPU by recipe)
*
* Subpel-fractional 8-tap horizontal filter; mx selects filter
* row. CPU path is the high-performance default; QPU path is
* available but never recommended by the recipe.
* ----------------------------------------------------------------- */
typedef struct {
uint32_t dst_off;
uint32_t src_off; /* raw, no pre-advance — shader handles -3 internally */
int32_t mx;
uint32_t _pad;
} daedalus_mc_meta;
int daedalus_recipe_dispatch_vp9_mc_8h(
daedalus_ctx *ctx,
uint8_t *dst, size_t dst_stride,
const uint8_t *src, size_t src_stride,
size_t n_blocks, const daedalus_mc_meta *meta);
int daedalus_dispatch_vp9_mc_8h(daedalus_ctx *ctx, daedalus_substrate sub,
uint8_t *dst, size_t dst_stride,
const uint8_t *src, size_t src_stride,
size_t n_blocks, const daedalus_mc_meta *meta);
/* -------------------------------------------------------------------
* AV1 CDEF 8x8 luma — cycle 5 (CPU by recipe; QPU opportunistic)
*
* tmp is an array of n_blocks * 192 uint16, with the padded-buffer
* layout that dav1d's NEON expects (stride 16, padding 2-rows-top +
* 2-cols-left + 2-cols-right + 2-rows-bottom). Caller supplies
* tmp populated with either source pixels (if all edges valid) or
* INT16_MIN sentinels at the boundary (if edge filtered out).
* ----------------------------------------------------------------- */
typedef struct {
uint32_t dst_off;
uint32_t tmp_off_u16; /* offset to block-origin in tmp[] (= padded_origin + 2*16+2) */
int32_t pri_strength; /* 1..7 */
int32_t sec_strength; /* 1..4 */
int32_t dir; /* 0..7 */
int32_t damping; /* 1..6 */
} daedalus_cdef_meta;
int daedalus_recipe_dispatch_cdef_8x8(
daedalus_ctx *ctx,
uint8_t *dst, size_t dst_stride,
const uint16_t *tmp,
size_t n_blocks, const daedalus_cdef_meta *meta);
int daedalus_dispatch_cdef_8x8(daedalus_ctx *ctx, daedalus_substrate sub,
uint8_t *dst, size_t dst_stride,
const uint16_t *tmp,
size_t n_blocks, const daedalus_cdef_meta *meta);
/* -------------------------------------------------------------------
* Recipe query — what does the API recommend for each kernel?
* ----------------------------------------------------------------- */
typedef enum {
DAEDALUS_KERNEL_VP9_IDCT8 = 1,
DAEDALUS_KERNEL_VP9_LPF4_INNER = 2,
DAEDALUS_KERNEL_VP9_MC_8H = 3,
DAEDALUS_KERNEL_VP9_LPF8_INNER = 4,
DAEDALUS_KERNEL_AV1_CDEF_8X8 = 5,
} daedalus_kernel;
daedalus_substrate daedalus_recipe_substrate_for(daedalus_kernel k);
#ifdef __cplusplus
}
#endif
#endif /* DAEDALUS_FOURIER_H */
+252
View File
@@ -0,0 +1,252 @@
/*
* daedalus-fourier core library — Phase 8 skeleton.
*
* Wraps cycles 1-5 kernels behind the public C API in
* include/daedalus.h. Recipe dispatch routes per-kernel to the
* verdict substrate from each cycle's Phase 7 doc.
*
* License: BSD-2-Clause. Links vendored FFmpeg LGPL-2.1+ +
* dav1d BSD-2-Clause NEON snapshots.
*/
#include "../include/daedalus.h"
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <assert.h>
/* -------------------- Context -------------------- */
struct daedalus_ctx {
/* For Phase 8 skeleton: just a flag. Real impl would hold the
* v3d_runner + per-kernel pipeline handles. */
int has_qpu;
};
daedalus_ctx *daedalus_ctx_create(void)
{
daedalus_ctx *ctx = calloc(1, sizeof(*ctx));
if (!ctx) return NULL;
/* Phase 8 deferred: real impl probes V3D Vulkan device; for now
* default to CPU-only (NEON paths are always available). */
ctx->has_qpu = 0;
return ctx;
}
int daedalus_ctx_has_qpu(const daedalus_ctx *ctx)
{
return ctx ? ctx->has_qpu : 0;
}
void daedalus_ctx_destroy(daedalus_ctx *ctx)
{
free(ctx);
}
/* -------------------- Recipe query -------------------- */
daedalus_substrate daedalus_recipe_substrate_for(daedalus_kernel k)
{
switch (k) {
case DAEDALUS_KERNEL_VP9_IDCT8: return DAEDALUS_SUBSTRATE_QPU;
case DAEDALUS_KERNEL_VP9_LPF4_INNER: return DAEDALUS_SUBSTRATE_QPU;
case DAEDALUS_KERNEL_VP9_MC_8H: return DAEDALUS_SUBSTRATE_CPU;
case DAEDALUS_KERNEL_VP9_LPF8_INNER: return DAEDALUS_SUBSTRATE_QPU;
case DAEDALUS_KERNEL_AV1_CDEF_8X8: return DAEDALUS_SUBSTRATE_CPU;
}
return DAEDALUS_SUBSTRATE_CPU; /* defensive default */
}
/* -------------------- NEON externs (per cycle bench links) ----- */
extern void ff_vp9_idct_idct_8x8_add_neon(uint8_t *dst, ptrdiff_t stride,
int16_t *block, int eob);
extern void ff_vp9_loop_filter_h_4_8_neon(uint8_t *dst, ptrdiff_t stride,
int E, int I, int H);
extern void ff_vp9_loop_filter_h_8_8_neon(uint8_t *dst, ptrdiff_t stride,
int E, int I, int H);
extern void ff_vp9_put_regular8_h_neon(uint8_t *dst, ptrdiff_t dst_stride,
const uint8_t *src, ptrdiff_t src_stride,
int h, int mx, int my);
extern void dav1d_cdef_filter8_8bpc_neon(uint8_t *dst, ptrdiff_t dst_stride,
const uint16_t *tmp,
int pri_strength, int sec_strength,
int dir, int damping, int h,
size_t edges);
/* -------------------- CPU dispatch implementations -------------- */
static int dispatch_idct8_cpu(daedalus_ctx *ctx,
uint8_t *dst, size_t dst_stride,
const int16_t *coeffs, size_t n_blocks,
const daedalus_idct8_meta *meta)
{
(void) ctx;
int16_t scratch[64];
for (size_t i = 0; i < n_blocks; i++) {
memcpy(scratch, coeffs + i * 64, 64 * sizeof(int16_t));
ff_vp9_idct_idct_8x8_add_neon(dst + meta[i].dst_off,
(ptrdiff_t) dst_stride,
scratch, 64);
}
return 0;
}
static int dispatch_lpf_cpu(daedalus_ctx *ctx, int wd_8,
uint8_t *dst, size_t dst_stride,
size_t n_edges, const daedalus_lpf_meta *meta)
{
(void) ctx;
for (size_t i = 0; i < n_edges; i++) {
uint8_t *p = dst + meta[i].dst_off;
if (wd_8) ff_vp9_loop_filter_h_8_8_neon(p, (ptrdiff_t) dst_stride,
meta[i].E, meta[i].I, meta[i].H);
else ff_vp9_loop_filter_h_4_8_neon(p, (ptrdiff_t) dst_stride,
meta[i].E, meta[i].I, meta[i].H);
}
return 0;
}
static int dispatch_mc_8h_cpu(daedalus_ctx *ctx,
uint8_t *dst, size_t dst_stride,
const uint8_t *src, size_t src_stride,
size_t n_blocks, const daedalus_mc_meta *meta)
{
(void) ctx;
for (size_t i = 0; i < n_blocks; i++) {
ff_vp9_put_regular8_h_neon(dst + meta[i].dst_off,
(ptrdiff_t) dst_stride,
src + meta[i].src_off + 3,
(ptrdiff_t) src_stride,
8, meta[i].mx, 0);
}
return 0;
}
static int dispatch_cdef_cpu(daedalus_ctx *ctx,
uint8_t *dst, size_t dst_stride,
const uint16_t *tmp,
size_t n_blocks, const daedalus_cdef_meta *meta)
{
(void) ctx;
for (size_t i = 0; i < n_blocks; i++) {
dav1d_cdef_filter8_8bpc_neon(dst + meta[i].dst_off,
(ptrdiff_t) dst_stride,
tmp + meta[i].tmp_off_u16,
meta[i].pri_strength,
meta[i].sec_strength,
meta[i].dir, meta[i].damping, 8, 0);
}
return 0;
}
/* -------------------- Public dispatch entry points -------------- */
#define ROUTE(_kernel, _cpu_fn, ...) \
daedalus_substrate eff = sub; \
if (eff == DAEDALUS_SUBSTRATE_AUTO) eff = daedalus_recipe_substrate_for(_kernel); \
if (eff == DAEDALUS_SUBSTRATE_QPU && !daedalus_ctx_has_qpu(ctx)) \
eff = DAEDALUS_SUBSTRATE_CPU; \
if (eff == DAEDALUS_SUBSTRATE_CPU) return _cpu_fn(ctx, __VA_ARGS__); \
return -1 /* QPU path not yet wired in Phase 8 skeleton */
int daedalus_dispatch_vp9_idct8(daedalus_ctx *ctx, daedalus_substrate sub,
uint8_t *dst, size_t dst_stride,
const int16_t *coeffs, size_t n_blocks,
const daedalus_idct8_meta *meta)
{
ROUTE(DAEDALUS_KERNEL_VP9_IDCT8, dispatch_idct8_cpu,
dst, dst_stride, coeffs, n_blocks, meta);
}
int daedalus_dispatch_vp9_lpf4(daedalus_ctx *ctx, daedalus_substrate sub,
uint8_t *dst, size_t dst_stride,
size_t n_edges, const daedalus_lpf_meta *meta)
{
daedalus_substrate eff = sub;
if (eff == DAEDALUS_SUBSTRATE_AUTO)
eff = daedalus_recipe_substrate_for(DAEDALUS_KERNEL_VP9_LPF4_INNER);
if (eff == DAEDALUS_SUBSTRATE_QPU && !daedalus_ctx_has_qpu(ctx))
eff = DAEDALUS_SUBSTRATE_CPU;
if (eff == DAEDALUS_SUBSTRATE_CPU)
return dispatch_lpf_cpu(ctx, 0, dst, dst_stride, n_edges, meta);
return -1;
}
int daedalus_dispatch_vp9_lpf8(daedalus_ctx *ctx, daedalus_substrate sub,
uint8_t *dst, size_t dst_stride,
size_t n_edges, const daedalus_lpf_meta *meta)
{
daedalus_substrate eff = sub;
if (eff == DAEDALUS_SUBSTRATE_AUTO)
eff = daedalus_recipe_substrate_for(DAEDALUS_KERNEL_VP9_LPF8_INNER);
if (eff == DAEDALUS_SUBSTRATE_QPU && !daedalus_ctx_has_qpu(ctx))
eff = DAEDALUS_SUBSTRATE_CPU;
if (eff == DAEDALUS_SUBSTRATE_CPU)
return dispatch_lpf_cpu(ctx, 1, dst, dst_stride, n_edges, meta);
return -1;
}
int daedalus_dispatch_vp9_mc_8h(daedalus_ctx *ctx, daedalus_substrate sub,
uint8_t *dst, size_t dst_stride,
const uint8_t *src, size_t src_stride,
size_t n_blocks, const daedalus_mc_meta *meta)
{
ROUTE(DAEDALUS_KERNEL_VP9_MC_8H, dispatch_mc_8h_cpu,
dst, dst_stride, src, src_stride, n_blocks, meta);
}
int daedalus_dispatch_cdef_8x8(daedalus_ctx *ctx, daedalus_substrate sub,
uint8_t *dst, size_t dst_stride,
const uint16_t *tmp,
size_t n_blocks, const daedalus_cdef_meta *meta)
{
ROUTE(DAEDALUS_KERNEL_AV1_CDEF_8X8, dispatch_cdef_cpu,
dst, dst_stride, tmp, n_blocks, meta);
}
/* -------------------- Recipe convenience wrappers --------------- */
int daedalus_recipe_dispatch_vp9_idct8(daedalus_ctx *ctx,
uint8_t *dst, size_t dst_stride,
const int16_t *coeffs, size_t n_blocks,
const daedalus_idct8_meta *meta)
{
return daedalus_dispatch_vp9_idct8(ctx, DAEDALUS_SUBSTRATE_AUTO,
dst, dst_stride, coeffs, n_blocks, meta);
}
int daedalus_recipe_dispatch_vp9_lpf4(daedalus_ctx *ctx,
uint8_t *dst, size_t dst_stride,
size_t n_edges, const daedalus_lpf_meta *meta)
{
return daedalus_dispatch_vp9_lpf4(ctx, DAEDALUS_SUBSTRATE_AUTO,
dst, dst_stride, n_edges, meta);
}
int daedalus_recipe_dispatch_vp9_lpf8(daedalus_ctx *ctx,
uint8_t *dst, size_t dst_stride,
size_t n_edges, const daedalus_lpf_meta *meta)
{
return daedalus_dispatch_vp9_lpf8(ctx, DAEDALUS_SUBSTRATE_AUTO,
dst, dst_stride, n_edges, meta);
}
int daedalus_recipe_dispatch_vp9_mc_8h(daedalus_ctx *ctx,
uint8_t *dst, size_t dst_stride,
const uint8_t *src, size_t src_stride,
size_t n_blocks, const daedalus_mc_meta *meta)
{
return daedalus_dispatch_vp9_mc_8h(ctx, DAEDALUS_SUBSTRATE_AUTO,
dst, dst_stride, src, src_stride, n_blocks, meta);
}
int daedalus_recipe_dispatch_cdef_8x8(daedalus_ctx *ctx,
uint8_t *dst, size_t dst_stride,
const uint16_t *tmp,
size_t n_blocks, const daedalus_cdef_meta *meta)
{
return daedalus_dispatch_cdef_8x8(ctx, DAEDALUS_SUBSTRATE_AUTO,
dst, dst_stride, tmp, n_blocks, meta);
}
+103
View File
@@ -0,0 +1,103 @@
/*
* Phase 8 — first end-to-end test through the public API.
*
* Exercises `daedalus_recipe_dispatch_vp9_idct8` end-to-end:
* 1. Create context.
* 2. Generate random VP9 coefficient blocks + dst pixels.
* 3. Compute reference output via the C ref (tests/vp9_idct8_ref.c).
* 4. Run public API dispatch on a copy of dst.
* 5. Assert bit-exact.
*
* In Phase 8 skeleton, the API routes to CPU NEON (QPU dispatch
* not yet wired through the API). Bit-exact gate against C ref
* still passes because the underlying NEON kernel was the cycle 1
* reference.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "../include/daedalus.h"
extern void daedalus_vp9_idct_idct_8x8_add_ref(
uint8_t *dst, ptrdiff_t stride, int16_t *block, int eob);
#define BLOCKS_W 8
#define BLOCKS_H 8
#define N_BLOCKS (BLOCKS_W * BLOCKS_H)
#define DST_STRIDE (BLOCKS_W * 8)
#define DST_BYTES (BLOCKS_H * 8 * DST_STRIDE)
static uint64_t xs_state = 0xa57edbeef5717ULL;
static inline uint64_t xs(void) {
uint64_t x = xs_state;
x ^= x << 13; x ^= x >> 7; x ^= x << 17;
return xs_state = x;
}
int main(void)
{
daedalus_ctx *ctx = daedalus_ctx_create();
if (!ctx) { fprintf(stderr, "ctx create failed\n"); return 1; }
printf("=== Phase 8 API smoke: VP9 IDCT 8x8 via recipe dispatch ===\n");
printf(" has_qpu: %d (Phase 8 skeleton: NEON-only)\n",
daedalus_ctx_has_qpu(ctx));
printf(" recipe substrate for VP9_IDCT8: %d (1=CPU, 2=QPU)\n",
(int) daedalus_recipe_substrate_for(DAEDALUS_KERNEL_VP9_IDCT8));
/* Generate random VP9 IDCT inputs: 64-coef blocks + a dst surface. */
int16_t coeffs[N_BLOCKS * 64];
memset(coeffs, 0, sizeof(coeffs));
for (int i = 0; i < N_BLOCKS; i++) {
/* Sparse non-zero coefs to keep range realistic. */
int n = 1 + (int)(xs() % 16);
for (int j = 0; j < n; j++) {
int pos = (int)(xs() % 64);
int16_t v = (int16_t)((int)(xs() % 8192) - 4096);
coeffs[i * 64 + pos] = v;
}
}
uint8_t dst_ref[DST_BYTES], dst_api[DST_BYTES];
for (int i = 0; i < DST_BYTES; i++)
dst_ref[i] = dst_api[i] = (uint8_t)(xs() & 0xff);
/* 8x8 grid of 8x8 blocks. Block (bx, by) at byte offset
* by*8*stride + bx*8. */
daedalus_idct8_meta meta[N_BLOCKS];
for (int by = 0; by < BLOCKS_H; by++) {
for (int bx = 0; bx < BLOCKS_W; bx++) {
int i = by * BLOCKS_W + bx;
meta[i].dst_off = (uint32_t)(by * 8 * DST_STRIDE + bx * 8);
meta[i].block_x = (uint32_t) bx;
meta[i].block_y = (uint32_t) by;
meta[i]._pad = 0;
}
}
/* Compute reference via the C ref (mutates a scratch copy of
* coeffs because the C ref destroys its input). */
int16_t scratch[64];
for (int i = 0; i < N_BLOCKS; i++) {
memcpy(scratch, coeffs + i * 64, 64 * sizeof(int16_t));
daedalus_vp9_idct_idct_8x8_add_ref(dst_ref + meta[i].dst_off,
DST_STRIDE, scratch, 64);
}
/* Dispatch through the public API. */
int rc = daedalus_recipe_dispatch_vp9_idct8(ctx, dst_api, DST_STRIDE,
coeffs, N_BLOCKS, meta);
if (rc != 0) { fprintf(stderr, "API dispatch failed rc=%d\n", rc); return 1; }
/* Compare. */
int diffs = 0;
for (int i = 0; i < DST_BYTES; i++) if (dst_ref[i] != dst_api[i]) diffs++;
printf(" bytes bit-exact: %d / %d (%.4f%%)\n",
DST_BYTES - diffs, DST_BYTES, 100.0 * (DST_BYTES - diffs) / DST_BYTES);
daedalus_ctx_destroy(ctx);
return diffs == 0 ? 0 : 1;
}