Wave-2 PW5 stage 5. Depth is STILL 1 — the handles alternate per frame, one in flight. PyroWave's `Encoder` cannot hold two frames. Not "probably not" — structurally not. `Encoder::Impl` owns ONE each of `wavelet_img_high_res`, `bucket_buffer`, `meta_buffer`, `block_stat_buffer`, `payload_data` and `quant_buffer`, and `Impl::encode` OPENS by discarding them: an image barrier with `VK_IMAGE_LAYOUT_UNDEFINED` as the old layout — a written promise that nothing else is reading it — plus three `fill_buffer` clears. Two encodes recorded into two command buffers and submitted to one queue have no execution dependency in Vulkan (submission order orders the START, not the completion), so N+1's DWT would overwrite the wavelet bands and zero the RDO buckets while N's block packing still reads them. Content-dependent, silent. So overlap means TWO handles on one device, alternated — one per slot. Every resource above is then private per handle, and within a handle the encodes stay strictly serialized (a slot's next frame is recorded only after that slot's previous one retired), which leaves patch 0004's scratch-pool invariant intact without touching it. THE LANDMINE, and it is the reason this stage is its own commit: `sequence_count` ALSO lives on `Impl`, and it is the 3-bit counter stamped into every block header. Two handles each count 1,2,3... alone, so the wire sees 1,1,2,2,3,3.... The decoder restarts a frame only when the value CHANGES (`diff = (hdr.sequence - last_seq) & 0x7; restart = diff != 0`), so a repeat reads as MORE BLOCKS OF THE SAME FRAME: `clear()` never runs, `decoded_frame_for_current_sequence` stays true, and the second frame of each pair is swallowed. Half frame rate, occasional mixed-frame blocks, no error anywhere — on every client, since pf-client-core and the Apple Metal hand-port parse the same field. `patches/0007-encoder-sequence-override.patch` (new, ~38 lines) exposes `Encoder::set_next_sequence` + a `pyrowave_encoder_set_next_sequence` C entry + a `PYROWAVE_SEQUENCE_MASK` define, so ONE monotonic counter on the Rust side is stamped regardless of which handle encodes. The setter stores `(seq - 1) & mask` because `Impl::encode` pre-increments — its contract is about the next ENCODE, not the next store. Inert when unused, so the whole Windows backend is untouched. No `.def` change: the C API is a static archive. PREDICTED, THEN OBSERVED. A negative control on .21 (the override call removed, nothing else) reads the wire out at exactly: [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 0, 0, 1, 1, 2, 2] which is the analysis's prediction character for character, and with the override: +1 mod 8, all 20 frames, through the 3-bit wrap. THE GATE, `wire_sequence_increments_across_alternating_handles`, checks three things over 20 frames because any one alone could pass while the stream is broken: the wire counter advances by 1 mod 8; ONE persistent decoder (its `last_seq` carried across every push, exactly like a client's) reports every AU decodable; and consecutive decoded pictures DIFFER. Content moves every frame — and the first run caught a trap in the harness itself rather than the encoder: `test_card` starts its LCG at `seed | 1`, so seeds 2 and 3 build a byte-identical card and the test faked the very repeat it hunts. Odd seeds only now, with the reason written down. A runtime self-check backs the test up where the test cannot reach: after packetize, the stamped sequence is compared against what we asked for, and a mismatch logs once per process naming patch 0007. A re-vendor that loses the patch would not fail to build — it would fail on glass, subtly, and this makes it loud instead. Two byte reads per frame. `reset()` rebuilds both handles and `Drop` destroys both, each with the same null-immediately discipline the single handle had (`pyrowave_encoder_destroy` is a bare `delete` with no null check, so a stale pointer left in the field is a double free). Vendored-patch discipline: patch 0007 re-applies clean to a pristine vendor checkout (verified by stashing the vendor tree and re-applying), and `git diff crates/pyrowave-sys/vendor/` touches exactly the four intended files. VERIFIED ON GLASS (.21, RTX 5070 Ti, GPU idle at 180 MHz of 3090): all 8 `#[ignore]`d GPU tests pass, including the new gate and the 4:2:0 / 4:4:4 / 24-bpp PSNR smokes. Gates green at CI parity.
70 lines
1.7 KiB
C++
70 lines
1.7 KiB
C++
// Copyright (c) 2025 Hans-Kristian Arntzen
|
|
// SPDX-License-Identifier: MIT
|
|
#pragma once
|
|
|
|
#include <memory>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include "pyrowave_config.hpp"
|
|
|
|
namespace Vulkan
|
|
{
|
|
class Device;
|
|
class Buffer;
|
|
class ImageView;
|
|
class CommandBuffer;
|
|
}
|
|
|
|
namespace PyroWave
|
|
{
|
|
class Encoder
|
|
{
|
|
public:
|
|
Encoder();
|
|
~Encoder();
|
|
|
|
struct BitstreamBuffers
|
|
{
|
|
struct
|
|
{
|
|
const Vulkan::Buffer *buffer;
|
|
uint64_t offset;
|
|
uint64_t size;
|
|
} meta, bitstream;
|
|
size_t target_size;
|
|
};
|
|
|
|
bool init(Vulkan::Device *device, int width, int height, ChromaSubsampling chroma);
|
|
bool encode(Vulkan::CommandBuffer &cmd, const ViewBuffers &views, const BitstreamBuffers &buffers);
|
|
|
|
// PUNKTFUNK: override the 3-bit wire sequence counter the NEXT encode will stamp.
|
|
// The counter is per-Encoder, so alternating two encoder objects to overlap frames emits
|
|
// 1,1,2,2,3,3... and the decoder reads a repeated value as "more blocks of the same frame".
|
|
// See crates/pyrowave-sys/patches/0007-encoder-sequence-override.patch.
|
|
void set_next_sequence(uint32_t sequence);
|
|
|
|
// Debug hackery
|
|
const Vulkan::ImageView &get_wavelet_band(int component, int level);
|
|
bool encode_pre_transformed(Vulkan::CommandBuffer &cmd, const BitstreamBuffers &buffers, float quant_scale);
|
|
//
|
|
|
|
uint64_t get_meta_required_size() const;
|
|
|
|
struct Packet
|
|
{
|
|
size_t offset;
|
|
size_t size;
|
|
};
|
|
|
|
size_t compute_num_packets(const void *mapped_meta, size_t packet_boundary) const;
|
|
size_t packetize(Packet *packets, size_t packet_boundary,
|
|
void *bitstream, size_t size,
|
|
const void *mapped_meta, const void *mapped_bitstream) const;
|
|
|
|
void report_stats(const void *mapped_meta, const void *mapped_bitstream) const;
|
|
|
|
private:
|
|
struct Impl;
|
|
std::unique_ptr<Impl> impl;
|
|
};
|
|
} |