scaffold: CMake + API skeleton + smoke test #2
+15
@@ -0,0 +1,15 @@
|
||||
build/
|
||||
build-*/
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*.so.*
|
||||
*.spv
|
||||
.vscode/
|
||||
.cache/
|
||||
compile_commands.json
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
Makefile
|
||||
.ninja_*
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
# daedalus-decoder — frame-level GPU H.264 decoder for V3D7 (Pi 5).
|
||||
# Phase 1 scaffold; see DESIGN.md for architecture.
|
||||
#
|
||||
# Build dependencies:
|
||||
# - daedalus-fourier ≥ 0.1.0 (kernel pack, V3D primitives + recipe layer)
|
||||
# resolved via pkg-config; install via the daedalus-fourier upstream
|
||||
# `cmake --install` rule (PR #5 made the .pc relocatable, so any
|
||||
# install prefix works as long as $PKG_CONFIG_PATH is set).
|
||||
# - Vulkan headers + libvulkan (pulled in transitively via
|
||||
# daedalus-fourier, listed here explicitly for the link order).
|
||||
#
|
||||
# Build:
|
||||
# cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
|
||||
# cmake --build build
|
||||
# ctest --test-dir build
|
||||
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(daedalus-decoder
|
||||
VERSION 0.0.1
|
||||
DESCRIPTION "Frame-level GPU H.264 decoder for Raspberry Pi 5 / V3D7"
|
||||
LANGUAGES C)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_C_EXTENSIONS OFF)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Release)
|
||||
endif()
|
||||
|
||||
# Pi 5 is the only supported target. Other aarch64 SoCs (Pi 4 V3D4,
|
||||
# RK3588 Mali, …) might work but would need explicit substrate +
|
||||
# shader-pack validation per the daedalus-fourier architecture
|
||||
# backlog. Don't pretend to support what we haven't validated.
|
||||
if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
|
||||
message(WARNING
|
||||
"daedalus-decoder is designed for aarch64 (Pi 5 BCM2712 / V3D7). "
|
||||
"Build will proceed but is unlikely to function.")
|
||||
endif()
|
||||
|
||||
add_compile_options(-Wall -Wextra -Wno-unused-parameter)
|
||||
|
||||
# ---- Dependencies --------------------------------------------------
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
# daedalus-fourier — find_package via pkg-config per the Phase 1
|
||||
# decision §9.6. Minimum version 0.1.0 (the cycle 6-9 shaders + pool
|
||||
# + recipe-flip baseline). PKG_CONFIG_PATH should point at the
|
||||
# directory holding daedalus-fourier.pc (e.g. /usr/local/lib/pkgconfig
|
||||
# or a custom install prefix).
|
||||
pkg_check_modules(DAEDALUS_FOURIER REQUIRED daedalus-fourier>=0.1.0)
|
||||
|
||||
# Vulkan — daedalus-fourier already depends on this; we add it
|
||||
# explicitly so the link order stays correct (daedalus-fourier static
|
||||
# archive contains undefined vk* symbols that the loader resolves).
|
||||
find_package(Vulkan REQUIRED)
|
||||
|
||||
# ---- Version string baked into the library ------------------------
|
||||
|
||||
# git rev tagged onto the version string for traceability; degrades
|
||||
# gracefully to bare semver if git isn't available.
|
||||
execute_process(
|
||||
COMMAND git -C ${CMAKE_CURRENT_SOURCE_DIR} rev-parse --short=7 HEAD
|
||||
OUTPUT_VARIABLE DAEDALUS_DECODER_GITREV
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET)
|
||||
if(DAEDALUS_DECODER_GITREV)
|
||||
set(DAEDALUS_DECODER_VERSION "${PROJECT_VERSION}+g${DAEDALUS_DECODER_GITREV}")
|
||||
else()
|
||||
set(DAEDALUS_DECODER_VERSION "${PROJECT_VERSION}")
|
||||
endif()
|
||||
message(STATUS "daedalus-decoder version: ${DAEDALUS_DECODER_VERSION}")
|
||||
|
||||
# ---- Library ------------------------------------------------------
|
||||
|
||||
add_library(daedalus_decoder STATIC
|
||||
src/daedalus_decoder.c
|
||||
)
|
||||
target_include_directories(daedalus_decoder
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
src
|
||||
${DAEDALUS_FOURIER_INCLUDE_DIRS}
|
||||
)
|
||||
target_link_directories(daedalus_decoder
|
||||
PUBLIC
|
||||
${DAEDALUS_FOURIER_LIBRARY_DIRS}
|
||||
)
|
||||
target_link_libraries(daedalus_decoder
|
||||
PUBLIC
|
||||
# Order matters: daedalus-fourier static archive references
|
||||
# vulkan symbols; the loader needs daedalus-fourier first then
|
||||
# vulkan to resolve them.
|
||||
${DAEDALUS_FOURIER_LIBRARIES}
|
||||
Vulkan::Vulkan
|
||||
)
|
||||
target_compile_definitions(daedalus_decoder
|
||||
PRIVATE
|
||||
DAEDALUS_DECODER_VERSION="${DAEDALUS_DECODER_VERSION}"
|
||||
)
|
||||
target_compile_options(daedalus_decoder PRIVATE -O2)
|
||||
|
||||
# ---- Smoke test ---------------------------------------------------
|
||||
|
||||
enable_testing()
|
||||
|
||||
add_executable(test_smoke tests/test_smoke.c)
|
||||
target_link_libraries(test_smoke PRIVATE daedalus_decoder)
|
||||
target_compile_options(test_smoke PRIVATE -O2)
|
||||
|
||||
add_test(NAME smoke COMMAND test_smoke)
|
||||
|
||||
# ---- Install ------------------------------------------------------
|
||||
#
|
||||
# Library + public header. Stage 2/3 will add a pkg-config file and
|
||||
# CMake config exports once the API stabilises; pre-0.1 the scaffold
|
||||
# install just gives the static archive a home.
|
||||
|
||||
include(GNUInstallDirs)
|
||||
install(TARGETS daedalus_decoder
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(FILES include/daedalus_decoder.h
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
@@ -0,0 +1,24 @@
|
||||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2026, Markus Fritsche
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,182 @@
|
||||
/* SPDX-License-Identifier: BSD-2-Clause */
|
||||
/*
|
||||
* daedalus-decoder — public C API.
|
||||
*
|
||||
* Frame-level GPU H.264 decoder targeting V3D7 (Raspberry Pi 5). Built
|
||||
* on daedalus-fourier's V3D compute primitives at frame granularity —
|
||||
* one Vulkan submit per frame, one fence wait per frame, encoded
|
||||
* bitstream in (via libavcodec's per-MB intercept), NV12 frame out.
|
||||
*
|
||||
* Per the 2026-05-24 Phase 1 design decisions:
|
||||
* - libavcodec intercept is at macroblock-level (substitution-arc
|
||||
* evolution): the caller is expected to drive the per-MB CABAC /
|
||||
* CAVLC entropy decode and feed each macroblock's descriptor +
|
||||
* coefficients via daedalus_decoder_append_mb(). flush_frame()
|
||||
* builds the per-frame VkCommandBuffer and submits.
|
||||
* - DPB is Vulkan-native VkImage with VK_KHR_external_memory_dma_buf
|
||||
* export. The caller can obtain the output frame's dmabuf fd
|
||||
* via daedalus_decoder_export_dmabuf().
|
||||
* - Daemon integration shape: this library is statically linked into
|
||||
* daedalus_v4l2_daemon. No IPC.
|
||||
*
|
||||
* STATUS: scaffold. No GPU pipeline implemented yet; all functions
|
||||
* are stubs that compile but do not decode anything. See DESIGN.md
|
||||
* for the architecture.
|
||||
*
|
||||
* ABI: pre-0.1 — every signature here may change. Don't rely on
|
||||
* stability yet.
|
||||
*/
|
||||
#ifndef DAEDALUS_DECODER_H
|
||||
#define DAEDALUS_DECODER_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* -------------------------------------------------------------------
|
||||
* Opaque decoder context. One per concurrent stream.
|
||||
* ----------------------------------------------------------------- */
|
||||
typedef struct daedalus_decoder daedalus_decoder;
|
||||
|
||||
/* -------------------------------------------------------------------
|
||||
* Per-macroblock input. Mirrors §3 of DESIGN.md. The caller's
|
||||
* libavcodec intercept populates this from the H264SliceContext
|
||||
* fields after ff_h264_decode_mb_cabac/cavlc returns and before
|
||||
* ff_h264_hl_decode_mb is supposed to run (we replace the latter).
|
||||
* ----------------------------------------------------------------- */
|
||||
struct daedalus_decoder_mb_input {
|
||||
/* Frame coordinates (macroblock units). */
|
||||
uint16_t mb_x;
|
||||
uint16_t mb_y;
|
||||
|
||||
/* Type + quantisation. */
|
||||
uint8_t mb_type; /* H.264 spec table 7-13/7-14/7-17/7-18 enum */
|
||||
uint8_t mb_qp_y;
|
||||
uint8_t mb_qp_uv;
|
||||
uint8_t cbp; /* coded block pattern, 0..47 */
|
||||
|
||||
/* Intra prediction (used iff mb_type == I_NxN or I_16x16). */
|
||||
uint8_t intra_4x4_modes[16];
|
||||
uint8_t intra_16x16_mode;
|
||||
uint8_t intra_chroma_mode;
|
||||
|
||||
/* Inter motion / partitions (used iff P_* or B_*). */
|
||||
uint8_t partition_mode; /* P_16x16 / P_16x8 / P_8x16 / P_8x8 / etc. */
|
||||
int8_t ref_idx_l0[4]; /* per partition; -1 = not used */
|
||||
int8_t ref_idx_l1[4]; /* B only */
|
||||
int16_t mv_l0[4][2]; /* qpel precision (1/4 sample); (x, y) */
|
||||
int16_t mv_l1[4][2];
|
||||
|
||||
/* Deblocking filter parameters. */
|
||||
uint8_t deblock_disable; /* 0 = enabled */
|
||||
int8_t deblock_alpha_c0;
|
||||
int8_t deblock_beta;
|
||||
|
||||
/* Transform coefficients — 256 luma (4x4 x 16) + 64 cb + 64 cr,
|
||||
* column-major within each 4x4 block (matches FFmpeg convention).
|
||||
* Caller-owned; copied during append. */
|
||||
const int16_t *coeffs; /* points at exactly 384 int16_t */
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------
|
||||
* Output frame format selector.
|
||||
* ----------------------------------------------------------------- */
|
||||
typedef enum {
|
||||
DAEDALUS_DECODER_OUTPUT_NV12 = 0, /* default; Stage 4 final */
|
||||
DAEDALUS_DECODER_OUTPUT_RGBA = 1, /* Stage 5 opt-in */
|
||||
} daedalus_decoder_output_format;
|
||||
|
||||
/* -------------------------------------------------------------------
|
||||
* Lifecycle
|
||||
* ----------------------------------------------------------------- */
|
||||
|
||||
/* Create a decoder context for the given **coded** frame dimensions.
|
||||
*
|
||||
* width, height: pixels of the H.264 coded picture, NOT the displayed
|
||||
* picture. Both must be multiples of 16 (macroblock granularity).
|
||||
* For displayed 1080p (1920×1080), the coded frame is 1920×1088 with
|
||||
* the SPS's `frame_cropping_*` offsets cropping the bottom 8 rows.
|
||||
* The caller is responsible for translating from SPS dims + crop
|
||||
* rectangle to the values passed here; we decode the coded frame.
|
||||
*
|
||||
* Returns NULL on bad dimensions or allocation failure. Returns a
|
||||
* usable context with daedalus_decoder_has_qpu() == 0 when Vulkan
|
||||
* init fails — callers that need GPU work should check has_qpu
|
||||
* before relying on it.
|
||||
*/
|
||||
daedalus_decoder *daedalus_decoder_create(int width, int height);
|
||||
|
||||
/* Free all resources. Safe with NULL. */
|
||||
void daedalus_decoder_destroy(daedalus_decoder *dec);
|
||||
|
||||
/* Switch output format BEFORE the first append_mb call of a frame.
|
||||
* Default is NV12. Returns 0 on success, -1 if called mid-frame
|
||||
* (caller must flush first). */
|
||||
int daedalus_decoder_set_output_format(daedalus_decoder *dec,
|
||||
daedalus_decoder_output_format fmt);
|
||||
|
||||
/* -------------------------------------------------------------------
|
||||
* Per-frame submission
|
||||
* ----------------------------------------------------------------- */
|
||||
|
||||
/* Append one macroblock's data to the current frame's descriptor SSBO
|
||||
* + coefficient SSBO. No GPU dispatch yet — just CPU-side writes.
|
||||
*
|
||||
* Must be called in raster order (mb_y * mb_width + mb_x) for the
|
||||
* intra-prediction wavefront to work correctly in Phase 1.
|
||||
*
|
||||
* Returns 0 on success, negative on bounds violation or OOM.
|
||||
*/
|
||||
int daedalus_decoder_append_mb(daedalus_decoder *dec,
|
||||
const struct daedalus_decoder_mb_input *mb);
|
||||
|
||||
/* End-of-frame flush: builds the per-frame VkCommandBuffer with all
|
||||
* pipeline stages, submits once, waits on a single fence, copies the
|
||||
* NV12 (or RGBA when opted in) output into the caller-provided
|
||||
* planes.
|
||||
*
|
||||
* For NV12:
|
||||
* out_y / y_stride: Y plane (W*H bytes minimum, at the given stride)
|
||||
* out_uv / uv_stride: interleaved UV plane (W*(H/2) bytes minimum)
|
||||
*
|
||||
* For RGBA: out_y receives 4*W*H bytes at y_stride; out_uv ignored.
|
||||
*
|
||||
* Returns 0 on success, negative on Vulkan failure or undecodable
|
||||
* frame. After return, the decoder is ready for the next frame's
|
||||
* append calls.
|
||||
*/
|
||||
int daedalus_decoder_flush_frame(daedalus_decoder *dec,
|
||||
uint8_t *out_y, size_t y_stride,
|
||||
uint8_t *out_uv, size_t uv_stride);
|
||||
|
||||
/* Export the most-recently-decoded frame as a dma_buf fd. The fd is
|
||||
* owned by the caller and must be closed when done. Lets V4L2
|
||||
* consumers (daedalus_v4l2_daemon, libva-v4l2-request-fourier) attach
|
||||
* the GPU-decoded surface directly to a CAPTURE plane without a CPU
|
||||
* round-trip.
|
||||
*
|
||||
* Returns the dmabuf fd on success, -1 on failure. Must be called
|
||||
* AFTER flush_frame returns for the relevant frame.
|
||||
*/
|
||||
int daedalus_decoder_export_dmabuf(daedalus_decoder *dec, int plane);
|
||||
|
||||
/* -------------------------------------------------------------------
|
||||
* Diagnostics
|
||||
* ----------------------------------------------------------------- */
|
||||
|
||||
/* daedalus-decoder build version (semver string, e.g. "0.0.1+g0a1b2c3"). */
|
||||
const char *daedalus_decoder_version(void);
|
||||
|
||||
/* Whether the underlying daedalus-fourier context picked up a working
|
||||
* V3D7 Vulkan instance. Returns 0 if Vulkan init failed and the
|
||||
* decoder is operating in stub / failure mode. */
|
||||
int daedalus_decoder_has_qpu(const daedalus_decoder *dec);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* DAEDALUS_DECODER_H */
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/* SPDX-License-Identifier: BSD-2-Clause */
|
||||
/*
|
||||
* daedalus-decoder — internal types shared across translation units.
|
||||
* Not installed; pure-internal.
|
||||
*/
|
||||
#ifndef DAEDALUS_DECODER_INTERNAL_H
|
||||
#define DAEDALUS_DECODER_INTERNAL_H
|
||||
|
||||
#include "daedalus_decoder.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <daedalus.h> /* daedalus-fourier public API */
|
||||
|
||||
/* Per-MB descriptor as the GPU sees it. Bit-laid-out to match the
|
||||
* shader's std430 layout. Kept narrow (32 bytes target) so a 1080p
|
||||
* frame's 8160 entries fit in ~256 KiB SSBO.
|
||||
*
|
||||
* TODO once the shaders exist: nail down the exact std430 layout and
|
||||
* static_assert sizeof / alignof here. */
|
||||
struct daedalus_decoder_mb_desc {
|
||||
uint16_t mb_x;
|
||||
uint16_t mb_y;
|
||||
uint8_t mb_type;
|
||||
uint8_t mb_qp_y;
|
||||
uint8_t mb_qp_uv;
|
||||
uint8_t cbp;
|
||||
|
||||
uint8_t intra_4x4_modes[16];
|
||||
uint8_t intra_16x16_mode;
|
||||
uint8_t intra_chroma_mode;
|
||||
uint8_t partition_mode;
|
||||
uint8_t _pad0;
|
||||
|
||||
int8_t ref_idx_l0[4];
|
||||
int8_t ref_idx_l1[4];
|
||||
int16_t mv_l0[4][2];
|
||||
int16_t mv_l1[4][2];
|
||||
|
||||
uint8_t deblock_disable;
|
||||
int8_t deblock_alpha_c0;
|
||||
int8_t deblock_beta;
|
||||
uint8_t _pad1;
|
||||
};
|
||||
|
||||
struct daedalus_decoder {
|
||||
/* Geometry. */
|
||||
int width;
|
||||
int height;
|
||||
int mb_width; /* width / 16 */
|
||||
int mb_height; /* height / 16 */
|
||||
int n_mbs;
|
||||
|
||||
/* daedalus-fourier context (Vulkan + V3D7 runner). */
|
||||
daedalus_ctx *dctx;
|
||||
|
||||
/* Frame-shaped staging (CPU-side; will move to mapped SSBO once
|
||||
* Vulkan plumbing is in place). */
|
||||
struct daedalus_decoder_mb_desc *mb_descs; /* n_mbs */
|
||||
int16_t *coeffs; /* n_mbs * 384 */
|
||||
int mbs_appended; /* per-frame count */
|
||||
|
||||
/* Output format. */
|
||||
daedalus_decoder_output_format output_fmt;
|
||||
};
|
||||
|
||||
#endif /* DAEDALUS_DECODER_INTERNAL_H */
|
||||
@@ -0,0 +1,100 @@
|
||||
/* SPDX-License-Identifier: BSD-2-Clause */
|
||||
/*
|
||||
* Scaffold smoke test — verifies the daedalus-decoder library links
|
||||
* cleanly against daedalus-fourier and the lifecycle entry points
|
||||
* don't immediately crash. No actual decoding work yet.
|
||||
*
|
||||
* Returns 0 on success, non-zero on any unexpected behaviour.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "daedalus_decoder.h"
|
||||
|
||||
#define EXPECT(cond, msg) do { \
|
||||
if (!(cond)) { \
|
||||
fprintf(stderr, "EXPECT FAIL %s:%d: %s\n", __FILE__, __LINE__, msg); \
|
||||
return 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("daedalus-decoder version: %s\n", daedalus_decoder_version());
|
||||
|
||||
/* Create / destroy null is a no-op. */
|
||||
daedalus_decoder_destroy(NULL);
|
||||
|
||||
/* Bad dimensions rejected. */
|
||||
EXPECT(daedalus_decoder_create(0, 0 ) == NULL, "zero dims must reject");
|
||||
EXPECT(daedalus_decoder_create(1919, 1088) == NULL, "non-16-multiple width must reject");
|
||||
EXPECT(daedalus_decoder_create(1920, 1079) == NULL, "non-16-multiple height must reject");
|
||||
|
||||
/* Valid 1088p create. */
|
||||
daedalus_decoder *dec = daedalus_decoder_create(1920, 1088);
|
||||
if (!dec) {
|
||||
/* Vulkan init failure on this host — degrades to skip, not fail.
|
||||
* (CI runners without V3D7 will hit this; the smoke test
|
||||
* shouldn't gate on hardware presence.) */
|
||||
fprintf(stderr, "SKIP: daedalus_decoder_create returned NULL "
|
||||
"(Vulkan / V3D7 unavailable on this host)\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("ctx created: 1920x1088, has_qpu=%d\n",
|
||||
daedalus_decoder_has_qpu(dec));
|
||||
|
||||
/* set_output_format mid-frame on virgin ctx is allowed
|
||||
* (mbs_appended == 0). */
|
||||
EXPECT(daedalus_decoder_set_output_format(dec, DAEDALUS_DECODER_OUTPUT_RGBA) == 0,
|
||||
"switch to RGBA on virgin ctx");
|
||||
EXPECT(daedalus_decoder_set_output_format(dec, DAEDALUS_DECODER_OUTPUT_NV12) == 0,
|
||||
"switch back to NV12");
|
||||
|
||||
/* Append rejects out-of-bounds + null inputs. */
|
||||
int16_t coeffs[384] = {0};
|
||||
struct daedalus_decoder_mb_input mb = {0};
|
||||
mb.coeffs = coeffs;
|
||||
|
||||
mb.mb_x = 0; mb.mb_y = 0;
|
||||
EXPECT(daedalus_decoder_append_mb(dec, NULL) == -1, "null mb rejects");
|
||||
{
|
||||
struct daedalus_decoder_mb_input mb2 = mb;
|
||||
mb2.coeffs = NULL;
|
||||
EXPECT(daedalus_decoder_append_mb(dec, &mb2) == -1, "null coeffs rejects");
|
||||
}
|
||||
{
|
||||
struct daedalus_decoder_mb_input mb2 = mb;
|
||||
mb2.mb_x = 9999; mb2.mb_y = 9999;
|
||||
EXPECT(daedalus_decoder_append_mb(dec, &mb2) == -1, "OOB coords reject");
|
||||
}
|
||||
|
||||
/* Append first MB at raster index 0 — should succeed. */
|
||||
EXPECT(daedalus_decoder_append_mb(dec, &mb) == 0, "append (0,0)");
|
||||
|
||||
/* Skipping (0,1) and appending (1,0) violates raster order — reject. */
|
||||
{
|
||||
struct daedalus_decoder_mb_input mb2 = mb;
|
||||
mb2.mb_x = 0; mb2.mb_y = 1;
|
||||
EXPECT(daedalus_decoder_append_mb(dec, &mb2) == -1,
|
||||
"out-of-raster-order rejects");
|
||||
}
|
||||
|
||||
/* In-order: (1,0). */
|
||||
mb.mb_x = 1; mb.mb_y = 0;
|
||||
EXPECT(daedalus_decoder_append_mb(dec, &mb) == 0, "append (1,0)");
|
||||
|
||||
/* Flush an incomplete frame: should fail because mbs_appended != n_mbs. */
|
||||
EXPECT(daedalus_decoder_flush_frame(dec, NULL, 0, NULL, 0) == -1,
|
||||
"incomplete-frame flush rejects");
|
||||
|
||||
/* set_output_format mid-frame (mbs_appended > 0) must reject. */
|
||||
EXPECT(daedalus_decoder_set_output_format(dec, DAEDALUS_DECODER_OUTPUT_RGBA) == -1,
|
||||
"mid-frame format change rejects");
|
||||
|
||||
daedalus_decoder_destroy(dec);
|
||||
|
||||
printf("smoke OK\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user