OBS Studio 图形数学库详解:vec4 四维向量 API 全解析(libobs graphics)
libobs 的图形子系统为 OBS Studio 的插件、效果(effect)与渲染管线提供一套跨平台的 C 语言数学库。本篇以 docs/sphinx/reference-libobs-graphics-vec4.rst 这份 4 分量向量(vec4)API 参考文档为主体,逐节覆盖 struct vec4 的每个成员与全部 25 个 API 函数的语义,并结合 libobs/graphics/vec4.h 与 libobs/graphics/vec4.c 的源码实现,讲清其基于 SSE 的 SIMD 优化方式、色彩转换辅助函数,以及 vec4 在 libobs 图形渲染管线中的位置。读完本文,你将能够直接使用 graphics/vec4.h 编写插件或 effect 参数处理逻辑,并理解每个函数在源码层面的实际行为。
一、头文件与 struct vec4 结构
按参考文档的约定,API 通过如下方式引入:
#include <graphics/vec4.h>
struct vec4 是一个四分量浮点向量结构。原文档给出的成员有 x、y、z、w 四个分量以及一个联合数组 ptr[4]。实际源码(vec4.h)中该结构比文档描述多一个成员——__m128 m:
struct vec4 {
union {
struct {
float x, y, z, w;
};
float ptr[4];
__m128 m; /* 文档未列出的 SIMD 视图 */
};
};
三个视图通过 C 的 union 重叠在同一块 16 字节内存上:
x/y/z/w:文档列出的按名访问方式;ptr[4]:文档列出的“所有分量的联合数组”,支持按下标批量访问(例如 memcpy、传给按float*接收的 API);m:把整个向量当作一个__m128(128 位 SSE 寄存器类型),使得任何算术运算都可以一条指令完成 4 个分量的并行计算。
这正是 vec4 实现高性能的根本原因:下文几乎所有函数都直接对 v->m 执行一条 SIMD 指令。
关于 m 的可移植性值得注意:sse-intrin.h 中,Windows 上 x64/x86(MSVC 与 MinGW)直接包含原生 <emmintrin.h>;其他平台(Linux、macOS、ARM 等)则通过 SIMDe 库的 simde/x86/sse2.h 提供 SSE 内建函数的软件模拟或平台等价映射。因此在任何受支持的平台上 vec4_* 函数语义一致,只是性能特征不同。此外 vec4.h 以 extern "C" 包裹声明,保证 C/C++ 混合编译时符号一致;它同时依赖 math-defs.h 提供的 M_PI、RAD/DEG 与各级 EPSILON 常量。
二、构造与基础操作
零化与赋值
| 函数 | 签名 | 语义 | 源码实现要点 |
|---|---|---|---|
vec4_zero |
void vec4_zero(struct vec4 *dst) |
将向量置零 | v->m = _mm_setzero_ps(),单条指令清零四个分量 |
vec4_set |
void vec4_set(struct vec4 *dst, float x, float y, float z, float w) |
按分量设置 | dst->m = _mm_set_ps(w, z, y, x) |
两个细节:
vec4_zero的参数文档写作dst: Destination,源码中形参名是v,语义相同——原地清零,无返回值;vec4_set在 SSE 层调用的_mm_set_ps参数顺序是从高位到低位(w, z, y, x),这是 SSE 指令的固有约定,API 使用者只需按数学习惯传x, y, z, w即可,封装已经处理了顺序问题。
复制与从 vec3 构造
| 函数 | 签名 | 语义 |
|---|---|---|
vec4_copy |
void vec4_copy(struct vec4 *dst, const struct vec4 *v) |
复制向量 |
vec4_from_vec3 |
void vec4_from_vec3(struct vec4 *dst, const struct vec3 *v) |
由 3 分量向量构造 4 分量向量 |
vec4_copy 即 dst->m = v->m 的一次 128 位拷贝。vec4_from_vec3 是唯一在 vec4.c 中实现的非内联函数之一:
void vec4_from_vec3(struct vec4 *dst, const struct vec3 *v)
{
dst->m = v->m;
dst->w = 1.0f;
}
它把 vec3 的三个分量直接搬入,并将 w 强制置为 1.0f——即生成齐次坐标(homogeneous coordinate)中的普通空间点,而非方向向量。这一默认行为在变换管线(配合 w 做透视除法)中很常见,使用时若需要表示“方向”,应显式调用 vec4_set 把 w 设为 0。
逐分量四则运算
文档中的四个二元运算函数:
vec4_add(dst, v1, v2):dst = v1 + v2vec4_sub(dst, v1, v2):dst = v1 - v2vec4_mul(dst, v1, v2):逐分量相乘(Hadamard 积),不是点积vec4_div(dst, v1, v2):v1为被除数,v2为除数,逐分量相除
对应源码分别是一条 _mm_add_ps / _mm_sub_ps / _mm_mul_ps / _mm_div_ps(见 vec4.h)。需要警惕的两个坑:
- 全部是非 in-place 语义(三参数:目标 + 两个源),若写
vec4_add(&a, &a, &b)这种dst与源重合的调用,语义上等价于a = a + b,SSE 指令对同一寄存器读写是安全的; vec4_div不处理除零——某分量除数为 0 时得到 Inf/NaN,是调用者的责任。
标量广播运算(*f 系列)
文档中 vec4_addf / vec4_subf / vec4_mulf / vec4_divf 把一个 float 与向量所有分量运算:
static inline void vec4_addf(struct vec4 *dst, const struct vec4 *v, float f)
{
dst->m = _mm_add_ps(v->m, _mm_set1_ps(f));
}
实现上使用 _mm_set1_ps(f) 把标量广播到四个通道,再做向量运算(vec4.h)。典型用途:给颜色 vec4 整体提亮(vec4_addf)、按系数缩放 RGB 并保留 alpha(缩放时注意 w 也会一起缩放,需要单独处理)。原文档中 vec4_addf 与 vec4_mulf 的参数说明笔误地把向量参数标成了 dst,实际第二个参数是源向量 v,以源码为准。
三、点积、长度与距离
这三个函数把 vec4 当作 4 维欧氏空间中的向量处理:
| 函数 | 签名 | 说明 |
|---|---|---|
vec4_dot |
float vec4_dot(const struct vec4 *v1, const struct vec4 *v2) |
点积,返回标量 |
vec4_len |
float vec4_len(const struct vec4 *v) |
向量长度(模) |
vec4_dist |
float vec4_dist(const struct vec4 *v1, const struct vec4 *v2) |
两向量 4 维空间距离 |
vec4_dot 的实现(vec4.h)展示了经典 SSE 归约技巧:
static inline float vec4_dot(const struct vec4 *v1, const struct vec4 *v2)
{
struct vec4 add;
__m128 mul = _mm_mul_ps(v1->m, v2->m);
add.m = _mm_add_ps(_mm_movehl_ps(mul, mul), mul);
add.m = _mm_add_ps(_mm_shuffle_ps(add.m, add.m, 0x55), add.m);
return add.x;
}
先逐分量相乘,再经 movehl + shuffle(0x55) 两次横向加,最终标量结果落在 x 分量读出。由此:
vec4_len(v)计算vec4_dot(v, v)后开方,且做了保护:dot_val > 0.0f ? sqrtf(dot_val) : 0.0f,避免对(理论上不该出现的)负值开方;vec4_dist(v1, v2)先vec4_sub再对差向量自点积开方,逻辑与vec4_len(vec4_sub(v1, v2))等价。
实际使用建议:几何上绝大多数“向量”场景用 vec3 即可;vec4_len/dist/dot 主要用于把颜色、齐次坐标等 4 分量数据当作普通数值向量做距离或内积比较时。
四、归一化与取反
vec4_neg(dst, v):逐分量取负。文档归在基础运算区,实现为逐分量-v->x等四条标量赋值(vec4.h);vec4_norm(dst, v):归一化,使结果长度(若w参与)为 1。实现上直接v / |v|的一次 SIMD 乘法:
static inline void vec4_norm(struct vec4 *dst, const struct vec4 *v)
{
float dot_val = vec4_dot(v, v);
dst->m = (dot_val > 0.0f) ? _mm_mul_ps(v->m, _mm_set1_ps(1.0f / sqrtf(dot_val))) : _mm_setzero_ps();
}
零向量保护:当 |v|² <= 0(即零向量)时返回全零而不是 NaN,这是与“裸写 v/len”的重要区别——归一化零向量安全地得到零向量。
五、分量级极值、绝对值与取整
文档中 6 个“逐分量”函数,全部对应一条 SSE 指令或标量逐分量循环:
| 函数 | 语义 | 实现 |
|---|---|---|
vec4_minf(dst, v, val) |
每分量取 min(v[i], val) |
_mm_min_ps(v->m, _mm_set1_ps(val)) |
vec4_min(dst, v, min_v) |
逐分量取两向量较小值 | _mm_min_ps(v1->m, v2->m) |
vec4_maxf(dst, v, val) |
每分量取 max(v[i], val) |
_mm_max_ps(v->m, _mm_set1_ps(val)) |
vec4_max(dst, v, max_v) |
逐分量取两向量较大值 | _mm_max_ps(v1->m, v2->m) |
vec4_abs(dst, v) |
逐分量绝对值 | 标量 fabsf 四次 |
vec4_floor(dst, v) |
逐分量向下取整 | 标量 floorf 四次 |
vec4_ceil(dst, v) |
逐分量向上取整 | 标量 ceilf 四次 |
(见 vec4.h。)
典型组合:vec4_maxf(&c, &c, 0.0f) 可把负颜色分量钳到 0,vec4_minf(&c, &c, 1.0f) 把分量钳到 1,两者连用即是对 RGB 分量做 [0,1] clamp(若需保留 alpha 不钳制,需先备份 w 或用 vec4_set 恢复)。
六、向量比较:vec4_close
int vec4_close(const struct vec4 *v1, const struct vec4 *v2, float epsilon)
文档语义:以 epsilon 作为“最大比较精度”比较两个向量。源码实现(vec4.h):
static inline int vec4_close(const struct vec4 *v1, const struct vec4 *v2, float epsilon)
{
struct vec4 test;
vec4_sub(&test, v1, v2);
return test.x < epsilon && test.y < epsilon && test.z < epsilon && test.w < epsilon;
}
两个必须注意的实现细节:
- 返回
int(非bool),非零即“相等”,符合文档中int返回类型; - 比较的是
差分量 < epsilon而非绝对值差|v1-v2| < epsilon——当某个分量v1[i] > v2[i] + epsilon且差值为负时仍会判为“close”。实践中应传入非负 epsilon 且对顺序敏感场景自行取绝对差(例如先vec4_sub再vec4_abs后逐分量判断)。epsilon 的选取可参考 math-defs.h 中定义的LARGE_EPSILON 1e-2f、EPSILON 1e-4f、TINY_EPSILON 1e-5f。
七、矩阵变换:vec4_transform
void vec4_transform(struct vec4 *dst, const struct vec4 *v, const struct matrix4 *m)
文档一句话概括“Transfoms a vector”,源码(vec4.c)给出了完整实现:
void vec4_transform(struct vec4 *dst, const struct vec4 *v, const struct matrix4 *m)
{
struct vec4 temp;
struct matrix4 transpose;
matrix4_transpose(&transpose, m);
temp.x = vec4_dot(&transpose.x, v);
temp.y = vec4_dot(&transpose.y, v);
temp.z = vec4_dot(&transpose.z, v);
temp.w = vec4_dot(&transpose.t, v);
vec4_copy(dst, &temp);
}
实现要点:
- 先对
struct matrix4(其行x/y/z/t本身就是vec4)做转置,再用 4 次vec4_dot分别算出结果四分量,等价于dst = M * v的向量-矩阵乘法; - 这里没有对
w == 0(方向)或w == 1(点)做透视除法——齐次除法(除以w)由渲染管线下游负责,本函数只做线性变换部分; dst允许与v重叠(先算临时向量再复制)。
配合 graphics.rst 中描述的 effect/着色器体系,vec4_transform 常用于 CPU 侧预计算世界/视口坐标,再把结果作为 float4 uniform 传给 effect。
八、源码中的扩展:颜色转换辅助函数
参考文档未收录、但同为 vec4.h 公共 API 的一组颜色转换内联函数,是把 vec4 当 RGBA 颜色容器使用时的高频工具(vec4.h):
| 函数 | 作用 |
|---|---|
vec4_to_rgba(const struct vec4 *src) |
向量 → uint32_t RGBA,经 gs_float4_to_u8x4 做浮点→8 位量化 |
vec4_to_bgra(const struct vec4 *src) |
同上但交换 R/B 通道,输出 BGRA 布局 |
vec4_from_rgba(struct vec4 *dst, uint32_t rgba) |
uint32_t RGBA → 向量(0..255 量化值线性映射到 0..1 浮点) |
vec4_from_bgra(struct vec4 *dst, uint32_t bgra) |
BGRA → 向量,内部完成通道交换 |
vec4_from_rgba_srgb(struct vec4 *dst, uint32_t rgba) |
转换后额外调用 gs_float3_srgb_nonlinear_to_linear,把 sRGB 非线性值转为线性值供渲染管线使用 |
实现细节:量化通过 memcpy + srgb.h 中定义的 gs_float4_to_u8x4 / gs_u8x4_to_float4 完成,不依赖平台字节序的假设写法是“先 memcpy 成 uint8_t[4],必要时交换 u[0] 与 u[2] 再 memcpy 回 uint32_t”。写 effect 或插件时,若设置/读取的是 32 位颜色值,优先使用这组函数而不是手写位移,可保证与 libobs 内部量化行为一致。
九、API 速查表
以下为参考文档收录的全部 25 个函数(结构成员 5 个已见第一节),均位于 graphics/vec4.h:
| 分类 | 函数 | 返回值 |
|---|---|---|
| 构造 | vec4_zero、vec4_set、vec4_copy、vec4_from_vec3 |
void |
| 向量算术 | vec4_add、vec4_sub、vec4_mul、vec4_div、vec4_neg |
void |
| 标量算术 | vec4_addf、vec4_subf、vec4_mulf、vec4_divf |
void |
| 度量 | vec4_dot、vec4_len、vec4_dist、vec4_norm |
float / void |
| 分量级 | vec4_minf、vec4_min、vec4_maxf、vec4_max、vec4_abs、vec4_floor、vec4_ceil |
void |
| 比较 | vec4_close |
int |
| 变换 | vec4_transform |
void |
十、实践要点小结
- 性能模型:
vec4的所有算术函数都是“一次函数调用 = 一条 128 位 SIMD 指令”,在热路径中批量处理大量 vec4(如颜色 LUT、逐像素参数表)时收益明显;SIMD 支持由 sse-intrin.h 统一抽象,Windows 走原生 SSE2,其余平台经 SIMDe 等价实现,API 层无需分支; - 安全边界:
vec4_norm、vec4_len、vec4_dist对零向量做了保护(返回零而非 NaN),而vec4_div/vec4_divf不做除零检查,使用前需自行保证除数非零; - 与 vec3 的衔接:
vec4_from_vec3固定w = 1;需要方向向量(w = 0)时请显式vec4_set; - 文档与源码的差异点:参考文档中
vec4_addf/vec4_mulf的参数标签笔误、struct vec4描述成 “Two component vector structure”(应为 Four component)均为上游文档笔误,实现与本文给出的签名一致;文档亦未覆盖__m128 m联合成员与颜色转换函数族,使用时以 vec4.h 为准; - 适用场景:vec4 是 OBS Studio 插件(
plugins/下各 effect/C 插件)、libobs-data颜色参数处理以及 CPU 侧几何预计算中的标准 4 分量载体,与vec3、matrix4(同目录 vec3.h、matrix4.h)共同构成 libobs 图形数学基础。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00