Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
/**
* Library to read packed vertex buffer data of a `gpu::Batch` using a SSBO rather than using input
* assembly. It is **not** needed to use these macros if the data is known to be aligned and
* contiguous. Arrays of any 4-byte component vector except 3 component vectors do not need this.
*
* Implemented as macros to avoid compiler differences with buffer qualifiers.
*/
/** Returns index in the first component. Needed for non trivially packed data. */
uint gpu_attr_load_index(uint vertex_index, int2 stride_and_offset)
{
return vertex_index * uint(stride_and_offset.x) + uint(stride_and_offset.y);
}
float4 gpu_attr_decode_1010102_snorm(uint in_data)
{
/* TODO(fclem): Improve this. */
uint4 v_data = uint4(in_data) >> uint4(0, 10, 20, 30);
bool4 v_sign = greaterThan(v_data & uint4(0x3FF, 0x3FF, 0x3FF, 0x3),
uint4(0x1FF, 0x1FF, 0x1FF, 0x1));
uint4 v_data_u = floatBitsToUint(mix(uintBitsToFloat(v_data), uintBitsToFloat(~v_data), v_sign));
float4 mag = float4(v_data_u & uint4(0x1FF, 0x1FF, 0x1FF, 0x1)) /
float4(0x1FF, 0x1FF, 0x1FF, 0x1);
return mix(mag, -mag, v_sign);
}
float4 gpu_attr_decode_short4_to_float4_snorm(uint data0, uint data1)
{
/* TODO(fclem): Improve this. */
uint4 v_data = uint4(data0, data0 >> 16u, data1, data1 >> 16u);
bool4 v_sign = greaterThan(v_data & uint4(0xFFFF), uint4(0x7FFF));
uint4 v_data_u = floatBitsToUint(mix(uintBitsToFloat(v_data), uintBitsToFloat(~v_data), v_sign));
float4 mag = float4(v_data_u & 0x7FFFu) / float(0x7FFF);
return mix(mag, -mag, v_sign);
}
uint4 gpu_attr_decode_uchar4_to_uint4(uint in_data)
{
return (uint4(in_data) >> uint4(0, 8, 16, 24)) & uint4(0xFF);
}
/* TODO(fclem): Once the stride and offset are made obsolete, we can think of wrapping vec3 into
* structs of floats as they do not have the 16byte alignment restriction. */
#define gpu_attr_load_triplet(_type, _data, _stride_and_offset, _i) \
_type(_data[gpu_attr_load_index(_i, _stride_and_offset) + 0], \
_data[gpu_attr_load_index(_i, _stride_and_offset) + 1], \
_data[gpu_attr_load_index(_i, _stride_and_offset) + 2])
#define gpu_attr_load_tuple(_type, _data, _stride_and_offset, _i) \
_type(_data[gpu_attr_load_index(_i, _stride_and_offset) + 0], \
_data[gpu_attr_load_index(_i, _stride_and_offset) + 1])
/* Assumes _data is declared as an array of float. */
#define gpu_attr_load_float3(_data, _stride_and_offset, _i) \
gpu_attr_load_triplet(float3, _data, _stride_and_offset, _i)
#define gpu_attr_load_float2(_data, _stride_and_offset, _i) \
gpu_attr_load_tuple(float2, _data, _stride_and_offset, _i)
/* Assumes _data is declared as an array of uint. */
#define gpu_attr_load_uint3(_data, _stride_and_offset, _i) \
gpu_attr_load_triplet(int3, _data, _stride_and_offset, _i)
#define gpu_attr_load_uint2(_data, _stride_and_offset, _i) \
gpu_attr_load_tuple(int2, _data, _stride_and_offset, _i)
/* Assumes _data is declared as an array of int. */
#define gpu_attr_load_int3(_data, _stride_and_offset, _i) \
gpu_attr_load_triplet(uint3, _data, _stride_and_offset, _i)
#define gpu_attr_load_int2(_data, _stride_and_offset, _i) \
gpu_attr_load_tuple(uint2, _data, _stride_and_offset, _i)
/* Assumes _data is declared as an array of uint. */
#define gpu_attr_load_uint_1010102_snorm(_data, _stride_and_offset, _i) \
gpu_attr_decode_1010102_snorm(_data[gpu_attr_load_index(_i, _stride_and_offset)])
/* TODO(fclem): Once the stride and offset are made obsolete, we can think of wrapping short4 into
* structs of uint as they do not have the 16byte alignment restriction. */
/* Assumes _data is declared as an array of uint. */
#define gpu_attr_load_short4_snorm(_data, _stride_and_offset, _i) \
gpu_attr_decode_short4_to_float4_snorm(_data[gpu_attr_load_index(_i, _stride_and_offset) + 0], \
_data[gpu_attr_load_index(_i, _stride_and_offset) + 1])
/* Assumes _data is declared as an array of uint. */
#define gpu_attr_load_uchar4(_data, _stride_and_offset, _i) \
gpu_attr_decode_uchar4_to_uint4(_data[gpu_attr_load_index(_i, _stride_and_offset)])
/* Assumes _data is declared as an array of uint. */
#define gpu_attr_load_uchar(_data, _i) \
gpu_attr_decode_uchar4_to_uint4( \
_data[gpu_attr_load_index(uint(_i) >> 2u, int2(1, 0))])[uint(_i) & 3u]
/* Assumes _data is declared as an array of uint. */
#define gpu_attr_load_bool(_data, _i) (gpu_attr_load_uchar(_data, _i) != 0u)

View File

@@ -0,0 +1,77 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
/** \param f: Offset from texel center in pixel space. */
void cubic_bspline_coefficients(float2 f, float2 &w0, float2 &w1, float2 &w2, float2 &w3)
{
float2 f2 = f * f;
float2 f3 = f2 * f;
/* Optimized formulae for cubic B-Spline coefficients. */
w3 = f3 / 6.0f;
w0 = -w3 + f2 * 0.5f - f * 0.5f + 1.0f / 6.0f;
w1 = f3 * 0.5f - f2 * 1.0f + 2.0f / 3.0f;
w2 = 1.0f - w0 - w1 - w3;
}
/* Samples the given 2D sampler at the given coordinates using Bicubic interpolation. This function
* uses an optimized algorithm which assumes a linearly filtered sampler, so the caller needs to
* take that into account when setting up the sampler. */
float4 texture_bicubic(sampler2D sampler_2d, float2 coordinates)
{
float2 texture_size = float2(textureSize(sampler_2d, 0).xy);
coordinates.xy *= texture_size;
float2 w0, w1, w2, w3;
float2 texel_center = floor(coordinates.xy - 0.5f) + 0.5f;
cubic_bspline_coefficients(coordinates.xy - texel_center, w0, w1, w2, w3);
#if 1 /* Optimized version using 4 filtered taps. */
float2 s0 = w0 + w1;
float2 s1 = w2 + w3;
float2 f0 = w1 / (w0 + w1);
float2 f1 = w3 / (w2 + w3);
float4 sampling_coordinates;
sampling_coordinates.xy = texel_center - 1.0f + f0;
sampling_coordinates.zw = texel_center + 1.0f + f1;
sampling_coordinates /= texture_size.xyxy;
float4 sampled_color = textureLod(sampler_2d, sampling_coordinates.xy, 0.0f) * s0.x * s0.y;
sampled_color += textureLod(sampler_2d, sampling_coordinates.zy, 0.0f) * s1.x * s0.y;
sampled_color += textureLod(sampler_2d, sampling_coordinates.xw, 0.0f) * s0.x * s1.y;
sampled_color += textureLod(sampler_2d, sampling_coordinates.zw, 0.0f) * s1.x * s1.y;
return sampled_color;
#else /* Reference brute-force 16 taps. */
float4 color = texelFetch(sampler_2d, int2(texel_center + float2(-1.0f, -1.0f)), 0) * w0.x *
w0.y;
color += texelFetch(sampler_2d, int2(texel_center + float2(0.0f, -1.0f)), 0) * w1.x * w0.y;
color += texelFetch(sampler_2d, int2(texel_center + float2(1.0f, -1.0f)), 0) * w2.x * w0.y;
color += texelFetch(sampler_2d, int2(texel_center + float2(2.0f, -1.0f)), 0) * w3.x * w0.y;
color += texelFetch(sampler_2d, int2(texel_center + float2(-1.0f, 0.0f)), 0) * w0.x * w1.y;
color += texelFetch(sampler_2d, int2(texel_center + float2(0.0f, 0.0f)), 0) * w1.x * w1.y;
color += texelFetch(sampler_2d, int2(texel_center + float2(1.0f, 0.0f)), 0) * w2.x * w1.y;
color += texelFetch(sampler_2d, int2(texel_center + float2(2.0f, 0.0f)), 0) * w3.x * w1.y;
color += texelFetch(sampler_2d, int2(texel_center + float2(-1.0f, 1.0f)), 0) * w0.x * w2.y;
color += texelFetch(sampler_2d, int2(texel_center + float2(0.0f, 1.0f)), 0) * w1.x * w2.y;
color += texelFetch(sampler_2d, int2(texel_center + float2(1.0f, 1.0f)), 0) * w2.x * w2.y;
color += texelFetch(sampler_2d, int2(texel_center + float2(2.0f, 1.0f)), 0) * w3.x * w2.y;
color += texelFetch(sampler_2d, int2(texel_center + float2(-1.0f, 2.0f)), 0) * w0.x * w3.y;
color += texelFetch(sampler_2d, int2(texel_center + float2(0.0f, 2.0f)), 0) * w1.x * w3.y;
color += texelFetch(sampler_2d, int2(texel_center + float2(1.0f, 2.0f)), 0) * w2.x * w3.y;
color += texelFetch(sampler_2d, int2(texel_center + float2(2.0f, 2.0f)), 0) * w3.x * w3.y;
return color;
#endif
}

View File

@@ -0,0 +1,63 @@
/* SPDX-FileCopyrightText: 2019-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
[[node]]
void valtorgb_opti_constant(
float fac, float edge, float4 color1, float4 color2, float4 &outcol, float &outalpha)
{
outcol = (fac > edge) ? color2 : color1;
outalpha = outcol.a;
}
[[node]]
void valtorgb_opti_linear(
float fac, float2 mulbias, float4 color1, float4 color2, float4 &outcol, float &outalpha)
{
fac = clamp(fac * mulbias.x + mulbias.y, 0.0f, 1.0f);
outcol = mix(color1, color2, fac);
outalpha = outcol.a;
}
[[node]]
void valtorgb_opti_ease(
float fac, float2 mulbias, float4 color1, float4 color2, float4 &outcol, float &outalpha)
{
fac = clamp(fac * mulbias.x + mulbias.y, 0.0f, 1.0f);
fac = fac * fac * (3.0f - 2.0f * fac);
outcol = mix(color1, color2, fac);
outalpha = outcol.a;
}
/* Color maps are stored in texture samplers, so ensure that the coordinate evaluates the sampler
* at the center of the pixels, because samplers are evaluated using linear interpolation. Given
* the coordinate in the [0, 1] range. */
float compute_color_map_coordinate(float coordinate)
{
/* Color maps have a fixed width of 257. We offset by the equivalent of half a pixel and scale
* down such that the normalized coordinate 1.0 corresponds to the center of the last pixel. */
constexpr float sampler_resolution = 257.0f;
constexpr float sampler_offset = 0.5f / sampler_resolution;
constexpr float sampler_scale = 1.0f - (1.0f / sampler_resolution);
return coordinate * sampler_scale + sampler_offset;
}
[[node]]
void valtorgb(float fac, sampler1DArray colormap, float layer, float4 &outcol, float &outalpha)
{
outcol = texture(colormap, float2(compute_color_map_coordinate(fac), layer));
outalpha = outcol.a;
}
[[node]]
void valtorgb_nearest(
float fac, sampler1DArray colormap, float layer, float4 &outcol, float &outalpha)
{
fac = clamp(fac, 0.0f, 1.0f);
outcol = texelFetch(colormap, int2(fac * (textureSize(colormap, 0).x - 1), layer), 0);
outalpha = outcol.a;
}

View File

@@ -0,0 +1,307 @@
/* SPDX-FileCopyrightText: 2019-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
[[node]]
void rgb_to_hsv(float4 rgb, float4 &outcol)
{
float cmax, cmin, h, s, v, cdelta;
float3 c;
cmax = max(rgb[0], max(rgb[1], rgb[2]));
cmin = min(rgb[0], min(rgb[1], rgb[2]));
cdelta = cmax - cmin;
v = cmax;
if (cmax != 0.0f) {
s = cdelta / cmax;
}
else {
s = 0.0f;
h = 0.0f;
}
if (s == 0.0f) {
h = 0.0f;
}
else {
c = (float3(cmax) - rgb.xyz) / cdelta;
if (rgb.x == cmax) {
h = c[2] - c[1];
}
else if (rgb.y == cmax) {
h = 2.0f + c[0] - c[2];
}
else {
h = 4.0f + c[1] - c[0];
}
h /= 6.0f;
if (h < 0.0f) {
h += 1.0f;
}
}
outcol = float4(h, s, v, rgb.w);
}
[[node]]
void hsv_to_rgb(float4 hsv, float4 &outcol)
{
float i, f, p, q, t, h, s, v;
float3 rgb;
h = hsv[0];
s = hsv[1];
v = hsv[2];
if (s == 0.0f) {
rgb = float3(v, v, v);
}
else {
if (h == 1.0f) {
h = 0.0f;
}
h *= 6.0f;
i = floor(h);
f = h - i;
rgb = float3(f, f, f);
p = v * (1.0f - s);
q = v * (1.0f - (s * f));
t = v * (1.0f - (s * (1.0f - f)));
if (i == 0.0f) {
rgb = float3(v, t, p);
}
else if (i == 1.0f) {
rgb = float3(q, v, p);
}
else if (i == 2.0f) {
rgb = float3(p, v, t);
}
else if (i == 3.0f) {
rgb = float3(p, q, v);
}
else if (i == 4.0f) {
rgb = float3(t, p, v);
}
else {
rgb = float3(v, p, q);
}
}
outcol = float4(rgb, hsv.w);
}
[[node]]
void rgb_to_hsl(float4 rgb, float4 &outcol)
{
float cmax, cmin, h, s, l;
cmax = max(rgb[0], max(rgb[1], rgb[2]));
cmin = min(rgb[0], min(rgb[1], rgb[2]));
l = min(1.0f, (cmax + cmin) / 2.0f);
if (cmax == cmin) {
h = s = 0.0f; /* achromatic */
}
else {
float cdelta = cmax - cmin;
s = l > 0.5f ? cdelta / (2.0f - cmax - cmin) : cdelta / (cmax + cmin);
if (cmax == rgb[0]) {
h = (rgb[1] - rgb[2]) / cdelta + (rgb[1] < rgb[2] ? 6.0f : 0.0f);
}
else if (cmax == rgb[1]) {
h = (rgb[2] - rgb[0]) / cdelta + 2.0f;
}
else {
h = (rgb[0] - rgb[1]) / cdelta + 4.0f;
}
}
h /= 6.0f;
outcol = float4(h, s, l, rgb.w);
}
[[node]]
void hsl_to_rgb(float4 hsl, float4 &outcol)
{
float nr, ng, nb, chroma, h, s, l;
h = hsl[0];
s = hsl[1];
l = hsl[2];
nr = abs(h * 6.0f - 3.0f) - 1.0f;
ng = 2.0f - abs(h * 6.0f - 2.0f);
nb = 2.0f - abs(h * 6.0f - 4.0f);
nr = clamp(nr, 0.0f, 1.0f);
nb = clamp(nb, 0.0f, 1.0f);
ng = clamp(ng, 0.0f, 1.0f);
chroma = (1.0f - abs(2.0f * l - 1.0f)) * s;
outcol = float4(
(nr - 0.5f) * chroma + l, (ng - 0.5f) * chroma + l, (nb - 0.5f) * chroma + l, hsl.w);
}
/* ** YCCA to RGBA ** */
[[node]]
void ycca_to_rgba_itu_601(float4 ycca, float4 &color)
{
ycca.xyz *= 255.0f;
ycca.xyz -= float3(16.0f, 128.0f, 128.0f);
color.rgb = float3x3(1.164f, 1.164f, 1.164f, 0.0f, -0.392f, 2.017f, 1.596f, -0.813f, 0.0f) *
ycca.xyz;
color.rgb /= 255.0f;
color.a = ycca.a;
}
[[node]]
void ycca_to_rgba_itu_709(float4 ycca, float4 &color)
{
ycca.xyz *= 255.0f;
ycca.xyz -= float3(16.0f, 128.0f, 128.0f);
color.rgb = float3x3(1.164f, 1.164f, 1.164f, 0.0f, -0.213f, 2.115f, 1.793f, -0.534f, 0.0f) *
ycca.xyz;
color.rgb /= 255.0f;
color.a = ycca.a;
}
[[node]]
void ycca_to_rgba_jpeg(float4 ycca, float4 &color)
{
ycca.xyz *= 255.0f;
color.rgb = float3x3(1.0f, 1.0f, 1.0f, 0.0f, -0.34414f, 1.772f, 1.402f, -0.71414f, 0.0f) *
ycca.xyz;
color.rgb += float3(-179.456f, 135.45984f, -226.816f);
color.rgb /= 255.0f;
color.a = ycca.a;
}
/* ** RGBA to YCCA ** */
[[node]]
void rgba_to_ycca_itu_601(float4 rgba, float4 &ycca)
{
rgba.rgb *= 255.0f;
ycca.xyz = float3x3(0.257f, -0.148f, 0.439f, 0.504f, -0.291f, -0.368f, 0.098f, 0.439f, -0.071f) *
rgba.rgb;
ycca.xyz += float3(16.0f, 128.0f, 128.0f);
ycca.xyz /= 255.0f;
ycca.a = rgba.a;
}
[[node]]
void rgba_to_ycca_itu_709(float4 rgba, float4 &ycca)
{
rgba.rgb *= 255.0f;
ycca.xyz = float3x3(0.183f, -0.101f, 0.439f, 0.614f, -0.338f, -0.399f, 0.062f, 0.439f, -0.040f) *
rgba.rgb;
ycca.xyz += float3(16.0f, 128.0f, 128.0f);
ycca.xyz /= 255.0f;
ycca.a = rgba.a;
}
[[node]]
void rgba_to_ycca_jpeg(float4 rgba, float4 &ycca)
{
rgba.rgb *= 255.0f;
ycca.xyz = float3x3(
0.299f, -0.16874f, 0.5f, 0.587f, -0.33126f, -0.41869f, 0.114f, 0.5f, -0.08131f) *
rgba.rgb;
ycca.xyz += float3(0.0f, 128.0f, 128.0f);
ycca.xyz /= 255.0f;
ycca.a = rgba.a;
}
/* ** YUVA to RGBA ** */
[[node]]
void yuva_to_rgba_itu_709(float4 yuva, float4 &color)
{
color.rgb = float3x3(1.0f, 1.0f, 1.0f, 0.0f, -0.21482f, 2.12798f, 1.28033f, -0.38059f, 0.0f) *
yuva.xyz;
color.a = yuva.a;
}
/* ** RGBA to YUVA ** */
[[node]]
void rgba_to_yuva_itu_709(float4 rgba, float4 &yuva)
{
yuva.xyz =
float3x3(
0.2126f, -0.09991f, 0.615f, 0.7152f, -0.33609f, -0.55861f, 0.0722f, 0.436f, -0.05639f) *
rgba.rgb;
yuva.a = rgba.a;
}
/* ** Alpha Handling ** */
[[node]]
void color_alpha_clear(float4 color, float4 &result)
{
result = float4(color.rgb, 1.0f);
}
[[node]]
void color_alpha_premultiply(float4 color, float4 &result)
{
result = float4(color.rgb * color.a, color.a);
}
[[node]]
void color_alpha_unpremultiply(float4 color, float4 &result)
{
if (color.a == 0.0f || color.a == 1.0f) {
result = color;
}
else {
result = float4(color.rgb / color.a, color.a);
}
}
float linear_rgb_to_srgb(float color)
{
if (color < 0.0031308f) {
return (color < 0.0f) ? 0.0f : color * 12.92f;
}
return 1.055f * pow(color, 1.0f / 2.4f) - 0.055f;
}
float3 linear_rgb_to_srgb(float3 color)
{
return float3(
linear_rgb_to_srgb(color.r), linear_rgb_to_srgb(color.g), linear_rgb_to_srgb(color.b));
}
float srgb_to_linear_rgb(float color)
{
if (color < 0.04045f) {
return (color < 0.0f) ? 0.0f : color * (1.0f / 12.92f);
}
return pow((color + 0.055f) * (1.0f / 1.055f), 2.4f);
}
float3 srgb_to_linear_rgb(float3 color)
{
return float3(
srgb_to_linear_rgb(color.r), srgb_to_linear_rgb(color.g), srgb_to_linear_rgb(color.b));
}
float get_luminance(float3 color, float3 luminance_coefficients)
{
return dot(color, luminance_coefficients);
}

