Files
punktfunk/crates/pyrowave-sys/vendor/pyrowave/shaders/analyze_rate_control.comp
T
enricobuehler 4c3b11445c feat(host): vendor PyroWave + minimal Granite subset as crates/pyrowave-sys
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>
2026-07-15 00:35:10 +02:00

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();
}