scaffold: CMake + API skeleton + smoke test
First code on daedalus-decoder per the Phase 1 decisions merged 2026-05-24.
Repo skeleton only — no Vulkan pipeline yet, no shaders, no libavcodec
intercept. Establishes the build shape so subsequent work has a place
to land.
Layout:
LICENSE BSD-2-Clause (matches daedalus-fourier)
.gitignore build/, CMake artefacts, *.spv
CMakeLists.txt top-level — finds daedalus-fourier
≥0.1.0 via pkg-config (per §9.6
decision: find_package, pinned to
tagged release; .pc consumed via
pkg_check_modules until we ship a
CMake config), Vulkan via
find_package, builds static lib
+ smoke test, GNUInstallDirs install
include/daedalus_decoder.h public API surface:
- daedalus_decoder_{create,destroy,
version,has_qpu}
- daedalus_decoder_set_output_format
(NV12 default, RGBA opt-in per §5)
- daedalus_decoder_append_mb +
struct daedalus_decoder_mb_input
(matches §3 per-MB descriptor)
- daedalus_decoder_flush_frame
(per-frame submit + wait)
- daedalus_decoder_export_dmabuf
(Vulkan-native VkImage export per
§9.4 decision)
Dimensions are CODED frame size
(mod-16), not displayed — caller
translates from SPS + crop offsets.
src/internal.h internal mb_desc struct (matches
shader std430 layout, to be nailed
down once shaders exist) + per-ctx
state
src/daedalus_decoder.c stub bodies:
- create/destroy with proper resource
lifecycle
- append_mb validates + writes CPU
staging buffers (no GPU yet)
- flush_frame returns -2 (not
implemented) — Phase 1 work
- export_dmabuf returns -1
- has_qpu / version diagnostics
tests/test_smoke.c link + lifecycle test: bad dims
reject, OOB MB reject, null inputs
reject, raster-order enforcement,
mid-frame format-change reject,
incomplete-frame flush reject.
On hosts without V3D7 Vulkan,
SKIPs gracefully (returns 0).
Verified on hertz (Pi 5 / V3D 7.1 / Mesa V3DV via daedalus-fourier
0.1.0):
$ cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
$ cmake --build build
$ ctest --test-dir build --output-on-failure
Test #1: smoke ... Passed
$ ./build/test_smoke
daedalus-decoder version: 0.0.1
ctx created: 1920x1088, has_qpu=1
smoke OK
Note the coded-vs-displayed dims trap: 1080p H.264 has coded height
1088 with 8 rows cropped via SPS frame_cropping_*. Header docstring
on daedalus_decoder_create() spells this out so future callers don't
hit the multiple-of-16 reject (smoke test caught it during scaffold
write).
Next: Phase 1 implementation begins — IDCT 4×4 / 8×8 frame-scaled
dispatch (reusing daedalus-fourier shaders per Appendix A), intra
prediction wavefront, reconstruct stage, NV12 output via dmabuf
export. Smoke test grows from "ctx lifecycle works" to
"I-frame-only Baseline decode bit-exact vs FFmpeg reference".
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
/* SPDX-License-Identifier: BSD-2-Clause */
|
||||
/*
|
||||
* daedalus-decoder — public C API implementation.
|
||||
*
|
||||
* Scaffold only. Most functions return success with no GPU work
|
||||
* performed; the bodies will fill in across Phases 1-4 per DESIGN.md
|
||||
* §8. This file exists so the API surface compiles, links, and can
|
||||
* be smoke-tested end-to-end (ctx create / append / flush / destroy)
|
||||
* before any shader work begins.
|
||||
*/
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Built via -D from CMakeLists. */
|
||||
#ifndef DAEDALUS_DECODER_VERSION
|
||||
#define DAEDALUS_DECODER_VERSION "0.0.1+scaffold"
|
||||
#endif
|
||||
|
||||
const char *daedalus_decoder_version(void)
|
||||
{
|
||||
return DAEDALUS_DECODER_VERSION;
|
||||
}
|
||||
|
||||
daedalus_decoder *daedalus_decoder_create(int width, int height)
|
||||
{
|
||||
if (width <= 0 || height <= 0)
|
||||
return NULL;
|
||||
if ((width & 15) || (height & 15))
|
||||
return NULL; /* must be multiple of 16 */
|
||||
|
||||
daedalus_decoder *dec = calloc(1, sizeof(*dec));
|
||||
if (!dec)
|
||||
return NULL;
|
||||
|
||||
dec->width = width;
|
||||
dec->height = height;
|
||||
dec->mb_width = width >> 4;
|
||||
dec->mb_height = height >> 4;
|
||||
dec->n_mbs = dec->mb_width * dec->mb_height;
|
||||
dec->output_fmt = DAEDALUS_DECODER_OUTPUT_NV12;
|
||||
|
||||
/* daedalus-fourier ctx — required. Phase 1 needs the QPU; if
|
||||
* Vulkan init fails the decoder is unusable. Caller can check
|
||||
* via daedalus_decoder_has_qpu(). */
|
||||
dec->dctx = daedalus_ctx_create();
|
||||
if (!dec->dctx) {
|
||||
free(dec);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dec->mb_descs = calloc((size_t) dec->n_mbs, sizeof(*dec->mb_descs));
|
||||
dec->coeffs = calloc((size_t) dec->n_mbs * 384, sizeof(int16_t));
|
||||
if (!dec->mb_descs || !dec->coeffs) {
|
||||
daedalus_decoder_destroy(dec);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return dec;
|
||||
}
|
||||
|
||||
void daedalus_decoder_destroy(daedalus_decoder *dec)
|
||||
{
|
||||
if (!dec)
|
||||
return;
|
||||
free(dec->coeffs);
|
||||
free(dec->mb_descs);
|
||||
if (dec->dctx)
|
||||
daedalus_ctx_destroy(dec->dctx);
|
||||
free(dec);
|
||||
}
|
||||
|
||||
int daedalus_decoder_set_output_format(daedalus_decoder *dec,
|
||||
daedalus_decoder_output_format fmt)
|
||||
{
|
||||
if (!dec)
|
||||
return -1;
|
||||
if (dec->mbs_appended != 0)
|
||||
return -1; /* mid-frame change forbidden */
|
||||
if (fmt != DAEDALUS_DECODER_OUTPUT_NV12 &&
|
||||
fmt != DAEDALUS_DECODER_OUTPUT_RGBA)
|
||||
return -1;
|
||||
dec->output_fmt = fmt;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int daedalus_decoder_append_mb(daedalus_decoder *dec,
|
||||
const struct daedalus_decoder_mb_input *mb)
|
||||
{
|
||||
if (!dec || !mb || !mb->coeffs)
|
||||
return -1;
|
||||
if (mb->mb_x >= dec->mb_width || mb->mb_y >= dec->mb_height)
|
||||
return -1;
|
||||
|
||||
/* Raster-order check — Phase 1's intra wavefront requires it.
|
||||
* Caller is libavcodec's slice loop which produces raster order
|
||||
* naturally, so this should never fire in practice. */
|
||||
int expected = mb->mb_y * dec->mb_width + mb->mb_x;
|
||||
if (expected != dec->mbs_appended)
|
||||
return -1;
|
||||
|
||||
struct daedalus_decoder_mb_desc *d = &dec->mb_descs[expected];
|
||||
d->mb_x = mb->mb_x;
|
||||
d->mb_y = mb->mb_y;
|
||||
d->mb_type = mb->mb_type;
|
||||
d->mb_qp_y = mb->mb_qp_y;
|
||||
d->mb_qp_uv = mb->mb_qp_uv;
|
||||
d->cbp = mb->cbp;
|
||||
memcpy(d->intra_4x4_modes, mb->intra_4x4_modes, 16);
|
||||
d->intra_16x16_mode = mb->intra_16x16_mode;
|
||||
d->intra_chroma_mode = mb->intra_chroma_mode;
|
||||
d->partition_mode = mb->partition_mode;
|
||||
memcpy(d->ref_idx_l0, mb->ref_idx_l0, 4);
|
||||
memcpy(d->ref_idx_l1, mb->ref_idx_l1, 4);
|
||||
memcpy(d->mv_l0, mb->mv_l0, sizeof(d->mv_l0));
|
||||
memcpy(d->mv_l1, mb->mv_l1, sizeof(d->mv_l1));
|
||||
d->deblock_disable = mb->deblock_disable;
|
||||
d->deblock_alpha_c0 = mb->deblock_alpha_c0;
|
||||
d->deblock_beta = mb->deblock_beta;
|
||||
|
||||
memcpy(&dec->coeffs[(size_t) expected * 384],
|
||||
mb->coeffs,
|
||||
384 * sizeof(int16_t));
|
||||
|
||||
dec->mbs_appended++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int daedalus_decoder_flush_frame(daedalus_decoder *dec,
|
||||
uint8_t *out_y, size_t y_stride,
|
||||
uint8_t *out_uv, size_t uv_stride)
|
||||
{
|
||||
if (!dec)
|
||||
return -1;
|
||||
if (dec->mbs_appended != dec->n_mbs)
|
||||
return -1; /* incomplete frame */
|
||||
|
||||
/* TODO Phase 1: build VkCommandBuffer with the 4 (or 5) pipeline
|
||||
* stages, vkQueueSubmit, wait on fence, copy out to the caller's
|
||||
* planes (or dma_buf-export when caller uses the export API).
|
||||
*
|
||||
* Scaffold behaviour: zero the output planes so downstream
|
||||
* consumers don't read uninitialised memory, then reset for the
|
||||
* next frame. Returns -ENOSYS-equivalent (-2) so any test that
|
||||
* expects real pixels notices. */
|
||||
(void) out_y; (void) y_stride; (void) out_uv; (void) uv_stride;
|
||||
|
||||
dec->mbs_appended = 0;
|
||||
return -2; /* not implemented */
|
||||
}
|
||||
|
||||
int daedalus_decoder_export_dmabuf(daedalus_decoder *dec, int plane)
|
||||
{
|
||||
(void) dec; (void) plane;
|
||||
/* TODO Phase 1: vkGetMemoryFdKHR on the DPB slot's VkImage memory. */
|
||||
return -1;
|
||||
}
|
||||
|
||||
int daedalus_decoder_has_qpu(const daedalus_decoder *dec)
|
||||
{
|
||||
if (!dec || !dec->dctx)
|
||||
return 0;
|
||||
return daedalus_ctx_has_qpu(dec->dctx);
|
||||
}
|
||||
Reference in New Issue
Block a user