View File

@@ -0,0 +1,362 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
float4 white_balance(float4 color, float4 black_level, float4 white_level)
{
float4 range = max(white_level - black_level, float4(1e-5f));
return (color - black_level) / range;
}
float extrapolate_if_needed(float parameter, float value, float start_slope, float end_slope)
{
if (parameter < 0.0f) {
return value + parameter * start_slope;
}
if (parameter > 1.0f) {
return value + (parameter - 1.0f) * end_slope;
}
return value;
}
/* Same as extrapolate_if_needed but vectorized. */
float3 extrapolate_if_needed(float3 parameters,
float3 values,
float3 start_slopes,
float3 end_slopes)
{
float3 end_or_zero_slopes = mix(float3(0.0f), end_slopes, greaterThan(parameters, float3(1.0f)));
float3 slopes = mix(end_or_zero_slopes, start_slopes, lessThan(parameters, float3(0.0f)));
parameters = parameters - mix(float3(0.0f), float3(1.0f), greaterThan(parameters, float3(1.0f)));
return values + parameters * slopes;
}
/* Curve maps are stored in texture samplers, so ensure that the parameters evaluate the sampler at
* the center of the pixels, because samplers are evaluated using linear interpolation. Given the
* parameter in the [0, 1] range. */
float compute_curve_map_coordinates(float parameter)
{
/* Curve maps have a fixed width of 257. We offset by the equivalent of half a pixel and scale
* down such that the normalized parameter 1.0 corresponds to the center of the last pixel. */
float sampler_offset = 0.5f / 257.0f;
float sampler_scale = 1.0f - (1.0f / 257.0f);
return parameter * sampler_scale + sampler_offset;
}
/* Same as compute_curve_map_coordinates but vectorized. */
float3 compute_curve_map_coordinates(float3 parameters)
{
float sampler_offset = 0.5f / 257.0f;
float sampler_scale = 1.0f - (1.0f / 257.0f);
return parameters * sampler_scale + sampler_offset;
}
[[node]]
void curves_combined_rgb(float factor,
float4 color,
float4 black_level,
float4 white_level,
sampler1DArray curve_map,
const float layer,
float4 range_minimums,
float4 range_dividers,
float4 start_slopes,
float4 end_slopes,
float4 &result)
{
float4 balanced = white_balance(color, black_level, white_level);
/* First, evaluate alpha curve map at all channels. The alpha curve is the Combined curve in the
* UI. The channels are first normalized into the [0, 1] range. */
float3 parameters = (balanced.rgb - range_minimums.aaa) * range_dividers.aaa;
float3 coordinates = compute_curve_map_coordinates(parameters);
result.r = texture(curve_map, float2(coordinates.x, layer)).a;
result.g = texture(curve_map, float2(coordinates.y, layer)).a;
result.b = texture(curve_map, float2(coordinates.z, layer)).a;
/* Then, extrapolate if needed. */
result.rgb = extrapolate_if_needed(parameters, result.rgb, start_slopes.aaa, end_slopes.aaa);
/* Then, evaluate each channel on its curve map. The channels are first normalized into the
* [0, 1] range. */
parameters = (result.rgb - range_minimums.rgb) * range_dividers.rgb;
coordinates = compute_curve_map_coordinates(parameters);
result.r = texture(curve_map, float2(coordinates.r, layer)).r;
result.g = texture(curve_map, float2(coordinates.g, layer)).g;
result.b = texture(curve_map, float2(coordinates.b, layer)).b;
/* Then, extrapolate again if needed. */
result.rgb = extrapolate_if_needed(parameters, result.rgb, start_slopes.rgb, end_slopes.rgb);
result.a = color.a;
result = mix(color, result, factor);
}
[[node]]
void curves_combined_rgb_compositor(float4 color,
float factor,
float4 black_level,
float4 white_level,
sampler1DArray curve_map,
const float layer,
float4 range_minimums,
float4 range_dividers,
float4 start_slopes,
float4 end_slopes,
float4 &result)
{
curves_combined_rgb(factor,
color,
black_level,
white_level,
curve_map,
layer,
range_minimums,
range_dividers,
start_slopes,
end_slopes,
result);
}
[[node]]
void curves_combined_only(float factor,
float4 color,
float4 black_level,
float4 white_level,
sampler1DArray curve_map,
const float layer,
float range_minimum,
float range_divider,
float start_slope,
float end_slope,
float4 &result)
{
float4 balanced = white_balance(color, black_level, white_level);
/* Evaluate alpha curve map at all channels. The alpha curve is the Combined curve in the
* UI. The channels are first normalized into the [0, 1] range. */
float3 parameters = (balanced.rgb - float3(range_minimum)) * float3(range_divider);
float3 coordinates = compute_curve_map_coordinates(parameters);
result.r = texture(curve_map, float2(coordinates.x, layer)).a;
result.g = texture(curve_map, float2(coordinates.y, layer)).a;
result.b = texture(curve_map, float2(coordinates.z, layer)).a;
/* Then, extrapolate if needed. */
result.rgb = extrapolate_if_needed(
parameters, result.rgb, float3(start_slope), float3(end_slope));
result.a = color.a;
result = mix(color, result, factor);
}
[[node]]
void curves_combined_only_compositor(float4 color,
float factor,
float4 black_level,
float4 white_level,
sampler1DArray curve_map,
const float layer,
float range_minimum,
float range_divider,
float start_slope,
float end_slope,
float4 &result)
{
curves_combined_only(factor,
color,
black_level,
white_level,
curve_map,
layer,
range_minimum,
range_divider,
start_slope,
end_slope,
result);
}
/* Contrary to standard tone curve implementations, the film-like implementation tries to preserve
* the hue of the colors as much as possible. To understand why this might be a problem, consider
* the violet color (0.5, 0.0, 1.0). If this color was to be evaluated at a power curve x^4, the
* color will be blue (0.0625, 0.0, 1.0). So the color changes and not just its luminosity,
* which is what film-like tone curves tries to avoid.
*
* First, the channels with the lowest and highest values are identified and evaluated at the
* curve. Then, the third channel---the median---is computed while maintaining the original hue of
* the color. To do that, we look at the equation for deriving the hue from RGB values. Assuming
* the maximum, minimum, and median channels are known, and ignoring the 1/3 period offset of the
* hue, the equation is:
*
* hue = (median - min) / (max - min) [1]
*
* Since we have the new values for the minimum and maximum after evaluating at the curve, we also
* have:
*
* hue = (new_median - new_min) / (new_max - new_min) [2]
*
* Since we want the hue to be equivalent, by equating [1] and [2] and rearranging:
*
* (new_median - new_min) / (new_max - new_min) = (median - min) / (max - min)
* new_median - new_min = (new_max - new_min) * (median - min) / (max - min)
* new_median = new_min + (new_max - new_min) * (median - min) / (max - min)
* new_median = new_min + (median - min) * ((new_max - new_min) / (max - min)) [QED]
*
* Which gives us the median color that preserves the hue. More intuitively, the median is computed
* such that the change in the distance from the median to the minimum is proportional to the
* change in the distance from the minimum to the maximum. Finally, each of the new minimum,
* maximum, and median values are written to the color channel that they were originally extracted
* from. */
[[node]]
void curves_film_like(float factor,
float4 color,
float4 black_level,
float4 white_level,
sampler1DArray curve_map,
const float layer,
float range_minimum,
float range_divider,
float start_slope,
float end_slope,
float4 &result)
{
float4 balanced = white_balance(color, black_level, white_level);
/* Find the maximum, minimum, and median of the color channels. */
float minimum = min(balanced.r, min(balanced.g, balanced.b));
float maximum = max(balanced.r, max(balanced.g, balanced.b));
float median = max(min(balanced.r, balanced.g), min(balanced.b, max(balanced.r, balanced.g)));
/* Evaluate alpha curve map at the maximum and minimum channels. The alpha curve is the Combined
* curve in the UI. The channels are first normalized into the [0, 1] range. */
float min_parameter = (minimum - range_minimum) * range_divider;
float max_parameter = (maximum - range_minimum) * range_divider;
float min_coordinates = compute_curve_map_coordinates(min_parameter);
float max_coordinates = compute_curve_map_coordinates(max_parameter);
float new_min = texture(curve_map, float2(min_coordinates, layer)).a;
float new_max = texture(curve_map, float2(max_coordinates, layer)).a;
/* Then, extrapolate if needed. */
new_min = extrapolate_if_needed(min_parameter, new_min, start_slope, end_slope);
new_max = extrapolate_if_needed(max_parameter, new_max, start_slope, end_slope);
/* Compute the new median using the ratio between the new and the original range. */
float scaling_ratio = (new_max - new_min) / (maximum - minimum);
float new_median = new_min + (median - minimum) * scaling_ratio;
/* Write each value to its original channel. */
bool3 channel_is_min = equal(balanced.rgb, float3(minimum));
float3 median_or_min = mix(float3(new_median), float3(new_min), channel_is_min);
bool3 channel_is_max = equal(balanced.rgb, float3(maximum));
result.rgb = mix(median_or_min, float3(new_max), channel_is_max);
result.a = color.a;
result = mix(color, result, clamp(factor, 0.0f, 1.0f));
}
[[node]]
void curves_film_like_compositor(float4 color,
float factor,
float4 black_level,
float4 white_level,
sampler1DArray curve_map,
const float layer,
float range_minimum,
float range_divider,
float start_slope,
float end_slope,
float4 &result)
{
curves_film_like(factor,
color,
black_level,
white_level,
curve_map,
layer,
range_minimum,
range_divider,
start_slope,
end_slope,
result);
}
[[node]]
void curves_vector(float3 vector,
sampler1DArray curve_map,
const float layer,
float3 range_minimums,
float3 range_dividers,
float3 start_slopes,
float3 end_slopes,
float3 &result)
{
/* Evaluate each component on its curve map.
* The components are first normalized into the [0, 1] range. */
float3 parameters = (vector - range_minimums) * range_dividers;
float3 coordinates = compute_curve_map_coordinates(parameters);
result.x = texture(curve_map, float2(coordinates.x, layer)).x;
result.y = texture(curve_map, float2(coordinates.y, layer)).y;
result.z = texture(curve_map, float2(coordinates.z, layer)).z;
/* Then, extrapolate if needed. */
result = extrapolate_if_needed(parameters, result, start_slopes, end_slopes);
}
[[node]]
void curves_vector_mixed(float factor,
float3 vector,
sampler1DArray curve_map,
const float layer,
float3 range_minimums,
float3 range_dividers,
float3 start_slopes,
float3 end_slopes,
float3 &result)
{
curves_vector(
vector, curve_map, layer, range_minimums, range_dividers, start_slopes, end_slopes, result);
result = mix(vector, result, factor);
}
[[node]]
void curves_float(float value,
sampler1DArray curve_map,
const float layer,
float range_minimum,
float range_divider,
float start_slope,
float end_slope,
float &result)
{
/* Evaluate the normalized value on the first curve map. */
float parameter = (value - range_minimum) * range_divider;
float coordinates = compute_curve_map_coordinates(parameter);
result = texture(curve_map, float2(coordinates, layer)).x;
/* Then, extrapolate if needed. */
result = extrapolate_if_needed(parameter, result, start_slope, end_slope);
}
[[node]]
void curves_float_mixed(float factor,
float value,
sampler1DArray curve_map,
const float layer,
float range_minimum,
float range_divider,
float start_slope,
float end_slope,
float &result)
{
curves_float(
value, curve_map, layer, range_minimum, range_divider, start_slope, end_slope, result);
result = mix(value, result, factor);
}

View File

@@ -0,0 +1,335 @@
/* SPDX-FileCopyrightText: 2019-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
/* ***** Jenkins Lookup3 Hash Functions ***** */
/* Source: http://burtleburtle.net/bob/c/lookup3.c */
#define rot(x, k) (((x) << (k)) | ((x) >> (32 - (k))))
#define mix(a, b, c) \
{ \
a -= c; \
a ^= rot(c, 4); \
c += b; \
b -= a; \
b ^= rot(a, 6); \
a += c; \
c -= b; \
c ^= rot(b, 8); \
b += a; \
a -= c; \
a ^= rot(c, 16); \
c += b; \
b -= a; \
b ^= rot(a, 19); \
a += c; \
c -= b; \
c ^= rot(b, 4); \
b += a; \
}
#define final(a, b, c) \
{ \
c ^= b; \
c -= rot(b, 14); \
a ^= c; \
a -= rot(c, 11); \
b ^= a; \
b -= rot(a, 25); \
c ^= b; \
c -= rot(b, 16); \
a ^= c; \
a -= rot(c, 4); \
b ^= a; \
b -= rot(a, 14); \
c ^= b; \
c -= rot(b, 24); \
}
uint hash_uint(uint kx)
{
uint a, b, c;
a = b = c = 0xdeadbeefu + (1u << 2u) + 13u;
a += kx;
final(a, b, c);
return c;
}
uint hash_uint2(uint kx, uint ky)
{
uint a, b, c;
a = b = c = 0xdeadbeefu + (2u << 2u) + 13u;
b += ky;
a += kx;
final(a, b, c);
return c;
}
uint hash_uint3(uint kx, uint ky, uint kz)
{
uint a, b, c;
a = b = c = 0xdeadbeefu + (3u << 2u) + 13u;
c += kz;
b += ky;
a += kx;
final(a, b, c);
return c;
}
uint hash_uint4(uint kx, uint ky, uint kz, uint kw)
{
uint a, b, c;
a = b = c = 0xdeadbeefu + (4u << 2u) + 13u;
a += kx;
b += ky;
c += kz;
mix(a, b, c);
a += kw;
final(a, b, c);
return c;
}
#undef rot
#undef final
#undef mix
uint hash_int(int kx)
{
return hash_uint(uint(kx));
}
uint hash_int2(int kx, int ky)
{
return hash_uint2(uint(kx), uint(ky));
}
uint hash_int3(int kx, int ky, int kz)
{
return hash_uint3(uint(kx), uint(ky), uint(kz));
}
uint hash_int4(int kx, int ky, int kz, int kw)
{
return hash_uint4(uint(kx), uint(ky), uint(kz), uint(kw));
}
/* PCG 2D, 3D and 4D hash functions,
* from "Hash Functions for GPU Rendering" JCGT 2020
* https://jcgt.org/published/0009/03/02/
*
* Slightly modified to only use signed integers,
* so that they can also be implemented in OSL. */
int2 hash_pcg2d_i(int2 v)
{
v = v * 1664525 + 1013904223;
v.x += v.y * 1664525;
v.y += v.x * 1664525;
v = v ^ (v >> 16);
v.x += v.y * 1664525;
v.y += v.x * 1664525;
return v;
}
int3 hash_pcg3d_i(int3 v)
{
v = v * 1664525 + 1013904223;
v.x += v.y * v.z;
v.y += v.z * v.x;
v.z += v.x * v.y;
v = v ^ (v >> 16);
v.x += v.y * v.z;
v.y += v.z * v.x;
v.z += v.x * v.y;
return v;
}
int4 hash_pcg4d_i(int4 v)
{
v = v * 1664525 + 1013904223;
v.x += v.y * v.w;
v.y += v.z * v.x;
v.z += v.x * v.y;
v.w += v.y * v.z;
v = v ^ (v >> 16);
v.x += v.y * v.w;
v.y += v.z * v.x;
v.z += v.x * v.y;
v.w += v.y * v.z;
return v;
}
/* Hashing uint or uint[234] into a float in the range [0, 1]. */
float hash_uint_to_float(uint kx)
{
return float(hash_uint(kx)) / float(0xFFFFFFFFu);
}
float hash_uint2_to_float(uint kx, uint ky)
{
return float(hash_uint2(kx, ky)) / float(0xFFFFFFFFu);
}
float hash_uint3_to_float(uint kx, uint ky, uint kz)
{
return float(hash_uint3(kx, ky, kz)) / float(0xFFFFFFFFu);
}
float hash_uint4_to_float(uint kx, uint ky, uint kz, uint kw)
{
return float(hash_uint4(kx, ky, kz, kw)) / float(0xFFFFFFFFu);
}
/* Hashing float or vec[234] into a float in the range [0, 1]. */
float hash_float_to_float(float k)
{
return hash_uint_to_float(floatBitsToUint(k));
}
float hash_vec2_to_float(float2 k)
{
return hash_uint2_to_float(floatBitsToUint(k.x), floatBitsToUint(k.y));
}
float hash_vec3_to_float(float3 k)
{
return hash_uint3_to_float(floatBitsToUint(k.x), floatBitsToUint(k.y), floatBitsToUint(k.z));
}
float hash_vec4_to_float(float4 k)
{
return hash_uint4_to_float(
floatBitsToUint(k.x), floatBitsToUint(k.y), floatBitsToUint(k.z), floatBitsToUint(k.w));
}
/* Hashing vec[234] into vec[234] of components in the range [0, 1]. */
float2 hash_vec2_to_vec2(float2 k)
{
return float2(hash_vec2_to_float(k), hash_vec3_to_float(float3(k, 1.0f)));
}
float3 hash_vec3_to_vec3(float3 k)
{
return float3(hash_vec3_to_float(k),
hash_vec4_to_float(float4(k, 1.0f)),
hash_vec4_to_float(float4(k, 2.0f)));
}
float4 hash_vec4_to_vec4(float4 k)
{
return float4(hash_vec4_to_float(k.xyzw),
hash_vec4_to_float(k.wxyz),
hash_vec4_to_float(k.zwxy),
hash_vec4_to_float(k.yzwx));
}
/* Hashing a number of integers into floats in [0..1] range. */
float2 hash_int2_to_vec2(int2 k)
{
int2 h = hash_pcg2d_i(k);
return float2(h & 0x7fffffff) * (1.0 / float(0x7fffffff));
}
float3 hash_int3_to_vec3(int3 k)
{
int3 h = hash_pcg3d_i(k);
return float3(h & 0x7fffffff) * (1.0 / float(0x7fffffff));
}
float4 hash_int4_to_vec4(int4 k)
{
int4 h = hash_pcg4d_i(k);
return float4(h & 0x7fffffff) * (1.0 / float(0x7fffffff));
}
float3 hash_int2_to_vec3(int2 k)
{
return hash_int3_to_vec3(int3(k.x, k.y, 0));
}
float3 hash_int4_to_vec3(int4 k)
{
return hash_int4_to_vec4(k).xyz;
}
/* Hashing float or vec[234] into vec3 of components in range [0, 1]. */
float3 hash_float_to_vec3(float k)
{
return float3(hash_float_to_float(k),
hash_vec2_to_float(float2(k, 1.0f)),
hash_vec2_to_float(float2(k, 2.0f)));
}
float3 hash_vec2_to_vec3(float2 k)
{
return float3(hash_vec2_to_float(k),
hash_vec3_to_float(float3(k, 1.0f)),
hash_vec3_to_float(float3(k, 2.0f)));
}
float3 hash_vec4_to_vec3(float4 k)
{
return float3(
hash_vec4_to_float(k.xyzw), hash_vec4_to_float(k.zxwy), hash_vec4_to_float(k.wzyx));
}
/* Hashing float or vec[234] into vec2 of components in range [0, 1]. */
float2 hash_float_to_vec2(float k)
{
return float2(hash_float_to_float(k), hash_vec2_to_float(float2(k, 1.0f)));
}
float2 hash_vec3_to_vec2(float3 k)
{
return float2(hash_vec3_to_float(k.xyz), hash_vec3_to_float(k.zxy));
}
float2 hash_vec4_to_vec2(float4 k)
{
return float2(hash_vec4_to_float(k.xyzw), hash_vec4_to_float(k.zxwy));
}
/* Other Hash Functions */
float integer_noise(int n)
{
/* Integer bit-shifts for these calculations can cause precision problems on macOS.
* Using uint resolves these issues. */
uint nn;
nn = (uint(n) + 1013u) & 0x7fffffffu;
nn = (nn >> 13u) ^ nn;
nn = (uint(nn * (nn * nn * 60493u + 19990303u)) + 1376312589u) & 0x7fffffffu;
return 0.5f * (float(nn) / 1073741824.0f);
}
float wang_hash_noise(uint s)
{
s = (s ^ 61u) ^ (s >> 16u);
s *= 9u;
s = s ^ (s >> 4u);
s *= 0x27d4eb2du;
s = s ^ (s >> 15u);
return fract(float(s) / 4294967296.0f);
}

View File

