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>
371 lines
12 KiB
C++
371 lines
12 KiB
C++
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "transforms.hpp"
|
|
#include "aabb.hpp"
|
|
#include "simd.hpp"
|
|
#include "muglm/matrix_helper.hpp"
|
|
#include <assert.h>
|
|
|
|
namespace Granite
|
|
{
|
|
bool compute_plane_reflection(mat4 &projection, mat4 &view, vec3 camera_pos, vec3 center, vec3 normal, vec3 look_up,
|
|
float radius_up, float radius_other, float &z_near, float z_far)
|
|
{
|
|
normal = normalize(normal);
|
|
|
|
// Reflect the camera position from the plane.
|
|
float over_plane = dot(normal, camera_pos - center);
|
|
if (over_plane <= 0.0f)
|
|
return false;
|
|
|
|
camera_pos -= 2.0f * over_plane * normal;
|
|
|
|
// The look direction is up through the plane direction.
|
|
// This way we avoid skewed near and far planes (i.e. oblique).
|
|
// Make sure look_up is perpendicular to normal.
|
|
vec3 look_pos_x = normalize(cross(normal, look_up));
|
|
look_up = normalize(cross(look_pos_x, normal));
|
|
|
|
view = mat4_cast(look_at(normal, look_up)) * translate(-camera_pos);
|
|
|
|
float dist_x = dot(look_pos_x, center - camera_pos);
|
|
float left = dist_x - radius_other;
|
|
float right = dist_x + radius_other;
|
|
|
|
float dist_y = dot(look_up, center - camera_pos);
|
|
float bottom = dist_y - radius_up;
|
|
float top = dist_y + radius_up;
|
|
|
|
z_near = over_plane;
|
|
projection = frustum(left, right, bottom, top, over_plane, z_far);
|
|
if (z_near >= z_far)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool compute_plane_refraction(mat4 &projection, mat4 &view, vec3 camera_pos, vec3 center, vec3 normal, vec3 look_up,
|
|
float radius_up, float radius_other, float &z_near, float z_far)
|
|
{
|
|
normal = normalize(normal);
|
|
|
|
// Reflect the camera position from the plane.
|
|
float over_plane = dot(normal, camera_pos - center);
|
|
if (over_plane <= 0.0f)
|
|
return false;
|
|
|
|
normal = -normal;
|
|
|
|
// The look direction is up through the plane direction.
|
|
// This way we avoid skewed near and far planes (i.e. oblique).
|
|
// Make sure look_up is perpendicular to normal.
|
|
vec3 look_pos_x = normalize(cross(normal, look_up));
|
|
look_up = normalize(cross(look_pos_x, normal));
|
|
|
|
view = mat4_cast(look_at(normal, look_up)) * translate(-camera_pos);
|
|
|
|
float dist_x = dot(look_pos_x, center - camera_pos);
|
|
float left = dist_x - radius_other;
|
|
float right = dist_x + radius_other;
|
|
|
|
float dist_y = dot(look_up, center - camera_pos);
|
|
float bottom = dist_y - radius_up;
|
|
float top = dist_y + radius_up;
|
|
|
|
z_near = over_plane;
|
|
projection = frustum(left, right, bottom, top, over_plane, z_far);
|
|
if (z_near >= z_far)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
void compute_model_transform(mat_affine &world, vec3 s, quat rot, vec3 trans, const mat_affine &parent)
|
|
{
|
|
// TODO: Make this more affine friendly.
|
|
mat4 model;
|
|
model[3] = vec4(trans, 1.0f);
|
|
SIMD::convert_quaternion_with_scale(&model[0], rot, s);
|
|
|
|
SIMD::mul(world, parent, mat_affine(model));
|
|
}
|
|
|
|
void compute_normal_transform(mat4 &normal, const mat4 &world)
|
|
{
|
|
normal = mat4(transpose(inverse(mat3(world))));
|
|
}
|
|
|
|
void compute_normal_transform(mat_affine &normal, const mat_affine &world)
|
|
{
|
|
// Can be done better, but not important unless it gets used a lot.
|
|
normal = mat_affine(mat4(transpose(inverse(world.to_mat3()))));
|
|
}
|
|
|
|
quat rotate_vector(vec3 from, vec3 to)
|
|
{
|
|
from = normalize(from);
|
|
to = normalize(to);
|
|
|
|
float cos_angle = dot(from, to);
|
|
if (abs(cos_angle) > 0.9999f)
|
|
{
|
|
if (cos_angle > 0.9999f)
|
|
return quat(1.0f, 0.0f, 0.0f, 0.0f);
|
|
else
|
|
{
|
|
vec3 rotation = cross(vec3(1.0f, 0.0f, 0.0f), from);
|
|
if (dot(rotation, rotation) > 0.001f)
|
|
rotation = normalize(rotation);
|
|
else
|
|
rotation = normalize(cross(vec3(0.0f, 1.0f, 0.0f), from));
|
|
return quat(0.0f, rotation);
|
|
}
|
|
}
|
|
|
|
vec3 rotation = normalize(cross(from, to));
|
|
vec3 half_vector = normalize(from + to);
|
|
float cos_half_range = clamp(dot(half_vector, from), 0.0f, 1.0f);
|
|
float sin_half_angle = sqrtf(1.0f - cos_half_range * cos_half_range);
|
|
return quat(cos_half_range, rotation * sin_half_angle);
|
|
}
|
|
|
|
quat rotate_vector_axis(vec3 from, vec3 to, vec3 axis)
|
|
{
|
|
axis = normalize(axis);
|
|
from = normalize(cross(axis, from));
|
|
to = normalize(cross(axis, to));
|
|
|
|
if (dot(to, from) < -0.9999f)
|
|
return quat(0.0f, axis);
|
|
|
|
// Rotate CCW or CW, we only find the angle of rotation below.
|
|
float quat_sign = sign(dot(axis, cross(from, to)));
|
|
|
|
vec3 half_vector = normalize(from + to);
|
|
float cos_half_range = clamp(dot(half_vector, from), 0.0f, 1.0f);
|
|
float sin_half_angle = quat_sign * sqrtf(1.0f - cos_half_range * cos_half_range);
|
|
return quat(cos_half_range, axis * sin_half_angle);
|
|
}
|
|
|
|
quat look_at(vec3 direction, vec3 up)
|
|
{
|
|
static const vec3 z(0.0f, 0.0f, -1.0f);
|
|
static const vec3 y(0.0f, 1.0f, 0.0f);
|
|
direction = normalize(direction);
|
|
vec3 right = cross(direction, up);
|
|
vec3 actual_up = cross(right, direction);
|
|
quat look_transform = rotate_vector(direction, z);
|
|
quat up_transform = rotate_vector_axis(look_transform * actual_up, y, z);
|
|
return up_transform * look_transform;
|
|
}
|
|
|
|
quat look_at_arbitrary_up(vec3 direction)
|
|
{
|
|
return rotate_vector(normalize(direction), vec3(0.0f, 0.0f, -1.0f));
|
|
}
|
|
|
|
mat4 projection(float fovy, float aspect, float znear, float zfar)
|
|
{
|
|
return perspective(fovy, aspect, znear, zfar);
|
|
}
|
|
|
|
mat4 ortho(const AABB &aabb)
|
|
{
|
|
vec3 min = aabb.get_minimum();
|
|
vec3 max = aabb.get_maximum();
|
|
|
|
// Flip Z for RH, ortho zNear/zFar is LH style.
|
|
std::swap(max.z, min.z);
|
|
max.z = -max.z;
|
|
min.z = -min.z;
|
|
|
|
return muglm::ortho(min.x, max.x, min.y, max.y, min.z, max.z);
|
|
}
|
|
|
|
void compute_cube_render_transform(vec3 center, unsigned face, mat4 &proj, mat4 &view, float znear, float zfar)
|
|
{
|
|
static const vec3 dirs[6] = {
|
|
vec3(1.0f, 0.0f, 0.0f),
|
|
vec3(-1.0f, 0.0f, 0.0f),
|
|
vec3(0.0f, 1.0f, 0.0f),
|
|
vec3(0.0f, -1.0f, 0.0f),
|
|
vec3(0.0f, 0.0f, 1.0f),
|
|
vec3(0.0f, 0.0f, -1.0f),
|
|
};
|
|
|
|
static const vec3 ups[6] = {
|
|
vec3(0.0f, 1.0f, 0.0f),
|
|
vec3(0.0f, 1.0f, 0.0f),
|
|
vec3(0.0f, 0.0f, -1.0f),
|
|
vec3(0.0f, 0.0f, +1.0f),
|
|
vec3(0.0f, 1.0f, 0.0f),
|
|
vec3(0.0f, 1.0f, 0.0f),
|
|
};
|
|
|
|
view = mat4_cast(look_at(dirs[face], ups[face])) * translate(-center);
|
|
proj = scale(vec3(-1.0f, 1.0f, 1.0f)) * projection(0.5f * pi<float>(), 1.0f, znear, zfar);
|
|
}
|
|
|
|
vec3 PositionalSampler::sample(unsigned index, float l) const
|
|
{
|
|
if (l == 0.0f)
|
|
return values[index];
|
|
else if (l == 1.0f)
|
|
return values[index + 1];
|
|
|
|
assert(index + 1 < values.size());
|
|
return mix(values[index], values[index + 1], l);
|
|
}
|
|
|
|
template <typename T>
|
|
static T compute_cubic_spline(const std::vector<T> &values, unsigned index, float t, float dt)
|
|
{
|
|
assert(3 * index + 4 < values.size());
|
|
T p0 = values[3 * index + 1];
|
|
T p1 = values[3 * index + 4];
|
|
|
|
// For t == 0.0f, the result must be exactly on the point as specified by glTF.
|
|
if (t == 0.0f)
|
|
return p0;
|
|
else if (t == 1.0f)
|
|
return p1;
|
|
|
|
T m0 = dt * values[3 * index + 2];
|
|
T m1 = dt * values[3 * index + 3];
|
|
|
|
float t2 = t * t;
|
|
float t3 = t2 * t;
|
|
|
|
return (2.0f * t3 - 3.0f * t2 + 1.0f) * p0 +
|
|
(t3 - 2.0f * t2 + t) * m0 +
|
|
(-2.0f * t3 + 3.0f * t2) * p1 +
|
|
(t3 - t2) * m1;
|
|
}
|
|
|
|
vec3 PositionalSampler::sample_spline(unsigned index, float t, float dt) const
|
|
{
|
|
return compute_cubic_spline(values, index, t, dt);
|
|
}
|
|
|
|
quat SphericalSampler::sample(unsigned index, float l) const
|
|
{
|
|
if (l == 0.0f)
|
|
return quat(values[index]);
|
|
else if (l == 1.0f)
|
|
return quat(values[index + 1]);
|
|
|
|
assert(index + 1 < values.size());
|
|
return slerp(quat(values[index]), quat(values[index + 1]), l);
|
|
}
|
|
|
|
quat SphericalSampler::sample_spline(unsigned index, float t, float dt) const
|
|
{
|
|
// CUBICSPLINE for quaternion is defined as simple vec4 interpolation with normalization.
|
|
return normalize(quat(compute_cubic_spline(values, index, t, dt)));
|
|
}
|
|
|
|
// See math/docs/squad.md for more detail and derivation.
|
|
|
|
quat SphericalSampler::sample_squad(unsigned index, float l) const
|
|
{
|
|
assert(3 * index + 4 < values.size());
|
|
|
|
if (l == 0.0f)
|
|
return quat(values[3 * index + 1]);
|
|
else if (l == 1.0f)
|
|
return quat(values[3 * index + 4]);
|
|
|
|
quat q0 = quat(values[3 * index + 1]);
|
|
quat cp0 = quat(values[3 * index + 2]);
|
|
quat cp1 = quat(values[3 * index + 3]);
|
|
quat q1 = quat(values[3 * index + 4]);
|
|
|
|
return slerp_no_invert(slerp_no_invert(q0, q1, l), slerp_no_invert(cp0, cp1, l), 2.0f * l * (1.0f - l));
|
|
}
|
|
|
|
quat compute_inner_control_point(const quat &q, const vec3 &delta)
|
|
{
|
|
return q * quat_exp(-delta);
|
|
}
|
|
|
|
vec3 compute_inner_control_point_delta(const quat &q0, const quat &q1, const quat &q2,
|
|
float dt0, float dt1)
|
|
{
|
|
// This is almost gibberish, as this is just copy-pastaed from various implementations
|
|
// found on the interwebs.
|
|
// From studying it in greater detail,
|
|
// the basic gist is that quaternion log and exp are used to
|
|
// decompose what should be a series of multiplications (quat rotations) into additions, since
|
|
// ln(a * b) = ln(a) + ln(b), and exp(ln(a)) = a.
|
|
|
|
// ln(q) means encoding a vec3 where the length encodes theta, and direction encodes direction.
|
|
// Summing ln(a) + ln(b) will therefore "add" the addition together, similar to how one
|
|
// would add torque vectors in physics. The exp must then re-encode the vector-magnitude encoding
|
|
// back to normal quaternion form.
|
|
|
|
// In this domain we can average rotations, and go back again to a normal quaternion with exp.
|
|
// inv_q1 * q2 and inv_q1 * q0 both do some form of "differential" of the rotations.
|
|
// q12 and q10 estimate first derivative at the control points.
|
|
// q12 and q10 have opposing signs,
|
|
// so the sum of the logs is therefore seen as instantaneous acceleration at the q1.
|
|
|
|
// quat_log() breaks down if q.w goes negative it seems, so that explains some shenanigans
|
|
// where some docs say that this only works for "normal" interpolation scenarios.
|
|
// Probably more than good enough for us though.
|
|
|
|
// Weigh the deltas so that they compute absolute velocity and acceleration.
|
|
// Rescale back to spline time domain after.
|
|
|
|
quat inv_q1 = conjugate(q1);
|
|
quat delta_k = inv_q1 * q2; // q2 - q1
|
|
quat delta_k_minus1 = inv_q1 * q0; // q0 - q1 = -(q1 - q0)
|
|
vec3 delta_k_log = quat_log(delta_k);
|
|
vec3 delta_k_minus1_log = quat_log(delta_k_minus1);
|
|
|
|
// We sample velocity at the center of the segment when taking the difference.
|
|
// Future sample is at t = +1/2 dt
|
|
// Past sample is at t = -1/2 dt
|
|
float segment_time = 0.5f * (dt0 + dt1);
|
|
vec3 absolute_accel = (delta_k_log / dt1 + delta_k_minus1_log / dt0) / segment_time;
|
|
vec3 delta = (0.25f * dt1 * dt1) * absolute_accel;
|
|
return delta;
|
|
}
|
|
|
|
// From https://mina86.com/2019/srgb-xyz-matrix/
|
|
static vec3 convert_primary(const vec2 &xy)
|
|
{
|
|
float X = xy.x / xy.y;
|
|
float Y = 1.0f;
|
|
float Z = (1.0f - xy.x - xy.y) / xy.y;
|
|
return vec3(X, Y, Z);
|
|
}
|
|
|
|
mat3 compute_xyz_matrix(const Primaries &primaries)
|
|
{
|
|
vec3 red = convert_primary(primaries.red);
|
|
vec3 green = convert_primary(primaries.green);
|
|
vec3 blue = convert_primary(primaries.blue);
|
|
vec3 white = convert_primary(primaries.white_point);
|
|
|
|
vec3 component_scale = inverse(mat3(red, green, blue)) * white;
|
|
return mat3(red * component_scale.x, green * component_scale.y, blue * component_scale.z);
|
|
}
|
|
} |