4c3b11445c
Phase 0 of design/pyrowave-codec-plan.md — the opt-in wired-LAN ultra-low- latency codec. Vendored at upstream 509e4f88 (API 0.4.0, Granite 44362775, volk + vulkan-headers pins in PUNKTFUNK-VENDOR.txt), pruned to the 6.6 MB the standalone no-renderer build needs; scripts/vendor-pyrowave.sh reproduces the tree (a pin bump is protocol-affecting, plan §4.2). build.rs drives the wrapper CMakeLists (static archives incl. a static C-API lib upstream only ships shared) + bindgen over pyrowave.h; Linux and Windows only, empty stub elsewhere (Apple gets a native Metal port, §4.7). Offline-safe by construction: no network, no system lib, vendored Vulkan headers — same model as the opus dep (flatpak builder has no network). Phase-0 validation on .21 (RTX 5070 Ti, driver 610.43.03): - upstream pyrowave-c-test + interop test (incl. dmabuf/DRM-modifier Vulkan<->Vulkan) pass, from the pristine AND the pruned tree - GPU kernel times at ~1.6 bpp noise: encode/decode 0.090/0.042 ms @800p, 0.146/0.067 @1080p, 0.226/0.103 @1440p, 0.477/0.201 @4K — order of magnitude under NVENC's 1-2 ms retrieve, CBR lands within ~100 B of target - cargo test -p pyrowave-sys green (static link + API-version pin check) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
221 lines
6.6 KiB
Plaintext
221 lines
6.6 KiB
Plaintext
#version 450
|
|
#extension GL_KHR_shader_subgroup_basic : require
|
|
#extension GL_KHR_shader_subgroup_ballot : require
|
|
#extension GL_KHR_shader_subgroup_arithmetic : require
|
|
#extension GL_KHR_shader_subgroup_vote : require
|
|
#extension GL_KHR_shader_subgroup_shuffle_relative : require
|
|
#extension GL_KHR_shader_subgroup_shuffle : require
|
|
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
|
|
#extension GL_EXT_shader_explicit_arithmetic_types_int16 : require
|
|
|
|
#include "dwt_quant_scale.h"
|
|
#include "constants.h"
|
|
|
|
layout(local_size_x = 64) in;
|
|
|
|
struct BlockMeta
|
|
{
|
|
uint code_word;
|
|
uint offset;
|
|
};
|
|
|
|
struct RDOperation
|
|
{
|
|
int quant;
|
|
uint block_offset_saving;
|
|
};
|
|
|
|
const int BLOCK_SPACE_SUBDIVISION = 16;
|
|
|
|
layout(set = 0, binding = 0) buffer Buckets
|
|
{
|
|
uint count;
|
|
uint consumed_payload;
|
|
layout(offset = 64) uint total_savings_per_bucket[128 * BLOCK_SPACE_SUBDIVISION];
|
|
RDOperation rdo_operations[];
|
|
} buckets;
|
|
|
|
struct QuantStats
|
|
{
|
|
float16_t square_error;
|
|
uint16_t payload_cost;
|
|
};
|
|
|
|
struct BlockStats
|
|
{
|
|
uint num_planes;
|
|
QuantStats errors[15];
|
|
};
|
|
|
|
layout(set = 0, binding = 1) readonly buffer SSBOBlockStats
|
|
{
|
|
BlockStats stats[];
|
|
} block_stats;
|
|
|
|
layout(push_constant) uniform Registers
|
|
{
|
|
ivec2 resolution;
|
|
ivec2 resolution_8x8_blocks;
|
|
int block_offset_8x8;
|
|
int block_stride_8x8;
|
|
int block_offset_32x32;
|
|
int block_stride_32x32;
|
|
uint total_wg_count;
|
|
uint num_blocks_aligned;
|
|
uint block_index_shamt;
|
|
} registers;
|
|
|
|
shared uint shared_rate_cost[16];
|
|
shared float shared_distortion[16];
|
|
shared uint shared_tmp[4];
|
|
|
|
// Perform operations that cause lower distortion first.
|
|
uint distortion_to_bucket_index(float d, float cost, float d_base, float cost_base)
|
|
{
|
|
if (cost == cost_base)
|
|
return 0;
|
|
|
|
// Compress a large range into 64 possible buckets.
|
|
// Every band is ~1.5 dB.
|
|
// Greedily chase least added (weighted) distortion per byte removed from code stream.
|
|
float index = 60.0 + 2.0 * log2(max(d - d_base, 0.0) / (cost_base - cost));
|
|
return uint(max(index + 0.5, 0.0));
|
|
}
|
|
|
|
uint inclusive_max_clustered16(uint v)
|
|
{
|
|
// Ensures that we never end up with a value > 127.
|
|
v = min(v, 128 - 16 + gl_SubgroupInvocationID);
|
|
|
|
for (uint i = 1; i < 16; i *= 2)
|
|
{
|
|
// Ensure monotonic progression for buckets.
|
|
// Separate every quant level out by at least one bucket.
|
|
uint up = subgroupShuffleUp(v, i) + i;
|
|
v = max(v, gl_SubgroupInvocationID >= i ? up : 0);
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
void emit_rdo_operations()
|
|
{
|
|
float distortion;
|
|
float cost;
|
|
|
|
if (gl_SubgroupInvocationID < 16)
|
|
{
|
|
cost = float(shared_rate_cost[gl_SubgroupInvocationID]);
|
|
distortion = shared_distortion[gl_SubgroupInvocationID];
|
|
}
|
|
else
|
|
{
|
|
// Dummy values.
|
|
cost = float(shared_rate_cost[gl_SubgroupInvocationID]);
|
|
distortion = 1e30;
|
|
}
|
|
|
|
uint bucket_index = distortion_to_bucket_index(distortion, cost, shared_distortion[0], float(shared_rate_cost[0]));
|
|
if (gl_SubgroupInvocationID == 0)
|
|
bucket_index = 0;
|
|
|
|
// Constraints:
|
|
// bucket_index for Q1 must be less than bucket_index for Q2 if Q1 < Q2.
|
|
// If a high quant target sees very favorable RD, lower bucket indices for lower Q values.
|
|
uint inclusive_bucket_index = inclusive_max_clustered16(bucket_index);
|
|
|
|
if (gl_SubgroupInvocationID == 0)
|
|
{
|
|
uint unquantized_cost = shared_rate_cost[0];
|
|
atomicAdd(buckets.consumed_payload, unquantized_cost);
|
|
}
|
|
else if (gl_SubgroupInvocationID < 16)
|
|
{
|
|
uint saving = shared_rate_cost[gl_SubgroupInvocationID - 1] - shared_rate_cost[gl_SubgroupInvocationID];
|
|
|
|
if (saving != 0)
|
|
{
|
|
ivec2 block32x32_index = ivec2(gl_WorkGroupID.xy);
|
|
int block_index = registers.block_offset_32x32 +
|
|
block32x32_index.y * registers.block_stride_32x32 + block32x32_index.x;
|
|
uint subdivision = block_index >> registers.block_index_shamt;
|
|
atomicAdd(buckets.total_savings_per_bucket[inclusive_bucket_index * BLOCK_SPACE_SUBDIVISION + subdivision], saving);
|
|
buckets.rdo_operations[block_index + inclusive_bucket_index * registers.num_blocks_aligned] =
|
|
RDOperation(int(gl_SubgroupInvocationID), block_index | (saving << 16));
|
|
}
|
|
}
|
|
}
|
|
|
|
void main()
|
|
{
|
|
// Each workgroup processes a 64x64 block and computes all possible rate wins for every potential quant rate.
|
|
uint index = gl_SubgroupInvocationID + gl_SubgroupSize * gl_SubgroupID;
|
|
ivec2 block32x32_index = ivec2(gl_WorkGroupID.xy);
|
|
ivec2 local_block_index = ivec2(bitfieldExtract(index, 0, 2), bitfieldExtract(index, 2, 2));
|
|
ivec2 block8x8_index = 4 * block32x32_index + local_block_index;
|
|
|
|
uint num_active_planes;
|
|
|
|
bool block8x8_in_range = all(lessThan(block8x8_index, registers.resolution_8x8_blocks));
|
|
int block_index_8x8 = registers.block_offset_8x8 +
|
|
registers.block_stride_8x8 * block8x8_index.y +
|
|
block8x8_index.x;
|
|
|
|
if (block8x8_in_range)
|
|
num_active_planes = block_stats.stats[block_index_8x8].num_planes;
|
|
|
|
uint bit_index = index >> 4;
|
|
|
|
for (uint i = bit_index; i < 16; i += 4)
|
|
{
|
|
float dist = 0.0;
|
|
uint cost = 0;
|
|
|
|
if (block8x8_in_range)
|
|
{
|
|
QuantStats stats = block_stats.stats[block_index_8x8].errors[min(i, num_active_planes)];
|
|
dist = float(stats.square_error);
|
|
cost = uint(stats.payload_cost);
|
|
}
|
|
|
|
// 16 bits to encode the control codes, 8 bits to encode Q bits + quant scale.
|
|
// Cost is encoded in terms of bits. 8x8 blocks are decoded in isolation.
|
|
if (cost != 0)
|
|
cost += 24;
|
|
|
|
if (gl_SubgroupSize == 16)
|
|
{
|
|
cost = subgroupAdd(cost);
|
|
dist = subgroupAdd(dist);
|
|
}
|
|
else
|
|
{
|
|
cost += subgroupShuffleXor(cost, 1);
|
|
cost += subgroupShuffleXor(cost, 2);
|
|
cost += subgroupShuffleXor(cost, 4);
|
|
cost += subgroupShuffleXor(cost, 8);
|
|
|
|
dist += subgroupShuffleXor(dist, 1);
|
|
dist += subgroupShuffleXor(dist, 2);
|
|
dist += subgroupShuffleXor(dist, 4);
|
|
dist += subgroupShuffleXor(dist, 8);
|
|
}
|
|
|
|
if ((index & 15u) == 0u)
|
|
{
|
|
// Need to encode a header.
|
|
// We can eliminate 32x32 blocks if everything decodes to 0.
|
|
if (cost != 0)
|
|
cost += 64;
|
|
|
|
// Each packet is aligned to 4 bytes for practical reasons.
|
|
shared_rate_cost[i] = (cost + 31) >> 5;
|
|
shared_distortion[i] = dist;
|
|
}
|
|
}
|
|
|
|
barrier();
|
|
|
|
if (gl_SubgroupID == 0)
|
|
emit_rdo_operations();
|
|
} |