@@ -0,0 +1,289 @@
/* SPDX-FileCopyrightText: 2019-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
#include "gpu_shader_math_base_lib.glsl"
#include "gpu_shader_math_safe_lib.glsl"
[[node]]
void math_add(float a, float b, float c, float &result)
{
result = a + b;
}
[[node]]
void math_subtract(float a, float b, float c, float &result)
{
result = a - b;
}
[[node]]
void math_multiply(float a, float b, float c, float &result)
{
result = a * b;
}
[[node]]
void math_divide(float a, float b, float c, float &result)
{
result = safe_divide(a, b);
}
[[node]]
void math_power(float a, float b, float c, float &result)
{
if (a >= 0.0f) {
result = compatible_pow(a, b);
}
else {
float fraction = mod(abs(b), 1.0f);
if (fraction > 0.999f || fraction < 0.001f) {
result = compatible_pow(a, floor(b + 0.5f));
}
else {
result = 0.0f;
}
}
}
[[node]]
void math_logarithm(float a, float b, float c, float &result)
{
result = (a > 0.0f && b > 0.0f) ? log2(a) / log2(b) : 0.0f;
}
[[node]]
void math_sqrt(float a, float b, float c, float &result)
{
result = (a > 0.0f) ? sqrt(a) : 0.0f;
}
[[node]]
void math_inversesqrt(float a, float b, float c, float &result)
{
result = inversesqrt(a);
}
[[node]]
void math_absolute(float a, float b, float c, float &result)
{
result = abs(a);
}
[[node]]
void math_radians(float a, float b, float c, float &result)
{
result = radians(a);
}
[[node]]
void math_degrees(float a, float b, float c, float &result)
{
result = degrees(a);
}
[[node]]
void math_minimum(float a, float b, float c, float &result)
{
result = min(a, b);
}
[[node]]
void math_maximum(float a, float b, float c, float &result)
{
result = max(a, b);
}
[[node]]
void math_less_than(float a, float b, float c, float &result)
{
result = (a < b) ? 1.0f : 0.0f;
}
[[node]]
void math_greater_than(float a, float b, float c, float &result)
{
result = (a > b) ? 1.0f : 0.0f;
}
[[node]]
void math_round(float a, float b, float c, float &result)
{
result = floor(a + 0.5f);
}
[[node]]
void math_floor(float a, float b, float c, float &result)
{
result = floor(a);
}
[[node]]
void math_ceil(float a, float b, float c, float &result)
{
result = ceil(a);
}
[[node]]
void math_fraction(float a, float b, float c, float &result)
{
result = a - floor(a);
}
[[node]]
void math_modulo(float a, float b, float c, float &result)
{
result = compatible_mod(a, b);
}
[[node]]
void math_floored_modulo(float a, float b, float c, float &result)
{
result = (b != 0.0f) ? a - floor(a / b) * b : 0.0f;
}
[[node]]
void math_trunc(float a, float b, float c, float &result)
{
result = trunc(a);
}
[[node]]
void math_snap(float a, float b, float c, float &result)
{
result = floor(safe_divide(a, b)) * b;
}
[[node]]
void math_pingpong(float a, float b, float c, float &result)
{
result = (b != 0.0f) ? abs(fract((a - b) / (b * 2.0f)) * b * 2.0f - b) : 0.0f;
}
/* Adapted from GODOT-engine math_funcs.h. */
[[node]]
void math_wrap(float a, float b, float c, float &result)
{
result = wrap(a, b, c);
}
[[node]]
void math_sine(float a, float b, float c, float &result)
{
result = sin(a);
}
[[node]]
void math_cosine(float a, float b, float c, float &result)
{
result = cos(a);
}
[[node]]
void math_tangent(float a, float b, float c, float &result)
{
result = tan(a);
}
[[node]]
void math_sinh(float a, float b, float c, float &result)
{
result = sinh(a);
}
[[node]]
void math_cosh(float a, float b, float c, float &result)
{
result = cosh(a);
}
[[node]]
void math_tanh(float a, float b, float c, float &result)
{
result = tanh(a);
}
[[node]]
void math_arcsine(float a, float b, float c, float &result)
{
result = (a <= 1.0f && a >= -1.0f) ? asin(a) : 0.0f;
}
[[node]]
void math_arccosine(float a, float b, float c, float &result)
{
result = (a <= 1.0f && a >= -1.0f) ? acos(a) : 0.0f;
}
[[node]]
void math_arctangent(float a, float b, float c, float &result)
{
result = atan(a);
}
/* The behavior of `atan2(0, 0)` is undefined on many platforms, to ensure consistent behavior, we
* return 0 in this case. See !126951. */
[[node]]
void math_arctan2(float a, float b, float c, float &result)
{
result = ((a == 0.0f && b == 0.0f) ? 0.0f : atan(a, b));
}
[[node]]
void math_sign(float a, float b, float c, float &result)
{
result = sign(a);
}
[[node]]
void math_exponent(float a, float b, float c, float &result)
{
result = exp(a);
}
[[node]]
void math_compare(float a, float b, float c, float &result)
{
result = (abs(a - b) <= max(c, 1e-5f)) ? 1.0f : 0.0f;
}
[[node]]
void math_multiply_add(float a, float b, float c, float &result)
{
result = a * b + c;
}
/* See: https://www.iquilezles.org/www/articles/smin/smin.htm. */
[[node]]
void math_smoothmin(float a, float b, float c, float &result)
{
if (c != 0.0f) {
float h = max(c - abs(a - b), 0.0f) / c;
result = min(a, b) - h * h * h * c * (1.0f / 6.0f);
}
else {
result = min(a, b);
}
}
[[node]]
void math_smoothmax(float a, float b, float c, float &result)
{
math_smoothmin(-a, -b, c, result);
result = -result;
}
/* TODO(fclem): Fix dependency hell one EEVEE legacy is removed. */
float math_reduce_max(float3 a)
{
return max(a.x, max(a.y, a.z));
}
float math_average(float3 a)
{
return (a.x + a.y + a.z) * (1.0f / 3.0f);
}

View File

@@ -0,0 +1,28 @@
/* SPDX-FileCopyrightText: 2019-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
[[node]]
void invert_z(float3 v, float3 &outv)
{
v.z = -v.z;
outv = v;
}
[[node]]
void vector_normalize(float3 normal, float3 &outnormal)
{
/* Match the safe normalize function in Cycles by defaulting to float3(0.0f) */
float length_sqr = dot(normal, normal);
outnormal = (length_sqr > 1e-35f) ? normal * inversesqrt(length_sqr) : float3(0.0f);
}
[[node]]
void vector_copy(float3 normal, float3 &outnormal)
{
outnormal = normal;
}

View File

@@ -0,0 +1,335 @@
/* SPDX-FileCopyrightText: 2019-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
#include "gpu_shader_common_color_utils.glsl"
[[node]]
void mix_blend(float fac, float4 col1, float4 col2, float4 &outcol)
{
outcol = mix(col1, col2, fac);
outcol.a = col1.a;
}
[[node]]
void mix_add(float fac, float4 col1, float4 col2, float4 &outcol)
{
outcol = mix(col1, col1 + col2, fac);
outcol.a = col1.a;
}
[[node]]
void mix_mult(float fac, float4 col1, float4 col2, float4 &outcol)
{
outcol = mix(col1, col1 * col2, fac);
outcol.a = col1.a;
}
[[node]]
void mix_screen(float fac, float4 col1, float4 col2, float4 &outcol)
{
float facm = 1.0f - fac;
outcol = float4(1.0f) - (float4(facm) + fac * (float4(1.0f) - col2)) * (float4(1.0f) - col1);
outcol.a = col1.a;
}
[[node]]
void mix_overlay(float fac, float4 col1, float4 col2, float4 &outcol)
{
float facm = 1.0f - fac;
outcol = col1;
if (outcol.r < 0.5f) {
outcol.r *= facm + 2.0f * fac * col2.r;
}
else {
outcol.r = 1.0f - (facm + 2.0f * fac * (1.0f - col2.r)) * (1.0f - outcol.r);
}
if (outcol.g < 0.5f) {
outcol.g *= facm + 2.0f * fac * col2.g;
}
else {
outcol.g = 1.0f - (facm + 2.0f * fac * (1.0f - col2.g)) * (1.0f - outcol.g);
}
if (outcol.b < 0.5f) {
outcol.b *= facm + 2.0f * fac * col2.b;
}
else {
outcol.b = 1.0f - (facm + 2.0f * fac * (1.0f - col2.b)) * (1.0f - outcol.b);
}
}
[[node]]
void mix_sub(float fac, float4 col1, float4 col2, float4 &outcol)
{
outcol = mix(col1, col1 - col2, fac);
outcol.a = col1.a;
}
[[node]]
void mix_div(float fac, float4 col1, float4 col2, float4 &outcol)
{
float facm = 1.0f - fac;
outcol = float4(float3(0.0f), col1.a);
if (col2.r != 0.0f) {
outcol.r = facm * col1.r + fac * col1.r / col2.r;
}
if (col2.g != 0.0f) {
outcol.g = facm * col1.g + fac * col1.g / col2.g;
}
if (col2.b != 0.0f) {
outcol.b = facm * col1.b + fac * col1.b / col2.b;
}
}
/* A variant of mix_div that fallback to the first color upon zero division. */
[[node]]
void mix_div_fallback(float fac, float4 col1, float4 col2, float4 &outcol)
{
float facm = 1.0f - fac;
outcol = col1;
if (col2.r != 0.0f) {
outcol.r = facm * outcol.r + fac * outcol.r / col2.r;
}
if (col2.g != 0.0f) {
outcol.g = facm * outcol.g + fac * outcol.g / col2.g;
}
if (col2.b != 0.0f) {
outcol.b = facm * outcol.b + fac * outcol.b / col2.b;
}
}
[[node]]
void mix_diff(float fac, float4 col1, float4 col2, float4 &outcol)
{
outcol = mix(col1, abs(col1 - col2), fac);
outcol.a = col1.a;
}
[[node]]
void mix_exclusion(float fac, float4 col1, float4 col2, float4 &outcol)
{
outcol = max(mix(col1, col1 + col2 - 2.0f * col1 * col2, fac), 0.0f);
outcol.a = col1.a;
}
[[node]]
void mix_dark(float fac, float4 col1, float4 col2, float4 &outcol)
{
outcol.rgb = mix(col1.rgb, min(col1.rgb, col2.rgb), fac);
outcol.a = col1.a;
}
[[node]]
void mix_light(float fac, float4 col1, float4 col2, float4 &outcol)
{
outcol.rgb = mix(col1.rgb, max(col1.rgb, col2.rgb), fac);
outcol.a = col1.a;
}
[[node]]
void mix_dodge(float fac, float4 col1, float4 col2, float4 &outcol)
{
outcol = col1;
if (outcol.r != 0.0f) {
float tmp = 1.0f - fac * col2.r;
if (tmp <= 0.0f) {
outcol.r = 1.0f;
}
else if ((tmp = outcol.r / tmp) > 1.0f) {
outcol.r = 1.0f;
}
else {
outcol.r = tmp;
}
}
if (outcol.g != 0.0f) {
float tmp = 1.0f - fac * col2.g;
if (tmp <= 0.0f) {
outcol.g = 1.0f;
}
else if ((tmp = outcol.g / tmp) > 1.0f) {
outcol.g = 1.0f;
}
else {
outcol.g = tmp;
}
}
if (outcol.b != 0.0f) {
float tmp = 1.0f - fac * col2.b;
if (tmp <= 0.0f) {
outcol.b = 1.0f;
}
else if ((tmp = outcol.b / tmp) > 1.0f) {
outcol.b = 1.0f;
}
else {
outcol.b = tmp;
}
}
}
[[node]]
void mix_burn(float fac, float4 col1, float4 col2, float4 &outcol)
{
float tmp, facm = 1.0f - fac;
outcol = col1;
tmp = facm + fac * col2.r;
if (tmp <= 0.0f) {
outcol.r = 0.0f;
}
else if ((tmp = (1.0f - (1.0f - outcol.r) / tmp)) < 0.0f) {
outcol.r = 0.0f;
}
else if (tmp > 1.0f) {
outcol.r = 1.0f;
}
else {
outcol.r = tmp;
}
tmp = facm + fac * col2.g;
if (tmp <= 0.0f) {
outcol.g = 0.0f;
}
else if ((tmp = (1.0f - (1.0f - outcol.g) / tmp)) < 0.0f) {
outcol.g = 0.0f;
}
else if (tmp > 1.0f) {
outcol.g = 1.0f;
}
else {
outcol.g = tmp;
}
tmp = facm + fac * col2.b;
if (tmp <= 0.0f) {
outcol.b = 0.0f;
}
else if ((tmp = (1.0f - (1.0f - outcol.b) / tmp)) < 0.0f) {
outcol.b = 0.0f;
}
else if (tmp > 1.0f) {
outcol.b = 1.0f;
}
else {
outcol.b = tmp;
}
}
[[node]]
void mix_hue(float fac, float4 col1, float4 col2, float4 &outcol)
{
float facm = 1.0f - fac;
outcol = col1;
float4 hsv, hsv2, tmp;
rgb_to_hsv(col2, hsv2);
if (hsv2.y != 0.0f) {
rgb_to_hsv(outcol, hsv);
hsv.x = hsv2.x;
hsv_to_rgb(hsv, tmp);
outcol = mix(outcol, tmp, fac);
outcol.a = col1.a;
}
}
[[node]]
void mix_sat(float fac, float4 col1, float4 col2, float4 &outcol)
{
float facm = 1.0f - fac;
outcol = col1;
float4 hsv, hsv2;
rgb_to_hsv(outcol, hsv);
if (hsv.y != 0.0f) {
rgb_to_hsv(col2, hsv2);
hsv.y = facm * hsv.y + fac * hsv2.y;
hsv_to_rgb(hsv, outcol);
}
}
[[node]]
void mix_val(float fac, float4 col1, float4 col2, float4 &outcol)
{
float facm = 1.0f - fac;
float4 hsv, hsv2;
rgb_to_hsv(col1, hsv);
rgb_to_hsv(col2, hsv2);
hsv.z = facm * hsv.z + fac * hsv2.z;
hsv_to_rgb(hsv, outcol);
}
[[node]]
void mix_color(float fac, float4 col1, float4 col2, float4 &outcol)
{
float facm = 1.0f - fac;
outcol = col1;
float4 hsv, hsv2, tmp;
rgb_to_hsv(col2, hsv2);
if (hsv2.y != 0.0f) {
rgb_to_hsv(outcol, hsv);
hsv.x = hsv2.x;
hsv.y = hsv2.y;
hsv_to_rgb(hsv, tmp);
outcol = mix(outcol, tmp, fac);
outcol.a = col1.a;
}
}
[[node]]
void mix_soft(float fac, float4 col1, float4 col2, float4 &outcol)
{
float facm = 1.0f - fac;
float4 one = float4(1.0f);
float4 scr = one - (one - col2) * (one - col1);
outcol = facm * col1 + fac * ((one - col1) * col2 * col1 + col1 * scr);
outcol.a = col1.a;
}
[[node]]
void mix_linear(float fac, float4 col1, float4 col2, float4 &outcol)
{
outcol = col1 + fac * (2.0f * (col2 - float4(0.5f)));
outcol.a = col1.a;
}
[[node]]
void clamp_color(float4 vec, const float4 min, const float4 max, float4 &out_vec)
{
out_vec = clamp(vec, min, max);
}
[[node]]
void multiply_by_alpha(float factor, float4 color, float &result)
{
result = factor * color.a;
}

View File

@@ -0,0 +1,36 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
/*
* For debugging purpose mainly.
* From https://www.shadertoy.com/view/4dsSzr
* By Morgan McGuire @morgan3d, http://graphicscodex.com
* Reuse permitted under the BSD license.
*/
float3 neon_gradient(float t)
{
float tt = abs(0.43f - t) * 1.7f;
return clamp(float3(t * 1.3f + 0.1f, tt * tt, (1.0f - t) * 1.7f), 0.0f, 1.0f);
}
float3 heatmap_gradient(float t)
{
float a = pow(t, 1.5f) * 0.8f + 0.2f;
float b = smoothstep(0.0f, 0.35f, t) + t * 0.5f;
float c = smoothstep(0.5f, 1.0f, t);
float d = max(1.0f - t * 1.7f, t * 7.0f - 6.0f);
return clamp(a * float3(b, c, d), float3(0.0f), float3(1.0f));
}
float3 hue_gradient(float t)
{
float3 p = abs(fract(t + float3(1.0f, 2.0f / 3.0f, 1.0f / 3.0f)) * 6.0f - 3.0f);
return (clamp(p - 1.0f, 0.0f, 1.0f));
}
float3 green_to_red_gradient(float t)
{
return mix(float3(0.0f, 1.0f, 0.0f), float3(1.0f, 0.0f, 0.0f), t);
}

View File

@@ -0,0 +1,22 @@
/* SPDX-FileCopyrightText: 2015-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
/* Position vertices of a triangle to cover the whole screen. */
void fullscreen_vertex(int vertex_id, float4 &out_position)
{
int v = vertex_id % 3;
float x = -1.0f + float((v & 1) << 2);
float y = -1.0f + float((v & 2) << 1);
out_position = float4(x, y, 1.0f, 1.0f);
}
void fullscreen_vertex(int vertex_id, float4 &out_position, float2 &out_uv)
{
fullscreen_vertex(vertex_id, out_position);
out_uv = (out_position.xy + 1.0f) * 0.5f;
}

View File

@@ -0,0 +1,15 @@
/* SPDX-FileCopyrightText: 2015-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_fullscreen_infos.hh"
#include "gpu_shader_sequencer_infos.hh"
VERTEX_SHADER_CREATE_INFO(gpu_fullscreen)
#include "gpu_shader_fullscreen_lib.glsl"
void main()
{
fullscreen_vertex(gl_VertexID, gl_Position, screen_uv);
}

View File

@@ -0,0 +1,41 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "infos/gpu_index_load_infos.hh"
SHADER_LIBRARY_CREATE_INFO(gpu_index_buffer_load)
/**
* Library to read the index buffer of a `gpu::Batch` using a SSBO rather than using `gl_VertexID`.
* This is required for primitive expansion without geometry shader.
* It is **not** needed if it is guaranteed that the processed `gpu::Batch` will not use any index
* buffer.
*/
#ifndef WORKAROUND_INDEX_LOAD_INCLUDE
# ifndef GPU_INDEX_LOAD
# error Missing gpu_index_buffer_load create info dependency
# endif
/**
* Returns the resolved index after index buffer (a.k.a. element buffer) indirection.
*/
uint gpu_index_load(uint element_index)
{
if (gpu_index_no_buffer) {
return element_index;
}
uint raw_index = gpu_index_buf[gpu_index_16bit ? element_index >> 1u : element_index];
if (gpu_index_16bit) {
raw_index = ((element_index & 1u) == 0u) ? (raw_index & 0xFFFFu) : (raw_index >> 16u);
}
return raw_index + uint(gpu_index_base_index);
}
#endif

View File

@@ -0,0 +1,77 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
/* WORKAROUND: Workaround include order hell. */
#ifdef GLSL_CPP_STUBS
#elif defined(GPU_SHADER)
# define static
#endif
struct IndexRange {
int start_;
int size_;
static IndexRange from_begin_end(int begin, int end)
{
return {begin, end - begin};
}
/**
* Get the first element in the range.
*/
int first() const
{
return this->start_;
}
/**
* Get the first element in the range. The returned value is undefined when the range is empty.
*/
int start() const
{
return this->start_;
}
/**
* Get the nth last element in the range.
*/
int last(int n = 0) const
{
return this->start_ + this->size_ - 1 - n;
}
/**
* Get the amount of numbers in the range.
*/
int size() const
{
return this->size_;
}
/**
* Returns a new range, that contains a sub-interval of the current one.
*/
IndexRange slice(int start, int size) const
{
int new_start = this->start_ + start;
return {new_start, size};
}
IndexRange slice(IndexRange range) const
{
return this->slice(range.start(), range.size());
}
/**
* Move the range forward or backward within the larger array. The amount may be negative,
* but its absolute value cannot be greater than the existing start of the range.
*/
IndexRange shift(int n) const
{
return {this->start_ + n, this->size_};
}
};

View File

@@ -0,0 +1,24 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
#include "gpu_shader_math_constants_lib.glsl"
struct AngleRadian {
/* Note that value is public because of the lack of casting operator in GLSL. */
float angle;
static AngleRadian identity()
{
return {0};
}
static AngleRadian from_degree(float angle_degree)
{
return {angle_degree * (M_PI / 180.0f)};
}
};

View File

@@ -0,0 +1,17 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
struct AxisAngle {
float3 axis;
float angle;
static AxisAngle identity()
{
return {float3(0, 1, 0), 0};
}
};

View File

