feat(apple): PyroWave Phase 5 — native Metal decode on Mac / Apple TV / iPad (§4.7)
ci / web (push) Successful in 1m3s
ci / docs-site (push) Successful in 58s
decky / build-publish (push) Successful in 16s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 7s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 7s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 43s
ci / bench (push) Successful in 5m21s
arch / build-publish (push) Successful in 10m58s
docker / deploy-docs (push) Successful in 28s
android / android (push) Successful in 13m23s
deb / build-publish (push) Successful in 12m58s
ci / rust (push) Successful in 18m5s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 12m31s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 12m56s
flatpak / build-publish (push) Successful in 5m53s
windows-host / package (push) Successful in 14m37s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 4m9s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m15s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m12s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 6m6s
apple / swift (push) Has been cancelled
apple / screenshots (push) Has been cancelled
release / apple (push) Has been cancelled
ci / web (push) Successful in 1m3s
ci / docs-site (push) Successful in 58s
decky / build-publish (push) Successful in 16s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 7s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 7s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 43s
ci / bench (push) Successful in 5m21s
arch / build-publish (push) Successful in 10m58s
docker / deploy-docs (push) Successful in 28s
android / android (push) Successful in 13m23s
deb / build-publish (push) Successful in 12m58s
ci / rust (push) Successful in 18m5s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 12m31s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 12m56s
flatpak / build-publish (push) Successful in 5m53s
windows-host / package (push) Successful in 14m37s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 4m9s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m15s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m12s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 6m6s
apple / swift (push) Has been cancelled
apple / screenshots (push) Has been cancelled
release / apple (push) Has been cancelled
The Apple client now decodes PyroWave natively on the presenter's own MTLDevice —
no MoltenVK, no upstream C++ in the app. Completes and wires up the decoder whose
early working-tree snapshot rode along in 9127c346:
- MetalWaveletShaders.swift: wavelet_dequant + idwt hand-ported from the vendored
GLSL (STORAGE_MODE 0 only; subgroup scans → 32-wide simdgroups; DCShift spec
constant → function constant; precision-1 split: fp16 levels 0-1 / fp32 2-4).
- MetalWaveletDecoder.swift: Swift reimplementation of push_packet/decode_packet
incl. the Phase-4 chunk-aligned window walk (FRAG chains, zeroed missing shards,
the >half-blocks partial rule), init_block_meta's block-index space, and the
42-dequant + 13-idwt dispatch structure with encoder-boundary barriers. SOF-dims
changes rebuild the size-dependent resources, which is also the mid-stream
resize path. Ring of 4 output plane sets on the presenter's queue.
- Presenter: pf_frag_planar (3xR8, the planar_csc.frag twin) + renderPlanar with
a shared present tail; ReadyFrame carries an image enum (.video | .planar).
- Stage2Pipeline: a dedicated PyroWave pump — no VideoToolbox machinery, no
keyframe/re-anchor recovery (all-intra; partials render as localized blur by
design), newest-frame-index staleness guard for late partials.
- Opt-in: "PyroWave (wired LAN)" codec entry (probe-gated, ≈A13 floor via a real
kernel-compile probe), selecting it advertises + prefers the codec and forces
the session SDR (HDR/10-bit/4:4:4 caps dropped, plan contract).
- Core ABI: punktfunk_connection_shard_payload() — the Welcome's negotiated shard
payload, needed by native decoders to walk chunk-aligned AUs.
- Validation: golden fixtures generated by the host encoder + upstream's own
decoder (pyrowave_dump_golden, RTX 5070 Ti); the Metal decode PSNR-matches at
77-88 dB across all planes for dense AND chunk-aligned AUs, and a hole-punched
partial still decodes. Parser unit tests cover the window walk, FRAG chains,
broken chains, the half-blocks gate, and the block-index layout.
Tests: apple 134 green (mac; iOS/tvOS build), host 312 w/ pyrowave on .21,
core 148 w/ quic; clippy/fmt clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -183,18 +183,12 @@ enum WaveletBitstream {
|
||||
}
|
||||
return state.pushPackets(UnsafeBufferPointer(start: base, count: count))
|
||||
}
|
||||
guard ok, var frame = state.finish() else { return nil }
|
||||
guard ok, let frame = state.finish() else { return nil }
|
||||
// Upstream decode_is_ready(allow_partial=true): with no SOF the frame is undecodable;
|
||||
// at half the blocks or fewer it is presumed garbage.
|
||||
guard frame.totalBlocks > 0, frame.decodedBlocks > frame.totalBlocks / 2 else {
|
||||
return nil
|
||||
}
|
||||
// The dequant kernel indexes the offset table by the LAYOUT's block space; the wire's
|
||||
// total_blocks only counts blocks the encoder emitted. They agree for a full-coverage
|
||||
// frame, but size the table by the layout.
|
||||
if frame.offsets.count != frame.layout.blockCount32 {
|
||||
frame.offsets = Array(frame.offsets.prefix(frame.layout.blockCount32))
|
||||
}
|
||||
return frame
|
||||
}
|
||||
|
||||
@@ -293,17 +287,17 @@ enum WaveletBitstream {
|
||||
|
||||
/// One decoded frame's output planes, handed to the presenter's planar render path. The
|
||||
/// textures belong to the decoder's ring — ring depth (4) plus same-queue hazard tracking keep
|
||||
/// them valid while referenced.
|
||||
struct WaveletPlanes {
|
||||
let y: MTLTexture
|
||||
let cb: MTLTexture
|
||||
let cr: MTLTexture
|
||||
let csc: CscUniform
|
||||
var width: Int { y.width }
|
||||
var height: Int { y.height }
|
||||
/// them valid while referenced. Public because it rides inside `ReadyImage`.
|
||||
public struct WaveletPlanes: @unchecked Sendable {
|
||||
public let y: MTLTexture
|
||||
public let cb: MTLTexture
|
||||
public let cr: MTLTexture
|
||||
public let csc: CscUniform
|
||||
public var width: Int { y.width }
|
||||
public var height: Int { y.height }
|
||||
}
|
||||
|
||||
final class MetalWaveletDecoder {
|
||||
public final class MetalWaveletDecoder {
|
||||
/// Matches the Vulkan client's ring: deep enough that a slot is never rewritten while the
|
||||
/// presenter still samples it in practice; same-queue hazard tracking is the hard backstop.
|
||||
private static let ringDepth = 4
|
||||
@@ -312,7 +306,7 @@ final class MetalWaveletDecoder {
|
||||
/// dequant kernel needs simdgroup prefix sums with its 16 header lanes inside one
|
||||
/// simdgroup, so compile the real kernels once and check the pipeline facts. Apple6 (A13)
|
||||
/// and every Mac2 device pass the family check; the compile probe is authoritative.
|
||||
static let supported: Bool = {
|
||||
public static let supported: Bool = {
|
||||
guard let device = MTLCreateSystemDefaultDevice() else { return false }
|
||||
guard device.supportsFamily(.apple6) || device.supportsFamily(.mac2) else { return false }
|
||||
do {
|
||||
@@ -358,6 +352,12 @@ final class MetalWaveletDecoder {
|
||||
private var slots: [Slot] = []
|
||||
private var nextSlot = 0
|
||||
|
||||
/// The current geometry (from the last SOF that built the resources) — the pump reports
|
||||
/// decoded-size changes to the resize overlay from this. PUMP THREAD.
|
||||
var decodedSize: (width: Int, height: Int)? {
|
||||
layout.map { ($0.width, $0.height) }
|
||||
}
|
||||
|
||||
/// The pump thread owns `decode`; everything mutable is confined to it.
|
||||
init?(device: MTLDevice, queue: MTLCommandQueue) {
|
||||
self.device = device
|
||||
|
||||
Reference in New Issue
Block a user