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,54 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/globals.h"
#include "util/types_spectrum.h"
CCL_NAMESPACE_BEGIN
ccl_device float3 xyz_to_rgb(KernelGlobals kg, const float3 xyz)
{
return make_float3(dot(make_float3(kernel_data.film.xyz_to_r), xyz),
dot(make_float3(kernel_data.film.xyz_to_g), xyz),
dot(make_float3(kernel_data.film.xyz_to_b), xyz));
}
ccl_device float3 xyz_to_rgb_clamped(KernelGlobals kg, const float3 xyz)
{
return max(xyz_to_rgb(kg, xyz), zero_float3());
}
ccl_device float3 rec709_to_rgb(KernelGlobals kg, const float3 rec709)
{
return (kernel_data.film.is_rec709) ?
rec709 :
make_float3(dot(make_float3(kernel_data.film.rec709_to_r), rec709),
dot(make_float3(kernel_data.film.rec709_to_g), rec709),
dot(make_float3(kernel_data.film.rec709_to_b), rec709));
}
template<class T> ccl_device auto linear_rgb_to_gray(KernelGlobals kg, const T c)
{
return dot(c, make_float3(kernel_data.film.rgb_to_y));
}
ccl_device_inline Spectrum rgb_to_spectrum(const float3 rgb)
{
return rgb;
}
ccl_device_inline float3 spectrum_to_rgb(Spectrum s)
{
return s;
}
ccl_device float spectrum_to_gray(KernelGlobals kg, Spectrum c)
{
return linear_rgb_to_gray(kg, spectrum_to_rgb(c));
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,169 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
CCL_NAMESPACE_BEGIN
/* See "Tracing Ray Differentials", Homan Igehy, 1999. */
ccl_device void differential_transfer(ccl_private differential3 *surface_dP,
const differential3 ray_dP,
const float3 ray_D,
const differential3 ray_dD,
const float3 surface_Ng,
const float ray_t)
{
/* ray differential transfer through homogeneous medium, to
* compute dPdx/dy at a shading point from the incoming ray */
const float3 tmp = ray_D / dot(ray_D, surface_Ng);
const float3 tmpx = ray_dP.dx + ray_t * ray_dD.dx;
const float3 tmpy = ray_dP.dy + ray_t * ray_dD.dy;
surface_dP->dx = tmpx - dot(tmpx, surface_Ng) * tmp;
surface_dP->dy = tmpy - dot(tmpy, surface_Ng) * tmp;
}
ccl_device void differential_incoming(ccl_private differential3 *dI, const differential3 dD)
{
/* compute dIdx/dy at a shading point, we just need to negate the
* differential of the ray direction */
dI->dx = -dD.dx;
dI->dy = -dD.dy;
}
ccl_device void differential_dudv(ccl_private differential *du,
ccl_private differential *dv,
float3 dPdu,
float3 dPdv,
differential3 dP,
const float3 Ng)
{
/* now we have dPdx/dy from the ray differential transfer, and dPdu/dv
* from the primitive, we can compute dudx/dy and dvdx/dy. these are
* mainly used for differentials of arbitrary mesh attributes. */
/* find most stable axis to project to 2D */
const float xn = fabsf(Ng.x);
const float yn = fabsf(Ng.y);
const float zn = fabsf(Ng.z);
if (zn < xn || zn < yn) {
if (yn < xn || yn < zn) {
dPdu.x = dPdu.y;
dPdv.x = dPdv.y;
dP.dx.x = dP.dx.y;
dP.dy.x = dP.dy.y;
}
dPdu.y = dPdu.z;
dPdv.y = dPdv.z;
dP.dx.y = dP.dx.z;
dP.dy.y = dP.dy.z;
}
/* using Cramer's rule, we solve for dudx and dvdx in a 2x2 linear system,
* and the same for dudy and dvdy. the denominator is the same for both
* solutions, so we compute it only once.
*
* `dP.dx = dPdu * dudx + dPdv * dvdx;`
* `dP.dy = dPdu * dudy + dPdv * dvdy;` */
float det = (dPdu.x * dPdv.y - dPdv.x * dPdu.y);
if (det != 0.0f) {
det = 1.0f / det;
}
du->dx = (dP.dx.x * dPdv.y - dP.dx.y * dPdv.x) * det;
dv->dx = (dP.dx.y * dPdu.x - dP.dx.x * dPdu.y) * det;
du->dy = (dP.dy.x * dPdv.y - dP.dy.y * dPdv.x) * det;
dv->dy = (dP.dy.y * dPdu.x - dP.dy.x * dPdu.y) * det;
}
ccl_device differential differential_zero()
{
differential d;
d.dx = 0.0f;
d.dy = 0.0f;
return d;
}
ccl_device differential3 differential3_zero()
{
differential3 d;
d.dx = zero_float3();
d.dy = zero_float3();
return d;
}
/* Compact ray differentials that are just a radius to reduce memory usage and access cost
* on GPUs, basically cone tracing.
*
* See above for more accurate reference implementations of ray differentials. */
ccl_device_forceinline float differential_zero_compact()
{
return 0.0f;
}
ccl_device_forceinline float differential_make_compact(const float dD)
{
return dD;
}
ccl_device_forceinline float differential_make_compact(const differential3 dD)
{
return 0.5f * (len(dD.dx) + len(dD.dy));
}
ccl_device_forceinline float differential_make_compact(const dual3 D)
{
return 0.5f * (len(D.dx) + len(D.dy));
}
ccl_device_forceinline float differential_incoming_compact(const float dD)
{
return dD;
}
ccl_device_forceinline float differential_transfer_compact(const float ray_dP,
const float3 /* ray_D */,
const float ray_dD,
const float ray_t)
{
return ray_dP + ray_t * ray_dD;
}
ccl_device_forceinline differential3 differential_from_compact(const float3 D, const float dD)
{
float3 dx;
float3 dy;
make_orthonormals(D, &dx, &dy);
differential3 d;
d.dx = dD * dx;
d.dy = dD * dy;
return d;
}
ccl_device void differential_dudv_compact(ccl_private differential *du,
ccl_private differential *dv,
const float3 dPdu,
const float3 dPdv,
const float dP,
const float3 Ng)
{
/* TODO: can we speed this up? */
differential_dudv(du, dv, dPdu, dPdv, differential_from_compact(Ng, dP), Ng);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,145 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/globals.h"
CCL_NAMESPACE_BEGIN
/* IES Light */
ccl_device_inline float interpolate_ies_vertical(KernelGlobals kg,
const int ofs,
const bool wrap_vlow,
const bool wrap_vhigh,
const int v,
const int v_num,
const float v_frac,
const int h)
{
/* Since lookups are performed in spherical coordinates, clamping the coordinates at the low end
* of v (corresponding to the north pole) would result in artifacts. The proper way of dealing
* with this would be to lookup the corresponding value on the other side of the pole, but since
* the horizontal coordinates might be nonuniform, this would require yet another interpolation.
* Therefore, the assumption is made that the light is going to be symmetrical, which means that
* we can just take the corresponding value at the current horizontal coordinate. */
#define IES_LOOKUP(v) kernel_data_fetch(ies, ofs + h * v_num + (v))
/* Look up the inner two points directly. */
const float c = IES_LOOKUP(v + 1);
const float b = IES_LOOKUP(v);
/* Look up first point, or fall back to second point if not available. */
float a = b;
if (v > 0) {
a = IES_LOOKUP(v - 1);
}
else if (wrap_vlow) {
a = IES_LOOKUP(1);
}
/* Look up last point, or fall back to third point if not available. */
float d = c;
if (v + 2 < v_num) {
d = IES_LOOKUP(v + 2);
}
else if (wrap_vhigh) {
d = IES_LOOKUP(v_num - 2);
}
#undef IES_LOOKUP
return cubic_interp(a, b, c, d, v_frac);
}
ccl_device_inline float kernel_ies_interp(KernelGlobals kg,
const int slot,
const float h_angle,
const float v_angle)
{
/* Find offset of the IES data in the table. */
int ofs = __float_as_int(kernel_data_fetch(ies, slot));
if (ofs == -1) {
return 100.0f;
}
const int h_num = __float_as_int(kernel_data_fetch(ies, ofs++));
const int v_num = __float_as_int(kernel_data_fetch(ies, ofs++));
#define IES_LOOKUP_ANGLE_H(h) kernel_data_fetch(ies, ofs + (h))
#define IES_LOOKUP_ANGLE_V(v) kernel_data_fetch(ies, ofs + h_num + (v))
/* Check whether the angle is within the bounds of the IES texture. */
const float v_low = IES_LOOKUP_ANGLE_V(0);
const float v_high = IES_LOOKUP_ANGLE_V(v_num - 1);
const float h_low = IES_LOOKUP_ANGLE_H(0);
const float h_high = IES_LOOKUP_ANGLE_H(h_num - 1);
if (v_angle < v_low || v_angle >= v_high) {
return 0.0f;
}
if (h_angle < h_low || h_angle >= h_high) {
return 0.0f;
}
/* If the texture covers the full 360° range horizontally, wrap around the lookup
* to get proper cubic interpolation. Otherwise, just set the out-of-range values to zero.
* Similar logic for V, but there we check the lower and upper wrap separately. */
const bool wrap_h = (h_low < 1e-7f && h_high > M_2PI_F - 1e-7f);
const bool wrap_vlow = (v_low < 1e-7f);
const bool wrap_vhigh = (v_high > M_PI_F - 1e-7f);
/* Lookup the angles to find the table position. */
int h_i;
int v_i;
/* TODO(lukas): Consider using bisection.
* Probably not worth it for the vast majority of IES files. */
for (h_i = 0; IES_LOOKUP_ANGLE_H(h_i + 1) < h_angle; h_i++) {
;
}
for (v_i = 0; IES_LOOKUP_ANGLE_V(v_i + 1) < v_angle; v_i++) {
;
}
const float h_frac = inverse_lerp(IES_LOOKUP_ANGLE_H(h_i), IES_LOOKUP_ANGLE_H(h_i + 1), h_angle);
const float v_frac = inverse_lerp(IES_LOOKUP_ANGLE_V(v_i), IES_LOOKUP_ANGLE_V(v_i + 1), v_angle);
#undef IES_LOOKUP_ANGLE_H
#undef IES_LOOKUP_ANGLE_V
/* Skip forward to the actual intensity data. */
ofs += h_num + v_num;
/* Interpolate the inner two points directly. */
const float b = interpolate_ies_vertical(
kg, ofs, wrap_vlow, wrap_vhigh, v_i, v_num, v_frac, h_i);
const float c = interpolate_ies_vertical(
kg, ofs, wrap_vlow, wrap_vhigh, v_i, v_num, v_frac, h_i + 1);
/* Interpolate first point, or fall back to second point if not available. */
float a = b;
if (h_i > 0) {
a = interpolate_ies_vertical(kg, ofs, wrap_vlow, wrap_vhigh, v_i, v_num, v_frac, h_i - 1);
}
else if (wrap_h) {
/* The last entry (360°) equals the first one, so we need to wrap around to the one before. */
a = interpolate_ies_vertical(kg, ofs, wrap_vlow, wrap_vhigh, v_i, v_num, v_frac, h_num - 2);
}
/* Interpolate last point, or fall back to second point if not available. */
float d = b;
if (h_i + 2 < h_num) {
d = interpolate_ies_vertical(kg, ofs, wrap_vlow, wrap_vhigh, v_i, v_num, v_frac, h_i + 2);
}
else if (wrap_h) {
/* Same logic here, wrap around to the second element if necessary. */
d = interpolate_ies_vertical(kg, ofs, wrap_vlow, wrap_vhigh, v_i, v_num, v_frac, 1);
}
/* Cubic interpolation can result in negative values, so get rid of them. */
return max(cubic_interp(a, b, c, d, h_frac), 0.0f);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,192 @@
/* SPDX-FileCopyrightText: 2011-2026 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/globals.h"
#include "kernel/sample/lcg.h"
#include "util/atomic.h"
#include "util/defines.h"
#include "util/math_fast.h"
#include "util/types_image.h"
CCL_NAMESPACE_BEGIN
ccl_device_forceinline int kernel_image_udim_map(KernelGlobals kg,
const int id,
ccl_private float2 &uv)
{
if (id >= 0) {
return id;
}
const int tx = (int)uv.x;
const int ty = (int)uv.y;
if (tx < 0 || ty < 0 || tx >= 10) {
return KERNEL_IMAGE_NONE;
}
int udim_id = -id - 1;
const int num_udims = kernel_data_fetch(image_texture_udims, udim_id++).tile;
const int tile = 1001 + 10 * ty + tx;
for (int i = 0; i < num_udims; i++) {
const KernelImageUDIM udim = kernel_data_fetch(image_texture_udims, udim_id++);
if (udim.tile == tile) {
/* If we found the tile, offset the UVs to be relative to it. */
uv.x -= tx;
uv.y -= ty;
return udim.image_texture_id;
}
}
return KERNEL_IMAGE_NONE;
}
ccl_device_forceinline bool kernel_image_tile_wrap(const ExtensionType extension,
ccl_private float2 &uv)
{
/* Wrapping. */
switch (extension) {
case EXTENSION_REPEAT:
uv = uv - floor(uv);
return true;
case EXTENSION_CLIP:
return (uv.x >= 0.0f && uv.x <= 1.0f && uv.y >= 0.0f && uv.y <= 1.0f);
case EXTENSION_EXTEND:
uv = clamp(uv, zero_float2(), one_float2());
return true;
case EXTENSION_MIRROR: {
const float2 t = uv * 0.5f;
uv = 2.0f * (t - floor(t));
uv = select(uv >= one_float2(), 2.0f * one_float2() - uv, uv);
return true;
}
default:
break;
}
return false;
}
/* From UV coordinates in 0..1 range, compute tile and pixel coordinates. */
ccl_device_forceinline KernelTileDescriptor
kernel_image_tile_map(KernelGlobals kg,
ccl_private ShaderData *sd,
const ccl_global KernelImageTexture &tex,
const uint image_texture_id,
const dual2 uv,
ccl_private float2 &xy)
{
/* Find mipmap level. Use squared lengths to avoid two sqrt operations,
* compensating with 0.5 factor on the log2. */
const float dudxy_sq = len_squared(make_float2(uv.dx.x, uv.dy.x)) * float(tex.width * tex.width);
const float dvdxy_sq = len_squared(make_float2(uv.dx.y, uv.dy.y)) *
float(tex.height * tex.height);
/* Limit max anisotropy ratio, to avoid loading too high mip resolutions
* for stretched UV coordinates, which don't really benefit from it anyway. */
const float maxdxy_sq = max(dudxy_sq, dvdxy_sq);
const float mindxy_sq = min(dudxy_sq, dvdxy_sq);
const float inv_aniso_ratio_sq = 1.0f / (16.0f * 16.0f);
/* Native log2 is faster on GPU. */
#ifdef __KERNEL_GPU__
float flevel = 0.5f * log2(max(mindxy_sq, maxdxy_sq * inv_aniso_ratio_sq));
#else
float flevel = 0.5f * fast_log2f(max(mindxy_sq, maxdxy_sq * inv_aniso_ratio_sq));
#endif
/* Select mipmap level. */
if (sd->lcg_state != 0) {
/* For rounding instead of flooring. */
flevel += 0.5f;
/* Randomize mip level, except for some cases like displacement or importance map. */
const float transition = 0.5f;
flevel += (lcg_step_float(&sd->lcg_state) - 0.5f) * transition;
}
else {
/* When not using stochastic interpolation, round to higher level. */
}
flevel += kernel_data.image.mip_bias;
const int level = clamp(int(flevel), 0, tex.tile_levels - 1);
/* Compute width of this mipmap level. */
const int width = max(1, tex.width >> level);
const int height = max(1, tex.height >> level);
/* Convert coordinates to pixel space.
* Flip Y convention for tiles to match tx files. */
xy = make_float2(uv.val.x * width, (1.0f - uv.val.y) * height);
/* Tile mapping */
const int ix = clamp((int)xy.x, 0, width - 1);
const int iy = clamp((int)xy.y, 0, height - 1);
const int tile_size_shift = tex.tile_size_shift;
const int tile_size_padded = (1 << tile_size_shift) + KERNEL_IMAGE_TEX_PADDING * 2;
const int tile_x = ix >> tile_size_shift;
const int tile_y = iy >> tile_size_shift;
const int tile_offset = kernel_data_fetch(image_texture_tile_descriptors,
tex.tile_descriptor_offset + level) +
tile_x + tile_y * divide_up_by_shift(width, tile_size_shift);
KernelTileDescriptor tile_descriptor = kernel_data_fetch(
image_texture_tile_descriptors, tex.tile_descriptor_offset + tile_offset);
const uint access_index = tex.tile_descriptor_offset + tile_offset;
if (!kernel_tile_descriptor_loaded(tile_descriptor)) {
#ifdef __KERNEL_GPU__
/* For GPU, mark load requested and cancel shader execution. */
if (tile_descriptor == KERNEL_TILE_LOAD_NONE) {
tile_descriptor = KERNEL_TILE_LOAD_REQUEST;
/* Write load request for quick rejection of subsequent reads. */
kernel_data_write(image_texture_tile_descriptors,
tex.tile_descriptor_offset + tile_offset,
tile_descriptor);
/* Set access state that will be read back to host. Using a byte
* instead of a bitmask avoids the need for atomics. */
kernel_data_array(
image_texture_tile_access_state)[access_index] = KERNEL_TILE_ACCESS_REQUESTED;
}
if (tile_descriptor == KERNEL_TILE_LOAD_REQUEST) {
sd->flag |= SD_CACHE_MISS;
}
return tile_descriptor;
#else
/* For CPU, load tile immediately. */
if (tile_descriptor != KERNEL_TILE_LOAD_FAILED) {
KernelTileDescriptor &p_tile_descriptor =
kg->image_texture_tile_descriptors.data[access_index];
kg->image_load_requested_cpu(image_texture_id,
level,
tile_x << tile_size_shift,
tile_y << tile_size_shift,
p_tile_descriptor);
tile_descriptor = p_tile_descriptor;
}
if (!kernel_tile_descriptor_loaded(tile_descriptor)) {
return tile_descriptor;
}
#endif
}
/* Mark tile as used for cache eviction tracking. Read before writing as we
* expect most of the time this was already written. */
if (kernel_data_array(image_texture_tile_access_state)[access_index] != KERNEL_TILE_ACCESS_USED)
{
kernel_data_array(image_texture_tile_access_state)[access_index] = KERNEL_TILE_ACCESS_USED;
}
/* Remap coordinates into tiled image space. */
const int offset = kernel_tile_descriptor_offset(tile_descriptor);
xy += make_float2(KERNEL_IMAGE_TEX_PADDING - (tile_x << tile_size_shift) +
offset * tile_size_padded,
KERNEL_IMAGE_TEX_PADDING - (tile_y << tile_size_shift));
return tile_descriptor;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,266 @@
/* SPDX-FileCopyrightText: 2011-2025 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/globals.h"
#include "kernel/sample/lcg.h"
#include "util/types_image.h"
#if !defined(__KERNEL_METAL__) && !defined(__KERNEL_ONEAPI__)
# ifdef WITH_NANOVDB
# include "kernel/util/nanovdb.h"
# endif
#endif
CCL_NAMESPACE_BEGIN
#ifndef __KERNEL_GPU__
/* Make template functions private so symbols don't conflict between kernels with different
* instruction sets. */
namespace {
#endif
#ifdef WITH_NANOVDB
/* Cubic interpolation weights. */
ccl_device_forceinline void fill_cubic_weights(float3 w[4], float3 t)
{
w[0] = (((-1.0f / 6.0f) * t + 0.5f) * t - 0.5f) * t + (1.0f / 6.0f);
w[1] = ((0.5f * t - 1.0f) * t) * t + (2.0f / 3.0f);
w[2] = ((-0.5f * t + 0.5f) * t + 0.5f) * t + (1.0f / 6.0f);
w[3] = (1.0f / 6.0f) * t * t * t;
}
/* -------------------------------------------------------------------- */
/** Return the sample position for stochastical one-tap sampling.
* From "Stochastic Texture Filtering": https://arxiv.org/abs/2305.05810
* \{ */
ccl_device_inline float3 interp_tricubic_stochastic(const float3 P, ccl_private float3 &rand)
{
const float3 p = floor(P);
const float3 t = P - p;
float3 w[4];
fill_cubic_weights(w, t);
/* For reservoir sampling, always accept the first in the stream. */
float3 total_weight = w[0];
float3 offset = make_float3(-1.0f);
for (int j = 1; j < 4; j++) {
total_weight += w[j];
const float3 thresh = w[j] / total_weight;
const auto mask = rand < thresh;
offset = select(mask, make_float3(float(j) - 1.0f), offset);
rand = select(mask, safe_divide(rand, thresh), safe_divide(rand - thresh, 1.0f - thresh));
}
return p + offset;
}
ccl_device_inline float3 interp_trilinear_stochastic(const float3 P, const float3 rand)
{
const float3 p = floor(P);
const float3 t = P - p;
return select(rand < t, p + 1.0f, p);
}
ccl_device_inline float3 interp_stochastic(const float3 P,
ccl_private InterpolationType &interpolation,
ccl_private float3 &rand)
{
float3 P_new = P;
if (interpolation == INTERPOLATION_CUBIC) {
P_new = interp_tricubic_stochastic(P, rand);
}
else if (interpolation == INTERPOLATION_LINEAR) {
P_new = interp_trilinear_stochastic(P, rand);
}
else {
kernel_assert(interpolation == INTERPOLATION_CLOSEST);
}
interpolation = INTERPOLATION_CLOSEST;
return P_new;
}
/** \} */
template<typename OutT, typename Acc>
ccl_device OutT kernel_image_interp_trilinear_nanovdb(ccl_private Acc &acc, const float3 P)
{
const float3 floor_P = floor(P);
const float3 t = P - floor_P;
const int3 index = make_int3(floor_P);
const int ix = index.x;
const int iy = index.y;
const int iz = index.z;
return mix(mix(mix(OutT(acc.getValue(make_int3(ix, iy, iz))),
OutT(acc.getValue(make_int3(ix, iy, iz + 1))),
t.z),
mix(OutT(acc.getValue(make_int3(ix, iy + 1, iz + 1))),
OutT(acc.getValue(make_int3(ix, iy + 1, iz))),
1.0f - t.z),
t.y),
mix(mix(OutT(acc.getValue(make_int3(ix + 1, iy + 1, iz))),
OutT(acc.getValue(make_int3(ix + 1, iy + 1, iz + 1))),
t.z),
mix(OutT(acc.getValue(make_int3(ix + 1, iy, iz + 1))),
OutT(acc.getValue(make_int3(ix + 1, iy, iz))),
1.0f - t.z),
1.0f - t.y),
t.x);
}
template<typename OutT, typename Acc>
ccl_device OutT kernel_image_interp_tricubic_nanovdb(ccl_private Acc &acc, const float3 P)
{
# if defined(__KERNEL_HIP__)
/* Explicitly unroll for HIP compiler to unroll the loop. Without this the render result is wrong
* on a specific platform/compiler combinations. ALso don't rely on the `unroll` hint as it has
* a performance impact. See #152126 and discussion/benchmark in !152321. */
const float3 floor_P = floor(P);
const float3 t = P - floor_P;
const int3 index = make_int3(floor_P);
const int xc[4] = {index.x - 1, index.x, index.x + 1, index.x + 2};
const int yc[4] = {index.y - 1, index.y, index.y + 1, index.y + 2};
const int zc[4] = {index.z - 1, index.z, index.z + 1, index.z + 2};
float3 weight[4];
fill_cubic_weights(weight, t);
# define DATA(x, y, z) (OutT(acc.getValue(make_int3(xc[x], yc[y], zc[z]))))
# define COL_TERM(col, row) \
(weight[col].y * (weight[0].x * DATA(0, col, row) + weight[1].x * DATA(1, col, row) + \
weight[2].x * DATA(2, col, row) + weight[3].x * DATA(3, col, row)))
# define ROW_TERM(row) \
(weight[row].z * (COL_TERM(0, row) + COL_TERM(1, row) + COL_TERM(2, row) + COL_TERM(3, row)))
/* Actual interpolation. */
return ROW_TERM(0) + ROW_TERM(1) + ROW_TERM(2) + ROW_TERM(3);
# undef COL_TERM
# undef ROW_TERM
# undef DATA
# else
const float3 floor_P = floor(P);
const float3 t = P - floor_P;
const int3 index = make_int3(floor_P) - make_int3(1);
float3 w[4];
fill_cubic_weights(w, t);
OutT result = make_zero<OutT>();
for (int k = 0; k < 4; k++) {
OutT col_term_acc = make_zero<OutT>();
for (int j = 0; j < 4; j++) {
col_term_acc += w[j].y * (w[0].x * (OutT(acc.getValue(index + make_int3(0, j, k)))) +
w[1].x * (OutT(acc.getValue(index + make_int3(1, j, k)))) +
w[2].x * (OutT(acc.getValue(index + make_int3(2, j, k)))) +
w[3].x * (OutT(acc.getValue(index + make_int3(3, j, k)))));
}
result += w[k].z * col_term_acc;
}
return result;
# endif
}
template<typename OutT, typename T>
# if defined(__KERNEL_METAL__)
__attribute__((noinline))
# else
ccl_device_noinline
# endif
OutT kernel_image_interp_nanovdb(const ccl_global KernelImageInfo &info,
float3 P,
const InterpolationType interp)
{
ccl_global nanovdb::NanoGrid<T> *const grid = (ccl_global nanovdb::NanoGrid<T> *)info.data;
if (interp == INTERPOLATION_CLOSEST) {
nanovdb::ReadAccessor<T> acc(grid->tree().root());
return OutT(acc.getValue(make_int3(floor(P))));
}
nanovdb::CachedReadAccessor<T> acc(grid->tree().root());
if (interp == INTERPOLATION_LINEAR) {
return kernel_image_interp_trilinear_nanovdb<OutT>(acc, P);
}
return kernel_image_interp_tricubic_nanovdb<OutT>(acc, P);
}
#endif /* WITH_NANOVDB */
ccl_device float4 kernel_image_interp_3d(KernelGlobals kg,
ccl_private ShaderData *sd,
const int image_texture_id,
float3 P,
InterpolationType interp,
const bool stochastic)
{
#ifdef WITH_NANOVDB
const ccl_global KernelImageTexture &tex = kernel_data_fetch(image_textures, image_texture_id);
const ccl_global KernelImageInfo &info = kernel_data_fetch(image_info, tex.image_info_id);
if (tex.use_transform_3d) {
P = transform_point(&tex.transform_3d, P);
}
InterpolationType interpolation = (interp == INTERPOLATION_NONE) ?
(InterpolationType)info.interpolation :
interp;
if (stochastic) {
float3 rand = lcg_step_float3(&sd->lcg_state);
P = interp_stochastic(P, interpolation, rand);
}
const ImageDataType data_type = (ImageDataType)info.data_type;
if (data_type == IMAGE_DATA_TYPE_NANOVDB_FLOAT) {
const float f = kernel_image_interp_nanovdb<float, float>(info, P, interpolation);
return make_float4(f, f, f, 1.0f);
}
if (data_type == IMAGE_DATA_TYPE_NANOVDB_FLOAT3) {
const float3 f = kernel_image_interp_nanovdb<float3, packed_float3>(info, P, interpolation);
return make_float4(f, 1.0f);
}
if (data_type == IMAGE_DATA_TYPE_NANOVDB_FLOAT4) {
return kernel_image_interp_nanovdb<float4, float4>(info, P, interpolation);
}
if (data_type == IMAGE_DATA_TYPE_NANOVDB_FPN) {
const float f = kernel_image_interp_nanovdb<float, nanovdb::FpN>(info, P, interpolation);
return make_float4(f, f, f, 1.0f);
}
if (data_type == IMAGE_DATA_TYPE_NANOVDB_FP16) {
const float f = kernel_image_interp_nanovdb<float, nanovdb::Fp16>(info, P, interpolation);
return make_float4(f, f, f, 1.0f);
}
if (data_type == IMAGE_DATA_TYPE_NANOVDB_EMPTY) {
return zero_float4();
}
#else
(void)kg;
(void)sd;
(void)image_texture_id;
(void)P;
(void)interp;
(void)stochastic;
#endif
return IMAGE_MISSING_RGBA;
}
#ifndef __KERNEL_GPU__
} /* Namespace. */
#endif
CCL_NAMESPACE_END

View File

@@ -0,0 +1,74 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/globals.h"
#include "kernel/types.h"
CCL_NAMESPACE_BEGIN
/* Interpolated lookup table access */
ccl_device float lookup_table_read(KernelGlobals kg, float x, const int offset, const int size)
{
x = saturatef(x) * (size - 1);
const int index = min(float_to_int(x), size - 1);
const int nindex = min(index + 1, size - 1);
const float t = x - index;
const float data0 = kernel_data_fetch(lookup_table, index + offset);
if (t == 0.0f) {
return data0;
}
const float data1 = kernel_data_fetch(lookup_table, nindex + offset);
return (1.0f - t) * data0 + t * data1;
}
ccl_device float lookup_table_read_2D(
KernelGlobals kg, const float x, float y, const int offset, const int xsize, const int ysize)
{
y = saturatef(y) * (ysize - 1);
const int index = min(float_to_int(y), ysize - 1);
const int nindex = min(index + 1, ysize - 1);
const float t = y - index;
const float data0 = lookup_table_read(kg, x, offset + xsize * index, xsize);
if (t == 0.0f) {
return data0;
}
const float data1 = lookup_table_read(kg, x, offset + xsize * nindex, xsize);
return (1.0f - t) * data0 + t * data1;
}
ccl_device float lookup_table_read_3D(KernelGlobals kg,
const float x,
float y,
float z,
const int offset,
const int xsize,
const int ysize,
const int zsize)
{
z = saturatef(z) * (zsize - 1);
const int index = min(float_to_int(z), zsize - 1);
const int nindex = min(index + 1, zsize - 1);
const float t = z - index;
const float data0 = lookup_table_read_2D(kg, x, y, offset + xsize * ysize * index, xsize, ysize);
if (t == 0.0f) {
return data0;
}
const float data1 = lookup_table_read_2D(
kg, x, y, offset + xsize * ysize * nindex, xsize, ysize);
return (1.0f - t) * data0 + t * data1;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,436 @@
/* SPDX-FileCopyrightText: 2020-2021 Contributors to the OpenVDB Project
* SPDX-FileCopyrightText: 2023-2025 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0
*
* This is an extract from NanoVDB.h, with minimal code needed for kernel side access to grids. The
* original headers are not compatible with Metal due to missing address space qualifiers. */
#pragma once
#include "util/defines.h"
#include "util/math_int3.h"
#include "util/types_base.h"
#include "util/types_int3.h"
#ifndef __KERNEL_GPU__
# include <climits>
#endif
CCL_NAMESPACE_BEGIN
#define NANOVDB_USE_SINGLE_ROOT_KEY
#define NANOVDB_DATA_ALIGNMENT 32 // NOLINT
namespace nanovdb {
/* Utilities */
template<typename DstT, typename SrcT>
const ccl_device ccl_global DstT *PtrAdd(const ccl_global SrcT *p, int64_t offset)
{
return reinterpret_cast<const ccl_global DstT *>(reinterpret_cast<const ccl_global char *>(p) +
offset);
}
/* Coord */
using Coord = int3;
using PackedCoord = packed_int3;
/* Mask */
template<uint32_t LOG2DIM> struct Mask {
ccl_static_constexpr uint32_t SIZE = 1U << (3 * LOG2DIM);
ccl_static_constexpr uint32_t WORD_COUNT = SIZE >> 6;
uint64_t mWords[WORD_COUNT];
ccl_device_inline_method bool isOff(const uint32_t n) const ccl_global
{
return 0 == (mWords[n >> 6] & (uint64_t(1) << (n & 63)));
}
};
/* Grid */
template<typename TreeT> struct alignas(NANOVDB_DATA_ALIGNMENT) Grid {
ccl_static_constexpr int MaxNameSize = 256;
uint64_t mMagic;
uint64_t mChecksum;
uint32_t mVersion;
uint32_t mFlags;
uint32_t mGridIndex;
uint32_t mGridCount;
uint64_t mGridSize;
char mGridName[MaxNameSize];
uint8_t mMap[264];
uint8_t mWorldBBox[48]; // double[6], but no doubles in Metal
uint8_t mVoxelSize[24]; // double[3], but no doubles in Metal
uint32_t mGridClass;
uint32_t mGridType;
uint32_t mData0;
uint64_t mData1, mData2;
using BuildType = typename TreeT::BuildType;
const ccl_device_inline_method ccl_global TreeT &tree() const ccl_global
{
return *reinterpret_cast<const ccl_global TreeT *>(this + 1);
}
};
/* Tree */
template<typename RootT> struct alignas(NANOVDB_DATA_ALIGNMENT) Tree {
int64_t mNodeOffset[4];
uint32_t mNodeCount[3];
uint32_t mTileCount[3];
uint64_t mVoxelCount;
using ValueType = typename RootT::ValueType;
using BuildType = typename RootT::BuildType;
const ccl_device_inline_method ccl_global RootT &root() const ccl_global
{
return *reinterpret_cast<const ccl_global RootT *>(
mNodeOffset[3] ? PtrAdd<uint8_t>(this, mNodeOffset[3]) : nullptr);
}
};
/* RootNode */
template<typename ChildT> struct alignas(NANOVDB_DATA_ALIGNMENT) RootNode {
using ValueType = typename ChildT::ValueType;
using BuildType = typename ChildT::BuildType;
#ifdef NANOVDB_USE_SINGLE_ROOT_KEY
using KeyT = uint64_t;
static ccl_device_inline_method uint64_t CoordToKey(const Coord ijk)
{
return (uint64_t(uint32_t(ijk.z) >> ChildT::TOTAL)) |
(uint64_t(uint32_t(ijk.y) >> ChildT::TOTAL) << 21) |
(uint64_t(uint32_t(ijk.x) >> ChildT::TOTAL) << 42);
}
#else
using KeyT = Coord;
static ccl_device_inline_method Coord CoordToKey(const CoordT ijk)
{
return ijk & ~ChildT::MASK;
}
#endif
PackedCoord mBBox[2];
uint32_t mTableSize;
ValueType mBackground;
ValueType mMinimum;
ValueType mMaximum;
float mAverage;
float mStdDevi;
struct alignas(NANOVDB_DATA_ALIGNMENT) Tile {
KeyT key;
int64_t child;
uint32_t state;
ValueType value;
};
const ccl_device_inline_method ccl_global Tile *probeTile(const Coord ijk) const ccl_global
{
const auto key = CoordToKey(ijk);
const ccl_global Tile *p = reinterpret_cast<const ccl_global Tile *>(this + 1);
const ccl_global Tile *q = p + mTableSize;
for (; p < q; ++p) {
if (p->key == key) {
return p;
}
}
return nullptr;
}
const ccl_device_inline_method ccl_global ChildT *getChild(const ccl_global Tile *tile) const
ccl_global
{
return PtrAdd<ChildT>(this, tile->child);
}
ccl_static_constexpr uint32_t LEVEL = 1 + ChildT::LEVEL;
};
/* InternalNode */
template<typename ChildT, const uint32_t Log2Dim = ChildT::LOG2DIM + 1>
struct alignas(NANOVDB_DATA_ALIGNMENT) InternalNode {
using ValueType = typename ChildT::ValueType;
using BuildType = typename ChildT::BuildType;
union Tile {
ValueType value;
int64_t child;
};
PackedCoord mBBox[2];
uint64_t mFlags;
Mask<Log2Dim> mValueMask;
Mask<Log2Dim> mChildMask;
ValueType mMinimum;
ValueType mMaximum;
float mAverage;
float mStdDevi;
alignas(32) Tile mTable[1u << (3 * Log2Dim)];
const ccl_device_inline_method ccl_global ChildT *getChild(const uint32_t n) const ccl_global
{
return PtrAdd<ChildT>(this, mTable[n].child);
}
ccl_static_constexpr uint32_t LOG2DIM = Log2Dim;
ccl_static_constexpr uint32_t TOTAL = LOG2DIM + ChildT::TOTAL;
ccl_static_constexpr uint32_t DIM = 1u << TOTAL;
ccl_static_constexpr uint32_t SIZE = 1u << (3 * LOG2DIM);
ccl_static_constexpr uint32_t MASK = (1u << TOTAL) - 1u;
ccl_static_constexpr uint32_t LEVEL = 1 + ChildT::LEVEL;
static ccl_device_inline_method uint32_t CoordToOffset(const Coord ijk)
{
return (((ijk.x & MASK) >> ChildT::TOTAL) << (2 * LOG2DIM)) |
(((ijk.y & MASK) >> ChildT::TOTAL) << (LOG2DIM)) | ((ijk.z & MASK) >> ChildT::TOTAL);
}
};
/* LeafData */
template<typename ValueT, const uint32_t LOG2DIM> struct alignas(NANOVDB_DATA_ALIGNMENT) LeafData {
using ValueType = ValueT;
using BuildType = ValueT;
PackedCoord mBBoxMin;
uint8_t mBBoxDif[3];
uint8_t mFlags;
Mask<LOG2DIM> mValueMask;
ValueType mMinimum;
ValueType mMaximum;
float mAverage;
float mStdDevi;
alignas(32) ValueType mValues[1u << 3 * LOG2DIM];
ccl_device_inline_method ValueType getValue(const uint32_t i) const ccl_global
{
return mValues[i];
}
};
/* LeafFnBase */
template<uint32_t LOG2DIM> struct alignas(NANOVDB_DATA_ALIGNMENT) LeafFnBase {
PackedCoord mBBoxMin;
uint8_t mBBoxDif[3];
uint8_t mFlags;
Mask<LOG2DIM> mValueMask;
float mMinimum;
float mQuantum;
uint16_t mMin, mMax, mAvg, mDev;
};
/* LeafData<Fp16> */
class Fp16 {};
template<uint32_t LOG2DIM> struct alignas(NANOVDB_DATA_ALIGNMENT) LeafData<Fp16, LOG2DIM> {
using ValueType = float;
using BuildType = Fp16;
LeafFnBase<LOG2DIM> base;
alignas(32) uint16_t mCode[1u << 3 * LOG2DIM];
ccl_device_inline_method float getValue(const uint32_t i) const ccl_global
{
return mCode[i] * base.mQuantum + base.mMinimum;
}
};
/* LeafData<FpN> */
class FpN {};
template<uint32_t LOG2DIM> struct alignas(NANOVDB_DATA_ALIGNMENT) LeafData<FpN, LOG2DIM> {
using ValueType = float;
using BuildType = FpN;
LeafFnBase<LOG2DIM> base;
ccl_device_inline_method float getValue(const uint32_t i) const ccl_global
{
const int b = base.mFlags >> 5;
uint32_t code = reinterpret_cast<const ccl_global uint32_t *>(this + 1)[i >> (5 - b)];
code >>= (i & ((32 >> b) - 1)) << b;
code &= (1 << (1 << b)) - 1;
return float(code) * base.mQuantum + base.mMinimum;
}
};
/* LeafNode */
template<typename BuildT, const uint32_t Log2Dim = 3>
struct alignas(NANOVDB_DATA_ALIGNMENT) LeafNode {
using DataType = LeafData<BuildT, Log2Dim>;
using ValueType = typename DataType::ValueType;
using BuildType = typename DataType::BuildType;
DataType data;
ccl_static_constexpr uint32_t LOG2DIM = Log2Dim;
ccl_static_constexpr uint32_t TOTAL = LOG2DIM;
ccl_static_constexpr uint32_t DIM = 1u << TOTAL;
ccl_static_constexpr uint32_t SIZE = 1u << 3 * LOG2DIM;
ccl_static_constexpr uint32_t MASK = (1u << LOG2DIM) - 1u;
ccl_static_constexpr uint32_t LEVEL = 0;
static ccl_device_inline_method uint32_t CoordToOffset(const Coord ijk)
{
return ((ijk.x & MASK) << (2 * LOG2DIM)) | ((ijk.y & MASK) << LOG2DIM) | (ijk.z & MASK);
}
ccl_device_inline_method ValueType getValue(const uint32_t offset) const ccl_global
{
return data.getValue(offset);
}
ccl_device_inline_method ValueType getValue(const Coord ijk) const ccl_global
{
return getValue(CoordToOffset(ijk));
}
};
/* Template Specializations */
template<typename BuildT> using NanoLeaf = LeafNode<BuildT, 3>;
template<typename BuildT> using NanoLower = InternalNode<NanoLeaf<BuildT>, 4>;
template<typename BuildT> using NanoUpper = InternalNode<NanoLower<BuildT>, 5>;
template<typename BuildT> using NanoRoot = RootNode<NanoUpper<BuildT>>;
template<typename BuildT> using NanoTree = Tree<NanoRoot<BuildT>>;
template<typename BuildT> using NanoGrid = Grid<NanoTree<BuildT>>;
/* ReadAccessor */
template<typename BuildT> class ReadAccessor {
using RootT = NanoRoot<BuildT>;
using LeafT = NanoLeaf<BuildT>;
mutable const ccl_global RootT *mRoot;
public:
using ValueType = typename RootT::ValueType;
ccl_device_inline_method ReadAccessor(const ccl_global RootT &root) : mRoot(&root) {}
ccl_device_inline_method ValueType getValue(const Coord ijk) const
{
const ccl_global auto *tile = mRoot->probeTile(ijk);
if (tile == nullptr) {
return mRoot->mBackground;
}
if (tile->child == 0) {
return tile->value;
}
const ccl_global auto *upper = mRoot->getChild(tile);
const uint32_t upper_n = upper->CoordToOffset(ijk);
if (upper->mChildMask.isOff(upper_n)) {
return upper->mTable[upper_n].value;
}
const ccl_global auto *lower = upper->getChild(upper_n);
const uint32_t lower_n = lower->CoordToOffset(ijk);
if (lower->mChildMask.isOff(lower_n)) {
return lower->mTable[lower_n].value;
}
const ccl_global LeafT *leaf = lower->getChild(lower_n);
return leaf->getValue(ijk);
}
};
template<typename BuildT> class CachedReadAccessor {
using RootT = NanoRoot<BuildT>;
using UpperT = NanoUpper<BuildT>;
using LowerT = NanoLower<BuildT>;
using LeafT = NanoLeaf<BuildT>;
mutable Coord mKeys[3] = {make_int3(INT_MAX), make_int3(INT_MAX), make_int3(INT_MAX)};
mutable const ccl_global RootT *mRoot = nullptr;
mutable const ccl_global void *mNode[3] = {nullptr, nullptr, nullptr};
public:
using ValueType = typename RootT::ValueType;
ccl_device_inline_method CachedReadAccessor(const ccl_global RootT &root) : mRoot(&root) {}
template<typename NodeT> ccl_device_inline_method bool isCached(const Coord ijk) const
{
return (ijk.x & int32_t(~NodeT::MASK)) == mKeys[NodeT::LEVEL].x &&
(ijk.y & int32_t(~NodeT::MASK)) == mKeys[NodeT::LEVEL].y &&
(ijk.z & int32_t(~NodeT::MASK)) == mKeys[NodeT::LEVEL].z;
}
ccl_device_inline_method ValueType getValueAndCache(const ccl_global RootT &node,
const Coord ijk) const
{
if (const ccl_global auto *tile = node.probeTile(ijk)) {
if (tile->child != 0) {
const ccl_global auto *child = node.getChild(tile);
insert(ijk, child);
return getValueAndCache(*child, ijk);
}
return tile->value;
}
return node.mBackground;
}
ccl_device_inline_method ValueType getValueAndCache(const ccl_global LeafT &node,
const Coord ijk) const
{
return node.getValue(ijk);
}
template<typename NodeT>
ccl_device_inline_method ValueType getValueAndCache(const ccl_global NodeT &node,
const Coord ijk) const
{
const uint32_t n = node.CoordToOffset(ijk);
if (node.mChildMask.isOff(n)) {
return node.mTable[n].value;
}
const ccl_global auto *child = node.getChild(n);
insert(ijk, child);
return getValueAndCache(*child, ijk);
}
ccl_device_inline_method ValueType getValue(const Coord ijk) const
{
if (isCached<LeafT>(ijk)) {
return getValueAndCache(*((const ccl_global LeafT *)mNode[0]), ijk);
}
if (isCached<LowerT>(ijk)) {
return getValueAndCache(*((const ccl_global LowerT *)mNode[1]), ijk);
}
if (isCached<UpperT>(ijk)) {
return getValueAndCache(*((const ccl_global UpperT *)mNode[2]), ijk);
}
return getValueAndCache(*mRoot, ijk);
}
template<typename NodeT>
ccl_device_inline_method void insert(const Coord ijk, const ccl_global NodeT *node) const
{
mKeys[NodeT::LEVEL] = ijk & ~NodeT::MASK;
mNode[NodeT::LEVEL] = node;
}
};
} // namespace nanovdb
CCL_NAMESPACE_END

View File

@@ -0,0 +1,28 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifndef __KERNEL_GPU__
# include "util/profiling.h" // IWYU pragma: export
#endif
CCL_NAMESPACE_BEGIN
#ifndef __KERNEL_GPU__
# define PROFILING_INIT(kg, event) \
ProfilingHelper profiling_helper((ProfilingState *)&kg->profiler, event)
# define PROFILING_EVENT(event) profiling_helper.set_event(event)
# define PROFILING_INIT_FOR_SHADER(kg, event) \
ProfilingWithShaderHelper profiling_helper((ProfilingState *)&kg->profiler, event)
# define PROFILING_SHADER(object, shader) \
profiling_helper.set_shader(object, (shader) & SHADER_MASK);
#else
# define PROFILING_INIT(kg, event)
# define PROFILING_EVENT(event)
# define PROFILING_INIT_FOR_SHADER(kg, event)
# define PROFILING_SHADER(object, shader)
#endif /* !__KERNEL_GPU__ */
CCL_NAMESPACE_END