@@ -0,0 +1,128 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
/* `powf` is really slow for raising to integer powers. */
float pow2f(float x)
{
return x * x;
}
float pow3f(float x)
{
return x * x * x;
}
float pow4f(float x)
{
return pow2f(pow2f(x));
}
float pow5f(float x)
{
return pow4f(x) * x;
}
float pow6f(float x)
{
return pow2f(pow3f(x));
}
float pow7f(float x)
{
return pow6f(x) * x;
}
float pow8f(float x)
{
return pow2f(pow4f(x));
}
float square(float v)
{
return v * v;
}
float2 square(float2 v)
{
return v * v;
}
float3 square(float3 v)
{
return v * v;
}
float4 square(float4 v)
{
return v * v;
}
float hypot(float x, float y)
{
return sqrt(x * x + y * y);
}
/* Declared as _atan2 to prevent errors with `WITH_GPU_SHADER_CPP_COMPILATION` on VS2019 due
* to `corecrt_math` conflicting functions. */
float _atan2(float y, float x)
{
return atan(y, x);
}
#define atan2 _atan2
/**
* Returns \a a if it is a multiple of \a b or the next multiple or \a b after \b a .
* In other words, it is equivalent to `divide_ceil(a, b) * b`.
* It is undefined if \a a is negative or \b b is not strictly positive.
*/
int ceil_to_multiple(int a, int b)
{
return ((a + b - 1) / b) * b;
}
uint ceil_to_multiple(uint a, uint b)
{
return ((a + b - 1u) / b) * b;
}
/**
* Integer division that returns the ceiling, instead of flooring like normal C division.
* It is undefined if \a a is negative or \b b is not strictly positive.
*/
int divide_ceil(int a, int b)
{
return (a + b - 1) / b;
}
uint divide_ceil(uint a, uint b)
{
return (a + b - 1u) / b;
}
/**
* Component wise, use vector to replace min if it is smaller and max if bigger.
*/
void min_max(float value, float &min_v, float &max_v)
{
min_v = min(value, min_v);
max_v = max(value, max_v);
}
/**
* Return true if the difference between`a` and `b` is below the `epsilon` value.
*/
bool is_equal(float a, float b, const float epsilon)
{
return abs(a - b) <= epsilon;
}
float sin_from_cos(float c)
{
return sqrt(max(0.0f, 1.0f - square(c)));
}
float cos_from_sin(float s)
{
return sqrt(max(0.0f, 1.0f - square(s)));
}
float cos_from_tan(float t)
{
return inversesqrt(1.0f + square(t));
}

View File

@@ -0,0 +1,20 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#define M_PI 3.14159265358979323846f /* pi */
#define M_TAU 6.28318530717958647692f /* tau = 2*pi */
#define M_PI_2 1.57079632679489661923f /* pi/2 */
#define M_PI_4 0.78539816339744830962f /* pi/4 */
#define M_SQRT2 1.41421356237309504880f /* sqrt(2) */
#define M_SQRT1_2 0.70710678118654752440f /* 1/sqrt(2) */
#define M_SQRT3 1.73205080756887729352f /* sqrt(3) */
#define M_SQRT1_3 0.57735026918962576450f /* 1/sqrt(3) */
#define M_1_PI 0.318309886183790671538f /* 1/pi */
#define M_E 2.7182818284590452354f /* e */
#define M_LOG2E 1.4426950408889634074f /* log_2 e */
#define M_LOG10E 0.43429448190325182765f /* log_10 e */
#define M_LN2 0.69314718055994530942f /* log_e 2 */
#define M_LN10 2.30258509299404568402f /* log_e 10 */

View File

@@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
struct EulerXYZ {
float x, y, z;
static EulerXYZ from_float3(float3 eul)
{
return {eul.x, eul.y, eul.z};
}
static EulerXYZ identity()
{
return {0, 0, 0};
}
float3 as_float3() const
{
return float3(this->x, this->y, this->z);
}
};

View File

@@ -0,0 +1,53 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
#include "gpu_shader_math_constants_lib.glsl"
/* [Drobot2014a] Low Level Optimizations for GCN. */
float sqrt_fast(float v)
{
return intBitsToFloat(0x1fbd1df5 + (floatBitsToInt(v) >> 1));
}
float2 sqrt_fast(float2 v)
{
return intBitsToFloat(0x1fbd1df5 + (floatBitsToInt(v) >> 1));
}
/* [Eberly2014] GPGPU Programming for Games and Science. */
float acos_fast(float v)
{
float res = -0.156583f * abs(v) + M_PI_2;
res *= sqrt_fast(1.0f - abs(v));
return (v >= 0) ? res : M_PI - res;
}
float2 acos_fast(float2 v)
{
float2 res = -0.156583f * abs(v) + M_PI_2;
res *= sqrt_fast(1.0f - abs(v));
v.x = (v.x >= 0) ? res.x : M_PI - res.x;
v.y = (v.y >= 0) ? res.y : M_PI - res.y;
return v;
}
float atan_fast(float x)
{
float a = abs(x);
float k = a > 1.0f ? (1.0f / a) : a;
float s = 1.0f - (1.0f - k); /* Crush denormals. */
float t = s * s;
/* http://mathforum.org/library/drmath/view/62672.html
* Examined 4278190080 values of atan:
* 2.36864877 avg ULP diff, 302 max ULP, 6.55651e-06f max error // (with denormals)
* Examined 4278190080 values of atan:
* 171160502 avg ULP diff, 855638016 max ULP, 6.55651e-06f max error // (crush denormals)
*/
float r = s * fma(0.43157974f, t, 1.0f) / fma(fma(0.05831938f, t, 0.76443945f), t, 1.0f);
if (a > 1.0f) {
r = 1.57079632679489661923f - r;
}
return (x < 0.0f) ? -r : r;
}

View File

@@ -0,0 +1,92 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
/* -------------------------------------------------------------------- */
/** \name Normalize
* \{ */
/**
* Returns the adjoint of the matrix (also known as adjugate matrix).
*/
float2x2 adjoint(float2x2 mat)
{
float2x2 adj = float2x2(0.0f);
for (int c = 0; c < 2; c++) {
for (int r = 0; r < 2; r++) {
/* Copy other cells except the "cross" to compute the determinant. */
float tmp = 0.0f;
for (int m_c = 0; m_c < 2; m_c++) {
for (int m_r = 0; m_r < 2; m_r++) {
if (m_c != c && m_r != r) {
tmp = mat[m_c][m_r];
}
}
}
float minor = tmp;
/* Transpose directly to get the adjugate. Swap destination row and col. */
adj[r][c] = (((c + r) & 1) != 0) ? -minor : minor;
}
}
return adj;
}
/**
* Returns the adjoint of the matrix (also known as adjugate matrix).
*/
float3x3 adjoint(float3x3 mat)
{
float3x3 adj = float3x3(0.0f);
for (int c = 0; c < 3; c++) {
for (int r = 0; r < 3; r++) {
/* Copy other cells except the "cross" to compute the determinant. */
float2x2 tmp = float2x2(0.0f);
for (int m_c = 0; m_c < 3; m_c++) {
for (int m_r = 0; m_r < 3; m_r++) {
if (m_c != c && m_r != r) {
int d_c = (m_c < c) ? m_c : (m_c - 1);
int d_r = (m_r < r) ? m_r : (m_r - 1);
tmp[d_c][d_r] = mat[m_c][m_r];
}
}
}
float minor = determinant(tmp);
/* Transpose directly to get the adjugate. Swap destination row and col. */
adj[r][c] = (((c + r) & 1) != 0) ? -minor : minor;
}
}
return adj;
}
/**
* Returns the adjoint of the matrix (also known as adjugate matrix).
*/
float4x4 adjoint(float4x4 mat)
{
float4x4 adj = float4x4(0.0f);
for (int c = 0; c < 4; c++) {
for (int r = 0; r < 4; r++) {
/* Copy other cells except the "cross" to compute the determinant. */
float3x3 tmp = float3x3(0.0f);
for (int m_c = 0; m_c < 4; m_c++) {
for (int m_r = 0; m_r < 4; m_r++) {
if (m_c != c && m_r != r) {
int d_c = (m_c < c) ? m_c : (m_c - 1);
int d_r = (m_r < r) ? m_r : (m_r - 1);
tmp[d_c][d_r] = mat[m_c][m_r];
}
}
}
float minor = determinant(tmp);
/* Transpose directly to get the adjugate. Swap destination row and col. */
adj[r][c] = (((c + r) & 1) != 0) ? -minor : minor;
}
}
return adj;
}
/** \} */

View File

@@ -0,0 +1,214 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_math_vector_compare_lib.glsl"
/* -------------------------------------------------------------------- */
/** \name Compare / Test
* \{ */
/**
* Returns true if all of the matrices components are strictly equal to 0.
*/
bool is_zero(float3x3 a)
{
if (is_zero(a[0])) {
if (is_zero(a[1])) {
if (is_zero(a[2])) {
return true;
}
}
}
return false;
}
/**
* Returns true if all of the matrices components are strictly equal to 0.
*/
bool is_zero(float4x4 a)
{
if (is_zero(a[0])) {
if (is_zero(a[1])) {
if (is_zero(a[2])) {
if (is_zero(a[3])) {
return true;
}
}
}
}
return false;
}
/**
* Returns true if matrix has inverted handedness.
*/
bool is_negative(float3x3 mat)
{
return determinant(mat) < 0.0f;
}
/**
* Returns true if matrix has inverted handedness.
*
* \note It doesn't use determinant(mat4x4) as only the 3x3 components are needed
* when the matrix is used as a transformation to represent location/scale/rotation.
*/
bool is_negative(float4x4 mat)
{
return is_negative(to_float3x3(mat));
}
/**
* Returns true if matrices are equal within the given epsilon.
*/
bool is_equal(float2x2 a, float2x2 b, float epsilon)
{
if (is_equal(a[0], b[0], epsilon)) {
if (is_equal(a[1], b[1], epsilon)) {
return true;
}
}
return false;
}
/**
* Returns true if matrices are equal within the given epsilon.
*/
bool is_equal(float3x3 a, float3x3 b, float epsilon)
{
if (is_equal(a[0], b[0], epsilon)) {
if (is_equal(a[1], b[1], epsilon)) {
if (is_equal(a[2], b[2], epsilon)) {
return true;
}
}
}
return false;
}
/**
* Returns true if matrices are equal within the given epsilon.
*/
bool is_equal(float4x4 a, float4x4 b, float epsilon)
{
if (is_equal(a[0], b[0], epsilon)) {
if (is_equal(a[1], b[1], epsilon)) {
if (is_equal(a[2], b[2], epsilon)) {
if (is_equal(a[3], b[3], epsilon)) {
return true;
}
}
}
}
return false;
}
/**
* Test if the X, Y and Z axes are perpendicular with each other.
*/
bool is_orthogonal(float3x3 mat)
{
if (abs(dot(mat[0], mat[1])) > 1e-5f) {
return false;
}
if (abs(dot(mat[1], mat[2])) > 1e-5f) {
return false;
}
if (abs(dot(mat[2], mat[0])) > 1e-5f) {
return false;
}
return true;
}
/**
* Test if the X, Y and Z axes are perpendicular with each other.
*/
bool is_orthogonal(float4x4 mat)
{
return is_orthogonal(to_float3x3(mat));
}
/**
* Test if the X, Y and Z axes are perpendicular with each other and unit length.
*/
bool is_orthonormal(float3x3 mat)
{
if (!is_orthogonal(mat)) {
return false;
}
if (abs(dot(mat[0], mat[0]) - 1.0f) > 1e-5f) {
return false;
}
if (abs(dot(mat[1], mat[1]) - 1.0f) > 1e-5f) {
return false;
}
if (abs(dot(mat[2], mat[2]) - 1.0f) > 1e-5f) {
return false;
}
return true;
}
/**
* Test if the X, Y and Z axes are perpendicular with each other and unit length.
*/
bool is_orthonormal(float4x4 mat)
{
return is_orthonormal(to_float3x3(mat));
}
/**
* Test if the X, Y and Z axes are perpendicular with each other and the same length.
*/
bool is_uniformly_scaled(float3x3 mat)
{
if (!is_orthogonal(mat)) {
return false;
}
constexpr float eps = 1e-7f;
float x = dot(mat[0], mat[0]);
float y = dot(mat[1], mat[1]);
float z = dot(mat[2], mat[2]);
return (abs(x - y) < eps) && abs(x - z) < eps;
}
/**
* Test if the X, Y and Z axes are perpendicular with each other and the same length.
*/
bool is_uniformly_scaled(float4x4 mat)
{
return is_uniformly_scaled(to_float3x3(mat));
}
/* Returns true if each individual columns are unit scaled. Mainly for assert usage. */
bool is_unit_scale(float4x4 m)
{
if (is_unit_scale(m[0])) {
if (is_unit_scale(m[1])) {
if (is_unit_scale(m[2])) {
if (is_unit_scale(m[3])) {
return true;
}
}
}
}
return false;
}
/* Returns true if each individual columns are unit scaled. Mainly for assert usage. */
bool is_unit_scale(float3x3 m)
{
if (is_unit_scale(m[0])) {
if (is_unit_scale(m[1])) {
if (is_unit_scale(m[2])) {
return true;
}
}
}
return false;
}
/* Returns true if each individual columns are unit scaled. Mainly for assert usage. */
bool is_unit_scale(float2x2 m)
{
if (is_unit_scale(m[0])) {
if (is_unit_scale(m[1])) {
return true;
}
}
return false;
}
/** \} */

View File

@@ -0,0 +1,346 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_math_angle_lib.glsl"
#include "gpu_shader_math_axis_angle_lib.glsl"
#include "gpu_shader_math_base_lib.glsl"
#include "gpu_shader_math_euler_lib.glsl"
#include "gpu_shader_math_quaternion_lib.glsl"
/* -------------------------------------------------------------------- */
/** \name Static constructors
* \{ */
float2x2 mat2x2_diagonal(float v)
{
return float2x2(float2(v, 0.0f), float2(0.0f, v));
}
float3x3 mat3x3_diagonal(float v)
{
return float3x3(float3(v, 0.0f, 0.0f), float3(0.0f, v, 0.0f), float3(0.0f, 0.0f, v));
}
float4x4 mat4x4_diagonal(float v)
{
return float4x4(float4(v, 0.0f, 0.0f, 0.0f),
float4(0.0f, v, 0.0f, 0.0f),
float4(0.0f, 0.0f, v, 0.0f),
float4(0.0f, 0.0f, 0.0f, v));
}
float2x2 mat2x2_all(float v)
{
return float2x2(float2(v), float2(v));
}
float3x3 mat3x3_all(float v)
{
return float3x3(float3(v), float3(v), float3(v));
}
float4x4 mat4x4_all(float v)
{
return float4x4(float4(v), float4(v), float4(v), float4(v));
}
float2x2 mat2x2_zero()
{
return mat2x2_all(0.0f);
}
float3x3 mat3x3_zero()
{
return mat3x3_all(0.0f);
}
float4x4 mat4x4_zero()
{
return mat4x4_all(0.0f);
}
float2x2 mat2x2_identity()
{
return mat2x2_diagonal(1.0f);
}
float3x3 mat3x3_identity()
{
return mat3x3_diagonal(1.0f);
}
float4x4 mat4x4_identity()
{
return mat4x4_diagonal(1.0f);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Init helpers.
* \{ */
/**
* Create a translation only matrix. Matrix dimensions should be at least 4 col x 3 row.
*/
float4x4 from_location(float3 location)
{
float4x4 ret = float4x4(1.0f);
ret[3].xyz = location;
return ret;
}
/**
* Create a matrix whose diagonal is defined by the given scale vector.
*/
float2x2 from_scale(float2 scale)
{
float2x2 ret = float2x2(0.0f);
ret[0][0] = scale[0];
ret[1][1] = scale[1];
return ret;
}
/**
* Create a matrix whose diagonal is defined by the given scale vector.
*/
float3x3 from_scale(float3 scale)
{
float3x3 ret = float3x3(0.0f);
ret[0][0] = scale[0];
ret[1][1] = scale[1];
ret[2][2] = scale[2];
return ret;
}
/**
* Create a matrix whose diagonal is defined by the given scale vector.
*/
float4x4 from_scale(float4 scale)
{
float4x4 ret = float4x4(0.0f);
ret[0][0] = scale[0];
ret[1][1] = scale[1];
ret[2][2] = scale[2];
ret[3][3] = scale[3];
return ret;
}
/**
* Create a rotation only matrix.
*/
float2x2 from_rotation(AngleRadian rotation)
{
float c = cos(rotation.angle);
float s = sin(rotation.angle);
return float2x2(c, -s, s, c);
}
/**
* Create a rotation only matrix.
*/
float3x3 from_rotation(EulerXYZ rotation)
{
float ci = cos(rotation.x);
float cj = cos(rotation.y);
float ch = cos(rotation.z);
float si = sin(rotation.x);
float sj = sin(rotation.y);
float sh = sin(rotation.z);
float cc = ci * ch;
float cs = ci * sh;
float sc = si * ch;
float ss = si * sh;
float3x3 mat;
mat[0][0] = cj * ch;
mat[1][0] = sj * sc - cs;
mat[2][0] = sj * cc + ss;
mat[0][1] = cj * sh;
mat[1][1] = sj * ss + cc;
mat[2][1] = sj * cs - sc;
mat[0][2] = -sj;
mat[1][2] = cj * si;
mat[2][2] = cj * ci;
return mat;
}
/**
* Create a rotation only matrix.
*/
float3x3 from_rotation(Quaternion rotation)
{
/* NOTE: Should be double but support isn't native on most GPUs. */
float q0 = M_SQRT2 * float(rotation.x);
float q1 = M_SQRT2 * float(rotation.y);
float q2 = M_SQRT2 * float(rotation.z);
float q3 = M_SQRT2 * float(rotation.w);
float qda = q0 * q1;
float qdb = q0 * q2;
float qdc = q0 * q3;
float qaa = q1 * q1;
float qab = q1 * q2;
float qac = q1 * q3;
float qbb = q2 * q2;
float qbc = q2 * q3;
float qcc = q3 * q3;
float3x3 mat;
mat[0][0] = float(1.0f - qbb - qcc);
mat[0][1] = float(qdc + qab);
mat[0][2] = float(-qdb + qac);
mat[1][0] = float(-qdc + qab);
mat[1][1] = float(1.0f - qaa - qcc);
mat[1][2] = float(qda + qbc);
mat[2][0] = float(qdb + qac);
mat[2][1] = float(-qda + qbc);
mat[2][2] = float(1.0f - qaa - qbb);
return mat;
}
/**
* Create a rotation only matrix.
*/
float3x3 from_rotation(AxisAngle rotation)
{
float angle_sin = sin(rotation.angle);
float angle_cos = cos(rotation.angle);
float3 axis = rotation.axis;
float ico = (float(1) - angle_cos);
float3 nsi = axis * angle_sin;
float3 n012 = (axis * axis) * ico;
float n_01 = (axis[0] * axis[1]) * ico;
float n_02 = (axis[0] * axis[2]) * ico;
float n_12 = (axis[1] * axis[2]) * ico;
float3x3 mat = from_scale(n012 + angle_cos);
mat[0][1] = n_01 + nsi[2];
mat[0][2] = n_02 - nsi[1];
mat[1][0] = n_01 - nsi[2];
mat[1][2] = n_12 + nsi[0];
mat[2][0] = n_02 + nsi[1];
mat[2][1] = n_12 - nsi[0];
return mat;
}
/**
* Create a transform matrix with rotation and scale applied in this order.
*/
float3x3 from_rot_scale(EulerXYZ rotation, float3 scale)
{
return from_rotation(rotation) * from_scale(scale);
}
/**
* Create a transform matrix with rotation and scale applied in this order.
*/
float3x3 from_rot_scale(Quaternion rotation, float3 scale)
{
return from_rotation(rotation) * from_scale(scale);
}
/**
* Create a transform matrix with rotation and scale applied in this order.
*/
float3x3 from_rot_scale(AxisAngle rotation, float3 scale)
{
return from_rotation(rotation) * from_scale(scale);
}
/**
* Create a transform matrix with translation and rotation applied in this order.
*/
float4x4 from_loc_rot(float3 location, EulerXYZ rotation)
{
float4x4 ret = to_float4x4(from_rotation(rotation));
ret[3].xyz = location;
return ret;
}
/**
* Create a transform matrix with translation and rotation applied in this order.
*/
float4x4 from_loc_rot(float3 location, Quaternion rotation)
{
float4x4 ret = to_float4x4(from_rotation(rotation));
ret[3].xyz = location;
return ret;
}
/**
* Create a transform matrix with translation and rotation applied in this order.
*/
float4x4 from_loc_rot(float3 location, AxisAngle rotation)
{
float4x4 ret = to_float4x4(from_rotation(rotation));
ret[3].xyz = location;
return ret;
}
/**
* Create a transform matrix with translation, rotation and scale applied in this order.
*/
float4x4 from_loc_rot_scale(float3 location, EulerXYZ rotation, float3 scale)
{
float4x4 ret = to_float4x4(from_rot_scale(rotation, scale));
ret[3].xyz = location;
return ret;
}
/**
* Create a transform matrix with translation, rotation and scale applied in this order.
*/
float4x4 from_loc_rot_scale(float3 location, Quaternion rotation, float3 scale)
{
float4x4 ret = to_float4x4(from_rot_scale(rotation, scale));
ret[3].xyz = location;
return ret;
}
/**
* Create a transform matrix with translation, rotation and scale applied in this order.
*/
float4x4 from_loc_rot_scale(float3 location, AxisAngle rotation, float3 scale)
{
float4x4 ret = to_float4x4(from_rot_scale(rotation, scale));
ret[3].xyz = location;
return ret;
}
/**
* Creates a 2D rotation matrix with the angle that the given direction makes with the x axis.
* Assumes the direction vector is normalized.
*/
float2x2 from_direction(float2 direction)
{
float cos_angle = direction.x;
float sin_angle = direction.y;
return float2x2(cos_angle, sin_angle, -sin_angle, cos_angle);
}
/**
* Create a rotation matrix from 2 basis vectors.
* The matrix determinant is given to be positive and it can be converted to other rotation types.
* \note `forward` and `up` must be normalized.
*/
// mat3x3 from_normalized_axis_data(vec3 forward, vec3 up); /* TODO. */
/**
* Create a transform matrix with translation and rotation from 2 basis vectors and a translation.
* \note `forward` and `up` must be normalized.
*/
// mat4x4 from_normalized_axis_data(vec3 location, vec3 forward, vec3 up); /* TODO. */
/**
* Create a rotation matrix from only one \a up axis.
* The other axes are chosen to always be orthogonal. The resulting matrix is a basis matrix.
* \note `up` must be normalized.
* \note This can be used to create a tangent basis from a normal vector.
* \note The output of this function is not given to be same across blender version. Prefer using
* `from_orthonormal_axes` for more stable output.
*/
float3x3 from_up_axis(float3 up)
{
/* Duff, Tom, et al. "Building an orthonormal basis, revisited." JCGT 6.1 (2017). */
float z_sign = up.z >= 0.0f ? 1.0f : -1.0f;
float a = -1.0f / (z_sign + up.z);
float b = up.x * up.y * a;
float3x3 basis;
basis[0] = float3(1.0f + z_sign * square(up.x) * a, z_sign * b, -z_sign * up.x);
basis[1] = float3(b, z_sign + square(up.y) * a, -up.y);
basis[2] = up;
return basis;
}
/** \} */

View File

@@ -0,0 +1,49 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_math_base_lib.glsl"
#include "gpu_shader_math_euler_lib.glsl"
#include "gpu_shader_math_matrix_compare_lib.glsl"
#include "gpu_shader_math_quaternion_lib.glsl"
#include "gpu_shader_utildefines_lib.glsl"
/* -------------------------------------------------------------------- */
/** \name Conversion function.
* \{ */
/**
* Extract the absolute 3d scale from a transform matrix.
*/
float3 to_scale(float3x3 mat)
{
return float3(length(mat[0]), length(mat[1]), length(mat[2]));
}
/**
* Extract the absolute 3d scale from a transform matrix.
*/
float3 to_scale(float4x4 mat)
{
return to_scale(to_float3x3(mat));
}
/**
* Extract the absolute 3d scale from a transform matrix.
*/
template<typename MatT, bool allow_negative_scale> float3 to_scale(MatT mat)
{
float3 result = to_scale(mat);
if (allow_negative_scale) {
if (is_negative(mat)) {
result = -result;
}
}
return result;
}
template float3 to_scale<float3x3, true>(float3x3 mat);
template float3 to_scale<float3x3, false>(float3x3 mat);
template float3 to_scale<float4x4, true>(float4x4 mat);
template float3 to_scale<float4x4, false>(float4x4 mat);
/** \} */

View File

@@ -0,0 +1,57 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_math_matrix_construct_lib.glsl"
#include "gpu_shader_math_quaternion_lib.glsl"
#include "gpu_shader_math_rotation_conversion_lib.glsl"
#include "gpu_shader_math_rotation_lib.glsl"
#include "gpu_shader_math_vector_lib.glsl"
/* -------------------------------------------------------------------- */
/** \name Interpolate
* \{ */
#if 0 /* TODO(@fclem): Implement */
/**
* Naive interpolation implementation, faster than polar decomposition
*
* \note This code is about five times faster than the polar decomposition.
* However, it gives un-expected results even with non-uniformly scaled matrices,
* see #46418 for an example.
*
* \param a: Input matrix which is totally effective with `t = 0.0`.
* \param b: Input matrix which is totally effective with `t = 1.0`.
* \param t: Interpolation factor.
*/
float3x3 interpolate_fast(float3x3 a, float3x3 b, float t);
#endif
/**
* Naive transform matrix interpolation,
* based on naive-decomposition-based interpolation from #interpolate_fast<T, 3, 3>.
*/
float4x4 interpolate_fast(float4x4 a, float4x4 b, float t)
{
float3 a_loc, b_loc;
float3 a_scale, b_scale;
Quaternion a_quat, b_quat;
to_loc_rot_scale(a, a_loc, a_quat, a_scale);
to_loc_rot_scale(b, b_loc, b_quat, b_scale);
float3 location = interpolate(a_loc, b_loc, t);
float3 scale = interpolate(a_scale, b_scale, t);
Quaternion rotation = interpolate(a_quat, b_quat, t);
return from_loc_rot_scale(location, rotation, scale);
}
/**
* Compute Moore-Penrose pseudo inverse of matrix.
* Singular values below epsilon are ignored for stability (truncated SVD).
*/
/* TODO(fclem): Implement */
// mat4x4 pseudo_invert(mat4x4 mat, float epsilon); /* Not implemented. Too complex to port. */
/** \} */

View File

@@ -0,0 +1,136 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
/* -------------------------------------------------------------------- */
/** \name Matrix Operations
* \{ */
/**
* Flip the matrix across its diagonal. Also flips dimensions for non square matrices.
*/
// float3x3 transpose(float3x3 mat); /* Built-In using shading languages. */
/**
* Returns the determinant of the matrix.
* It can be interpreted as the signed volume (or area) of the unit cube after transformation.
*/
// float determinant(float3x3 mat); /* Built-In using shading languages. */
/**
* Returns the inverse of a square matrix or zero matrix on failure.
* \a r_success is optional and set to true if the matrix was inverted successfully.
*/
float2x2 invert(float2x2 mat)
{
return inverse(mat);
}
/**
* Returns the inverse of a square matrix or zero matrix on failure.
* \a r_success is optional and set to true if the matrix was inverted successfully.
*/
float3x3 invert(float3x3 mat)
{
return inverse(mat);
}
/**
* Returns the inverse of a square matrix or zero matrix on failure.
* \a r_success is optional and set to true if the matrix was inverted successfully.
*/
float4x4 invert(float4x4 mat)
{
return inverse(mat);
}
/**
* Returns the inverse of a square matrix or zero matrix on failure.
* \a r_success is optional and set to true if the matrix was inverted successfully.
*/
float2x2 invert(float2x2 mat, bool &r_success)
{
r_success = determinant(mat) != 0.0f;
return r_success ? inverse(mat) : float2x2(0.0f);
}
/**
* Returns the inverse of a square matrix or zero matrix on failure.
* \a r_success is optional and set to true if the matrix was inverted successfully.
*/
float3x3 invert(float3x3 mat, bool &r_success)
{
r_success = determinant(mat) != 0.0f;
return r_success ? inverse(mat) : float3x3(0.0f);
}
/**
* Returns the inverse of a square matrix or zero matrix on failure.
* \a r_success is optional and set to true if the matrix was inverted successfully.
*/
float4x4 invert(float4x4 mat, bool &r_success)
{
r_success = determinant(mat) != 0.0f;
return r_success ? inverse(mat) : float4x4(0.0f);
}
/**
* Equivalent to `mat * from_location(translation)` but with fewer operation.
*/
float4x4 translate(float4x4 mat, float3 translation)
{
mat[3].xyz += translation[0] * mat[0].xyz;
mat[3].xyz += translation[1] * mat[1].xyz;
mat[3].xyz += translation[2] * mat[2].xyz;
return mat;
}
/**
* Equivalent to `mat * from_location(translation)` but with fewer operation.
*/
float4x4 translate(float4x4 mat, float2 translation)
{
mat[3].xyz += translation[0] * mat[0].xyz;
mat[3].xyz += translation[1] * mat[1].xyz;
return mat;
}
/**
* Equivalent to `mat * from_scale(scale)` but with fewer operation.
*/
float3x3 scale(float3x3 mat, float2 scale)
{
mat[0] *= scale[0];
mat[1] *= scale[1];
return mat;
}
/**
* Equivalent to `mat * from_scale(scale)` but with fewer operation.
*/
float3x3 scale(float3x3 mat, float3 scale)
{
mat[0] *= scale[0];
mat[1] *= scale[1];
mat[2] *= scale[2];
return mat;
}
/**
* Equivalent to `mat * from_scale(scale)` but with fewer operation.
*/
float4x4 scale(float4x4 mat, float2 scale)
{
mat[0] *= scale[0];
mat[1] *= scale[1];
return mat;
}
/**
* Equivalent to `mat * from_scale(scale)` but with fewer operation.
*/
float4x4 scale(float4x4 mat, float3 scale)
{
mat[0] *= scale[0];
mat[1] *= scale[1];
mat[2] *= scale[2];
return mat;
}
/** \} */

View File

@@ -0,0 +1,209 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
#include "gpu_shader_math_vector_lib.glsl"
/* -------------------------------------------------------------------- */
/** \name Normalize
* \{ */
/* Needs to be defined for correct overloading. */
#if defined(GPU_OPENGL) || defined(GPU_METAL)
float2 normalize(float2 a)
{
return a * inversesqrt(length_squared(a));
}
float3 normalize(float3 a)
{
return a * inversesqrt(length_squared(a));
}
float4 normalize(float4 a)
{
return a * inversesqrt(length_squared(a));
}
#endif
/**
* Normalize each column of the matrix individually.
*/
#if 0 /* Remove unused variants as they are slow down compilation. */
float2x2 normalize(float2x2 mat)
{
float2x2 ret;
ret[0] = normalize(mat[0].xy);
ret[1] = normalize(mat[1].xy);
return ret;
}
float2x3 normalize(float2x3 mat)
{
float2x3 ret;
ret[0] = normalize(mat[0].xyz);
ret[1] = normalize(mat[1].xyz);
return ret;
}
float2x4 normalize(float2x4 mat)
{
float2x4 ret;
ret[0] = normalize(mat[0].xyzw);
ret[1] = normalize(mat[1].xyzw);
return ret;
}
float3x2 normalize(float3x2 mat)
{
float3x2 ret;
ret[0] = normalize(mat[0].xy);
ret[1] = normalize(mat[1].xy);
ret[2] = normalize(mat[2].xy);
return ret;
}
#endif
float3x3 normalize(float3x3 mat)
{
float3x3 ret;
ret[0] = normalize(mat[0].xyz);
ret[1] = normalize(mat[1].xyz);
ret[2] = normalize(mat[2].xyz);
return ret;
}
#if 0 /* Remove unused variants as they are slow down compilation. */
float3x4 normalize(float3x4 mat)
{
float3x4 ret;
ret[0] = normalize(mat[0].xyzw);
ret[1] = normalize(mat[1].xyzw);
ret[2] = normalize(mat[2].xyzw);
return ret;
}
float4x2 normalize(float4x2 mat)
{
float4x2 ret;
ret[0] = normalize(mat[0].xy);
ret[1] = normalize(mat[1].xy);
ret[2] = normalize(mat[2].xy);
ret[3] = normalize(mat[3].xy);
return ret;
}
float4x3 normalize(float4x3 mat)
{
float4x3 ret;
ret[0] = normalize(mat[0].xyz);
ret[1] = normalize(mat[1].xyz);
ret[2] = normalize(mat[2].xyz);
ret[3] = normalize(mat[3].xyz);
return ret;
}
#endif
float4x4 normalize(float4x4 mat)
{
float4x4 ret;
ret[0] = normalize(mat[0].xyzw);
ret[1] = normalize(mat[1].xyzw);
ret[2] = normalize(mat[2].xyzw);
ret[3] = normalize(mat[3].xyzw);
return ret;
}
/**
* Normalize each column of the matrix individually.
* Return the length of each column vector.
*/
#if 0 /* Remove unused variants as they are slow down compilation. */
float2x2 normalize_and_get_size(float2x2 mat, float2 & r_size)
{
float size_x = 0.0f, size_y = 0.0f;
float2x2 ret;
ret[0] = normalize_and_get_length(mat[0], size_x);
ret[1] = normalize_and_get_length(mat[1], size_y);
r_size = float2(size_x, size_y);
return ret;
}
float2x3 normalize_and_get_size(float2x3 mat, float2 & r_size)
{
float size_x = 0.0f, size_y = 0.0f;
float2x3 ret;
ret[0] = normalize_and_get_length(mat[0], size_x);
ret[1] = normalize_and_get_length(mat[1], size_y);
r_size = float2(size_x, size_y);
return ret;
}
float2x4 normalize_and_get_size(float2x4 mat, float2 & r_size)
{
float size_x = 0.0f, size_y = 0.0f;
float2x4 ret;
ret[0] = normalize_and_get_length(mat[0], size_x);
ret[1] = normalize_and_get_length(mat[1], size_y);
r_size = float2(size_x, size_y);
return ret;
}
float3x2 normalize_and_get_size(float3x2 mat, float3 & r_size)
{
float size_x = 0.0f, size_y = 0.0f, size_z = 0.0f;
float3x2 ret;
ret[0] = normalize_and_get_length(mat[0], size_x);
ret[1] = normalize_and_get_length(mat[1], size_y);
ret[2] = normalize_and_get_length(mat[2], size_z);
r_size = float3(size_x, size_y, size_z);
return ret;
}
#endif
float3x3 normalize_and_get_size(float3x3 mat, float3 &r_size)
{
float size_x = 0.0f, size_y = 0.0f, size_z = 0.0f;
float3x3 ret;
ret[0] = normalize_and_get_length(mat[0], size_x);
ret[1] = normalize_and_get_length(mat[1], size_y);
ret[2] = normalize_and_get_length(mat[2], size_z);
r_size = float3(size_x, size_y, size_z);
return ret;
}
#if 0 /* Remove unused variants as they are slow down compilation. */
float3x4 normalize_and_get_size(float3x4 mat, float3 & r_size)
{
float size_x = 0.0f, size_y = 0.0f, size_z = 0.0f;
float3x4 ret;
ret[0] = normalize_and_get_length(mat[0], size_x);
ret[1] = normalize_and_get_length(mat[1], size_y);
ret[2] = normalize_and_get_length(mat[2], size_z);
r_size = float3(size_x, size_y, size_z);
return ret;
}
float4x2 normalize_and_get_size(float4x2 mat, float4 & r_size)
{
float size_x = 0.0f, size_y = 0.0f, size_z = 0.0f, size_w = 0.0f;
float4x2 ret;
ret[0] = normalize_and_get_length(mat[0], size_x);
ret[1] = normalize_and_get_length(mat[1], size_y);
ret[2] = normalize_and_get_length(mat[2], size_z);
ret[3] = normalize_and_get_length(mat[3], size_w);
r_size = float4(size_x, size_y, size_z, size_w);
return ret;
}
float4x3 normalize_and_get_size(float4x3 mat, float4 & r_size)
{
float size_x = 0.0f, size_y = 0.0f, size_z = 0.0f, size_w = 0.0f;
float4x3 ret;
ret[0] = normalize_and_get_length(mat[0], size_x);
ret[1] = normalize_and_get_length(mat[1], size_y);
ret[2] = normalize_and_get_length(mat[2], size_z);
ret[3] = normalize_and_get_length(mat[3], size_w);
r_size = float4(size_x, size_y, size_z, size_w);
return ret;
}
#endif
float4x4 normalize_and_get_size(float4x4 mat, float4 &r_size)
{
float size_x = 0.0f, size_y = 0.0f, size_z = 0.0f, size_w = 0.0f;
float4x4 ret;
ret[0] = normalize_and_get_length(mat[0], size_x);
ret[1] = normalize_and_get_length(mat[1], size_y);
ret[2] = normalize_and_get_length(mat[2], size_z);
ret[3] = normalize_and_get_length(mat[3], size_w);
r_size = float4(size_x, size_y, size_z, size_w);
return ret;
}
/** \} */

View File

@@ -0,0 +1,84 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
/* -------------------------------------------------------------------- */
/** \name Projection Matrices.
* \{ */
/**
* \brief Create an orthographic projection matrix using OpenGL coordinate convention:
* Maps each axis range to [-1..1] range for all axes.
* The resulting matrix can be used with either #project_point or #transform_point.
*/
float4x4 projection_orthographic(
float left, float right, float bottom, float top, float near_clip, float far_clip)
{
float x_delta = right - left;
float y_delta = top - bottom;
float z_delta = far_clip - near_clip;
float4x4 mat = float4x4(1.0f);
if (x_delta != 0.0f && y_delta != 0.0f && z_delta != 0.0f) {
mat[0][0] = 2.0f / x_delta;
mat[3][0] = -(right + left) / x_delta;
mat[1][1] = 2.0f / y_delta;
mat[3][1] = -(top + bottom) / y_delta;
mat[2][2] = -2.0f / z_delta; /* NOTE: negate Z. */
mat[3][2] = -(far_clip + near_clip) / z_delta;
}
return mat;
}
/**
* \brief Create a perspective projection matrix using OpenGL coordinate convention:
* Maps each axis range to [-1..1] range for all axes.
* `left`, `right`, `bottom`, `top` are frustum side distances at `z=near_clip`.
* The resulting matrix can be used with #project_point.
*/
float4x4 projection_perspective(
float left, float right, float bottom, float top, float near_clip, float far_clip)
{
float x_delta = right - left;
float y_delta = top - bottom;
float z_delta = far_clip - near_clip;
float4x4 mat = float4x4(1.0f);
if (x_delta != 0.0f && y_delta != 0.0f && z_delta != 0.0f) {
mat[0][0] = near_clip * 2.0f / x_delta;
mat[1][1] = near_clip * 2.0f / y_delta;
mat[2][0] = (right + left) / x_delta; /* NOTE: negate Z. */
mat[2][1] = (top + bottom) / y_delta;
mat[2][2] = -(far_clip + near_clip) / z_delta;
mat[2][3] = -1.0f;
mat[3][2] = (-2.0f * near_clip * far_clip) / z_delta;
mat[3][3] = 0.0f;
}
return mat;
}
/**
* \brief Create a perspective projection matrix using OpenGL coordinate convention:
* Maps each axis range to [-1..1] range for all axes.
* Uses field of view angles instead of plane distances.
* The resulting matrix can be used with #project_point.
*/
float4x4 projection_perspective_fov(float angle_left,
float angle_right,
float angle_bottom,
float angle_top,
float near_clip,
float far_clip)
{
float4x4 mat = projection_perspective(
tan(angle_left), tan(angle_right), tan(angle_bottom), tan(angle_top), near_clip, far_clip);
mat[0][0] /= near_clip;
mat[1][1] /= near_clip;
return mat;
}
/** \} */

View File

@@ -0,0 +1,72 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
/* -------------------------------------------------------------------- */
/** \name Transform function.
* \{ */
/**
* Transform a 3d point using a 3x3 matrix (rotation & scale).
*/
float3 transform_point(float3x3 mat, float3 point)
{
return mat * point;
}
/**
* Transform a 3d point using a 4x4 matrix (location & rotation & scale).
*/
float3 transform_point(float4x4 mat, float3 point)
{
return (mat * float4(point, 1.0f)).xyz;
}
/**
* Transform a 2d point using a 3x3 matrix (location & rotation & scale).
*/
float2 transform_point(float3x3 mat, float2 point)
{
return (mat * float3(point, 1.0f)).xy;
}
/**
* Transform a 3d direction vector using a 3x3 matrix (rotation & scale).
*/
float3 transform_direction(float3x3 mat, float3 direction)
{
return mat * direction;
}
/**
* Transform a 3d direction vector using a 4x4 matrix (rotation & scale).
*/
float3 transform_direction(float4x4 mat, float3 direction)
{
return to_float3x3(mat) * direction;
}
/**
* Project a point using a matrix (location & rotation & scale & perspective divide).
*/
float2 project_point(float3x3 mat, float2 point)
{
float3 tmp = mat * float3(point, 1.0f);
/* Absolute value to not flip the frustum upside down behind the camera. */
return tmp.xy / abs(tmp.z);
}
/**
* Project a point using a matrix (location & rotation & scale & perspective divide).
*/
float3 project_point(float4x4 mat, float3 point)
{
float4 tmp = mat * float4(point, 1.0f);
/* Absolute value to not flip the frustum upside down behind the camera. */
return tmp.xyz / abs(tmp.w);
}
/** \} */

View File

@@ -0,0 +1,50 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
struct Quaternion {
float x, y, z, w;
static Quaternion identity()
{
return {1, 0, 0, 0};
}
float4 as_float4() const
{
return float4(this->x, this->y, this->z, this->w);
}
};
/* -------------------------------------------------------------------- */
/** \name Quaternion Math
* \{ */
Quaternion math_quaternion_multiply(Quaternion a, Quaternion b)
{
Quaternion result;
result.x = a.x * b.x - a.y * b.y - a.z * b.z - a.w * b.w;
result.y = a.x * b.y + a.y * b.x + a.z * b.w - a.w * b.z;
result.z = a.x * b.z - a.y * b.w + a.z * b.x + a.w * b.y;
result.w = a.x * b.w + a.y * b.z - a.z * b.y + a.w * b.x;
return result;
}
Quaternion quaternion_conjugate(Quaternion q)
{
return {q.x, -q.y, -q.z, -q.w};
}
float3 transform_point_by_quaternion(Quaternion q, float3 v)
{
const Quaternion v_quat = {0.0f, v.x, v.y, v.z};
const Quaternion result = math_quaternion_multiply(math_quaternion_multiply(q, v_quat),
quaternion_conjugate(q));
return float3(result.y, result.z, result.w);
}
/** \} */

View File

@@ -0,0 +1,396 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_math_axis_angle_lib.glsl"
#include "gpu_shader_math_base_lib.glsl"
#include "gpu_shader_math_euler_lib.glsl"
#include "gpu_shader_math_matrix_conversion_lib.glsl"
#include "gpu_shader_math_matrix_normalize_lib.glsl"
#include "gpu_shader_math_quaternion_lib.glsl"
#include "gpu_shader_math_vector_compare_lib.glsl"
#include "gpu_shader_math_vector_lib.glsl"
#include "gpu_shader_utildefines_lib.glsl"
namespace detail {
Quaternion normalized_to_quat_fast(float3x3 mat)
{
/* Caller must ensure matrices aren't negative for valid results, see: #24291, #94231. */
Quaternion q;
/* Method outlined by Mike Day, ref: https://math.stackexchange.com/a/3183435/220949
* with an additional `sqrtf(..)` for higher precision result.
* Removing the `sqrt` causes tests to fail unless the precision is set to 1e-6f or larger. */
if (mat[2][2] < 0.0f) {
if (mat[0][0] > mat[1][1]) {
float trace = 1.0f + mat[0][0] - mat[1][1] - mat[2][2];
float s = 2.0f * sqrt(trace);
if (mat[1][2] < mat[2][1]) {
/* Ensure W is non-negative for a canonical result. */
s = -s;
}
q.y = 0.25f * s;
s = 1.0f / s;
q.x = (mat[1][2] - mat[2][1]) * s;
q.z = (mat[0][1] + mat[1][0]) * s;
q.w = (mat[2][0] + mat[0][2]) * s;
if ((trace == 1.0f) && (q.x == 0.0f && q.z == 0.0f && q.w == 0.0f)) {
/* Avoids the need to normalize the degenerate case. */
q.y = 1.0f;
}
}
else {
float trace = 1.0f - mat[0][0] + mat[1][1] - mat[2][2];
float s = 2.0f * sqrt(trace);
if (mat[2][0] < mat[0][2]) {
/* Ensure W is non-negative for a canonical result. */
s = -s;
}
q.z = 0.25f * s;
s = 1.0f / s;
q.x = (mat[2][0] - mat[0][2]) * s;
q.y = (mat[0][1] + mat[1][0]) * s;
q.w = (mat[1][2] + mat[2][1]) * s;
if ((trace == 1.0f) && (q.x == 0.0f && q.y == 0.0f && q.w == 0.0f)) {
/* Avoids the need to normalize the degenerate case. */
q.z = 1.0f;
}
}
}
else {
if (mat[0][0] < -mat[1][1]) {
float trace = 1.0f - mat[0][0] - mat[1][1] + mat[2][2];
float s = 2.0f * sqrt(trace);
if (mat[0][1] < mat[1][0]) {
/* Ensure W is non-negative for a canonical result. */
s = -s;
}
q.w = 0.25f * s;
s = 1.0f / s;
q.x = (mat[0][1] - mat[1][0]) * s;
q.y = (mat[2][0] + mat[0][2]) * s;
q.z = (mat[1][2] + mat[2][1]) * s;
if ((trace == 1.0f) && (q.x == 0.0f && q.y == 0.0f && q.z == 0.0f)) {
/* Avoids the need to normalize the degenerate case. */
q.w = 1.0f;
}
}
else {
/* NOTE(@ideasman42): A zero matrix will fall through to this block,
* needed so a zero scaled matrices to return a quaternion without rotation, see: #101848. */
float trace = 1.0f + mat[0][0] + mat[1][1] + mat[2][2];
float s = 2.0f * sqrt(trace);
q.x = 0.25f * s;
s = 1.0f / s;
q.y = (mat[1][2] - mat[2][1]) * s;
q.z = (mat[2][0] - mat[0][2]) * s;
q.w = (mat[0][1] - mat[1][0]) * s;
if ((trace == 1.0f) && (q.y == 0.0f && q.z == 0.0f && q.w == 0.0f)) {
/* Avoids the need to normalize the degenerate case. */
q.x = 1.0f;
}
}
}
return q;
}
Quaternion normalized_to_quat_with_checks(float3x3 mat)
{
float det = determinant(mat);
if (!isfinite(det)) {
return Quaternion::identity();
}
if (det < 0.0f) {
return normalized_to_quat_fast(-mat);
}
return normalized_to_quat_fast(mat);
}
void normalized_to_eul2(float3x3 mat, EulerXYZ &eul1, EulerXYZ &eul2)
{
float cy = hypot(mat[0][0], mat[0][1]);
if (cy > 16.0f * FLT_EPSILON) {
eul1.x = atan2(mat[1][2], mat[2][2]);
eul1.y = atan2(-mat[0][2], cy);
eul1.z = atan2(mat[0][1], mat[0][0]);
eul2.x = atan2(-mat[1][2], -mat[2][2]);
eul2.y = atan2(-mat[0][2], -cy);
eul2.z = atan2(-mat[0][1], -mat[0][0]);
}
else {
eul1.x = atan2(-mat[2][1], mat[1][1]);
eul1.y = atan2(-mat[0][2], cy);
eul1.z = 0.0f;
eul2 = eul1;
}
}
} // namespace detail
/* -------------------------------------------------------------------- */
/** \name Quaternion Functions
* \{ */
Quaternion to_quaternion(EulerXYZ eul)
{
float ti = eul.x * 0.5f;
float tj = eul.y * 0.5f;
float th = eul.z * 0.5f;
float ci = cos(ti);
float cj = cos(tj);
float ch = cos(th);
float si = sin(ti);
float sj = sin(tj);
float sh = sin(th);
float cc = ci * ch;
float cs = ci * sh;
float sc = si * ch;
float ss = si * sh;
Quaternion quat;
quat.x = cj * cc + sj * ss;
quat.y = cj * sc - sj * cs;
quat.z = cj * ss + sj * cc;
quat.w = cj * cs - sj * sc;
return quat;
}
/**
* Extract quaternion rotation from transform matrix.
* \note normalized is set to false by default.
*/
Quaternion to_quaternion(float3x3 mat)
{
return detail::normalized_to_quat_with_checks(normalize(mat));
}
/**
* Extract quaternion rotation from transform matrix.
* \note normalized is set to false by default.
*/
Quaternion to_quaternion(float3x3 mat, const bool normalized)
{
if (!normalized) {
mat = normalize(mat);
}
return to_quaternion(mat);
}
/**
* Extract quaternion rotation from transform matrix.
* \note normalized is set to false by default.
*/
Quaternion to_quaternion(float4x4 mat)
{
return to_quaternion(to_float3x3(mat));
}
/**
* Extract quaternion rotation from transform matrix.
* \note normalized is set to false by default.
*/
Quaternion to_quaternion(float4x4 mat, const bool normalized)
{
return to_quaternion(to_float3x3(mat), normalized);
}
Quaternion to_quaternion(AxisAngle axis_angle)
{
float angle_cos = cos(axis_angle.angle);
/** Using half angle identities: sin(angle / 2) = sqrt((1 - angle_cos) / 2) */
float sine = sqrt(0.5f - angle_cos * 0.5f);
float cosine = sqrt(0.5f + angle_cos * 0.5f);
/* TODO(fclem): Optimize. */
float angle_sin = sin(axis_angle.angle);
if (angle_sin < 0.0f) {
sine = -sine;
}
Quaternion quat;
quat.x = cosine;
quat.y = axis_angle.axis.x * sine;
quat.z = axis_angle.axis.y * sine;
quat.w = axis_angle.axis.z * sine;
return quat;
}
Quaternion to_quaternion(float3 axis, float angle)
{
if (is_zero(axis)) {
return Quaternion::identity();
}
AxisAngle aa;
aa.axis = normalize(axis);
aa.angle = angle;
return to_quaternion(aa);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Euler Functions
* \{ */
/**
* Extract euler rotation from transform matrix.
* \return the rotation with the smallest values from the potential candidates.
*/
EulerXYZ to_euler(float3x3 mat, const bool normalized)
{
if (!normalized) {
mat = normalize(mat);
}
EulerXYZ eul1, eul2;
detail::normalized_to_eul2(mat, eul1, eul2);
/* Return best, which is just the one with lowest values it in. */
return (length_manhattan(eul1.as_float3()) > length_manhattan(eul2.as_float3())) ? eul2 : eul1;
}
/**
* Extract euler rotation from transform matrix.
* \return the rotation with the smallest values from the potential candidates.
*/
EulerXYZ to_euler(float3x3 mat)
{
return to_euler(mat, true);
}
/**
* Extract euler rotation from transform matrix.
* \return the rotation with the smallest values from the potential candidates.
*/
EulerXYZ to_euler(float4x4 mat, const bool normalized)
{
return to_euler(to_float3x3(mat), normalized);
}
/**
* Extract euler rotation from transform matrix.
* \return the rotation with the smallest values from the potential candidates.
*/
EulerXYZ to_euler(float4x4 mat)
{
return to_euler(to_float3x3(mat));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Axis Angle Functions
* \{ */
AxisAngle to_axis_angle(Quaternion quat)
{
/* Calculate angle/2, and sin(angle/2). */
float ha = acos(quat.x);
float si = sin(ha);
/* From half-angle to angle. */
float angle = ha * 2;
/* Prevent division by zero for axis conversion. */
if (abs(si) < 0.0005f) {
si = 1.0f;
}
float3 axis = float3(quat.y, quat.z, quat.w) / si;
if (is_zero(axis)) {
axis[1] = 1.0f;
}
return {axis, angle};
}
AxisAngle to_axis_angle(EulerXYZ eul)
{
/* Use quaternions as intermediate representation for now... */
return to_axis_angle(to_quaternion(eul));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Matrices Functions
* \{ */
/**
* Decompose a matrix into location, rotation, and scale components.
* \tparam allow_negative_scale: if true, will compute determinant to know if matrix is negative.
* Rotation and scale values will be flipped if it is negative.
* This is a costly operation so it is disabled by default.
*/
void to_rot_scale(float3x3 mat, EulerXYZ &r_rotation, float3 &r_scale)
{
r_scale = to_scale(mat);
r_rotation = to_euler(mat, true);
}
/**
* Decompose a matrix into location, rotation, and scale components.
* \tparam allow_negative_scale: if true, will compute determinant to know if matrix is negative.
* Rotation and scale values will be flipped if it is negative.
* This is a costly operation so it is disabled by default.
*/
void to_rot_scale(float3x3 mat,
EulerXYZ &r_rotation,
float3 &r_scale,
const bool allow_negative_scale)
{
float3x3 normalized_mat = normalize_and_get_size(mat, r_scale);
if (allow_negative_scale) {
if (is_negative(normalized_mat)) {
normalized_mat = -normalized_mat;
r_scale = -r_scale;
}
}
r_rotation = to_euler(normalized_mat, true);
}
void to_rot_scale(float3x3 mat, Quaternion &r_rotation, float3 &r_scale)
{
r_scale = to_scale(mat);
r_rotation = to_quaternion(mat, true);
}
void to_rot_scale(float3x3 mat,
Quaternion &r_rotation,
float3 &r_scale,
const bool allow_negative_scale)
{
float3x3 normalized_mat = normalize_and_get_size(mat, r_scale);
if (allow_negative_scale) {
if (is_negative(normalized_mat)) {
normalized_mat = -normalized_mat;
r_scale = -r_scale;
}
}
r_rotation = to_quaternion(normalized_mat, true);
}
void to_loc_rot_scale(float4x4 mat, float3 &r_location, EulerXYZ &r_rotation, float3 &r_scale)
{
r_location = mat[3].xyz;
to_rot_scale(to_float3x3(mat), r_rotation, r_scale);
}
void to_loc_rot_scale(float4x4 mat,
float3 &r_location,
EulerXYZ &r_rotation,
float3 &r_scale,
const bool allow_negative_scale)
{
r_location = mat[3].xyz;
to_rot_scale(to_float3x3(mat), r_rotation, r_scale, allow_negative_scale);
}
void to_loc_rot_scale(float4x4 mat, float3 &r_location, Quaternion &r_rotation, float3 &r_scale)
{
r_location = mat[3].xyz;
to_rot_scale(to_float3x3(mat), r_rotation, r_scale);
}
void to_loc_rot_scale(float4x4 mat,
float3 &r_location,
Quaternion &r_rotation,
float3 &r_scale,
const bool allow_negative_scale)
{
r_location = mat[3].xyz;
to_rot_scale(to_float3x3(mat), r_rotation, r_scale, allow_negative_scale);
}
/** \} */

View File

@@ -0,0 +1,153 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_math_axis_angle_lib.glsl"
#include "gpu_shader_math_euler_lib.glsl"
#include "gpu_shader_math_matrix_construct_lib.glsl"
#include "gpu_shader_math_quaternion_lib.glsl"
#include "gpu_shader_utildefines_lib.glsl"
/* -------------------------------------------------------------------- */
/** \name Interpolate
* \{ */
/**
* Generic function for implementing slerp
* (quaternions and spherical vector coords).
*
* \param t: factor in [0..1]
* \param cosom: dot product from normalized vectors/quaternions.
* \return calculated weights.
*/
float2 interpolate_dot_slerp(float t, float cosom)
{
float2 w = float2(1.0f - t, t);
/* Within [-1..1] range, avoid aligned axis. */
constexpr float eps = 1e-4f;
if (abs(cosom) < 1.0f - eps) {
float omega = acos(cosom);
w = sin(w * omega) / sin(omega);
}
return w;
}
Quaternion interpolate(Quaternion a, Quaternion b, float t)
{
float4 quat = a.as_float4();
float cosom = dot(a.as_float4(), b.as_float4());
/* Rotate around shortest angle. */
if (cosom < 0.0f) {
cosom = -cosom;
quat = -quat;
}
float2 w = interpolate_dot_slerp(t, cosom);
quat = w.x * quat + w.y * b.as_float4();
return Quaternion{UNPACK4(quat)};
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Rotate
* \{ */
/**
* Equivalent to `mat * from_rotation(rotation)` but with fewer operation.
* Optimized for rotation on basis vector (i.e: AxisAngle({1, 0, 0}, 0.2f)).
*/
float3x3 rotate(float3x3 mat, AxisAngle rotation)
{
float3x3 result;
/* axis_vec is given to be normalized. */
if (rotation.axis.x == 1.0f) {
float angle_cos = cos(rotation.angle);
float angle_sin = sin(rotation.angle);
for (int c = 0; c < 3; c++) {
result[0][c] = mat[0][c];
result[1][c] = angle_cos * mat[1][c] + angle_sin * mat[2][c];
result[2][c] = -angle_sin * mat[1][c] + angle_cos * mat[2][c];
}
}
else if (rotation.axis.y == 1.0f) {
float angle_cos = cos(rotation.angle);
float angle_sin = sin(rotation.angle);
for (int c = 0; c < 3; c++) {
result[0][c] = angle_cos * mat[0][c] - angle_sin * mat[2][c];
result[1][c] = mat[1][c];
result[2][c] = angle_sin * mat[0][c] + angle_cos * mat[2][c];
}
}
else if (rotation.axis.z == 1.0f) {
float angle_cos = cos(rotation.angle);
float angle_sin = sin(rotation.angle);
for (int c = 0; c < 3; c++) {
result[0][c] = angle_cos * mat[0][c] + angle_sin * mat[1][c];
result[1][c] = -angle_sin * mat[0][c] + angle_cos * mat[1][c];
result[2][c] = mat[2][c];
}
}
else {
/* Un-optimized case. Arbitrary rotation. */
result = mat * from_rotation(rotation);
}
return result;
}
/**
* Equivalent to `mat * from_rotation(rotation)` but with fewer operation.
* Optimized for rotation on basis vector (i.e: AxisAngle({1, 0, 0}, 0.2f)).
*/
float3x3 rotate(float3x3 mat, EulerXYZ rotation)
{
AxisAngle axis_angle;
if (rotation.y == 0.0f && rotation.z == 0.0f) {
axis_angle = AxisAngle{float3(1.0f, 0.0f, 0.0f), rotation.x};
}
else if (rotation.x == 0.0f && rotation.z == 0.0f) {
axis_angle = AxisAngle{float3(0.0f, 1.0f, 0.0f), rotation.y};
}
else if (rotation.x == 0.0f && rotation.y == 0.0f) {
axis_angle = AxisAngle{float3(0.0f, 0.0f, 1.0f), rotation.z};
}
else {
/* Un-optimized case. Arbitrary rotation. */
return mat * from_rotation(rotation);
}
return rotate(mat, axis_angle);
}
/**
* Equivalent to `mat * from_rotation(rotation)` but with fewer operation.
* Optimized for rotation on basis vector (i.e: AxisAngle({1, 0, 0}, 0.2f)).
*/
float4x4 rotate(float4x4 mat, AxisAngle rotation)
{
float4x4 result = to_float4x4(rotate(to_float3x3(mat), rotation));
result[0][3] = mat[0][3];
result[1][3] = mat[1][3];
result[2][3] = mat[2][3];
result[3][0] = mat[3][0];
result[3][1] = mat[3][1];
result[3][2] = mat[3][2];
result[3][3] = mat[3][3];
return result;
}
/**
* Equivalent to `mat * from_rotation(rotation)` but with fewer operation.
* Optimized for rotation on basis vector (i.e: AxisAngle({1, 0, 0}, 0.2f)).
*/
float4x4 rotate(float4x4 mat, EulerXYZ rotation)
{
float4x4 result = to_float4x4(rotate(to_float3x3(mat), rotation));
result[0][3] = mat[0][3];
result[1][3] = mat[1][3];
result[2][3] = mat[2][3];
result[3][0] = mat[3][0];
result[3][1] = mat[3][1];
result[3][2] = mat[3][2];
result[3][3] = mat[3][3];
return result;
}
/** \} */

View File

@@ -0,0 +1,129 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
#include "gpu_shader_math_constants_lib.glsl"
/* -------------------------------------------------------------------- */
/** \name Safe Math Functions
* \{ */
/**
* Safe `a` modulo `b`.
* If `b` equal 0 the result will be 0.
*/
float safe_mod(float a, float b)
{
return (b != 0.0f) ? mod(a, b) : 0.0f;
}
/**
* Safe divide `a` by `b`.
* If `b` equal 0 the result will be 0.
*/
float safe_divide(float a, float b)
{
return (b != 0.0f) ? (a / b) : 0.0f;
}
/**
* Safe reciprocal function. Returns `1/a`.
* If `a` equal 0 the result will be 0.
*/
float safe_rcp(float a)
{
return (a != 0.0f) ? (1.0f / a) : 0.0f;
}
/**
* Safe square root function. Returns `sqrt(a)`.
* If `a` is less or equal to 0 then the result will be 0.
*/
float safe_sqrt(float a)
{
return sqrt(max(0.0f, a));
}
/**
* Safe `arccosine` function. Returns `acos(a)`.
* If `a` is greater than 1, returns 0.
* If `a` is less than -1, returns PI.
*/
float safe_acos(float a)
{
if (a <= -1.0f) {
return M_PI;
}
else if (a >= 1.0f) {
return 0.0f;
}
return acos(a);
}
/**
* A version of pow that returns a fallback value if the computation is undefined. From the spec:
* The result is undefined if x < 0 or if x = 0 and y is less than or equal 0.
*/
float fallback_pow(float x, float y, float fallback)
{
if (x < 0.0f || (x == 0.0f && y <= 0.0f)) {
return fallback;
}
return pow(x, y);
}
/**
* A version of pow that behaves similar to C++ std::pow.
*/
float compatible_pow(float x, float y)
{
if (y == 0.0f) { /* x^0 -> 1, including 0^0 */
return 1.0f;
}
/* GLSL pow doesn't accept negative x. */
if (x < 0.0f) {
if (mod(-y, 2.0f) == 0.0f) {
return pow(-x, y);
}
else {
return -pow(-x, y);
}
}
else if (x == 0.0f) {
return 0.0f;
}
return pow(x, y);
}
/**
* A version of mod that behaves similar to C++ `std::modf`, and is safe such that it returns 0
* when b is also 0.
*/
float compatible_mod(float a, float b)
{
if (b != 0.0f) {
int N = int(a / b);
return a - N * b;
}
return 0.0f;
}
/**
* Wrap the given value a to fall within the range [b, c].
*/
float wrap(float a, float b, float c)
{
float range = b - c;
/* Avoid discrepancy on some hardware due to floating point accuracy and fast math. */
float s = (a != b) ? floor((a - c) / range) : 1.0f;
return (range != 0.0f) ? a - range * s : c;
}
/** \} */

View File

@@ -0,0 +1,78 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
/* ---------------------------------------------------------------------- */
/** \name Comparison
* \{ */
/**
* Return true if all components is equal to zero.
*/
template<typename VecT> bool is_zero(VecT vec)
{
return all(equal(vec, VecT(0.0f)));
}
template bool is_zero<float2>(float2);
template bool is_zero<float3>(float3);
template bool is_zero<float4>(float4);
/**
* Return true if any component is equal to zero.
*/
template<typename VecT> bool is_any_zero(VecT vec)
{
return any(equal(vec, VecT(0.0f)));
}
template bool is_any_zero<float2>(float2);
template bool is_any_zero<float3>(float3);
template bool is_any_zero<float4>(float4);
/**
* Return true if the difference between`a` and `b` is below the `epsilon` value.
*/
template<typename VecT> bool is_equal(VecT a, VecT b, float epsilon)
{
return all(lessThanEqual(abs(a - b), VecT(epsilon)));
}
template bool is_equal<float2>(float2, float2, float);
template bool is_equal<float3>(float3, float3, float);
template bool is_equal<float4>(float4, float4, float);
/**
* Return true if the deference between`a` and `b` is below the `epsilon` value.
* Epsilon value is scaled by magnitude of `a` before comparison.
*/
template<typename VecT, int dim>
bool almost_equal_relative(VecT a, VecT b, const float epsilon_factor)
{
for (int i = 0; i < dim; i++) {
if (abs(a[i] - b[i]) > epsilon_factor * abs(a[i])) {
return false;
}
}
return true;
}
template bool almost_equal_relative<float2, 2>(float2 a, float2 b, const float epsilon_factor);
template bool almost_equal_relative<float3, 3>(float3 a, float3 b, const float epsilon_factor);
template bool almost_equal_relative<float4, 4>(float4 a, float4 b, const float epsilon_factor);
/* Checks are flipped so NAN doesn't assert because we're making sure the value was
* normalized and in the case we don't want NAN to be raising asserts since there
* is nothing to be done in that case. */
template<typename VecT> bool is_unit_scale(VecT v)
{
constexpr float assert_unit_epsilon = 0.0002f;
float test_unit = dot(v, v);
return (!(abs(test_unit - 1.0f) >= assert_unit_epsilon) ||
!(abs(test_unit) >= assert_unit_epsilon));
}
template bool is_unit_scale<float2>(float2);
template bool is_unit_scale<float3>(float3);
template bool is_unit_scale<float4>(float4);
/** \} */

View File

@@ -0,0 +1,179 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
/**
* Returns \a a if it is a multiple of \a b or the next multiple or \a b after \b a .
* In other words, it is equivalent to `divide_ceil(a, b) * b`.
* It is undefined if \a a is negative or \b b is not strictly positive.
*/
template<typename VecT> VecT ceil_to_multiple(VecT a, VecT b)
{
return ((a + b - VecT(1)) / b) * b;
}
template int2 ceil_to_multiple<int2>(int2, int2);
template int3 ceil_to_multiple<int3>(int3, int3);
template int4 ceil_to_multiple<int4>(int4, int4);
template uint2 ceil_to_multiple<uint2>(uint2, uint2);
template uint3 ceil_to_multiple<uint3>(uint3, uint3);
template uint4 ceil_to_multiple<uint4>(uint4, uint4);
/**
* Integer division that returns the ceiling, instead of flooring like normal C division.
* It is undefined if \a a is negative or \b b is not strictly positive.
*/
template<typename VecT> VecT divide_ceil(VecT a, VecT b)
{
return (a + b - VecT(1)) / b;
}
template int2 divide_ceil<int2>(int2, int2);
template int3 divide_ceil<int3>(int3, int3);
template int4 divide_ceil<int4>(int4, int4);
template uint2 divide_ceil<uint2>(uint2, uint2);
template uint3 divide_ceil<uint3>(uint3, uint3);
template uint4 divide_ceil<uint4>(uint4, uint4);
/**
* Component wise, use vector to replace min if it is smaller and max if bigger.
*/
template<typename VecT> void min_max(VecT vector, VecT &min_v, VecT &max_v)
{
min_v = min(vector, min_v);
max_v = max(vector, max_v);
}
template void min_max<float2>(float2, float2 &, float2 &);
template void min_max<float3>(float3, float3 &, float3 &);
template void min_max<float4>(float4, float4 &, float4 &);
/**
* Return the manhattan length of `a`.
* This is also the sum of the absolute value of all components.
*/
template<typename VecT> float length_manhattan(VecT a)
{
return dot(abs(a), VecT(1));
}
template float length_manhattan<float2>(float2);
template float length_manhattan<float3>(float3);
template float length_manhattan<float4>(float4);
/**
* Return the length squared of `a`.
*/
template<typename VecT> float length_squared(VecT a)
{
return dot(a, a);
}
template float length_squared<float2>(float2);
template float length_squared<float3>(float3);
template float length_squared<float4>(float4);
/**
* Return the manhattan distance between `a` and `b`.
*/
template<typename VecT> float distance_manhattan(VecT a, VecT b)
{
return length_manhattan(a - b);
}
template float distance_manhattan<float2>(float2, float2);
template float distance_manhattan<float3>(float3, float3);
template float distance_manhattan<float4>(float4, float4);
/**
* Return the squared distance between `a` and `b`.
*/
template<typename VecT> float distance_squared(VecT a, VecT b)
{
return length_squared(a - b);
}
template float distance_squared<float2>(float2, float2);
template float distance_squared<float3>(float3, float3);
template float distance_squared<float4>(float4, float4);
/**
* Return normalized version of the `vector` and its length.
*/
template<typename VecT> VecT normalize_and_get_length(VecT vector, float &out_length)
{
out_length = length_squared(vector);
constexpr float threshold = 1e-35f;
if (out_length > threshold) {
out_length = sqrt(out_length);
return vector / out_length;
}
/* Either the vector is small or one of its values contained `nan`. */
out_length = 0.0f;
return VecT(0.0f);
}
template float2 normalize_and_get_length<float2>(float2, float &);
template float3 normalize_and_get_length<float3>(float3, float &);
template float4 normalize_and_get_length<float4>(float4, float &);
/**
* Per component linear interpolation.
*/
template<typename VecT> VecT interpolate(VecT a, VecT b, float t)
{
return mix(a, b, t);
}
template float2 interpolate<float2>(float2, float2, float);
template float3 interpolate<float3>(float3, float3, float);
template float4 interpolate<float4>(float4, float4, float);
/**
* Return half-way point between `a` and `b`.
*/
template<typename VecT> VecT midpoint(VecT a, VecT b)
{
return (a + b) * 0.5f;
}
template float2 midpoint<float2>(float2, float2);
template float3 midpoint<float3>(float3, float3);
template float4 midpoint<float4>(float4, float4);
/**
* Return the index of the component with the greatest absolute value.
*/
int dominant_axis(float3 a)
{
float3 b = abs(a);
return ((b.x > b.y) ? ((b.x > b.z) ? 0 : 2) : ((b.y > b.z) ? 1 : 2));
}
/**
* Calculates a perpendicular vector to \a v.
* \note Returned vector is always rotated 90 degrees counter clock wise.
*/
template<typename VecT> VecT orthogonal(VecT v)
{
return VecT(-v.y, v.x);
}
template int2 orthogonal<int2>(int2);
template float2 orthogonal<float2>(float2);
/**
* Calculates a perpendicular vector to \a v.
* \note Returned vector can be in any perpendicular direction.
* \note Returned vector might not the same length as \a v.
*/
template<> float3 orthogonal<float3>(float3 v)
{
switch (dominant_axis(v)) {
default:
case 0:
return float3(-v.y - v.z, v.x, v.x);
case 1:
return float3(v.y, -v.x - v.z, v.y);
case 2:
return float3(v.z, v.z, -v.x - v.y);
}
}
/**
* Return `vector` if `incident` and `reference` are pointing in the same direction.
*/
// float2 faceforward(float2 vector, float2 incident, float2 reference); /* Built-in GLSL. */

View File

@@ -0,0 +1,128 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
/* ---------------------------------------------------------------------- */
/** \name Comparison
* \{ */
float reduce_max(float2 a)
{
return max(a.x, a.y);
}
float reduce_max(float3 a)
{
return max(a.x, max(a.y, a.z));
}
float reduce_max(float4 a)
{
return max(max(a.x, a.y), max(a.z, a.w));
}
int reduce_max(int2 a)
{
return max(a.x, a.y);
}
int reduce_max(int3 a)
{
return max(a.x, max(a.y, a.z));
}
int reduce_max(int4 a)
{
return max(max(a.x, a.y), max(a.z, a.w));
}
float reduce_min(float2 a)
{
return min(a.x, a.y);
}
float reduce_min(float3 a)
{
return min(a.x, min(a.y, a.z));
}
float reduce_min(float4 a)
{
return min(min(a.x, a.y), min(a.z, a.w));
}
int reduce_min(int2 a)
{
return min(a.x, a.y);
}
int reduce_min(int3 a)
{
return min(a.x, min(a.y, a.z));
}
int reduce_min(int4 a)
{
return min(min(a.x, a.y), min(a.z, a.w));
}
float reduce_add(float2 a)
{
return a.x + a.y;
}
float reduce_add(float3 a)
{
return a.x + a.y + a.z;
}
float reduce_add(float4 a)
{
return a.x + a.y + a.z + a.w;
}
int reduce_add(int2 a)
{
return a.x + a.y;
}
int reduce_add(int3 a)
{
return a.x + a.y + a.z;
}
int reduce_add(int4 a)
{
return a.x + a.y + a.z + a.w;
}
#if 0 /* Remove unused variants as they are slow down compilation. */
float reduce_mul(float2 a)
{
return a.x * a.y;
}
float reduce_mul(float3 a)
{
return a.x * a.y * a.z;
}
float reduce_mul(float4 a)
{
return a.x * a.y * a.z * a.w;
}
int reduce_mul(int2 a)
{
return a.x * a.y;
}
int reduce_mul(int3 a)
{
return a.x * a.y * a.z;
}
int reduce_mul(int4 a)
{
return a.x * a.y * a.z * a.w;
}
#endif
float average(float2 a)
{
return reduce_add(a) * (1.0f / 2.0f);
}
float average(float3 a)
{
return reduce_add(a) * (1.0f / 3.0f);
}
float average(float4 a)
{
return reduce_add(a) * (1.0f / 4.0f);
}
/** \} */

View File

@@ -0,0 +1,246 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_math_safe_lib.glsl"
/**
* Safe `a` modulo `b`.
* If `b` equal 0 the result will be 0.
*/
template<typename VecT> VecT safe_mod(VecT a, VecT b)
{
return select(VecT(0), mod(a, b), notEqual(b, VecT(0)));
}
template float2 safe_mod<float2>(float2, float2);
template float3 safe_mod<float3>(float3, float3);
template float4 safe_mod<float4>(float4, float4);
/**
* Safe `a` modulo `b`.
* If `b` equal 0 the result will be 0.
*/
float2 safe_mod(float2 a, float b)
{
return (b != 0.0f) ? mod(a, float2(b)) : float2(0);
}
float3 safe_mod(float3 a, float b)
{
return (b != 0.0f) ? mod(a, float3(b)) : float3(0);
}
float4 safe_mod(float4 a, float b)
{
return (b != 0.0f) ? mod(a, float4(b)) : float4(0);
}
/**
* A version of mod that behaves similar to C++ `std::modf`, and is safe such that it returns 0
* when b is also 0.
*/
float2 compatible_mod(float2 a, float b)
{
return float2(compatible_mod(a.x, b), compatible_mod(a.y, b));
}
/**
* A version of mod that behaves similar to C++ `std::modf`, and is safe such that it returns 0
* when b is also 0.
*/
float3 compatible_mod(float3 a, float b)
{
return float3(compatible_mod(a.x, b), compatible_mod(a.y, b), compatible_mod(a.z, b));
}
/**
* A version of mod that behaves similar to C++ `std::modf`, and is safe such that it returns 0
* when b is also 0.
*/
float4 compatible_mod(float4 a, float b)
{
return float4(compatible_mod(a.x, b),
compatible_mod(a.y, b),
compatible_mod(a.z, b),
compatible_mod(a.w, b));
}
/**
* A version of mod that behaves similar to C++ `std::modf`, and is safe such that it returns 0
* when b is also 0.
*/
float2 compatible_mod(float2 a, float2 b)
{
return float2(compatible_mod(a.x, b.x), compatible_mod(a.y, b.y));
}
/**
* A version of mod that behaves similar to C++ `std::modf`, and is safe such that it returns 0
* when b is also 0.
*/
float3 compatible_mod(float3 a, float3 b)
{
return float3(compatible_mod(a.x, b.x), compatible_mod(a.y, b.y), compatible_mod(a.z, b.z));
}
/**
* A version of mod that behaves similar to C++ `std::modf`, and is safe such that it returns 0
* when b is also 0.
*/
float4 compatible_mod(float4 a, float4 b)
{
return float4(compatible_mod(a.x, b.x),
compatible_mod(a.y, b.y),
compatible_mod(a.z, b.z),
compatible_mod(a.w, b.w));
}
/**
* Safe divide `a` by `b`.
* If `b` equal 0 the result will be 0.
*/
template<typename VecT> VecT safe_divide(VecT a, VecT b)
{
return select(VecT(0), a / b, notEqual(b, VecT(0)));
}
template float2 safe_divide<float2>(float2, float2);
template float3 safe_divide<float3>(float3, float3);
template float4 safe_divide<float4>(float4, float4);
/* NOTE: Cannot overload templates. */
/**
* Safe divide `a` by `b`.
* If `b` equal 0 the result will be 0.
*/
float2 safe_divide(float2 a, float b)
{
return (b != 0.0f) ? (a / b) : float2(0);
}
float3 safe_divide(float3 a, float b)
{
return (b != 0.0f) ? (a / b) : float3(0);
}
float4 safe_divide(float4 a, float b)
{
return (b != 0.0f) ? (a / b) : float4(0);
}
/**
* Return normalized version of the `vector` or a default normalized vector if `vector` is invalid.
*/
template<typename VecT> VecT safe_normalize_and_get_length(VecT vector, float &out_length)
{
float length_squared = dot(vector, vector);
constexpr float threshold = 1e-35f;
if (length_squared > threshold) {
out_length = sqrt(length_squared);
return vector / out_length;
}
/* Either the vector is small or one of its values contained `nan`. */
out_length = 0.0f;
VecT result = VecT(0.0f);
result[0] = 1.0f;
return result;
}
template float2 safe_normalize_and_get_length<float2>(float2, float &);
template float3 safe_normalize_and_get_length<float3>(float3, float &);
template float4 safe_normalize_and_get_length<float4>(float4, float &);
/**
* Return normalized version of the `vector` or a default normalized vector if `vector` is invalid.
*/
template<typename VecT> VecT safe_normalize(VecT vector)
{
float unused_length = 0.0f;
return safe_normalize_and_get_length(vector, unused_length);
}
template float2 safe_normalize<float2>(float2);
template float3 safe_normalize<float3>(float3);
template float4 safe_normalize<float4>(float4);
/**
* Return normalized version of the `vector` or `fallback` vector if `vector` is invalid.
*/
template<typename VecT> VecT normalize_fallback(VecT vector, VecT fallback)
{
float length_squared = dot(vector, vector);
constexpr float threshold = 1e-35f;
if (length_squared > threshold) {
return vector * inversesqrt(length_squared);
}
/* Either the vector is small or one of its values contained `nan`. */
return fallback;
}
template float2 normalize_fallback<float2>(float2, float2);
template float3 normalize_fallback<float3>(float3, float3);
template float4 normalize_fallback<float4>(float4, float4);
/**
* Safe reciprocal function. Returns `1/a`.
* If `a` equal 0 the result will be 0.
*/
template<typename VecT> VecT safe_rcp(VecT a)
{
return select(VecT(0.0f), (1.0f / a), notEqual(a, VecT(0.0f)));
}
template float2 safe_rcp<float2>(float2);
template float3 safe_rcp<float3>(float3);
template float4 safe_rcp<float4>(float4);
/**
* A version of pow that returns a fallback value if the computation is undefined. From the spec:
* The result is undefined if x < 0 or if x = 0 and y is less than or equal 0.
*/
float2 fallback_pow(float2 a, float b, float2 fallback)
{
return float2(fallback_pow(a.x, b, fallback.x), fallback_pow(a.y, b, fallback.y));
}
float3 fallback_pow(float3 a, float b, float3 fallback)
{
return float3(fallback_pow(a.x, b, fallback.x),
fallback_pow(a.y, b, fallback.y),
fallback_pow(a.z, b, fallback.z));
}
float4 fallback_pow(float4 a, float b, float4 fallback)
{
return float4(fallback_pow(a.x, b, fallback.x),
fallback_pow(a.y, b, fallback.y),
fallback_pow(a.z, b, fallback.z),
fallback_pow(a.w, b, fallback.w));
}
float2 compatible_pow(float2 a, float2 b)
{
return float2(compatible_pow(a.x, b.x), compatible_pow(a.y, b.y));
}
float3 compatible_pow(float3 a, float3 b)
{
return float3(compatible_pow(a.x, b.x), compatible_pow(a.y, b.y), compatible_pow(a.z, b.z));
}
float4 compatible_pow(float4 a, float4 b)
{
return float4(compatible_pow(a.x, b.x),
compatible_pow(a.y, b.y),
compatible_pow(a.z, b.z),
compatible_pow(a.w, b.w));
}
/**
* Wrap the given value a to fall within the range [b, c].
*/
float2 wrap(float2 a, float2 b, float2 c)
{
return float2(wrap(a.x, b.x, c.x), wrap(a.y, b.y, c.y));
}
/**
* Wrap the given value a to fall within the range [b, c].
*/
float3 wrap(float3 a, float3 b, float3 c)
{
return float3(wrap(a.x, b.x, c.x), wrap(a.y, b.y, c.y), wrap(a.z, b.z, c.z));
}
/**
* Wrap the given value a to fall within the range [b, c].
*/
float4 wrap(float4 a, float4 b, float4 c)
{
return float4(
wrap(a.x, b.x, c.x), wrap(a.y, b.y, c.y), wrap(a.z, b.z, c.z), wrap(a.w, b.w, c.w));
}

View File

@@ -0,0 +1,33 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
#include "gpu_shader_index_range_lib.glsl"
/**
* See `OffsetIndices` C++ definition for formal definition.
*
* OffsetIndices cannot be implemented on GPU because of the lack of operator overloading and
* buffer reference in GLSL. So we simply interpret a given integer buffer as a `OffsetIndices`
* buffer and load a specific item as a range.
*/
namespace offset_indices {
#ifdef GLSL_CPP_STUBS
/* Equivalent of `IndexRange OffsetIndices<int>operator[]`.
* Implementation for C++ compilation. */
inline static IndexRange load_range_from_buffer(const int (&buf)[], int i)
{
return IndexRange::from_begin_end(buf[i], buf[i + 1]);
}
#endif
} // namespace offset_indices
/* Shader implementation because of missing buffer reference as argument in GLSL. */
#define offset_indices_load_range_from_buffer(buf_, i_) \
IndexRange::from_begin_end(buf_[i_], buf_[i_ + 1])

View File

@@ -0,0 +1,38 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "infos/gpu_shader_print_infos.hh"
SHADER_LIBRARY_CREATE_INFO(gpu_print)
uint print_data(uint offset, uint data)
{
if (offset < GPU_SHADER_PRINTF_MAX_CAPACITY) {
gpu_print_buf[offset] = data;
}
return offset + 1u;
}
uint print_data(uint offset, string_t data)
{
return print_data(offset, as_uint(data));
}
uint print_data(uint offset, int data)
{
return print_data(offset, uint(data));
}
uint print_data(uint offset, float data)
{
return print_data(offset, floatBitsToUint(data));
}
uint print_start(const uint data_len)
{
/* Add one to skip the length stored in the first element of the buffer. */
return atomicAdd(gpu_print_buf[0], data_len) + 1u;
}

View File

@@ -0,0 +1,14 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
/* General purpose 3D ray. */
struct Ray {
packed_float3 direction;
float max_time;
packed_float3 origin;
};

View File

@@ -0,0 +1,33 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
/**
* Ray offset to avoid self intersection.
*
* This can be used to compute a modified ray start position for rays leaving from a surface.
* From:
* "A Fast and Robust Method for Avoiding Self-Intersection"
* Ray Tracing Gems, chapter 6.
*/
float3 offset_ray(float3 P, float3 Ng)
{
constexpr float origin = 1.0f / 32.0f;
constexpr float float_scale = 1.0f / 65536.0f;
constexpr float int_scale = 256.0f;
int3 of_i = int3(int_scale * Ng);
of_i = int3((P.x < 0.0f) ? -of_i.x : of_i.x,
(P.y < 0.0f) ? -of_i.y : of_i.y,
(P.z < 0.0f) ? -of_i.z : of_i.z);
float3 P_i = intBitsToFloat(floatBitsToInt(P) + of_i);
float3 uf = P + float_scale * Ng;
return float3((abs(P.x) < origin) ? uf.x : P_i.x,
(abs(P.y) < origin) ? uf.y : P_i.y,
(abs(P.z) < origin) ? uf.z : P_i.z);
}

View File

@@ -0,0 +1,45 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "infos/gpu_shader_sequencer_infos.hh"
SHADER_LIBRARY_CREATE_INFO(gpu_shader_sequencer_strips)
/* Signed distance to rounded box, centered at origin.
* Reference: https://iquilezles.org/articles/distfunctions2d/ */
float sdf_rounded_box(float2 pos, float2 size, float radius)
{
float2 q = abs(pos) - size + radius;
return min(max(q.x, q.y), 0.0f) + length(max(q, 0.0f)) - radius;
}
void strip_box(float left,
float right,
float bottom,
float top,
float2 pos,
float2 &r_pos1,
float2 &r_pos2,
float2 &r_size,
float2 &r_center,
float2 &r_pos,
float &r_radius)
{
/* Snap to pixel grid coordinates, so that outline/border is non-fractional
* pixel sizes. */
r_pos1 = round(float2(left, bottom));
r_pos2 = round(float2(right, top));
/* Make sure strip is at least 1px wide. */
r_pos2.x = max(r_pos2.x, r_pos1.x + 1.0f);
r_size = (r_pos2 - r_pos1) * 0.5f;
r_center = (r_pos1 + r_pos2) * 0.5f;
r_pos = round(pos);
r_radius = context_data.round_radius;
if (r_radius > r_size.x) {
r_radius = 0.0f;
}
}

View File

@@ -0,0 +1,82 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
/**
* Software implementation of encoding and decoding of shared exponent texture as described by the
* OpenGL extension EXT_texture_shared_exponent Appendix
* https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_shared_exponent.txt
*
* This allows to read and write the RGB9_E5 format in a R32UI texture without explicit support on
* the hardware for this type. However, filtering is not supported in this case.
*/
#define RGB9E5_EXPONENT_BITS 5
#define RGB9E5_MANTISSA_BITS 9
#define RGB9E5_EXP_BIAS 15
#define RGB9E5_MAX_VALID_BIASED_EXP 31
#define MAX_RGB9E5_EXP (RGB9E5_MAX_VALID_BIASED_EXP - RGB9E5_EXP_BIAS)
#define RGB9E5_MANTISSA_VALUES (1 << RGB9E5_MANTISSA_BITS)
#define MAX_RGB9E5_MANTISSA (RGB9E5_MANTISSA_VALUES - 1)
int rgb9e5_floor_log2(float x)
{
/* Ok, rgb9e5_floor_log2 is not correct for the denorm and zero values, but we
* are going to do a max of this value with the minimum rgb9e5 exponent
* that will hide these problem cases. */
int biased_exponent = floatBitsToInt(x) >> 23;
return biased_exponent - 127;
}
float rgb9e5_exponent_factor(int exponent)
{
/* This pow function could be replaced by a table. There is only 32 values. */
return exp2(float(exponent - RGB9E5_EXP_BIAS - RGB9E5_MANTISSA_BITS));
}
struct rgb9e5_t {
uint exp_shared;
uint3 mantissa;
};
rgb9e5_t rgb9e5_from_float3(float3 color)
{
constexpr float max_rgb9e5 = float(0xFF80u);
color = clamp(color, 0.0f, max_rgb9e5);
float max_component = max(max(color.r, color.g), color.b);
int log2_floored = rgb9e5_floor_log2(max_component);
int exp_shared = max(-RGB9E5_EXP_BIAS - 1, log2_floored) + (1 + RGB9E5_EXP_BIAS);
float denom = rgb9e5_exponent_factor(exp_shared);
int maxm = int(max_component / denom + 0.5f);
if (maxm == MAX_RGB9E5_MANTISSA + 1) {
denom *= 2.0f;
exp_shared += 1;
}
rgb9e5_t result;
result.exp_shared = uint(exp_shared);
result.mantissa = uint3(color / denom + 0.5f);
return result;
}
uint rgb9e5_encode(float3 color)
{
rgb9e5_t result = rgb9e5_from_float3(color);
result.exp_shared <<= RGB9E5_MANTISSA_BITS * 3;
result.mantissa <<= RGB9E5_MANTISSA_BITS * uint3(0, 1, 2);
return result.mantissa.r | result.mantissa.g | result.mantissa.b | result.exp_shared;
}
float3 rgb9e5_decode(uint data)
{
int exp_shared = int(data >> (RGB9E5_MANTISSA_BITS * 3));
uint3 mantissa = (uint3(data) >> (RGB9E5_MANTISSA_BITS * uint3(0, 1, 2))) &
uint(MAX_RGB9E5_MANTISSA);
return float3(mantissa) * rgb9e5_exponent_factor(exp_shared);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,182 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
#include "gpu_shader_math_base_lib.glsl"
#include "gpu_shader_math_vector_compare_lib.glsl"
#include "GPU_shader_shared.hh"
/* clang-format off */
#ifndef GPU_METAL
bool is_integer(bool /*v*/) { return true; }
#endif
bool is_integer(uint /*v*/) { return true; }
bool is_integer(int /*v*/) { return true; }
bool is_integer(float /*v*/) { return false; }
bool is_integer(int2 /*v*/) { return true; }
bool is_integer(int3 /*v*/) { return true; }
bool is_integer(int4 /*v*/) { return true; }
bool is_integer(uint2 /*v*/) { return true; }
bool is_integer(uint3 /*v*/) { return true; }
bool is_integer(uint4 /*v*/) { return true; }
bool is_integer(float2 /*v*/) { return false; }
bool is_integer(float3 /*v*/) { return false; }
bool is_integer(float4 /*v*/) { return false; }
bool is_integer(float2x2 /*v*/) { return false; }
bool is_integer(float2x3 /*v*/) { return false; }
bool is_integer(float2x4 /*v*/) { return false; }
bool is_integer(float3x2 /*v*/) { return false; }
bool is_integer(float3x3 /*v*/) { return false; }
bool is_integer(float3x4 /*v*/) { return false; }
bool is_integer(float4x2 /*v*/) { return false; }
bool is_integer(float4x3 /*v*/) { return false; }
bool is_integer(float4x4 /*v*/) { return false; }
int mat_row_len(float2x2 /*v*/) { return 2; }
int mat_row_len(float2x3 /*v*/) { return 3; }
int mat_row_len(float2x4 /*v*/) { return 4; }
int mat_row_len(float3x2 /*v*/) { return 2; }
int mat_row_len(float3x3 /*v*/) { return 3; }
int mat_row_len(float3x4 /*v*/) { return 4; }
int mat_row_len(float4x2 /*v*/) { return 2; }
int mat_row_len(float4x3 /*v*/) { return 3; }
int mat_row_len(float4x4 /*v*/) { return 4; }
int mat_col_len(float2x2 /*v*/) { return 2; }
int mat_col_len(float2x3 /*v*/) { return 2; }
int mat_col_len(float2x4 /*v*/) { return 2; }
int mat_col_len(float3x2 /*v*/) { return 3; }
int mat_col_len(float3x3 /*v*/) { return 3; }
int mat_col_len(float3x4 /*v*/) { return 3; }
int mat_col_len(float4x2 /*v*/) { return 4; }
int mat_col_len(float4x3 /*v*/) { return 4; }
int mat_col_len(float4x4 /*v*/) { return 4; }
int mat_col_len(int2 /*v*/) { return 2; }
int mat_col_len(int3 /*v*/) { return 3; }
int mat_col_len(int4 /*v*/) { return 4; }
int mat_col_len(uint2 /*v*/) { return 2; }
int mat_col_len(uint3 /*v*/) { return 3; }
int mat_col_len(uint4 /*v*/) { return 4; }
int mat_col_len(float2 /*v*/) { return 2; }
int mat_col_len(float3 /*v*/) { return 3; }
int mat_col_len(float4 /*v*/) { return 4; }
#ifndef GPU_METAL
uint to_type(bool /*v*/) { return TEST_TYPE_BOOL; }
#endif
uint to_type(uint /*v*/) { return TEST_TYPE_UINT; }
uint to_type(int /*v*/) { return TEST_TYPE_INT; }
uint to_type(float /*v*/) { return TEST_TYPE_FLOAT; }
uint to_type(int2 /*v*/) { return TEST_TYPE_IVEC2; }
uint to_type(int3 /*v*/) { return TEST_TYPE_IVEC3; }
uint to_type(int4 /*v*/) { return TEST_TYPE_IVEC4; }
uint to_type(uint2 /*v*/) { return TEST_TYPE_UVEC2; }
uint to_type(uint3 /*v*/) { return TEST_TYPE_UVEC3; }
uint to_type(uint4 /*v*/) { return TEST_TYPE_UVEC4; }
uint to_type(float2 /*v*/) { return TEST_TYPE_VEC2; }
uint to_type(float3 /*v*/) { return TEST_TYPE_VEC3; }
uint to_type(float4 /*v*/) { return TEST_TYPE_VEC4; }
uint to_type(float2x2 /*v*/) { return TEST_TYPE_MAT2X2; }
uint to_type(float2x3 /*v*/) { return TEST_TYPE_MAT2X3; }
uint to_type(float2x4 /*v*/) { return TEST_TYPE_MAT2X4; }
uint to_type(float3x2 /*v*/) { return TEST_TYPE_MAT3X2; }
uint to_type(float3x3 /*v*/) { return TEST_TYPE_MAT3X3; }
uint to_type(float3x4 /*v*/) { return TEST_TYPE_MAT3X4; }
uint to_type(float4x2 /*v*/) { return TEST_TYPE_MAT4X2; }
uint to_type(float4x3 /*v*/) { return TEST_TYPE_MAT4X3; }
uint to_type(float4x4 /*v*/) { return TEST_TYPE_MAT4X4; }
/* clang-format on */
#define WRITE_MATRIX(v) \
TestOutputRawData raw; \
for (int c = 0; c < mat_col_len(v); c++) { \
for (int r = 0; r < mat_row_len(v); r++) { \
raw.data[c * mat_row_len(v) + r] = floatBitsToUint(v[c][r]); \
} \
} \
return raw;
#define WRITE_FLOAT_VECTOR(v) \
TestOutputRawData raw; \
for (int c = 0; c < mat_col_len(v); c++) { \
raw.data[c] = floatBitsToUint(v[c]); \
} \
return raw;
#define WRITE_INT_VECTOR(v) \
TestOutputRawData raw; \
for (int c = 0; c < mat_col_len(v); c++) { \
raw.data[c] = uint(v[c]); \
} \
return raw;
#define WRITE_FLOAT_SCALAR(v) \
TestOutputRawData raw; \
raw.data[0] = floatBitsToUint(v); \
return raw;
#define WRITE_INT_SCALAR(v) \
TestOutputRawData raw; \
raw.data[0] = uint(v); \
return raw;
/* clang-format off */
#ifndef GPU_METAL
TestOutputRawData as_raw_data(bool v) { WRITE_INT_SCALAR(v); }
#endif
TestOutputRawData as_raw_data(uint v) { WRITE_INT_SCALAR(v); }
TestOutputRawData as_raw_data(int v) { WRITE_INT_SCALAR(v); }
TestOutputRawData as_raw_data(float v) { WRITE_FLOAT_SCALAR(v); }
TestOutputRawData as_raw_data(int2 v) { WRITE_INT_VECTOR(v); }
TestOutputRawData as_raw_data(int3 v) { WRITE_INT_VECTOR(v); }
TestOutputRawData as_raw_data(int4 v) { WRITE_INT_VECTOR(v); }
TestOutputRawData as_raw_data(uint2 v) { WRITE_INT_VECTOR(v); }
TestOutputRawData as_raw_data(uint3 v) { WRITE_INT_VECTOR(v); }
TestOutputRawData as_raw_data(uint4 v) { WRITE_INT_VECTOR(v); }
TestOutputRawData as_raw_data(float2 v) { WRITE_FLOAT_VECTOR(v); }
TestOutputRawData as_raw_data(float3 v) { WRITE_FLOAT_VECTOR(v); }
TestOutputRawData as_raw_data(float4 v) { WRITE_FLOAT_VECTOR(v); }
TestOutputRawData as_raw_data(float2x2 v) { WRITE_MATRIX(v); }
TestOutputRawData as_raw_data(float2x3 v) { WRITE_MATRIX(v); }
TestOutputRawData as_raw_data(float2x4 v) { WRITE_MATRIX(v); }
TestOutputRawData as_raw_data(float3x2 v) { WRITE_MATRIX(v); }
TestOutputRawData as_raw_data(float3x3 v) { WRITE_MATRIX(v); }
TestOutputRawData as_raw_data(float3x4 v) { WRITE_MATRIX(v); }
TestOutputRawData as_raw_data(float4x2 v) { WRITE_MATRIX(v); }
TestOutputRawData as_raw_data(float4x3 v) { WRITE_MATRIX(v); }
TestOutputRawData as_raw_data(float4x4 v) { WRITE_MATRIX(v); }
/* clang-format on */
#ifdef GPU_METAL
/* Vector comparison in MSL return a `bvec`. Collapse it like in GLSL. */
# define COLLAPSE_BOOL(OP) bool(all(OP))
#else
# undef COLLAPSE_BOOL /* Silence warning caused by define grepping in info files. */
# define COLLAPSE_BOOL(OP) (OP)
#endif
#define EXPECT_OP(OP, val1, val2) \
test_output(as_raw_data(val1), as_raw_data(val2), COLLAPSE_BOOL(OP), to_type(val1))
#define EXPECT_EQ(result, expect) EXPECT_OP((result) == (expect), result, expect)
#define EXPECT_NE(result, expect) EXPECT_OP((result) != (expect), result, expect)
#define EXPECT_LE(result, expect) EXPECT_OP((result) <= (expect), result, expect)
#define EXPECT_LT(result, expect) EXPECT_OP((result) < (expect), result, expect)
#define EXPECT_GE(result, expect) EXPECT_OP((result) >= (expect), result, expect)
#define EXPECT_GT(result, expect) EXPECT_OP((result) > (expect), result, expect)
#define EXPECT_TRUE(result) EXPECT_OP(result, result, true)
#define EXPECT_FALSE(result) EXPECT_OP(!result, result, false)
#define EXPECT_NEAR(result, expect, threshold) \
EXPECT_OP(is_equal(result, expect, threshold), result, expect)
struct ShaderTestOutput {
[[storage(0, write)]] TestOutput (&out_test)[];
};
#define TEST(a, b) if (true)

View File

@@ -0,0 +1,29 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
bool tiled_image_lookup(float3 &co, sampler2DArray ima, sampler1DArray map)
{
float2 tile_pos = floor(co.xy);
if (tile_pos.x < 0 || tile_pos.y < 0 || tile_pos.x >= 10) {
return false;
}
float tile = 10 * tile_pos.y + tile_pos.x;
if (tile >= textureSize(map, 0).x) {
return false;
}
/* Fetch tile information. */
float tile_layer = texelFetch(map, int2(tile, 0), 0).x;
if (tile_layer < 0) {
return false;
}
float4 tile_info = texelFetch(map, int2(tile, 1), 0);
co = float3(((co.xy - tile_pos) * tile_info.zw) + tile_info.xy, tile_layer);
return true;
}

View File

@@ -0,0 +1,134 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "gpu_shader_compat.hh"
#ifndef FLT_MAX
# define FLT_MAX uintBitsToFloat(0x7F7FFFFFu)
# define FLT_MIN uintBitsToFloat(0x00800000u)
# define FLT_EPSILON 1.192092896e-07F
#endif
#ifndef SHRT_MAX
# define SHRT_MAX 0x00007FFF
# define INT_MAX 0x7FFFFFFF
# define USHRT_MAX 0x0000FFFFu
# define UINT_MAX 0xFFFFFFFFu
#endif
#define NAN_FLT uintBitsToFloat(0x7FC00000u)
#define FLT_11_MAX uintBitsToFloat(0x477E0000)
#define FLT_10_MAX uintBitsToFloat(0x477C0000)
#define FLT_11_11_10_MAX float3(FLT_11_MAX, FLT_11_MAX, FLT_10_MAX)
#define UNPACK2(a) (a)[0], (a)[1]
#define UNPACK3(a) (a)[0], (a)[1], (a)[2]
#define UNPACK4(a) (a)[0], (a)[1], (a)[2], (a)[3]
/**
* Clamp input into [0..1] range.
*/
#define saturate(a) clamp(a, 0.0f, 1.0f)
#define isfinite(a) (!isinf(a) && !isnan(a))
/* clang-format off */
#define in_range_inclusive(val, min_v, max_v) (all(greaterThanEqual(val, min_v)) && all(lessThanEqual(val, max_v)))
#define in_range_exclusive(val, min_v, max_v) (all(greaterThan(val, min_v)) && all(lessThan(val, max_v)))
#define in_texture_range(texel, tex) (all(greaterThanEqual(texel, int2(0))) && all(lessThan(texel, textureSize(tex, 0).xy)))
#define in_image_range(texel, tex) (all(greaterThanEqual(texel, int2(0))) && all(lessThan(texel, imageSize(tex).xy)))
#define weighted_sum(val0, val1, val2, val3, weights) ((val0 * weights[0] + val1 * weights[1] + val2 * weights[2] + val3 * weights[3]) * safe_rcp(weights[0] + weights[1] + weights[2] + weights[3]))
#define weighted_sum_array(val, weights) ((val[0] * weights[0] + val[1] * weights[1] + val[2] * weights[2] + val[3] * weights[3]) * safe_rcp(weights[0] + weights[1] + weights[2] + weights[3]))
/* clang-format on */
bool flag_test(uint flag, uint val)
{
return (flag & val) != 0u;
}
bool flag_test(int flag, uint val)
{
return flag_test(uint(flag), val);
}
bool flag_test(int flag, int val)
{
return (flag & val) != 0;
}
void set_flag_from_test(uint &value, bool test, uint flag)
{
if (test) {
value |= flag;
}
else {
value &= ~flag;
}
}
void set_flag_from_test(int &value, bool test, int flag)
{
if (test) {
value |= flag;
}
else {
value &= ~flag;
}
}
/* Keep define to match C++ implementation. */
#define SET_FLAG_FROM_TEST(value, test, flag) set_flag_from_test(value, test, flag)
/**
* Return true if the bit inside bitmask at bit_index is set high.
* Assume the lower bits are inside first component of bitmask,
*/
bool bitmask64_test(uint2 bitmask, uint bit_index)
{
uint bitmask32 = (bit_index >= 32u) ? bitmask.y : bitmask.x;
return flag_test(bitmask32, 1u << (bit_index & 0x1Fu));
}
/**
* Pack two 16-bit uint into one 32-bit uint.
*/
uint packUvec2x16(uint2 a)
{
a = (a & 0xFFFFu) << uint2(0u, 16u);
return a.x | a.y;
}
uint2 unpackUvec2x16(uint a)
{
return (uint2(a) >> uint2(0u, 16u)) & uint2(0xFFFFu);
}
/**
* Pack four 8-bit uint into one 32-bit uint.
*/
uint packUvec4x8(uint4 a)
{
a = (a & 0xFFu) << uint4(0u, 8u, 16u, 24u);
return a.x | a.y | a.z | a.w;
}
uint4 unpackUvec4x8(uint a)
{
return (uint4(a) >> uint4(0u, 8u, 16u, 24u)) & uint4(0xFFu);
}
/**
* Convert from float representation to ordered int allowing min/max atomic operation.
* Based on: https://stackoverflow.com/a/31010352
*/
int floatBitsToOrderedInt(float value)
{
/* Floats can be sorted using their bits interpreted as integers for positive values.
* Negative values do not follow int's two's complement ordering which is reversed.
* So we have to XOR all bits except the sign bits in order to reverse the ordering.
* Note that this is highly hardware dependent, but there seems to be no case of GPU where the
* ints ares not two's complement. */
int int_value = floatBitsToInt(value);
return (int_value < 0) ? (int_value ^ 0x7FFFFFFF) : int_value;
}
float orderedIntBitsToFloat(int int_value)
{
return intBitsToFloat((int_value < 0) ? (int_value ^ 0x7FFFFFFF) : int_value);
}