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,453 @@
# SPDX-FileCopyrightText: 2011-2026 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
set(INC
..
)
set(INC_SYS
)
set(SRC_KERNEL_DEVICE_GPU_HEADERS
device/gpu/block_sizes.h
device/gpu/image.h
device/gpu/kernel.h
device/gpu/parallel_active_index.h
device/gpu/parallel_prefix_sum.h
device/gpu/parallel_sorted_index.h
device/gpu/work_stealing.h
)
set(SRC_KERNEL_CLOSURE_HEADERS
closure/alloc.h
closure/bsdf.h
closure/bsdf_ashikhmin_velvet.h
closure/bsdf_burley.h
closure/bsdf_diffuse.h
closure/bsdf_diffuse_ramp.h
closure/bsdf_microfacet.h
closure/bsdf_oren_nayar.h
closure/bsdf_phong_ramp.h
closure/bsdf_ray_portal.h
closure/bsdf_sheen.h
closure/bsdf_toon.h
closure/bsdf_transparent.h
closure/bsdf_util.h
closure/bsdf_ashikhmin_shirley.h
closure/bsdf_hair.h
closure/bssrdf.h
closure/emissive.h
closure/volume.h
closure/volume_util.h
closure/volume_henyey_greenstein.h
closure/volume_rayleigh.h
closure/volume_fournier_forand.h
closure/volume_draine.h
closure/bsdf_principled_hair_chiang.h
closure/bsdf_principled_hair_huang.h
)
set(SRC_KERNEL_SVM_HEADERS
svm/svm.h
svm/ao.h
svm/aov.h
svm/attribute.h
svm/bevel.h
svm/blackbody.h
svm/bump.h
svm/camera.h
svm/clamp.h
svm/closure.h
svm/convert.h
svm/checker.h
svm/color_util.h
svm/brick.h
svm/displace.h
svm/fresnel.h
svm/wireframe.h
svm/wavelength.h
svm/gabor.h
svm/gamma.h
svm/brightness.h
svm/geometry.h
svm/gradient.h
svm/hsv.h
svm/ies.h
svm/image.h
svm/invert.h
svm/light_path.h
svm/magic.h
svm/map_range.h
svm/mapping.h
svm/mapping_util.h
svm/math.h
svm/math_util.h
svm/mix.h
svm/node_types.h
svm/node_types_template.h
svm/noise.h
svm/noisetex.h
svm/normal.h
svm/ramp.h
svm/ramp_util.h
svm/radial_tiling.h
svm/radial_tiling_shared.h
svm/raycast.h
svm/scene_time.h
svm/sepcomb_color.h
svm/sepcomb_vector.h
svm/sky.h
svm/tex_coord.h
svm/fractal_noise.h
svm/types.h
svm/util.h
svm/value.h
svm/vector_rotate.h
svm/vector_transform.h
svm/voronoi.h
svm/wave.h
svm/white_noise.h
svm/vertex_color.h
)
if(WITH_CYCLES_OSL)
set(SRC_KERNEL_OSL_HEADERS
osl/osl.h
osl/camera.h
osl/closures_setup.h
osl/closures_template.h
osl/services_gpu.h
osl/strings.h
osl/types.h
)
endif()
set(SRC_KERNEL_GEOM_HEADERS
geom/attribute.h
geom/curve.h
geom/curve_intersect.h
geom/geom_intersect.h
geom/motion_curve.h
geom/motion_point.h
geom/motion_triangle.h
geom/motion_triangle_intersect.h
geom/motion_triangle_shader.h
geom/object.h
geom/point.h
geom/point_intersect.h
geom/primitive.h
geom/shader_data.h
geom/triangle.h
geom/triangle_intersect.h
geom/volume.h
)
set(SRC_KERNEL_BAKE_HEADERS
bake/bake.h
)
set(SRC_KERNEL_BVH_HEADERS
bvh/bvh.h
bvh/intersect_filter.h
bvh/nodes.h
bvh/shadow_all.h
bvh/local.h
bvh/traversal.h
bvh/types.h
bvh/util.h
bvh/volume.h
bvh/volume_all.h
)
set(SRC_KERNEL_CAMERA_HEADERS
camera/camera.h
camera/projection.h
)
set(SRC_KERNEL_FILM_HEADERS
film/adaptive_sampling.h
film/aov_passes.h
film/data_passes.h
film/denoising_passes.h
film/volume_guiding_denoise.h
film/cryptomatte_passes.h
film/light_passes.h
film/read.h
film/write.h
)
set(SRC_KERNEL_INTEGRATOR_HEADERS
integrator/displacement_shader.h
integrator/init_from_bake.h
integrator/init_from_camera.h
integrator/intersect_dedicated_light.h
integrator/intersect_closest.h
integrator/intersect_mnee.h
integrator/intersect_shadow.h
integrator/intersect_subsurface.h
integrator/intersect_volume_stack.h
integrator/guiding.h
integrator/megakernel.h
integrator/mnee.h
integrator/path_state.h
integrator/shade_background.h
integrator/shade_light.h
integrator/shade_shadow.h
integrator/shade_surface.h
integrator/shade_volume.h
integrator/shade_dedicated_light.h
integrator/shadow_catcher.h
integrator/shadow_linking.h
integrator/shadow_state_template.h
integrator/state_flow.h
integrator/state.h
integrator/state_template.h
integrator/state_util.h
integrator/subsurface_disk.h
integrator/subsurface.h
integrator/subsurface_random_walk.h
integrator/surface_shader.h
integrator/volume_shader.h
integrator/volume_stack.h
)
set(SRC_KERNEL_LIGHT_HEADERS
light/area.h
light/background.h
light/common.h
light/distribution.h
light/light.h
light/point.h
light/sample.h
light/spot.h
light/sun.h
light/tree.h
light/triangle.h
)
set(SRC_KERNEL_SAMPLE_HEADERS
sample/lcg.h
sample/mapping.h
sample/mis.h
sample/pattern.h
sample/sobol_burley.h
sample/tabulated_sobol.h
sample/util.h
)
set(SRC_KERNEL_UTIL_HEADERS
util/colorspace.h
util/differential.h
util/ies.h
util/image_2d.h
util/image_3d.h
util/lookup_table.h
util/nanovdb.h
util/profiler.h
)
set(SRC_KERNEL_TYPES_HEADERS
data_arrays.h
data_template.h
features.h
globals.h
image.h
tables.h
types.h
)
set(SRC_KERNEL_HEADERS
${SRC_KERNEL_BAKE_HEADERS}
${SRC_KERNEL_BVH_HEADERS}
${SRC_KERNEL_CAMERA_HEADERS}
${SRC_KERNEL_CLOSURE_HEADERS}
${SRC_KERNEL_FILM_HEADERS}
${SRC_KERNEL_GEOM_HEADERS}
${SRC_KERNEL_INTEGRATOR_HEADERS}
${SRC_KERNEL_LIGHT_HEADERS}
${SRC_KERNEL_OSL_HEADERS}
${SRC_KERNEL_SAMPLE_HEADERS}
${SRC_KERNEL_SVM_HEADERS}
${SRC_KERNEL_TYPES_HEADERS}
${SRC_KERNEL_UTIL_HEADERS}
)
set(SRC_UTIL_HEADERS
../util/atomic.h
../util/color.h
../util/defines.h
../util/guiding.h
../util/half.h
../util/hash.h
../util/math.h
../util/math_base.h
../util/math_fast.h
../util/math_intersect.h
../util/math_float2.h
../util/math_float3.h
../util/math_float4.h
../util/math_float8.h
../util/math_int2.h
../util/math_int3.h
../util/math_int4.h
../util/math_int8.h
../util/math_dual.h
../util/projection.h
../util/projection_inverse.h
../util/rect.h
../util/static_assert.h
../util/transform.h
../util/types.h
../util/types_base.h
../util/types_float2.h
../util/types_float3.h
../util/types_float4.h
../util/types_float8.h
../util/types_image.h
../util/types_int2.h
../util/types_int3.h
../util/types_int4.h
../util/types_int8.h
../util/types_normal.h
../util/types_spectrum.h
../util/types_rgbe.h
../util/types_uchar2.h
../util/types_uchar3.h
../util/types_uchar4.h
../util/types_uint2.h
../util/types_uint3.h
../util/types_uint4.h
../util/types_ushort4.h
../util/types_dual.h
)
set(LIB
)
# `Zstd` compressor for kernels.
add_executable(zstd_compress ../cmake/zstd_compress.cpp)
target_link_libraries(zstd_compress PRIVATE bf::dependencies::zstd)
target_link_libraries(zstd_compress PRIVATE bf::dependencies::pthreads)
if(NOT WITH_BLENDER)
# For the Cycles standalone put libraries next to the Cycles application.
set(cycles_kernel_runtime_lib_target_path ${CYCLES_INSTALL_PATH})
else()
# For Blender put the libraries next to the Blender executable.
#
# Note that the installation path in the delayed_install is relative to the versioned folder,
# which means we need to go one level up.
set(cycles_kernel_runtime_lib_target_path "../")
endif()
if(UNIX AND NOT APPLE)
set(cycles_kernel_runtime_lib_target_path ${cycles_kernel_runtime_lib_target_path}/lib)
endif()
if(WITH_COMPILER_ASAN)
if((CMAKE_C_COMPILER_ID STREQUAL "GNU") AND (NOT WITH_CYCLES_KERNEL_ASAN))
# GCC hangs compiling the big kernel files with ASAN and release, so disable by default.
string(APPEND CMAKE_CXX_FLAGS_RELWITHDEBINFO " -fno-sanitize=all")
string(APPEND CMAKE_CXX_FLAGS_DEBUG " -fno-sanitize=vptr")
elseif(CMAKE_C_COMPILER_ID MATCHES "Clang")
# With OSL, Cycles disables RTTI in some modules, which then breaks at linking
# when trying to use `vptr` sanitizer (included into 'undefined' general option).
string(APPEND CMAKE_CXX_FLAGS_RELWITHDEBINFO " -fno-sanitize=vptr")
string(APPEND CMAKE_CXX_FLAGS_DEBUG " -fno-sanitize=vptr")
endif()
endif()
# -----------------------------------------------------------------------------
# Create module with headers common for all compute backends.
add_library(cycles_kernel INTERFACE)
target_sources(cycles_kernel INTERFACE
${SRC_KERNEL_HEADERS}
${SRC_KERNEL_DEVICE_GPU_HEADERS}
${SRC_KERNEL_UTIL_HEADERS}
)
source_group("bake" FILES ${SRC_KERNEL_BAKE_HEADERS})
source_group("bvh" FILES ${SRC_KERNEL_BVH_HEADERS})
source_group("camera" FILES ${SRC_KERNEL_CAMERA_HEADERS})
source_group("closure" FILES ${SRC_KERNEL_CLOSURE_HEADERS})
source_group("device\\gpu" FILES ${SRC_KERNEL_DEVICE_GPU_HEADERS})
source_group("film" FILES ${SRC_KERNEL_FILM_HEADERS})
source_group("geom" FILES ${SRC_KERNEL_GEOM_HEADERS})
source_group("integrator" FILES ${SRC_KERNEL_INTEGRATOR_HEADERS})
source_group("kernel" FILES ${SRC_KERNEL_TYPES_HEADERS})
source_group("light" FILES ${SRC_KERNEL_LIGHT_HEADERS})
source_group("osl" FILES ${SRC_KERNEL_OSL_HEADERS})
source_group("sample" FILES ${SRC_KERNEL_SAMPLE_HEADERS})
source_group("svm" FILES ${SRC_KERNEL_SVM_HEADERS})
source_group("util" FILES ${SRC_KERNEL_UTIL_HEADERS})
# -----------------------------------------------------------------------------
# Install kernel source for runtime compilation.
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_BAKE_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/bake)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_BVH_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/bvh)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_CAMERA_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/camera)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_CLOSURE_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/closure)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_DEVICE_GPU_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/device/gpu)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_FILM_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/film)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_GEOM_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/geom)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_INTEGRATOR_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/integrator)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_LIGHT_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/light)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_OSL_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/osl)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_SAMPLE_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/sample)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_SVM_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/svm)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_TYPES_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_UTIL_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/util)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_UTIL_HEADERS}" ${CYCLES_INSTALL_PATH}/source/util)
# -----------------------------------------------------------------------------
# OSL module.
if(WITH_CYCLES_OSL)
list(APPEND LIB
cycles_kernel_osl
)
add_subdirectory(osl)
add_subdirectory(osl/shaders)
endif()
# -----------------------------------------------------------------------------
# Backend-specific rules.
add_subdirectory(device/cpu)
add_subdirectory(device/cuda)
add_subdirectory(device/hip)
add_subdirectory(device/hiprt)
add_subdirectory(device/metal)
add_subdirectory(device/oneapi)
add_subdirectory(device/optix)
target_link_libraries(cycles_kernel
INTERFACE cycles_kernel_cpu
)
if(NOT WITH_CYCLES_PARALLEL_DEVICE_KERNEL_BUILD)
set(_cycles_prev_gpu_kernel_target "")
# Modifies in parent scope:
# - `_cycles_prev_gpu_kernel_target`: updated to current target for chaining.
function(cycles_add_gpu_kernel_dependencies GPU_KERNEL_TARGET)
if(TARGET ${GPU_KERNEL_TARGET})
if(_cycles_prev_gpu_kernel_target)
add_dependencies(${GPU_KERNEL_TARGET} ${_cycles_prev_gpu_kernel_target})
endif()
set(_cycles_prev_gpu_kernel_target ${GPU_KERNEL_TARGET} PARENT_SCOPE)
endif()
endfunction()
cycles_add_gpu_kernel_dependencies(cycles_kernel_cuda)
cycles_add_gpu_kernel_dependencies(cycles_kernel_optix)
cycles_add_gpu_kernel_dependencies(cycles_kernel_hip)
cycles_add_gpu_kernel_dependencies(cycles_kernel_hiprt)
cycles_add_gpu_kernel_dependencies(cycles_kernel_oneapi)
cycles_add_gpu_kernel_dependencies(cycles_kernel_cpu)
endif()

View File

@@ -0,0 +1,236 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/globals.h"
#include "kernel/camera/projection.h"
#include "kernel/integrator/displacement_shader.h"
#include "kernel/integrator/state.h"
#include "kernel/integrator/surface_shader.h"
#include "kernel/integrator/volume_shader.h"
#include "kernel/geom/object.h"
#include "kernel/geom/shader_data.h"
#include "kernel/util/colorspace.h"
CCL_NAMESPACE_BEGIN
ccl_device void kernel_displace_evaluate(KernelGlobals kg,
const ccl_global KernelShaderEvalInput *input,
ccl_global float *output,
ccl_global uint *cache_miss,
const int offset)
{
/* Setup shader data. */
const KernelShaderEvalInput in = input[offset];
ShaderData sd;
shader_setup_from_displace(kg, &sd, in.object, in.prim, in.u, in.v);
/* Evaluate displacement shader. */
ConstIntegratorBakeState state;
const float3 P = sd.P;
displacement_shader_eval(kg, state, &sd);
float3 D = sd.P - P;
if (sd.flag & SD_CACHE_MISS) {
*cache_miss = true;
}
object_inverse_dir_transform(kg, &sd, &D);
#ifdef __KERNEL_DEBUG_NAN__
if (!isfinite_safe(D)) {
kernel_assert(!"Cycles displacement with non-finite value detected");
}
#endif
/* Ensure finite displacement, preventing BVH from becoming degenerate and avoiding possible
* traversal issues caused by non-finite math. */
D = ensure_finite(D);
/* Write output. */
output[offset * 3 + 0] = D.x;
output[offset * 3 + 1] = D.y;
output[offset * 3 + 2] = D.z;
}
ccl_device void kernel_background_evaluate(KernelGlobals kg,
const ccl_global KernelShaderEvalInput *input,
ccl_global float *output,
ccl_global uint *cache_miss,
const int offset)
{
/* Setup ray */
const KernelShaderEvalInput in = input[offset];
const float3 ray_P = zero_float3();
const float3 ray_D = equirectangular_to_direction(in.u, in.v);
const float ray_time = 0.5f;
/* Compute ray differential from resolution passed via object and prim fields. */
const float du = 1.0f / in.object;
const float dv = 1.0f / in.prim;
const float3 ray_D_du = equirectangular_to_direction(in.u + du, in.v);
const float3 ray_D_dv = equirectangular_to_direction(in.u, in.v + dv);
const float ray_dD = 0.5f * (len(ray_D_du - ray_D) + len(ray_D_dv - ray_D));
/* Setup shader data. */
ShaderData sd;
shader_setup_from_background(kg, &sd, ray_P, ray_D, ray_dD, ray_time);
/* Evaluate shader.
* This is being evaluated for all BSDFs, so path flag does not contain a specific type.
* However, we want to flag the ray visibility to ignore the sun in the background map. */
ConstIntegratorBakeState state;
const uint32_t path_flag = PATH_RAY_EMISSION | PATH_RAY_IMPORTANCE_BAKE;
surface_shader_eval<KERNEL_FEATURE_NODE_MASK_SURFACE_LIGHT &
~(KERNEL_FEATURE_NODE_RAYTRACE | KERNEL_FEATURE_NODE_LIGHT_PATH)>(
kg, state, &sd, nullptr, PATH_RAY_VISIBILITY_NONE, path_flag);
if (sd.flag & SD_CACHE_MISS) {
*cache_miss = true;
}
Spectrum color = surface_shader_background(&sd);
#ifdef __KERNEL_DEBUG_NAN__
if (!isfinite_safe(color)) {
kernel_assert(!"Cycles background with non-finite value detected");
}
#endif
/* Ensure finite color, avoiding possible numerical instabilities in the path tracing kernels. */
color = ensure_finite(color);
const float3 color_rgb = spectrum_to_rgb(color);
/* Write output. */
output[offset * 3 + 0] = color_rgb.x;
output[offset * 3 + 1] = color_rgb.y;
output[offset * 3 + 2] = color_rgb.z;
}
ccl_device void kernel_curve_shadow_transparency_evaluate(
KernelGlobals kg,
const ccl_global KernelShaderEvalInput *input,
ccl_global float *output,
ccl_global uint *cache_miss,
const int offset)
{
#ifdef __HAIR__
/* Setup shader data. */
const KernelShaderEvalInput in = input[offset];
ShaderData sd;
shader_setup_from_curve(kg, &sd, in.object, in.prim, __float_as_int(in.v), in.u);
/* Evaluate transparency. */
ConstIntegratorBakeState state;
surface_shader_eval<KERNEL_FEATURE_NODE_MASK_SURFACE_SHADOW &
~(KERNEL_FEATURE_NODE_RAYTRACE | KERNEL_FEATURE_NODE_LIGHT_PATH)>(
kg, state, &sd, nullptr, PATH_RAY_VISIBILITY_SHADOW, PATH_RAY_FLAG_NONE);
if (sd.flag & SD_CACHE_MISS) {
*cache_miss = true;
}
/* Write output. */
output[offset] = clamp(average(surface_shader_transparency(&sd)), 0.0f, 1.0f);
#endif
}
ccl_device void kernel_volume_density_evaluate(KernelGlobals kg,
ccl_global const KernelShaderEvalInput *input,
ccl_global float *output,
ccl_global uint *cache_miss,
const int offset)
{
#ifdef __VOLUME__
if (input[offset * 2 + 1].object == SHADER_NONE) {
return;
}
KernelShaderEvalInput in = input[offset * 2];
/* Setup ray. */
Ray ray;
ray.P = make_float3(__int_as_float(in.prim), in.u, in.v);
ray.D = zero_float3();
ray.tmin = 0.0f;
/* Motion blur is ignored when computing the extrema of the density, but we also don't expect the
* value to change a lot in one frame. */
ray.time = 0.5f;
/* Setup shader data. */
ShaderData sd;
shader_setup_from_volume(&sd, &ray, in.object);
sd.flag = SD_IS_VOLUME_SHADER_EVAL;
/* For stochastic texture sampling. */
sd.lcg_state = lcg_state_init(offset, 0, 0, 0x15b4f88d);
/* Evaluate extinction and emission without allocating closures. */
sd.num_closure_left = 0;
/* Evaluate density for camera ray because it usually makes the most visual impact. For shaders
* that depends on ray types, the extrema are estimated on the fly. */
/* TODO(weizhen): Volume invisible to camera ray might appear noisy. We can at least build a
* separate octree for shadow ray. */
const PathRayVisibility path_visibility = PATH_RAY_VISIBILITY_CAMERA;
const uint32_t path_flag = PATH_RAY_FLAG_NONE;
/* Setup volume stack entry. */
in = input[offset * 2 + 1];
const int shader = in.object;
const VolumeStack entry = {sd.object, shader};
const float3 voxel_size = make_float3(__int_as_float(in.prim), in.u, in.v);
Extrema<float> extrema = {FLT_MAX, -FLT_MAX};
/* For heterogeneous volume, we take 16 samples per grid;
* for homogeneous volume, only 1 sample is needed. */
const int num_samples = volume_is_homogeneous(kg, entry) ? 1 : 16;
const bool need_transformation = !(kernel_data_fetch(object_flag, sd.object) &
SD_OBJECT_TRANSFORM_APPLIED);
const Transform tfm = need_transformation ?
object_fetch_transform(kg, sd.object, OBJECT_TRANSFORM) :
Transform();
for (int sample = 0; sample < num_samples; sample++) {
/* Blue noise indexing. The sequence length is the number of samples. */
const uint3 index = make_uint3(sample + offset * num_samples, 0, 0xffffffff);
/* Sample a random position inside the voxel. */
const float3 rand_p = sobol_burley_sample_3D(
index.x, PRNG_BAKE_VOLUME_DENSITY_EVAL, index.y, index.z);
sd.P = ray.P + rand_p * voxel_size;
if (need_transformation) {
/* Convert to world space. */
sd.P = transform_point(&tfm, sd.P);
}
sd.closure_transparent_extinction = zero_float3();
sd.closure_emission_background = zero_float3();
/* Evaluate volume coefficients. */
ConstIntegratorBakeState state;
volume_shader_eval_entry<false,
KERNEL_FEATURE_NODE_MASK_VOLUME & ~KERNEL_FEATURE_NODE_LIGHT_PATH>(
kg, state, &sd, entry, path_visibility, path_flag);
if (sd.flag & SD_CACHE_MISS) {
/* Note we keep rendering other samples so we find all cache misses in one go. */
*cache_miss = true;
}
const float sigma = reduce_max(sd.closure_transparent_extinction);
const float emission = reduce_max(sd.closure_emission_background);
extrema = merge(extrema, fmaxf(sigma, emission));
}
/* Write output. */
const float scale = object_volume_density(kg, sd.object);
output[offset * 2 + 0] = extrema.min / scale;
output[offset * 2 + 1] = extrema.max / scale;
#endif
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,437 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/bvh/intersect_filter.h"
#include "kernel/bvh/nodes.h"
#include "kernel/bvh/types.h"
#include "kernel/bvh/util.h"
#include "kernel/geom/curve_intersect.h"
#include "kernel/geom/motion_triangle_intersect.h"
#include "kernel/geom/object.h"
#include "kernel/geom/point_intersect.h"
#include "kernel/geom/triangle_intersect.h"
/* Device specific acceleration structures for ray tracing. */
#if defined(__EMBREE__)
# include "kernel/device/cpu/bvh.h"
# define __BVH2__
#elif defined(__KERNEL_METALRT__)
# include "kernel/device/metal/bvh.h"
#elif defined(__KERNEL_OPTIX__)
# include "kernel/device/optix/bvh.h"
#elif defined(__KERNEL_HIPRT__)
# include "kernel/device/hiprt/bvh.h"
#else
# define __BVH2__
#endif
#if defined(__KERNEL_ONEAPI__) && defined(WITH_EMBREE_GPU)
/* bool is apparently not tested for specialization constants:
* https://github.com/intel/llvm/blob/39d1c65272a786b2b13a6f094facfddf9408406d/sycl/test/basic_tests/SYCL-2020-spec-constants.cpp#L25-L27
* Instead of adding one more bool specialization constant, we reuse existing embree_features one
* and use RTC_FEATURE_FLAG_NONE as value to test for avoiding to call Embree on GPU.
*/
/* We set it to RTC_FEATURE_FLAG_NONE by default so AoT binaries contain MNE and ray-trace kernels
* pre-compiled without Embree.
* Changing this default value would require updating the logic in oneapi_load_kernels(). */
static constexpr sycl::specialization_id<RTCFeatureFlags> oneapi_embree_features{
RTC_FEATURE_FLAG_NONE};
# define IF_USING_EMBREE \
if (kernel_handler.get_specialization_constant<oneapi_embree_features>() != \
RTC_FEATURE_FLAG_NONE)
# define IF_NOT_USING_EMBREE \
if (kernel_handler.get_specialization_constant<oneapi_embree_features>() == \
RTC_FEATURE_FLAG_NONE)
#else
# define IF_USING_EMBREE
# define IF_NOT_USING_EMBREE
#endif
CCL_NAMESPACE_BEGIN
/* --------------------------------------------------------------------
* Transparent shadow BVH traversal, recording multiple intersections.
*/
#ifdef __TRANSPARENT_SHADOWS__
# if defined(__BVH2__)
# define BVH_FUNCTION_NAME bvh_intersect_shadow_all
# define BVH_FUNCTION_FEATURES BVH_POINTCLOUD
# include "kernel/bvh/shadow_all.h"
# if defined(__HAIR__)
# define BVH_FUNCTION_NAME bvh_intersect_shadow_all_hair
# define BVH_FUNCTION_FEATURES BVH_HAIR | BVH_POINTCLOUD
# include "kernel/bvh/shadow_all.h"
# endif
# if defined(__OBJECT_MOTION__)
# define BVH_FUNCTION_NAME bvh_intersect_shadow_all_motion
# define BVH_FUNCTION_FEATURES BVH_MOTION | BVH_POINTCLOUD
# include "kernel/bvh/shadow_all.h"
# endif
# if defined(__HAIR__) && defined(__OBJECT_MOTION__)
# define BVH_FUNCTION_NAME bvh_intersect_shadow_all_hair_motion
# define BVH_FUNCTION_FEATURES BVH_HAIR | BVH_MOTION | BVH_POINTCLOUD
# include "kernel/bvh/shadow_all.h"
# endif
ccl_device_inline void scene_intersect_shadow_all_bvh2(
KernelGlobals kg,
const ccl_private Ray *ccl_restrict ray,
ccl_private BVHShadowAllPayload &ccl_restrict payload)
{
# ifdef __OBJECT_MOTION__
if (kernel_data.bvh.have_motion) {
# ifdef __HAIR__
if (kernel_data.bvh.have_curves) {
bvh_intersect_shadow_all_hair_motion(kg, ray, payload);
return;
}
# endif /* __HAIR__ */
bvh_intersect_shadow_all_motion(kg, ray, payload);
return;
}
# endif /* __OBJECT_MOTION__ */
# ifdef __HAIR__
if (kernel_data.bvh.have_curves) {
bvh_intersect_shadow_all_hair(kg, ray, payload);
return;
}
# endif /* __HAIR__ */
bvh_intersect_shadow_all(kg, ray, payload);
}
# endif /* __BVH2__ */
ccl_device_intersect void scene_intersect_shadow_all(KernelGlobals kg,
IntegratorShadowState state,
const ccl_private Ray *ray,
const uint visibility,
const uint max_transparent_hits,
ccl_private uint *num_recorded_hits,
ccl_private float *throughput)
{
# if !defined(__KERNEL_OPTIX__)
/* OptiX does not perform well with conditional trace calls, so it handles the validity of the
* ray in the scene_intersect_shadow_all_optix(). */
if (!intersection_ray_valid(ray)) {
*num_recorded_hits = 0;
*throughput = 1.0f;
return;
}
# endif
BVHShadowAllPayload payload;
/* A bit of a tricky initialization:
* - Some backends require extra ray information for custom motion blur intersection.
* - Some backends utilize registers to pass commonly accessed data to the trace calls. */
# if !defined(__KERNEL_OPTIX__)
BVH_PAYLOAD_BASE(payload).ray_self = ray->self;
BVH_PAYLOAD_BASE(payload).ray_visibility = visibility;
# if defined(__KERNEL_HIPRT__)
BVH_PAYLOAD_BASE(payload).ray_time = ray->time;
# endif
# endif
payload.state = state;
payload.max_transparent_hits = max_transparent_hits;
payload.max_record_isect_t = ray->tmax;
# ifdef __EMBREE__
IF_USING_EMBREE
{
if (kernel_data.device_bvh) {
kernel_embree_intersect_shadow_all(kg, ray, payload);
*num_recorded_hits = payload.num_recorded_hits;
*throughput = payload.throughput;
return;
}
}
# endif
IF_NOT_USING_EMBREE
{
# if defined(__BVH2__)
scene_intersect_shadow_all_bvh2(kg, ray, payload);
# elif defined(__KERNEL_HIPRT__)
scene_intersect_shadow_all_hiprt(kg, ray, payload);
# elif defined(__KERNEL_METALRT__)
scene_intersect_shadow_all_metalrt(ray, payload);
# elif defined(__KERNEL_OPTIX__)
scene_intersect_shadow_all_optix(ray, visibility, payload);
# endif
*num_recorded_hits = payload.num_recorded_hits;
*throughput = payload.throughput;
return;
}
kernel_assert(false);
}
#endif /* __TRANSPARENT_SHADOWS__ */
// ------------------------------------------------------------------------------------------------
#ifdef __BVH2__
/* BVH2
*
* Bounding volume hierarchy for ray tracing, when no native acceleration
* structure is available for the device.
*
* We compile different variations of the same BVH traversal function for
* faster rendering when some types of primitives are not needed, using #includes
* to work around the lack of C++ templates in OpenCL.
*
* Originally based on "Understanding the Efficiency of Ray Traversal on GPUs",
* the code has been extended and modified to support more primitives and work
* with CPU and various GPU kernel languages. */
/* Regular BVH traversal */
# define BVH_FUNCTION_NAME bvh_intersect
# define BVH_FUNCTION_FEATURES BVH_POINTCLOUD
# include "kernel/bvh/traversal.h"
# if defined(__HAIR__)
# define BVH_FUNCTION_NAME bvh_intersect_hair
# define BVH_FUNCTION_FEATURES BVH_HAIR | BVH_POINTCLOUD
# include "kernel/bvh/traversal.h"
# endif
# if defined(__OBJECT_MOTION__)
# define BVH_FUNCTION_NAME bvh_intersect_motion
# define BVH_FUNCTION_FEATURES BVH_MOTION | BVH_POINTCLOUD
# include "kernel/bvh/traversal.h"
# endif
# if defined(__HAIR__) && defined(__OBJECT_MOTION__)
# define BVH_FUNCTION_NAME bvh_intersect_hair_motion
# define BVH_FUNCTION_FEATURES BVH_HAIR | BVH_MOTION | BVH_POINTCLOUD
# include "kernel/bvh/traversal.h"
# endif
ccl_device_intersect bool scene_intersect(KernelGlobals kg,
const ccl_private Ray *ray,
const uint visibility,
ccl_private Intersection *isect)
{
if (!intersection_ray_valid(ray)) {
return false;
}
# ifdef __EMBREE__
IF_USING_EMBREE
{
if (kernel_data.device_bvh) {
return kernel_embree_intersect(kg, ray, visibility, isect);
}
}
# endif
IF_NOT_USING_EMBREE
{
# ifdef __OBJECT_MOTION__
if (kernel_data.bvh.have_motion) {
# ifdef __HAIR__
if (kernel_data.bvh.have_curves) {
return bvh_intersect_hair_motion(kg, ray, isect, visibility);
}
# endif /* __HAIR__ */
return bvh_intersect_motion(kg, ray, isect, visibility);
}
# endif /* __OBJECT_MOTION__ */
# ifdef __HAIR__
if (kernel_data.bvh.have_curves) {
return bvh_intersect_hair(kg, ray, isect, visibility);
}
# endif /* __HAIR__ */
return bvh_intersect(kg, ray, isect, visibility);
}
kernel_assert(false);
return false;
}
ccl_device_intersect bool scene_intersect_shadow(KernelGlobals kg,
const ccl_private Ray *ray,
const uint visibility)
{
Intersection isect;
return scene_intersect(kg, ray, visibility, &isect);
}
/* Single object BVH traversal, for SSS/AO/bevel. */
# ifdef __BVH_LOCAL__
# define BVH_FUNCTION_NAME bvh_intersect_local
# define BVH_FUNCTION_FEATURES BVH_HAIR
# include "kernel/bvh/local.h"
# if defined(__OBJECT_MOTION__)
# define BVH_FUNCTION_NAME bvh_intersect_local_motion
# define BVH_FUNCTION_FEATURES BVH_MOTION | BVH_HAIR
# include "kernel/bvh/local.h"
# endif
template<bool single_hit = false>
ccl_device_intersect bool scene_intersect_local(KernelGlobals kg,
const ccl_private Ray *ray,
ccl_private LocalIntersection *local_isect,
const int local_object,
ccl_private uint *lcg_state,
const int max_hits)
{
if (!intersection_ray_valid(ray)) {
if (local_isect) {
local_isect->num_hits = 0;
}
return false;
}
# ifdef __EMBREE__
IF_USING_EMBREE
{
if (kernel_data.device_bvh) {
return kernel_embree_intersect_local(
kg, ray, local_isect, local_object, lcg_state, max_hits);
}
}
# endif
IF_NOT_USING_EMBREE
{
# ifdef __OBJECT_MOTION__
if (kernel_data.bvh.have_motion) {
return bvh_intersect_local_motion(kg, ray, local_isect, local_object, lcg_state, max_hits);
}
# endif /* __OBJECT_MOTION__ */
return bvh_intersect_local(kg, ray, local_isect, local_object, lcg_state, max_hits);
}
kernel_assert(false);
return false;
}
# endif
/* Volume BVH traversal, for initializing or updating the volume stack. */
# if defined(__VOLUME__) && !defined(__VOLUME_RECORD_ALL__)
# define BVH_FUNCTION_NAME bvh_intersect_volume
# define BVH_FUNCTION_FEATURES BVH_HAIR
# include "kernel/bvh/volume.h"
# if defined(__OBJECT_MOTION__)
# define BVH_FUNCTION_NAME bvh_intersect_volume_motion
# define BVH_FUNCTION_FEATURES BVH_MOTION | BVH_HAIR
# include "kernel/bvh/volume.h"
# endif
ccl_device_intersect bool scene_intersect_volume(KernelGlobals kg,
const ccl_private Ray *ray,
ccl_private Intersection *isect,
const uint visibility)
{
if (!intersection_ray_valid(ray)) {
return false;
}
# ifdef __EMBREE__
IF_USING_EMBREE
{
if (kernel_data.device_bvh) {
return kernel_embree_intersect_volume(kg, ray, isect, visibility);
}
}
# endif
IF_NOT_USING_EMBREE
{
# ifdef __OBJECT_MOTION__
if (kernel_data.bvh.have_motion) {
return bvh_intersect_volume_motion(kg, ray, isect, visibility);
}
# endif /* __OBJECT_MOTION__ */
return bvh_intersect_volume(kg, ray, isect, visibility);
}
kernel_assert(false);
return false;
}
# endif /* defined(__VOLUME__) && !defined(__VOLUME_RECORD_ALL__) */
/* Volume BVH traversal, for initializing or updating the volume stack.
* Variation that records multiple intersections at once. */
# if defined(__VOLUME__) && defined(__VOLUME_RECORD_ALL__)
# define BVH_FUNCTION_NAME bvh_intersect_volume_all
# define BVH_FUNCTION_FEATURES BVH_HAIR
# include "kernel/bvh/volume_all.h"
# if defined(__OBJECT_MOTION__)
# define BVH_FUNCTION_NAME bvh_intersect_volume_all_motion
# define BVH_FUNCTION_FEATURES BVH_MOTION | BVH_HAIR
# include "kernel/bvh/volume_all.h"
# endif
ccl_device_intersect uint scene_intersect_volume(KernelGlobals kg,
const ccl_private Ray *ray,
ccl_private Intersection *isect,
const uint max_hits,
const uint visibility)
{
if (!intersection_ray_valid(ray)) {
return false;
}
# ifdef __EMBREE__
IF_USING_EMBREE
{
if (kernel_data.device_bvh) {
return kernel_embree_intersect_volume(kg, ray, isect, max_hits, visibility);
}
}
# endif
IF_NOT_USING_EMBREE
{
# ifdef __OBJECT_MOTION__
if (kernel_data.bvh.have_motion) {
return bvh_intersect_volume_all_motion(kg, ray, isect, max_hits, visibility);
}
# endif /* __OBJECT_MOTION__ */
return bvh_intersect_volume_all(kg, ray, isect, max_hits, visibility);
}
kernel_assert(false);
return false;
}
# endif /* defined(__VOLUME__) && defined(__VOLUME_RECORD_ALL__) */
# undef BVH_FEATURE
# undef BVH_NAME_JOIN
# undef BVH_NAME_EVAL
# undef BVH_FUNCTION_FULL_NAME
#endif /* __BVH2__ */
CCL_NAMESPACE_END

View File

@@ -0,0 +1,311 @@
/* SPDX-FileCopyrightText: 2026 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* Intersection and filtering functions for hardware ray-trace style of API.
*
* Filter functions are invoked for an intersection to give BVH traversal hints whether
* traversal is to continue. Returning true from filter functions means the intersection is
* filtered (ignored) and the traversal is to continue.
*
* Note on the template parameters
* ===============================
*
* perform_intersection_tests controls whether checks that are typically are performed during
* intersection are to be done in the filter function. Intersection checks that are done by the
* hardware do not perform self-intersection and shadow-linking checks: they are done in the
* filter function instead. However, if the intersection check uses custom function it performs
* these checks early on, so skipping them in the filter function will lead to a better
* performance. */
#pragma once
#include "kernel/bvh/util.h"
#include "kernel/globals.h"
#include "kernel/integrator/state.h"
#include "kernel/integrator/state_util.h"
#include "kernel/types.h"
CCL_NAMESPACE_BEGIN
enum IntersectionTest : uint {
ISECT_TEST_NONE = 0,
ISECT_TEST_VISIBILITY_FLAG = (1 << 0),
ISECT_TEST_SHADOW_LINKING = (1 << 1),
ISECT_TEST_SELF_SHADOW = (1 << 2),
ISECT_TEST_ALL = (ISECT_TEST_VISIBILITY_FLAG | ISECT_TEST_SHADOW_LINKING |
ISECT_TEST_SELF_SHADOW),
};
/* Special tricks to subclass payload.
* The issue here is Metal does not support subclassing, but HIP-RT had performance issues with
* composition in the past (see !136823). */
#if defined(__KERNEL_HIPRT__)
# define BVH_PAYLOAD_SUBCLASS(cls, base_cls) struct cls : base_cls
# define BVH_PAYLOAD_SUBCLASS_DEFINE(base_cls)
# define BVH_PAYLOAD_BASE(obj) (obj)
#else
# define BVH_PAYLOAD_SUBCLASS(cls, base_cls) struct cls
# define BVH_PAYLOAD_SUBCLASS_DEFINE(base_cls) base_cls base;
# define BVH_PAYLOAD_BASE(obj) ((obj).base)
#endif
struct BVHPayload {
/* In OptiX, self-intersection information and ray visibility are passed via Ray's pointer as
* extra payload data. */
#if !defined(__KERNEL_OPTIX__)
/* Primitives for the self-intersections. */
RaySelfPrimitives ray_self;
/* Ray visibility flags and time. */
uint ray_visibility;
#endif
#if defined(__KERNEL_HIPRT__)
float ray_time;
#endif
};
/* OptiX passes various parameters via registers to the tracing calls. No need to store duplicate
* data for OptiX. This will essentially make it so BVHPayload contains data which is strictly
* needed for intersection recording and for tracking curve transparency. */
#if defined(__KERNEL_OPTIX__)
# define BVH_SHADOW_ALL_PAYLOAD_SUBCLASS(cls, base_cls) struct cls
# define BVH_SHADOW_ALL_PAYLOAD_SUBCLASS_DEFINE(base_cls)
#else
# define BVH_SHADOW_ALL_PAYLOAD_SUBCLASS(cls, base_cls) BVH_PAYLOAD_SUBCLASS(cls, base_cls)
# define BVH_SHADOW_ALL_PAYLOAD_SUBCLASS_DEFINE(base_cls) BVH_PAYLOAD_SUBCLASS_DEFINE(base_cls)
#endif
BVH_SHADOW_ALL_PAYLOAD_SUBCLASS(BVHShadowAllPayload, BVHPayload)
{
BVH_SHADOW_ALL_PAYLOAD_SUBCLASS_DEFINE(BVHPayload);
/* Using uint16_t is slower on HIP, while it is similar performance but potentially lower memory
* footprint on other backends. */
#if defined(__KERNEL_HIPRT__)
using UIntType = uint;
#else
using UIntType = uint16_t;
#endif
IntegratorShadowState state;
/* The maximum number of transparent intersections to consider: if there are more intersections
* than this value, all light is considered blocked. */
UIntType max_transparent_hits;
/* The number of transparent intersections tested during BVH traversal. It might be higher than
* the number of recorded intersections. */
UIntType num_transparent_hits = 0;
/* Maximum intersection distance t for intersections that are to be recorded.
* If intersection's distance exceeds this value, it is not recoded. */
float max_record_isect_t;
/* An index within the shadow_isect array at which the next intersection will be recorded. */
UIntType record_isect_index = 0;
/* The number of intersections that has been attempted to be recorded.
* It might be higher than the shadow_isect size, indicating that more invocations of the
* intersection kernel are needed. It is different from the num_transparent_hits as it does not
* include transparent curve intersections that are handled by accumulating throughput in the
* filter function. */
UIntType num_recorded_hits = 0;
/* Accumulated throughput of transparent curve intersections.
* Curves are using special optimization by baking their transparency and handling it in the
* filter function. */
float throughput = 1.0f;
};
/* Filter intersection with possibly transparent surface.
*
* Designed to be used from the any-hit type of traversal:
* - If an opaque surface is hit, returns false, stopping traversal. The scene intersection
* function will consider the shadow ray to be blocked.
* - If a transparent surface is hit, the intersection is recorded into the shadow_isect array in
* the state. The closest N intersections are recorded. */
template<uint perform_intersection_tests, uint enabled_primitive_types = PRIMITIVE_ALL>
ccl_device_forceinline bool bvh_shadow_all_anyhit_filter(
KernelGlobals kg,
IntegratorShadowState state,
ccl_ray_data BVHShadowAllPayload &ccl_restrict payload,
const ccl_ray_data RaySelfPrimitives &ccl_restrict ray_self,
const uint ray_visibility,
const Intersection isect)
{
#if defined(__VISIBILITY_FLAG__)
if constexpr ((perform_intersection_tests & ISECT_TEST_VISIBILITY_FLAG) != 0) {
if ((kernel_data_fetch(objects, isect.object).visibility & ray_visibility) == 0) {
return true;
}
}
#endif
#if defined(__SHADOW_LINKING__)
if constexpr ((perform_intersection_tests & ISECT_TEST_SHADOW_LINKING) != 0) {
if (intersection_skip_shadow_link(kg, ray_self, isect.object)) {
return true;
}
}
#endif
if constexpr ((perform_intersection_tests & ISECT_TEST_SELF_SHADOW) != 0) {
if (intersection_skip_self_shadow(ray_self, isect.object, isect.prim)) {
return true;
}
}
#if !defined(__TRANSPARENT_SHADOWS__)
/* No transparent shadows in the scene, all light is blocked and we can stop immediately. */
payload.throughput = 0.0f;
return false;
#else
/* Detect if this surface has a shader with transparent shadows. */
/* TODO: optimize so primitive visibility flag indicates if the primitive has a transparent
* shadow shader? */
const int shader_flags = intersection_get_shader_flags(kg, isect.prim, isect.type);
if ((shader_flags & SD_HAS_TRANSPARENT_SHADOW) == 0) {
/* No transparent shadows for the shader, all light is blocked, and we can stop immediately. */
payload.throughput = 0.0f;
return false;
}
/* Fetch commonly accessed payload data, ensuring that it is used from either register to a
* stack, without going to the global memory. */
uint num_recorded_hits = payload.num_recorded_hits;
/* If the intersection is already recorded, ignore it completely: don't update throughput as it
* has already been updated. But also don't count it for num_hits as that could result in a
* situation when the same ray will be considered transparent when spatial split is off and be
* opaque when spatial split is on. Since curves do not record intersections, there is
* a possibility for optimization:
* - Don't compile this code if the filter is only used for curve primitives.
* - Don't run the check if the current intersection comes from the curve, as it will not match
* any recorded intersection anyway.
*
* NOTE: Currently, spatial splits are not used with OptiX, so there is no need to check whether
* the intersection has been already recorded. */
# if !defined(__KERNEL_OPTIX__)
if constexpr ((enabled_primitive_types & (PRIMITIVE_ALL & ~PRIMITIVE_CURVE)) != 0) {
if ((isect.type & PRIMITIVE_CURVE) == 0) {
if (intersection_skip_shadow_already_recoded(
state, isect.object, isect.prim, num_recorded_hits))
{
return true;
}
}
}
# endif
/* Only count transparent bounces, volume bounds bounces are counted when shading. */
payload.num_transparent_hits += !(shader_flags & SD_HAS_ONLY_VOLUME);
if (payload.num_transparent_hits > payload.max_transparent_hits) {
/* The maximum number of intersections has been reached, consider that all light has been
* blocked. */
payload.throughput = 0.0f;
return false;
}
# if defined(__HAIR__)
if constexpr ((enabled_primitive_types & PRIMITIVE_CURVE) != 0) {
/* Always use baked shadow transparency for curves. */
if (isect.type & PRIMITIVE_CURVE) {
payload.throughput *= intersection_curve_shadow_transparency(
kg, isect.object, isect.prim, isect.type, isect.u);
if (payload.throughput < CURVE_SHADOW_TRANSPARENCY_CUTOFF) {
/* Light attenuated too much through the curve intersections, assume all light is blocked
* and do early output. */
payload.throughput = 0.0f;
return false;
}
/* Don't record the intersection as the throughput has been already modified here.
* Simply continue BVH traversal for other intersections. */
return true;
}
}
# endif
/* If the filter function only handles curves, it is known for the fact that nothing is to be
* recorded: curves accumulated baked transparency. Skip this code for a curve-only case. */
if constexpr ((enabled_primitive_types & (PRIMITIVE_ALL & ~PRIMITIVE_CURVE)) != 0) {
/* Always increase the number of recorded hits, even beyond the maximum, so that we can detect
* this and trace another ray if needed. */
num_recorded_hits += 1;
payload.num_recorded_hits = num_recorded_hits;
constexpr uint max_record_hits = INTEGRATOR_SHADOW_ISECT_SIZE;
if (num_recorded_hits <= max_record_hits || isect.t < payload.max_record_isect_t) {
integrator_state_write_shadow_isect(state, &isect, payload.record_isect_index);
if (num_recorded_hits >= max_record_hits) {
/* If the maximum number of hits is reached, find the furthest intersection to replace it
* with the next closer one. We want the N closest intersections. */
uint record_isect_index = 0;
float tmax_hits = INTEGRATOR_STATE_ARRAY(state, shadow_isect, 0, t);
for (uint i = 1; i < max_record_hits; ++i) {
const float isect_t = INTEGRATOR_STATE_ARRAY(state, shadow_isect, i, t);
if (isect_t > tmax_hits) {
record_isect_index = i;
tmax_hits = isect_t;
}
}
payload.max_record_isect_t = tmax_hits;
payload.record_isect_index = record_isect_index;
}
else {
payload.record_isect_index = num_recorded_hits;
}
}
}
return true;
#endif
}
/* Filter intersection to intersections with only primitives with volume shader.
*
* Expected to be called only on a triangle primitive. The caller is to filter out intersections
* with non-triangle primitives.
*
* Returns false if the primitive is not to be filtered out (accepted), true if the primitive is to
* be ignored. */
template<bool do_visibility_check = true>
ccl_device_forceinline bool bvh_volume_anyhit_triangle_filter(
KernelGlobals kg,
const int object,
const int prim,
const ccl_ray_data RaySelfPrimitives &ccl_restrict ray_self,
const uint ray_visibility)
{
#ifdef __VISIBILITY_FLAG__
if constexpr (do_visibility_check) {
if ((kernel_data_fetch(objects, object).visibility & ray_visibility) == 0) {
return true;
}
}
#endif
if ((kernel_data_fetch(object_flag, object) & SD_OBJECT_HAS_VOLUME) == 0) {
return true;
}
if (intersection_skip_self(ray_self, object, prim)) {
return true;
}
const int shader = kernel_data_fetch(tri_shader, prim);
const int shader_flag = kernel_data_fetch(shaders, (shader & SHADER_MASK)).flags;
if (!(shader_flag & SD_HAS_VOLUME)) {
return true;
}
return false;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,235 @@
/* SPDX-FileCopyrightText: 2009-2010 NVIDIA Corporation
* SPDX-FileCopyrightText: 2009-2012 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*
* Adapted from code by Intel & NVIDIA. */
#if BVH_FEATURE(BVH_HAIR)
# define NODE_INTERSECT bvh_node_intersect
#else
# define NODE_INTERSECT bvh_aligned_node_intersect
#endif
/* This is a template BVH traversal function for finding local intersections
* around the shading point, for subsurface scattering and bevel. We disable
* various features for performance, and for instanced objects avoid traversing
* other parts of the scene.
*
* BVH_MOTION: motion blur rendering
*/
#ifndef __KERNEL_GPU__
ccl_device
#else
ccl_device_inline
#endif
bool
BVH_FUNCTION_FULL_NAME(BVH)(KernelGlobals kg,
const ccl_private Ray *ray,
ccl_private LocalIntersection *local_isect,
const int local_object,
ccl_private uint *lcg_state,
const int max_hits)
{
/* todo:
* - test if pushing distance on the stack helps (for non shadow rays)
* - separate version for shadow rays
* - likely and unlikely for if() statements
* - test restrict attribute for pointers
*/
/* traversal stack in CUDA thread-local memory */
int traversal_stack[BVH_STACK_SIZE];
traversal_stack[0] = ENTRYPOINT_SENTINEL;
/* traversal variables in registers */
int stack_ptr = 0;
int node_addr = kernel_data_fetch(object_node, local_object);
/* ray parameters in registers */
float3 P = ray->P;
float3 dir = bvh_clamp_direction(ray->D);
float3 idir = bvh_inverse_direction(dir);
float tmin = ray->tmin;
int object = OBJECT_NONE;
float isect_t = ray->tmax;
if (local_isect != nullptr) {
local_isect->num_hits = 0;
}
kernel_assert((local_isect == nullptr) == (max_hits == 0));
const uint object_flag = kernel_data_fetch(object_flag, local_object);
if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
#if BVH_FEATURE(BVH_MOTION)
bvh_instance_motion_push(kg, local_object, ray, &P, &dir, &idir);
#else
bvh_instance_push(kg, local_object, ray, &P, &dir, &idir);
#endif
object = local_object;
}
/* traversal loop */
do {
do {
/* traverse internal nodes */
while (node_addr >= 0 && node_addr != ENTRYPOINT_SENTINEL) {
int node_addr_child1, traverse_mask;
float dist[2];
float4 cnodes = kernel_data_fetch(bvh_nodes, node_addr + 0);
traverse_mask = NODE_INTERSECT(kg,
P,
#if BVH_FEATURE(BVH_HAIR)
dir,
#endif
idir,
tmin,
isect_t,
node_addr,
PATH_RAY_VISIBILITY_ALL,
dist);
node_addr = __float_as_int(cnodes.z);
node_addr_child1 = __float_as_int(cnodes.w);
if (traverse_mask == 3) {
/* Both children were intersected, push the farther one. */
bool is_closest_child1 = (dist[1] < dist[0]);
if (is_closest_child1) {
int tmp = node_addr;
node_addr = node_addr_child1;
node_addr_child1 = tmp;
}
++stack_ptr;
kernel_assert(stack_ptr < BVH_STACK_SIZE);
traversal_stack[stack_ptr] = node_addr_child1;
}
else {
/* One child was intersected. */
if (traverse_mask == 2) {
node_addr = node_addr_child1;
}
else if (traverse_mask == 0) {
/* Neither child was intersected. */
node_addr = traversal_stack[stack_ptr];
--stack_ptr;
}
}
}
/* if node is leaf, fetch triangle list */
if (node_addr < 0) {
float4 leaf = kernel_data_fetch(bvh_leaf_nodes, (-node_addr - 1));
int prim_addr = __float_as_int(leaf.x);
const int prim_addr2 = __float_as_int(leaf.y);
const uint type = __float_as_int(leaf.w);
/* pop */
node_addr = traversal_stack[stack_ptr];
--stack_ptr;
/* primitive intersection */
switch (type & PRIMITIVE_ALL) {
case PRIMITIVE_TRIANGLE: {
/* intersect ray against primitive */
for (; prim_addr < prim_addr2; prim_addr++) {
kernel_assert((kernel_data_fetch(prim_type, prim_addr) & PRIMITIVE_ALL) ==
(type & PRIMITIVE_ALL));
/* Only intersect with matching object, for instanced objects we
* already know we are only intersecting the right object. */
if (object == OBJECT_NONE) {
if (kernel_data_fetch(prim_object, prim_addr) != local_object) {
continue;
}
}
/* Skip self intersection. */
const int prim = kernel_data_fetch(prim_index, prim_addr);
if (intersection_skip_self_local(ray->self, prim)) {
continue;
}
if (triangle_intersect_local(kg,
local_isect,
P,
dir,
local_object,
prim,
tmin,
isect_t,
lcg_state,
max_hits))
{
return true;
}
}
break;
}
#if BVH_FEATURE(BVH_MOTION)
case PRIMITIVE_MOTION_TRIANGLE: {
/* intersect ray against primitive */
for (; prim_addr < prim_addr2; prim_addr++) {
kernel_assert((kernel_data_fetch(prim_type, prim_addr) & PRIMITIVE_ALL) ==
(type & PRIMITIVE_ALL));
/* Only intersect with matching object, for instanced objects we
* already know we are only intersecting the right object. */
if (object == OBJECT_NONE) {
if (kernel_data_fetch(prim_object, prim_addr) != local_object) {
continue;
}
}
/* Skip self intersection. */
const int prim = kernel_data_fetch(prim_index, prim_addr);
if (intersection_skip_self_local(ray->self, prim)) {
continue;
}
if (motion_triangle_intersect_local(kg,
local_isect,
P,
dir,
ray->time,
local_object,
prim,
tmin,
isect_t,
lcg_state,
max_hits))
{
return true;
}
}
break;
}
#endif
default: {
break;
}
}
}
} while (node_addr != ENTRYPOINT_SENTINEL);
} while (node_addr != ENTRYPOINT_SENTINEL);
return false;
}
ccl_device_inline bool BVH_FUNCTION_NAME(KernelGlobals kg,
const ccl_private Ray *ray,
ccl_private LocalIntersection *local_isect,
const int local_object,
ccl_private uint *lcg_state,
const int max_hits)
{
return BVH_FUNCTION_FULL_NAME(BVH)(kg, ray, local_isect, local_object, lcg_state, max_hits);
}
#undef BVH_FUNCTION_NAME
#undef BVH_FUNCTION_FEATURES
#undef NODE_INTERSECT

View File

@@ -0,0 +1,149 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "kernel/geom/object.h"
#include "kernel/globals.h"
CCL_NAMESPACE_BEGIN
// TODO(sergey): Look into avoid use of full Transform and use 3x3 matrix and
// 3-vector which might be faster.
ccl_device_forceinline Transform bvh_unaligned_node_fetch_space(KernelGlobals kg,
const int node_addr,
const int child)
{
Transform space;
const int child_addr = node_addr + child * 3;
space.x = kernel_data_fetch(bvh_nodes, child_addr + 1);
space.y = kernel_data_fetch(bvh_nodes, child_addr + 2);
space.z = kernel_data_fetch(bvh_nodes, child_addr + 3);
return space;
}
ccl_device_forceinline int bvh_aligned_node_intersect(KernelGlobals kg,
const float3 P,
const float3 idir,
const float tmin,
const float tmax,
const int node_addr,
const uint visibility,
float dist[2])
{
/* fetch node data */
#ifdef __VISIBILITY_FLAG__
float4 cnodes = kernel_data_fetch(bvh_nodes, node_addr + 0);
#endif
float4 node0 = kernel_data_fetch(bvh_nodes, node_addr + 1);
float4 node1 = kernel_data_fetch(bvh_nodes, node_addr + 2);
float4 node2 = kernel_data_fetch(bvh_nodes, node_addr + 3);
/* intersect ray against child nodes */
float c0lox = (node0.x - P.x) * idir.x;
float c0hix = (node0.z - P.x) * idir.x;
float c0loy = (node1.x - P.y) * idir.y;
float c0hiy = (node1.z - P.y) * idir.y;
float c0loz = (node2.x - P.z) * idir.z;
float c0hiz = (node2.z - P.z) * idir.z;
float c0min = max4(tmin, min(c0lox, c0hix), min(c0loy, c0hiy), min(c0loz, c0hiz));
float c0max = min4(tmax, max(c0lox, c0hix), max(c0loy, c0hiy), max(c0loz, c0hiz));
float c1lox = (node0.y - P.x) * idir.x;
float c1hix = (node0.w - P.x) * idir.x;
float c1loy = (node1.y - P.y) * idir.y;
float c1hiy = (node1.w - P.y) * idir.y;
float c1loz = (node2.y - P.z) * idir.z;
float c1hiz = (node2.w - P.z) * idir.z;
float c1min = max4(tmin, min(c1lox, c1hix), min(c1loy, c1hiy), min(c1loz, c1hiz));
float c1max = min4(tmax, max(c1lox, c1hix), max(c1loy, c1hiy), max(c1loz, c1hiz));
dist[0] = c0min;
dist[1] = c1min;
#ifdef __VISIBILITY_FLAG__
/* this visibility test gives a 5% performance hit, how to solve? */
return (((c0max >= c0min) && (__float_as_uint(cnodes.x) & visibility)) ? 1 : 0) |
(((c1max >= c1min) && (__float_as_uint(cnodes.y) & visibility)) ? 2 : 0);
#else
return ((c0max >= c0min) ? 1 : 0) | ((c1max >= c1min) ? 2 : 0);
#endif
}
ccl_device_forceinline bool bvh_unaligned_node_intersect_child(KernelGlobals kg,
const float3 P,
const float3 dir,
const float tmin,
const float tmax,
const int node_addr,
const int child,
float dist[2])
{
Transform space = bvh_unaligned_node_fetch_space(kg, node_addr, child);
float3 aligned_dir = transform_direction(&space, dir);
float3 aligned_P = transform_point(&space, P);
float3 nrdir = -bvh_inverse_direction(aligned_dir);
float3 lower_xyz = aligned_P * nrdir;
float3 upper_xyz = lower_xyz - nrdir;
const float near_x = min(lower_xyz.x, upper_xyz.x);
const float near_y = min(lower_xyz.y, upper_xyz.y);
const float near_z = min(lower_xyz.z, upper_xyz.z);
const float far_x = max(lower_xyz.x, upper_xyz.x);
const float far_y = max(lower_xyz.y, upper_xyz.y);
const float far_z = max(lower_xyz.z, upper_xyz.z);
const float tnear = max4(tmin, near_x, near_y, near_z);
const float tfar = min4(tmax, far_x, far_y, far_z);
*dist = tnear;
return tnear <= tfar;
}
ccl_device_forceinline int bvh_unaligned_node_intersect(KernelGlobals kg,
const float3 P,
const float3 dir,
const float tmin,
const float tmax,
const int node_addr,
const uint visibility,
float dist[2])
{
int mask = 0;
#ifdef __VISIBILITY_FLAG__
float4 cnodes = kernel_data_fetch(bvh_nodes, node_addr + 0);
#endif
if (bvh_unaligned_node_intersect_child(kg, P, dir, tmin, tmax, node_addr, 0, &dist[0])) {
#ifdef __VISIBILITY_FLAG__
if ((__float_as_uint(cnodes.x) & visibility))
#endif
{
mask |= 1;
}
}
if (bvh_unaligned_node_intersect_child(kg, P, dir, tmin, tmax, node_addr, 1, &dist[1])) {
#ifdef __VISIBILITY_FLAG__
if ((__float_as_uint(cnodes.y) & visibility))
#endif
{
mask |= 2;
}
}
return mask;
}
ccl_device_forceinline int bvh_node_intersect(KernelGlobals kg,
const float3 P,
const float3 dir,
const float3 idir,
const float tmin,
const float tmax,
const int node_addr,
const uint visibility,
float dist[2])
{
float4 node = kernel_data_fetch(bvh_nodes, node_addr);
if (__float_as_uint(node.x) & PATH_RAY_VISIBILITY_NODE_UNALIGNED) {
return bvh_unaligned_node_intersect(kg, P, dir, tmin, tmax, node_addr, visibility, dist);
}
return bvh_aligned_node_intersect(kg, P, idir, tmin, tmax, node_addr, visibility, dist);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,269 @@
/* SPDX-FileCopyrightText: 2009-2010 NVIDIA Corporation
* SPDX-FileCopyrightText: 2009-2012 Intel Corporation
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0
*
* Adapted code from NVIDIA Corporation. */
#if BVH_FEATURE(BVH_HAIR)
# define NODE_INTERSECT bvh_node_intersect
#else
# define NODE_INTERSECT bvh_aligned_node_intersect
#endif
/* This is a template BVH traversal function, where various features can be
* enabled/disabled. This way we can compile optimized versions for each case
* without new features slowing things down.
*
* BVH_HAIR: hair curve rendering
* BVH_POINTCLOUD: point cloud rendering
* BVH_MOTION: motion blur rendering
*/
#ifndef __KERNEL_GPU__
ccl_device
#else
ccl_device_inline
#endif
void
BVH_FUNCTION_FULL_NAME(BVH)(KernelGlobals kg,
const ccl_private Ray *ccl_restrict ray,
ccl_private BVHShadowAllPayload &ccl_restrict payload)
{
/* todo:
* - likely and unlikely for if() statements
* - test restrict attribute for pointers
*/
/* traversal stack in CUDA thread-local memory */
int traversal_stack[BVH_STACK_SIZE];
traversal_stack[0] = ENTRYPOINT_SENTINEL;
/* traversal variables in registers */
int stack_ptr = 0;
int node_addr = kernel_data.bvh.root;
/* ray parameters in registers */
float3 P = ray->P;
float3 dir = bvh_clamp_direction(ray->D);
float3 idir = bvh_inverse_direction(dir);
float tmin = ray->tmin;
int object = OBJECT_NONE;
/* Max distance in world space. May be dynamically reduced when max number of recorded hits is
* exceeded and we no longer need to find hits beyond the max distance found. */
const float tmax = ray->tmax;
const uint visibility = payload.base.ray_visibility;
/* traversal loop */
do {
do {
/* traverse internal nodes */
while (node_addr >= 0 && node_addr != ENTRYPOINT_SENTINEL) {
int node_addr_child1, traverse_mask;
float dist[2];
float4 cnodes = kernel_data_fetch(bvh_nodes, node_addr + 0);
traverse_mask = NODE_INTERSECT(kg,
P,
#if BVH_FEATURE(BVH_HAIR)
dir,
#endif
idir,
tmin,
tmax,
node_addr,
visibility,
dist);
node_addr = __float_as_int(cnodes.z);
node_addr_child1 = __float_as_int(cnodes.w);
if (traverse_mask == 3) {
/* Both children were intersected, push the farther one. */
bool is_closest_child1 = (dist[1] < dist[0]);
if (is_closest_child1) {
int tmp = node_addr;
node_addr = node_addr_child1;
node_addr_child1 = tmp;
}
++stack_ptr;
kernel_assert(stack_ptr < BVH_STACK_SIZE);
traversal_stack[stack_ptr] = node_addr_child1;
}
else {
/* One child was intersected. */
if (traverse_mask == 2) {
node_addr = node_addr_child1;
}
else if (traverse_mask == 0) {
/* Neither child was intersected. */
node_addr = traversal_stack[stack_ptr];
--stack_ptr;
}
}
}
/* if node is leaf, fetch triangle list */
if (node_addr < 0) {
float4 leaf = kernel_data_fetch(bvh_leaf_nodes, (-node_addr - 1));
int prim_addr = __float_as_int(leaf.x);
if (prim_addr >= 0) {
const int prim_addr2 = __float_as_int(leaf.y);
const uint type = __float_as_int(leaf.w);
/* pop */
node_addr = traversal_stack[stack_ptr];
--stack_ptr;
/* primitive intersection */
for (; prim_addr < prim_addr2; prim_addr++) {
kernel_assert((kernel_data_fetch(prim_type, prim_addr) & PRIMITIVE_ALL) ==
(type & PRIMITIVE_ALL));
bool hit;
/* todo: specialized intersect functions which don't fill in
* isect unless needed and check SD_HAS_TRANSPARENT_SHADOW?
* might give a few % performance improvement */
Intersection isect ccl_optional_struct_init;
const int prim_object = (object == OBJECT_NONE) ?
kernel_data_fetch(prim_object, prim_addr) :
object;
const int prim = kernel_data_fetch(prim_index, prim_addr);
if (intersection_skip_self_shadow(ray->self, prim_object, prim)) {
continue;
}
#ifdef __SHADOW_LINKING__
if (intersection_skip_shadow_link(kg, ray->self, prim_object)) {
continue;
}
#endif
switch (type & PRIMITIVE_ALL) {
case PRIMITIVE_TRIANGLE: {
hit = triangle_intersect(
kg, &isect, P, dir, tmin, tmax, visibility, prim_object, prim, prim_addr);
break;
}
#if BVH_FEATURE(BVH_MOTION)
case PRIMITIVE_MOTION_TRIANGLE: {
hit = motion_triangle_intersect(kg,
&isect,
P,
dir,
tmin,
tmax,
ray->time,
visibility,
prim_object,
prim,
prim_addr);
break;
}
#endif
#if BVH_FEATURE(BVH_HAIR) && defined(__HAIR__)
case PRIMITIVE_CURVE_THICK:
case PRIMITIVE_MOTION_CURVE_THICK:
case PRIMITIVE_CURVE_RIBBON:
case PRIMITIVE_MOTION_CURVE_RIBBON:
case PRIMITIVE_CURVE_THICK_LINEAR:
case PRIMITIVE_MOTION_CURVE_THICK_LINEAR: {
if ((type & PRIMITIVE_MOTION) && kernel_data.bvh.use_bvh_steps) {
const float2 prim_time = kernel_data_fetch(prim_time, prim_addr);
if (ray->time < prim_time.x || ray->time > prim_time.y) {
hit = false;
break;
}
}
const int curve_type = kernel_data_fetch(prim_type, prim_addr);
hit = curve_intersect(
kg, &isect, P, dir, tmin, tmax, prim_object, prim, ray->time, curve_type);
break;
}
#endif
#if BVH_FEATURE(BVH_POINTCLOUD) && defined(__POINTCLOUD__)
case PRIMITIVE_POINT:
case PRIMITIVE_MOTION_POINT: {
if ((type & PRIMITIVE_MOTION) && kernel_data.bvh.use_bvh_steps) {
const float2 prim_time = kernel_data_fetch(prim_time, prim_addr);
if (ray->time < prim_time.x || ray->time > prim_time.y) {
hit = false;
break;
}
}
const int point_type = kernel_data_fetch(prim_type, prim_addr);
hit = point_intersect(
kg, &isect, P, dir, tmin, tmax, prim_object, prim, ray->time, point_type);
break;
}
#endif /* BVH_FEATURE(BVH_POINTCLOUD) */
default: {
hit = false;
break;
}
}
if (hit) {
if (!bvh_shadow_all_anyhit_filter<ISECT_TEST_NONE>(kg,
payload.state,
payload,
payload.base.ray_self,
payload.base.ray_visibility,
isect))
{
return;
}
}
}
}
else {
/* instance push */
object = kernel_data_fetch(prim_object, -prim_addr - 1);
#if BVH_FEATURE(BVH_MOTION)
bvh_instance_motion_push(kg, object, ray, &P, &dir, &idir);
#else
bvh_instance_push(kg, object, ray, &P, &dir, &idir);
#endif
++stack_ptr;
kernel_assert(stack_ptr < BVH_STACK_SIZE);
traversal_stack[stack_ptr] = ENTRYPOINT_SENTINEL;
node_addr = kernel_data_fetch(object_node, object);
}
}
} while (node_addr != ENTRYPOINT_SENTINEL);
if (stack_ptr >= 0) {
kernel_assert(object != OBJECT_NONE);
/* Instance pop. */
bvh_instance_pop(ray, &P, &dir, &idir);
object = OBJECT_NONE;
node_addr = traversal_stack[stack_ptr];
--stack_ptr;
}
} while (node_addr != ENTRYPOINT_SENTINEL);
}
ccl_device_inline void BVH_FUNCTION_NAME(KernelGlobals kg,
const ccl_private Ray *ccl_restrict ray,
ccl_private BVHShadowAllPayload &ccl_restrict payload)
{
BVH_FUNCTION_FULL_NAME(BVH)(kg, ray, payload);
}
#undef BVH_FUNCTION_NAME
#undef BVH_FUNCTION_FEATURES
#undef NODE_INTERSECT

View File

@@ -0,0 +1,273 @@
/* SPDX-FileCopyrightText: 2009-2010 NVIDIA Corporation
* SPDX-FileCopyrightText: 2009-2012 Intel Corporation
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0
*
* Adapted code from NVIDIA Corporation. */
#if BVH_FEATURE(BVH_HAIR)
# define NODE_INTERSECT bvh_node_intersect
#else
# define NODE_INTERSECT bvh_aligned_node_intersect
#endif
/* This is a template BVH traversal function, where various features can be
* enabled/disabled. This way we can compile optimized versions for each case
* without new features slowing things down.
*
* BVH_HAIR: hair curve rendering
* BVH_POINTCLOUD: point cloud rendering
* BVH_MOTION: motion blur rendering
*/
ccl_device_noinline bool BVH_FUNCTION_FULL_NAME(BVH)(KernelGlobals kg,
const ccl_private Ray *ray,
ccl_private Intersection *isect,
const uint visibility)
{
/* todo:
* - test if pushing distance on the stack helps (for non shadow rays)
* - separate version for shadow rays
* - likely and unlikely for if() statements
* - test restrict attribute for pointers
*/
/* traversal stack in CUDA thread-local memory */
int traversal_stack[BVH_STACK_SIZE];
traversal_stack[0] = ENTRYPOINT_SENTINEL;
/* traversal variables in registers */
int stack_ptr = 0;
int node_addr = kernel_data.bvh.root;
/* ray parameters in registers */
float3 P = ray->P;
float3 dir = bvh_clamp_direction(ray->D);
float3 idir = bvh_inverse_direction(dir);
const float tmin = ray->tmin;
int object = OBJECT_NONE;
isect->t = ray->tmax;
isect->u = 0.0f;
isect->v = 0.0f;
isect->prim = PRIM_NONE;
isect->object = OBJECT_NONE;
/* traversal loop */
do {
do {
/* traverse internal nodes */
while (node_addr >= 0 && node_addr != ENTRYPOINT_SENTINEL) {
int node_addr_child1, traverse_mask;
float dist[2];
float4 cnodes = kernel_data_fetch(bvh_nodes, node_addr + 0);
{
traverse_mask = NODE_INTERSECT(kg,
P,
#if BVH_FEATURE(BVH_HAIR)
dir,
#endif
idir,
tmin,
isect->t,
node_addr,
visibility,
dist);
}
node_addr = __float_as_int(cnodes.z);
node_addr_child1 = __float_as_int(cnodes.w);
if (traverse_mask == 3) {
/* Both children were intersected, push the farther one. */
bool is_closest_child1 = (dist[1] < dist[0]);
if (is_closest_child1) {
int tmp = node_addr;
node_addr = node_addr_child1;
node_addr_child1 = tmp;
}
++stack_ptr;
kernel_assert(stack_ptr < BVH_STACK_SIZE);
traversal_stack[stack_ptr] = node_addr_child1;
}
else {
/* One child was intersected. */
if (traverse_mask == 2) {
node_addr = node_addr_child1;
}
else if (traverse_mask == 0) {
/* Neither child was intersected. */
node_addr = traversal_stack[stack_ptr];
--stack_ptr;
}
}
}
/* if node is leaf, fetch triangle list */
if (node_addr < 0) {
float4 leaf = kernel_data_fetch(bvh_leaf_nodes, (-node_addr - 1));
int prim_addr = __float_as_int(leaf.x);
if (prim_addr >= 0) {
const int prim_addr2 = __float_as_int(leaf.y);
const uint type = __float_as_int(leaf.w);
/* pop */
node_addr = traversal_stack[stack_ptr];
--stack_ptr;
/* primitive intersection */
for (; prim_addr < prim_addr2; prim_addr++) {
const int prim_object = (object == OBJECT_NONE) ?
kernel_data_fetch(prim_object, prim_addr) :
object;
const int prim = kernel_data_fetch(prim_index, prim_addr);
if (intersection_skip_self_shadow(ray->self, prim_object, prim)) {
continue;
}
#ifdef __SHADOW_LINKING__
if (intersection_skip_shadow_link(kg, ray->self, prim_object)) {
continue;
}
#endif
switch (type & PRIMITIVE_ALL) {
case PRIMITIVE_TRIANGLE: {
if (triangle_intersect(kg,
isect,
P,
dir,
tmin,
isect->t,
visibility,
prim_object,
prim,
prim_addr))
{
/* shadow ray early termination */
if (visibility & PATH_RAY_VISIBILITY_SHADOW_OPAQUE) {
return true;
}
}
break;
}
#if BVH_FEATURE(BVH_MOTION)
case PRIMITIVE_MOTION_TRIANGLE: {
if (motion_triangle_intersect(kg,
isect,
P,
dir,
tmin,
isect->t,
ray->time,
visibility,
prim_object,
prim,
prim_addr))
{
/* shadow ray early termination */
if (visibility & PATH_RAY_VISIBILITY_SHADOW_OPAQUE)
return true;
}
break;
}
#endif /* BVH_FEATURE(BVH_MOTION) */
#if BVH_FEATURE(BVH_HAIR) && defined(__HAIR__)
case PRIMITIVE_CURVE_THICK:
case PRIMITIVE_MOTION_CURVE_THICK:
case PRIMITIVE_CURVE_RIBBON:
case PRIMITIVE_MOTION_CURVE_RIBBON:
case PRIMITIVE_CURVE_THICK_LINEAR:
case PRIMITIVE_MOTION_CURVE_THICK_LINEAR: {
if ((type & PRIMITIVE_MOTION) && kernel_data.bvh.use_bvh_steps) {
const float2 prim_time = kernel_data_fetch(prim_time, prim_addr);
if (ray->time < prim_time.x || ray->time > prim_time.y) {
break;
}
}
const int curve_type = kernel_data_fetch(prim_type, prim_addr);
const bool hit = curve_intersect(
kg, isect, P, dir, tmin, isect->t, prim_object, prim, ray->time, curve_type);
if (hit) {
/* shadow ray early termination */
if (visibility & PATH_RAY_VISIBILITY_SHADOW_OPAQUE)
return true;
}
break;
}
#endif /* BVH_FEATURE(BVH_HAIR) */
#if BVH_FEATURE(BVH_POINTCLOUD) && defined(__POINTCLOUD__)
case PRIMITIVE_POINT:
case PRIMITIVE_MOTION_POINT: {
if ((type & PRIMITIVE_MOTION) && kernel_data.bvh.use_bvh_steps) {
const float2 prim_time = kernel_data_fetch(prim_time, prim_addr);
if (ray->time < prim_time.x || ray->time > prim_time.y) {
break;
}
}
const int point_type = kernel_data_fetch(prim_type, prim_addr);
const bool hit = point_intersect(
kg, isect, P, dir, tmin, isect->t, prim_object, prim, ray->time, point_type);
if (hit) {
/* shadow ray early termination */
if (visibility & PATH_RAY_VISIBILITY_SHADOW_OPAQUE)
return true;
}
break;
}
#endif /* BVH_FEATURE(BVH_POINTCLOUD) */
}
}
}
else {
/* instance push */
object = kernel_data_fetch(prim_object, -prim_addr - 1);
#if BVH_FEATURE(BVH_MOTION)
bvh_instance_motion_push(kg, object, ray, &P, &dir, &idir);
#else
bvh_instance_push(kg, object, ray, &P, &dir, &idir);
#endif
++stack_ptr;
kernel_assert(stack_ptr < BVH_STACK_SIZE);
traversal_stack[stack_ptr] = ENTRYPOINT_SENTINEL;
node_addr = kernel_data_fetch(object_node, object);
}
}
} while (node_addr != ENTRYPOINT_SENTINEL);
if (stack_ptr >= 0) {
kernel_assert(object != OBJECT_NONE);
/* instance pop */
bvh_instance_pop(ray, &P, &dir, &idir);
object = OBJECT_NONE;
node_addr = traversal_stack[stack_ptr];
--stack_ptr;
}
} while (node_addr != ENTRYPOINT_SENTINEL);
return (isect->prim != PRIM_NONE);
}
ccl_device_inline bool BVH_FUNCTION_NAME(KernelGlobals kg,
const ccl_private Ray *ray,
ccl_private Intersection *isect,
const uint visibility)
{
return BVH_FUNCTION_FULL_NAME(BVH)(kg, ray, isect, visibility);
}
#undef BVH_FUNCTION_NAME
#undef BVH_FUNCTION_FEATURES
#undef NODE_INTERSECT

View File

@@ -0,0 +1,33 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
CCL_NAMESPACE_BEGIN
/* Don't inline intersect functions on GPU, this is faster */
#ifdef __KERNEL_GPU__
# define ccl_device_intersect ccl_device_forceinline
#else
# define ccl_device_intersect ccl_device_inline
#endif
/* bottom-most stack entry, indicating the end of traversal */
#define ENTRYPOINT_SENTINEL 0x76543210
/* 64 object BVH + 64 mesh BVH + 64 object node splitting */
#define BVH_STACK_SIZE 192
/* BVH intersection function variations */
#define BVH_MOTION 1
#define BVH_HAIR 2
#define BVH_POINTCLOUD 4
#define BVH_NAME_JOIN(x, y) x##_##y
#define BVH_NAME_EVAL(x, y) BVH_NAME_JOIN(x, y)
#define BVH_FUNCTION_FULL_NAME(prefix) BVH_NAME_EVAL(prefix, BVH_FUNCTION_NAME)
#define BVH_FEATURE(f) (((BVH_FUNCTION_FEATURES) & (f)) != 0)
CCL_NAMESPACE_END

View File

@@ -0,0 +1,313 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/globals.h"
#include "kernel/integrator/state.h"
#include "kernel/types.h"
CCL_NAMESPACE_BEGIN
ccl_device_inline bool intersection_ray_valid(const ccl_private Ray *ray)
{
/* NOTE: Due to some vectorization code non-finite origin point might
* cause lots of false-positive intersections which will overflow traversal
* stack.
* This code is a quick way to perform early output, to avoid crashes in
* such cases.
* From production scenes so far it seems it's enough to test first element
* only.
* Scene intersection may also called with empty rays for conditional trace
* calls that evaluate to false, so filter those out.
*/
return isfinite_safe(ray->P.x) && isfinite_safe(ray->D.x) && len_squared(ray->D) != 0.0f &&
ray->tmin < FLT_MAX;
}
/* Offset intersection distance by the smallest possible amount, to skip
* intersections at this distance. This works in cases where the ray start
* position is unchanged and only tmin is updated, since for self
* intersection we'll be comparing against the exact same distances.
*
* Always returns normalized floating point value. */
ccl_device_forceinline float intersection_t_offset(const float t)
{
/* This is a simplified version of `nextafterf(t, FLT_MAX)`, only dealing with
* non-negative and finite t. */
kernel_assert(t >= 0.0f && isfinite_safe(t));
/* Special handling of zero, which also includes handling of denormal values:
* always return smallest normalized value. If a denormalized zero is returned
* it will cause false-positive intersection detection with a distance of 0.
*
* The check relies on the fact that comparison of denormal values with zero
* returns true. */
if (t == 0.0f) {
/* The exact bit value of this should be 0x1p-126, but hex floating point values notation is
* not available in CUDA/OptiX. */
return FLT_MIN;
}
const uint32_t bits = __float_as_uint(t) + 1;
const float result = __uint_as_float(bits);
/* Assert that the calculated value is indeed considered to be offset from the
* original value. */
kernel_assert(result > t);
return result;
}
/* Ray offset to avoid self intersection.
*
* This function can be used to compute a modified ray start position for rays
* leaving from a surface. This is from:
* "A Fast and Robust Method for Avoiding Self-Intersection"
* Ray Tracing Gems, chapter 6.
*/
ccl_device_inline float3 ray_offset(const float3 P, const float3 Ng)
{
const float int_scale = 256.0f;
const int3 of_i = make_int3(
(int)(int_scale * Ng.x), (int)(int_scale * Ng.y), (int)(int_scale * Ng.z));
const float3 p_i = make_float3(
__int_as_float(__float_as_int(P.x) + ((P.x < 0) ? -of_i.x : of_i.x)),
__int_as_float(__float_as_int(P.y) + ((P.y < 0) ? -of_i.y : of_i.y)),
__int_as_float(__float_as_int(P.z) + ((P.z < 0) ? -of_i.z : of_i.z)));
const float origin = 1.0f / 32.0f;
const float float_scale = 1.0f / 65536.0f;
return make_float3(fabsf(P.x) < origin ? P.x + float_scale * Ng.x : p_i.x,
fabsf(P.y) < origin ? P.y + float_scale * Ng.y : p_i.y,
fabsf(P.z) < origin ? P.z + float_scale * Ng.z : p_i.z);
}
#ifndef __KERNEL_GPU__
ccl_device int intersections_compare(const void *a, const void *b)
{
const Intersection *isect_a = (const Intersection *)a;
const Intersection *isect_b = (const Intersection *)b;
if (isect_a->t < isect_b->t) {
return -1;
}
if (isect_a->t > isect_b->t) {
return 1;
}
return 0;
}
#endif
/* For subsurface scattering, only sorting a small amount of intersections
* so bubble sort is fine for CPU and GPU. */
ccl_device_inline void sort_intersections_and_normals(ccl_private Intersection *hits,
ccl_private float3 *Ng,
uint num_hits)
{
bool swapped;
do {
swapped = false;
for (uint j = 0; j < num_hits - 1; ++j) {
if (hits[j].t > hits[j + 1].t) {
Intersection tmp_hit = hits[j];
float3 tmp_Ng = Ng[j];
hits[j] = hits[j + 1];
Ng[j] = Ng[j + 1];
hits[j + 1] = tmp_hit;
Ng[j + 1] = tmp_Ng;
swapped = true;
}
}
--num_hits;
} while (swapped);
}
/* Utility to quickly get flags from an intersection. */
ccl_device_forceinline int intersection_get_shader_flags(KernelGlobals kg,
const int prim,
const int type)
{
int shader = 0;
if (type & PRIMITIVE_TRIANGLE) {
shader = kernel_data_fetch(tri_shader, prim);
}
#ifdef __POINTCLOUD__
else if (type & PRIMITIVE_POINT) {
shader = kernel_data_fetch(points_shader, prim);
}
#endif
#ifdef __HAIR__
else if (type & PRIMITIVE_CURVE) {
shader = kernel_data_fetch(curves, prim).shader_id;
}
#endif
return kernel_data_fetch(shaders, (shader & SHADER_MASK)).flags;
}
ccl_device_forceinline int intersection_get_shader_from_isect_prim(KernelGlobals kg,
const int prim,
const int isect_type)
{
int shader = 0;
if (isect_type & PRIMITIVE_TRIANGLE) {
shader = kernel_data_fetch(tri_shader, prim);
}
#ifdef __POINTCLOUD__
else if (isect_type & PRIMITIVE_POINT) {
shader = kernel_data_fetch(points_shader, prim);
}
#endif
#ifdef __HAIR__
else if (isect_type & PRIMITIVE_CURVE) {
shader = kernel_data_fetch(curves, prim).shader_id;
}
#endif
return shader & SHADER_MASK;
}
ccl_device_forceinline int intersection_get_shader(
KernelGlobals kg, const ccl_private Intersection *ccl_restrict isect)
{
return intersection_get_shader_from_isect_prim(kg, isect->prim, isect->type);
}
ccl_device_forceinline uint
intersection_get_object_flags(KernelGlobals kg, const ccl_private Intersection *ccl_restrict isect)
{
return kernel_data_fetch(object_flag, isect->object);
}
/* TODO: find a better (faster) solution for this. Maybe store offset per object for
* attributes needed in intersection? */
ccl_device_inline int intersection_find_attribute(KernelGlobals kg,
const int object,
const uint id)
{
uint attr_offset = kernel_data_fetch(objects, object).attribute_map_offset;
AttributeMap attr_map = kernel_data_fetch(attributes_map, attr_offset);
while (attr_map.id != id) {
if (UNLIKELY(attr_map.id == ATTR_STD_NONE)) {
if (UNLIKELY(attr_map.element == 0)) {
return (int)ATTR_STD_NOT_FOUND;
}
/* Chain jump to a different part of the table. */
attr_offset = attr_map.offset;
}
else {
attr_offset += ATTR_PRIM_TYPES;
}
attr_map = kernel_data_fetch(attributes_map, attr_offset);
}
/* return result */
return (attr_map.element == ATTR_ELEMENT_NONE) ? (int)ATTR_STD_NOT_FOUND : attr_map.offset;
}
/* Transparent Shadows */
/* Cut-off value to stop transparent shadow tracing when practically opaque. */
#define CURVE_SHADOW_TRANSPARENCY_CUTOFF 0.001f
ccl_device_inline float intersection_curve_shadow_transparency(
KernelGlobals kg, const int object, const int prim, const int type, const float u)
{
/* Find attribute. */
const int offset = intersection_find_attribute(kg, object, ATTR_STD_SHADOW_TRANSPARENCY);
if (offset == ATTR_STD_NOT_FOUND) {
/* If no shadow transparency attribute, assume opaque. */
return 0.0f;
}
/* Interpolate transparency between curve keys. */
const KernelCurve kcurve = kernel_data_fetch(curves, prim);
const int k0 = kcurve.first_key + PRIMITIVE_UNPACK_SEGMENT(type);
const int k1 = k0 + 1;
const float f0 = kernel_data_fetch(attributes_float, offset + k0);
const float f1 = kernel_data_fetch(attributes_float, offset + k1);
return (1.0f - u) * f0 + u * f1;
}
ccl_device_inline bool intersection_skip_self(const ccl_ray_data RaySelfPrimitives &self,
const int object,
const int prim)
{
return (self.prim == prim) && (self.object == object);
}
ccl_device_inline bool intersection_skip_self_shadow(const ccl_ray_data RaySelfPrimitives &self,
const int object,
const int prim)
{
return ((self.prim == prim) && (self.object == object)) ||
((self.light_prim == prim) && (self.light_object == object));
}
ccl_device_inline bool intersection_skip_self_local(const ccl_ray_data RaySelfPrimitives &self,
const int prim)
{
return (self.prim == prim);
}
#ifdef __SHADOW_LINKING__
ccl_device_inline uint64_t
ray_get_shadow_set_membership(KernelGlobals kg, const ccl_ray_data RaySelfPrimitives &self)
{
if (self.light_object != OBJECT_NONE) {
return kernel_data_fetch(objects, self.light_object).shadow_set_membership;
}
return LIGHT_LINK_MASK_ALL;
}
#endif
ccl_device_inline bool intersection_skip_shadow_link(KernelGlobals kg,
const ccl_ray_data RaySelfPrimitives &self,
const int isect_object)
{
#ifdef __SHADOW_LINKING__
if (!(kernel_data.kernel_features & KERNEL_FEATURE_SHADOW_LINKING)) {
return false;
}
const uint64_t set_membership = ray_get_shadow_set_membership(kg, self);
if (set_membership == LIGHT_LINK_MASK_ALL) {
return false;
}
const uint blocker_set = kernel_data_fetch(objects, isect_object).blocker_shadow_set;
return ((uint64_t(1) << uint64_t(blocker_set)) & set_membership) == 0;
#else
return false;
#endif
}
/* Check whether an intersection denoted by its object and primitive is to be skipped due to it
* being already recoded.
* The situation when primitive is already recoded happens when BVH spatial splits are used. */
ccl_device_forceinline bool intersection_skip_shadow_already_recoded(IntegratorShadowState state,
const int object,
const int prim,
const uint num_hits)
{
const uint num_recorded_hits = min(num_hits, INTEGRATOR_SHADOW_ISECT_SIZE);
for (uint i = 0; i < num_recorded_hits; ++i) {
const int isect_object = INTEGRATOR_STATE_ARRAY(state, shadow_isect, i, object);
const int isect_prim = INTEGRATOR_STATE_ARRAY(state, shadow_isect, i, prim);
if (object == isect_object && prim == isect_prim) {
return true;
}
}
return false;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,235 @@
/* SPDX-FileCopyrightText: 2009-2010 NVIDIA Corporation
* SPDX-FileCopyrightText: 2009-2012 Intel Corporation
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0
*
* Adapted code from NVIDIA Corporation. */
#if BVH_FEATURE(BVH_HAIR)
# define NODE_INTERSECT bvh_node_intersect
#else
# define NODE_INTERSECT bvh_aligned_node_intersect
#endif
/* This is a template BVH traversal function for volumes, where
* various features can be enabled/disabled. This way we can compile optimized
* versions for each case without new features slowing things down.
*
* BVH_MOTION: motion blur rendering
*/
#ifndef __KERNEL_GPU__
ccl_device
#else
ccl_device_inline
#endif
bool
BVH_FUNCTION_FULL_NAME(BVH)(KernelGlobals kg,
const ccl_private Ray *ray,
ccl_private Intersection *isect,
const uint visibility)
{
/* todo:
* - test if pushing distance on the stack helps (for non shadow rays)
* - separate version for shadow rays
* - likely and unlikely for if() statements
* - test restrict attribute for pointers
*/
/* traversal stack in CUDA thread-local memory */
int traversal_stack[BVH_STACK_SIZE];
traversal_stack[0] = ENTRYPOINT_SENTINEL;
/* traversal variables in registers */
int stack_ptr = 0;
int node_addr = kernel_data.bvh.root;
/* ray parameters in registers */
float3 P = ray->P;
float3 dir = bvh_clamp_direction(ray->D);
float3 idir = bvh_inverse_direction(dir);
const float tmin = ray->tmin;
int object = OBJECT_NONE;
isect->t = ray->tmax;
isect->u = 0.0f;
isect->v = 0.0f;
isect->prim = PRIM_NONE;
isect->object = OBJECT_NONE;
/* traversal loop */
do {
do {
/* traverse internal nodes */
while (node_addr >= 0 && node_addr != ENTRYPOINT_SENTINEL) {
int node_addr_child1, traverse_mask;
float dist[2];
float4 cnodes = kernel_data_fetch(bvh_nodes, node_addr + 0);
traverse_mask = NODE_INTERSECT(kg,
P,
#if BVH_FEATURE(BVH_HAIR)
dir,
#endif
idir,
tmin,
isect->t,
node_addr,
visibility,
dist);
node_addr = __float_as_int(cnodes.z);
node_addr_child1 = __float_as_int(cnodes.w);
if (traverse_mask == 3) {
/* Both children were intersected, push the farther one. */
bool is_closest_child1 = (dist[1] < dist[0]);
if (is_closest_child1) {
int tmp = node_addr;
node_addr = node_addr_child1;
node_addr_child1 = tmp;
}
++stack_ptr;
kernel_assert(stack_ptr < BVH_STACK_SIZE);
traversal_stack[stack_ptr] = node_addr_child1;
}
else {
/* One child was intersected. */
if (traverse_mask == 2) {
node_addr = node_addr_child1;
}
else if (traverse_mask == 0) {
/* Neither child was intersected. */
node_addr = traversal_stack[stack_ptr];
--stack_ptr;
}
}
}
/* if node is leaf, fetch triangle list */
if (node_addr < 0) {
float4 leaf = kernel_data_fetch(bvh_leaf_nodes, (-node_addr - 1));
int prim_addr = __float_as_int(leaf.x);
if (prim_addr >= 0) {
const int prim_addr2 = __float_as_int(leaf.y);
const uint type = __float_as_int(leaf.w);
/* pop */
node_addr = traversal_stack[stack_ptr];
--stack_ptr;
/* primitive intersection */
switch (type & PRIMITIVE_ALL) {
case PRIMITIVE_TRIANGLE: {
/* intersect ray against primitive */
for (; prim_addr < prim_addr2; prim_addr++) {
kernel_assert((kernel_data_fetch(prim_type, prim_addr) & PRIMITIVE_ALL) ==
(type & PRIMITIVE_ALL));
/* only primitives from volume object */
const int prim_object = (object == OBJECT_NONE) ?
kernel_data_fetch(prim_object, prim_addr) :
object;
const int prim = kernel_data_fetch(prim_index, prim_addr);
if (bvh_volume_anyhit_triangle_filter<false>(
kg, prim_object, prim, ray->self, visibility))
{
continue;
}
triangle_intersect(
kg, isect, P, dir, tmin, isect->t, visibility, prim_object, prim, prim_addr);
}
break;
}
#if BVH_FEATURE(BVH_MOTION)
case PRIMITIVE_MOTION_TRIANGLE: {
/* intersect ray against primitive */
for (; prim_addr < prim_addr2; prim_addr++) {
kernel_assert((kernel_data_fetch(prim_type, prim_addr) & PRIMITIVE_ALL) ==
(type & PRIMITIVE_ALL));
/* only primitives from volume object */
const int prim_object = (object == OBJECT_NONE) ?
kernel_data_fetch(prim_object, prim_addr) :
object;
const int prim = kernel_data_fetch(prim_index, prim_addr);
if (bvh_volume_anyhit_triangle_filter<false>(
kg, prim_object, prim, ray->self, visibility))
{
continue;
}
motion_triangle_intersect(kg,
isect,
P,
dir,
tmin,
isect->t,
ray->time,
visibility,
prim_object,
prim,
prim_addr);
}
break;
}
#endif
default: {
break;
}
}
}
else {
/* instance push */
object = kernel_data_fetch(prim_object, -prim_addr - 1);
uint object_flag = kernel_data_fetch(object_flag, object);
if (object_flag & SD_OBJECT_HAS_VOLUME) {
#if BVH_FEATURE(BVH_MOTION)
bvh_instance_motion_push(kg, object, ray, &P, &dir, &idir);
#else
bvh_instance_push(kg, object, ray, &P, &dir, &idir);
#endif
++stack_ptr;
kernel_assert(stack_ptr < BVH_STACK_SIZE);
traversal_stack[stack_ptr] = ENTRYPOINT_SENTINEL;
node_addr = kernel_data_fetch(object_node, object);
}
else {
/* pop */
object = OBJECT_NONE;
node_addr = traversal_stack[stack_ptr];
--stack_ptr;
}
}
}
} while (node_addr != ENTRYPOINT_SENTINEL);
if (stack_ptr >= 0) {
kernel_assert(object != OBJECT_NONE);
/* instance pop */
bvh_instance_pop(ray, &P, &dir, &idir);
object = OBJECT_NONE;
node_addr = traversal_stack[stack_ptr];
--stack_ptr;
}
} while (node_addr != ENTRYPOINT_SENTINEL);
return (isect->prim != PRIM_NONE);
}
ccl_device_inline bool BVH_FUNCTION_NAME(KernelGlobals kg,
const ccl_private Ray *ray,
ccl_private Intersection *isect,
const uint visibility)
{
return BVH_FUNCTION_FULL_NAME(BVH)(kg, ray, isect, visibility);
}
#undef BVH_FUNCTION_NAME
#undef BVH_FUNCTION_FEATURES
#undef NODE_INTERSECT

View File

@@ -0,0 +1,263 @@
/* SPDX-FileCopyrightText: 2009-2010 NVIDIA Corporation
* SPDX-FileCopyrightText: 2009-2012 Intel Corporation
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0
*
* Adapted code from NVIDIA Corporation. */
#if BVH_FEATURE(BVH_HAIR)
# define NODE_INTERSECT bvh_node_intersect
#else
# define NODE_INTERSECT bvh_aligned_node_intersect
#endif
/* This is a template BVH traversal function for volumes, where
* various features can be enabled/disabled. This way we can compile optimized
* versions for each case without new features slowing things down.
*
* BVH_MOTION: motion blur rendering
*/
#ifndef __KERNEL_GPU__
ccl_device
#else
ccl_device_inline
#endif
uint
BVH_FUNCTION_FULL_NAME(BVH)(KernelGlobals kg,
const ccl_private Ray *ray,
Intersection *isect_array,
const uint max_hits,
const uint visibility)
{
/* todo:
* - test if pushing distance on the stack helps (for non shadow rays)
* - separate version for shadow rays
* - likely and unlikely for if() statements
* - test restrict attribute for pointers
*/
/* traversal stack in CUDA thread-local memory */
int traversal_stack[BVH_STACK_SIZE];
traversal_stack[0] = ENTRYPOINT_SENTINEL;
/* traversal variables in registers */
int stack_ptr = 0;
int node_addr = kernel_data.bvh.root;
/* ray parameters in registers */
float3 P = ray->P;
float3 dir = bvh_clamp_direction(ray->D);
float3 idir = bvh_inverse_direction(dir);
const float tmin = ray->tmin;
int object = OBJECT_NONE;
float isect_t = ray->tmax;
uint num_hits = 0;
isect_array->t = ray->tmax;
/* traversal loop */
do {
do {
/* traverse internal nodes */
while (node_addr >= 0 && node_addr != ENTRYPOINT_SENTINEL) {
int node_addr_child1, traverse_mask;
float dist[2];
float4 cnodes = kernel_data_fetch(bvh_nodes, node_addr + 0);
traverse_mask = NODE_INTERSECT(kg,
P,
#if BVH_FEATURE(BVH_HAIR)
dir,
#endif
idir,
tmin,
isect_t,
node_addr,
visibility,
dist);
node_addr = __float_as_int(cnodes.z);
node_addr_child1 = __float_as_int(cnodes.w);
if (traverse_mask == 3) {
/* Both children were intersected, push the farther one. */
bool is_closest_child1 = (dist[1] < dist[0]);
if (is_closest_child1) {
int tmp = node_addr;
node_addr = node_addr_child1;
node_addr_child1 = tmp;
}
++stack_ptr;
kernel_assert(stack_ptr < BVH_STACK_SIZE);
traversal_stack[stack_ptr] = node_addr_child1;
}
else {
/* One child was intersected. */
if (traverse_mask == 2) {
node_addr = node_addr_child1;
}
else if (traverse_mask == 0) {
/* Neither child was intersected. */
node_addr = traversal_stack[stack_ptr];
--stack_ptr;
}
}
}
/* if node is leaf, fetch triangle list */
if (node_addr < 0) {
float4 leaf = kernel_data_fetch(bvh_leaf_nodes, (-node_addr - 1));
int prim_addr = __float_as_int(leaf.x);
if (prim_addr >= 0) {
const int prim_addr2 = __float_as_int(leaf.y);
const uint type = __float_as_int(leaf.w);
bool hit;
/* pop */
node_addr = traversal_stack[stack_ptr];
--stack_ptr;
/* primitive intersection */
switch (type & PRIMITIVE_ALL) {
case PRIMITIVE_TRIANGLE: {
/* intersect ray against primitive */
for (; prim_addr < prim_addr2; prim_addr++) {
kernel_assert((kernel_data_fetch(prim_type, prim_addr) & PRIMITIVE_ALL) ==
(type & PRIMITIVE_ALL));
/* only primitives from volume object */
const int prim_object = (object == OBJECT_NONE) ?
kernel_data_fetch(prim_object, prim_addr) :
object;
const int prim = kernel_data_fetch(prim_index, prim_addr);
if (bvh_volume_anyhit_triangle_filter<false>(
kg, prim_object, prim, ray->self, visibility))
{
continue;
}
hit = triangle_intersect(kg,
isect_array,
P,
dir,
tmin,
isect_t,
visibility,
prim_object,
prim,
prim_addr);
if (hit) {
/* Move on to next entry in intersections array. */
isect_array++;
num_hits++;
isect_array->t = isect_t;
if (num_hits == max_hits) {
return num_hits;
}
}
}
break;
}
#if BVH_FEATURE(BVH_MOTION)
case PRIMITIVE_MOTION_TRIANGLE: {
/* intersect ray against primitive */
for (; prim_addr < prim_addr2; prim_addr++) {
kernel_assert((kernel_data_fetch(prim_type, prim_addr) & PRIMITIVE_ALL) ==
(type & PRIMITIVE_ALL));
/* only primitives from volume object */
const int prim_object = (object == OBJECT_NONE) ?
kernel_data_fetch(prim_object, prim_addr) :
object;
const int prim = kernel_data_fetch(prim_index, prim_addr);
if (bvh_volume_anyhit_triangle_filter<false>(
kg, prim_object, prim, ray->self, visibility))
{
continue;
}
hit = motion_triangle_intersect(kg,
isect_array,
P,
dir,
tmin,
isect_t,
ray->time,
visibility,
prim_object,
prim,
prim_addr);
if (hit) {
/* Move on to next entry in intersections array. */
isect_array++;
num_hits++;
isect_array->t = isect_t;
if (num_hits == max_hits) {
return num_hits;
}
}
}
break;
}
#endif /* BVH_MOTION */
default: {
break;
}
}
}
else {
/* instance push */
object = kernel_data_fetch(prim_object, -prim_addr - 1);
const uint object_flag = kernel_data_fetch(object_flag, object);
if (object_flag & SD_OBJECT_HAS_VOLUME) {
#if BVH_FEATURE(BVH_MOTION)
bvh_instance_motion_push(kg, object, ray, &P, &dir, &idir);
#else
bvh_instance_push(kg, object, ray, &P, &dir, &idir);
#endif
isect_array->t = isect_t;
++stack_ptr;
kernel_assert(stack_ptr < BVH_STACK_SIZE);
traversal_stack[stack_ptr] = ENTRYPOINT_SENTINEL;
node_addr = kernel_data_fetch(object_node, object);
}
else {
/* pop */
object = OBJECT_NONE;
node_addr = traversal_stack[stack_ptr];
--stack_ptr;
}
}
}
} while (node_addr != ENTRYPOINT_SENTINEL);
if (stack_ptr >= 0) {
kernel_assert(object != OBJECT_NONE);
/* Instance pop. */
bvh_instance_pop(ray, &P, &dir, &idir);
object = OBJECT_NONE;
node_addr = traversal_stack[stack_ptr];
--stack_ptr;
}
} while (node_addr != ENTRYPOINT_SENTINEL);
return num_hits;
}
ccl_device_inline uint BVH_FUNCTION_NAME(KernelGlobals kg,
const ccl_private Ray *ray,
Intersection *isect_array,
const uint max_hits,
const uint visibility)
{
return BVH_FUNCTION_FULL_NAME(BVH)(kg, ray, isect_array, max_hits, visibility);
}
#undef BVH_FUNCTION_NAME
#undef BVH_FUNCTION_FEATURES
#undef NODE_INTERSECT

View File

@@ -0,0 +1,664 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/globals.h"
#include "kernel/camera/projection.h"
#include "kernel/sample/mapping.h"
#include "kernel/util/differential.h"
#include "kernel/util/lookup_table.h"
#ifdef WITH_OSL
# include "kernel/osl/camera.h"
#endif
CCL_NAMESPACE_BEGIN
/* Perspective Camera */
ccl_device float2 camera_sample_aperture(ccl_constant KernelCamera *cam, const float2 rand)
{
const float blades = cam->blades;
float2 bokeh;
if (blades == 0.0f) {
/* sample disk */
bokeh = sample_uniform_disk(rand);
}
else {
/* sample polygon */
const float rotation = cam->bladesrotation;
bokeh = regular_polygon_sample(blades, rotation, rand);
}
/* anamorphic lens bokeh */
bokeh.x *= cam->inv_aperture_ratio;
return bokeh;
}
ccl_device Spectrum camera_sample_perspective(KernelGlobals kg,
const float2 raster_xy,
const float2 rand_lens,
ccl_private Ray *ray)
{
/* create ray form raster position */
const ProjectionTransform rastertocamera = kernel_data.cam.rastertocamera;
const float3 raster = make_float3(raster_xy);
float3 Pcamera = transform_perspective(&rastertocamera, raster);
if (kernel_data.cam.have_perspective_motion) {
/* TODO(sergey): Currently we interpolate projected coordinate which
* gives nice looking result and which is simple, but is in fact a bit
* different comparing to constructing projective matrix from an
* interpolated field of view.
*/
if (ray->time < 0.5f) {
const ProjectionTransform rastertocamera_pre = kernel_data.cam.perspective_pre;
const float3 Pcamera_pre = transform_perspective(&rastertocamera_pre, raster);
Pcamera = interp(Pcamera_pre, Pcamera, ray->time * 2.0f);
}
else {
const ProjectionTransform rastertocamera_post = kernel_data.cam.perspective_post;
const float3 Pcamera_post = transform_perspective(&rastertocamera_post, raster);
Pcamera = interp(Pcamera, Pcamera_post, (ray->time - 0.5f) * 2.0f);
}
}
float3 P = zero_float3();
float3 D = Pcamera;
/* modify ray for depth of field */
const float aperturesize = kernel_data.cam.aperturesize;
if (aperturesize > 0.0f) {
/* sample point on aperture */
const float2 lens_uv = camera_sample_aperture(&kernel_data.cam, rand_lens) * aperturesize;
/* compute point on plane of focus */
const float ft = kernel_data.cam.focaldistance / D.z;
const float3 Pfocus = D * ft;
/* update ray for effect of lens */
P = make_float3(lens_uv);
D = normalize(Pfocus - P);
}
/* transform ray from camera to world */
Transform cameratoworld = kernel_data.cam.cameratoworld;
if (kernel_data.cam.num_motion_steps) {
transform_motion_array_interpolate(&cameratoworld,
kernel_data_array(camera_motion),
kernel_data.cam.num_motion_steps,
ray->time);
}
P = transform_point(&cameratoworld, P);
D = normalize(transform_direction(&cameratoworld, D));
const bool use_stereo = kernel_data.cam.interocular_offset != 0.0f;
if (!use_stereo) {
/* No stereo */
ray->P = P;
ray->D = D;
#ifdef __RAY_DIFFERENTIALS__
const float3 Dcenter = transform_direction(&cameratoworld, Pcamera);
const float3 Dcenter_normalized = normalize(Dcenter);
/* TODO: can this be optimized to give compact differentials directly? */
ray->dP = differential_zero_compact();
differential3 dD;
dD.dx = normalize(Dcenter + make_float3(kernel_data.cam.dx)) - Dcenter_normalized;
dD.dy = normalize(Dcenter + make_float3(kernel_data.cam.dy)) - Dcenter_normalized;
ray->dD = differential_make_compact(dD) * kernel_data.cam.differential_scale;
#endif
}
else {
/* Spherical stereo */
spherical_stereo_transform(&kernel_data.cam, &P, &D);
ray->P = P;
ray->D = D;
#ifdef __RAY_DIFFERENTIALS__
/* Ray differentials, computed from scratch using the raster coordinates
* because we don't want to be affected by depth of field. We compute
* ray origin and direction for the center and two neighboring pixels
* and simply take their differences. */
const float3 Pnostereo = transform_point(&cameratoworld, zero_float3());
float3 Pcenter = Pnostereo;
float3 Dcenter = Pcamera;
Dcenter = normalize(transform_direction(&cameratoworld, Dcenter));
spherical_stereo_transform(&kernel_data.cam, &Pcenter, &Dcenter);
float3 Px = Pnostereo;
float3 Dx = transform_perspective(&rastertocamera,
make_float3(raster.x + 1.0f, raster.y, 0.0f));
Dx = normalize(transform_direction(&cameratoworld, Dx));
spherical_stereo_transform(&kernel_data.cam, &Px, &Dx);
differential3 dP;
differential3 dD;
dP.dx = Px - Pcenter;
dD.dx = Dx - Dcenter;
float3 Py = Pnostereo;
float3 Dy = transform_perspective(&rastertocamera,
make_float3(raster.x, raster.y + 1.0f, 0.0f));
Dy = normalize(transform_direction(&cameratoworld, Dy));
spherical_stereo_transform(&kernel_data.cam, &Py, &Dy);
dP.dy = Py - Pcenter;
dD.dy = Dy - Dcenter;
ray->dD = differential_make_compact(dD) * kernel_data.cam.differential_scale;
ray->dP = differential_make_compact(dP) * kernel_data.cam.differential_scale;
#endif
}
/* clipping */
const float z_inv = 1.0f / normalize(Pcamera).z;
const float nearclip = kernel_data.cam.nearclip * z_inv;
ray->P += nearclip * ray->D;
ray->dP += nearclip * ray->dD;
ray->tmin = 0.0f;
ray->tmax = kernel_data.cam.cliplength * z_inv;
return one_spectrum();
}
/* Orthographic Camera */
ccl_device Spectrum camera_sample_orthographic(KernelGlobals kg,
const float2 raster_xy,
const float2 rand_lens,
ccl_private Ray *ray)
{
/* create ray form raster position */
const ProjectionTransform rastertocamera = kernel_data.cam.rastertocamera;
const float3 Pcamera = transform_perspective(&rastertocamera, make_float3(raster_xy));
float3 P;
float3 D = make_float3(0.0f, 0.0f, 1.0f);
/* modify ray for depth of field */
const float aperturesize = kernel_data.cam.aperturesize;
if (aperturesize > 0.0f) {
/* sample point on aperture */
const float2 lens_uv = camera_sample_aperture(&kernel_data.cam, rand_lens) * aperturesize;
/* compute point on plane of focus */
const float3 Pfocus = D * kernel_data.cam.focaldistance;
/* Update ray for effect of lens */
const float3 lens_uvw = make_float3(lens_uv);
D = normalize(Pfocus - lens_uvw);
/* Compute position the ray will be if it traveled until it intersected the near clip plane.
* This allows for correct DOF while allowing near clipping. */
P = Pcamera + lens_uvw + (D * (kernel_data.cam.nearclip / D.z));
}
else {
P = Pcamera + make_float3(0.0f, 0.0f, kernel_data.cam.nearclip);
}
/* transform ray from camera to world */
Transform cameratoworld = kernel_data.cam.cameratoworld;
if (kernel_data.cam.num_motion_steps) {
transform_motion_array_interpolate(&cameratoworld,
kernel_data_array(camera_motion),
kernel_data.cam.num_motion_steps,
ray->time);
}
ray->P = transform_point(&cameratoworld, P);
ray->D = normalize(transform_direction(&cameratoworld, D));
#ifdef __RAY_DIFFERENTIALS__
/* ray differential */
differential3 dP;
dP.dx = make_float3(kernel_data.cam.dx);
dP.dy = make_float3(kernel_data.cam.dy);
ray->dP = differential_make_compact(dP) * kernel_data.cam.differential_scale;
ray->dD = differential_zero_compact();
#endif
/* clipping */
ray->tmin = 0.0f;
ray->tmax = kernel_data.cam.cliplength;
return one_spectrum();
}
/* Custom Camera */
ccl_device_inline void camera_sample_to_ray(ccl_constant KernelCamera *cam,
const ccl_global DecomposedTransform *cam_motion,
float3 P,
float3 D,
#ifdef __RAY_DIFFERENTIALS__
float3 Pcenter,
float3 Dcenter,
float3 Px,
float3 Dx,
float3 Py,
float3 Dy,
#endif
ccl_private Ray *ray)
{
/* Transform the ray from camera to world. */
Transform cameratoworld = cam->cameratoworld;
if (cam->num_motion_steps) {
transform_motion_array_interpolate(
&cameratoworld, cam_motion, cam->num_motion_steps, ray->time);
}
/* Stereo transform */
const bool use_stereo = cam->interocular_offset != 0.0f;
if (use_stereo) {
spherical_stereo_transform(cam, &P, &D);
}
P = transform_point(&cameratoworld, P);
D = normalize(transform_direction(&cameratoworld, D));
ray->P = P;
ray->D = D;
#ifdef __RAY_DIFFERENTIALS__
if (use_stereo) {
spherical_stereo_transform(cam, &Pcenter, &Dcenter);
spherical_stereo_transform(cam, &Px, &Dx);
spherical_stereo_transform(cam, &Py, &Dy);
differential3 dP;
Pcenter = transform_point(&cameratoworld, Pcenter);
dP.dx = transform_point(&cameratoworld, Px) - Pcenter;
dP.dy = transform_point(&cameratoworld, Py) - Pcenter;
ray->dP = differential_make_compact(dP) * cam->differential_scale;
}
else {
ray->dP = differential_zero_compact();
}
differential3 dD;
Dcenter = normalize(transform_direction(&cameratoworld, Dcenter));
dD.dx = normalize(transform_direction(&cameratoworld, Dx)) - Dcenter;
dD.dy = normalize(transform_direction(&cameratoworld, Dy)) - Dcenter;
ray->dD = differential_make_compact(dD) * cam->differential_scale;
#endif
/* clipping */
const float nearclip = cam->nearclip;
ray->P += nearclip * ray->D;
ray->dP += nearclip * ray->dD;
ray->tmin = 0.0f;
ray->tmax = cam->cliplength;
}
ccl_device_inline Spectrum camera_sample_custom(KernelGlobals kg,
ccl_constant KernelCamera *cam,
const ccl_global DecomposedTransform *cam_motion,
const float2 raster,
const float2 rand_lens,
ccl_private Ray *ray,
ccl_private int &r_cache_miss)
{
#ifdef WITH_OSL
/* Transform raster position to camera space. */
const ProjectionTransform rastertocamera = cam->rastertocamera;
float3 sensor = transform_perspective(&rastertocamera, make_float3(raster.x, raster.y, 0.0f));
float3 dSdx = transform_perspective_direction(&rastertocamera, make_float3(1.0f, 0.0f, 0.0f));
float3 dSdy = transform_perspective_direction(&rastertocamera, make_float3(0.0f, 1.0f, 0.0f));
ShaderDataTinyStorage sd_storage = {};
ccl_private ShaderData *sd = AS_SHADER_DATA(&sd_storage);
sd->object = OBJECT_NONE;
sd->prim = PRIM_NONE;
sd->shader = SHADER_NONE;
sd->type = PRIMITIVE_NONE;
sd->flag = 0;
/* Execute OSL shader to sample position, direction and transmission. */
packed_float3 P, dPdx, dPdy, D, dDdx, dDdy, throughput;
r_cache_miss = false;
throughput = osl_eval_camera(
kg, sd, sensor, dSdx, dSdy, rand_lens, P, dPdx, dPdy, D, dDdx, dDdy);
if (sd->flag & SD_CACHE_MISS) {
r_cache_miss = true;
return zero_spectrum();
}
/* Zero throughput indicates failed sampling. */
if (is_zero(throughput)) {
return zero_spectrum();
}
camera_sample_to_ray(cam,
cam_motion,
P,
D,
# ifdef __RAY_DIFFERENTIALS__
P,
D,
P + dPdx,
D + dDdx,
P + dPdy,
D + dDdy,
# endif
ray);
return throughput;
#else
(void)kg;
(void)cam;
(void)cam_motion;
(void)raster;
(void)rand_lens;
(void)ray;
(void)r_cache_miss;
return zero_spectrum();
#endif
}
/* Panorama Camera */
ccl_device_inline float3 camera_panorama_direction(ccl_constant KernelCamera *cam,
const float x,
const float y)
{
const ProjectionTransform rastertocamera = cam->rastertocamera;
const float3 Pcamera = transform_perspective(&rastertocamera, make_float3(x, y, 0.0f));
return panorama_to_direction(cam, Pcamera.x, Pcamera.y);
}
ccl_device_inline Spectrum camera_sample_panorama(ccl_constant KernelCamera *cam,
const ccl_global DecomposedTransform *cam_motion,
const float2 raster,
const float2 rand_lens,
ccl_private Ray *ray)
{
/* Create ray from raster position. */
float3 P = zero_float3();
float3 D = camera_panorama_direction(cam, raster.x, raster.y);
#ifdef __RAY_DIFFERENTIALS__
/* Ray differentials, computed from scratch using the raster coordinates
* because we don't want to be affected by depth of field. We compute
* ray origin and direction for the center and two neighboring pixels
* and simply take their differences. */
float3 Dcenter = D;
float3 Dx = camera_panorama_direction(cam, raster.x + 1.0f, raster.y);
float3 Dy = camera_panorama_direction(cam, raster.x, raster.y + 1.0f);
#endif
/* Here, zero indicates failed sampling, e.g. when the raster position is outside
* the fisheye lens. */
if (is_zero(D)) {
return zero_spectrum();
}
/* Perform depth-of-field sampling. */
const float aperturesize = cam->aperturesize;
if (aperturesize > 0.0f) {
/* Sample a point on the aperture. */
const float2 lens_uv = camera_sample_aperture(cam, rand_lens) * aperturesize;
/* Compute the intersection of the original ray with the focal plane. */
const float3 Dfocus = normalize(D);
const float3 Pfocus = Dfocus * cam->focaldistance;
/* Calculate orthonormal coordinate system perpendicular to Dfocus. */
const float3 U = normalize(make_float3(1.0f, 0.0f, 0.0f) - Dfocus.x * Dfocus);
const float3 V = normalize(cross(Dfocus, U));
/* Compute new ray by shifting its origin (to account for aperture position) and
* setting its direction to meet the original ray at the focal plane. */
P = U * lens_uv.x + V * lens_uv.y;
D = normalize(Pfocus - P);
}
camera_sample_to_ray(cam,
cam_motion,
P,
D,
#ifdef __RAY_DIFFERENTIALS__
zero_float3(),
Dcenter,
zero_float3(),
Dx,
zero_float3(),
Dy,
#endif
ray);
return one_spectrum();
}
/* Common */
/* Generates an outgoing camera ray for the given raster position and random inputs.
* Returns camera sensitivity (used to initialize path throughput). */
ccl_device_inline Spectrum camera_sample(KernelGlobals kg,
const int x,
const int y,
const float2 filter_uv,
const float time,
const float2 lens_uv,
ccl_private Ray *ray,
ccl_private int &r_cache_miss)
{
float2 raster = make_float2(x, y);
/* pixel filter */
if (kernel_data.integrator.pixel_jitter.x == FLT_MAX) {
const int filter_table_offset = kernel_data.tables.filter_table_offset;
raster.x += lookup_table_read(kg, filter_uv.x, filter_table_offset, FILTER_TABLE_SIZE);
raster.y += lookup_table_read(kg, filter_uv.y, filter_table_offset, FILTER_TABLE_SIZE);
}
else {
raster += -kernel_data.integrator.pixel_jitter;
}
/* motion blur */
if (kernel_data.cam.shuttertime == -1.0f) {
ray->time = 0.5f;
}
else {
/* TODO(sergey): Such lookup is unneeded when there's rolling shutter
* effect in use but rolling shutter duration is set to 0.0.
*/
const int shutter_table_offset = kernel_data.cam.shutter_table_offset;
ray->time = lookup_table_read(kg, time, shutter_table_offset, SHUTTER_TABLE_SIZE);
/* TODO(sergey): Currently single rolling shutter effect type only
* where scan-lines are acquired from top to bottom and whole scan-line
* is acquired at once (no delay in acquisition happens between pixels
* of single scan-line).
*
* Might want to support more models in the future.
*/
if (kernel_data.cam.rolling_shutter_type) {
/* Time corresponding to a fully rolling shutter only effect:
* top of the frame is time 0.0, bottom of the frame is time 1.0.
*/
const float time = 1.0f - (float)y / kernel_data.cam.height;
const float duration = kernel_data.cam.rolling_shutter_duration;
if (duration != 0.0f) {
/* This isn't fully physical correct, but lets us to have simple
* controls in the interface. The idea here is basically sort of
* linear interpolation between how much rolling shutter effect
* exist on the frame and how much of it is a motion blur effect.
*/
ray->time = (ray->time - 0.5f) * duration;
ray->time += (time - 0.5f) * (1.0f - duration) + 0.5f;
}
else {
ray->time = time;
}
}
}
/* sample */
r_cache_miss = false;
if (kernel_data.cam.type == CAMERA_PERSPECTIVE) {
return camera_sample_perspective(kg, raster, lens_uv, ray);
}
if (kernel_data.cam.type == CAMERA_ORTHOGRAPHIC) {
return camera_sample_orthographic(kg, raster, lens_uv, ray);
}
if (kernel_data.cam.type == CAMERA_PANORAMA) {
const ccl_global DecomposedTransform *cam_motion = kernel_data_array(camera_motion);
return camera_sample_panorama(&kernel_data.cam, cam_motion, raster, lens_uv, ray);
}
if (kernel_data.cam.type == CAMERA_CUSTOM) {
const ccl_global DecomposedTransform *cam_motion = kernel_data_array(camera_motion);
return camera_sample_custom(
kg, &kernel_data.cam, cam_motion, raster, lens_uv, ray, r_cache_miss);
}
kernel_assert(false);
return zero_spectrum();
}
/* Utilities */
ccl_device_inline float3 camera_position(KernelGlobals kg)
{
const Transform cameratoworld = kernel_data.cam.cameratoworld;
return make_float3(cameratoworld.x.w, cameratoworld.y.w, cameratoworld.z.w);
}
ccl_device_inline float camera_distance(KernelGlobals kg, const float3 P)
{
const Transform cameratoworld = kernel_data.cam.cameratoworld;
const float3 camP = make_float3(cameratoworld.x.w, cameratoworld.y.w, cameratoworld.z.w);
if (kernel_data.cam.type == CAMERA_ORTHOGRAPHIC) {
const float3 camD = make_float3(cameratoworld.x.z, cameratoworld.y.z, cameratoworld.z.z);
return fabsf(dot((P - camP), camD));
}
return len(P - camP);
}
ccl_device_inline float camera_z_depth(KernelGlobals kg, const float3 P)
{
if (kernel_data.cam.type == CAMERA_PERSPECTIVE || kernel_data.cam.type == CAMERA_ORTHOGRAPHIC) {
const Transform worldtocamera = kernel_data.cam.worldtocamera;
return transform_point(&worldtocamera, P).z;
}
const Transform cameratoworld = kernel_data.cam.cameratoworld;
const float3 camP = make_float3(cameratoworld.x.w, cameratoworld.y.w, cameratoworld.z.w);
return len(P - camP);
}
ccl_device_inline float3 camera_direction_from_point(KernelGlobals kg, const float3 P)
{
const Transform cameratoworld = kernel_data.cam.cameratoworld;
if (kernel_data.cam.type == CAMERA_ORTHOGRAPHIC) {
const float3 camD = make_float3(cameratoworld.x.z, cameratoworld.y.z, cameratoworld.z.z);
return -camD;
}
const float3 camP = make_float3(cameratoworld.x.w, cameratoworld.y.w, cameratoworld.z.w);
return normalize(camP - P);
}
ccl_device_inline float3 camera_world_to_ndc(KernelGlobals kg,
ccl_private ShaderData *sd,
float3 P)
{
if (kernel_data.cam.type == CAMERA_PERSPECTIVE || kernel_data.cam.type == CAMERA_ORTHOGRAPHIC) {
/* perspective / ortho */
if (sd->object == PRIM_NONE && kernel_data.cam.type == CAMERA_PERSPECTIVE) {
P += camera_position(kg);
}
const ProjectionTransform tfm = kernel_data.cam.worldtondc;
return transform_perspective(&tfm, P);
}
/* panorama or custom */
const Transform tfm = kernel_data.cam.worldtocamera;
if (sd->object != OBJECT_NONE) {
P = normalize(transform_point(&tfm, P));
}
else {
P = normalize(transform_direction(&tfm, P));
}
if (kernel_data.cam.type == CAMERA_PANORAMA) {
return make_float3(direction_to_panorama(&kernel_data.cam, P));
}
/* TODO: Fall back to camera coordinates until we have inverse mappings for custom cameras. */
return P;
}
/* Motion vector for motion pass */
ccl_device_forceinline float4 camera_motion_vector_direction(KernelGlobals kg, const float3 D)
{
Transform tfm;
float3 motion_center;
float3 motion_pre;
float3 motion_post;
/* Camera motion, for perspective/orthographic motion.pre/post will be a
* world-to-raster matrix, for panorama it's world-to-camera, for custom
* we fall back to the world position until we have inverse mapping for it */
if (kernel_data.cam.type == CAMERA_CUSTOM) {
/* TODO: Custom cameras don't have inverse mappings yet, so we fall back to
* camera-space vectors here for now. */
tfm = kernel_data.cam.worldtocamera;
motion_center = normalize(transform_direction(&tfm, D));
tfm = kernel_data.cam.motion_pass_pre;
motion_pre = normalize(transform_direction(&tfm, D));
tfm = kernel_data.cam.motion_pass_post;
motion_post = normalize(transform_direction(&tfm, D));
}
else if (kernel_data.cam.type != CAMERA_PANORAMA) {
/* Perspective and orthographics camera use the world-to-raster matrix. */
ProjectionTransform projection = kernel_data.cam.worldtoraster;
motion_center = transform_perspective_direction(&projection, D);
projection = kernel_data.cam.perspective_pre;
motion_pre = transform_perspective_direction(&projection, D);
projection = kernel_data.cam.perspective_post;
motion_post = transform_perspective_direction(&projection, D);
}
else {
/* Panorama cameras have their own inverse mappings. */
tfm = kernel_data.cam.worldtocamera;
motion_center = normalize(transform_direction(&tfm, D));
motion_center = make_float3(direction_to_panorama(&kernel_data.cam, motion_center));
motion_center.x *= kernel_data.cam.width;
motion_center.y *= kernel_data.cam.height;
tfm = kernel_data.cam.motion_pass_pre;
motion_pre = normalize(transform_direction(&tfm, D));
motion_pre = make_float3(direction_to_panorama(&kernel_data.cam, motion_pre));
motion_pre.x *= kernel_data.cam.width;
motion_pre.y *= kernel_data.cam.height;
tfm = kernel_data.cam.motion_pass_post;
motion_post = normalize(transform_direction(&tfm, D));
motion_post = make_float3(direction_to_panorama(&kernel_data.cam, motion_post));
motion_post.x *= kernel_data.cam.width;
motion_post.y *= kernel_data.cam.height;
}
motion_pre = motion_pre - motion_center;
motion_post = motion_center - motion_post;
return make_float4(motion_pre.x, motion_pre.y, motion_post.x, motion_post.y);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,382 @@
/* SPDX-FileCopyrightText: 2009-2010 Sony Pictures Imageworks Inc., et al.
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/types.h"
#include "util/math.h"
#include "util/types.h"
CCL_NAMESPACE_BEGIN
/* Equirectangular coordinates <-> Cartesian direction */
ccl_device float2 direction_to_equirectangular_range(const float3 dir, const float4 range)
{
if (is_zero(dir)) {
return zero_float2();
}
const float u = (atan2f(dir.y, dir.x) - range.y) / range.x;
const float v = (acosf(dir.z / len(dir)) - range.w) / range.z;
return make_float2(u, v);
}
ccl_device dual2 direction_to_equirectangular_range(const dual3 dir, const float4 range)
{
if (is_zero(dir)) {
return make_zero<dual2>();
}
const dual1 u = (atan2(dir.y(), dir.x()) - range.y) / range.x;
const dual1 v = (acos(dir.z() / len(dir)) - range.w) / range.z;
return make_float2(u, v);
}
ccl_device float3 equirectangular_range_to_direction(const float u,
const float v,
const float4 range)
{
const float phi = range.x * u + range.y;
const float theta = range.z * v + range.w;
return spherical_to_direction(theta, phi);
}
ccl_device float2 direction_to_equirectangular(const float3 dir)
{
return direction_to_equirectangular_range(dir, make_float4(-M_2PI_F, M_PI_F, -M_PI_F, M_PI_F));
}
ccl_device dual2 direction_to_equirectangular(const dual3 dir)
{
return direction_to_equirectangular_range(dir, make_float4(-M_2PI_F, M_PI_F, -M_PI_F, M_PI_F));
}
ccl_device float3 equirectangular_to_direction(const float u, const float v)
{
return equirectangular_range_to_direction(u, v, make_float4(-M_2PI_F, M_PI_F, -M_PI_F, M_PI_F));
}
ccl_device float2 direction_to_central_cylindrical(const float3 dir, const float4 range)
{
const float z = dir.z / len(make_float2(dir.x, dir.y));
const float theta = atan2f(dir.y, dir.x);
const float u = inverse_lerp(range.x, range.y, theta);
const float v = inverse_lerp(range.z, range.w, z);
return make_float2(u, v);
}
ccl_device float3 central_cylindrical_to_direction(const float u,
const float v,
const float4 range)
{
const float theta = mix(range.x, range.y, u);
const float z = mix(range.z, range.w, v);
return make_float3(cosf(theta), sinf(theta), z);
}
/* Fisheye <-> Cartesian direction */
ccl_device_inline float3 fisheye_to_direction(const float theta,
const float u,
float v,
const float r)
{
float phi = safe_acosf(safe_divide(u, r));
if (v < 0.0f) {
phi = -phi;
}
return make_float3(cosf(theta), -cosf(phi) * sinf(theta), sinf(phi) * sinf(theta));
}
ccl_device float2 direction_to_fisheye_equidistant(const float3 dir, const float fov)
{
const float r = atan2f(len(make_float2(dir.y, dir.z)), dir.x) / fov;
const float2 uv = r * safe_normalize(make_float2(dir.y, dir.z));
return make_float2(0.5f - uv.x, uv.y + 0.5f);
}
ccl_device float3 fisheye_equidistant_to_direction(float u, float v, float fov)
{
u = (u - 0.5f) * 2.0f;
v = (v - 0.5f) * 2.0f;
const float r = sqrtf(u * u + v * v);
if (r > 1.0f) {
return zero_float3();
}
const float theta = r * fov * 0.5f;
return fisheye_to_direction(theta, u, v, r);
}
ccl_device float2 direction_to_fisheye_equisolid(const float3 dir,
const float lens,
const float width,
const float height)
{
const float theta = safe_acosf(dir.x);
const float r = 2.0f * lens * sinf(theta * 0.5f);
const float2 uv = r * safe_normalize(make_float2(dir.y, dir.z));
return make_float2(0.5f - uv.x / width, uv.y / height + 0.5f);
}
ccl_device_inline float3 fisheye_equisolid_to_direction(
float u, float v, float lens, const float fov, const float width, const float height)
{
u = (u - 0.5f) * width;
v = (v - 0.5f) * height;
const float rmax = 2.0f * lens * sinf(fov * 0.25f);
const float r = sqrtf(u * u + v * v);
if (r > rmax) {
return zero_float3();
}
const float theta = 2.0f * asinf(r / (2.0f * lens));
return fisheye_to_direction(theta, u, v, r);
}
ccl_device_inline float3 fisheye_lens_polynomial_to_direction(float u,
float v,
float coeff0,
const float4 coeffs,
const float fov,
const float width,
const float height)
{
u = (u - 0.5f) * width;
v = (v - 0.5f) * height;
const float r = sqrtf(u * u + v * v);
const float r2 = r * r;
const float4 rr = make_float4(r, r2, r2 * r, r2 * r2);
const float theta = -(coeff0 + dot(coeffs, rr));
if (fabsf(theta) > 0.5f * fov) {
return zero_float3();
}
return fisheye_to_direction(theta, u, v, r);
}
ccl_device float2 direction_to_fisheye_lens_polynomial(
float3 dir, const float coeff0, const float4 coeffs, const float width, const float height)
{
const float theta = -safe_acosf(dir.x);
/* Initialize r with the closed-form solution for the special case
* coeffs.y = coeffs.z = coeffs.w = 0 */
float r = (theta - coeff0) / coeffs.x;
const float4 diff_coeffs = make_float4(1.0f, 2.0f, 3.0f, 4.0f) * coeffs;
for (int i = 0; i < 20; i++) {
/** \name Newton's Method for Finding Roots
*
* Given is the result theta = distortion_model(r),
* we need to find r.
* Let F(r) := theta - distortion_model(r).
* Then F(r) = 0 <=> distortion_model(r) = theta
* Therefore we apply Newton's method for finding a root of F(r).
* Newton step for the function F:
* r_n+1 = r_n - F(r_n) / F'(r_n)
* The addition in the implementation is due to canceling of signs.
* \{ */
const float old_r = r;
const float r2 = r * r;
const float F_r = theta - (coeff0 + dot(coeffs, make_float4(r, r2, r2 * r, r2 * r2)));
const float dF_r = dot(diff_coeffs, make_float4(1.0f, r, r2, r2 * r));
r += F_r / dF_r;
/* Early termination if the change is below the threshold */
if (fabsf(r - old_r) < 1e-6f) {
break;
}
/** \} */
}
const float2 uv = r * safe_normalize(make_float2(dir.y, dir.z));
return make_float2(0.5f - uv.x / width, uv.y / height + 0.5f);
}
/* Mirror Ball <-> Cartesian direction. */
ccl_device float3 mirrorball_to_direction(const float u, const float v)
{
/* point on sphere */
float3 dir;
dir.x = 2.0f * u - 1.0f;
dir.z = 2.0f * v - 1.0f;
if (dir.x * dir.x + dir.z * dir.z > 1.0f) {
return zero_float3();
}
dir.y = -sqrtf(max(1.0f - dir.x * dir.x - dir.z * dir.z, 0.0f));
/* reflection */
const float3 I = make_float3(0.0f, -1.0f, 0.0f);
return 2.0f * dot(dir, I) * dir - I;
}
ccl_device float2 direction_to_mirrorball(float3 dir)
{
/* inverse of mirrorball_to_direction */
dir.y -= 1.0f;
const float div = 2.0f * sqrtf(max(-0.5f * dir.y, 0.0f));
if (div > 0.0f) {
dir /= div;
}
const float u = 0.5f * (dir.x + 1.0f);
const float v = 0.5f * (dir.z + 1.0f);
return make_float2(u, v);
}
ccl_device dual2 direction_to_mirrorball(dual3 dir)
{
/* inverse of mirrorball_to_direction */
dir.val.y -= 1.0f;
dir = dir * 0.5f * inversesqrt(-0.5f * dir.y());
const dual1 u = 0.5f * (dir.x() + 1.0f);
const dual1 v = 0.5f * (dir.z() + 1.0f);
return make_float2(u, v);
}
/* Single face of a equiangular cube map projection as described in
* https://blog.google/products/google-ar-vr/bringing-pixels-front-and-center-vr-video/ */
ccl_device float3 equiangular_cubemap_face_to_direction(float u, float v)
{
u = tanf((0.5f - u) * M_PI_2_F);
v = tanf((v - 0.5f) * M_PI_2_F);
return normalize(make_float3(1.0f, u, v));
}
ccl_device float2 direction_to_equiangular_cubemap_face(const float3 dir)
{
const float u = 0.5f - atan2f(dir.y, dir.x) * 2.0f / M_PI_F;
const float v = atan2f(dir.z, dir.x) * 2.0f / M_PI_F + 0.5f;
return make_float2(u, v);
}
ccl_device_inline float3 panorama_to_direction(ccl_constant KernelCamera *cam,
const float u,
float v)
{
switch (cam->panorama_type) {
case PANORAMA_EQUIRECTANGULAR:
return equirectangular_range_to_direction(u, v, cam->equirectangular_range);
case PANORAMA_EQUIANGULAR_CUBEMAP_FACE:
return equiangular_cubemap_face_to_direction(u, v);
case PANORAMA_MIRRORBALL:
return mirrorball_to_direction(u, v);
case PANORAMA_FISHEYE_EQUIDISTANT:
return fisheye_equidistant_to_direction(u, v, cam->fisheye_fov);
case PANORAMA_FISHEYE_LENS_POLYNOMIAL:
return fisheye_lens_polynomial_to_direction(u,
v,
cam->fisheye_lens_polynomial_bias,
cam->fisheye_lens_polynomial_coefficients,
cam->fisheye_fov,
cam->sensorwidth,
cam->sensorheight);
case PANORAMA_CENTRAL_CYLINDRICAL:
return central_cylindrical_to_direction(u, v, cam->central_cylindrical_range);
case PANORAMA_FISHEYE_EQUISOLID:
default:
return fisheye_equisolid_to_direction(
u, v, cam->fisheye_lens, cam->fisheye_fov, cam->sensorwidth, cam->sensorheight);
}
}
ccl_device_inline float2 direction_to_panorama(ccl_constant KernelCamera *cam, const float3 dir)
{
switch (cam->panorama_type) {
case PANORAMA_EQUIRECTANGULAR:
return direction_to_equirectangular_range(dir, cam->equirectangular_range);
case PANORAMA_EQUIANGULAR_CUBEMAP_FACE:
return direction_to_equiangular_cubemap_face(dir);
case PANORAMA_MIRRORBALL:
return direction_to_mirrorball(dir);
case PANORAMA_FISHEYE_EQUIDISTANT:
return direction_to_fisheye_equidistant(dir, cam->fisheye_fov);
case PANORAMA_FISHEYE_LENS_POLYNOMIAL:
return direction_to_fisheye_lens_polynomial(dir,
cam->fisheye_lens_polynomial_bias,
cam->fisheye_lens_polynomial_coefficients,
cam->sensorwidth,
cam->sensorheight);
case PANORAMA_CENTRAL_CYLINDRICAL:
return direction_to_central_cylindrical(dir, cam->central_cylindrical_range);
case PANORAMA_FISHEYE_EQUISOLID:
default:
return direction_to_fisheye_equisolid(
dir, cam->fisheye_lens, cam->sensorwidth, cam->sensorheight);
}
}
ccl_device_inline void spherical_stereo_transform(ccl_constant KernelCamera *cam,
ccl_private float3 *P,
ccl_private float3 *D)
{
float interocular_offset = cam->interocular_offset;
/* Interocular offset of zero means either non stereo, or stereo without
* spherical stereo. */
kernel_assert(interocular_offset != 0.0f);
if (cam->pole_merge_angle_to > 0.0f) {
const float pole_merge_angle_from = cam->pole_merge_angle_from;
const float pole_merge_angle_to = cam->pole_merge_angle_to;
const float altitude = fabsf(safe_asinf((*D).z));
if (altitude > pole_merge_angle_to) {
interocular_offset = 0.0f;
}
else if (altitude > pole_merge_angle_from) {
const float fac = (altitude - pole_merge_angle_from) /
(pole_merge_angle_to - pole_merge_angle_from);
const float fade = cosf(fac * M_PI_2_F);
interocular_offset *= fade;
}
}
const float3 up = make_float3(0.0f, 0.0f, 1.0f);
const float3 side = normalize(cross(*D, up));
const float3 stereo_offset = side * interocular_offset;
*P += stereo_offset;
/* Convergence distance is FLT_MAX in the case of parallel convergence mode,
* no need to modify direction in this case either. */
const float convergence_distance = cam->convergence_distance;
if (convergence_distance != FLT_MAX) {
const float3 screen_offset = convergence_distance * (*D);
*D = normalize(screen_offset - stereo_offset);
}
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,120 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
CCL_NAMESPACE_BEGIN
ccl_device ccl_private ShaderClosure *closure_alloc(ccl_private ShaderData *sd,
const uint size,
ClosureType type,
Spectrum weight)
{
kernel_assert(size <= sizeof(ShaderClosure));
(void)size;
if (sd->num_closure_left == 0) {
return nullptr;
}
ccl_private ShaderClosure *sc = &sd->closure[sd->num_closure];
sc->type = type;
sc->weight = weight;
sd->num_closure++;
sd->num_closure_left--;
return sc;
}
ccl_device ccl_private void *closure_alloc_extra(ccl_private ShaderData *sd, const int size)
{
/* Allocate extra space for closure that need more parameters. We allocate
* in chunks of sizeof(ShaderClosure) starting from the end of the closure
* array.
*
* This lets us keep the same fast array iteration over closures, as we
* found linked list iteration and iteration with skipping to be slower. */
const int num_extra = ((size + sizeof(ShaderClosure) - 1) / sizeof(ShaderClosure));
if (num_extra > sd->num_closure_left) {
/* Remove previous closure if it was allocated. */
sd->num_closure--;
sd->num_closure_left++;
return nullptr;
}
sd->num_closure_left -= num_extra;
return (ccl_private void *)(sd->closure + sd->num_closure + sd->num_closure_left);
}
ccl_device_inline float closure_sample_weight(const int flag, ccl_private Spectrum &weight)
{
kernel_assert(isfinite_safe(weight));
/* No negative weights allowed. */
weight = max(weight, zero_float3());
const float sample_weight = fabsf(average(weight));
/* Do not perform weight cutoff for volume shaders, because large volume with low density could
* still contribute significantly to the scene. It should be up to the volume shader to decide
* the cutoff. */
/* Use comparison this way to help dealing with non-finite weight: if the average is not finite
* we will not allocate new closure. */
if ((sample_weight >= CLOSURE_WEIGHT_CUTOFF) || (flag & SD_IS_VOLUME_SHADER_EVAL)) {
return sample_weight;
}
return 0.0f;
}
ccl_device_inline ccl_private ShaderClosure *bsdf_alloc(ccl_private ShaderData *sd,
const int size,
Spectrum weight)
{
const float sample_weight = closure_sample_weight(sd->flag, weight);
if (!(sample_weight > 0.0f)) {
return nullptr;
}
ccl_private ShaderClosure *sc = closure_alloc(sd, size, CLOSURE_NONE_ID, weight);
if (!sc) {
return nullptr;
}
sc->sample_weight = sample_weight;
return sc;
}
/* Allocate BSDF closures that are possibly used for emission. */
template<class Bsdf>
ccl_device_inline ccl_private Bsdf *bsdf_alloc_maybe_emission(ccl_private ShaderData *sd,
ccl_private Bsdf *bsdf,
const uint32_t path_flag,
Spectrum weight)
{
if (path_flag & PATH_RAY_EMISSION) {
/* When evaluating emission we don't allocate closures, but we still need a valid closure to
* compute the weight. */
const float sample_weight = closure_sample_weight(sd->flag, weight);
if (!(sample_weight > 0.0f)) {
return nullptr;
}
bsdf->weight = weight;
bsdf->sample_weight = sample_weight;
}
else {
bsdf = (ccl_private Bsdf *)bsdf_alloc(sd, sizeof(Bsdf), weight);
}
return bsdf;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,725 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
// clang-format off
#include "kernel/closure/bsdf_ashikhmin_velvet.h"
#include "kernel/closure/bsdf_diffuse.h"
#include "kernel/closure/bsdf_oren_nayar.h"
#include "kernel/closure/bsdf_phong_ramp.h"
#include "kernel/closure/bsdf_diffuse_ramp.h"
#include "kernel/closure/bsdf_microfacet.h"
#include "kernel/closure/bsdf_burley.h"
#include "kernel/closure/bsdf_sheen.h"
#include "kernel/closure/bsdf_transparent.h"
#include "kernel/closure/bsdf_ray_portal.h"
#include "kernel/closure/bsdf_ashikhmin_shirley.h"
#include "kernel/closure/bsdf_toon.h"
#include "kernel/closure/bsdf_hair.h"
#include "kernel/closure/bsdf_principled_hair_chiang.h"
#include "kernel/closure/bsdf_principled_hair_huang.h"
// clang-format on
CCL_NAMESPACE_BEGIN
/* Returns the square of the roughness of the closure if it has roughness,
* 0 for singular closures and 1 otherwise. */
ccl_device_inline float bsdf_get_specular_roughness_squared(const ccl_private ShaderClosure *sc)
{
if (CLOSURE_IS_BSDF_SINGULAR(sc->type)) {
return 0.0f;
}
if (CLOSURE_IS_BSDF_MICROFACET(sc->type)) {
ccl_private MicrofacetBsdf *bsdf = (ccl_private MicrofacetBsdf *)sc;
return bsdf->alpha_x * bsdf->alpha_y;
}
return 1.0f;
}
ccl_device_inline float bsdf_get_roughness_pass_squared(const ccl_private ShaderClosure *sc)
{
if (sc->type == CLOSURE_BSDF_OREN_NAYAR_ID || sc->type == CLOSURE_BSDF_ROUGH_TRANSLUCENT_ID) {
ccl_private OrenNayarBsdf *bsdf = (ccl_private OrenNayarBsdf *)sc;
return sqr(sqr(bsdf->param.roughness));
}
/* For the Principled BSDF, we want the Roughness pass to return the value that
* was set in the node. However, this value doesn't affect all closures (e.g.
* diffuse), so skip those that don't really have a concept of roughness. */
if (CLOSURE_IS_BSDF_DIFFUSE(sc->type)) {
return -1.0f;
}
return bsdf_get_specular_roughness_squared(sc);
}
/* Widen the compact ray differential dD after a non-specular bounce so that
* texture mip selection on subsequent hits reflects the BSDF lobe's angular
* spread. This significantly save memory, and is needed to make image cache
* memory usage scale with render tile size rather than overall resolution.
*
* This must be done consistently between next event estimation and forward
* sampling for both to converge to the same result for MIS. This is not just
* a theoretical concern, but can otherwise lead to seams.
*
* To achieve that, the sampled roughness is computed as a MIS weighted
* average. This makes it so directions with high contribution from sharp
* BSDFs have a lower roughness, as they will have a high MIS weight. */
ccl_device_forceinline float bsdf_widen_dD(const float prev_dD, const float avg_roughness_squared)
{
if (!(avg_roughness_squared > 0.0f)) {
return prev_dD;
}
return max(prev_dD, sqrtf(avg_roughness_squared));
}
/* An additional term to smooth illumination on grazing angles when using bump mapping
* based on "A Microfacet-Based Shadowing Function to Solve the Bump Terminator Problem"
* by Alejandro Conty Estevez, Pascal Lecocq, and Clifford Stein. It preserves detail
* close to the shadow terminator, and doesn't "wash out" intermediate bumps using a
* Cook-Torrance GGX function for shading. */
ccl_device_inline float bump_shadowing_term(const ccl_private ShaderData *sd,
const ccl_private ShaderClosure *sc,
const float3 I,
const bool is_eval)
{
if (isequal(sc->N, sd->N)) {
return 1.0f;
}
/* Smoothing doesn't apply to curve geometry. */
if (sd->type & PRIMITIVE_CURVE) {
return 1.0f;
}
/* In order to avoid artifacts at the shadow terminator when using smooth normals,
* the BSDF evaluation functions allow for light leaking through the actual geometry
* and only checks that the directions are in the correct hemisphere w.r.t. the
* shading normal.
* However, when using bump/normal mapping, this can lead to light leaking not just
* "around" the shadow terminator, but to the rear side of supposedly opaque geometry.
* In order to detect this case, we can ensure that the direction is also valid w.r.t.
* the smoothed (but non-bump-mapped) normal `sd->N` (or `Ns` for short below).
*
* `dot(Ns, I) * dot(Ns, N)` tells us if I and N are on the same side of the smoothed geometry.
* If incoming(I) and normal(N) are on the same side we reject refractions, `dot(N, I) < 0`.
* If they are on different sides we reject reflections, `dot(N, I) > 0`. */
const float cosNsI = dot(sd->N, I);
const float cosNsN = dot(sd->N, sc->N);
const float cosNI = dot(sc->N, I);
const bool is_diffuse = CLOSURE_IS_BSDF_DIFFUSE(sc->type);
if (cosNsI * cosNsN * cosNI < 0.0f && (is_eval || is_diffuse)) {
return 0.0f;
}
/* The above test applies to all closures, but the softening only applies to diffuse ones. */
if (!is_diffuse) {
return 1.0f;
}
/* When bump map correction is not used do skip the smoothing. */
if ((sd->flag & SD_USE_BUMP_MAP_CORRECTION) == 0) {
return 1.0f;
}
/* Get absolute incoming and shader normal deviation from smoothed normal, then clamp. */
const float cos_i = fabsf(cosNsI);
const float cos_d = fabsf(cosNsN);
if (cos_d >= 1.0f || cos_i >= 1.0f) {
return 1.0f;
}
if (cos_i < 1e-6f) {
return 0.0f;
}
/* Get GGX shading values for final smoothing. */
const float tan2_d = 1.0f / sqr(cos_d) - 1.0f;
const float bump_alpha2 = saturatef(0.125f * tan2_d);
/* Return smoothed value to avoid discontinuity at perpendicular angle. */
return bsdf_G<MicrofacetType::GGX>(bump_alpha2, cos_i);
}
ccl_device_inline float shift_cos_in(float cos_in, const float frequency_multiplier)
{
/* Shadow terminator workaround, taken from Appleseed.
* SPDX-License-Identifier: MIT
* Copyright (c) 2019 Francois Beaune, The appleseedhq Organization */
cos_in = min(cos_in, 1.0f);
const float angle = fast_acosf(cos_in);
const float val = max(cosf(angle * frequency_multiplier), 0.0f) / cos_in;
return val;
}
ccl_device_inline bool bsdf_is_transmission(const ccl_private ShaderClosure *sc, const float3 wo)
{
return dot(sc->N, wo) < 0.0f;
}
ccl_device_inline int bsdf_sample(KernelGlobals kg,
ccl_private ShaderData *sd,
const ccl_private ShaderClosure *sc,
const float3 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf,
ccl_private float2 *sampled_roughness,
ccl_private float *eta)
{
/* For curves use the smooth normal, particularly for ribbons the geometric
* normal gives too much darkening otherwise. */
*eval = zero_spectrum();
*pdf = 0.f;
int label = LABEL_NONE;
const float3 Ng = (sd->type & PRIMITIVE_CURVE) ? sc->N : sd->Ng;
const float2 rand_xy = make_float2(rand);
switch (sc->type) {
case CLOSURE_BSDF_DIFFUSE_ID:
label = bsdf_diffuse_sample(sc, Ng, sd->wi, rand_xy, eval, wo, pdf);
*sampled_roughness = one_float2();
*eta = 1.0f;
break;
#if defined(__SVM__) || defined(__OSL__)
case CLOSURE_BSDF_OREN_NAYAR_ID:
label = bsdf_oren_nayar_sample(
sc, Ng, sd->wi, rand_xy, eval, wo, pdf, sampled_roughness, eta);
break;
case CLOSURE_BSDF_ROUGH_TRANSLUCENT_ID:
label = bsdf_rough_translucent_sample(
sc, Ng, sd->wi, rand_xy, eval, wo, pdf, sampled_roughness, eta);
break;
# ifdef __OSL__
case CLOSURE_BSDF_BURLEY_ID:
label = bsdf_burley_sample(sc, Ng, sd->wi, rand_xy, eval, wo, pdf);
*sampled_roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_PHONG_RAMP_ID:
label = bsdf_phong_ramp_sample(sc, Ng, sd->wi, rand_xy, eval, wo, pdf, sampled_roughness);
*eta = 1.0f;
break;
case CLOSURE_BSDF_DIFFUSE_RAMP_ID:
label = bsdf_diffuse_ramp_sample(sc, Ng, sd->wi, rand_xy, eval, wo, pdf);
*sampled_roughness = one_float2();
*eta = 1.0f;
break;
# endif
case CLOSURE_BSDF_TRANSLUCENT_ID:
label = bsdf_translucent_sample(sc, Ng, sd->wi, rand_xy, eval, wo, pdf);
*sampled_roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_TRANSPARENT_ID:
label = bsdf_transparent_sample(sc, Ng, sd->wi, eval, wo, pdf);
*sampled_roughness = zero_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_RAY_PORTAL_ID:
/* ray portals are not handled by the BSDF code, we should never get here */
kernel_assert(false);
break;
case CLOSURE_BSDF_MICROFACET_GGX_ID:
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_GGX_GLASS_ID:
label = bsdf_microfacet_ggx_sample(
kg, sc, Ng, sd->wi, rand, eval, wo, pdf, sampled_roughness, eta);
break;
case CLOSURE_BSDF_THIN_GLASS_TRANSMISSION_ID:
label = bsdf_thin_glass_transmission_sample(
kg, sc, Ng, sd->wi, rand, eval, wo, pdf, sampled_roughness, eta);
break;
case CLOSURE_BSDF_MICROFACET_BECKMANN_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_GLASS_ID:
label = bsdf_microfacet_beckmann_sample(
kg, sc, Ng, sd->wi, rand, eval, wo, pdf, sampled_roughness, eta);
break;
case CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ID:
label = bsdf_ashikhmin_shirley_sample(
sc, Ng, sd->wi, rand_xy, eval, wo, pdf, sampled_roughness);
*eta = 1.0f;
break;
case CLOSURE_BSDF_ASHIKHMIN_VELVET_ID:
label = bsdf_ashikhmin_velvet_sample(sc, Ng, sd->wi, rand_xy, eval, wo, pdf);
*sampled_roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_DIFFUSE_TOON_ID:
label = bsdf_diffuse_toon_sample(sc, Ng, sd->wi, rand_xy, eval, wo, pdf);
*sampled_roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_GLOSSY_TOON_ID:
label = bsdf_glossy_toon_sample(sc, Ng, sd->wi, rand_xy, eval, wo, pdf);
// double check if this is valid
*sampled_roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_HAIR_REFLECTION_ID:
label = bsdf_hair_reflection_sample(
sc, Ng, sd->wi, rand_xy, eval, wo, pdf, sampled_roughness);
*eta = 1.0f;
break;
case CLOSURE_BSDF_HAIR_TRANSMISSION_ID:
label = bsdf_hair_transmission_sample(
sc, Ng, sd->wi, rand_xy, eval, wo, pdf, sampled_roughness);
*eta = 1.0f;
break;
# ifdef __PRINCIPLED_HAIR__
case CLOSURE_BSDF_HAIR_CHIANG_ID:
label = bsdf_hair_chiang_sample(kg, sc, sd, rand, eval, wo, pdf, sampled_roughness);
*eta = 1.0f;
break;
case CLOSURE_BSDF_HAIR_HUANG_ID:
label = bsdf_hair_huang_sample(kg, sc, sd, rand, eval, wo, pdf, sampled_roughness);
*eta = 1.0f;
break;
# endif
case CLOSURE_BSDF_SHEEN_ID:
label = bsdf_sheen_sample(sc, Ng, sd->wi, rand_xy, eval, wo, pdf);
*sampled_roughness = one_float2();
*eta = 1.0f;
break;
#endif
default:
label = LABEL_NONE;
break;
}
/* Test if BSDF sample should be treated as transparent for background. */
if (label & LABEL_TRANSMIT) {
const float threshold_squared = kernel_data.background.transparent_roughness_squared_threshold;
if (threshold_squared >= 0.0f && !(label & LABEL_DIFFUSE)) {
if (bsdf_get_specular_roughness_squared(sc) <= threshold_squared) {
label |= LABEL_TRANSMIT_TRANSPARENT;
}
}
}
else if (label != LABEL_NONE) {
/* Shadow terminator offset. */
const float frequency_multiplier =
kernel_data_fetch(objects, sd->object).shadow_terminator_shading_offset;
if (frequency_multiplier > 1.0f) {
const float cosNO = dot(*wo, sc->N);
*eval *= shift_cos_in(cosNO, frequency_multiplier);
}
*eval *= bump_shadowing_term(sd, sc, *wo, false);
}
#ifdef WITH_CYCLES_DEBUG
kernel_assert(*pdf >= 0.0f);
kernel_assert(eval->x >= 0.0f && eval->y >= 0.0f && eval->z >= 0.0f);
#endif
return label;
}
ccl_device_inline void bsdf_roughness_eta(const ccl_private ShaderClosure *sc,
const float3 wo,
ccl_private float2 *roughness,
ccl_private float *eta)
{
#ifdef __SVM__
float alpha = 1.0f;
#endif
switch (sc->type) {
case CLOSURE_BSDF_DIFFUSE_ID:
*roughness = one_float2();
*eta = 1.0f;
break;
#ifdef __SVM__
case CLOSURE_BSDF_OREN_NAYAR_ID:
case CLOSURE_BSDF_ROUGH_TRANSLUCENT_ID:
*roughness = one_float2();
*eta = 1.0f;
break;
# ifdef __OSL__
case CLOSURE_BSDF_BURLEY_ID:
*roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_PHONG_RAMP_ID:
alpha = phong_ramp_exponent_to_roughness(((const ccl_private PhongRampBsdf *)sc)->exponent);
*roughness = make_float2(alpha, alpha);
*eta = 1.0f;
break;
case CLOSURE_BSDF_DIFFUSE_RAMP_ID:
*roughness = one_float2();
*eta = 1.0f;
break;
# endif
case CLOSURE_BSDF_TRANSLUCENT_ID:
*roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_TRANSPARENT_ID:
case CLOSURE_BSDF_RAY_PORTAL_ID:
*roughness = zero_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_MICROFACET_GGX_ID:
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_GGX_GLASS_ID:
case CLOSURE_BSDF_THIN_GLASS_TRANSMISSION_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_GLASS_ID: {
const ccl_private MicrofacetBsdf *bsdf = (const ccl_private MicrofacetBsdf *)sc;
*roughness = make_float2(bsdf->alpha_x, bsdf->alpha_y);
*eta = (bsdf_is_transmission(sc, wo)) ? bsdf->ior : 1.0f;
break;
}
case CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ID: {
const ccl_private MicrofacetBsdf *bsdf = (const ccl_private MicrofacetBsdf *)sc;
*roughness = make_float2(bsdf->alpha_x, bsdf->alpha_y);
*eta = 1.0f;
break;
}
case CLOSURE_BSDF_ASHIKHMIN_VELVET_ID:
*roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_DIFFUSE_TOON_ID:
*roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_GLOSSY_TOON_ID:
// double check if this is valid
*roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_HAIR_REFLECTION_ID:
*roughness = make_float2(((ccl_private HairBsdf *)sc)->roughness1,
((ccl_private HairBsdf *)sc)->roughness2);
*eta = 1.0f;
break;
case CLOSURE_BSDF_HAIR_TRANSMISSION_ID:
*roughness = make_float2(((ccl_private HairBsdf *)sc)->roughness1,
((ccl_private HairBsdf *)sc)->roughness2);
*eta = 1.0f;
break;
# ifdef __PRINCIPLED_HAIR__
case CLOSURE_BSDF_HAIR_CHIANG_ID:
alpha = ((ccl_private ChiangHairBSDF *)sc)->m0_roughness;
*roughness = make_float2(alpha, alpha);
*eta = 1.0f;
break;
case CLOSURE_BSDF_HAIR_HUANG_ID:
alpha = ((ccl_private HuangHairBSDF *)sc)->roughness;
*roughness = make_float2(alpha, alpha);
*eta = 1.0f;
break;
# endif
case CLOSURE_BSDF_SHEEN_ID:
alpha = ((ccl_private SheenBsdf *)sc)->roughness;
*roughness = make_float2(alpha, alpha);
*eta = 1.0f;
break;
#endif
default:
*roughness = one_float2();
*eta = 1.0f;
break;
}
}
ccl_device_inline int bsdf_label(const KernelGlobals kg,
const ccl_private ShaderClosure *sc,
const float3 wo)
{
/* For curves use the smooth normal, particularly for ribbons the geometric
* normal gives too much darkening otherwise. */
int label;
switch (sc->type) {
case CLOSURE_BSDF_DIFFUSE_ID:
case CLOSURE_BSSRDF_BURLEY_ID:
case CLOSURE_BSSRDF_RANDOM_WALK_ID:
case CLOSURE_BSSRDF_RANDOM_WALK_SKIN_ID:
case CLOSURE_BSSRDF_RANDOM_WALK_LEGACY_ID:
label = LABEL_REFLECT | LABEL_DIFFUSE;
break;
#ifdef __SVM__
case CLOSURE_BSDF_OREN_NAYAR_ID:
label = LABEL_REFLECT | LABEL_DIFFUSE;
break;
# ifdef __OSL__
case CLOSURE_BSDF_BURLEY_ID:
label = LABEL_REFLECT | LABEL_DIFFUSE;
break;
case CLOSURE_BSDF_PHONG_RAMP_ID:
label = LABEL_REFLECT | LABEL_GLOSSY;
break;
case CLOSURE_BSDF_DIFFUSE_RAMP_ID:
label = LABEL_REFLECT | LABEL_DIFFUSE;
break;
# endif
case CLOSURE_BSDF_TRANSLUCENT_ID:
case CLOSURE_BSDF_ROUGH_TRANSLUCENT_ID:
label = LABEL_TRANSMIT | LABEL_DIFFUSE;
break;
case CLOSURE_BSDF_TRANSPARENT_ID:
label = LABEL_TRANSMIT | LABEL_TRANSPARENT;
break;
case CLOSURE_BSDF_RAY_PORTAL_ID:
label = LABEL_TRANSMIT | LABEL_RAY_PORTAL;
break;
case CLOSURE_BSDF_MICROFACET_GGX_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_ID:
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_GGX_GLASS_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_GLASS_ID: {
const ccl_private MicrofacetBsdf *bsdf = (const ccl_private MicrofacetBsdf *)sc;
label = ((bsdf_is_transmission(sc, wo)) ? LABEL_TRANSMIT : LABEL_REFLECT) |
((bsdf_microfacet_eval_flag(bsdf)) ? LABEL_GLOSSY : LABEL_SINGULAR);
break;
}
case CLOSURE_BSDF_THIN_GLASS_TRANSMISSION_ID:
label = LABEL_TRANSMIT | LABEL_GLOSSY;
break;
case CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ID:
label = LABEL_REFLECT | LABEL_GLOSSY;
break;
case CLOSURE_BSDF_ASHIKHMIN_VELVET_ID:
label = LABEL_REFLECT | LABEL_DIFFUSE;
break;
case CLOSURE_BSDF_DIFFUSE_TOON_ID:
label = LABEL_REFLECT | LABEL_DIFFUSE;
break;
case CLOSURE_BSDF_GLOSSY_TOON_ID:
label = LABEL_REFLECT | LABEL_GLOSSY;
break;
case CLOSURE_BSDF_HAIR_REFLECTION_ID:
label = LABEL_REFLECT | LABEL_GLOSSY;
break;
case CLOSURE_BSDF_HAIR_TRANSMISSION_ID:
label = LABEL_TRANSMIT | LABEL_GLOSSY;
break;
# ifdef __PRINCIPLED_HAIR__
case CLOSURE_BSDF_HAIR_CHIANG_ID:
if (bsdf_is_transmission(sc, wo)) {
label = LABEL_TRANSMIT | LABEL_GLOSSY;
}
else {
label = LABEL_REFLECT | LABEL_GLOSSY;
}
break;
case CLOSURE_BSDF_HAIR_HUANG_ID:
label = LABEL_REFLECT | LABEL_GLOSSY;
break;
# endif
case CLOSURE_BSDF_SHEEN_ID:
label = LABEL_REFLECT | LABEL_DIFFUSE;
break;
#endif
default:
label = LABEL_NONE;
break;
}
/* Test if BSDF sample should be treated as transparent for background. */
if (label & LABEL_TRANSMIT) {
const float threshold_squared = kernel_data.background.transparent_roughness_squared_threshold;
if (threshold_squared >= 0.0f) {
if (bsdf_get_specular_roughness_squared(sc) <= threshold_squared) {
label |= LABEL_TRANSMIT_TRANSPARENT;
}
}
}
return label;
}
#ifndef __KERNEL_CUDA__
ccl_device
#else
ccl_device_inline
#endif
Spectrum
bsdf_eval(KernelGlobals kg,
ccl_private ShaderData *sd,
const ccl_private ShaderClosure *sc,
const float3 wo,
ccl_private float *pdf)
{
Spectrum eval = zero_spectrum();
*pdf = 0.f;
const float bump_shadowing = bump_shadowing_term(sd, sc, wo, true);
if (bump_shadowing == 0.0f) {
return zero_spectrum();
}
switch (sc->type) {
case CLOSURE_BSDF_DIFFUSE_ID:
eval = bsdf_diffuse_eval(sc, sd->wi, wo, pdf);
break;
#if defined(__SVM__) || defined(__OSL__)
case CLOSURE_BSDF_OREN_NAYAR_ID:
eval = bsdf_oren_nayar_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_ROUGH_TRANSLUCENT_ID:
eval = bsdf_rough_translucent_eval(sc, sd->wi, wo, pdf);
break;
# ifdef __OSL__
case CLOSURE_BSDF_BURLEY_ID:
eval = bsdf_burley_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_PHONG_RAMP_ID:
eval = bsdf_phong_ramp_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_DIFFUSE_RAMP_ID:
eval = bsdf_diffuse_ramp_eval(sc, sd->wi, wo, pdf);
break;
# endif
case CLOSURE_BSDF_TRANSLUCENT_ID:
eval = bsdf_translucent_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_TRANSPARENT_ID:
eval = bsdf_transparent_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_RAY_PORTAL_ID:
eval = bsdf_ray_portal_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_MICROFACET_GGX_ID:
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_GGX_GLASS_ID:
eval = bsdf_microfacet_ggx_eval(kg, sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_THIN_GLASS_TRANSMISSION_ID:
eval = bsdf_thin_glass_transmission_eval(kg, sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_MICROFACET_BECKMANN_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_GLASS_ID:
eval = bsdf_microfacet_beckmann_eval(kg, sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ID:
eval = bsdf_ashikhmin_shirley_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_ASHIKHMIN_VELVET_ID:
eval = bsdf_ashikhmin_velvet_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_DIFFUSE_TOON_ID:
eval = bsdf_diffuse_toon_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_GLOSSY_TOON_ID:
eval = bsdf_glossy_toon_eval(sc, sd->wi, wo, pdf);
break;
# ifdef __PRINCIPLED_HAIR__
case CLOSURE_BSDF_HAIR_CHIANG_ID:
eval = bsdf_hair_chiang_eval(kg, sd, sc, wo, pdf);
break;
case CLOSURE_BSDF_HAIR_HUANG_ID:
eval = bsdf_hair_huang_eval(kg, sd, sc, wo, pdf);
break;
# endif
case CLOSURE_BSDF_HAIR_REFLECTION_ID:
eval = bsdf_hair_reflection_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_HAIR_TRANSMISSION_ID:
eval = bsdf_hair_transmission_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_SHEEN_ID:
eval = bsdf_sheen_eval(sc, sd->wi, wo, pdf);
break;
#endif
default:
break;
}
eval *= bump_shadowing;
/* Shadow terminator offset. */
const float frequency_multiplier =
kernel_data_fetch(objects, sd->object).shadow_terminator_shading_offset;
if (frequency_multiplier > 1.0f) {
const float cosNO = dot(wo, sc->N);
if (cosNO >= 0.0f) {
eval *= shift_cos_in(cosNO, frequency_multiplier);
}
}
#ifdef WITH_CYCLES_DEBUG
kernel_assert(*pdf >= 0.0f);
kernel_assert(eval.x >= 0.0f && eval.y >= 0.0f && eval.z >= 0.0f);
#endif
return eval;
}
ccl_device void bsdf_blur(ccl_private ShaderClosure *sc, const float roughness)
{
/* TODO: do we want to blur volume closures? */
#if defined(__SVM__) || defined(__OSL__)
switch (sc->type) {
case CLOSURE_BSDF_MICROFACET_GGX_ID:
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_GGX_GLASS_ID:
case CLOSURE_BSDF_THIN_GLASS_TRANSMISSION_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_GLASS_ID:
/* TODO: Recompute energy preservation after blur? */
bsdf_microfacet_blur(sc, roughness);
break;
case CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ID:
bsdf_ashikhmin_shirley_blur(sc, roughness);
break;
# ifdef __PRINCIPLED_HAIR__
case CLOSURE_BSDF_HAIR_CHIANG_ID:
bsdf_hair_chiang_blur(sc, roughness);
break;
case CLOSURE_BSDF_HAIR_HUANG_ID:
bsdf_hair_huang_blur(sc, roughness);
break;
# endif
default:
break;
}
#endif
}
ccl_device_inline Spectrum bsdf_albedo(KernelGlobals kg,
const ccl_private ShaderData *sd,
const ccl_private ShaderClosure *sc,
const bool reflection,
const bool transmission)
{
Spectrum albedo = sc->weight;
/* Some closures include additional components such as Fresnel terms that cause their albedo to
* be below 1. The point of this function is to return a best-effort estimation of their albedo,
* meaning the amount of reflected/refracted light that would be expected when illuminated by a
* uniform white background.
* This is used for the denoising albedo pass and diffuse/glossy/transmission color passes.
* NOTE: This should always match the sample_weight of the closure - as in, if there's an albedo
* adjustment in here, the sample_weight should also be reduced accordingly.
* TODO(lukas): Consider calling this function to determine the sample_weight? Would be a bit of
* extra overhead though. */
#if defined(__SVM__) || defined(__OSL__)
if (CLOSURE_IS_BSDF_MICROFACET(sc->type)) {
albedo *= bsdf_microfacet_estimate_albedo(
kg, sd->wi, (const ccl_private MicrofacetBsdf *)sc, reflection, transmission);
}
# ifdef __PRINCIPLED_HAIR__
else if (sc->type == CLOSURE_BSDF_HAIR_CHIANG_ID) {
/* TODO(lukas): Principled Hair could also be split into a glossy and a transmission component,
* similar to Glass BSDFs. */
albedo *= bsdf_hair_chiang_albedo(sd, sc);
}
else if (sc->type == CLOSURE_BSDF_HAIR_HUANG_ID) {
albedo *= bsdf_hair_huang_albedo(sd, sc);
}
# endif
#endif
return albedo;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,228 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/*
* ASHIKHMIN SHIRLEY BSDF
*
* Implementation of
* Michael Ashikhmin and Peter Shirley: "An Anisotropic Phong BRDF Model" (2000)
*
* The Fresnel factor is missing to get a separable bsdf (intensity*color), as is
* the case with all other microfacet-based BSDF implementations in Cycles.
*
* Other than that, the implementation directly follows the paper.
*/
#pragma once
#include "kernel/types.h"
#include "kernel/closure/bsdf_microfacet.h"
CCL_NAMESPACE_BEGIN
ccl_device int bsdf_ashikhmin_shirley_setup(ccl_private MicrofacetBsdf *bsdf)
{
bsdf->alpha_x = clamp(bsdf->alpha_x, 1e-4f, 1.0f);
bsdf->alpha_y = clamp(bsdf->alpha_y, 1e-4f, 1.0f);
bsdf->fresnel_type = MicrofacetFresnel::NONE;
bsdf->type = CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ID;
return SD_BSDF | SD_BSDF_HAS_EVAL;
}
ccl_device void bsdf_ashikhmin_shirley_blur(ccl_private ShaderClosure *sc, const float roughness)
{
ccl_private MicrofacetBsdf *bsdf = (ccl_private MicrofacetBsdf *)sc;
bsdf->alpha_x = fmaxf(roughness, bsdf->alpha_x);
bsdf->alpha_y = fmaxf(roughness, bsdf->alpha_y);
}
ccl_device_inline float bsdf_ashikhmin_shirley_roughness_to_exponent(const float roughness)
{
return 2.0f / (roughness * roughness) - 2.0f;
}
ccl_device_forceinline Spectrum bsdf_ashikhmin_shirley_eval(const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private MicrofacetBsdf *bsdf = (const ccl_private MicrofacetBsdf *)sc;
const float3 N = bsdf->N;
float NdotI = dot(N, wi);
float NdotO = dot(N, wo);
float out = 0.0f;
if (fmaxf(bsdf->alpha_x, bsdf->alpha_y) <= 1e-4f || (NdotI < 0.0f) || (NdotO < 0.0f)) {
*pdf = 0.0f;
return zero_spectrum();
}
NdotI = fmaxf(NdotI, 1e-6f);
NdotO = fmaxf(NdotO, 1e-6f);
const float3 H = normalize(wi + wo);
const float HdotI = fmaxf(fabsf(dot(H, wi)), 1e-6f);
const float HdotN = fmaxf(dot(H, N), 1e-6f);
/* pump from original paper
* (first derivative disc., but cancels the HdotI in the pdf nicely) */
const float pump = 1.0f / fmaxf(1e-6f, (HdotI * fmaxf(NdotI, NdotO)));
/* `pump` from D-BRDF paper. */
// float pump = 1.0f / fmaxf(1e-4f, ((NdotI + NdotO) * (NdotI * NdotO)));
const float n_x = bsdf_ashikhmin_shirley_roughness_to_exponent(bsdf->alpha_x);
const float n_y = bsdf_ashikhmin_shirley_roughness_to_exponent(bsdf->alpha_y);
if (n_x == n_y) {
/* isotropic */
const float e = n_x;
const float lobe = powf(HdotN, e);
const float norm = (n_x + 1.0f) / (8.0f * M_PI_F);
out = NdotO * norm * lobe * pump;
/* this is p_h / 4(H.I) (conversion from `wh measure` to `wi measure`, eq. 8 in paper). */
*pdf = norm * lobe / HdotI;
}
else {
/* anisotropic */
float3 X;
float3 Y;
make_orthonormals_tangent(N, bsdf->T, &X, &Y);
const float HdotX = dot(H, X);
const float HdotY = dot(H, Y);
float lobe;
if (HdotN < 1.0f) {
const float e = (n_x * HdotX * HdotX + n_y * HdotY * HdotY) / (1.0f - HdotN * HdotN);
lobe = powf(HdotN, e);
}
else {
lobe = 1.0f;
}
const float norm = sqrtf((n_x + 1.0f) * (n_y + 1.0f)) / (8.0f * M_PI_F);
out = NdotO * norm * lobe * pump;
*pdf = norm * lobe / HdotI;
}
return make_spectrum(out);
}
ccl_device_inline void bsdf_ashikhmin_shirley_sample_first_quadrant(float n_x,
const float n_y,
const float2 rand,
ccl_private float *phi,
ccl_private float *cos_theta)
{
*phi = atanf(sqrtf((n_x + 1.0f) / (n_y + 1.0f)) * tanf(M_PI_2_F * rand.x));
const float cos_phi = cosf(*phi);
const float sin_phi = sinf(*phi);
*cos_theta = powf(rand.y, 1.0f / (n_x * cos_phi * cos_phi + n_y * sin_phi * sin_phi + 1.0f));
}
ccl_device int bsdf_ashikhmin_shirley_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 wi,
float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf,
ccl_private float2 *sampled_roughness)
{
const ccl_private MicrofacetBsdf *bsdf = (const ccl_private MicrofacetBsdf *)sc;
*sampled_roughness = make_float2(bsdf->alpha_x, bsdf->alpha_y);
const float3 N = bsdf->N;
int label = LABEL_REFLECT | LABEL_GLOSSY;
const float NdotI = dot(N, wi);
if (!(NdotI > 0.0f)) {
*pdf = 0.0f;
*eval = zero_spectrum();
return LABEL_NONE;
}
const float n_x = bsdf_ashikhmin_shirley_roughness_to_exponent(bsdf->alpha_x);
const float n_y = bsdf_ashikhmin_shirley_roughness_to_exponent(bsdf->alpha_y);
/* get x,y basis on the surface for anisotropy */
float3 X;
float3 Y;
if (n_x == n_y) {
make_orthonormals(N, &X, &Y);
}
else {
make_orthonormals_tangent(N, bsdf->T, &X, &Y);
}
/* sample spherical coords for h in tangent space */
float phi;
float cos_theta;
if (n_x == n_y) {
/* isotropic sampling */
phi = M_2PI_F * rand.x;
cos_theta = powf(rand.y, 1.0f / (n_x + 1.0f));
}
else {
/* anisotropic sampling */
if (rand.x < 0.25f) { /* first quadrant */
rand.x *= 4.0f;
bsdf_ashikhmin_shirley_sample_first_quadrant(n_x, n_y, rand, &phi, &cos_theta);
}
else if (rand.x < 0.5f) { /* second quadrant */
rand.x = 4.0f * (0.5f - rand.x);
bsdf_ashikhmin_shirley_sample_first_quadrant(n_x, n_y, rand, &phi, &cos_theta);
phi = M_PI_F - phi;
}
else if (rand.x < 0.75f) { /* third quadrant */
rand.x = 4.0f * (rand.x - 0.5f);
bsdf_ashikhmin_shirley_sample_first_quadrant(n_x, n_y, rand, &phi, &cos_theta);
phi = M_PI_F + phi;
}
else { /* fourth quadrant */
rand.x = 4.0f * (1.0f - rand.x);
bsdf_ashikhmin_shirley_sample_first_quadrant(n_x, n_y, rand, &phi, &cos_theta);
phi = 2.0f * M_PI_F - phi;
}
}
/* get half vector in tangent space */
const float3 h = spherical_cos_to_direction(cos_theta, phi);
/* half vector to world space */
float3 H = to_global(h, X, Y, N);
const float HdotI = dot(H, wi);
if (HdotI < 0.0f) {
H = -H;
}
/* reflect wi on H to get wo */
*wo = -wi + (2.0f * HdotI) * H;
/* Check hemisphere. */
if (dot(Ng, *wo) < 0.0f) {
*pdf = 0.0f;
*eval = zero_spectrum();
return LABEL_NONE;
}
if (fmaxf(bsdf->alpha_x, bsdf->alpha_y) <= 1e-4f) {
/* Some high number for MIS. */
*pdf = 1e6f;
*eval = make_spectrum(1e6f);
label = LABEL_REFLECT | LABEL_SINGULAR;
}
else {
/* leave the rest to eval */
*eval = bsdf_ashikhmin_shirley_eval(sc, wi, *wo, pdf);
}
return label;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,134 @@
/* SPDX-FileCopyrightText: 2009-2010 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/types.h"
#include "kernel/sample/mapping.h"
CCL_NAMESPACE_BEGIN
struct VelvetBsdf {
SHADER_CLOSURE_BASE;
float sigma;
float invsigma2;
};
static_assert(sizeof(ShaderClosure) >= sizeof(VelvetBsdf), "VelvetBsdf is too large!");
ccl_device int bsdf_ashikhmin_velvet_setup(ccl_private VelvetBsdf *bsdf)
{
const float sigma = fmaxf(bsdf->sigma, 0.01f);
bsdf->invsigma2 = 1.0f / (sigma * sigma);
bsdf->type = CLOSURE_BSDF_ASHIKHMIN_VELVET_ID;
return SD_BSDF | SD_BSDF_HAS_EVAL;
}
ccl_device Spectrum bsdf_ashikhmin_velvet_eval(const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private VelvetBsdf *bsdf = (const ccl_private VelvetBsdf *)sc;
const float m_invsigma2 = bsdf->invsigma2;
const float3 N = bsdf->N;
const float cosNI = dot(N, wi);
const float cosNO = dot(N, wo);
if (!(cosNI > 0 && cosNO > 0)) {
*pdf = 0.0f;
return zero_spectrum();
}
const float3 H = normalize(wi + wo);
const float cosNH = dot(N, H);
const float cosHI = fabsf(dot(wi, H));
if (!(fabsf(cosNH) < 1.0f - 1e-5f && cosHI > 1e-5f)) {
*pdf = 0.0f;
return zero_spectrum();
}
float cosNHdivHI = cosNH / cosHI;
cosNHdivHI = fmaxf(cosNHdivHI, 1e-5f);
const float fac1 = 2 * fabsf(cosNHdivHI * cosNI);
const float fac2 = 2 * fabsf(cosNHdivHI * cosNO);
const float sinNH2 = 1 - cosNH * cosNH;
const float sinNH4 = sinNH2 * sinNH2;
const float cotangent2 = (cosNH * cosNH) / sinNH2;
const float D = expf(-cotangent2 * m_invsigma2) * m_invsigma2 * M_1_PI_F / sinNH4;
const float G = fminf(1.0f, fminf(fac1, fac2)); // TODO: derive G from D analytically
const float out = 0.25f * (D * G) / cosNI;
*pdf = 0.5f * M_1_PI_F;
return make_spectrum(out);
}
ccl_device int bsdf_ashikhmin_velvet_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 wi,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const ccl_private VelvetBsdf *bsdf = (const ccl_private VelvetBsdf *)sc;
const float m_invsigma2 = bsdf->invsigma2;
const float3 N = bsdf->N;
// we are viewing the surface from above - send a ray out with uniform
// distribution over the hemisphere
sample_uniform_hemisphere(N, rand, wo, pdf);
if (!(dot(Ng, *wo) > 0)) {
*pdf = 0.0f;
*eval = zero_spectrum();
return LABEL_NONE;
}
const float3 H = normalize(wi + *wo);
const float cosNI = dot(N, wi);
const float cosNO = dot(N, *wo);
const float cosHI = fabsf(dot(wi, H));
const float cosNH = dot(N, H);
if (!(cosNI > 1e-5f && fabsf(cosNH) < 1.0f - 1e-5f && cosHI > 1e-5f)) {
*pdf = 0.0f;
*eval = zero_spectrum();
return LABEL_NONE;
}
float cosNHdivHI = cosNH / cosHI;
cosNHdivHI = fmaxf(cosNHdivHI, 1e-5f);
const float fac1 = 2 * fabsf(cosNHdivHI * cosNI);
const float fac2 = 2 * fabsf(cosNHdivHI * cosNO);
const float sinNH2 = 1 - cosNH * cosNH;
const float sinNH4 = sinNH2 * sinNH2;
const float cotangent2 = (cosNH * cosNH) / sinNH2;
const float D = expf(-cotangent2 * m_invsigma2) * m_invsigma2 * M_1_PI_F / sinNH4;
const float G = fminf(1.0f, fminf(fac1, fac2)); // TODO: derive G from D analytically
const float power = 0.25f * (D * G) / cosNI;
*eval = make_spectrum(power);
return LABEL_REFLECT | LABEL_DIFFUSE;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,88 @@
/* SPDX-FileCopyrightText: 2009-2025 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2025 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/closure/bsdf_util.h"
#include "kernel/sample/mapping.h"
CCL_NAMESPACE_BEGIN
#ifdef __OSL__
struct BurleyBsdf {
SHADER_CLOSURE_BASE;
float roughness;
};
static_assert(sizeof(ShaderClosure) >= sizeof(BurleyBsdf), "BurleyBsdf is too large!");
ccl_device Spectrum bsdf_burley_get_intensity(const float roughness,
const float3 n,
const float3 v,
const float3 l)
{
const float NdotL = dot(n, l);
const float NdotV = dot(n, v);
const float fl = schlick_fresnel(NdotL);
const float fv = schlick_fresnel(NdotV);
const float LdotH = dot(l, normalize(l + v));
const float F90 = 0.5f + (2.0f * roughness * LdotH * LdotH);
return make_spectrum(M_1_PI_F * NdotL * mix(1.0f, F90, fl) * mix(1.0f, F90, fv));
}
ccl_device int bsdf_burley_setup(ccl_private BurleyBsdf *bsdf, const float roughness)
{
bsdf->type = CLOSURE_BSDF_BURLEY_ID;
bsdf->roughness = saturatef(roughness);
return SD_BSDF | SD_BSDF_HAS_EVAL;
}
ccl_device Spectrum bsdf_burley_eval(ccl_private const ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private float *pdf)
{
ccl_private const BurleyBsdf *bsdf = (ccl_private const BurleyBsdf *)sc;
const float cosNO = dot(bsdf->N, wo);
if (cosNO > 0.0f) {
*pdf = cosNO * M_1_PI_F;
return bsdf_burley_get_intensity(bsdf->roughness, bsdf->N, wi, wo);
}
*pdf = 0.0f;
return zero_spectrum();
}
ccl_device int bsdf_burley_sample(ccl_private const ShaderClosure *sc,
float3 Ng,
float3 wi,
float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
ccl_private const BurleyBsdf *bsdf = (ccl_private const BurleyBsdf *)sc;
float3 N = bsdf->N;
// distribution over the hemisphere
sample_cos_hemisphere(N, rand, wo, pdf);
if (dot(Ng, *wo) > 0.0f) {
*eval = bsdf_burley_get_intensity(bsdf->roughness, bsdf->N, wi, *wo);
}
else {
*pdf = 0.0f;
*eval = zero_spectrum();
}
return LABEL_REFLECT | LABEL_DIFFUSE;
}
#endif /* __OSL__ */
CCL_NAMESPACE_END

View File

@@ -0,0 +1,127 @@
/* SPDX-FileCopyrightText: 2009-2010 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/alloc.h"
#include "kernel/sample/mapping.h"
CCL_NAMESPACE_BEGIN
struct DiffuseBsdf {
SHADER_CLOSURE_BASE;
};
static_assert(sizeof(ShaderClosure) >= sizeof(DiffuseBsdf), "DiffuseBsdf is too large!");
/* DIFFUSE */
ccl_device void bsdf_diffuse_setup(ccl_private ShaderData *sd,
const float3 N,
const Spectrum weight)
{
ccl_private DiffuseBsdf *bsdf = (ccl_private DiffuseBsdf *)bsdf_alloc(
sd, sizeof(DiffuseBsdf), weight);
if (bsdf) {
bsdf->N = N;
bsdf->type = CLOSURE_BSDF_DIFFUSE_ID;
sd->flag |= (SD_BSDF | SD_BSDF_HAS_EVAL);
}
}
ccl_device Spectrum bsdf_diffuse_eval(const ccl_private ShaderClosure *sc,
const float3 /*wi*/,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private DiffuseBsdf *bsdf = (const ccl_private DiffuseBsdf *)sc;
const float3 N = bsdf->N;
const float cosNO = fmaxf(dot(N, wo), 0.0f) * M_1_PI_F;
*pdf = cosNO;
return make_spectrum(cosNO);
}
ccl_device int bsdf_diffuse_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 /*wi*/,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const ccl_private DiffuseBsdf *bsdf = (const ccl_private DiffuseBsdf *)sc;
const float3 N = bsdf->N;
// distribution over the hemisphere
sample_cos_hemisphere(N, rand, wo, pdf);
if (dot(Ng, *wo) > 0.0f) {
*eval = make_spectrum(*pdf);
}
else {
*pdf = 0.0f;
*eval = zero_spectrum();
}
return LABEL_REFLECT | LABEL_DIFFUSE;
}
/* TRANSLUCENT */
ccl_device void bsdf_translucent_setup(ccl_private ShaderData *sd,
const float3 N,
const Spectrum weight)
{
ccl_private DiffuseBsdf *bsdf = (ccl_private DiffuseBsdf *)bsdf_alloc(
sd, sizeof(DiffuseBsdf), weight);
if (bsdf) {
bsdf->N = N;
bsdf->type = CLOSURE_BSDF_TRANSLUCENT_ID;
sd->flag |= (SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION);
}
}
ccl_device Spectrum bsdf_translucent_eval(const ccl_private ShaderClosure *sc,
const float3 /*wi*/,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private DiffuseBsdf *bsdf = (const ccl_private DiffuseBsdf *)sc;
const float3 N = bsdf->N;
const float cosNO = fmaxf(-dot(N, wo), 0.0f) * M_1_PI_F;
*pdf = cosNO;
return make_spectrum(cosNO);
}
ccl_device int bsdf_translucent_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 /*wi*/,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const ccl_private DiffuseBsdf *bsdf = (const ccl_private DiffuseBsdf *)sc;
const float3 N = bsdf->N;
// we are viewing the surface from the right side - send a ray out with cosine
// distribution over the hemisphere
sample_cos_hemisphere(-N, rand, wo, pdf);
if (dot(Ng, *wo) < 0) {
*eval = make_spectrum(*pdf);
}
else {
*pdf = 0;
*eval = zero_spectrum();
}
return LABEL_TRANSMIT | LABEL_DIFFUSE;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,97 @@
/* SPDX-FileCopyrightText: 2009-2010 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/types.h"
#include "kernel/sample/mapping.h"
#include "kernel/util/colorspace.h"
CCL_NAMESPACE_BEGIN
#ifdef __OSL__
struct DiffuseRampBsdf {
SHADER_CLOSURE_BASE;
ccl_private float3 *colors;
};
static_assert(sizeof(ShaderClosure) >= sizeof(DiffuseRampBsdf), "DiffuseRampBsdf is too large!");
ccl_device float3 bsdf_diffuse_ramp_get_color(const float3 colors[8], float pos)
{
const int MAXCOLORS = 8;
const float npos = pos * (float)(MAXCOLORS - 1);
const int ipos = float_to_int(npos);
if (ipos < 0) {
return colors[0];
}
if (ipos >= (MAXCOLORS - 1)) {
return colors[MAXCOLORS - 1];
}
const float offset = npos - (float)ipos;
return colors[ipos] * (1.0f - offset) + colors[ipos + 1] * offset;
}
ccl_device int bsdf_diffuse_ramp_setup(DiffuseRampBsdf *bsdf)
{
bsdf->type = CLOSURE_BSDF_DIFFUSE_RAMP_ID;
return SD_BSDF | SD_BSDF_HAS_EVAL;
}
ccl_device void bsdf_diffuse_ramp_blur(ccl_private ShaderClosure * /*sc*/,
const float /*roughness*/)
{
}
ccl_device Spectrum bsdf_diffuse_ramp_eval(const ccl_private ShaderClosure *sc,
const float3 /*wi*/,
const float3 wo,
ccl_private float *pdf)
{
const DiffuseRampBsdf *bsdf = (const DiffuseRampBsdf *)sc;
const float3 N = bsdf->N;
const float cosNO = fmaxf(dot(N, wo), 0.0f);
if (cosNO >= 0.0f) {
*pdf = cosNO * M_1_PI_F;
return rgb_to_spectrum(bsdf_diffuse_ramp_get_color(bsdf->colors, cosNO) * M_1_PI_F);
}
*pdf = 0.0f;
return zero_spectrum();
}
ccl_device int bsdf_diffuse_ramp_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 /*wi*/,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const DiffuseRampBsdf *bsdf = (const DiffuseRampBsdf *)sc;
const float3 N = bsdf->N;
// distribution over the hemisphere
sample_cos_hemisphere(N, rand, wo, pdf);
if (dot(Ng, *wo) > 0.0f) {
*eval = rgb_to_spectrum(bsdf_diffuse_ramp_get_color(bsdf->colors, *pdf * M_PI_F) * M_1_PI_F);
}
else {
*pdf = 0.0f;
*eval = zero_spectrum();
}
return LABEL_REFLECT | LABEL_DIFFUSE;
}
#endif /* __OSL__ */
CCL_NAMESPACE_END

View File

@@ -0,0 +1,261 @@
/* SPDX-FileCopyrightText: 2009-2010 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/types.h"
#include "util/math_fast.h"
CCL_NAMESPACE_BEGIN
struct HairBsdf {
SHADER_CLOSURE_BASE;
float3 T;
float roughness1;
float roughness2;
float offset;
};
static_assert(sizeof(ShaderClosure) >= sizeof(HairBsdf), "HairBsdf is too large!");
ccl_device int bsdf_hair_reflection_setup(ccl_private HairBsdf *bsdf)
{
bsdf->type = CLOSURE_BSDF_HAIR_REFLECTION_ID;
bsdf->roughness1 = clamp(bsdf->roughness1, 0.001f, 1.0f);
bsdf->roughness2 = clamp(bsdf->roughness2, 0.001f, 1.0f);
return SD_BSDF | SD_BSDF_HAS_EVAL;
}
ccl_device int bsdf_hair_transmission_setup(ccl_private HairBsdf *bsdf)
{
bsdf->type = CLOSURE_BSDF_HAIR_TRANSMISSION_ID;
bsdf->roughness1 = clamp(bsdf->roughness1, 0.001f, 1.0f);
bsdf->roughness2 = clamp(bsdf->roughness2, 0.001f, 1.0f);
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
}
ccl_device Spectrum bsdf_hair_reflection_eval(const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private HairBsdf *bsdf = (const ccl_private HairBsdf *)sc;
if (dot(bsdf->N, wo) < 0.0f) {
*pdf = 0.0f;
return zero_spectrum();
}
const float offset = bsdf->offset;
const float3 Tg = bsdf->T;
const float roughness1 = bsdf->roughness1;
const float roughness2 = bsdf->roughness2;
const float Iz = dot(Tg, wi);
const float3 locy = normalize(wi - Tg * Iz);
const float theta_r = M_PI_2_F - fast_acosf(Iz);
const float wo_z = dot(Tg, wo);
const float3 wo_y = normalize(wo - Tg * wo_z);
const float theta_i = M_PI_2_F - fast_acosf(wo_z);
const float cosphi_i = dot(wo_y, locy);
if (M_PI_2_F - fabsf(theta_i) < 0.001f || cosphi_i < 0.0f) {
*pdf = 0.0f;
return zero_spectrum();
}
const float roughness1_inv = 1.0f / roughness1;
const float roughness2_inv = 1.0f / roughness2;
float phi_i = fast_acosf(cosphi_i) * roughness2_inv;
phi_i = fabsf(phi_i) < M_PI_F ? phi_i : M_PI_F;
const float costheta_i = fast_cosf(theta_i);
const float a_R = fast_atan2f(((M_PI_2_F + theta_r) * 0.5f - offset) * roughness1_inv, 1.0f);
const float b_R = fast_atan2f(((-M_PI_2_F + theta_r) * 0.5f - offset) * roughness1_inv, 1.0f);
const float theta_h = (theta_i + theta_r) * 0.5f;
const float t = theta_h - offset;
const float phi_pdf = fast_cosf(phi_i * 0.5f) * 0.25f * roughness2_inv;
const float theta_pdf = roughness1 /
(2 * (t * t + roughness1 * roughness1) * (a_R - b_R) * costheta_i);
*pdf = phi_pdf * theta_pdf;
return make_spectrum(*pdf);
}
ccl_device Spectrum bsdf_hair_transmission_eval(const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private HairBsdf *bsdf = (const ccl_private HairBsdf *)sc;
if (dot(bsdf->N, wo) >= 0.0f) {
*pdf = 0.0f;
return zero_spectrum();
}
const float offset = bsdf->offset;
const float3 Tg = bsdf->T;
const float roughness1 = bsdf->roughness1;
const float roughness2 = bsdf->roughness2;
const float Iz = dot(Tg, wi);
const float3 locy = normalize(wi - Tg * Iz);
const float theta_r = M_PI_2_F - fast_acosf(Iz);
const float wo_z = dot(Tg, wo);
const float3 wo_y = normalize(wo - Tg * wo_z);
const float theta_i = M_PI_2_F - fast_acosf(wo_z);
const float phi_i = fast_acosf(dot(wo_y, locy));
if (M_PI_2_F - fabsf(theta_i) < 0.001f) {
*pdf = 0.0f;
return zero_spectrum();
}
const float costheta_i = fast_cosf(theta_i);
const float roughness1_inv = 1.0f / roughness1;
const float a_TT = fast_atan2f(((M_PI_2_F + theta_r) / 2 - offset) * roughness1_inv, 1.0f);
const float b_TT = fast_atan2f(((-M_PI_2_F + theta_r) / 2 - offset) * roughness1_inv, 1.0f);
const float c_TT = 2 * fast_atan2f(M_PI_2_F / roughness2, 1.0f);
const float theta_h = (theta_i + theta_r) / 2;
const float t = theta_h - offset;
const float phi = fabsf(phi_i);
const float p = M_PI_F - phi;
const float theta_pdf = roughness1 /
(2 * (t * t + roughness1 * roughness1) * (a_TT - b_TT) * costheta_i);
const float phi_pdf = roughness2 / (c_TT * (p * p + roughness2 * roughness2));
*pdf = phi_pdf * theta_pdf;
return make_spectrum(*pdf);
}
ccl_device int bsdf_hair_reflection_sample(const ccl_private ShaderClosure *sc,
const float3 /*Ng*/,
const float3 wi,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf,
ccl_private float2 *sampled_roughness)
{
const ccl_private HairBsdf *bsdf = (const ccl_private HairBsdf *)sc;
const float offset = bsdf->offset;
const float3 Tg = bsdf->T;
const float roughness1 = bsdf->roughness1;
const float roughness2 = bsdf->roughness2;
*sampled_roughness = make_float2(roughness1, roughness2);
const float Iz = dot(Tg, wi);
const float3 locy = normalize(wi - Tg * Iz);
const float3 locx = cross(locy, Tg);
const float theta_r = M_PI_2_F - fast_acosf(Iz);
const float roughness1_inv = 1.0f / roughness1;
const float a_R = fast_atan2f(((M_PI_2_F + theta_r) * 0.5f - offset) * roughness1_inv, 1.0f);
const float b_R = fast_atan2f(((-M_PI_2_F + theta_r) * 0.5f - offset) * roughness1_inv, 1.0f);
const float t = roughness1 * tanf(rand.x * (a_R - b_R) + b_R);
const float theta_h = t + offset;
const float theta_i = 2 * theta_h - theta_r;
float costheta_i;
float sintheta_i;
fast_sincosf(theta_i, &sintheta_i, &costheta_i);
const float phi = 2 * safe_asinf(1 - 2 * rand.y) * roughness2;
const float phi_pdf = fast_cosf(phi * 0.5f) * 0.25f / roughness2;
const float theta_pdf = roughness1 /
(2 * (t * t + roughness1 * roughness1) * (a_R - b_R) * costheta_i);
float sinphi;
float cosphi;
fast_sincosf(phi, &sinphi, &cosphi);
*wo = (cosphi * costheta_i) * locy - (sinphi * costheta_i) * locx + (sintheta_i)*Tg;
*pdf = fabsf(phi_pdf * theta_pdf);
if (M_PI_2_F - fabsf(theta_i) < 0.001f) {
*pdf = 0.0f;
}
*eval = make_spectrum(*pdf);
return LABEL_REFLECT | LABEL_GLOSSY;
}
ccl_device int bsdf_hair_transmission_sample(const ccl_private ShaderClosure *sc,
const float3 /*Ng*/,
const float3 wi,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf,
ccl_private float2 *sampled_roughness)
{
const ccl_private HairBsdf *bsdf = (const ccl_private HairBsdf *)sc;
const float offset = bsdf->offset;
const float3 Tg = bsdf->T;
const float roughness1 = bsdf->roughness1;
const float roughness2 = bsdf->roughness2;
*sampled_roughness = make_float2(roughness1, roughness2);
const float Iz = dot(Tg, wi);
const float3 locy = normalize(wi - Tg * Iz);
const float3 locx = cross(locy, Tg);
const float theta_r = M_PI_2_F - fast_acosf(Iz);
const float roughness1_inv = 1.0f / roughness1;
const float a_TT = fast_atan2f(((M_PI_2_F + theta_r) / 2 - offset) * roughness1_inv, 1.0f);
const float b_TT = fast_atan2f(((-M_PI_2_F + theta_r) / 2 - offset) * roughness1_inv, 1.0f);
const float c_TT = 2 * fast_atan2f(M_PI_2_F / roughness2, 1.0f);
const float t = roughness1 * tanf(rand.x * (a_TT - b_TT) + b_TT);
const float theta_h = t + offset;
const float theta_i = 2 * theta_h - theta_r;
float costheta_i;
float sintheta_i;
fast_sincosf(theta_i, &sintheta_i, &costheta_i);
const float p = roughness2 * tanf(c_TT * (rand.y - 0.5f));
const float phi = p + M_PI_F;
const float theta_pdf = roughness1 /
(2 * (t * t + roughness1 * roughness1) * (a_TT - b_TT) * costheta_i);
const float phi_pdf = roughness2 / (c_TT * (p * p + roughness2 * roughness2));
float sinphi;
float cosphi;
fast_sincosf(phi, &sinphi, &cosphi);
*wo = (cosphi * costheta_i) * locy - (sinphi * costheta_i) * locx + (sintheta_i)*Tg;
*pdf = fabsf(phi_pdf * theta_pdf);
if (M_PI_2_F - fabsf(theta_i) < 0.001f) {
*pdf = 0.0f;
}
*eval = make_spectrum(*pdf);
/* TODO(sergey): Should always be negative, but seems some precision issue
* is involved here.
*/
kernel_assert(dot(locy, *wo) < 1e-4f);
return LABEL_TRANSMIT | LABEL_GLOSSY;
}
CCL_NAMESPACE_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,243 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/bsdf_diffuse.h"
#include "kernel/sample/mapping.h"
CCL_NAMESPACE_BEGIN
struct OrenNayarParam {
float roughness;
float a;
float b;
Spectrum multiscatter_term;
};
struct OrenNayarBsdf {
SHADER_CLOSURE_BASE;
OrenNayarParam param;
};
static_assert(sizeof(ShaderClosure) >= sizeof(OrenNayarBsdf), "OrenNayarBsdf is too large!");
/* NOTE: This implements the improved Oren-Nayar model by Yasuhiro Fujii
* (https://mimosa-pudica.net/improved-oren-nayar.html), plus an
* energy-preserving multi-scattering term based on the OpenPBR specification
* (https://academysoftwarefoundation.github.io/OpenPBR). */
/* Above certain roughness threshold we switch to Oren Nayar model. */
ccl_device_forceinline bool diffuse_roughness_is_almost_zero(const float alpha)
{
return alpha < 1e-5f;
}
ccl_device_inline float bsdf_oren_nayar_G(const float cosTheta)
{
if (cosTheta < 1e-6f) {
/* The tan(theta) term starts to act up at low cosTheta, so fall back to Taylor expansion. */
return (M_PI_2_F - 2.0f / 3.0f) - cosTheta;
}
const float sinTheta = sin_from_cos(cosTheta);
const float theta = safe_acosf(cosTheta);
return sinTheta * (theta - 2.0f / 3.0f - sinTheta * cosTheta) +
2.0f / 3.0f * (sinTheta / cosTheta) * (1.0f - sqr(sinTheta) * sinTheta);
}
ccl_device Spectrum bsdf_oren_nayar_get_intensity(const ccl_private OrenNayarBsdf *bsdf,
const float3 n,
const float3 v,
const float3 l)
{
const OrenNayarParam param = bsdf->param;
const float nl = max(dot(n, l), 0.0f);
if (param.b <= 0.0f) {
return make_spectrum(nl * M_1_PI_F);
}
const float nv = max(dot(n, v), 0.0f);
float t = dot(l, v) - nl * nv;
if (t > 0.0f) {
t /= max(nl, nv) + FLT_MIN;
}
const float single_scatter = param.a + param.b * t;
const float El = param.a * M_PI_F + param.b * bsdf_oren_nayar_G(nl);
const Spectrum multi_scatter = param.multiscatter_term * (1.0f - El);
return nl * (make_spectrum(single_scatter) + multi_scatter);
}
ccl_device_inline OrenNayarParam bsdf_oren_nayar_param(const Spectrum color,
const float nv,
const float roughness)
{
const float sigma = saturatef(roughness);
const float a = 1.0f / (M_PI_F + sigma * (M_PI_2_F - 2.0f / 3.0f));
const float b = sigma * a;
/* Compute energy compensation term (except for (1.0f - El) factor since it depends on wo). */
const Spectrum albedo = saturate(color);
const float Eavg = a * M_PI_F + ((M_2PI_F - 5.6f) / 3.0f) * b;
const Spectrum Ems = M_1_PI_F * sqr(albedo) * (Eavg / (1.0f - Eavg)) /
(one_spectrum() - albedo * (1.0f - Eavg));
const float Ev = a * M_PI_F + b * bsdf_oren_nayar_G(max(nv, 0.0f));
return {/* .roughness = */ roughness,
/* .a = */ a,
/* .b = */ b,
/* .multiscatter_term = */ Ems * (1.0f - Ev)};
}
ccl_device void bsdf_oren_nayar_setup(ccl_private ShaderData *sd,
const float3 N,
const Spectrum weight,
const float roughness,
const Spectrum color)
{
ccl_private OrenNayarBsdf *bsdf = (ccl_private OrenNayarBsdf *)bsdf_alloc(
sd, sizeof(OrenNayarBsdf), weight);
if (bsdf) {
bsdf->N = N;
bsdf->type = CLOSURE_BSDF_OREN_NAYAR_ID;
bsdf->param = bsdf_oren_nayar_param(color, dot(bsdf->N, sd->wi), roughness);
sd->flag |= SD_BSDF | SD_BSDF_HAS_EVAL;
}
}
ccl_device Spectrum bsdf_oren_nayar_eval(const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private OrenNayarBsdf *bsdf = (const ccl_private OrenNayarBsdf *)sc;
const float cosNO = dot(bsdf->N, wo);
if (cosNO > 0.0f) {
*pdf = cosNO * M_1_PI_F;
return bsdf_oren_nayar_get_intensity(bsdf, bsdf->N, wi, wo);
}
*pdf = 0.0f;
return zero_spectrum();
}
ccl_device int bsdf_oren_nayar_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 wi,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf,
ccl_private float2 *sampled_roughness,
ccl_private float *eta)
{
const ccl_private OrenNayarBsdf *bsdf = (const ccl_private OrenNayarBsdf *)sc;
sample_cos_hemisphere(bsdf->N, rand, wo, pdf);
if (dot(Ng, *wo) > 0.0f) {
*eval = bsdf_oren_nayar_get_intensity(bsdf, bsdf->N, wi, *wo);
}
else {
*pdf = 0.0f;
*eval = zero_spectrum();
}
*sampled_roughness = one_float2();
*eta = 1.0f;
return LABEL_REFLECT | LABEL_DIFFUSE;
}
/* ---------------------------------------------------------------------------------------- */
/** \name Subsurface in thin-walled mode
*
* An infinitesimally thin sheet of dense scattering material, following OpenPBR spec
* https://academysoftwarefoundation.github.io/OpenPBR/#model/thin-walledcase
*
* It is approximated by a diffuse lobe and a translucent lobe, the respective weights of both
* lobes are given by subsurface anisotropy, with specifies the relative amount of backward and
* forward scattering.
*
* \{ */
ccl_device_inline void bsdf_thin_subsurface_setup(ccl_private ShaderData *sd,
const float3 N,
const Spectrum weight,
const float anisotropy,
const float roughness,
const Spectrum color)
{
const Spectrum reflection_weight = saturatef(0.5f * (1.0f - anisotropy)) * weight;
const Spectrum transmission_weight = saturatef(0.5f * (1.0f + anisotropy)) * weight;
if (is_zero(reflection_weight) && is_zero(transmission_weight)) {
return;
}
if (sd->num_closure_left == 0) {
return;
}
if (diffuse_roughness_is_almost_zero(roughness)) {
bsdf_diffuse_setup(sd, N, reflection_weight);
bsdf_translucent_setup(sd, N, transmission_weight);
return;
}
const OrenNayarParam param = bsdf_oren_nayar_param(color, dot(N, sd->wi), roughness);
/* Reflection. */
{
ccl_private OrenNayarBsdf *bsdf = (ccl_private OrenNayarBsdf *)bsdf_alloc(
sd, sizeof(OrenNayarBsdf), reflection_weight);
if (bsdf) {
bsdf->type = CLOSURE_BSDF_OREN_NAYAR_ID;
bsdf->N = N;
bsdf->param = param;
sd->flag |= SD_BSDF | SD_BSDF_HAS_EVAL;
}
}
/* Transmission. */
{
ccl_private OrenNayarBsdf *bsdf = (ccl_private OrenNayarBsdf *)bsdf_alloc(
sd, sizeof(OrenNayarBsdf), transmission_weight);
if (bsdf) {
bsdf->type = CLOSURE_BSDF_ROUGH_TRANSLUCENT_ID;
bsdf->N = -N;
bsdf->param = param;
sd->flag |= SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
}
}
}
ccl_device int bsdf_rough_translucent_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 wi,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf,
ccl_private float2 *sampled_roughness,
ccl_private float *eta)
{
bsdf_oren_nayar_sample(sc, -Ng, reflect(wi, sc->N), rand, eval, wo, pdf, sampled_roughness, eta);
return LABEL_TRANSMIT | LABEL_DIFFUSE;
}
ccl_device Spectrum bsdf_rough_translucent_eval(const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private float *pdf)
{
return bsdf_oren_nayar_eval(sc, reflect(wi, sc->N), wo, pdf);
}
/** \} */
CCL_NAMESPACE_END

View File

@@ -0,0 +1,127 @@
/* SPDX-FileCopyrightText: 2009-2010 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/types.h"
#include "kernel/util/colorspace.h"
CCL_NAMESPACE_BEGIN
#ifdef __OSL__
struct PhongRampBsdf {
SHADER_CLOSURE_BASE;
float exponent;
ccl_private float3 *colors;
};
static_assert(sizeof(ShaderClosure) >= sizeof(PhongRampBsdf), "PhongRampBsdf is too large!");
ccl_device float3 bsdf_phong_ramp_get_color(const float3 colors[8], float pos)
{
const int MAXCOLORS = 8;
const float npos = pos * (float)(MAXCOLORS - 1);
const int ipos = float_to_int(npos);
if (ipos < 0) {
return colors[0];
}
if (ipos >= (MAXCOLORS - 1)) {
return colors[MAXCOLORS - 1];
}
const float offset = npos - (float)ipos;
return colors[ipos] * (1.0f - offset) + colors[ipos + 1] * offset;
}
ccl_device int bsdf_phong_ramp_setup(ccl_private PhongRampBsdf *bsdf)
{
bsdf->type = CLOSURE_BSDF_PHONG_RAMP_ID;
bsdf->exponent = max(bsdf->exponent, 0.0f);
return SD_BSDF | SD_BSDF_HAS_EVAL;
}
ccl_device Spectrum bsdf_phong_ramp_eval(const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private PhongRampBsdf *bsdf = (const ccl_private PhongRampBsdf *)sc;
const float m_exponent = bsdf->exponent;
const float cosNI = dot(bsdf->N, wi);
const float cosNO = dot(bsdf->N, wo);
if (cosNI > 0 && cosNO > 0) {
// reflect the view vector
const float3 R = (2 * cosNI) * bsdf->N - wi;
const float cosRO = dot(R, wo);
if (cosRO > 0) {
const float cosp = powf(cosRO, m_exponent);
const float common = 0.5f * M_1_PI_F * cosp;
const float out = cosNO * (m_exponent + 2) * common;
*pdf = (m_exponent + 1) * common;
return rgb_to_spectrum(bsdf_phong_ramp_get_color(bsdf->colors, cosp) * out);
}
}
*pdf = 0.0f;
return zero_spectrum();
}
ccl_device_inline float phong_ramp_exponent_to_roughness(const float exponent)
{
return sqrt(1.0f / ((exponent + 2.0f) / 2.0f));
}
ccl_device int bsdf_phong_ramp_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 wi,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf,
ccl_private float2 *sampled_roughness)
{
const ccl_private PhongRampBsdf *bsdf = (const ccl_private PhongRampBsdf *)sc;
const float cosNI = dot(bsdf->N, wi);
const float m_exponent = bsdf->exponent;
const float m_roughness = phong_ramp_exponent_to_roughness(m_exponent);
*sampled_roughness = make_float2(m_roughness, m_roughness);
if (cosNI > 0) {
// reflect the view vector
const float3 R = (2 * cosNI) * bsdf->N - wi;
float3 T;
float3 B;
make_orthonormals(R, &T, &B);
const float phi = M_2PI_F * rand.x;
const float cosTheta = powf(rand.y, 1 / (m_exponent + 1));
*wo = to_global(spherical_cos_to_direction(cosTheta, phi), T, B, R);
if (dot(Ng, *wo) > 0.0f) {
// common terms for pdf and eval
const float cosNO = dot(bsdf->N, *wo);
// make sure the direction we chose is still in the right hemisphere
if (cosNO > 0) {
const float cosp = powf(cosTheta, m_exponent);
const float common = 0.5f * M_1_PI_F * cosp;
*pdf = (m_exponent + 1) * common;
const float out = cosNO * (m_exponent + 2) * common;
*eval = rgb_to_spectrum(bsdf_phong_ramp_get_color(bsdf->colors, cosp) * out);
}
}
}
else {
*eval = zero_spectrum();
*pdf = 0.0f;
}
return LABEL_REFLECT | LABEL_GLOSSY;
}
#endif /* __OSL__ */
CCL_NAMESPACE_END

View File

@@ -0,0 +1,481 @@
/* SPDX-FileCopyrightText: 2018-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0
*
* This code implements the paper [A practical and controllable hair and fur model for production
* path tracing](https://doi.org/10.1145/2775280.2792559) by Chiang, Matt Jen-Yuan, et al. */
#pragma once
#ifndef __KERNEL_GPU__
# include <cfenv>
#endif
#include "kernel/types.h"
#include "kernel/closure/bsdf_util.h"
#include "kernel/util/colorspace.h"
CCL_NAMESPACE_BEGIN
struct ChiangHairBSDF {
SHADER_CLOSURE_BASE;
/* Absorption coefficient. */
Spectrum sigma;
/* Variance of the underlying logistic distribution. */
float v;
/* Scale factor of the underlying logistic distribution. */
float s;
/* Cuticle tilt angle. */
float alpha;
/* IOR. */
float eta;
/* Effective variance for the diffuse bounce only. */
float m0_roughness;
/* Azimuthal offset. */
float h;
};
static_assert(sizeof(ShaderClosure) >= sizeof(ChiangHairBSDF), "ChiangHairBSDF is too large!");
/* Gives the change in direction in the normal plane for the given angles and p-th-order
* scattering. */
ccl_device_inline float delta_phi(const int p, const float gamma_o, const float gamma_t)
{
return 2.0f * p * gamma_t - 2.0f * gamma_o + p * M_PI_F;
}
/* Remaps the given angle to [-pi, pi]. */
ccl_device_inline float wrap_angle(const float a)
{
return (a + M_PI_F) - M_2PI_F * floorf((a + M_PI_F) / M_2PI_F) - M_PI_F;
}
/* Logistic distribution function. */
ccl_device_inline float logistic(const float x, const float s)
{
const float v = expf(-fabsf(x) / s);
return v / (s * sqr(1.0f + v));
}
/* Logistic cumulative density function. */
ccl_device_inline float logistic_cdf(const float x, const float s)
{
const float arg = -x / s;
/* expf() overflows if arg >= 89.0. */
if (arg > 88.0f) {
return 0.0f;
}
return 1.0f / (1.0f + expf(arg));
}
/* Numerical approximation to the Bessel function of the first kind. */
ccl_device_inline float bessel_I0(float x)
{
x = sqr(x);
float val = 1.0f + 0.25f * x;
float pow_x_2i = sqr(x);
uint64_t i_fac_2 = 1;
int pow_4_i = 16;
for (int i = 2; i < 10; i++) {
i_fac_2 *= i * i;
const float newval = val + pow_x_2i / (pow_4_i * i_fac_2);
if (val == newval) {
return val;
}
val = newval;
pow_x_2i *= x;
pow_4_i *= 4;
}
return val;
}
/* Logarithm of the Bessel function of the first kind. */
ccl_device_inline float log_bessel_I0(const float x)
{
if (x > 12.0f) {
/* log(1/x) == -log(x) if x > 0.
* This is only used with positive cosines. */
return x + 0.5f * (1.f / (8.0f * x) - M_LN_2PI_F - logf(x));
}
return logf(bessel_I0(x));
}
/* Logistic distribution limited to the interval [-pi, pi]. */
ccl_device_inline float trimmed_logistic(const float x, const float s)
{
/* The logistic distribution is symmetric and centered around zero,
* so logistic_cdf(x, s) = 1 - logistic_cdf(-x, s).
* Therefore, logistic_cdf(x, s)-logistic_cdf(-x, s) = 1 - 2*logistic_cdf(-x, s) */
const float scaling_fac = 1.0f - 2.0f * logistic_cdf(-M_PI_F, s);
const float val = logistic(x, s);
return safe_divide(val, scaling_fac);
}
/* Sampling function for the trimmed logistic function. */
ccl_device_inline float sample_trimmed_logistic(const float u, const float s)
{
const float cdf_minuspi = logistic_cdf(-M_PI_F, s);
const float x = -s * logf(1.0f / (u * (1.0f - 2.0f * cdf_minuspi) + cdf_minuspi) - 1.0f);
return clamp(x, -M_PI_F, M_PI_F);
}
/* Azimuthal scattering function Np. */
ccl_device_inline float azimuthal_scattering(
float phi, const int p, const float s, float gamma_o, const float gamma_t)
{
const float phi_o = wrap_angle(phi - delta_phi(p, gamma_o, gamma_t));
const float val = trimmed_logistic(phi_o, s);
return val;
}
/* Longitudinal scattering function Mp. */
ccl_device_inline float longitudinal_scattering(float sin_theta_i,
const float cos_theta_i,
const float sin_theta_o,
const float cos_theta_o,
const float v)
{
const float inv_v = 1.0f / v;
const float cos_arg = cos_theta_i * cos_theta_o * inv_v;
const float sin_arg = sin_theta_i * sin_theta_o * inv_v;
if (v <= 0.1f) {
const float i0 = log_bessel_I0(cos_arg);
const float val = expf(i0 - sin_arg - inv_v + 0.6931f + logf(0.5f * inv_v));
kernel_assert(isfinite_safe(val));
return val;
}
const float i0 = bessel_I0(cos_arg);
const float val = (expf(-sin_arg) * i0) / (sinhf(inv_v) * 2.0f * v);
kernel_assert(isfinite_safe(val));
return val;
}
#ifdef __HAIR__
/* Set up the hair closure. */
ccl_device int bsdf_hair_chiang_setup(ccl_private ShaderData *sd, ccl_private ChiangHairBSDF *bsdf)
{
bsdf->type = CLOSURE_BSDF_HAIR_CHIANG_ID;
bsdf->v = clamp(bsdf->v, 0.001f, 1.0f);
bsdf->s = clamp(bsdf->s, 0.001f, 1.0f);
/* Apply Primary Reflection Roughness modifier. */
bsdf->m0_roughness = clamp(bsdf->m0_roughness * bsdf->v, 0.001f, 1.0f);
/* Map from roughness_u and roughness_v to variance and scale factor. */
bsdf->v = sqr(0.726f * bsdf->v + 0.812f * sqr(bsdf->v) + 3.700f * pow20(bsdf->v));
bsdf->s = (0.265f * bsdf->s + 1.194f * sqr(bsdf->s) + 5.372f * pow22(bsdf->s)) * M_SQRT_PI_8_F;
bsdf->m0_roughness = sqr(0.726f * bsdf->m0_roughness + 0.812f * sqr(bsdf->m0_roughness) +
3.700f * pow20(bsdf->m0_roughness));
/* Compute local frame, aligned to curve tangent and ray direction. */
const float3 X = safe_normalize(sd->dPdu);
const float3 Y = safe_normalize(cross(X, sd->wi));
const float3 Z = safe_normalize(cross(X, Y));
/* h -1..0..1 means the rays goes from grazing the hair, to hitting it at
* the center, to grazing the other edge. This is the sine of the angle
* between sd->Ng and Z, as seen from the tangent X. */
/* TODO: we convert this value to a cosine later and discard the sign, so
* we could probably save some operations. */
bsdf->h = ((sd->type & PRIMITIVE_CURVE) == PRIMITIVE_CURVE_RIBBON) ? -sd->v :
dot(cross(sd->Ng, X), Z);
kernel_assert(fabsf(bsdf->h) < 1.0f + 1e-4f);
kernel_assert(isfinite_safe(Y));
kernel_assert(isfinite_safe(bsdf->h));
bsdf->N = Y;
bsdf->alpha = -bsdf->alpha;
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
}
#endif /* __HAIR__ */
/* Given the Fresnel term and transmittance, generate the attenuation terms for each bounce. */
ccl_device_inline void hair_attenuation(KernelGlobals kg,
const float f,
Spectrum T,
ccl_private Spectrum *Ap,
ccl_private float *Ap_energy)
{
/* Primary specular (R). */
Ap[0] = make_spectrum(f);
Ap_energy[0] = f;
/* Transmission (TT). */
Spectrum col = sqr(1.0f - f) * T;
Ap[1] = col;
Ap_energy[1] = spectrum_to_gray(kg, col);
/* Secondary specular (TRT). */
col *= T * f;
Ap[2] = col;
Ap_energy[2] = spectrum_to_gray(kg, col);
/* Residual component (TRRT+). */
col *= safe_divide(T * f, one_spectrum() - T * f);
Ap[3] = col;
Ap_energy[3] = spectrum_to_gray(kg, col);
/* Normalize sampling weights. */
const float totweight = Ap_energy[0] + Ap_energy[1] + Ap_energy[2] + Ap_energy[3];
const float fac = safe_divide(1.0f, totweight);
Ap_energy[0] *= fac;
Ap_energy[1] *= fac;
Ap_energy[2] *= fac;
Ap_energy[3] *= fac;
}
/* Update sin_theta_o and cos_theta_o to account for scale tilt for each bounce. */
ccl_device_inline void hair_alpha_angles(const float sin_theta_o,
const float cos_theta_o,
const float alpha,
ccl_private float *angles)
{
const float sin_1alpha = sinf(alpha);
const float cos_1alpha = cos_from_sin(sin_1alpha);
const float sin_2alpha = 2.0f * sin_1alpha * cos_1alpha;
const float cos_2alpha = sqr(cos_1alpha) - sqr(sin_1alpha);
const float sin_4alpha = 2.0f * sin_2alpha * cos_2alpha;
const float cos_4alpha = sqr(cos_2alpha) - sqr(sin_2alpha);
angles[0] = sin_theta_o * cos_2alpha - cos_theta_o * sin_2alpha;
angles[1] = fabsf(cos_theta_o * cos_2alpha + sin_theta_o * sin_2alpha);
angles[2] = sin_theta_o * cos_1alpha + cos_theta_o * sin_1alpha;
angles[3] = fabsf(cos_theta_o * cos_1alpha - sin_theta_o * sin_1alpha);
angles[4] = sin_theta_o * cos_4alpha + cos_theta_o * sin_4alpha;
angles[5] = fabsf(cos_theta_o * cos_4alpha - sin_theta_o * sin_4alpha);
}
/* Evaluation function for our shader. */
ccl_device Spectrum bsdf_hair_chiang_eval(KernelGlobals kg,
const ccl_private ShaderData *sd,
const ccl_private ShaderClosure *sc,
const float3 wo,
ccl_private float *pdf)
{
kernel_assert(isfinite_safe(sd->P) && isfinite_safe(sd->ray_length));
const ccl_private ChiangHairBSDF *bsdf = (const ccl_private ChiangHairBSDF *)sc;
const float3 Y = bsdf->N;
const float3 X = safe_normalize(sd->dPdu);
kernel_assert(fabsf(dot(X, Y)) < 1e-3f);
const float3 Z = safe_normalize(cross(X, Y));
/* local_I is the illumination direction. */
const float3 local_O = to_local(sd->wi, X, Y, Z);
const float3 local_I = to_local(wo, X, Y, Z);
const float sin_theta_o = local_O.x;
const float cos_theta_o = cos_from_sin(sin_theta_o);
const float phi_o = atan2f(local_O.z, local_O.y);
const float sin_theta_t = sin_theta_o / bsdf->eta;
const float cos_theta_t = cos_from_sin(sin_theta_t);
const float sin_gamma_o = bsdf->h;
const float cos_gamma_o = cos_from_sin(sin_gamma_o);
const float gamma_o = safe_asinf(sin_gamma_o);
const float sin_gamma_t = sin_gamma_o * cos_theta_o / sqrtf(sqr(bsdf->eta) - sqr(sin_theta_o));
const float cos_gamma_t = cos_from_sin(sin_gamma_t);
const float gamma_t = safe_asinf(sin_gamma_t);
const Spectrum T = exp(-bsdf->sigma * (2.0f * cos_gamma_t / cos_theta_t));
Spectrum Ap[4];
float Ap_energy[4];
hair_attenuation(
kg, fresnel_dielectric_cos(cos_theta_o * cos_gamma_o, bsdf->eta), T, Ap, Ap_energy);
const float sin_theta_i = local_I.x;
const float cos_theta_i = cos_from_sin(sin_theta_i);
const float phi_i = atan2f(local_I.z, local_I.y);
const float phi = phi_i - phi_o;
float angles[6];
hair_alpha_angles(sin_theta_o, cos_theta_o, bsdf->alpha, angles);
Spectrum F = zero_spectrum();
float F_energy = 0.0f;
/* Primary specular (R), Transmission (TT) and Secondary Specular (TRT). */
for (int i = 0; i < 3; i++) {
const float Mp = longitudinal_scattering(sin_theta_i,
cos_theta_i,
angles[2 * i],
angles[2 * i + 1],
(i == 0) ? bsdf->m0_roughness :
(i == 1) ? 0.25f * bsdf->v :
4.0f * bsdf->v);
const float Np = azimuthal_scattering(phi, i, bsdf->s, gamma_o, gamma_t);
F += Ap[i] * Mp * Np;
F_energy += Ap_energy[i] * Mp * Np;
kernel_assert(isfinite_safe(F) && isfinite_safe(F_energy));
}
/* Residual component (TRRT+). */
{
const float Mp = longitudinal_scattering(
sin_theta_i, cos_theta_i, sin_theta_o, cos_theta_o, 4.0f * bsdf->v);
const float Np = M_1_2PI_F;
F += Ap[3] * Mp * Np;
F_energy += Ap_energy[3] * Mp * Np;
kernel_assert(isfinite_safe(F) && isfinite_safe(F_energy));
}
*pdf = F_energy;
return F;
}
/* Sampling function for the hair shader. */
ccl_device int bsdf_hair_chiang_sample(KernelGlobals kg,
const ccl_private ShaderClosure *sc,
ccl_private ShaderData *sd,
float3 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf,
ccl_private float2 *sampled_roughness)
{
ccl_private ChiangHairBSDF *bsdf = (ccl_private ChiangHairBSDF *)sc;
*sampled_roughness = make_float2(bsdf->m0_roughness, bsdf->m0_roughness);
const float3 Y = bsdf->N;
const float3 X = safe_normalize(sd->dPdu);
kernel_assert(fabsf(dot(X, Y)) < 1e-3f);
const float3 Z = safe_normalize(cross(X, Y));
/* `wo` in PBRT. */
const float3 local_O = to_local(sd->wi, X, Y, Z);
const float sin_theta_o = local_O.x;
const float cos_theta_o = cos_from_sin(sin_theta_o);
const float phi_o = atan2f(local_O.z, local_O.y);
const float sin_theta_t = sin_theta_o / bsdf->eta;
const float cos_theta_t = cos_from_sin(sin_theta_t);
const float sin_gamma_o = bsdf->h;
const float cos_gamma_o = cos_from_sin(sin_gamma_o);
const float gamma_o = safe_asinf(sin_gamma_o);
const float sin_gamma_t = sin_gamma_o * cos_theta_o / sqrtf(sqr(bsdf->eta) - sqr(sin_theta_o));
const float cos_gamma_t = cos_from_sin(sin_gamma_t);
const float gamma_t = safe_asinf(sin_gamma_t);
const Spectrum T = exp(-bsdf->sigma * (2.0f * cos_gamma_t / cos_theta_t));
Spectrum Ap[4];
float Ap_energy[4];
hair_attenuation(
kg, fresnel_dielectric_cos(cos_theta_o * cos_gamma_o, bsdf->eta), T, Ap, Ap_energy);
int p = 0;
for (; p < 3; p++) {
if (rand.z < Ap_energy[p]) {
break;
}
rand.z -= Ap_energy[p];
}
rand.z /= Ap_energy[p];
float v = bsdf->v;
if (p == 1) {
v *= 0.25f;
}
if (p >= 2) {
v *= 4.0f;
}
float angles[6];
hair_alpha_angles(sin_theta_o, cos_theta_o, bsdf->alpha, angles);
float sin_theta_o_tilted = sin_theta_o;
float cos_theta_o_tilted = cos_theta_o;
if (p < 3) {
sin_theta_o_tilted = angles[2 * p];
cos_theta_o_tilted = angles[2 * p + 1];
}
rand.z = max(rand.z, 1e-5f);
const float fac = 1.0f + v * logf(rand.z + (1.0f - rand.z) * expf(-2.0f / v));
const float sin_theta_i = -fac * sin_theta_o_tilted +
sin_from_cos(fac) * cosf(M_2PI_F * rand.y) * cos_theta_o_tilted;
const float cos_theta_i = cos_from_sin(sin_theta_i);
float phi;
if (p < 3) {
phi = delta_phi(p, gamma_o, gamma_t) + sample_trimmed_logistic(rand.x, bsdf->s);
}
else {
phi = M_2PI_F * rand.x;
}
const float phi_i = phi_o + phi;
Spectrum F = zero_spectrum();
float F_energy = 0.0f;
/* Primary specular (R), Transmission (TT) and Secondary Specular (TRT). */
for (int i = 0; i < 3; i++) {
const float Mp = longitudinal_scattering(sin_theta_i,
cos_theta_i,
angles[2 * i],
angles[2 * i + 1],
(i == 0) ? bsdf->m0_roughness :
(i == 1) ? 0.25f * bsdf->v :
4.0f * bsdf->v);
const float Np = azimuthal_scattering(phi, i, bsdf->s, gamma_o, gamma_t);
F += Ap[i] * Mp * Np;
F_energy += Ap_energy[i] * Mp * Np;
kernel_assert(isfinite_safe(F) && isfinite_safe(F_energy));
}
/* Residual component (TRRT+). */
{
const float Mp = longitudinal_scattering(
sin_theta_i, cos_theta_i, sin_theta_o, cos_theta_o, 4.0f * bsdf->v);
const float Np = M_1_2PI_F;
F += Ap[3] * Mp * Np;
F_energy += Ap_energy[3] * Mp * Np;
kernel_assert(isfinite_safe(F) && isfinite_safe(F_energy));
}
*eval = F;
*pdf = F_energy;
*wo = to_global(spherical_cos_to_direction(sin_theta_i, phi_i), Y, Z, X);
return LABEL_GLOSSY | ((p == 0) ? LABEL_REFLECT : LABEL_TRANSMIT);
}
/* Implements Filter Glossy by capping the effective roughness. */
ccl_device void bsdf_hair_chiang_blur(ccl_private ShaderClosure *sc, const float roughness)
{
ccl_private ChiangHairBSDF *bsdf = (ccl_private ChiangHairBSDF *)sc;
bsdf->v = fmaxf(roughness, bsdf->v);
bsdf->s = fmaxf(roughness, bsdf->s);
bsdf->m0_roughness = fmaxf(roughness, bsdf->m0_roughness);
}
/* Hair Albedo. */
ccl_device Spectrum bsdf_hair_chiang_albedo(const ccl_private ShaderData *sd,
const ccl_private ShaderClosure *sc)
{
ccl_private ChiangHairBSDF *bsdf = (ccl_private ChiangHairBSDF *)sc;
const float cos_theta_o = cos_from_sin(dot(sd->wi, safe_normalize(sd->dPdu)));
const float cos_gamma_o = cos_from_sin(bsdf->h);
const float f = fresnel_dielectric_cos(cos_theta_o * cos_gamma_o, bsdf->eta);
const float roughness_scale = bsdf_principled_hair_albedo_roughness_scale(bsdf->v);
/* TODO(lukas): Adding the Fresnel term here as a workaround until the proper refactor. */
return exp(-sqrt(bsdf->sigma) * roughness_scale) + make_spectrum(f);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,935 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* This code implements the paper [A Microfacet-based Hair Scattering
* Model](https://onlinelibrary.wiley.com/doi/full/10.1111/cgf.14588) by Weizhen Huang, Matthias B.
* Hullin and Johannes Hanika. */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/bsdf_microfacet.h"
#include "kernel/closure/bsdf_principled_hair_chiang.h"
#include "kernel/closure/bsdf_transparent.h"
#include "kernel/closure/bsdf_util.h"
#include "kernel/sample/lcg.h"
CCL_NAMESPACE_BEGIN
struct HuangHairExtra {
/* Optional modulation factors. */
float R, TT, TRT;
/* Local coordinate system. X is stored as `bsdf->N`. */
float3 Y, Z;
/* Incident direction in local coordinate system. */
float3 wi;
/* Projected radius from the view direction. */
float radius;
/* Squared Eccentricity. */
float e2;
/* The projected width of half a pixel at `sd->P` in `h` space. */
float pixel_coverage;
/* Valid integration interval, pre-divided by the radius so the range is [-1, 1]. */
Interval<float> h;
};
struct HuangHairBSDF {
SHADER_CLOSURE_BASE;
/* Absorption coefficient. */
Spectrum sigma;
/* Microfacet distribution roughness. */
float roughness;
/* Cuticle tilt angle. */
float tilt;
/* Index of refraction. */
float eta;
/* The ratio of the minor axis to the major axis. */
float aspect_ratio;
/* Azimuthal offset. */
float h;
/* Extra closure for optional modulation factors and local coordinate system. */
ccl_private HuangHairExtra *extra;
};
static_assert(sizeof(ShaderClosure) >= sizeof(HuangHairBSDF), "HuangHairBSDF is too large!");
static_assert(sizeof(ShaderClosure) >= sizeof(HuangHairExtra), "HuangHairExtra is too large!");
/* -------------------------------------------------------------------- */
/** \name Hair coordinate system utils.
* \{ */
/* TODO(weizhen): add a namespace `huanghair` for the below utility functions. */
/* Returns `sin(theta)` of the given direction. */
ccl_device_inline float sin_theta(const float3 w)
{
return w.y;
}
/* Returns `cos(theta)` of the given direction. */
ccl_device_inline float cos_theta(const float3 w)
{
return safe_sqrtf(sqr(w.x) + sqr(w.z));
}
/* Returns `tan(theta)` of the given direction. */
ccl_device_inline float tan_theta(const float3 w)
{
return sin_theta(w) / cos_theta(w);
}
/* Returns `sin(phi)` and `cos(phi)` of the given direction. */
ccl_device float sin_phi(const float3 w)
{
return w.x / cos_theta(w);
}
/* Check whether the hair cross-section is circular. */
ccl_device_inline bool is_circular(const float b)
{
return b == 1.0f;
}
ccl_device float2 sincos_phi(const float3 w)
{
const float c = cos_theta(w);
return make_float2(w.x / c, w.z / c);
}
/* Extract the theta coordinate from the given direction.
* -pi < theta < pi */
ccl_device_inline float dir_theta(const float3 w)
{
return atan2f(sin_theta(w), cos_theta(w));
}
/* Extract the phi coordinate from the given direction, assuming `phi(wi) == 0`.
* -pi < phi < pi */
ccl_device_inline float dir_phi(const float3 w)
{
return atan2f(w.x, w.z);
}
/* Extract theta and phi coordinates from the given direction, assuming `phi(wi) == 0`.
* -pi/2 < theta < pi/2, -pi < phi < pi */
ccl_device_inline float2 dir_sph(const float3 w)
{
return make_float2(dir_theta(w), dir_phi(w));
}
/* Conversion between `gamma` and `phi`. Notations see Figure 5 in the paper. */
ccl_device_inline float to_phi(const float gamma, const float b)
{
if (is_circular(b)) {
return gamma;
}
float sin_gamma;
float cos_gamma;
fast_sincosf(gamma, &sin_gamma, &cos_gamma);
return atan2f(b * sin_gamma, cos_gamma);
}
ccl_device_inline float to_gamma(const float phi, const float b)
{
if (is_circular(b)) {
return phi;
}
float sin_phi;
float cos_phi;
fast_sincosf(phi, &sin_phi, &cos_phi);
return atan2f(sin_phi, b * cos_phi);
}
/* Intersecting `wi` with the ellipse defined by `x = sin_gamma, y = b * cos_gamma` results in
* equation `h = -cos_phi_i * sin_gamma + b * sin_phi_i * cos_gamma`. */
ccl_device_inline float phi_to_h(const float phi, const float b, const float3 wi)
{
if (is_circular(b)) {
return -fast_sinf(phi);
}
float sin_gamma, cos_gamma;
fast_sincosf(to_gamma(phi, b), &sin_gamma, &cos_gamma);
const float2 sin_cos_phi_i = sincos_phi(wi);
return -sin_cos_phi_i.y * sin_gamma + b * sin_cos_phi_i.x * cos_gamma;
}
/* Solve for `gamma` in equation `h = -cos_phi_i * sin_gamma + b * sin_phi_i * cos_gamma`.
* Also, make use of `r = sqrt(sqr(cos_phi_i) + sqr(b * sin_phi_i))` to pre-map `h` to [-1, 1]. */
ccl_device_inline float h_to_gamma(const float h_div_r, const float b, const float3 wi)
{
return is_circular(b) ? -asinf(h_div_r) : atan2f(wi.z, -b * wi.x) - acosf(-h_div_r);
}
/* Jacobian |d_gamma/d_h|, used for changing variable in the integration. */
ccl_device_inline float d_gamma_d_h(const float2 sincos_phi_i, const float gamma, const float b)
{
if (is_circular(b)) {
return safe_divide(1.0f, fast_cosf(gamma));
}
float sin_gamma, cos_gamma;
fast_sincosf(gamma, &sin_gamma, &cos_gamma);
return safe_divide(1.0f, sincos_phi_i.y * cos_gamma + b * sincos_phi_i.x * sin_gamma);
}
/* Compute the coordinate on the ellipse, given `gamma` and the aspect ratio between the minor axis
* and the major axis. */
ccl_device_inline float2 to_point(const float gamma, const float b)
{
float sin_gamma;
float cos_gamma;
fast_sincosf(gamma, &sin_gamma, &cos_gamma);
return make_float2(sin_gamma, b * cos_gamma);
}
/* Compute the vector direction given by `theta` and `gamma`. */
ccl_device_inline float3 sphg_dir(const float theta, const float gamma, const float b)
{
float sin_theta;
float cos_theta;
float sin_gamma;
float cos_gamma;
float sin_phi;
float cos_phi;
fast_sincosf(theta, &sin_theta, &cos_theta);
fast_sincosf(gamma, &sin_gamma, &cos_gamma);
if (is_circular(b) || fabsf(cos_gamma) < 1e-6f) {
sin_phi = sin_gamma;
cos_phi = cos_gamma;
}
else {
const float tan_gamma = sin_gamma / cos_gamma;
const float tan_phi = b * tan_gamma;
cos_phi = signf(cos_gamma) * inversesqrtf(sqr(tan_phi) + 1.0f);
sin_phi = cos_phi * tan_phi;
}
return make_float3(sin_phi * cos_theta, sin_theta, cos_phi * cos_theta);
}
ccl_device_inline float arc_length(const float e2, const float gamma)
{
return e2 == 0 ? 1.0f : sqrtf(1.0f - e2 * sqr(sinf(gamma)));
}
ccl_device_inline bool is_nearfield(const ccl_private HuangHairBSDF *bsdf)
{
return bsdf->extra->radius > bsdf->extra->pixel_coverage;
}
/** \} */
#ifdef __HAIR__
/* Set up the hair closure. */
ccl_device int bsdf_hair_huang_setup(ccl_private ShaderData *sd,
ccl_private HuangHairBSDF *bsdf,
const uint32_t path_flag)
{
bsdf->type = CLOSURE_BSDF_HAIR_HUANG_ID;
bsdf->roughness = clamp(bsdf->roughness, 0.001f, 1.0f);
/* Negate to keep it consistent with principled hair BSDF. */
bsdf->tilt = -bsdf->tilt;
/* Compute local frame. The Y axis is aligned with the curve tangent; the X axis is perpendicular
* to the ray direction for circular cross-sections, or aligned with the major axis for
* elliptical cross-sections. */
bsdf->extra->Y = safe_normalize(sd->dPdu);
const float3 X = safe_normalize(cross(sd->dPdu, sd->wi));
/* h from -1..0..1 means the rays goes from grazing the hair, to hitting it at the center, to
* grazing the other edge. This is the cosine of the angle between `sd->N` and `X`. */
bsdf->h = ((sd->type & PRIMITIVE_CURVE) == PRIMITIVE_CURVE_RIBBON) ? -sd->v : -dot(X, sd->N);
kernel_assert(fabsf(bsdf->h) < 1.0f + 1e-4f);
kernel_assert(isfinite_safe(bsdf->h));
if (bsdf->aspect_ratio != 1.0f && (sd->type & PRIMITIVE_CURVE)) {
/* Adjust `bsdf->N` to be orthogonal to `sd->dPdu`. */
bsdf->N = safe_normalize(cross(sd->dPdu, safe_normalize(cross(bsdf->N, sd->dPdu))));
/* Align local frame with the curve normal. */
if (bsdf->aspect_ratio > 1.0f) {
/* Switch major and minor axis. */
bsdf->aspect_ratio = 1.0f / bsdf->aspect_ratio;
const float3 minor_axis = safe_normalize(cross(sd->dPdu, bsdf->N));
bsdf->N = safe_normalize(cross(minor_axis, sd->dPdu));
}
}
else {
/* Align local frame with the ray direction so that `phi_i == 0`. */
bsdf->N = X;
}
/* Fill extra closure. */
if (is_zero(bsdf->N) || !isfinite_safe(bsdf->N)) {
/* Construct arbitrary local coordinate system. The implementation should ensure smooth
* transition along the hair shaft. */
make_orthonormals(bsdf->extra->Y, &bsdf->extra->Z, &bsdf->N);
}
else {
bsdf->extra->Z = safe_normalize(cross(bsdf->N, sd->dPdu));
}
const float3 I = to_local(sd->wi, bsdf->N, bsdf->extra->Y, bsdf->extra->Z);
bsdf->extra->wi = I;
bsdf->extra->e2 = 1.0f - sqr(bsdf->aspect_ratio);
bsdf->extra->radius = bsdf->extra->e2 == 0 ?
1.0f :
sqrtf(1.0f - bsdf->extra->e2 * sqr(I.x) / (sqr(I.x) + sqr(I.z)));
/* Treat as transparent material if intersection lies outside of the projected radius. */
if (fabsf(bsdf->h) >= bsdf->extra->radius) {
/* Remove allocated closures. */
sd->num_closure--;
sd->num_closure_left += 2;
/* Allocate transparent closure. */
bsdf_transparent_setup(sd, bsdf->weight, path_flag);
return 0;
}
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
}
#endif /* __HAIR__ */
/* Albedo correction, treat as glass. `rough` has already applied square root. */
ccl_device_forceinline float bsdf_hair_huang_energy_scale(KernelGlobals kg,
const float mu,
const float rough,
const float ior)
{
const bool inv_table = (ior < 1.0f);
const int ofs = inv_table ? kernel_data.tables.ggx_glass_inv_E : kernel_data.tables.ggx_glass_E;
const float z = sqrtf(fabsf((ior - 1.0f) / (ior + 1.0f)));
return 1.0f / lookup_table_read_3D(kg, rough, mu, z, ofs, 16, 16, 16);
}
/* Sample microfacets from a tilted mesonormal. */
ccl_device_inline float3 sample_wh(const float roughness,
const float3 wi,
const float3 wm,
const float2 rand)
{
/* Coordinate transformation for microfacet sampling. */
float3 s;
float3 t;
make_orthonormals(wm, &s, &t);
const float3 wi_wm = to_local(wi, s, t, wm);
const float3 wh_wm = microfacet_ggx_sample_vndf(wi_wm, roughness, roughness, rand);
const float3 wh = to_global(wh_wm, s, t, wm);
return wh;
}
/* Check micronormal/mesonormal direct visibility from direction `v`. */
ccl_device_inline bool microfacet_visible(const float3 v, const float3 m, const float3 h)
{
return (dot(v, h) > 0.0f && dot(v, m) > 0.0f);
}
/* Check micronormal/mesonormal direct visibility from directions `wi` and `wo`. */
ccl_device_inline bool microfacet_visible(const float3 wi,
const float3 wo,
const float3 m,
const float3 h)
{
return microfacet_visible(wi, m, h) && microfacet_visible(wo, m, h);
}
/* Combined shadowing-masking term divided by the shadowing-masking in the incoming direction. */
ccl_device_inline float bsdf_Go(const float alpha2, const float cos_NI, const float cos_NO)
{
const float lambdaI = bsdf_lambda<MicrofacetType::GGX>(alpha2, cos_NI);
const float lambdaO = bsdf_lambda<MicrofacetType::GGX>(alpha2, cos_NO);
return (1.0f + lambdaI) / (1.0f + lambdaI + lambdaO);
}
ccl_device Spectrum bsdf_hair_huang_eval_r(KernelGlobals kg,
const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo)
{
ccl_private HuangHairBSDF *bsdf = (ccl_private HuangHairBSDF *)sc;
if (bsdf->extra->R <= 0.0f) {
return zero_float3();
}
/* Get minor axis, assuming major axis is 1. */
const float b = bsdf->aspect_ratio;
const float3 wh = normalize(wi + wo);
const float roughness = bsdf->roughness;
const float roughness2 = sqr(roughness);
const float2 sincos_phi_i = sincos_phi(wi);
/* Maximal sample resolution. */
float res = roughness * 0.7f;
const float h_range = bsdf->extra->h.length();
/* Number of intervals should be even. */
const size_t intervals = 2 * (size_t)ceilf(h_range / res * 0.5f);
/* Modified resolution based on numbers of intervals. */
res = h_range / float(intervals);
/* Integrate using Composite Simpson's 1/3 rule. */
float integral = 0.0f;
for (size_t i = 0; i <= intervals; i++) {
const float h = bsdf->extra->h.min + i * res;
const float gamma_m = h_to_gamma(h, b, wi);
const float3 wm = sphg_dir(bsdf->tilt, gamma_m, b);
if (microfacet_visible(wi, wo, make_float3(wm.x, 0.0f, wm.z), wh)) {
const float jacobian = d_gamma_d_h(sincos_phi_i, gamma_m, b);
const float weight = ((i == 0 || i == intervals) ? 0.5f : (i % 2 + 1)) * jacobian;
const float cos_mi = dot(wm, wi);
const float G = bsdf_G<MicrofacetType::GGX>(roughness2, cos_mi, dot(wm, wo));
integral += weight * bsdf_D<MicrofacetType::GGX>(roughness2, dot(wm, wh)) * G *
arc_length(bsdf->extra->e2, gamma_m) *
bsdf_hair_huang_energy_scale(kg, cos_mi, sqrtf(roughness), bsdf->eta);
}
}
/* Simpson coefficient */
integral *= (2.0f / 3.0f * res);
const float F = fresnel_dielectric_cos(dot(wi, wh), bsdf->eta);
return make_spectrum(bsdf->extra->R * 0.25f * F * integral);
}
/* Approximate components beyond TRT (starting TRRT) by summing up a geometric series. Attenuations
* are approximated from previous interactions. */
ccl_device Spectrum bsdf_hair_huang_eval_trrt(const float T, const float R, const Spectrum A)
{
/* `T` could be zero due to total internal reflection. Clamp to avoid numerical issues. */
const float T_avg = max(1.0f - R, 1e-5f);
const Spectrum TRRT_avg = T * sqr(R) * T_avg * A * A * A;
return TRRT_avg / (one_spectrum() - A * (1.0f - T_avg));
}
/* Evaluate components beyond R using numerical integration. TT and TRT are computed via combined
* Monte Carlo-Simpson integration; components beyond TRRT are integrated via Simpson's method. */
ccl_device Spectrum bsdf_hair_huang_eval_residual(KernelGlobals kg,
const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private uint *rng_quadrature)
{
ccl_private HuangHairBSDF *bsdf = (ccl_private HuangHairBSDF *)sc;
if (bsdf->extra->TT <= 0.0f && bsdf->extra->TRT <= 0.0f) {
return zero_spectrum();
}
/* Get minor axis, assuming major axis is 1. */
const float b = bsdf->aspect_ratio;
const Spectrum mu_a = bsdf->sigma;
const float eta = bsdf->eta;
const float inv_eta = 1.0f / eta;
const float roughness = bsdf->roughness;
const float roughness2 = sqr(roughness);
const float sqrt_roughness = sqrtf(roughness);
const float2 sincos_phi_i = sincos_phi(wi);
float res = roughness * 0.8f;
const float h_range = bsdf->extra->h.length();
const size_t intervals = 2 * (size_t)ceilf(h_range / res * 0.5f);
res = h_range / intervals;
Spectrum S_tt = zero_spectrum();
Spectrum S_trt = zero_spectrum();
Spectrum S_trrt = zero_spectrum();
for (size_t i = 0; i <= intervals; i++) {
const float h = bsdf->extra->h.min + i * res;
const float gamma_mi = h_to_gamma(h, b, wi);
const float3 wmi = sphg_dir(bsdf->tilt, gamma_mi, b);
const float3 wmi_ = sphg_dir(0.0f, gamma_mi, b);
/* Sample `wh1`. */
const float2 sample1 = make_float2(lcg_step_float(rng_quadrature),
lcg_step_float(rng_quadrature));
const float3 wh1 = sample_wh(roughness, wi, wmi, sample1);
const float cos_hi1 = dot(wi, wh1);
if (!(cos_hi1 > 0.0f)) {
continue;
}
const float cos_mi1 = dot(wi, wmi);
float cos_theta_t1;
const float T1 = 1.0f - fresnel_dielectric(cos_hi1, eta, &cos_theta_t1);
const float scale1 = bsdf_hair_huang_energy_scale(kg, cos_mi1, sqrt_roughness, eta);
/* Refraction at the first interface. */
const float3 wt = refract_angle(wi, wh1, cos_theta_t1, inv_eta);
const float phi_t = dir_phi(wt);
const float gamma_mt = 2.0f * to_phi(phi_t, b) - gamma_mi;
const float3 wmt = sphg_dir(-bsdf->tilt, gamma_mt, b);
const float3 wmt_ = sphg_dir(0.0f, gamma_mt, b);
const float cos_mo1 = dot(-wt, wmi);
const float cos_mi2 = dot(-wt, wmt);
const float G1o = bsdf_Go(roughness2, cos_mi1, cos_mo1);
if (!microfacet_visible(wi, -wt, wmi, wh1) || !microfacet_visible(wi, -wt, wmi_, wh1)) {
continue;
}
const float jacobian = d_gamma_d_h(sincos_phi_i, gamma_mi, b);
const float weight = ((i == 0 || i == intervals) ? 0.5f : (i % 2 + 1)) * jacobian;
const Spectrum A_t = exp(mu_a / cos_theta(wt) *
(is_circular(b) ?
2.0f * cosf(gamma_mi - phi_t) :
-len(to_point(gamma_mi, b) - to_point(gamma_mt + M_PI_F, b))));
const float scale2 = bsdf_hair_huang_energy_scale(kg, cos_mi2, sqrt_roughness, inv_eta);
/* TT */
if (bsdf->extra->TT > 0.0f) {
if (dot(wo, wt) >= inv_eta - 1e-5f) { /* Total internal reflection otherwise. */
float3 wh2 = -wt + inv_eta * wo;
const float rcp_norm_wh2 = 1.0f / len(wh2);
wh2 *= rcp_norm_wh2;
const float cos_mh2 = dot(wmt, wh2);
if (cos_mh2 >= 0.0f) { /* Microfacet visibility from macronormal. */
const float cos_hi2 = dot(-wt, wh2);
const float cos_ho2 = dot(-wo, wh2);
const float cos_mo2 = dot(-wo, wmt);
const float T2 = (1.0f - fresnel_dielectric_cos(cos_hi2, inv_eta)) * scale2;
const float D2 = bsdf_D<MicrofacetType::GGX>(roughness2, cos_mh2);
const float G2 = bsdf_G<MicrofacetType::GGX>(roughness2, cos_mi2, cos_mo2);
const Spectrum result = weight * T1 * scale1 * T2 * D2 * G1o * G2 * A_t / cos_mo1 *
cos_mi1 * cos_hi2 * cos_ho2 * sqr(rcp_norm_wh2);
if (isfinite_safe(result)) {
S_tt += bsdf->extra->TT * result * arc_length(bsdf->extra->e2, gamma_mt);
}
}
}
}
/* TRT and beyond. */
if (bsdf->extra->TRT > 0.0f) {
/* Sample `wh2`. */
const float2 sample2 = make_float2(lcg_step_float(rng_quadrature),
lcg_step_float(rng_quadrature));
const float3 wh2 = sample_wh(roughness, -wt, wmt, sample2);
const float cos_hi2 = dot(-wt, wh2);
if (!(cos_hi2 > 0.0f)) {
continue;
}
const float R2 = fresnel_dielectric_cos(cos_hi2, inv_eta);
const float3 wtr = -reflect(wt, wh2);
if (dot(-wtr, wo) < inv_eta - 1e-5f) {
/* Total internal reflection. */
S_trrt += weight * bsdf_hair_huang_eval_trrt(T1, R2, A_t);
continue;
}
if (!microfacet_visible(-wt, -wtr, wmt, wh2) || !microfacet_visible(-wt, -wtr, wmt_, wh2)) {
continue;
}
const float phi_tr = dir_phi(wtr);
const float gamma_mtr = gamma_mi - 2.0f * (to_phi(phi_t, b) - to_phi(phi_tr, b)) + M_PI_F;
const float3 wmtr = sphg_dir(-bsdf->tilt, gamma_mtr, b);
const float3 wmtr_ = sphg_dir(0.0f, gamma_mtr, b);
float3 wh3 = wtr + inv_eta * wo;
const float rcp_norm_wh3 = 1.0f / len(wh3);
wh3 *= rcp_norm_wh3;
const float cos_mh3 = dot(wmtr, wh3);
if (cos_mh3 < 0.0f || !microfacet_visible(wtr, -wo, wmtr, wh3) ||
!microfacet_visible(wtr, -wo, wmtr_, wh3))
{
S_trrt += weight * bsdf_hair_huang_eval_trrt(T1, R2, A_t);
continue;
}
const float cos_hi3 = dot(wh3, wtr);
const float cos_ho3 = dot(wh3, -wo);
const float cos_mi3 = dot(wmtr, wtr);
const float T3 = (1.0f - fresnel_dielectric_cos(cos_hi3, inv_eta)) *
bsdf_hair_huang_energy_scale(kg, cos_mi3, sqrt_roughness, inv_eta);
const float D3 = bsdf_D<MicrofacetType::GGX>(roughness2, cos_mh3);
const Spectrum A_tr = exp(mu_a / cos_theta(wtr) *
-(is_circular(b) ?
2.0f * fabsf(cosf(phi_tr - gamma_mt)) :
len(to_point(gamma_mtr, b) - to_point(gamma_mt, b))));
const float cos_mo2 = dot(wmt, -wtr);
const float G2o = bsdf_Go(roughness2, cos_mi2, cos_mo2);
const float G3 = bsdf_G<MicrofacetType::GGX>(roughness2, cos_mi3, dot(wmtr, -wo));
const Spectrum result = weight * T1 * scale1 * R2 * scale2 * T3 * D3 * G1o * G2o * G3 * A_t *
A_tr / (cos_mo1 * cos_mo2) * cos_mi1 * cos_mi2 * cos_hi3 * cos_ho3 *
sqr(rcp_norm_wh3);
if (isfinite_safe(result)) {
S_trt += bsdf->extra->TRT * result * arc_length(bsdf->extra->e2, gamma_mtr);
}
S_trrt += weight * bsdf_hair_huang_eval_trrt(T1, R2, A_t);
}
}
/* TRRT+ terms, following the approach in [A practical and controllable hair and fur model for
* production path tracing](https://doi.org/10.1145/2775280.2792559) by Chiang, Matt Jen-Yuan, et
* al. */
const float M = longitudinal_scattering(
sin_theta(wi), cos_theta(wi), sin_theta(wo), cos_theta(wo), 4.0f * bsdf->roughness);
const float N = M_1_2PI_F;
const float simpson_coeff = 2.0f / 3.0f * res;
return ((S_tt + S_trt) * sqr(inv_eta) + S_trrt * M * N * M_2_PI_F) * simpson_coeff;
}
ccl_device int bsdf_hair_huang_sample(const KernelGlobals kg,
const ccl_private ShaderClosure *sc,
ccl_private ShaderData *sd,
const float3 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf,
ccl_private float2 *sampled_roughness)
{
ccl_private HuangHairBSDF *bsdf = (ccl_private HuangHairBSDF *)sc;
const float roughness = bsdf->roughness;
*sampled_roughness = make_float2(roughness, roughness);
kernel_assert(fabsf(bsdf->h) < bsdf->extra->radius);
/* Generate samples. */
float sample_lobe = rand.x;
const float sample_h = rand.y;
const float2 sample_h1 = make_float2(rand.z, lcg_step_float(&sd->lcg_state));
const float2 sample_h2 = make_float2(lcg_step_float(&sd->lcg_state),
lcg_step_float(&sd->lcg_state));
const float2 sample_h3 = make_float2(lcg_step_float(&sd->lcg_state),
lcg_step_float(&sd->lcg_state));
/* Get `wi` in local coordinate. */
const float3 wi = bsdf->extra->wi;
/* Get minor axis, assuming major axis is 1. */
const float b = bsdf->aspect_ratio;
/* Sample `h` for farfield model, as the computed intersection might have numerical issues. */
const float h_div_r = is_nearfield(bsdf) ? bsdf->h / bsdf->extra->radius :
(sample_h * 2.0f - 1.0f);
const float gamma_mi = h_to_gamma(h_div_r, b, wi);
/* Macronormal. */
const float3 wmi_ = sphg_dir(0, gamma_mi, b);
/* Mesonormal. */
float st;
float ct;
fast_sincosf(bsdf->tilt, &st, &ct);
const float3 wmi = make_float3(wmi_.x * ct, st, wmi_.z * ct);
const float cos_mi1 = dot(wmi, wi);
if (cos_mi1 < 0.0f || dot(wmi_, wi) < 0.0f) {
/* Macro/mesonormal invisible. */
*pdf = 0.0f;
return LABEL_NONE;
}
/* Sample R lobe. */
const float roughness2 = sqr(roughness);
const float sqrt_roughness = sqrtf(roughness);
const float3 wh1 = sample_wh(roughness, wi, wmi, sample_h1);
const float3 wr = -reflect(wi, wh1);
/* Ensure that this is a valid sample. */
if (!microfacet_visible(wi, wmi_, wh1)) {
*pdf = 0.0f;
return LABEL_NONE;
}
float cos_theta_t1;
const float R1 = fresnel_dielectric(dot(wi, wh1), bsdf->eta, &cos_theta_t1);
const float scale1 = bsdf_hair_huang_energy_scale(kg, cos_mi1, sqrt_roughness, bsdf->eta);
const float R = bsdf->extra->R * R1 * scale1 * microfacet_visible(wr, wmi_, wh1) *
bsdf_Go(roughness2, cos_mi1, dot(wmi, wr));
/* Sample TT lobe. */
const float inv_eta = 1.0f / bsdf->eta;
const float3 wt = refract_angle(wi, wh1, cos_theta_t1, inv_eta);
const float phi_t = dir_phi(wt);
const float gamma_mt = 2.0f * to_phi(phi_t, b) - gamma_mi;
const float3 wmt = sphg_dir(-bsdf->tilt, gamma_mt, b);
const float3 wmt_ = sphg_dir(0.0f, gamma_mt, b);
const float3 wh2 = sample_wh(roughness, -wt, wmt, sample_h2);
const float3 wtr = -reflect(wt, wh2);
float3 wh3;
float3 wtt;
float3 wtrt;
float3 wmtr;
float3 wtrrt;
Spectrum TT = zero_spectrum();
Spectrum TRT = zero_spectrum();
Spectrum TRRT = zero_spectrum();
const float cos_mi2 = dot(-wt, wmt);
if (cos_mi2 > 0.0f && microfacet_visible(-wt, wmi_, wh1) && microfacet_visible(-wt, wmt_, wh2)) {
const Spectrum mu_a = bsdf->sigma;
const Spectrum A_t = exp(mu_a / cos_theta(wt) *
(is_circular(b) ?
2.0f * cosf(phi_t - gamma_mi) :
-len(to_point(gamma_mi, b) - to_point(gamma_mt + M_PI_F, b))));
float cos_theta_t2;
const float R2 = fresnel_dielectric(dot(-wt, wh2), inv_eta, &cos_theta_t2);
const float T1 = (1.0f - R1) * scale1 * bsdf_Go(roughness2, cos_mi1, dot(wmi, -wt));
const float T2 = 1.0f - R2;
const float scale2 = bsdf_hair_huang_energy_scale(kg, cos_mi2, sqrt_roughness, inv_eta);
wtt = refract_angle(-wt, wh2, cos_theta_t2, bsdf->eta);
if (dot(wmt, -wtt) > 0.0f && T2 > 0.0f && microfacet_visible(-wtt, wmt_, wh2)) {
TT = bsdf->extra->TT * T1 * A_t * T2 * scale2 * bsdf_Go(roughness2, cos_mi2, dot(wmt, -wtt));
}
/* Sample TRT lobe. */
const float phi_tr = dir_phi(wtr);
const float gamma_mtr = gamma_mi - 2.0f * (to_phi(phi_t, b) - to_phi(phi_tr, b)) + M_PI_F;
wmtr = sphg_dir(-bsdf->tilt, gamma_mtr, b);
wh3 = sample_wh(roughness, wtr, wmtr, sample_h3);
float cos_theta_t3;
const float R3 = fresnel_dielectric(dot(wtr, wh3), inv_eta, &cos_theta_t3);
wtrt = refract_angle(wtr, wh3, cos_theta_t3, bsdf->eta);
const float cos_mi3 = dot(wmtr, wtr);
if (cos_mi3 > 0.0f) {
const Spectrum A_tr = exp(mu_a / cos_theta(wtr) *
-(is_circular(b) ?
2.0f * fabsf(cosf(phi_tr - gamma_mt)) :
len(to_point(gamma_mt, b) - to_point(gamma_mtr, b))));
const Spectrum TR = T1 * R2 * scale2 * A_t * A_tr *
bsdf_hair_huang_energy_scale(kg, cos_mi3, sqrt_roughness, inv_eta) *
bsdf_Go(roughness2, cos_mi2, dot(wmt, -wtr));
const float T3 = 1.0f - R3;
if (T3 > 0.0f && microfacet_visible(wtr, -wtrt, make_float3(wmtr.x, 0.0f, wmtr.z), wh3)) {
TRT = bsdf->extra->TRT * TR * make_spectrum(T3) *
bsdf_Go(roughness2, cos_mi3, dot(wmtr, -wtrt));
}
/* Sample TRRT+ terms, following the approach in [A practical and controllable hair and fur
* model for production path tracing](https://doi.org/10.1145/2775280.2792559) by Chiang,
* Matt Jen-Yuan, et al. */
/* Sample `theta_o`. */
const float rand_theta = max(lcg_step_float(&sd->lcg_state), 1e-5f);
const float fac = 1.0f +
4.0f * bsdf->roughness *
logf(rand_theta + (1.0f - rand_theta) * expf(-0.5f / bsdf->roughness));
const float sin_theta_o = -fac * sin_theta(wi) +
cos_from_sin(fac) *
cosf(M_2PI_F * lcg_step_float(&sd->lcg_state)) * cos_theta(wi);
const float cos_theta_o = cos_from_sin(sin_theta_o);
/* Sample `phi_o`. */
const float phi_o = M_2PI_F * lcg_step_float(&sd->lcg_state);
float sin_phi_o;
float cos_phi_o;
fast_sincosf(phi_o, &sin_phi_o, &cos_phi_o);
/* Compute outgoing direction. */
wtrrt = make_float3(sin_phi_o * cos_theta_o, sin_theta_o, cos_phi_o * cos_theta_o);
/* Compute residual term by summing up the geometric series `A * T + A^2 * R * T + ...`.
* Attenuations are approximated from previous interactions. */
const Spectrum A_avg = sqrt(A_t * A_tr);
/* `T` could be zero due to total internal reflection. Clamp to avoid numerical issues. */
const float T_avg = max(0.5f * (T2 + T3), 1e-5f);
const Spectrum A_res = A_avg * T_avg / (one_spectrum() - A_avg * (1.0f - T_avg));
TRRT = TR * R3 * A_res * bsdf_Go(roughness2, cos_mi3, dot(wmtr, -reflect(wtr, wh3)));
}
}
/* Select lobe based on energy. */
const float r = R;
const float tt = average(TT);
const float trt = average(TRT);
const float trrt = average(TRRT);
const float total_energy = r + tt + trt + trrt;
if (total_energy == 0.0f) {
*pdf = 0.0f;
return LABEL_NONE;
}
float3 local_O;
sample_lobe *= total_energy;
if (sample_lobe < r) {
local_O = wr;
*eval = make_spectrum(total_energy);
}
else if (sample_lobe < (r + tt)) {
local_O = wtt;
*eval = TT / tt * total_energy;
}
else if (sample_lobe < (r + tt + trt)) {
local_O = wtrt;
*eval = TRT / trt * total_energy;
}
else {
local_O = wtrrt;
*eval = TRRT / trrt * make_spectrum(total_energy);
}
/* Transform `wo` to global coordinate system. */
*wo = to_global(local_O, bsdf->N, bsdf->extra->Y, bsdf->extra->Z);
/* Ensure the same pdf is returned for BSDF and emitter sampling. The importance sampling pdf is
* already factored in the value so this value is only used for MIS. */
*pdf = 1.0f;
return LABEL_GLOSSY | LABEL_REFLECT;
}
ccl_device Spectrum bsdf_hair_huang_eval(KernelGlobals kg,
ccl_private ShaderData *sd,
const ccl_private ShaderClosure *sc,
const float3 wo,
ccl_private float *pdf)
{
ccl_private HuangHairBSDF *bsdf = (ccl_private HuangHairBSDF *)sc;
kernel_assert(fabsf(bsdf->h) < bsdf->extra->radius);
/* Transform `wi`/`wo` from global coordinate system to local. */
const float3 local_I = bsdf->extra->wi;
const float3 local_O = to_local(wo, bsdf->N, bsdf->extra->Y, bsdf->extra->Z);
/* TODO: better estimation of the pdf */
*pdf = 1.0f;
/* Early detection of `dot(wo, wmo) < 0`. */
const float tan_tilt = tanf(bsdf->tilt);
if (tan_tilt * tan_theta(local_O) < -1.0f) {
return zero_spectrum();
}
/* Compute visible azimuthal range from the incoming direction. */
const float half_span = acosf(fmaxf(-tan_tilt * tan_theta(local_I), 0.0f));
if (isnan_safe(half_span)) {
/* Early detection of `dot(wi, wmi) < 0`. */
return zero_spectrum();
}
const float r = bsdf->extra->radius;
const float b = bsdf->aspect_ratio;
const float phi_i = is_circular(b) ? 0.0f : dir_phi(local_I);
Interval<float> h = {phi_to_h(phi_i + half_span, b, local_I),
phi_to_h(phi_i - half_span, b, local_I)};
/* Length of the integral interval. */
float dh = 2.0f;
if (is_nearfield(bsdf)) {
/* Reduce the integration interval to the subset that's visible to the current pixel.
* Inspired by [An Efficient and Practical Near and Far Field Fur Reflectance Model]
* (https://sites.cs.ucsb.edu/~lingqi/publications/paper_fur2.pdf) by Ling-Qi Yan, Henrik Wann
* Jensen and Ravi Ramamoorthi. */
const float half_pixel = bsdf->extra->pixel_coverage;
const Interval<float> nearfield_h = intervals_intersection(
Interval<float>{-r, r}, {bsdf->h - half_pixel, bsdf->h + half_pixel});
dh = nearfield_h.length() / r;
h = intervals_intersection(h, nearfield_h);
}
/* Pre-divide by radius for easier conversion to `gamma`. */
h /= r;
/* Clamp for numerical stability at the boundaries. */
h = intervals_intersection(h, {-0.999f, 0.999f});
if (h.is_empty()) {
/* No overlap between the valid range and the visible range. Can happen at grazing `theta`
* angles. */
return zero_spectrum();
}
bsdf->extra->h = h;
const float projected_area = cos_theta(local_I) * dh;
return (bsdf_hair_huang_eval_r(kg, sc, local_I, local_O) +
bsdf_hair_huang_eval_residual(kg, sc, local_I, local_O, &sd->lcg_state)) /
projected_area;
}
/* Implements Filter Glossy by capping the effective roughness. */
ccl_device void bsdf_hair_huang_blur(ccl_private ShaderClosure *sc, const float roughness)
{
ccl_private HuangHairBSDF *bsdf = (ccl_private HuangHairBSDF *)sc;
bsdf->roughness = fmaxf(roughness, bsdf->roughness);
}
/* Hair Albedo. Computed by summing up geometric series, assuming circular cross-section and
* specular reflection. */
ccl_device Spectrum bsdf_hair_huang_albedo(const ccl_private ShaderData * /*sd*/,
const ccl_private ShaderClosure *sc)
{
ccl_private HuangHairBSDF *bsdf = (ccl_private HuangHairBSDF *)sc;
const float3 wmi = make_float3(bsdf->h, 0.0f, cos_from_sin(bsdf->h));
float cos_t;
const float f = fresnel_dielectric(dot(wmi, bsdf->extra->wi), bsdf->eta, &cos_t);
const float3 wt = refract_angle(bsdf->extra->wi, wmi, cos_t, 1.0f / bsdf->eta);
const Spectrum A = exp(2.0f * bsdf->sigma * cos_t / (1.0f - sqr(wt.y)));
return safe_divide(A - 2.0f * f * A + f, one_spectrum() - f * A);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,59 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/alloc.h"
CCL_NAMESPACE_BEGIN
struct RayPortalClosure {
SHADER_CLOSURE_BASE;
float3 P;
float3 D;
};
static_assert(sizeof(ShaderClosure) >= sizeof(RayPortalClosure), "RayPortalClosure is too large!");
ccl_device void bsdf_ray_portal_setup(ccl_private ShaderData *sd,
const Spectrum weight,
const float3 position,
float3 direction)
{
/* Check cutoff weight. */
const float sample_weight = fabsf(average(weight));
if (!(sample_weight >= CLOSURE_WEIGHT_CUTOFF)) {
return;
}
sd->closure_transparent_extinction += weight;
ccl_private RayPortalClosure *pc = (ccl_private RayPortalClosure *)closure_alloc(
sd, sizeof(RayPortalClosure), CLOSURE_BSDF_RAY_PORTAL_ID, weight);
if (pc) {
sd->flag |= SD_BSDF | SD_RAY_PORTAL;
if (is_zero(direction)) {
direction = -sd->wi;
}
pc->sample_weight = sample_weight;
pc->N = sd->N;
pc->P = position;
pc->D = safe_normalize(direction);
}
}
ccl_device Spectrum bsdf_ray_portal_eval(const ccl_private ShaderClosure * /*sc*/,
const float3 /*wi*/,
const float3 /*wo*/,
ccl_private float *pdf)
{
*pdf = 0.0f;
return zero_spectrum();
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,112 @@
/* SPDX-FileCopyrightText: Copyright 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
/* Shading model by Tizian Zeltner, Brent Burley, Matt Jen-Yuan Chiang:
* "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines" (2022)
* https://tizianzeltner.com/projects/Zeltner2022Practical/
*/
#include "kernel/sample/mapping.h"
#include "kernel/util/lookup_table.h"
CCL_NAMESPACE_BEGIN
struct SheenBsdf {
SHADER_CLOSURE_BASE;
float roughness;
float transformA, transformB;
float3 T, B;
};
static_assert(sizeof(ShaderClosure) >= sizeof(SheenBsdf), "SheenBsdf is too large!");
ccl_device int bsdf_sheen_setup(KernelGlobals kg,
const ccl_private ShaderData *sd,
ccl_private SheenBsdf *bsdf)
{
bsdf->type = CLOSURE_BSDF_SHEEN_ID;
bsdf->roughness = clamp(bsdf->roughness, 1e-3f, 1.0f);
make_orthonormals_safe_tangent(bsdf->N, sd->wi, &bsdf->T, &bsdf->B);
const float cosNI = dot(bsdf->N, sd->wi);
const int offset = kernel_data.tables.sheen_ltc;
bsdf->transformA = lookup_table_read_2D(kg, cosNI, bsdf->roughness, offset, 32, 32);
bsdf->transformB = lookup_table_read_2D(kg, cosNI, bsdf->roughness, offset + 32 * 32, 32, 32);
const float albedo = lookup_table_read_2D(
kg, cosNI, bsdf->roughness, offset + 2 * 32 * 32, 32, 32);
/* If the given roughness and angle result in an invalid LTC, skip the closure. */
if (fabsf(bsdf->transformA) < 1e-5f || albedo < 1e-5f) {
bsdf->type = CLOSURE_NONE_ID;
bsdf->sample_weight = 0.0f;
return 0;
}
bsdf->weight *= albedo;
bsdf->sample_weight *= albedo;
return SD_BSDF | SD_BSDF_HAS_EVAL;
}
ccl_device Spectrum bsdf_sheen_eval(const ccl_private ShaderClosure *sc,
const float3 /*wi*/,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private SheenBsdf *bsdf = (const ccl_private SheenBsdf *)sc;
const float3 N = bsdf->N;
const float3 T = bsdf->T;
const float3 B = bsdf->B;
const float a = bsdf->transformA;
const float b = bsdf->transformB;
const float3 localO = to_local(wo, T, B, N);
const float lenSqr = sqr(a * localO.x + b * localO.z) + sqr(a * localO.y) + sqr(localO.z);
const float val = M_1_PI_F * fmaxf(localO.z, 0.0f) * sqr(a / lenSqr);
*pdf = val;
return make_spectrum(val);
}
ccl_device int bsdf_sheen_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 /*wi*/,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const ccl_private SheenBsdf *bsdf = (const ccl_private SheenBsdf *)sc;
const float3 N = bsdf->N;
const float3 T = bsdf->T;
const float3 B = bsdf->B;
const float a = bsdf->transformA;
const float b = bsdf->transformB;
const float2 disk = sample_uniform_disk(rand);
const float diskZ = safe_sqrtf(1.0f - dot(disk, disk));
const float3 localO = normalize(make_float3((disk.x - diskZ * b), disk.y, diskZ * a));
*wo = to_global(localO, T, B, N);
if (dot(Ng, *wo) <= 0) {
*eval = zero_spectrum();
*pdf = 0.0f;
return LABEL_REFLECT | LABEL_DIFFUSE;
}
const float lenSqr = sqr(a * localO.x + b * localO.z) + sqr(a * localO.y) + sqr(localO.z);
const float val = M_1_PI_F * localO.z * sqr(a / lenSqr);
*pdf = val;
*eval = make_spectrum(val);
return LABEL_REFLECT | LABEL_DIFFUSE;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,189 @@
/* SPDX-FileCopyrightText: 2009-2010 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/types.h"
#include "kernel/sample/mapping.h"
CCL_NAMESPACE_BEGIN
struct ToonBsdf {
SHADER_CLOSURE_BASE;
float size;
float smooth;
};
static_assert(sizeof(ShaderClosure) >= sizeof(ToonBsdf), "ToonBsdf is too large!");
ccl_device_inline int bsdf_toon_setup_common(ccl_private ToonBsdf *bsdf)
{
bsdf->size = clamp(bsdf->size, 1e-5f, 1.0f) * M_PI_2_F;
bsdf->smooth = saturatef(bsdf->smooth) * M_PI_2_F;
return SD_BSDF | SD_BSDF_HAS_EVAL;
}
/* DIFFUSE TOON */
ccl_device int bsdf_diffuse_toon_setup(ccl_private ToonBsdf *bsdf)
{
bsdf->type = CLOSURE_BSDF_DIFFUSE_TOON_ID;
return bsdf_toon_setup_common(bsdf);
}
ccl_device float bsdf_toon_get_intensity(const float max_angle,
const float smooth,
const float angle)
{
float is;
if (angle < max_angle) {
is = 1.0f;
}
else if (angle < (max_angle + smooth) && smooth != 0.0f) {
is = (1.0f - (angle - max_angle) / smooth);
}
else {
is = 0.0f;
}
return is;
}
ccl_device float bsdf_toon_get_sample_angle(const float max_angle, const float smooth)
{
return fminf(max_angle + smooth, M_PI_2_F);
}
ccl_device Spectrum bsdf_diffuse_toon_eval(const ccl_private ShaderClosure *sc,
const float3 /*wi*/,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private ToonBsdf *bsdf = (const ccl_private ToonBsdf *)sc;
const float max_angle = bsdf->size;
const float smooth = bsdf->smooth;
const float cosNO = dot(bsdf->N, wo);
if (cosNO >= 0.0f) {
const float angle = safe_acosf(fmaxf(cosNO, 0.0f));
const float sample_angle = bsdf_toon_get_sample_angle(max_angle, smooth);
if (angle < sample_angle) {
const float eval = bsdf_toon_get_intensity(max_angle, smooth, angle);
*pdf = M_1_2PI_F / one_minus_cos(sample_angle);
return make_spectrum(*pdf * eval);
}
}
*pdf = 0.0f;
return zero_spectrum();
}
ccl_device int bsdf_diffuse_toon_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 /*wi*/,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const ccl_private ToonBsdf *bsdf = (const ccl_private ToonBsdf *)sc;
const float max_angle = bsdf->size;
const float smooth = bsdf->smooth;
const float sample_angle = bsdf_toon_get_sample_angle(max_angle, smooth);
float cosNO;
*wo = sample_uniform_cone(bsdf->N, one_minus_cos(sample_angle), rand, &cosNO, pdf);
if (dot(Ng, *wo) > 0.0f) {
const float angle = acosf(cosNO);
*eval = make_spectrum(*pdf * bsdf_toon_get_intensity(max_angle, smooth, angle));
return LABEL_REFLECT | LABEL_DIFFUSE;
}
*pdf = 0.0f;
*eval = zero_spectrum();
return LABEL_NONE;
}
/* GLOSSY TOON */
ccl_device int bsdf_glossy_toon_setup(ccl_private ToonBsdf *bsdf)
{
bsdf->type = CLOSURE_BSDF_GLOSSY_TOON_ID;
return bsdf_toon_setup_common(bsdf);
}
ccl_device Spectrum bsdf_glossy_toon_eval(const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private ToonBsdf *bsdf = (const ccl_private ToonBsdf *)sc;
const float max_angle = bsdf->size;
const float smooth = bsdf->smooth;
const float cosNI = dot(bsdf->N, wi);
const float cosNO = dot(bsdf->N, wo);
if (cosNI > 0 && cosNO > 0) {
/* reflect the view vector */
const float3 R = (2 * cosNI) * bsdf->N - wi;
const float cosRO = dot(R, wo);
const float angle = safe_acosf(fmaxf(cosRO, 0.0f));
const float sample_angle = bsdf_toon_get_sample_angle(max_angle, smooth);
if (angle < sample_angle) {
const float eval = bsdf_toon_get_intensity(max_angle, smooth, angle);
*pdf = M_1_2PI_F / one_minus_cos(sample_angle);
return make_spectrum(*pdf * eval);
}
}
*pdf = 0.0f;
return zero_spectrum();
}
ccl_device int bsdf_glossy_toon_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 wi,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const ccl_private ToonBsdf *bsdf = (const ccl_private ToonBsdf *)sc;
const float max_angle = bsdf->size;
const float smooth = bsdf->smooth;
const float cosNI = dot(bsdf->N, wi);
if (cosNI > 0) {
/* reflect the view vector */
const float3 R = (2 * cosNI) * bsdf->N - wi;
const float sample_angle = bsdf_toon_get_sample_angle(max_angle, smooth);
float cosRO;
*wo = sample_uniform_cone(R, one_minus_cos(sample_angle), rand, &cosRO, pdf);
/* make sure the direction we chose is still in the right hemisphere */
if (dot(Ng, *wo) > 0.0f && dot(bsdf->N, *wo) > 0.0f) {
const float angle = acosf(cosRO);
*eval = make_spectrum(*pdf * bsdf_toon_get_intensity(max_angle, smooth, angle));
return LABEL_GLOSSY | LABEL_REFLECT;
}
}
*pdf = 0.0f;
*eval = zero_spectrum();
return LABEL_NONE;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,88 @@
/* SPDX-FileCopyrightText: 2009-2010 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/alloc.h"
CCL_NAMESPACE_BEGIN
ccl_device void bsdf_transparent_setup(ccl_private ShaderData *sd,
const Spectrum weight,
const uint32_t path_flag)
{
/* Check cutoff weight. */
const float sample_weight = fabsf(average(weight));
if (!(sample_weight >= CLOSURE_WEIGHT_CUTOFF)) {
return;
}
sd->closure_transparent_extinction += weight;
if (sd->flag & SD_TRANSPARENT) {
/* Add weight to existing transparent BSDF. */
for (int i = 0; i < sd->num_closure; i++) {
ccl_private ShaderClosure *sc = &sd->closure[i];
if (sc->type == CLOSURE_BSDF_TRANSPARENT_ID) {
sc->weight += weight;
sc->sample_weight += sample_weight;
break;
}
}
}
else {
sd->flag |= SD_BSDF | SD_TRANSPARENT;
if (path_flag & PATH_RAY_TERMINATE) {
/* In this case the number of closures is set to zero to disable
* all others, but we still want to get transparency so increase
* the number just for this. */
sd->num_closure_left = 1;
}
/* Create new transparent BSDF. */
ccl_private ShaderClosure *bsdf = closure_alloc(
sd, sizeof(ShaderClosure), CLOSURE_BSDF_TRANSPARENT_ID, weight);
if (bsdf) {
bsdf->sample_weight = sample_weight;
bsdf->N = sd->N;
}
else if (path_flag & PATH_RAY_TERMINATE) {
sd->num_closure_left = 0;
}
}
}
ccl_device Spectrum bsdf_transparent_eval(const ccl_private ShaderClosure * /*sc*/,
const float3 /*wi*/,
const float3 /*wo*/,
ccl_private float *pdf)
{
*pdf = 0.0f;
return zero_spectrum();
}
ccl_device int bsdf_transparent_sample(const ccl_private ShaderClosure * /*sc*/,
const float3 /*Ng*/,
const float3 wi,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
// only one direction is possible
*wo = -wi;
/* Some high number for MIS. */
*pdf = 1e6f;
*eval = one_spectrum() * 1e6f;
return LABEL_TRANSMIT | LABEL_TRANSPARENT;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,574 @@
/* SPDX-FileCopyrightText: 2009-2010 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/types.h"
#include "kernel/util/colorspace.h"
#include "kernel/util/lookup_table.h"
#include "util/color.h"
#include "util/types_spectrum.h"
CCL_NAMESPACE_BEGIN
struct FresnelThinFilm {
float thickness;
float ior;
};
template<typename T> struct complex {
T re;
T im;
ccl_device_inline_method complex<T> operator*=(ccl_private const complex<T> &other)
{
const T im = this->re * other.im + this->im * other.re;
this->re = this->re * other.re - this->im * other.im;
this->im = im;
return *this;
}
ccl_device_inline_method complex<T> operator*(ccl_private const float &other)
{
return complex<T>{this->re * other, this->im * other};
}
};
/* Compute fresnel reflectance for perpendicular (aka S-) and parallel (aka P-) polarized light.
* If requested by the caller, r_cos_phi is set to the cosine of the phase shift on reflection.
* Also returns the dot product of the refracted ray and the normal as `cos_theta_t`, as it is
* used when computing the direction of the refracted ray. */
ccl_device float2 fresnel_dielectric_polarized(float cos_theta_i,
const float eta,
ccl_private float *r_cos_theta_t,
ccl_private float2 *r_cos_phi)
{
kernel_assert(!isnan_safe(cos_theta_i));
/* Using Snell's law, calculate the squared cosine of the angle between the surface normal and
* the transmitted ray. */
const float eta_cos_theta_t_sq = sqr(eta) - (1.0f - sqr(cos_theta_i));
if (eta_cos_theta_t_sq <= 0) {
/* Total internal reflection. */
if (r_cos_phi) {
/* The following code would compute the proper phase shift on TIR.
* However, for the current user of this computation (the iridescence code),
* this doesn't actually affect the result, so don't bother with the computation for now.
*
* `const float fac = sqrtf(1.0f - sqr(cosThetaI) - sqr(eta));`
* `r_phi->x = -2.0f * atanf(fac / cosThetaI);`
* `r_phi->y = -2.0f * atanf(fac / (cosThetaI * sqr(eta)));`
*/
*r_cos_phi = one_float2();
}
return one_float2();
}
cos_theta_i = fabsf(cos_theta_i);
/* Relative to the surface normal. */
const float cos_theta_t = -safe_sqrtf(eta_cos_theta_t_sq) / eta;
if (r_cos_theta_t) {
*r_cos_theta_t = cos_theta_t;
}
/* Amplitudes of reflected waves. */
const float r_s = (cos_theta_i + eta * cos_theta_t) / (cos_theta_i - eta * cos_theta_t);
const float r_p = (cos_theta_t + eta * cos_theta_i) / (eta * cos_theta_i - cos_theta_t);
if (r_cos_phi) {
*r_cos_phi = make_float2(2 * (r_s >= 0.0f) - 1, 2 * (r_p >= 0.0f) - 1);
}
/* Return squared amplitude to get the fraction of reflected energy. */
return make_float2(sqr(r_s), sqr(r_p));
}
/* Compute fresnel reflectance for unpolarized light. */
ccl_device_forceinline float fresnel_dielectric(const float cos_theta_i,
const float eta,
ccl_private float *r_cos_theta_t)
{
return average(fresnel_dielectric_polarized(cos_theta_i, eta, r_cos_theta_t, nullptr));
}
/* Refract the incident ray, given the cosine of the refraction angle and the relative refractive
* index of the incoming medium w.r.t. the outgoing medium. */
ccl_device_inline float3 refract_angle(const float3 incident,
const float3 normal,
const float cos_theta_t,
const float inv_eta)
{
return (inv_eta * dot(normal, incident) + cos_theta_t) * normal - inv_eta * incident;
}
ccl_device float fresnel_dielectric_cos(const float cosi, const float eta)
{
// compute fresnel reflectance without explicitly computing
// the refracted direction
const float c = fabsf(cosi);
float g = eta * eta - 1 + c * c;
if (g > 0) {
g = sqrtf(g);
const float A = (g - c) / (g + c);
const float B = (c * (g + c) - 1) / (c * (g - c) + 1);
return 0.5f * A * A * (1 + B * B);
}
return 1.0f; // TIR(no refracted component)
}
/* Approximates the average single-scattering Fresnel for a given IOR.
* This is defined as the integral over 0...1 of 2*cosI * F(cosI, eta) d_cosI, with F being
* the real dielectric Fresnel.
* The implementation here uses a numerical fit from "Revisiting Physically Based Shading
* at Imageworks" by Christopher Kulla and Alejandro Conty. */
ccl_device_inline float fresnel_dielectric_Fss(const float eta)
{
if (eta < 1.0f) {
return 0.997118f + eta * (0.1014f - eta * (0.965241f + eta * 0.130607f));
}
return (eta - 1.0f) / (4.08567f + 1.00071f * eta);
}
/* Computes the average single-scattering Fresnel for the F82 metallic model. */
ccl_device_inline Spectrum fresnel_f82_Fss(const Spectrum F0, const Spectrum B)
{
return mix(F0, one_spectrum(), 1.0f / 21.0f) - B * (1.0f / 126.0f);
}
/* Precompute the B term for the F82 metallic model, given a tint factor. */
ccl_device_inline Spectrum fresnel_f82tint_B(const Spectrum F0, const Spectrum tint)
{
/* In the classic F82 model, the F82 input directly determines the value of the Fresnel
* model at ~82°, similar to F0 and F90.
* With F82-Tint, on the other hand, the value at 82° is the value of the classic Schlick
* model multiplied by the tint input.
* Therefore, the factor follows by setting F82Tint(cosI) = FSchlick(cosI) - b*cosI*(1-cosI)^6
* and F82Tint(acos(1/7)) = FSchlick(acos(1/7)) * f82_tint and solving for b. */
const float f = 6.0f / 7.0f;
const float f5 = sqr(sqr(f)) * f;
const Spectrum F_schlick = mix(F0, one_spectrum(), f5);
return F_schlick * (7.0f / (f5 * f)) * (one_spectrum() - tint);
}
/* Precompute the B term for the F82 metallic model, given the F82 value. */
ccl_device_inline Spectrum fresnel_f82_B(const Spectrum F0, const Spectrum F82)
{
const float f = 6.0f / 7.0f;
const float f5 = sqr(sqr(f)) * f;
const Spectrum F_schlick = mix(F0, one_spectrum(), f5);
return (7.0f / (f5 * f)) * (F_schlick - F82);
}
ccl_device_inline float fresnel_f82_B(const float F0, const float F82)
{
const float f = 6.0f / 7.0f;
const float f5 = sqr(sqr(f)) * f;
const float F_schlick = mix(F0, 1.0f, f5);
return (7.0f / (f5 * f)) * (F_schlick - F82);
}
/* Evaluate the F82 metallic model for the given parameters. */
ccl_device_inline Spectrum fresnel_f82(const float cosi, const Spectrum F0, const Spectrum B)
{
const float s = saturatef(1.0f - cosi);
const float s5 = sqr(sqr(s)) * s;
const Spectrum F_schlick = mix(F0, one_spectrum(), s5);
return saturate(F_schlick - B * cosi * s5 * s);
}
ccl_device_inline float fresnel_f82(const float cosi, const float F0, const float B)
{
const float s = saturatef(1.0f - cosi);
const float s5 = sqr(sqr(s)) * s;
const float F_schlick = mix(F0, 1.0f, s5);
return clamp(F_schlick - B * cosi * s5 * s, 0.0f, 1.0f);
}
/* Evaluates the Fresnel equations at a dielectric-conductor interface, calculating reflectances
* and phase shifts due to reflection if requested. The phase shifts phi_s and phi_p are returned
* as phasor_s = exp(i * phi_s) and phasor_p = exp(i * phi_p).
* This code is based on equations from section 14.4.1 of Principles of Optics 7th ed. by Born and
* Wolf, but uses `n + ik` instead of `n(1 + ik)` for IOR. The phase shifts are calculated so that
* phi_p = phi_s at 90 degree incidence to match fresnel_dielectric_polarized. */
ccl_device_forceinline void fresnel_conductor_polarized(
const float cosi,
const float ambient_ior,
const complex<float> conductor_ior,
const float F82,
ccl_private float &r_R_s,
ccl_private float &r_R_p,
ccl_private complex<float> *r_phasor_s = nullptr,
ccl_private complex<float> *r_phasor_p = nullptr)
{
const float eta1 = ambient_ior;
const float eta2 = conductor_ior.re;
const float k2 = conductor_ior.im;
const float eta1_sq = sqr(eta1);
const float eta2_sq = sqr(eta2);
const float k2_sq = sqr(k2);
const float two_eta2_k2 = 2.0f * eta2 * k2;
const float t1 = eta2_sq - k2_sq - eta1_sq * (1.0f - sqr(cosi));
const float t2 = sqrt(sqr(t1) + sqr(two_eta2_k2));
const float u_sq = max(0.5f * (t2 + t1), 0.0f);
const float v_sq = max(0.5f * (t2 - t1), 0.0f);
const float u = sqrt(u_sq);
const float v = sqrt(v_sq);
if (F82 >= 0.0f) {
/* Calculate reflectance using the F82 model if the caller requested it. */
/* Scale n and k by the film ior, and recompute F0. */
const float n = eta2 / eta1;
const float k_sq = sqr(k2 / eta1);
const float F0 = (sqr(n - 1.0f) + k_sq) / (sqr(n + 1.0f) + k_sq);
r_R_s = fresnel_f82(cosi, F0, fresnel_f82_B(F0, F82));
r_R_p = r_R_s;
}
else {
r_R_s = safe_divide(sqr(eta1 * cosi - u) + v_sq, sqr(eta1 * cosi + u) + v_sq);
const float t3 = (eta2_sq - k2_sq) * cosi;
const float t4 = two_eta2_k2 * cosi;
r_R_p = safe_divide(sqr(t3 - eta1 * u) + sqr(t4 - eta1 * v),
sqr(t3 + eta1 * u) + sqr(t4 + eta1 * v));
}
if (r_phasor_s && r_phasor_p) {
const float re_s = -u_sq - v_sq + sqr(eta1 * cosi);
const float im_s = -2.0f * eta1 * cosi * v;
const float mag_s = sqrt(sqr(re_s) + sqr(im_s));
r_phasor_s->re = (mag_s == 0.0f) ? 1.0f : re_s / mag_s;
r_phasor_s->im = (mag_s == 0.0f) ? 0.0f : im_s / mag_s;
const float re_p = sqr((eta2_sq + k2_sq) * cosi) - eta1_sq * (u_sq + v_sq);
const float im_p = 2.0f * eta1 * cosi * (two_eta2_k2 * u - (eta2_sq - k2_sq) * v);
const float mag_p = sqrt(sqr(re_p) + sqr(im_p));
r_phasor_p->re = mag_p == 0.0f ? 1.0f : re_p / mag_p;
r_phasor_p->im = mag_p == 0.0f ? 0.0f : im_p / mag_p;
}
}
/* Calculates Fresnel reflectance at a dielectric-conductor interface given the relative IOR.
*/
ccl_device Spectrum fresnel_conductor(const float cosi, const complex<Spectrum> ior)
{
float R_s_x, R_s_y, R_s_z, R_p_x, R_p_y, R_p_z;
fresnel_conductor_polarized(
cosi, 1.0f, {ior.re.x, ior.im.x}, -1.0f, R_s_x, R_p_x, nullptr, nullptr);
fresnel_conductor_polarized(
cosi, 1.0f, {ior.re.y, ior.im.y}, -1.0f, R_s_y, R_p_y, nullptr, nullptr);
fresnel_conductor_polarized(
cosi, 1.0f, {ior.re.z, ior.im.z}, -1.0f, R_s_z, R_p_z, nullptr, nullptr);
return (make_float3(R_s_x, R_s_y, R_s_z) + make_float3(R_p_x, R_p_y, R_p_z)) * 0.5f;
}
/* Approximates the average single-scattering Fresnel for a physical conductor. */
ccl_device_inline Spectrum fresnel_conductor_Fss(const complex<Spectrum> ior)
{
/* In order to estimate Fss of the conductor, we fit the F82 model to it based on the
* value at 0° and ~82° and then use the analytic expression for its Fss. */
const Spectrum F0 = fresnel_conductor(1.0f, ior);
const Spectrum F82 = fresnel_conductor(1.0f / 7.0f, ior);
return saturate(fresnel_f82_Fss(F0, fresnel_f82_B(F0, F82)));
}
ccl_device float ior_from_F0(const float f0)
{
const float sqrt_f0 = sqrtf(clamp(f0, 0.0f, 0.99f));
return (1.0f + sqrt_f0) / (1.0f - sqrt_f0);
}
ccl_device float F0_from_ior(const float ior)
{
return sqr((ior - 1.0f) / (ior + 1.0f));
}
ccl_device float schlick_fresnel(const float u)
{
const float m = clamp(1.0f - u, 0.0f, 1.0f);
const float m2 = m * m;
return m2 * m2 * m; // pow(m, 5)
}
/* Calculate the fresnel color, which is a blend between white and the F0 color */
ccl_device_forceinline Spectrum interpolate_fresnel_color(const float3 L,
const float3 H,
const float ior,
Spectrum F0)
{
/* Compute the real Fresnel term and remap it from real_F0..1 to F0..1.
* The reason why we use this remapping instead of directly doing the
* Schlick approximation mix(F0, 1.0, (1.0-cosLH)^5) is that for cases
* with similar IORs (e.g. ice in water), the relative IOR can be close
* enough to 1.0 that the Schlick approximation becomes inaccurate. */
const float real_F = fresnel_dielectric_cos(dot(L, H), ior);
const float real_F0 = fresnel_dielectric_cos(1.0f, ior);
return mix(F0, one_spectrum(), inverse_lerp(real_F0, 1.0f, real_F));
}
/* If the shading normal results in specular reflection in the lower hemisphere, raise the shading
* normal towards the geometry normal so that the specular reflection is just above the surface.
* Only used for glossy materials. */
ccl_device float3 ensure_valid_specular_reflection(const float3 Ng, const float3 I, float3 N)
{
const float3 R = 2 * dot(N, I) * N - I;
const float Iz = dot(I, Ng);
kernel_assert(Iz >= 0);
/* Reflection rays may always be at least as shallow as the incoming ray. */
const float threshold = min(0.9f * Iz, 0.01f);
if (dot(Ng, R) >= threshold) {
return N;
}
/* Form coordinate system with Ng as the Z axis and N inside the X-Z-plane.
* The X axis is found by normalizing the component of N that's orthogonal to Ng.
* The Y axis isn't actually needed.
*/
const float3 X = safe_normalize_fallback(N - dot(N, Ng) * Ng, N);
/* Calculate N.z and N.x in the local coordinate system.
*
* The goal of this computation is to find a N' that is rotated towards Ng just enough
* to lift R' above the threshold (here called t), therefore dot(R', Ng) = t.
*
* According to the standard reflection equation,
* this means that we want dot(2*dot(N', I)*N' - I, Ng) = t.
*
* Since the Z axis of our local coordinate system is Ng, dot(x, Ng) is just x.z, so we get
* 2*dot(N', I)*N'.z - I.z = t.
*
* The rotation is simple to express in the coordinate system we formed -
* since N lies in the X-Z-plane, we know that N' will also lie in the X-Z-plane,
* so N'.y = 0 and therefore dot(N', I) = N'.x*I.x + N'.z*I.z .
*
* Furthermore, we want N' to be normalized, so N'.x = sqrt(1 - N'.z^2).
*
* With these simplifications, we get the equation
* 2*(sqrt(1 - N'.z^2)*I.x + N'.z*I.z)*N'.z - I.z = t,
* or
* 2*sqrt(1 - N'.z^2)*I.x*N'.z = t + I.z * (1 - 2*N'.z^2),
* after rearranging terms.
* Raise both sides to the power of two and substitute terms with
* a = I.x^2 + I.z^2,
* b = 2*(a + Iz*t),
* c = (Iz + t)^2,
* we obtain
* 4*a*N'.z^4 - 2*b*N'.z^2 + c = 0.
*
* The only unknown here is N'.z, so we can solve for that.
*
* The equation has four solutions in general, two can immediately be discarded because they're
* negative so N' would lie in the lower hemisphere; one solves
* 2*sqrt(1 - N'.z^2)*I.x*N'.z = -(t + I.z * (1 - 2*N'.z^2))
* instead of the original equation (before squaring both sides).
* Therefore only one root is valid.
*/
const float Ix = dot(I, X);
const float a = sqr(Ix) + sqr(Iz);
const float b = 2.0f * (a + Iz * threshold);
const float c = sqr(threshold + Iz);
/* In order that the root formula solves 2*sqrt(1 - N'.z^2)*I.x*N'.z = t + I.z - 2*I.z*N'.z^2,
* Ix and (t + I.z * (1 - 2*N'.z^2)) must have the same sign (the rest terms are non-negative by
* definition). */
const float Nz2 = (Ix < 0) ? 0.25f * (b + safe_sqrtf(sqr(b) - 4.0f * a * c)) / a :
0.25f * (b - safe_sqrtf(sqr(b) - 4.0f * a * c)) / a;
const float Nx = safe_sqrtf(1.0f - Nz2);
const float Nz = safe_sqrtf(Nz2);
return Nx * X + Nz * Ng;
}
/* Do not call #ensure_valid_specular_reflection if the primitive type is curve or if the geometry
* normal and the shading normal is the same. */
ccl_device float3 maybe_ensure_valid_specular_reflection(ccl_private ShaderData *sd,
const float3 N)
{
if ((sd->flag & SD_USE_BUMP_MAP_CORRECTION) == 0) {
return N;
}
if ((sd->type & PRIMITIVE_CURVE) || isequal(sd->Ng, N)) {
return N;
}
return ensure_valid_specular_reflection(sd->Ng, sd->wi, N);
}
/* Principled Hair albedo and absorption coefficients. */
ccl_device_inline float bsdf_principled_hair_albedo_roughness_scale(
const float azimuthal_roughness)
{
const float x = azimuthal_roughness;
return (((((0.245f * x) + 5.574f) * x - 10.73f) * x + 2.532f) * x - 0.215f) * x + 5.969f;
}
ccl_device_inline Spectrum
bsdf_principled_hair_sigma_from_reflectance(const Spectrum color, const float azimuthal_roughness)
{
const Spectrum sigma = log(max(color, zero_spectrum())) /
bsdf_principled_hair_albedo_roughness_scale(azimuthal_roughness);
return sigma * sigma;
}
ccl_device_inline Spectrum bsdf_principled_hair_sigma_from_concentration(const float eumelanin,
const float pheomelanin)
{
const float3 eumelanin_color = make_float3(0.506f, 0.841f, 1.653f);
const float3 pheomelanin_color = make_float3(0.343f, 0.733f, 1.924f);
return eumelanin * rgb_to_spectrum(eumelanin_color) +
pheomelanin * rgb_to_spectrum(pheomelanin_color);
}
/* Computes the weight for base closure(s) which are layered under another closure.
* layer_albedo is an estimate of the top layer's reflectivity, while weight is the closure weight
* of the entire base+top combination. */
ccl_device_inline Spectrum closure_layering_weight(const Spectrum layer_albedo,
const Spectrum weight)
{
return weight * saturatef(1.0f - reduce_max(safe_divide_color(layer_albedo, weight)));
}
/* ******** Thin-film iridescence implementation ********
*
* Based on "A Practical Extension to Microfacet Theory for the Modeling of Varying Iridescence"
* by Laurent Belcour and Pascal Barla.
* https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html.
*/
/**
* Evaluate the sensitivity functions for the Fourier-space spectral integration.
*/
ccl_device_inline complex<float> iridescence_lookup_sensitivity_channel(KernelGlobals kg,
const int channel,
const float OPD)
{
/* The LUT covers 0 to 60 um. */
const float x = M_2PI_F * OPD / 60000.0f;
const int size = THIN_FILM_TABLE_SIZE;
const int base = kernel_data.tables.thin_film_table;
return {lookup_table_read(kg, x, base + channel * size, size),
lookup_table_read(kg, x, base + (channel + 3) * size, size)};
}
ccl_device_inline float iridescence_airy_summation_channel(KernelGlobals kg,
const int channel,
const float R12,
const float R23,
const float OPD,
const complex<float> phasor)
{
const float T121 = 1.0f - R12;
const float R123 = R12 * R23;
const float r123 = sqrtf(R123);
const float Rs = sqr(T121) * R23 / (1.0f - R123);
/* Initialize complex number for exp(i * phi)^m, equivalent to {cos(m * phi), sin(m * phi)} as
* used in equation 10. */
complex<float> accumulator = phasor;
/* Perform summation over path order differences (equation 10). */
float R = Rs + R12; /* C0 */
float Cm = Rs - T121;
/* Truncate after m=3, higher differences have barely any impact. */
for (int m = 1; m < 4; m++) {
Cm *= r123;
const complex<float> S = iridescence_lookup_sensitivity_channel(kg, channel, m * OPD);
R += Cm * 2.0f * (accumulator.re * S.re + accumulator.im * S.im);
accumulator *= phasor;
}
return R;
}
template<bool conductive>
ccl_device float fresnel_iridescence_channel(KernelGlobals kg,
const int channel,
const float ambient_ior,
const FresnelThinFilm thin_film,
const float substrate_n,
const float substrate_k,
const float F82,
const float cos_theta_1,
ccl_private float *r_cos_theta_3)
{
/* For films below 1nm, the wave-optic-based Airy summation approach no longer applies,
* so blend towards the case without coating. */
float film_ior = thin_film.ior;
if (thin_film.thickness < 1.0f) {
film_ior = mix(ambient_ior, film_ior, smoothstep(0.0f, 1.0f, thin_film.thickness));
}
float cos_theta_2;
/* The real component of exp(i * phi12), equivalent to cos(phi12). */
float2 phasor12_real;
/* Compute reflection at the top interface (ambient to film). */
const float2 R12 = fresnel_dielectric_polarized(
cos_theta_1, film_ior / ambient_ior, &cos_theta_2, &phasor12_real);
if (isequal(R12, one_float2())) {
/* TIR at the top interface. */
return 1.0f;
}
/* Compute reflection at the bottom interface (film to substrate). */
float R23_s, R23_p;
complex<float> phasor23_s, phasor23_p;
if constexpr (conductive) {
/* Material is a conductor. */
fresnel_conductor_polarized(-cos_theta_2,
film_ior,
{substrate_n, substrate_k},
F82,
R23_s,
R23_p,
&phasor23_s,
&phasor23_p);
}
else {
/* Material is a dielectric. */
float2 phasor23_real;
const float2 R23 = fresnel_dielectric_polarized(
-cos_theta_2, substrate_n / film_ior, r_cos_theta_3, &phasor23_real);
if (isequal(R23, one_float2())) {
/* TIR at the bottom interface.
* All the Airy summation math still simplifies to 1.0 in this case. */
return 1.0f;
}
R23_s = R23.x;
R23_p = R23.y;
phasor23_s = {phasor23_real.x, 0.0f};
phasor23_p = {phasor23_real.y, 0.0f};
}
/* Compute optical path difference inside the thin film. */
const float OPD = -2.0f * film_ior * thin_film.thickness * cos_theta_2;
/* Compute full phase shifts due to reflection, as a complex number exp(i * (phi23 + phi21)).
* This complex form avoids the atan2 and cos calls needed to directly get the phase shift. */
const complex<float> phasor_s = phasor23_s * -phasor12_real.x;
const float R_s = iridescence_airy_summation_channel(kg, channel, R12.x, R23_s, OPD, phasor_s);
/* Perform Airy summation and average the polarizations. */
const complex<float> phasor_p = phasor23_p * -phasor12_real.y;
const float R_p = iridescence_airy_summation_channel(kg, channel, R12.y, R23_p, OPD, phasor_p);
return saturatef(0.5f * (R_s + R_p));
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,355 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/alloc.h"
#include "kernel/closure/bsdf_diffuse.h"
CCL_NAMESPACE_BEGIN
struct Bssrdf {
SHADER_CLOSURE_BASE;
Spectrum radius;
Spectrum albedo;
float anisotropy;
/* Parameters for refractive entry bounce. */
float ior;
float alpha;
};
static_assert(sizeof(ShaderClosure) >= sizeof(Bssrdf), "Bssrdf is too large!");
/* Random Walk BSSRDF */
ccl_device float bssrdf_dipole_compute_Rd(const float alpha_prime, const float fourthirdA)
{
const float s = sqrtf(3.0f * (1.0f - alpha_prime));
return 0.5f * alpha_prime * (1.0f + expf(-fourthirdA * s)) * expf(-s);
}
ccl_device float bssrdf_dipole_compute_alpha_prime(const float rd, const float fourthirdA)
{
/* Little Newton solver. */
if (rd < 1e-4f) {
return 0.0f;
}
if (rd >= 0.995f) {
return 0.999999f;
}
float x0 = 0.0f;
float x1 = 1.0f;
float xmid;
float fmid;
constexpr const int max_num_iterations = 12;
for (int i = 0; i < max_num_iterations; ++i) {
xmid = 0.5f * (x0 + x1);
fmid = bssrdf_dipole_compute_Rd(xmid, fourthirdA);
if (fmid < rd) {
x0 = xmid;
}
else {
x1 = xmid;
}
}
return xmid;
}
ccl_device void bssrdf_setup_radius(ccl_private Bssrdf *bssrdf, const ClosureType type)
{
if (type == CLOSURE_BSSRDF_BURLEY_ID || type == CLOSURE_BSSRDF_RANDOM_WALK_LEGACY_ID) {
/* Scale mean free path length so that Burley and Random Walk Legacy look similar to before. */
bssrdf->radius *= 0.25f * M_1_PI_F;
}
else if (type == CLOSURE_BSSRDF_RANDOM_WALK_SKIN_ID) {
/* Adjust radius based on IOR and albedo. */
const float inv_eta = 1.0f / bssrdf->ior;
const float F_dr = inv_eta * (-1.440f * inv_eta + 0.710f) + 0.668f + 0.0636f * bssrdf->ior;
const float fourthirdA = (4.0f / 3.0f) * (1.0f + F_dr) /
(1.0f - F_dr); /* From Jensen's `Fdr` ratio formula. */
Spectrum alpha_prime;
FOREACH_SPECTRUM_CHANNEL (i) {
GET_SPECTRUM_CHANNEL(alpha_prime, i) = bssrdf_dipole_compute_alpha_prime(
GET_SPECTRUM_CHANNEL(bssrdf->albedo, i), fourthirdA);
}
bssrdf->radius *= sqrt(3.0f * (one_spectrum() - alpha_prime));
}
}
/* Christensen-Burley BSSRDF.
*
* Approximate Reflectance Profiles from
* http://graphics.pixar.com/library/ApproxBSSRDF/paper.pdf
*/
/* This is a bit arbitrary, just need big enough radius so it matches
* the mean free length, but still not too big so sampling is still
* effective. */
#define BURLEY_TRUNCATE 16.0f
#define BURLEY_TRUNCATE_CDF 0.9963790093708328f // cdf(BURLEY_TRUNCATE)
ccl_device_inline float bssrdf_burley_fitting(const float A)
{
/* Diffuse surface transmission, equation (6). */
return 1.9f - A + 3.5f * (A - 0.8f) * (A - 0.8f);
}
/* Scale mean free path length so it gives similar looking result
* to Cubic and Gaussian models. */
ccl_device_inline Spectrum bssrdf_burley_compatible_mfp(Spectrum r)
{
return 0.25f * M_1_PI_F * r;
}
ccl_device void bssrdf_burley_setup(ccl_private Bssrdf *bssrdf)
{
/* Mean free path length. */
const Spectrum l = bssrdf_burley_compatible_mfp(bssrdf->radius);
/* Surface albedo. */
const Spectrum A = bssrdf->albedo;
Spectrum s;
FOREACH_SPECTRUM_CHANNEL (i) {
GET_SPECTRUM_CHANNEL(s, i) = bssrdf_burley_fitting(GET_SPECTRUM_CHANNEL(A, i));
}
bssrdf->radius = l / s;
}
ccl_device float bssrdf_burley_eval(const float d, const float r)
{
const float Rm = BURLEY_TRUNCATE * d;
if (r >= Rm) {
return 0.0f;
}
/* Burley reflectance profile, equation (3).
*
* NOTES:
* - Surface albedo is already included into `sc->weight`, no need to
* multiply by this term here.
* - This is normalized diffuse model, so the equation is multiplied
* by `2*pi`, which also matches `cdf()`.
*/
const float exp_r_3_d = expf(-r / (3.0f * d));
const float exp_r_d = exp_r_3_d * exp_r_3_d * exp_r_3_d;
return (exp_r_d + exp_r_3_d) / (4.0f * d);
}
ccl_device float bssrdf_burley_pdf(const float d, const float r)
{
if (r == 0.0f) {
return 0.0f;
}
return bssrdf_burley_eval(d, r) * (1.0f / BURLEY_TRUNCATE_CDF);
}
/* Find the radius for desired CDF value.
* Returns scaled radius, meaning the result is to be scaled up by d.
* Since there's no closed form solution we do Newton-Raphson method to find it.
*/
ccl_device_forceinline float bssrdf_burley_root_find(const float xi)
{
const float tolerance = 1e-6f;
const int max_iteration_count = 10;
/* Do initial guess based on manual curve fitting, this allows us to reduce
* number of iterations to maximum 4 across the [0..1] range. We keep maximum
* number of iteration higher just to be sure we didn't miss root in some
* corner case.
*/
float r;
if (xi <= 0.9f) {
r = expf(xi * xi * 2.4f) - 1.0f;
}
else {
/* TODO(sergey): Some nicer curve fit is possible here. */
r = 15.0f;
}
/* Solve against scaled radius. */
for (int i = 0; i < max_iteration_count; i++) {
const float exp_r_3 = expf(-r / 3.0f);
const float exp_r = exp_r_3 * exp_r_3 * exp_r_3;
const float f = 1.0f - 0.25f * exp_r - 0.75f * exp_r_3 - xi;
const float f_ = 0.25f * exp_r + 0.25f * exp_r_3;
if (fabsf(f) < tolerance || f_ == 0.0f) {
break;
}
r = r - f / f_;
r = fmaxf(r, 0.0f);
}
return r;
}
ccl_device void bssrdf_burley_sample(const float d,
const float xi,
ccl_private float *r,
ccl_private float *h)
{
const float Rm = BURLEY_TRUNCATE * d;
const float r_ = bssrdf_burley_root_find(xi * BURLEY_TRUNCATE_CDF) * d;
*r = r_;
/* h^2 + r^2 = Rm^2 */
*h = safe_sqrtf(Rm * Rm - r_ * r_);
}
ccl_device float bssrdf_num_channels(const Spectrum radius)
{
float channels = 0;
FOREACH_SPECTRUM_CHANNEL (i) {
if (GET_SPECTRUM_CHANNEL(radius, i) > 0.0f) {
channels += 1.0f;
}
}
return channels;
}
ccl_device void bssrdf_sample(const Spectrum radius,
float xi,
ccl_private float *r,
ccl_private float *h)
{
const float num_channels = bssrdf_num_channels(radius);
float sampled_radius;
/* Sample color channel and reuse random number. Only a subset of channels
* may be used if their radius was too small to handle as BSSRDF. */
xi *= num_channels;
sampled_radius = 0.0f;
float sum = 0.0f;
FOREACH_SPECTRUM_CHANNEL (i) {
const float channel_radius = GET_SPECTRUM_CHANNEL(radius, i);
if (channel_radius > 0.0f) {
const float next_sum = sum + 1.0f;
if (xi < next_sum) {
xi -= sum;
sampled_radius = channel_radius;
break;
}
sum = next_sum;
}
}
/* Sample BSSRDF. */
bssrdf_burley_sample(sampled_radius, xi, r, h);
}
ccl_device_forceinline Spectrum bssrdf_eval(const Spectrum radius, const float r)
{
Spectrum result;
FOREACH_SPECTRUM_CHANNEL (i) {
GET_SPECTRUM_CHANNEL(result, i) = bssrdf_burley_pdf(GET_SPECTRUM_CHANNEL(radius, i), r);
}
return result;
}
ccl_device_forceinline float bssrdf_pdf(const Spectrum radius, const float r)
{
const Spectrum pdf = bssrdf_eval(radius, r);
return reduce_add(pdf) / bssrdf_num_channels(radius);
}
/* Setup */
ccl_device_inline ccl_private Bssrdf *bssrdf_alloc(ccl_private ShaderData *sd, Spectrum weight)
{
const float sample_weight = fabsf(average(weight));
if (sample_weight < CLOSURE_WEIGHT_CUTOFF) {
return nullptr;
}
ccl_private Bssrdf *bssrdf = (ccl_private Bssrdf *)closure_alloc(
sd, sizeof(Bssrdf), CLOSURE_NONE_ID, weight);
if (bssrdf == nullptr) {
return nullptr;
}
bssrdf->sample_weight = sample_weight;
return bssrdf;
}
ccl_device int bssrdf_setup(ccl_private ShaderData *sd,
ccl_private Bssrdf *bssrdf,
const uint32_t path_flag,
ClosureType type)
{
/* Clamp anisotropy to avoid delta function. */
if (type == CLOSURE_BSSRDF_RANDOM_WALK_ID) {
bssrdf->anisotropy = clamp(bssrdf->anisotropy, -0.99f, 0.99f);
}
else {
bssrdf->anisotropy = clamp(bssrdf->anisotropy, -0.99f, 0.9f);
}
bssrdf->ior = clamp(bssrdf->ior, 1.01f, 3.8f);
int flag = 0;
if (type == CLOSURE_BSSRDF_RANDOM_WALK_SKIN_ID) {
/* CLOSURE_BSSRDF_RANDOM_WALK_SKIN_ID uses a fixed roughness. */
bssrdf->alpha = 1.0f;
}
/* Verify if the radii are large enough to sample without precision issues. */
int bssrdf_channels = SPECTRUM_CHANNELS;
Spectrum diffuse_weight = zero_spectrum();
if (type == CLOSURE_BSSRDF_BURLEY_ID && (path_flag & PATH_RAY_DIFFUSE_ANCESTOR)) {
/* Fall back to diffuse after a diffuse ancestor for Christensen-Burley. Can't see it that
* well and it adds considerable noise due to probabilities of continuing the path getting
* lower and lower. The disk sampling must probe in directions where most of the time
* nothing will be hit. */
bssrdf_channels = 0;
diffuse_weight = bssrdf->weight;
}
else {
FOREACH_SPECTRUM_CHANNEL (i) {
if (GET_SPECTRUM_CHANNEL(bssrdf->radius, i) < BSSRDF_MIN_RADIUS) {
GET_SPECTRUM_CHANNEL(diffuse_weight, i) = GET_SPECTRUM_CHANNEL(bssrdf->weight, i);
GET_SPECTRUM_CHANNEL(bssrdf->weight, i) = 0.0f;
GET_SPECTRUM_CHANNEL(bssrdf->radius, i) = 0.0f;
bssrdf_channels--;
}
}
}
if (bssrdf_channels < SPECTRUM_CHANNELS) {
/* Add diffuse BSDF if any radius too small. */
bsdf_diffuse_setup(sd, bssrdf->N, diffuse_weight);
}
/* Setup BSSRDF if radius is large enough. */
if (bssrdf_channels > 0) {
bssrdf->type = type;
bssrdf->sample_weight = fabsf(average(bssrdf->weight)) * bssrdf_channels;
bssrdf_setup_radius(bssrdf, type);
flag |= SD_BSSRDF;
}
else {
bssrdf->type = CLOSURE_NONE_ID;
bssrdf->sample_weight = 0.0f;
}
return flag;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,65 @@
/* SPDX-FileCopyrightText: 2009-2010 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/types.h"
CCL_NAMESPACE_BEGIN
/* BACKGROUND CLOSURE */
ccl_device void background_setup(ccl_private ShaderData *sd, const Spectrum weight)
{
if (sd->flag & SD_EMISSION) {
sd->closure_emission_background += weight;
}
else {
sd->flag |= SD_EMISSION;
sd->closure_emission_background = weight;
}
}
/* EMISSION CLOSURE */
ccl_device void emission_setup(ccl_private ShaderData *sd, const Spectrum weight)
{
if (sd->flag & SD_EMISSION) {
sd->closure_emission_background += weight;
}
else {
sd->flag |= SD_EMISSION;
sd->closure_emission_background = weight;
}
}
/* return the probability distribution function in the direction wi,
* given the parameters and the light's surface normal. This MUST match
* the PDF computed by sample(). */
ccl_device float emissive_pdf(const float3 Ng, const float3 wi)
{
const float cosNI = fabsf(dot(Ng, wi));
return (cosNI > 0.0f) ? 1.0f : 0.0f;
}
ccl_device void emissive_sample(const float3 /*Ng*/,
const float /*randu*/,
const float /*randv*/,
ccl_private float3 * /*wi*/,
ccl_private float * /*pdf*/)
{
/* todo: not implemented and used yet */
}
ccl_device Spectrum emissive_simple_eval(const float3 Ng, const float3 wi)
{
const float res = emissive_pdf(Ng, wi);
return make_spectrum(res);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,191 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/volume_draine.h"
#include "kernel/closure/volume_fournier_forand.h"
#include "kernel/closure/volume_henyey_greenstein.h"
#include "kernel/closure/volume_rayleigh.h"
CCL_NAMESPACE_BEGIN
/* VOLUME EXTINCTION */
ccl_device void volume_extinction_setup(ccl_private ShaderData *sd, Spectrum weight)
{
if (sd->flag & SD_EXTINCTION) {
sd->closure_transparent_extinction += weight;
}
else {
sd->flag |= SD_EXTINCTION;
sd->closure_transparent_extinction = weight;
}
}
/* VOLUME SCATTERING */
ccl_device Spectrum volume_phase_eval(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float3 wo,
ccl_private float *pdf)
{
switch (svc->type) {
case CLOSURE_VOLUME_FOURNIER_FORAND_ID:
return volume_fournier_forand_eval(sd, svc, wo, pdf);
case CLOSURE_VOLUME_RAYLEIGH_ID:
return volume_rayleigh_eval(sd, wo, pdf);
case CLOSURE_VOLUME_DRAINE_ID:
return volume_draine_eval(sd, svc, wo, pdf);
case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID:
return volume_henyey_greenstein_eval(sd, svc, wo, pdf);
default:
kernel_assert(false);
*pdf = 0.0f;
return zero_spectrum();
}
}
ccl_device int volume_phase_sample(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
switch (svc->type) {
case CLOSURE_VOLUME_FOURNIER_FORAND_ID:
return volume_fournier_forand_sample(sd, svc, rand, eval, wo, pdf);
case CLOSURE_VOLUME_RAYLEIGH_ID:
return volume_rayleigh_sample(sd, rand, eval, wo, pdf);
case CLOSURE_VOLUME_DRAINE_ID:
return volume_draine_sample(sd, svc, rand, eval, wo, pdf);
case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID:
return volume_henyey_greenstein_sample(sd, svc, rand, eval, wo, pdf);
default:
kernel_assert(false);
*pdf = 0.0f;
return 0;
}
}
/* Widen the compact ray differential dD after a phase function scatter to
* match the lobe's angular spread. See bsdf_widen_dD for details. */
ccl_device_forceinline float volume_phase_widen_dD(const float prev_dD,
const float sampled_roughness)
{
return max(prev_dD, sampled_roughness);
}
ccl_device bool volume_phase_equal(const ccl_private ShaderClosure *c1,
const ccl_private ShaderClosure *c2)
{
if (c1->type != c2->type) {
return false;
}
switch (c1->type) {
case CLOSURE_VOLUME_FOURNIER_FORAND_ID: {
ccl_private FournierForandVolume *v1 = (ccl_private FournierForandVolume *)c1;
ccl_private FournierForandVolume *v2 = (ccl_private FournierForandVolume *)c2;
return v1->c1 == v2->c1 && v1->c2 == v2->c2 && v1->c3 == v2->c3;
}
case CLOSURE_VOLUME_RAYLEIGH_ID:
return true;
case CLOSURE_VOLUME_DRAINE_ID: {
ccl_private DraineVolume *v1 = (ccl_private DraineVolume *)c1;
ccl_private DraineVolume *v2 = (ccl_private DraineVolume *)c2;
return v1->g == v2->g && v1->alpha == v2->alpha;
}
case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID: {
ccl_private HenyeyGreensteinVolume *v1 = (ccl_private HenyeyGreensteinVolume *)c1;
ccl_private HenyeyGreensteinVolume *v2 = (ccl_private HenyeyGreensteinVolume *)c2;
return v1->g == v2->g;
}
default:
return false;
}
return false;
}
/* Approximate phase functions as Henyey-Greenstein for volume guiding.
* TODO: This is not ideal, we should use RIS guiding for non-HG phase functions. */
ccl_device float volume_phase_get_g(const ccl_private ShaderVolumeClosure *svc)
{
switch (svc->type) {
case CLOSURE_VOLUME_FOURNIER_FORAND_ID:
/* TODO */
return 1.0f;
case CLOSURE_VOLUME_RAYLEIGH_ID:
/* Approximate as isotropic */
return 0.0f;
case CLOSURE_VOLUME_DRAINE_ID:
/* Approximate as HG, TODO */
return ((ccl_private DraineVolume *)svc)->g;
case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID:
return ((ccl_private HenyeyGreensteinVolume *)svc)->g;
default:
return 0.0f;
}
}
/* Volume sampling utilities. */
/* Ignore paths that have volume throughput below this value, to avoid unnecessary work
* and precision issues.
* TODO: this value could be tweaked or turned into a probability to avoid unnecessary work in
* volumes and subsurface scattering. */
#define VOLUME_THROUGHPUT_EPSILON 1e-6f
ccl_device Spectrum volume_color_transmittance(Spectrum sigma, const float t)
{
return exp(-sigma * t);
}
ccl_device float volume_channel_get(Spectrum value, const int channel)
{
return GET_SPECTRUM_CHANNEL(value, channel);
}
/* Sample color channel proportional to throughput and single scattering albedo, to significantly
* reduce noise with many bounce, following:
*
* "Practical and Controllable Subsurface Scattering for Production Path Tracing".
* Matt Jen-Yuan Chiang, Peter Kutz, Brent Burley. SIGGRAPH 2016. */
ccl_device_inline Spectrum volume_sample_channel_pdf(Spectrum albedo, Spectrum throughput)
{
const Spectrum weights = fabs(throughput * albedo);
const float sum_weights = reduce_add(weights);
if ((1.0f - sum_weights) < 1.0f) {
/* The same as `sum_weights > 0.0f`, but avoids the case where `sum_weight` is denormal, which
* could produce `nan` after division. */
return weights / sum_weights;
}
return make_spectrum(1.0f / SPECTRUM_CHANNELS);
}
ccl_device int volume_sample_channel(Spectrum albedo,
Spectrum throughput,
ccl_private float *rand,
ccl_private Spectrum *pdf)
{
*pdf = volume_sample_channel_pdf(albedo, throughput);
float pdf_sum = 0.0f;
FOREACH_SPECTRUM_CHANNEL (i) {
const float channel_pdf = GET_SPECTRUM_CHANNEL(*pdf, i);
if (*rand < pdf_sum + channel_pdf) {
/* Rescale to reuse. */
*rand = (*rand - pdf_sum) / channel_pdf;
return i;
}
pdf_sum += channel_pdf;
}
return SPECTRUM_CHANNELS - 1;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,62 @@
/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/volume_util.h"
CCL_NAMESPACE_BEGIN
/* DRAINE CLOSURE */
struct DraineVolume {
SHADER_CLOSURE_VOLUME_BASE;
float g;
float alpha;
};
static_assert(sizeof(ShaderVolumeClosure) >= sizeof(DraineVolume), "DraineVolume is too large!");
ccl_device int volume_draine_setup(ccl_private DraineVolume *volume)
{
volume->type = CLOSURE_VOLUME_DRAINE_ID;
/* clamp anisotropy */
volume->g = signf(volume->g) * min(fabsf(volume->g), 1.0f - 1e-3f);
return SD_SCATTER;
}
ccl_device Spectrum volume_draine_eval(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private DraineVolume *volume = (const ccl_private DraineVolume *)svc;
/* note that wi points towards the viewer */
const float cos_theta = dot(-sd->wi, wo);
*pdf = phase_draine(cos_theta, volume->g, volume->alpha);
return make_spectrum(*pdf);
}
ccl_device int volume_draine_sample(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const ccl_private DraineVolume *volume = (const ccl_private DraineVolume *)svc;
/* note that wi points towards the viewer and so is used negated */
*wo = phase_draine_sample(-sd->wi, volume->g, volume->alpha, rand, pdf);
*eval = make_spectrum(*pdf); /* perfect importance sampling */
return LABEL_VOLUME_SCATTER;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,73 @@
/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/volume_util.h"
CCL_NAMESPACE_BEGIN
/* FOURNIER-FORAND CLOSURE */
struct FournierForandVolume {
SHADER_CLOSURE_VOLUME_BASE;
/* Precomputed coefficients, based on B and IOR */
float c1, c2, c3;
};
static_assert(sizeof(ShaderVolumeClosure) >= sizeof(FournierForandVolume),
"FournierForandVolume is too large!");
ccl_device int volume_fournier_forand_setup(ccl_private FournierForandVolume *volume,
float B,
float IOR)
{
volume->type = CLOSURE_VOLUME_FOURNIER_FORAND_ID;
/* clamp backscatter fraction to avoid delta function */
B = min(fabsf(B), 0.5f - 1e-3f);
IOR = max(IOR, 1.0f + 1e-3f);
const float3 coeffs = phase_fournier_forand_coeffs(B, IOR);
volume->c1 = coeffs.x;
volume->c2 = coeffs.y;
volume->c3 = coeffs.z;
return SD_SCATTER;
}
ccl_device Spectrum volume_fournier_forand_eval(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private FournierForandVolume *volume = (const ccl_private FournierForandVolume *)svc;
const float3 coeffs = make_float3(volume->c1, volume->c2, volume->c3);
/* note that wi points towards the viewer */
const float cos_theta = dot(-sd->wi, wo);
*pdf = phase_fournier_forand(cos_theta, coeffs);
return make_spectrum(*pdf);
}
ccl_device int volume_fournier_forand_sample(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const ccl_private FournierForandVolume *volume = (const ccl_private FournierForandVolume *)svc;
const float3 coeffs = make_float3(volume->c1, volume->c2, volume->c3);
/* note that wi points towards the viewer and so is used negated */
*wo = phase_fournier_forand_sample(-sd->wi, coeffs, rand, pdf);
*eval = make_spectrum(*pdf); /* perfect importance sampling */
return LABEL_VOLUME_SCATTER;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,64 @@
/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/volume_util.h"
CCL_NAMESPACE_BEGIN
/* HENYEY-GREENSTEIN CLOSURE */
struct HenyeyGreensteinVolume {
SHADER_CLOSURE_VOLUME_BASE;
float g;
};
static_assert(sizeof(ShaderVolumeClosure) >= sizeof(HenyeyGreensteinVolume),
"HenyeyGreensteinVolume is too large!");
ccl_device int volume_henyey_greenstein_setup(ccl_private HenyeyGreensteinVolume *volume)
{
volume->type = CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID;
/* clamp anisotropy to avoid delta function */
volume->g = signf(volume->g) * min(fabsf(volume->g), 1.0f - 1e-3f);
return SD_SCATTER;
}
ccl_device Spectrum volume_henyey_greenstein_eval(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private HenyeyGreensteinVolume *volume = (const ccl_private HenyeyGreensteinVolume *)
svc;
/* note that wi points towards the viewer */
const float cos_theta = dot(-sd->wi, wo);
*pdf = phase_henyey_greenstein(cos_theta, volume->g);
return make_spectrum(*pdf);
}
ccl_device int volume_henyey_greenstein_sample(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const ccl_private HenyeyGreensteinVolume *volume = (const ccl_private HenyeyGreensteinVolume *)
svc;
/* note that wi points towards the viewer and so is used negated */
*wo = phase_henyey_greenstein_sample(-sd->wi, volume->g, rand, pdf);
*eval = make_spectrum(*pdf); /* perfect importance sampling */
return LABEL_VOLUME_SCATTER;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,51 @@
/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/volume_util.h"
CCL_NAMESPACE_BEGIN
/* RAYLEIGH CLOSURE */
struct RayleighVolume {
SHADER_CLOSURE_VOLUME_BASE;
};
static_assert(sizeof(ShaderVolumeClosure) >= sizeof(RayleighVolume),
"RayleighVolume is too large!");
ccl_device int volume_rayleigh_setup(ccl_private RayleighVolume *volume)
{
volume->type = CLOSURE_VOLUME_RAYLEIGH_ID;
return SD_SCATTER;
}
ccl_device Spectrum volume_rayleigh_eval(const ccl_private ShaderData *sd,
const float3 wo,
ccl_private float *pdf)
{
/* note that wi points towards the viewer */
const float cos_theta = dot(-sd->wi, wo);
*pdf = phase_rayleigh(cos_theta);
return make_spectrum(*pdf);
}
ccl_device int volume_rayleigh_sample(const ccl_private ShaderData *sd,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
/* note that wi points towards the viewer and so is used negated */
*wo = phase_rayleigh_sample(-sd->wi, rand, pdf);
*eval = make_spectrum(*pdf); /* perfect importance sampling */
return LABEL_VOLUME_SCATTER;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,310 @@
/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "util/math_fast.h"
#include "util/projection.h"
CCL_NAMESPACE_BEGIN
/* Given a random number, sample a direction that makes an angle of theta with direction D. */
ccl_device float3 phase_sample_direction(const float3 D, const float cos_theta, const float rand)
{
const float phi = M_2PI_F * rand;
const float3 dir = spherical_cos_to_direction(cos_theta, phi);
float3 T;
float3 B;
make_orthonormals(D, &T, &B);
return to_global(dir, T, B, D);
}
/* Given cosine between rays, return probability density that a photon bounces
* to that direction. The g parameter controls how different it is from the
* uniform sphere. g=0 uniform diffuse-like, g=1 close to sharp single ray. */
ccl_device float phase_henyey_greenstein(const float cos_theta, const float g)
{
if (fabsf(g) < 1e-3f) {
return M_1_4PI_F;
}
const float fac = 1 + g * (g - 2 * cos_theta);
return (1 - sqr(g)) / (M_4PI_F * fac * safe_sqrtf(fac));
}
ccl_device float3 phase_henyey_greenstein_sample(const float3 D,
const float g,
const float2 rand,
ccl_private float *pdf)
{
float cos_theta = 1 - 2 * rand.x;
if (fabsf(g) >= 1e-3f) {
const float k = (1 - sqr(g)) / (1 - g * cos_theta);
cos_theta = (1 + sqr(g) - sqr(k)) / (2 * g);
}
*pdf = phase_henyey_greenstein(cos_theta, g);
return phase_sample_direction(D, cos_theta, rand.y);
}
/* Given cosine between rays, return probability density that a photon bounces to that direction
* according to the constant Rayleigh phase function.
* See https://doi.org/10.1364/JOSAA.28.002436 for details. */
ccl_device float phase_rayleigh(const float cos_theta)
{
return (0.1875f * M_1_PI_F) * (1.0f + sqr(cos_theta));
}
ccl_device float3 phase_rayleigh_sample(const float3 D, const float2 rand, ccl_private float *pdf)
{
const float a = 2 - 4 * rand.x;
/* Metal doesn't have cbrtf, but since we compute u - 1/u anyways, we can just as well
* use the inverse cube root for which there is a simple Quake-style fast implementation. */
const float inv_u = -fast_inv_cbrtf(sqrtf(1 + sqr(a)) + a);
const float cos_theta = 1 / inv_u - inv_u;
*pdf = phase_rayleigh(cos_theta);
return phase_sample_direction(D, cos_theta, rand.y);
}
/* Given cosine between rays, return probability density that a photon bounces to that direction
* according to the Draine phase function. This is a generalization of the Henyey-Greenstein
* function which bridges the cases of HG and Rayleigh scattering. The parameter g mainly controls
* the first moment <cos theta>, and alpha the second moment <cos2 theta> of the exact phase
* function. alpha=0 reduces to HG function, g=0, alpha=1 reduces to Rayleigh function, alpha=1
* reduces to Cornette-Shanks function.
* See https://doi.org/10.1086/379118 for details. */
ccl_device float phase_draine(const float cos_theta, const float g, float alpha)
{
/* Check special cases. */
if (fabsf(g) < 1e-3f && alpha > 0.999f) {
return phase_rayleigh(cos_theta);
}
if (fabsf(alpha) < 1e-3f) {
return phase_henyey_greenstein(cos_theta, g);
}
const float g2 = sqr(g);
const float fac = 1 + g2 - 2 * g * cos_theta;
return ((1 - g2) * (1 + alpha * sqr(cos_theta))) /
((1 + (alpha * (1 + 2 * g2)) * (1 / 3.0f)) * M_4PI_F * fac * sqrtf(fac));
}
/* Adapted from the HLSL code provided in https://research.nvidia.com/labs/rtr/approximate-mie/ */
ccl_device float phase_draine_sample_cos(const float g, const float alpha, const float rand)
{
if (fabsf(g) < 1e-2f) {
/* Special case to prevent division by zero.
* The sample technique is similar as in https://doi.org/10.1364/JOSAA.28.002436. */
const float inv_alpha = 1.0f / alpha;
const float b_2 = (3 + alpha) * inv_alpha * (0.5f - rand);
const float inv_u = -fast_inv_cbrtf(b_2 + sqrtf(sqr(b_2) + sqr(inv_alpha) * inv_alpha));
return 1 / inv_u - inv_u / alpha;
}
const float g2 = sqr(g);
const float g3 = g * g2;
const float g4 = sqr(g2);
const float g6 = g2 * g4;
const float pgp1_2 = sqr(1 + g2);
const float T1a = alpha * (g4 - 1);
const float T1a3 = sqr(T1a) * T1a;
const float T2 = -1296 * (g2 - 1) * (alpha - alpha * g2) * T1a * (4 * g2 + alpha * pgp1_2);
const float T9 = 2 + g2 + g3 * (1 + 2 * g2) * (2 * rand - 1);
const float T3 = 3 * g2 * (1 + g * (2 * rand - 1)) + alpha * T9;
const float T4a = 432 * T1a3 + T2 + 432 * (alpha * (1 - g2)) * sqr(T3);
const float T10 = alpha * (2 * g4 - g2 - g6);
const float T4b = 144 * T10;
const float T4b3 = sqr(T4b) * T4b;
const float T4 = T4a + sqrtf(-4 * T4b3 + sqr(T4a));
const float inv_T4p3 = fast_inv_cbrtf(T4);
const float T8 = 48 * M_CBRT2_F * T10;
const float T6 = (2 * T1a + T8 * inv_T4p3 + 1 / (3 * M_CBRT2_F * inv_T4p3)) / (alpha * (1 - g2));
const float T5 = 6 * (1 + g2) + T6;
const float T7 = 6 * (1 + g2) - (8 * T3) / (alpha * (g2 - 1) * sqrtf(T5)) - T6;
return (1 + g2 - 0.25f * sqr(sqrtf(T7) - sqrtf(T5))) / (2 * g);
}
ccl_device float3 phase_draine_sample(
const float3 D, const float g, float alpha, const float2 rand, ccl_private float *pdf)
{
/* Check special cases. */
if (fabsf(g) < 1e-3f && alpha > 0.999f) {
return phase_rayleigh_sample(D, rand, pdf);
}
if (fabsf(alpha) < 1e-3f) {
return phase_henyey_greenstein_sample(D, g, rand, pdf);
}
const float cos_theta = phase_draine_sample_cos(g, alpha, rand.x);
*pdf = phase_draine(cos_theta, g, alpha);
return phase_sample_direction(D, cos_theta, rand.y);
}
ccl_device float phase_fournier_forand_delta(const float n, const float sin_htheta_sqr)
{
const float u = 4 * sin_htheta_sqr;
return u / (3 * sqr(n - 1));
}
ccl_device_inline float3 phase_fournier_forand_coeffs(const float B, const float IOR)
{
const float d90 = phase_fournier_forand_delta(IOR, 0.5f);
const float d180 = phase_fournier_forand_delta(IOR, 1.0f);
const float v = -logf(2 * B * (d90 - 1) + 1) / logf(d90);
return make_float3(IOR, v, (powf(d180, -v) - 1) / (d180 - 1));
}
/* Given cosine between rays, return probability density that a photon bounces to that direction
* according to the Fournier-Forand phase function. The n parameter is the particle index of
* refraction and controls how much of the light is refracted. B is the particle backscatter
* fraction, B = b_b / b.
* See https://doi.org/10.1117/12.366488 for details. */
ccl_device_inline float phase_fournier_forand_impl(float cos_theta,
const float delta,
const float pow_delta_v,
const float v,
float sin_htheta_sqr,
const float pf_coeff)
{
const float m_delta = 1 - delta;
const float m_pow_delta_v = 1 - pow_delta_v;
float pf;
if (fabsf(m_delta) < 1e-3f) {
/* Special case (first-order Taylor expansion) to avoid singularity at delta near 1.0 */
pf = v * ((v - 1) - (v + 1) / sin_htheta_sqr) * (1 / (8 * M_PI_F));
pf += v * (v + 1) * m_delta * (2 * (v - 1) - (2 * v + 1) / sin_htheta_sqr) *
(1 / (24 * M_PI_F));
}
else {
pf = (v * m_delta - m_pow_delta_v + (delta * m_pow_delta_v - v * m_delta) / sin_htheta_sqr) /
(M_4PI_F * sqr(m_delta) * pow_delta_v);
}
pf += pf_coeff * (3 * sqr(cos_theta) - 1);
return pf;
}
ccl_device float phase_fournier_forand(const float cos_theta, const float3 coeffs)
{
if (fabsf(cos_theta) >= 1.0f) {
return 0.0f;
}
const float n = coeffs.x;
const float v = coeffs.y;
const float pf_coeff = coeffs.z * (1.0f / (16.0f * M_PI_F));
const float sin_htheta_sqr = 0.5f * (1 - cos_theta); /* `sin^2(theta / 2)`. */
const float delta = phase_fournier_forand_delta(n, sin_htheta_sqr);
return phase_fournier_forand_impl(cos_theta, delta, powf(delta, v), v, sin_htheta_sqr, pf_coeff);
}
ccl_device float phase_fournier_forand_newton(const float rand, const float3 coeffs)
{
const float n = coeffs.x;
const float v = coeffs.y;
const float cdf_coeff = coeffs.z * (1.0f / 8.0f);
const float pf_coeff = coeffs.z * (1.0f / (16.0f * M_PI_F));
float cos_theta = 0.64278760968f; /* Initial guess: 50 degrees */
for (int it = 0; it < 20; it++) {
const float sin_htheta_sqr = 0.5f * (1 - cos_theta); /* `sin^2(theta / 2)`. */
const float delta = phase_fournier_forand_delta(n, sin_htheta_sqr);
const float pow_delta_v = powf(delta, v);
const float m_delta = 1 - delta;
const float m_pow_delta_v = 1 - pow_delta_v;
/* Evaluate CDF and phase functions */
float cdf;
if (fabsf(m_delta) < 1e-3f) {
/* Special case (first-order Taylor expansion) to avoid singularity at delta near 1.0 */
cdf = 1 + v * (1 - sin_htheta_sqr) * (1 - 0.5f * (v + 1) * m_delta);
}
else {
cdf = (1 - pow_delta_v * delta - m_pow_delta_v * sin_htheta_sqr) / (m_delta * pow_delta_v);
}
cdf += cdf_coeff * cos_theta * (1 - sqr(cos_theta));
const float pf = phase_fournier_forand_impl(
cos_theta, delta, pow_delta_v, v, sin_htheta_sqr, pf_coeff);
/* Perform Newton iteration step */
float new_cos_theta = cos_theta + M_1_2PI_F * (cdf - rand) / pf;
/* Don't step off past 1.0, approach the peak slowly */
if (new_cos_theta >= 1.0f) {
new_cos_theta = max(mix(cos_theta, 1.0f, 0.5f), 0.99f);
}
if (fabsf(cos_theta - new_cos_theta) < 1e-6f || new_cos_theta == 1.0f) {
return new_cos_theta;
}
cos_theta = new_cos_theta;
}
/* Reached iteration limit, so give up and use what we have. */
return cos_theta;
}
ccl_device float3 phase_fournier_forand_sample(const float3 D,
const float3 coeffs,
const float2 rand,
ccl_private float *pdf)
{
const float cos_theta = phase_fournier_forand_newton(rand.x, coeffs);
*pdf = phase_fournier_forand(cos_theta, coeffs);
return phase_sample_direction(D, cos_theta, rand.y);
}
/* We approximate the Mie phase function for water droplets with diameters 0 < d < 50 um using a
* mixture of Draine and Henyey-Greenstein, following
* "An Approximate Mie Scattering Function for Fog and Cloud Rendering (Supplemental)"
* https://research.nvidia.com/labs/rtr/approximate-mie/publications/approximate-mie-supplemental.pdf
* For d > 1, the phase function is strong forward-scattering. For d very close to 0, the phase
* function is a mixture of Henyey-Greenstein and Rayleigh.
*/
ccl_device void phase_mie_fitted_parameters(float d,
ccl_private float *g_HG,
ccl_private float *g_D,
ccl_private float *alpha,
ccl_private float *w)
{
d = fmaxf(d, 0.0f);
if (d <= 0.1f) {
/* Eq (11 - 14). */
*g_HG = 13.8f * sqr(d);
*g_D = 1.1456f * d * fast_sinf(9.29044f * d);
*alpha = 250.0f;
*w = 0.252977f - 312.983f * powf(d, 4.3f);
}
else if (d < 1.5f) {
/* Eq (15 - 18). */
const float log_d = fast_logf(d);
*g_HG = 0.862f - 0.143f * sqr(log_d);
const float a = (log_d - 0.238604f) * (log_d + 1.00667f);
const float b = 0.507522f - 0.15677f * log_d;
const float c = 1.19692f * fast_cosf(a / b) + 1.37932f * log_d + 0.0625835f;
*g_D = 0.379685f * fast_cosf(c) + 0.344213f;
*alpha = 250.0f;
*w = 0.146209f * fast_cosf(3.38707f * log_d + 2.11193f) + 0.316072f + 0.0778917f * log_d;
}
else if (d < 5.0f) {
/* Eq (19 - 22). */
const float log_d = fast_logf(d);
*g_HG = 0.0604931f * fast_logf(log_d) + 0.940256f;
*g_D = 0.500411f - (0.081287f / (-2.0f * log_d + fast_tanf(log_d) + 1.27551f));
*alpha = 7.30354f * log_d + 6.31675f;
const float temp = fast_cosf(5.68947f * (fast_logf(log_d) - 0.0292149f));
*w = 0.026914f * (log_d - temp) + 0.3764f;
}
else {
/* Eq (7 - 10). */
*g_HG = fast_expf(-0.0990567f / (d - 1.67154f));
*g_D = fast_expf(-2.20679f / (d + 3.91029f) - 0.428934f);
*alpha = fast_expf(3.62489f - 8.29288f / (d + 5.52825f));
*w = fast_expf(-0.599085f / (d - 0.641583f) - 0.665888f);
}
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,100 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "kernel/types.h"
#ifndef KERNEL_DATA_ARRAY
# define KERNEL_DATA_ARRAY(type, name)
#endif
#ifndef KERNEL_DATA_ARRAY_WRITABLE
# define KERNEL_DATA_ARRAY_WRITABLE(type, name) KERNEL_DATA_ARRAY(type, name)
#endif
/* BVH2, not used for OptiX or Embree. */
KERNEL_DATA_ARRAY(float4, bvh_nodes)
KERNEL_DATA_ARRAY(float4, bvh_leaf_nodes)
KERNEL_DATA_ARRAY(uint, prim_type)
KERNEL_DATA_ARRAY(uint, prim_visibility)
KERNEL_DATA_ARRAY(uint, prim_index)
KERNEL_DATA_ARRAY(uint, prim_object)
KERNEL_DATA_ARRAY(uint, object_node)
KERNEL_DATA_ARRAY(float2, prim_time)
/* objects */
KERNEL_DATA_ARRAY(KernelObject, objects)
KERNEL_DATA_ARRAY(Transform, object_motion_pass)
KERNEL_DATA_ARRAY(DecomposedTransform, object_motion)
KERNEL_DATA_ARRAY(uint, object_flag)
KERNEL_DATA_ARRAY(uint, object_prim_offset)
/* cameras */
KERNEL_DATA_ARRAY(DecomposedTransform, camera_motion)
/* triangles */
KERNEL_DATA_ARRAY(uint, tri_shader)
KERNEL_DATA_ARRAY(packed_uint3, tri_vindex)
KERNEL_DATA_ARRAY(packed_float3, tri_verts)
/* curves */
KERNEL_DATA_ARRAY(KernelCurve, curves)
KERNEL_DATA_ARRAY(float4, curve_keys)
KERNEL_DATA_ARRAY(KernelCurveSegment, curve_segments)
/* pointclouds */
KERNEL_DATA_ARRAY(float4, points)
KERNEL_DATA_ARRAY(uint, points_shader)
/* attributes */
KERNEL_DATA_ARRAY(AttributeMap, attributes_map)
KERNEL_DATA_ARRAY(float, attributes_float)
KERNEL_DATA_ARRAY(float2, attributes_float2)
KERNEL_DATA_ARRAY(packed_float3, attributes_float3)
KERNEL_DATA_ARRAY(float4, attributes_float4)
KERNEL_DATA_ARRAY(uchar4, attributes_uchar4)
KERNEL_DATA_ARRAY(packed_normal, attributes_normal)
/* lights */
KERNEL_DATA_ARRAY(KernelLightDistribution, light_distribution)
KERNEL_DATA_ARRAY(KernelLight, lights)
KERNEL_DATA_ARRAY(float2, light_background_marginal_cdf)
KERNEL_DATA_ARRAY(float2, light_background_conditional_cdf)
/* light tree */
KERNEL_DATA_ARRAY(KernelLightTreeNode, light_tree_nodes)
KERNEL_DATA_ARRAY(KernelLightTreeEmitter, light_tree_emitters)
KERNEL_DATA_ARRAY(uint, light_to_tree)
KERNEL_DATA_ARRAY(uint, object_lookup_offset)
KERNEL_DATA_ARRAY(uint, triangle_to_tree)
/* particles */
KERNEL_DATA_ARRAY(KernelParticle, particles)
/* shaders */
KERNEL_DATA_ARRAY(uint, svm_nodes)
KERNEL_DATA_ARRAY(KernelShader, shaders)
/* lookup tables */
KERNEL_DATA_ARRAY(float, lookup_table)
/* tabulated Sobol sample pattern */
KERNEL_DATA_ARRAY(float, sample_pattern_lut)
/* ies lights */
KERNEL_DATA_ARRAY(float, ies)
/* Volume. */
KERNEL_DATA_ARRAY(KernelOctreeNode, volume_tree_nodes)
KERNEL_DATA_ARRAY(KernelOctreeRoot, volume_tree_roots)
KERNEL_DATA_ARRAY(int, volume_tree_root_ids)
KERNEL_DATA_ARRAY(float, volume_step_size)
/* image textures */
KERNEL_DATA_ARRAY(KernelImageTexture, image_textures)
KERNEL_DATA_ARRAY_WRITABLE(KernelTileDescriptor, image_texture_tile_descriptors)
KERNEL_DATA_ARRAY_WRITABLE(uint8_t, image_texture_tile_access_state)
KERNEL_DATA_ARRAY(KernelImageUDIM, image_texture_udims)
KERNEL_DATA_ARRAY(KernelImageInfo, image_info)
#undef KERNEL_DATA_ARRAY
#undef KERNEL_DATA_ARRAY_WRITABLE

View File

@@ -0,0 +1,265 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifndef KERNEL_STRUCT_BEGIN
# define KERNEL_STRUCT_BEGIN(name, parent)
#endif
#ifndef KERNEL_STRUCT_END
# define KERNEL_STRUCT_END(name)
#endif
#ifndef KERNEL_STRUCT_MEMBER
# define KERNEL_STRUCT_MEMBER(parent, type, name)
#endif
#ifndef KERNEL_STRUCT_MEMBER_DONT_SPECIALIZE
# define KERNEL_STRUCT_MEMBER_DONT_SPECIALIZE
#endif
/* Background. */
KERNEL_STRUCT_BEGIN(KernelBackground, background)
/* xyz store direction, w the angle. float4 instead of float3 is used
* to ensure consistent padding/alignment across devices. */
KERNEL_STRUCT_MEMBER(background, float4, sun)
KERNEL_STRUCT_MEMBER(background, int, use_sun_guiding)
/* Only shader index. */
KERNEL_STRUCT_MEMBER(background, int, surface_shader)
KERNEL_STRUCT_MEMBER(background, int, volume_shader)
KERNEL_STRUCT_MEMBER(background, int, transparent)
KERNEL_STRUCT_MEMBER(background, float, transparent_roughness_squared_threshold)
/* Sun sampling. */
KERNEL_STRUCT_MEMBER(background, float, sun_weight)
/* Importance map sampling. */
KERNEL_STRUCT_MEMBER(background, float, map_weight)
KERNEL_STRUCT_MEMBER(background, float, portal_weight)
KERNEL_STRUCT_MEMBER(background, int, map_res_x)
KERNEL_STRUCT_MEMBER(background, int, map_res_y)
/* Ray differential used for generating the importance map. */
KERNEL_STRUCT_MEMBER(background, float, map_dD)
/* Multiple importance sampling. */
KERNEL_STRUCT_MEMBER(background, int, use_mis)
/* Light-group. */
KERNEL_STRUCT_MEMBER(background, int, lightgroup)
/* Object Index. */
KERNEL_STRUCT_MEMBER(background, int, object_index)
/* Padding. */
KERNEL_STRUCT_MEMBER(background, int, pad1)
KERNEL_STRUCT_END(KernelBackground)
/* BVH: own BVH2 if no native device acceleration struct used. */
KERNEL_STRUCT_BEGIN(KernelBVH, bvh)
KERNEL_STRUCT_MEMBER(bvh, int, root)
KERNEL_STRUCT_MEMBER(bvh, int, have_motion)
KERNEL_STRUCT_MEMBER(bvh, int, have_curves)
KERNEL_STRUCT_MEMBER(bvh, int, have_points)
KERNEL_STRUCT_MEMBER(bvh, int, have_volumes)
KERNEL_STRUCT_MEMBER(bvh, int, bvh_layout)
KERNEL_STRUCT_MEMBER(bvh, int, use_bvh_steps)
KERNEL_STRUCT_MEMBER(bvh, int, curve_subdivisions)
KERNEL_STRUCT_END(KernelBVH)
/* Film. */
KERNEL_STRUCT_BEGIN(KernelFilm, film)
/* XYZ to rendering color space transform. float4 instead of float3 to
* ensure consistent padding/alignment across devices. */
KERNEL_STRUCT_MEMBER(film, float4, xyz_to_r)
KERNEL_STRUCT_MEMBER(film, float4, xyz_to_g)
KERNEL_STRUCT_MEMBER(film, float4, xyz_to_b)
KERNEL_STRUCT_MEMBER(film, float4, rgb_to_y)
KERNEL_STRUCT_MEMBER(film, float4, white_xyz)
/* Rec709 to rendering color space. */
KERNEL_STRUCT_MEMBER(film, float4, rec709_to_r)
KERNEL_STRUCT_MEMBER(film, float4, rec709_to_g)
KERNEL_STRUCT_MEMBER(film, float4, rec709_to_b)
KERNEL_STRUCT_MEMBER(film, int, is_rec709)
/* Exposure. */
KERNEL_STRUCT_MEMBER(film, float, exposure)
/* Passed used. */
KERNEL_STRUCT_MEMBER(film, int, pass_flag)
KERNEL_STRUCT_MEMBER(film, int, denoising_pass_flag)
KERNEL_STRUCT_MEMBER(film, int, light_pass_flag)
/* Pass offsets. */
KERNEL_STRUCT_MEMBER(film, int, pass_stride)
KERNEL_STRUCT_MEMBER(film, int, pass_combined)
KERNEL_STRUCT_MEMBER(film, int, pass_depth)
KERNEL_STRUCT_MEMBER(film, int, pass_position)
KERNEL_STRUCT_MEMBER(film, int, pass_normal)
KERNEL_STRUCT_MEMBER(film, int, pass_roughness)
KERNEL_STRUCT_MEMBER(film, int, pass_motion)
KERNEL_STRUCT_MEMBER(film, int, pass_motion_weight)
KERNEL_STRUCT_MEMBER(film, int, pass_uv)
KERNEL_STRUCT_MEMBER(film, int, pass_object_id)
KERNEL_STRUCT_MEMBER(film, int, pass_material_id)
KERNEL_STRUCT_MEMBER(film, int, pass_diffuse_color)
KERNEL_STRUCT_MEMBER(film, int, pass_glossy_color)
KERNEL_STRUCT_MEMBER(film, int, pass_transmission_color)
KERNEL_STRUCT_MEMBER(film, int, pass_diffuse_indirect)
KERNEL_STRUCT_MEMBER(film, int, pass_glossy_indirect)
KERNEL_STRUCT_MEMBER(film, int, pass_transmission_indirect)
KERNEL_STRUCT_MEMBER(film, int, pass_volume_indirect)
KERNEL_STRUCT_MEMBER(film, int, pass_diffuse_direct)
KERNEL_STRUCT_MEMBER(film, int, pass_glossy_direct)
KERNEL_STRUCT_MEMBER(film, int, pass_transmission_direct)
KERNEL_STRUCT_MEMBER(film, int, pass_volume_direct)
KERNEL_STRUCT_MEMBER(film, int, pass_volume_scatter)
KERNEL_STRUCT_MEMBER(film, int, pass_volume_scatter_denoised)
KERNEL_STRUCT_MEMBER(film, int, pass_volume_transmit)
KERNEL_STRUCT_MEMBER(film, int, pass_volume_transmit_denoised)
KERNEL_STRUCT_MEMBER(film, int, pass_volume_majorant)
KERNEL_STRUCT_MEMBER(film, int, pass_volume_majorant_sample_count)
KERNEL_STRUCT_MEMBER(film, int, pass_emission)
KERNEL_STRUCT_MEMBER(film, int, pass_background)
KERNEL_STRUCT_MEMBER(film, int, pass_ao)
KERNEL_STRUCT_MEMBER(film, float, pass_alpha_threshold)
KERNEL_STRUCT_MEMBER(film, int, pass_shadow_catcher)
KERNEL_STRUCT_MEMBER(film, int, pass_shadow_catcher_sample_count)
KERNEL_STRUCT_MEMBER(film, int, pass_shadow_catcher_matte)
KERNEL_STRUCT_MEMBER(film, int, pass_render_time)
/* Cryptomatte. */
KERNEL_STRUCT_MEMBER(film, int, cryptomatte_passes)
KERNEL_STRUCT_MEMBER(film, int, cryptomatte_depth)
KERNEL_STRUCT_MEMBER(film, int, pass_cryptomatte)
/* Adaptive sampling. */
KERNEL_STRUCT_MEMBER(film, int, pass_adaptive_aux_buffer)
KERNEL_STRUCT_MEMBER(film, int, pass_sample_count)
/* Mist. */
KERNEL_STRUCT_MEMBER(film, int, pass_mist)
KERNEL_STRUCT_MEMBER(film, float, mist_start)
KERNEL_STRUCT_MEMBER(film, float, mist_inv_depth)
KERNEL_STRUCT_MEMBER(film, float, mist_falloff)
/* Denoising. */
KERNEL_STRUCT_MEMBER(film, int, pass_denoising_albedo)
KERNEL_STRUCT_MEMBER(film, int, pass_denoising_specular_albedo)
KERNEL_STRUCT_MEMBER(film, int, pass_denoising_normal)
KERNEL_STRUCT_MEMBER(film, int, pass_denoising_roughness)
KERNEL_STRUCT_MEMBER(film, int, pass_denoising_depth)
KERNEL_STRUCT_MEMBER(film, int, pass_denoising_backward_motion)
KERNEL_STRUCT_MEMBER(film, int, denoising_pass_options_flag)
/* AOVs. */
KERNEL_STRUCT_MEMBER(film, int, pass_aov_color)
KERNEL_STRUCT_MEMBER(film, int, pass_aov_value)
/* Light groups. */
KERNEL_STRUCT_MEMBER(film, int, pass_lightgroup)
/* Baking. */
KERNEL_STRUCT_MEMBER(film, int, pass_bake_primitive)
KERNEL_STRUCT_MEMBER(film, int, pass_bake_seed)
KERNEL_STRUCT_MEMBER(film, int, pass_bake_differential)
/* Shadow catcher. */
KERNEL_STRUCT_MEMBER(film, int, use_approximate_shadow_catcher)
/* Path Guiding */
KERNEL_STRUCT_MEMBER(film, int, pass_guiding_color)
KERNEL_STRUCT_MEMBER(film, int, pass_guiding_probability)
KERNEL_STRUCT_MEMBER(film, int, pass_guiding_avg_roughness)
/* Padding. */
KERNEL_STRUCT_MEMBER(film, int, pad1)
KERNEL_STRUCT_END(KernelFilm)
/* Integrator. */
KERNEL_STRUCT_BEGIN(KernelIntegrator, integrator)
/* Emission. */
KERNEL_STRUCT_MEMBER(integrator, int, use_direct_light)
KERNEL_STRUCT_MEMBER(integrator, int, use_light_mis)
KERNEL_STRUCT_MEMBER(integrator, int, use_light_tree)
KERNEL_STRUCT_MEMBER(integrator, int, num_lights)
KERNEL_STRUCT_MEMBER(integrator, int, num_distant_lights)
KERNEL_STRUCT_MEMBER(integrator, int, num_background_lights)
/* Portal sampling. */
KERNEL_STRUCT_MEMBER(integrator, int, num_portals)
KERNEL_STRUCT_MEMBER(integrator, int, portal_offset)
/* Flat light distribution. */
KERNEL_STRUCT_MEMBER(integrator, int, num_distribution)
KERNEL_STRUCT_MEMBER(integrator, float, distribution_pdf_triangles)
KERNEL_STRUCT_MEMBER(integrator, float, distribution_pdf_lights)
KERNEL_STRUCT_MEMBER(integrator, float, light_inv_rr_threshold)
/* Bounces. */
KERNEL_STRUCT_MEMBER(integrator, int, min_bounce)
KERNEL_STRUCT_MEMBER(integrator, int, max_bounce)
KERNEL_STRUCT_MEMBER(integrator, int, max_diffuse_bounce)
KERNEL_STRUCT_MEMBER(integrator, int, max_glossy_bounce)
KERNEL_STRUCT_MEMBER(integrator, int, max_transmission_bounce)
KERNEL_STRUCT_MEMBER(integrator, int, max_volume_bounce)
/* AO bounces. */
KERNEL_STRUCT_MEMBER(integrator, int, ao_bounces)
KERNEL_STRUCT_MEMBER(integrator, float, ao_bounces_distance)
KERNEL_STRUCT_MEMBER(integrator, float, ao_bounces_factor)
KERNEL_STRUCT_MEMBER(integrator, float, ao_additive_factor)
/* Transparency. */
KERNEL_STRUCT_MEMBER(integrator, int, transparent_min_bounce)
KERNEL_STRUCT_MEMBER(integrator, int, transparent_max_bounce)
KERNEL_STRUCT_MEMBER(integrator, int, transparent_shadows)
/* Caustics. */
KERNEL_STRUCT_MEMBER(integrator, int, caustics_reflective)
KERNEL_STRUCT_MEMBER(integrator, int, caustics_refractive)
KERNEL_STRUCT_MEMBER(integrator, float, filter_glossy)
/* Seed. */
KERNEL_STRUCT_MEMBER_DONT_SPECIALIZE
KERNEL_STRUCT_MEMBER(integrator, int, seed)
/* Clamp. */
KERNEL_STRUCT_MEMBER(integrator, float, sample_clamp_direct)
KERNEL_STRUCT_MEMBER(integrator, float, sample_clamp_indirect)
/* Caustics. */
KERNEL_STRUCT_MEMBER(integrator, int, use_caustics)
/* Sampling pattern. */
KERNEL_STRUCT_MEMBER(integrator, int, sampling_pattern)
KERNEL_STRUCT_MEMBER(integrator, float, scrambling_distance)
/* Sobol pattern. */
KERNEL_STRUCT_MEMBER_DONT_SPECIALIZE
KERNEL_STRUCT_MEMBER(integrator, int, tabulated_sobol_sequence_size)
KERNEL_STRUCT_MEMBER_DONT_SPECIALIZE
KERNEL_STRUCT_MEMBER(integrator, int, sobol_index_mask)
KERNEL_STRUCT_MEMBER_DONT_SPECIALIZE
KERNEL_STRUCT_MEMBER(integrator, int, blue_noise_sequence_length)
/* Volume render. */
KERNEL_STRUCT_MEMBER(integrator, int, use_volumes)
KERNEL_STRUCT_MEMBER(integrator, int, volume_ray_marching)
KERNEL_STRUCT_MEMBER(integrator, int, volume_max_steps)
/* Shadow catcher. */
KERNEL_STRUCT_MEMBER(integrator, int, has_shadow_catcher)
/* Closure filter. */
KERNEL_STRUCT_MEMBER(integrator, int, filter_closures)
/* MIS debugging. */
KERNEL_STRUCT_MEMBER(integrator, int, direct_light_sampling_type)
/* Path Guiding */
KERNEL_STRUCT_MEMBER(integrator, float, surface_guiding_probability)
KERNEL_STRUCT_MEMBER(integrator, float, volume_guiding_probability)
KERNEL_STRUCT_MEMBER(integrator, int, guiding_distribution_type)
KERNEL_STRUCT_MEMBER(integrator, int, guiding_directional_sampling_type)
KERNEL_STRUCT_MEMBER(integrator, float, guiding_roughness_threshold)
KERNEL_STRUCT_MEMBER(integrator, int, use_guiding)
KERNEL_STRUCT_MEMBER(integrator, int, train_guiding)
KERNEL_STRUCT_MEMBER(integrator, int, use_surface_guiding)
KERNEL_STRUCT_MEMBER(integrator, int, use_volume_guiding)
KERNEL_STRUCT_MEMBER(integrator, int, use_guiding_direct_light)
KERNEL_STRUCT_MEMBER(integrator, int, use_guiding_mis_weights)
KERNEL_STRUCT_MEMBER(integrator, float2, pixel_jitter)
KERNEL_STRUCT_END(KernelIntegrator)
/* Image. */
KERNEL_STRUCT_BEGIN(KernelImage, image)
KERNEL_STRUCT_MEMBER(image, float, mip_bias)
/* Padding. */
KERNEL_STRUCT_MEMBER(image, int, pad1)
KERNEL_STRUCT_MEMBER(image, int, pad2)
KERNEL_STRUCT_MEMBER(image, int, pad3)
KERNEL_STRUCT_END(KernelImage)
/* SVM. For shader specialization. */
KERNEL_STRUCT_BEGIN(KernelSVMUsage, svm_usage)
#define SHADER_NODE_TYPE(type) KERNEL_STRUCT_MEMBER(svm_usage, int, type)
#define SHADER_NODE_TYPE_DERIVATIVE(type) \
SHADER_NODE_TYPE(type) \
SHADER_NODE_TYPE(type##_DERIVATIVE)
#include "kernel/svm/node_types_template.h"
KERNEL_STRUCT_END(KernelSVMUsage)
#undef KERNEL_STRUCT_BEGIN
#undef KERNEL_STRUCT_MEMBER
#undef KERNEL_STRUCT_MEMBER_DONT_SPECIALIZE
#undef KERNEL_STRUCT_END

View File

@@ -0,0 +1,70 @@
# SPDX-FileCopyrightText: 2011-2026 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
set(INC
../../..
)
set(INC_SYS
)
set(SRC_KERNEL_DEVICE_CPU
globals.cpp
kernel.cpp
kernel_avx2.cpp
)
set(SRC_KERNEL_DEVICE_CPU_HEADERS
bvh.h
compat.h
image.h
globals.h
kernel.h
kernel_arch.h
kernel_arch_impl.h
)
set(LIB
PUBLIC cycles_util
)
# -----------------------------------------------------------------------------
# CPU module.
include_directories(${INC})
include_directories(SYSTEM ${INC_SYS})
if(DEFINED CYCLES_KERNEL_FLAGS)
set_source_files_properties(kernel.cpp PROPERTIES COMPILE_FLAGS "${CYCLES_KERNEL_FLAGS}")
endif()
if(CXX_HAS_AVX2 AND CXX_HAS_F16C)
set_source_files_properties(kernel_avx2.cpp PROPERTIES COMPILE_FLAGS "${CYCLES_AVX2_F16C_FLAGS}")
endif()
# Warnings to avoid using doubles in the kernel.
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_C_COMPILER_ID MATCHES "Clang")
add_check_cxx_compiler_flags(
CMAKE_CXX_FLAGS
_has_cxxflag_float_conversion "-Werror=float-conversion"
_has_cxxflag_double_promotion "-Werror=double-promotion"
)
unset(_has_cxxflag_float_conversion)
unset(_has_cxxflag_double_promotion)
endif()
if(WITH_CYCLES_OSL)
list(APPEND LIB
PUBLIC cycles_kernel_osl
)
endif()
cycles_add_library(cycles_kernel_cpu "${LIB}"
${SRC_KERNEL_DEVICE_CPU}
${SRC_KERNEL_DEVICE_CPU_HEADERS}
)
cycles_set_solution_folder(cycles_kernel_cpu)
source_group("device\\cpu" FILES ${SRC_KERNEL_DEVICE_CPU} ${SRC_KERNEL_DEVICE_CPU_HEADERS})

View File

@@ -0,0 +1,660 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* CPU Embree implementation of ray-scene intersection. */
#pragma once
#include <embree4/rtcore_geometry.h>
#include <embree4/rtcore_ray.h>
#include <embree4/rtcore_scene.h>
#ifdef __KERNEL_ONEAPI__
# include "kernel/device/oneapi/compat.h"
# include "kernel/device/oneapi/globals.h"
#else
# include "kernel/device/cpu/compat.h"
# include "kernel/device/cpu/globals.h"
#endif
#include "kernel/bvh/intersect_filter.h"
#include "kernel/bvh/types.h"
#include "kernel/bvh/util.h"
#include "kernel/geom/object.h"
#include "kernel/integrator/state.h"
#include "kernel/integrator/state_util.h"
#include "kernel/sample/lcg.h"
CCL_NAMESPACE_BEGIN
#ifdef __KERNEL_ONEAPI__
using numhit_t = uint16_t;
#else
using numhit_t = uint32_t;
#endif
/* Before Embree 4.4, the so-called Traversable functionality was exposed through Scene API.
* So, in order to simplify code between different versions, we are defining the traversable class
* and calls for older Embree versions as well. */
#if RTC_VERSION < 40400
# define RTCTraversable RTCScene
# define rtcGetGeometryUserDataFromTraversable rtcGetGeometryUserDataFromScene
# define rtcTraversableIntersect1 rtcIntersect1
# define rtcTraversableOccluded1 rtcOccluded1
#endif
#ifdef __KERNEL_ONEAPI__
# define CYCLES_EMBREE_USED_FEATURES \
(kernel_handler.get_specialization_constant<oneapi_embree_features>())
#else
# define CYCLES_EMBREE_USED_FEATURES \
(RTCFeatureFlags)(RTC_FEATURE_FLAG_TRIANGLE | RTC_FEATURE_FLAG_INSTANCE | \
RTC_FEATURE_FLAG_FILTER_FUNCTION_IN_ARGUMENTS | RTC_FEATURE_FLAG_POINT | \
RTC_FEATURE_FLAG_MOTION_BLUR | RTC_FEATURE_FLAG_ROUND_CATMULL_ROM_CURVE | \
RTC_FEATURE_FLAG_FLAT_CATMULL_ROM_CURVE | \
RTC_FEATURE_FLAG_ROUND_LINEAR_CURVE)
#endif
#define EMBREE_IS_HAIR(x) (x & 1)
/* Intersection context. */
struct CCLFirstHitContext : public RTCRayQueryContext {
KernelGlobals kg;
/* For avoiding self intersections */
const Ray *ray;
};
struct CCLShadowContext : public RTCRayQueryContext {
#if defined(__KERNEL_ONEAPI__)
ONEAPIKernelContext *oneapi_kernel_context;
#else
KernelGlobals kg;
#endif
BVHShadowAllPayload *payload;
};
struct CCLLocalContext : public RTCRayQueryContext {
KernelGlobals kg;
const Ray *ray;
numhit_t max_hits;
int local_object_id;
LocalIntersection *local_isect;
uint *lcg_state;
bool is_sss;
};
struct CCLVolumeContext : public RTCRayQueryContext {
KernelGlobals kg;
const Ray *ray;
#ifdef __VOLUME_RECORD_ALL__
numhit_t max_hits;
#endif
numhit_t num_hits;
Intersection *vol_isect;
};
/* Utilities. */
ccl_device_inline void kernel_embree_setup_ray(const Ray &ray,
RTCRay &rtc_ray,
const uint visibility)
{
rtc_ray.org_x = ray.P.x;
rtc_ray.org_y = ray.P.y;
rtc_ray.org_z = ray.P.z;
rtc_ray.dir_x = ray.D.x;
rtc_ray.dir_y = ray.D.y;
rtc_ray.dir_z = ray.D.z;
rtc_ray.tnear = ray.tmin;
rtc_ray.tfar = ray.tmax;
rtc_ray.time = ray.time;
rtc_ray.mask = visibility;
}
ccl_device_inline void kernel_embree_setup_rayhit(const Ray &ray,
RTCRayHit &rayhit,
const uint visibility)
{
kernel_embree_setup_ray(ray, rayhit.ray, visibility);
rayhit.hit.geomID = RTC_INVALID_GEOMETRY_ID;
rayhit.hit.instID[0] = RTC_INVALID_GEOMETRY_ID;
}
ccl_device_inline int kernel_embree_get_hit_object(const RTCHit *hit)
{
return (hit->instID[0] != RTC_INVALID_GEOMETRY_ID ? hit->instID[0] : hit->geomID) / 2;
}
ccl_device_inline bool kernel_embree_is_self_intersection(const KernelGlobals kg,
const RTCHit *hit,
const Ray *ray,
const intptr_t prim_offset)
{
const int object = kernel_embree_get_hit_object(hit);
int prim;
if ((ray->self.object == object) || (ray->self.light_object == object)) {
prim = hit->primID + prim_offset;
}
else {
return false;
}
const bool is_hair = hit->geomID & 1;
if (is_hair) {
prim = kernel_data_fetch(curve_segments, prim).prim;
}
return intersection_skip_self_shadow(ray->self, object, prim);
}
ccl_device_inline void kernel_embree_convert_hit(KernelGlobals kg,
const RTCRay *ray,
const RTCHit *hit,
Intersection *isect,
const intptr_t prim_offset)
{
isect->t = ray->tfar;
isect->prim = hit->primID + prim_offset;
isect->object = kernel_embree_get_hit_object(hit);
const bool is_hair = hit->geomID & 1;
if (is_hair) {
const KernelCurveSegment segment = kernel_data_fetch(curve_segments, isect->prim);
isect->type = segment.type;
isect->prim = segment.prim;
isect->u = hit->u;
isect->v = hit->v;
}
else {
isect->type = kernel_data_fetch(objects, isect->object).primitive_type;
isect->u = hit->u;
isect->v = hit->v;
}
}
ccl_device_inline void kernel_embree_convert_hit(KernelGlobals kg,
const RTCRay *ray,
const RTCHit *hit,
Intersection *isect)
{
intptr_t prim_offset;
if (hit->instID[0] != RTC_INVALID_GEOMETRY_ID) {
RTCTraversable inst_scene = (RTCTraversable)rtcGetGeometryUserDataFromTraversable(
kernel_data.device_bvh, hit->instID[0]);
prim_offset = intptr_t(rtcGetGeometryUserDataFromTraversable(inst_scene, hit->geomID));
}
else {
prim_offset = intptr_t(
rtcGetGeometryUserDataFromTraversable(kernel_data.device_bvh, hit->geomID));
}
kernel_embree_convert_hit(kg, ray, hit, isect, prim_offset);
}
ccl_device_inline void kernel_embree_convert_sss_hit(KernelGlobals kg,
const RTCRay *ray,
const RTCHit *hit,
Intersection *isect,
const int object,
const intptr_t prim_offset)
{
isect->u = hit->u;
isect->v = hit->v;
isect->t = ray->tfar;
isect->prim = hit->primID + prim_offset;
isect->object = object;
isect->type = kernel_data_fetch(objects, object).primitive_type;
}
/* Ray filter functions. */
/* This gets called by Embree at every valid ray/object intersection.
* Things like recording subsurface or shadow hits for later evaluation
* as well as filtering for volume objects happen here.
* Cycles' own BVH does that directly inside the traversal calls. */
ccl_device_forceinline void kernel_embree_filter_intersection_func_impl(
const RTCFilterFunctionNArguments *args)
{
/* Current implementation in Cycles assumes only single-ray intersection queries. */
assert(args->N == 1);
RTCHit *hit = (RTCHit *)args->hit;
CCLFirstHitContext *ctx = (CCLFirstHitContext *)(args->context);
#ifdef __KERNEL_ONEAPI__
KernelGlobalsGPU *kg = nullptr;
#else
const ThreadKernelGlobalsCPU *kg = ctx->kg;
#endif
const Ray *cray = ctx->ray;
if (kernel_embree_is_self_intersection(
kg, hit, cray, reinterpret_cast<intptr_t>(args->geometryUserPtr)))
{
*args->valid = 0;
return;
}
#ifdef __SHADOW_LINKING__
if (intersection_skip_shadow_link(kg, cray->self, kernel_embree_get_hit_object(hit))) {
*args->valid = 0;
return;
}
#endif
}
/* This gets called by Embree at every valid ray/object intersection.
* Things like recording subsurface or shadow hits for later evaluation
* as well as filtering for volume objects happen here.
* Cycles' own BVH does that directly inside the traversal calls.
*/
ccl_device_forceinline void kernel_embree_filter_occluded_shadow_all_func_impl(
const RTCFilterFunctionNArguments *args)
{
/* Current implementation in Cycles assumes only single-ray intersection queries. */
assert(args->N == 1);
const RTCRay *ray = (RTCRay *)args->ray;
const RTCHit *hit = (RTCHit *)args->hit;
CCLShadowContext *ctx = (CCLShadowContext *)(args->context);
BVHShadowAllPayload &payload = *ctx->payload;
#ifdef __KERNEL_ONEAPI__
KernelGlobalsGPU *kg = nullptr;
#else
const ThreadKernelGlobalsCPU *kg = ctx->kg;
#endif
Intersection isect;
kernel_embree_convert_hit(
kg, ray, hit, &isect, reinterpret_cast<intptr_t>(args->geometryUserPtr));
if (!bvh_shadow_all_anyhit_filter<ISECT_TEST_ALL & ~ISECT_TEST_VISIBILITY_FLAG>(
kg, payload.state, payload, payload.base.ray_self, 0, isect))
{
return;
}
*args->valid = 0;
}
ccl_device_forceinline void kernel_embree_filter_occluded_local_func_impl(
const RTCFilterFunctionNArguments *args)
{
/* Current implementation in Cycles assumes only single-ray intersection queries. */
assert(args->N == 1);
const RTCRay *ray = (RTCRay *)args->ray;
RTCHit *hit = (RTCHit *)args->hit;
CCLLocalContext *ctx = (CCLLocalContext *)(args->context);
#ifdef __KERNEL_ONEAPI__
KernelGlobalsGPU *kg = nullptr;
#else
const ThreadKernelGlobalsCPU *kg = ctx->kg;
#endif
const Ray *cray = ctx->ray;
/* Check if it's hitting the correct object. */
Intersection current_isect;
if (ctx->is_sss) {
kernel_embree_convert_sss_hit(kg,
ray,
hit,
&current_isect,
ctx->local_object_id,
reinterpret_cast<intptr_t>(args->geometryUserPtr));
}
else {
kernel_embree_convert_hit(
kg, ray, hit, &current_isect, reinterpret_cast<intptr_t>(args->geometryUserPtr));
if (ctx->local_object_id != current_isect.object) {
/* This tells Embree to continue tracing. */
*args->valid = 0;
return;
}
}
if (intersection_skip_self_local(cray->self, current_isect.prim)) {
*args->valid = 0;
return;
}
/* No intersection information requested, just return a hit. */
if (ctx->max_hits == 0) {
return;
}
/* Ignore curves. */
if (EMBREE_IS_HAIR(hit->geomID)) {
/* This tells Embree to continue tracing. */
*args->valid = 0;
return;
}
LocalIntersection *local_isect = ctx->local_isect;
int hit_idx = 0;
if (ctx->lcg_state) {
/* See triangle_intersect_subsurface() for the native equivalent. */
for (int i = min((int)ctx->max_hits, local_isect->num_hits) - 1; i >= 0; --i) {
if (local_isect->hits[i].t == ray->tfar) {
/* This tells Embree to continue tracing. */
*args->valid = 0;
return;
}
}
local_isect->num_hits++;
if (local_isect->num_hits <= ctx->max_hits) {
hit_idx = local_isect->num_hits - 1;
}
else {
/* reservoir sampling: if we are at the maximum number of
* hits, randomly replace element or skip it */
hit_idx = lcg_step_uint(ctx->lcg_state) % local_isect->num_hits;
if (hit_idx >= ctx->max_hits) {
/* This tells Embree to continue tracing. */
*args->valid = 0;
return;
}
}
}
else {
/* Record closest intersection only. */
if (local_isect->num_hits && current_isect.t > local_isect->hits[0].t) {
*args->valid = 0;
return;
}
local_isect->num_hits = 1;
}
/* record intersection */
local_isect->hits[hit_idx] = current_isect;
local_isect->Ng[hit_idx] = normalize(make_float3(hit->Ng_x, hit->Ng_y, hit->Ng_z));
/* This tells Embree to continue tracing. */
*args->valid = 0;
}
ccl_device_forceinline void kernel_embree_filter_occluded_volume_all_func_impl(
const RTCFilterFunctionNArguments *args)
{
/* Current implementation in Cycles assumes only single-ray intersection queries. */
assert(args->N == 1);
const RTCRay *ray = (RTCRay *)args->ray;
RTCHit *hit = (RTCHit *)args->hit;
CCLVolumeContext *ctx = (CCLVolumeContext *)(args->context);
#ifdef __KERNEL_ONEAPI__
KernelGlobalsGPU *kg = nullptr;
#else
const ThreadKernelGlobalsCPU *kg = ctx->kg;
#endif
const Ray *cray = ctx->ray;
#ifdef __VOLUME_RECORD_ALL__
/* Append the intersection to the end of the array. */
if (ctx->num_hits < ctx->max_hits) {
#endif
Intersection current_isect;
kernel_embree_convert_hit(
kg, ray, hit, &current_isect, reinterpret_cast<intptr_t>(args->geometryUserPtr));
if (bvh_volume_anyhit_triangle_filter<false>(
kg, current_isect.object, current_isect.prim, cray->self, 0))
{
*args->valid = 0;
return;
}
Intersection *isect = &ctx->vol_isect[ctx->num_hits];
++ctx->num_hits;
*isect = current_isect;
#ifdef __VOLUME_RECORD_ALL__
/* This tells Embree to continue tracing. */
*args->valid = 0;
}
#endif
}
#ifdef __KERNEL_ONEAPI__
/* Static wrappers so we can call the callbacks from out side the ONEAPIKernelContext class */
RTC_SYCL_INDIRECTLY_CALLABLE static void ccl_always_inline
kernel_embree_filter_intersection_func_static(const RTCFilterFunctionNArguments *args)
{
RTCHit *hit = (RTCHit *)args->hit;
CCLFirstHitContext *ctx = (CCLFirstHitContext *)(args->context);
ONEAPIKernelContext *context = static_cast<ONEAPIKernelContext *>(ctx->kg);
context->kernel_embree_filter_intersection_func_impl(args);
}
RTC_SYCL_INDIRECTLY_CALLABLE static void ccl_always_inline
kernel_embree_filter_occluded_shadow_all_func_static(const RTCFilterFunctionNArguments *args)
{
RTCHit *hit = (RTCHit *)args->hit;
CCLShadowContext *ctx = (CCLShadowContext *)(args->context);
ONEAPIKernelContext *context = ctx->oneapi_kernel_context;
context->kernel_embree_filter_occluded_shadow_all_func_impl(args);
}
RTC_SYCL_INDIRECTLY_CALLABLE static void ccl_always_inline
kernel_embree_filter_occluded_local_func_static(const RTCFilterFunctionNArguments *args)
{
RTCHit *hit = (RTCHit *)args->hit;
CCLLocalContext *ctx = (CCLLocalContext *)(args->context);
ONEAPIKernelContext *context = static_cast<ONEAPIKernelContext *>(ctx->kg);
context->kernel_embree_filter_occluded_local_func_impl(args);
}
RTC_SYCL_INDIRECTLY_CALLABLE static void ccl_always_inline
kernel_embree_filter_occluded_volume_all_func_static(const RTCFilterFunctionNArguments *args)
{
RTCHit *hit = (RTCHit *)args->hit;
CCLVolumeContext *ctx = (CCLVolumeContext *)(args->context);
ONEAPIKernelContext *context = static_cast<ONEAPIKernelContext *>(ctx->kg);
context->kernel_embree_filter_occluded_volume_all_func_impl(args);
}
# define kernel_embree_filter_intersection_func \
ONEAPIKernelContext::kernel_embree_filter_intersection_func_static
# define kernel_embree_filter_occluded_shadow_all_func \
ONEAPIKernelContext::kernel_embree_filter_occluded_shadow_all_func_static
# define kernel_embree_filter_occluded_local_func \
ONEAPIKernelContext::kernel_embree_filter_occluded_local_func_static
# define kernel_embree_filter_occluded_volume_all_func \
ONEAPIKernelContext::kernel_embree_filter_occluded_volume_all_func_static
#else
# define kernel_embree_filter_intersection_func kernel_embree_filter_intersection_func_impl
# define kernel_embree_filter_occluded_shadow_all_func \
kernel_embree_filter_occluded_shadow_all_func_impl
# define kernel_embree_filter_occluded_local_func kernel_embree_filter_occluded_local_func_impl
# define kernel_embree_filter_occluded_volume_all_func \
kernel_embree_filter_occluded_volume_all_func_impl
#endif
/* Scene intersection. */
ccl_device_intersect bool kernel_embree_intersect(KernelGlobals kg,
const ccl_private Ray *ray,
const uint visibility,
ccl_private Intersection *isect)
{
isect->t = ray->tmax;
CCLFirstHitContext ctx;
rtcInitRayQueryContext(&ctx);
#ifdef __KERNEL_ONEAPI__
/* NOTE(sirgienko): Cycles GPU back-ends passes nullptr to KernelGlobals and
* uses global device allocation (CUDA, Optix, HIP) or passes all needed data
* as a class context (Metal, oneAPI). So we need to pass this context here
* in order to have an access to it later in Embree filter functions on GPU. */
ctx.kg = (KernelGlobals)this;
#else
ctx.kg = kg;
#endif
RTCRayHit ray_hit;
ctx.ray = ray;
kernel_embree_setup_rayhit(*ray, ray_hit, visibility);
RTCIntersectArguments args;
rtcInitIntersectArguments(&args);
args.filter = reinterpret_cast<RTCFilterFunctionN>(kernel_embree_filter_intersection_func);
args.feature_mask = CYCLES_EMBREE_USED_FEATURES;
args.context = &ctx;
rtcTraversableIntersect1(kernel_data.device_bvh, &ray_hit, &args);
if (ray_hit.hit.geomID == RTC_INVALID_GEOMETRY_ID ||
ray_hit.hit.primID == RTC_INVALID_GEOMETRY_ID)
{
return false;
}
kernel_embree_convert_hit(kg, &ray_hit.ray, &ray_hit.hit, isect);
return true;
}
#ifdef __BVH_LOCAL__
ccl_device_intersect bool kernel_embree_intersect_local(KernelGlobals kg,
const ccl_private Ray *ray,
ccl_private LocalIntersection *local_isect,
const int local_object,
ccl_private uint *lcg_state,
const int max_hits)
{
const bool has_bvh = !(kernel_data_fetch(object_flag, local_object) &
SD_OBJECT_TRANSFORM_APPLIED);
CCLLocalContext ctx;
rtcInitRayQueryContext(&ctx);
# ifdef __KERNEL_ONEAPI__
/* NOTE(sirgienko): Cycles GPU back-ends passes nullptr to KernelGlobals and
* uses global device allocation (CUDA, Optix, HIP) or passes all needed data
* as a class context (Metal, oneAPI). So we need to pass this context here
* in order to have an access to it later in Embree filter functions on GPU. */
ctx.kg = (KernelGlobals)this;
# else
ctx.kg = kg;
# endif
ctx.is_sss = has_bvh;
ctx.lcg_state = lcg_state;
ctx.max_hits = max_hits;
ctx.ray = ray;
ctx.local_isect = local_isect;
if (local_isect) {
local_isect->num_hits = 0;
}
ctx.local_object_id = local_object;
RTCRay rtc_ray;
kernel_embree_setup_ray(*ray, rtc_ray, PATH_RAY_VISIBILITY_ALL);
RTCOccludedArguments args;
rtcInitOccludedArguments(&args);
args.filter = reinterpret_cast<RTCFilterFunctionN>(kernel_embree_filter_occluded_local_func);
args.feature_mask = CYCLES_EMBREE_USED_FEATURES;
args.context = &ctx;
/* If this object has its own BVH, use it. */
if (has_bvh) {
float3 P = ray->P;
float3 dir = ray->D;
float3 idir = ray->D;
# ifdef __OBJECT_MOTION__
bvh_instance_motion_push(kg, local_object, ray, &P, &dir, &idir);
# else
bvh_instance_push(kg, local_object, ray, &P, &dir, &idir);
# endif
rtc_ray.org_x = P.x;
rtc_ray.org_y = P.y;
rtc_ray.org_z = P.z;
rtc_ray.dir_x = dir.x;
rtc_ray.dir_y = dir.y;
rtc_ray.dir_z = dir.z;
rtc_ray.tnear = ray->tmin;
rtc_ray.tfar = ray->tmax;
RTCTraversable scene = (RTCTraversable)rtcGetGeometryUserDataFromTraversable(
kernel_data.device_bvh, local_object * 2);
kernel_assert(scene);
if (scene) {
rtcTraversableOccluded1(scene, &rtc_ray, &args);
}
}
else {
rtcTraversableOccluded1(kernel_data.device_bvh, &rtc_ray, &args);
}
/* rtcOccluded1 sets tfar to -inf if a hit was found. */
return (local_isect && local_isect->num_hits > 0) || (rtc_ray.tfar < 0);
}
#endif
#ifdef __TRANSPARENT_SHADOWS__
ccl_device_intersect void kernel_embree_intersect_shadow_all(KernelGlobals kg,
const ccl_private Ray *ray,
BVHShadowAllPayload &payload)
{
CCLShadowContext ctx;
rtcInitRayQueryContext(&ctx);
# if defined(__KERNEL_ONEAPI__)
ctx.oneapi_kernel_context = this;
# else
ctx.kg = kg;
# endif
ctx.payload = &payload;
RTCRay rtc_ray;
kernel_embree_setup_ray(*ray, rtc_ray, payload.base.ray_visibility);
RTCOccludedArguments args;
rtcInitOccludedArguments(&args);
args.filter = reinterpret_cast<RTCFilterFunctionN>(
kernel_embree_filter_occluded_shadow_all_func);
args.feature_mask = CYCLES_EMBREE_USED_FEATURES;
args.context = &ctx;
rtcTraversableOccluded1(kernel_data.device_bvh, &rtc_ray, &args);
}
#endif
#ifdef __VOLUME__
ccl_device_intersect uint kernel_embree_intersect_volume(KernelGlobals kg,
const ccl_private Ray *ray,
ccl_private Intersection *isect,
# ifdef __VOLUME_RECORD_ALL__
const uint max_hits,
# endif
const uint visibility)
{
CCLVolumeContext ctx;
rtcInitRayQueryContext(&ctx);
# ifdef __KERNEL_ONEAPI__
/* NOTE(sirgienko) Cycles GPU back-ends passes nullptr to KernelGlobals and
* uses global device allocation (CUDA, Optix, HIP) or passes all needed data
* as a class context (Metal, oneAPI). So we need to pass this context here
* in order to have an access to it later in Embree filter functions on GPU. */
ctx.kg = (KernelGlobals)this;
# else
ctx.kg = kg;
# endif
ctx.vol_isect = isect;
# ifdef __VOLUME_RECORD_ALL__
ctx.max_hits = numhit_t(max_hits);
# endif
ctx.num_hits = numhit_t(0);
ctx.ray = ray;
RTCRay rtc_ray;
kernel_embree_setup_ray(*ray, rtc_ray, visibility);
RTCOccludedArguments args;
rtcInitOccludedArguments(&args);
args.filter = reinterpret_cast<RTCFilterFunctionN>(
kernel_embree_filter_occluded_volume_all_func);
args.feature_mask = CYCLES_EMBREE_USED_FEATURES;
args.context = &ctx;
rtcTraversableOccluded1(kernel_data.device_bvh, &rtc_ray, &args);
return ctx.num_hits;
}
#endif
CCL_NAMESPACE_END

View File

@@ -0,0 +1,22 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
/* Release kernel has too much false-positive maybe-uninitialized warnings,
* which makes it possible to miss actual warnings.
*/
#if (defined(__GNUC__) && !defined(__clang__)) && defined(NDEBUG)
# pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
# pragma GCC diagnostic ignored "-Wuninitialized"
#endif
CCL_NAMESPACE_BEGIN
/* Assertions inside the kernel only work for the CPU device, so we wrap it in
* a macro which is empty for other devices */
#define kernel_assert(cond) assert(cond)
CCL_NAMESPACE_END

View File

@@ -0,0 +1,43 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "kernel/device/cpu/globals.h"
#include "kernel/osl/globals.h"
#include "util/guiding.h" // IWYU pragma: keep
#include "util/profiling.h"
CCL_NAMESPACE_BEGIN
ThreadKernelGlobalsCPU::ThreadKernelGlobalsCPU(const KernelGlobalsCPU &kernel_globals,
OSLGlobals *osl_globals,
Profiler &cpu_profiler,
const int thread_index)
: KernelGlobalsCPU(kernel_globals),
#ifdef WITH_OSL
osl(osl_globals, thread_index),
#endif
cpu_profiler_(cpu_profiler)
{
#ifndef WITH_OSL
(void)thread_index;
(void)osl_globals;
#endif
#if defined(WITH_PATH_GUIDING)
opgl_path_segment_storage = make_unique<openpgl::cpp::PathSegmentStorage>();
#endif
}
void ThreadKernelGlobalsCPU::start_profiling()
{
cpu_profiler_.add_state(&profiler);
}
void ThreadKernelGlobalsCPU::stop_profiling()
{
cpu_profiler_.remove_state(&profiler);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,113 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* Constant Globals */
#pragma once
#include "kernel/types.h"
#include "kernel/util/profiler.h"
#ifdef __OSL__
# include "kernel/osl/globals.h"
#endif
#include "util/guiding.h" // IWYU pragma: keep
#include "util/types_image.h" // IWYU pragma: keep
#include "util/unique_ptr.h"
CCL_NAMESPACE_BEGIN
struct OSLGlobals;
/* On the CPU, we pass along the struct KernelGlobals to nearly everywhere in
* the kernel, to access constant data. These are all stored as flat arrays.
* these are really just standard arrays. We can't use actually globals because
* multiple renders may be running inside the same process. */
/* Array for kernel data, with size to be able to assert on invalid data access. */
template<typename T> struct kernel_array {
const ccl_always_inline T &fetch(const int index) const
{
kernel_assert(index >= 0 && index < width);
return data[index];
}
ccl_always_inline void write(const int index, const T &value) const
{
data[index] = value;
}
T *data = nullptr;
int width = 0;
};
/* Constant globals shared between all threads. */
struct KernelGlobalsCPU {
#define KERNEL_DATA_ARRAY(type, name) kernel_array<const type> name;
#define KERNEL_DATA_ARRAY_WRITABLE(type, name) kernel_array<type> name;
#include "kernel/data_arrays.h"
KernelData data = {};
KernelImageLoadRequestedCPU image_load_requested_cpu;
ProfilingState profiler;
};
/* Per-thread global state.
*
* To avoid pointer indirection, the constant globals are copied to each thread.
*
* This may not be ideal for cache pressure. Alternative would be to pass an
* additional thread index to every function, and potentially to make the shared
* part an actual global variable. That would match the GPU more closely, but
* also require mutex locks for multiple Cycles instances. */
struct ThreadKernelGlobalsCPU : public KernelGlobalsCPU {
ThreadKernelGlobalsCPU(const KernelGlobalsCPU &kernel_globals,
OSLGlobals *osl_globals_memory,
Profiler &cpu_profiler,
const int thread_index);
ThreadKernelGlobalsCPU(ThreadKernelGlobalsCPU &other) = delete;
ThreadKernelGlobalsCPU(ThreadKernelGlobalsCPU &&other) noexcept = default;
ThreadKernelGlobalsCPU &operator=(const ThreadKernelGlobalsCPU &other) = delete;
ThreadKernelGlobalsCPU &operator=(ThreadKernelGlobalsCPU &&other) = delete;
void start_profiling();
void stop_profiling();
#ifdef __OSL__
OSLThreadData osl;
#endif
#if defined(__PATH_GUIDING__)
/* Pointers to shared global data structures. */
openpgl::cpp::SampleStorage *opgl_sample_data_storage = nullptr;
openpgl::cpp::Field *opgl_guiding_field = nullptr;
/* Local data structures owned by the thread. */
unique_ptr<openpgl::cpp::PathSegmentStorage> opgl_path_segment_storage;
unique_ptr<openpgl::cpp::SurfaceSamplingDistribution> opgl_surface_sampling_distribution;
unique_ptr<openpgl::cpp::VolumeSamplingDistribution> opgl_volume_sampling_distribution;
#endif
protected:
Profiler &cpu_profiler_;
};
using KernelGlobals = const ThreadKernelGlobalsCPU *;
/* Abstraction macros */
#define kernel_data_fetch(name, index) (kg->name.fetch(index))
#define kernel_data_write(name, index, value) (kg->name.write(index, value))
#define kernel_data_array(name) (kg->name.data)
#define kernel_data (kg->data)
#if defined(WITH_PATH_GUIDING)
# define guiding_guiding_field kg->opgl_guiding_field
# define guiding_ssd kg->opgl_surface_sampling_distribution
# define guiding_vsd kg->opgl_volume_sampling_distribution
#endif
CCL_NAMESPACE_END

View File

@@ -0,0 +1,418 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/device/cpu/compat.h"
#include "kernel/device/cpu/globals.h"
#include "kernel/util/image_2d.h"
#include "util/defines.h"
#include "util/half.h"
#include "util/types_image.h"
CCL_NAMESPACE_BEGIN
/* Make template functions private so symbols don't conflict between kernels with different
* instruction sets. */
namespace {
#define SET_CUBIC_SPLINE_WEIGHTS(u, t) \
{ \
u[0] = (((-1.0f / 6.0f) * t + 0.5f) * t - 0.5f) * t + (1.0f / 6.0f); \
u[1] = ((0.5f * t - 1.0f) * t) * t + (2.0f / 3.0f); \
u[2] = ((-0.5f * t + 0.5f) * t + 0.5f) * t + (1.0f / 6.0f); \
u[3] = (1.0f / 6.0f) * t * t * t; \
} \
(void)0
ccl_device_inline float frac(const float x, int *ix)
{
int i = float_to_int(x) - ((x < 0.0f) ? 1 : 0);
*ix = i;
return x - (float)i;
}
template<typename TexT, typename OutT = float4> struct ImageInterpolator {
static ccl_always_inline OutT zero()
{
if constexpr (std::is_same_v<OutT, float4>) {
return zero_float4();
}
else {
return 0.0f;
}
}
static ccl_always_inline float4 read(const float4 r)
{
return r;
}
static ccl_always_inline float4 read(const uchar4 r)
{
const float f = 1.0f / 255.0f;
return make_float4(r.x * f, r.y * f, r.z * f, r.w * f);
}
static ccl_always_inline float read(const uchar r)
{
return r * (1.0f / 255.0f);
}
static ccl_always_inline float read(const float r)
{
return r;
}
static ccl_always_inline float4 read(half4 r)
{
return half4_to_float4_image(r);
}
static ccl_always_inline float read(half r)
{
return half_to_float_image(r);
}
static ccl_always_inline float read(const uint16_t r)
{
return r * (1.0f / 65535.0f);
}
static ccl_always_inline float4 read(ushort4 r)
{
const float f = 1.0f / 65535.0f;
return make_float4(r.x * f, r.y * f, r.z * f, r.w * f);
}
/* Read 2D Texture Data
* Does not check if data request is in bounds. */
static ccl_always_inline OutT
read(const TexT *data, const int x, int y, const int width, const int /*height*/)
{
return read(data[y * width + x]);
}
/* Read 2D Texture Data Clip
* Returns transparent black if data request is out of bounds. */
static ccl_always_inline OutT
read_clip(const TexT *data, const int x, int y, const int width, const int height)
{
if (x < 0 || x >= width || y < 0 || y >= height) {
return zero();
}
return read(data[y * width + x]);
}
static ccl_always_inline int wrap_periodic(int x, const int width)
{
x %= width;
if (x < 0) {
x += width;
}
return x;
}
static ccl_always_inline int wrap_clamp(const int x, const int width)
{
return clamp(x, 0, width - 1);
}
static ccl_always_inline int wrap_mirror(const int x, const int width)
{
const int m = abs(x + (x < 0)) % (2 * width);
if (m >= width) {
return 2 * width - m - 1;
}
return m;
}
/* ******** 2D interpolation ******** */
static ccl_always_inline OutT interp_closest(const KernelImageInfo &info, const float x, float y)
{
const int width = info.width;
const int height = info.height;
int ix, iy;
frac(x, &ix);
frac(y, &iy);
switch (info.extension) {
case EXTENSION_REPEAT:
ix = wrap_periodic(ix, width);
iy = wrap_periodic(iy, height);
break;
case EXTENSION_CLIP:
/* No samples are inside the clip region. */
if (ix < 0 || ix >= width || iy < 0 || iy >= height) {
return zero();
}
break;
case EXTENSION_EXTEND:
ix = wrap_clamp(ix, width);
iy = wrap_clamp(iy, height);
break;
case EXTENSION_MIRROR:
ix = wrap_mirror(ix, width);
iy = wrap_mirror(iy, height);
break;
default:
kernel_assert(0);
return zero();
}
const TexT *data = (const TexT *)info.data;
return read(data, ix, iy, width, height);
}
static ccl_always_inline OutT interp_linear(const KernelImageInfo &info, const float x, float y)
{
const int width = info.width;
const int height = info.height;
/* A -0.5 offset is used to center the linear samples around the sample point. */
int ix, iy;
int nix, niy;
const float tx = frac(x - 0.5f, &ix);
const float ty = frac(y - 0.5f, &iy);
const TexT *data = (const TexT *)info.data;
switch (info.extension) {
case EXTENSION_REPEAT:
ix = wrap_periodic(ix, width);
nix = wrap_periodic(ix + 1, width);
iy = wrap_periodic(iy, height);
niy = wrap_periodic(iy + 1, height);
break;
case EXTENSION_CLIP:
/* No linear samples are inside the clip region. */
if (ix < -1 || ix >= width || iy < -1 || iy >= height) {
return zero();
}
nix = ix + 1;
niy = iy + 1;
return (1.0f - ty) * (1.0f - tx) * read_clip(data, ix, iy, width, height) +
(1.0f - ty) * tx * read_clip(data, nix, iy, width, height) +
ty * (1.0f - tx) * read_clip(data, ix, niy, width, height) +
ty * tx * read_clip(data, nix, niy, width, height);
case EXTENSION_EXTEND:
nix = wrap_clamp(ix + 1, width);
ix = wrap_clamp(ix, width);
niy = wrap_clamp(iy + 1, height);
iy = wrap_clamp(iy, height);
break;
case EXTENSION_MIRROR:
nix = wrap_mirror(ix + 1, width);
ix = wrap_mirror(ix, width);
niy = wrap_mirror(iy + 1, height);
iy = wrap_mirror(iy, height);
break;
default:
kernel_assert(0);
return zero();
}
return (1.0f - ty) * (1.0f - tx) * read(data, ix, iy, width, height) +
(1.0f - ty) * tx * read(data, nix, iy, width, height) +
ty * (1.0f - tx) * read(data, ix, niy, width, height) +
ty * tx * read(data, nix, niy, width, height);
}
static ccl_always_inline OutT interp_cubic(const KernelImageInfo &info, const float x, float y)
{
const int width = info.width;
const int height = info.height;
/* A -0.5 offset is used to center the cubic samples around the sample point. */
int ix, iy;
const float tx = frac(x - 0.5f, &ix);
const float ty = frac(y - 0.5f, &iy);
int pix, piy;
int nix, niy;
int nnix, nniy;
switch (info.extension) {
case EXTENSION_REPEAT:
ix = wrap_periodic(ix, width);
pix = wrap_periodic(ix - 1, width);
nix = wrap_periodic(ix + 1, width);
nnix = wrap_periodic(ix + 2, width);
iy = wrap_periodic(iy, height);
piy = wrap_periodic(iy - 1, height);
niy = wrap_periodic(iy + 1, height);
nniy = wrap_periodic(iy + 2, height);
break;
case EXTENSION_CLIP:
/* No cubic samples are inside the clip region. */
if (ix < -2 || ix > width || iy < -2 || iy > height) {
return zero();
}
pix = ix - 1;
nix = ix + 1;
nnix = ix + 2;
piy = iy - 1;
niy = iy + 1;
nniy = iy + 2;
break;
case EXTENSION_EXTEND:
pix = wrap_clamp(ix - 1, width);
nix = wrap_clamp(ix + 1, width);
nnix = wrap_clamp(ix + 2, width);
ix = wrap_clamp(ix, width);
piy = wrap_clamp(iy - 1, height);
niy = wrap_clamp(iy + 1, height);
nniy = wrap_clamp(iy + 2, height);
iy = wrap_clamp(iy, height);
break;
case EXTENSION_MIRROR:
pix = wrap_mirror(ix - 1, width);
nix = wrap_mirror(ix + 1, width);
nnix = wrap_mirror(ix + 2, width);
ix = wrap_mirror(ix, width);
piy = wrap_mirror(iy - 1, height);
niy = wrap_mirror(iy + 1, height);
nniy = wrap_mirror(iy + 2, height);
iy = wrap_mirror(iy, height);
break;
default:
kernel_assert(0);
return zero();
}
const TexT *data = (const TexT *)info.data;
const int xc[4] = {pix, ix, nix, nnix};
const int yc[4] = {piy, iy, niy, nniy};
float u[4], v[4];
/* Some helper macros to keep code size reasonable.
* Lets the compiler inline all the matrix multiplications.
*/
#define DATA(x, y) (read_clip(data, xc[x], yc[y], width, height))
#define TERM(col) \
(v[col] * \
(u[0] * DATA(0, col) + u[1] * DATA(1, col) + u[2] * DATA(2, col) + u[3] * DATA(3, col)))
SET_CUBIC_SPLINE_WEIGHTS(u, tx);
SET_CUBIC_SPLINE_WEIGHTS(v, ty);
/* Actual interpolation. */
return TERM(0) + TERM(1) + TERM(2) + TERM(3);
#undef TERM
#undef DATA
}
static ccl_always_inline OutT interp(const KernelImageInfo &info, const float x, float y)
{
switch (info.interpolation) {
case INTERPOLATION_CLOSEST:
return interp_closest(info, x, y);
case INTERPOLATION_LINEAR:
return interp_linear(info, x, y);
default:
return interp_cubic(info, x, y);
}
}
};
#undef SET_CUBIC_SPLINE_WEIGHTS
ccl_device float4 kernel_image_interp(KernelGlobals kg,
ShaderData *sd,
const int image_texture_id,
dual2 uv)
{
if (image_texture_id == KERNEL_IMAGE_NONE) {
return IMAGE_MISSING_RGBA;
}
const ccl_global KernelImageTexture &tex = kernel_data_fetch(image_textures, image_texture_id);
const ccl_global KernelImageInfo *info;
float2 xy = zero_float2();
if (tex.tile_descriptor_offset != UINT_MAX) {
/* Wrapping. */
if (!kernel_image_tile_wrap(ExtensionType(tex.extension), uv.val)) {
return zero_float4();
}
/* Tile mapping */
const KernelTileDescriptor tile_descriptor = kernel_image_tile_map(
kg, sd, tex, image_texture_id, uv, xy);
if (!kernel_tile_descriptor_loaded(tile_descriptor)) {
return (tile_descriptor == KERNEL_TILE_LOAD_FAILED) ? IMAGE_MISSING_RGBA : tex.average_color;
}
info = &kernel_data_fetch(image_info, kernel_tile_descriptor_image_info_id(tile_descriptor));
}
else {
/* Full image sampling. */
if (tex.image_info_id == KERNEL_IMAGE_NONE) {
return IMAGE_MISSING_RGBA;
}
/* Convert to pixel space. */
info = &kernel_data_fetch(image_info, tex.image_info_id);
xy = make_float2(uv.val.x * info->width, uv.val.y * info->height);
}
if (UNLIKELY(!info->data)) {
return zero_float4();
}
switch (info->data_type) {
case IMAGE_DATA_TYPE_HALF: {
const float f = ImageInterpolator<half, float>::interp(*info, xy.x, xy.y);
return make_float4(f, f, f, 1.0f);
}
case IMAGE_DATA_TYPE_BYTE: {
const float f = ImageInterpolator<uchar, float>::interp(*info, xy.x, xy.y);
return make_float4(f, f, f, 1.0f);
}
case IMAGE_DATA_TYPE_USHORT: {
const float f = ImageInterpolator<uint16_t, float>::interp(*info, xy.x, xy.y);
return make_float4(f, f, f, 1.0f);
}
case IMAGE_DATA_TYPE_FLOAT: {
const float f = ImageInterpolator<float, float>::interp(*info, xy.x, xy.y);
return make_float4(f, f, f, 1.0f);
}
case IMAGE_DATA_TYPE_HALF4:
return ImageInterpolator<half4>::interp(*info, xy.x, xy.y);
case IMAGE_DATA_TYPE_BYTE4:
return ImageInterpolator<uchar4>::interp(*info, xy.x, xy.y);
case IMAGE_DATA_TYPE_USHORT4:
return ImageInterpolator<ushort4>::interp(*info, xy.x, xy.y);
case IMAGE_DATA_TYPE_FLOAT4:
return ImageInterpolator<float4>::interp(*info, xy.x, xy.y);
default:
assert(0);
return IMAGE_MISSING_RGBA;
}
}
ccl_device_forceinline float4 kernel_image_interp_with_udim(KernelGlobals kg,
ShaderData *sd,
const int udim_id,
dual2 uv)
{
const int image_texture_id = kernel_image_udim_map(kg, udim_id, uv.val);
if (image_texture_id == KERNEL_IMAGE_NONE) {
return IMAGE_MISSING_RGBA;
}
return kernel_image_interp(kg, sd, image_texture_id, uv);
}
} /* Namespace. */
CCL_NAMESPACE_END

View File

@@ -0,0 +1,86 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* CPU kernel entry points */
/* On x86-64, our minimum is SSE4.2, so avoid the extra kernel and compile this
* one with SSE4.2 intrinsics.
*/
#if defined(__x86_64__) || defined(_M_X64)
# define __KERNEL_SSE__
# define __KERNEL_SSE2__
# define __KERNEL_SSE3__
# define __KERNEL_SSSE3__
# define __KERNEL_SSE42__
#endif
/* When building kernel for native machine detect kernel features from the flags
* set by compiler.
*/
#ifdef WITH_KERNEL_NATIVE
# ifdef __SSE4_2__
# ifndef __KERNEL_SSE42__
# define __KERNEL_SSE42__
# endif
# endif
# ifdef __AVX__
# ifndef __KERNEL_SSE__
# define __KERNEL_SSE__
# endif
# define __KERNEL_AVX__
# endif
# ifdef __AVX2__
# ifndef __KERNEL_SSE__
# define __KERNEL_SSE__
# endif
# define __KERNEL_AVX2__
# endif
#endif
/* quiet unused define warnings */
#if defined(__KERNEL_SSE2__)
/* do nothing */
#endif
#include "kernel/device/cpu/globals.h"
#include "kernel/device/cpu/kernel.h"
#define KERNEL_ARCH cpu
#include "kernel/device/cpu/kernel_arch_impl.h"
CCL_NAMESPACE_BEGIN
/* Memory Copy */
void kernel_const_copy(KernelGlobalsCPU *kg, const char *name, void *host, size_t /*unused*/)
{
if (strcmp(name, "data") == 0) {
kg->data = *(KernelData *)host;
}
else {
assert(0);
}
}
void kernel_global_memory_copy(KernelGlobalsCPU *kg,
const char *name,
void *mem,
const size_t size)
{
if (false) {
}
#define KERNEL_DATA_ARRAY(type, tname) \
else if (strcmp(name, #tname) == 0) { \
kg->tname.data = (type *)mem; \
kg->tname.width = size; \
}
#include "kernel/data_arrays.h"
else {
assert(0);
}
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,41 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
/* CPU Kernel Interface */
#include "kernel/types.h"
#include "util/half.h"
CCL_NAMESPACE_BEGIN
#define KERNEL_NAME_JOIN(x, y, z) x##_##y##_##z
#define KERNEL_NAME_EVAL(arch, name) KERNEL_NAME_JOIN(kernel, arch, name)
#define KERNEL_FUNCTION_FULL_NAME(name) KERNEL_NAME_EVAL(KERNEL_ARCH, name)
struct IntegratorStateCPU;
struct KernelGlobalsCPU;
struct KernelData;
KernelGlobalsCPU *kernel_globals_create();
void kernel_globals_free(KernelGlobalsCPU *kg);
void *kernel_osl_memory(const KernelGlobalsCPU *kg);
bool kernel_osl_use(const KernelGlobalsCPU *kg);
void kernel_const_copy(KernelGlobalsCPU *kg, const char *name, void *host, const size_t size);
void kernel_global_memory_copy(KernelGlobalsCPU *kg,
const char *name,
void *mem,
const size_t size);
#define KERNEL_ARCH cpu
#include "kernel/device/cpu/kernel_arch.h"
#define KERNEL_ARCH cpu_avx2
#include "kernel/device/cpu/kernel_arch.h"
CCL_NAMESPACE_END

View File

@@ -0,0 +1,148 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* Templated common declaration part of all CPU kernels. */
/* --------------------------------------------------------------------
* Integrator.
*/
#define KERNEL_INTEGRATOR_FUNCTION(name) \
void KERNEL_FUNCTION_FULL_NAME(integrator_##name)( \
const ThreadKernelGlobalsCPU *ccl_restrict kg, IntegratorStateCPU *state)
#define KERNEL_INTEGRATOR_SHADE_FUNCTION(name) \
void KERNEL_FUNCTION_FULL_NAME(integrator_##name)( \
const ThreadKernelGlobalsCPU *ccl_restrict kg, \
IntegratorStateCPU *state, \
ccl_global float *render_buffer)
#define KERNEL_INTEGRATOR_INIT_FUNCTION(name) \
bool KERNEL_FUNCTION_FULL_NAME(integrator_##name)( \
const ThreadKernelGlobalsCPU *ccl_restrict kg, \
IntegratorStateCPU *state, \
KernelWorkTile *tile, \
ccl_global float *render_buffer)
KERNEL_INTEGRATOR_INIT_FUNCTION(init_from_camera);
KERNEL_INTEGRATOR_INIT_FUNCTION(init_from_bake);
KERNEL_INTEGRATOR_SHADE_FUNCTION(megakernel);
#undef KERNEL_INTEGRATOR_FUNCTION
#undef KERNEL_INTEGRATOR_INIT_FUNCTION
#undef KERNEL_INTEGRATOR_SHADE_FUNCTION
#define KERNEL_FILM_CONVERT_FUNCTION(name) \
void KERNEL_FUNCTION_FULL_NAME(film_convert_##name)(const KernelFilmConvert *kfilm_convert, \
const float *buffer, \
float *pixel, \
const int width, \
const int buffer_stride, \
const int pixel_stride); \
void KERNEL_FUNCTION_FULL_NAME(film_convert_half_rgba_##name)( \
const KernelFilmConvert *kfilm_convert, \
const float *buffer, \
half4 *pixel, \
const int width, \
const int buffer_stride);
KERNEL_FILM_CONVERT_FUNCTION(depth)
KERNEL_FILM_CONVERT_FUNCTION(mist)
KERNEL_FILM_CONVERT_FUNCTION(sample_count)
KERNEL_FILM_CONVERT_FUNCTION(volume_majorant)
KERNEL_FILM_CONVERT_FUNCTION(float)
KERNEL_FILM_CONVERT_FUNCTION(light_path)
KERNEL_FILM_CONVERT_FUNCTION(rgbe)
KERNEL_FILM_CONVERT_FUNCTION(float3)
KERNEL_FILM_CONVERT_FUNCTION(motion)
KERNEL_FILM_CONVERT_FUNCTION(cryptomatte)
KERNEL_FILM_CONVERT_FUNCTION(shadow_catcher)
KERNEL_FILM_CONVERT_FUNCTION(shadow_catcher_matte_with_shadow)
KERNEL_FILM_CONVERT_FUNCTION(combined)
KERNEL_FILM_CONVERT_FUNCTION(float4)
#undef KERNEL_FILM_CONVERT_FUNCTION
/* --------------------------------------------------------------------
* Shader evaluation.
*/
void KERNEL_FUNCTION_FULL_NAME(shader_eval_background)(const ThreadKernelGlobalsCPU *kg,
const KernelShaderEvalInput *input,
float *output,
const int offset);
void KERNEL_FUNCTION_FULL_NAME(shader_eval_displace)(const ThreadKernelGlobalsCPU *kg,
const KernelShaderEvalInput *input,
float *output,
const int offset);
void KERNEL_FUNCTION_FULL_NAME(shader_eval_curve_shadow_transparency)(
const ThreadKernelGlobalsCPU *kg,
const KernelShaderEvalInput *input,
float *output,
const int offset);
void KERNEL_FUNCTION_FULL_NAME(shader_eval_volume_density)(const ThreadKernelGlobalsCPU *kg,
const KernelShaderEvalInput *input,
float *output,
const int offset);
/* --------------------------------------------------------------------
* Adaptive sampling.
*/
bool KERNEL_FUNCTION_FULL_NAME(adaptive_sampling_convergence_check)(
const ThreadKernelGlobalsCPU *kg,
ccl_global float *render_buffer,
const int x,
const int y,
const float threshold,
const int reset,
const int offset,
int stride);
void KERNEL_FUNCTION_FULL_NAME(adaptive_sampling_filter_x)(const ThreadKernelGlobalsCPU *kg,
ccl_global float *render_buffer,
const int y,
const int start_x,
const int width,
const int offset,
int stride);
void KERNEL_FUNCTION_FULL_NAME(adaptive_sampling_filter_y)(const ThreadKernelGlobalsCPU *kg,
ccl_global float *render_buffer,
const int x,
const int start_y,
const int height,
const int offset,
int stride);
/* --------------------------------------------------------------------
* Cryptomatte.
*/
void KERNEL_FUNCTION_FULL_NAME(cryptomatte_postprocess)(const ThreadKernelGlobalsCPU *kg,
ccl_global float *render_buffer,
int pixel_index);
/* --------------------------------------------------------------------
* Volume Scattering Probability Guiding.
*/
void KERNEL_FUNCTION_FULL_NAME(volume_guiding_filter_x)(const ThreadKernelGlobalsCPU *kg,
ccl_global float *render_buffer,
const int y,
const int center_x,
const int min_x,
const int max_x,
const int offset,
int stride);
void KERNEL_FUNCTION_FULL_NAME(volume_guiding_filter_y)(const ThreadKernelGlobalsCPU *kg,
ccl_global float *render_buffer,
const int x,
const int center_y,
const int height,
const int offset,
int stride);
#undef KERNEL_ARCH

View File

@@ -0,0 +1,401 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* Templated common implementation part of all CPU kernels.
*
* The idea is that particular `.cpp` files sets needed optimization flags and
* simply includes this file without worry of copying actual implementation over.
*/
#pragma once
// clang-format off
#include "kernel/device/cpu/compat.h"
#ifndef KERNEL_STUB
# include "kernel/globals.h"
# include "kernel/device/cpu/image.h"
# include "kernel/integrator/state.h"
# include "kernel/integrator/state_flow.h"
# include "kernel/integrator/state_util.h"
# include "kernel/integrator/init_from_camera.h"
# include "kernel/integrator/init_from_bake.h"
# include "kernel/integrator/megakernel.h"
# include "kernel/film/adaptive_sampling.h"
# include "kernel/film/cryptomatte_passes.h"
# include "kernel/film/read.h"
# include "kernel/film/volume_guiding_denoise.h"
# include "kernel/bake/bake.h"
#else
# define STUB_ASSERT(arch, name) \
assert(!(#name " kernel stub for architecture " #arch " was called!"))
#endif /* KERNEL_STUB */
// clang-format on
CCL_NAMESPACE_BEGIN
/* --------------------------------------------------------------------
* Integrator.
*/
#ifdef KERNEL_STUB
# define KERNEL_INVOKE(name, ...) (STUB_ASSERT(KERNEL_ARCH, name), 0)
#else
# define KERNEL_INVOKE(name, ...) integrator_##name(__VA_ARGS__)
#endif
/* TODO: Either use something like get_work_pixel(), or simplify tile which is passed here, so
* that it does not contain unused fields. */
#define DEFINE_INTEGRATOR_INIT_KERNEL(name) \
bool KERNEL_FUNCTION_FULL_NAME(integrator_##name)(const ThreadKernelGlobalsCPU *kg, \
IntegratorStateCPU *state, \
KernelWorkTile *tile, \
ccl_global float *render_buffer) \
{ \
(void)kg; \
(void)state; \
(void)tile; \
(void)render_buffer; \
return KERNEL_INVOKE( \
name, kg, state, tile, render_buffer, tile->x, tile->y, tile->start_sample); \
}
#define DEFINE_INTEGRATOR_SHADE_KERNEL(name) \
void KERNEL_FUNCTION_FULL_NAME(integrator_##name)(const ThreadKernelGlobalsCPU *kg, \
IntegratorStateCPU *state, \
ccl_global float *render_buffer) \
{ \
(void)kg; \
(void)state; \
(void)render_buffer; \
KERNEL_INVOKE(name, kg, state, render_buffer); \
}
DEFINE_INTEGRATOR_INIT_KERNEL(init_from_camera)
DEFINE_INTEGRATOR_INIT_KERNEL(init_from_bake)
DEFINE_INTEGRATOR_SHADE_KERNEL(megakernel)
/* --------------------------------------------------------------------
* Shader evaluation.
*/
void KERNEL_FUNCTION_FULL_NAME(shader_eval_displace)(const ThreadKernelGlobalsCPU *kg,
const KernelShaderEvalInput *input,
float *output,
const int offset)
{
#ifdef KERNEL_STUB
STUB_ASSERT(KERNEL_ARCH, shader_eval_displace);
(void)kg;
(void)input;
(void)output;
(void)offset;
#else
uint cache_miss_unused = false;
kernel_displace_evaluate(kg, input, output, &cache_miss_unused, offset);
#endif
}
void KERNEL_FUNCTION_FULL_NAME(shader_eval_background)(const ThreadKernelGlobalsCPU *kg,
const KernelShaderEvalInput *input,
float *output,
const int offset)
{
#ifdef KERNEL_STUB
STUB_ASSERT(KERNEL_ARCH, shader_eval_background);
(void)kg;
(void)input;
(void)output;
(void)offset;
#else
uint cache_miss_unused = false;
kernel_background_evaluate(kg, input, output, &cache_miss_unused, offset);
#endif
}
void KERNEL_FUNCTION_FULL_NAME(shader_eval_curve_shadow_transparency)(
const ThreadKernelGlobalsCPU *kg,
const KernelShaderEvalInput *input,
float *output,
const int offset)
{
#ifdef KERNEL_STUB
STUB_ASSERT(KERNEL_ARCH, shader_eval_curve_shadow_transparency);
(void)kg;
(void)input;
(void)output;
(void)offset;
#else
uint cache_miss_unused = false;
kernel_curve_shadow_transparency_evaluate(kg, input, output, &cache_miss_unused, offset);
#endif
}
void KERNEL_FUNCTION_FULL_NAME(shader_eval_volume_density)(const ThreadKernelGlobalsCPU *kg,
const KernelShaderEvalInput *input,
float *output,
const int offset)
{
#ifdef KERNEL_STUB
STUB_ASSERT(KERNEL_ARCH, shader_eval_volume_density);
(void)kg;
(void)input;
(void)output;
(void)offset;
#else
uint cache_miss_unused = false;
kernel_volume_density_evaluate(kg, input, output, &cache_miss_unused, offset);
#endif
}
/* --------------------------------------------------------------------
* Adaptive sampling.
*/
bool KERNEL_FUNCTION_FULL_NAME(adaptive_sampling_convergence_check)(
const ThreadKernelGlobalsCPU *kg,
ccl_global float *render_buffer,
const int x,
const int y,
const float threshold,
const int reset,
const int offset,
const int stride)
{
#ifdef KERNEL_STUB
STUB_ASSERT(KERNEL_ARCH, adaptive_sampling_convergence_check);
(void)kg;
(void)render_buffer;
(void)x;
(void)y;
(void)threshold;
(void)reset;
(void)offset;
(void)stride;
return false;
#else
return film_adaptive_sampling_convergence_check(
kg, render_buffer, x, y, threshold, reset, offset, stride);
#endif
}
void KERNEL_FUNCTION_FULL_NAME(adaptive_sampling_filter_x)(const ThreadKernelGlobalsCPU *kg,
ccl_global float *render_buffer,
const int y,
const int start_x,
const int width,
const int offset,
const int stride)
{
#ifdef KERNEL_STUB
STUB_ASSERT(KERNEL_ARCH, adaptive_sampling_filter_x);
(void)kg;
(void)render_buffer;
(void)y;
(void)start_x;
(void)width;
(void)offset;
(void)stride;
#else
film_adaptive_sampling_filter_x(kg, render_buffer, y, start_x, width, offset, stride);
#endif
}
void KERNEL_FUNCTION_FULL_NAME(adaptive_sampling_filter_y)(const ThreadKernelGlobalsCPU *kg,
ccl_global float *render_buffer,
const int x,
const int start_y,
const int height,
const int offset,
const int stride)
{
#ifdef KERNEL_STUB
STUB_ASSERT(KERNEL_ARCH, adaptive_sampling_filter_y);
(void)kg;
(void)render_buffer;
(void)x;
(void)start_y;
(void)height;
(void)offset;
(void)stride;
#else
film_adaptive_sampling_filter_y(kg, render_buffer, x, start_y, height, offset, stride);
#endif
}
/* --------------------------------------------------------------------
* Cryptomatte.
*/
void KERNEL_FUNCTION_FULL_NAME(cryptomatte_postprocess)(const ThreadKernelGlobalsCPU *kg,
ccl_global float *render_buffer,
const int pixel_index)
{
#ifdef KERNEL_STUB
STUB_ASSERT(KERNEL_ARCH, cryptomatte_postprocess);
(void)kg;
(void)render_buffer;
(void)pixel_index;
#else
film_cryptomatte_post(kg, render_buffer, pixel_index);
#endif
}
/* --------------------------------------------------------------------
* Volume Scattering Probability Guiding.
*/
void KERNEL_FUNCTION_FULL_NAME(volume_guiding_filter_x)(const ThreadKernelGlobalsCPU *kg,
ccl_global float *render_buffer,
const int y,
const int center_x,
const int min_x,
const int max_x,
const int offset,
const int stride)
{
#ifdef KERNEL_STUB
STUB_ASSERT(KERNEL_ARCH, volume_guiding_filter_x);
(void)kg;
(void)render_buffer;
(void)y;
(void)center_x;
(void)min_x;
(void)max_x;
(void)offset;
(void)stride;
#else
volume_guiding_filter_x(kg, render_buffer, y, center_x, min_x, max_x, offset, stride);
#endif
}
void KERNEL_FUNCTION_FULL_NAME(volume_guiding_filter_y)(const ThreadKernelGlobalsCPU *kg,
ccl_global float *render_buffer,
const int x,
const int min_y,
const int max_y,
const int offset,
const int stride)
{
#ifdef KERNEL_STUB
STUB_ASSERT(KERNEL_ARCH, volume_guiding_filter_y);
(void)kg;
(void)render_buffer;
(void)x;
(void)min_y;
(void)max_y;
(void)offset;
(void)stride;
#else
volume_guiding_filter_y(kg, render_buffer, x, min_y, max_y, offset, stride);
#endif
}
/* --------------------------------------------------------------------
* Film Convert.
*/
#ifdef KERNEL_STUB
# define KERNEL_FILM_CONVERT_FUNCTION(name, is_float) \
void KERNEL_FUNCTION_FULL_NAME(film_convert_##name)(const KernelFilmConvert *kfilm_convert, \
const float *buffer, \
float *pixel, \
const int width, \
const int buffer_stride, \
const int pixel_stride) \
{ \
STUB_ASSERT(KERNEL_ARCH, film_convert_##name); \
(void)kfilm_convert; \
(void)buffer; \
(void)pixel; \
(void)width; \
(void)buffer_stride; \
(void)pixel_stride; \
} \
void KERNEL_FUNCTION_FULL_NAME(film_convert_half_rgba_##name)( \
const KernelFilmConvert *kfilm_convert, \
const float *buffer, \
half4 *pixel, \
const int width, \
const int buffer_stride) \
{ \
STUB_ASSERT(KERNEL_ARCH, film_convert_##name); \
(void)kfilm_convert; \
(void)buffer; \
(void)pixel; \
(void)width; \
(void)buffer_stride; \
}
#else
# define KERNEL_FILM_CONVERT_FUNCTION(name, is_float) \
void KERNEL_FUNCTION_FULL_NAME(film_convert_##name)(const KernelFilmConvert *kfilm_convert, \
const float *buffer, \
float *pixel, \
const int width, \
const int buffer_stride, \
const int pixel_stride) \
{ \
for (int i = 0; i < width; i++, buffer += buffer_stride, pixel += pixel_stride) { \
film_get_pass_pixel_##name(kfilm_convert, buffer, pixel); \
} \
} \
void KERNEL_FUNCTION_FULL_NAME(film_convert_half_rgba_##name)( \
const KernelFilmConvert *kfilm_convert, \
const float *buffer, \
half4 *pixel, \
const int width, \
const int buffer_stride) \
{ \
for (int i = 0; i < width; i++, buffer += buffer_stride, pixel++) { \
float pixel_rgba[4] = {0.0f, 0.0f, 0.0f, 1.0f}; \
film_get_pass_pixel_##name(kfilm_convert, buffer, pixel_rgba); \
if (is_float) { \
pixel_rgba[1] = pixel_rgba[0]; \
pixel_rgba[2] = pixel_rgba[0]; \
} \
film_apply_pass_pixel_overlays_rgba(kfilm_convert, buffer, pixel_rgba); \
*pixel = float4_to_half4_display( \
make_float4(pixel_rgba[0], pixel_rgba[1], pixel_rgba[2], pixel_rgba[3])); \
} \
}
#endif
KERNEL_FILM_CONVERT_FUNCTION(depth, true)
KERNEL_FILM_CONVERT_FUNCTION(mist, true)
KERNEL_FILM_CONVERT_FUNCTION(sample_count, true)
KERNEL_FILM_CONVERT_FUNCTION(volume_majorant, true)
KERNEL_FILM_CONVERT_FUNCTION(float, true)
KERNEL_FILM_CONVERT_FUNCTION(light_path, false)
KERNEL_FILM_CONVERT_FUNCTION(rgbe, false)
KERNEL_FILM_CONVERT_FUNCTION(float3, false)
KERNEL_FILM_CONVERT_FUNCTION(motion, false)
KERNEL_FILM_CONVERT_FUNCTION(cryptomatte, false)
KERNEL_FILM_CONVERT_FUNCTION(shadow_catcher, false)
KERNEL_FILM_CONVERT_FUNCTION(shadow_catcher_matte_with_shadow, false)
KERNEL_FILM_CONVERT_FUNCTION(combined, false)
KERNEL_FILM_CONVERT_FUNCTION(float4, false)
#undef KERNEL_FILM_CONVERT_FUNCTION
#undef KERNEL_INVOKE
#undef DEFINE_INTEGRATOR_SHADE_KERNEL
#undef DEFINE_INTEGRATOR_INIT_KERNEL
#undef KERNEL_STUB
#undef STUB_ASSERT
#undef KERNEL_ARCH
CCL_NAMESPACE_END

View File

@@ -0,0 +1,29 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* Optimized CPU kernel entry points. This file is compiled with AVX2
* optimization flags and nearly all functions inlined, while kernel.cpp
* is compiled without for other CPU's. */
#include "util/optimization.h"
#ifndef WITH_CYCLES_OPTIMIZED_KERNEL_AVX2
# define KERNEL_STUB
#else
/* SSE optimization disabled for now on 32 bit, see bug #36316. */
# if !(defined(__GNUC__) && (defined(i386) || defined(_M_IX86)))
# define __KERNEL_SSE__
# define __KERNEL_SSE2__
# define __KERNEL_SSE3__
# define __KERNEL_SSSE3__
# define __KERNEL_SSE42__
# define __KERNEL_AVX__
# define __KERNEL_AVX2__
# endif
#endif /* WITH_CYCLES_OPTIMIZED_KERNEL_AVX2 */
#include "kernel/device/cpu/globals.h"
#include "kernel/device/cpu/kernel.h"
#define KERNEL_ARCH cpu_avx2
#include "kernel/device/cpu/kernel_arch_impl.h"

View File

@@ -0,0 +1,235 @@
# SPDX-FileCopyrightText: 2011-2026 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
set(INC
../../..
)
set(INC_SYS
)
set(SRC_KERNEL_DEVICE_CUDA
kernel.cu
)
set(SRC_KERNEL_DEVICE_CUDA_HEADERS
compat.h
config.h
globals.h
)
set(LIB
)
function(cuda_get_version out_version)
execute_process(COMMAND ${CUDA_NVCC_EXECUTABLE} "--version" OUTPUT_VARIABLE NVCC_OUT)
string(REGEX REPLACE ".*release ([0-9]+)\\.([0-9]+).*" "\\1" CUDA_VERSION_MAJOR "${NVCC_OUT}")
string(REGEX REPLACE ".*release ([0-9]+)\\.([0-9]+).*" "\\2" CUDA_VERSION_MINOR "${NVCC_OUT}")
set(${out_version} "${CUDA_VERSION_MAJOR}${CUDA_VERSION_MINOR}" PARENT_SCOPE)
endfunction()
function(cuda_add_common_flags cuda_version arch in_flags out_flags)
set(flags ${in_flags})
if(CUDA_HOST_COMPILER)
set(flags ${flags} -ccbin="${CUDA_HOST_COMPILER}")
endif()
set(flags ${flags}
# Helps with compatibility when using recent clang host compiler.
"-std=c++17"
--use_fast_math
-Wno-deprecated-gpu-targets)
if(WITH_CYCLES_DEBUG)
set(flags ${flags}
-D WITH_CYCLES_DEBUG
--ptxas-options="-v")
endif()
if(WITH_NANOVDB)
set(flags ${flags} -D WITH_NANOVDB)
endif()
if("${cuda_version}" GREATER_EQUAL 123 AND "${arch}" STREQUAL "sm_120")
# Enable jump table generation for the SVM switch statement.
set(flags ${flags} --jump-table-density 80)
endif()
if(NOT WITH_CYCLES_CUDA_BUILD_SERIAL AND "${cuda_version}" GREATER_EQUAL 129)
# Only use split compile with few binaries, to avoid excessive memory usage.
# This is mainly helpful for quick local builds for one architecture.
list(LENGTH CYCLES_CUDA_BINARIES_ARCH _num_binaries)
if(_num_binaries LESS_EQUAL 2)
set(flags ${flags} --split-compile=0)
endif()
endif()
set(${out_flags} ${flags} PARENT_SCOPE)
endfunction()
if(WITH_CYCLES_CUDA_BINARIES)
# 64 bit only
set(CUDA_BITS 64)
# CUDA version
cuda_get_version(CUDA_VERSION)
# warn for other versions
if((CUDA_VERSION STREQUAL "101") OR
(CUDA_VERSION STREQUAL "102") OR
(CUDA_VERSION_MAJOR STREQUAL "11") OR
(CUDA_VERSION_MAJOR STREQUAL "12"))
else()
message(WARNING
"CUDA version ${CUDA_VERSION_MAJOR}.${CUDA_VERSION_MINOR} detected, "
"build may succeed but only CUDA 12, 11, 10.2 and 10.1 have been tested")
endif()
# build for each arch
set(cuda_sources kernel.cu
${SRC_KERNEL_DEVICE_CUDA_HEADERS}
$<TARGET_PROPERTY:cycles_kernel,INTERFACE_SOURCES>
)
set(cuda_cubins "")
# Modifies in parent scope:
# - `cuda_cubins`: appended with compressed `cubin/ptx` output.
function(CYCLES_CUDA_KERNEL_ADD arch prev_arch name flags sources experimental)
if(${arch} MATCHES "compute_.*")
set(format "ptx")
else()
set(format "cubin")
endif()
set(cuda_file ${name}_${arch}.${format})
set(cuda_file_compressed ${cuda_file}.zst)
set(kernel_sources ${sources})
if(NOT ${prev_arch} STREQUAL "none")
if(${prev_arch} MATCHES "compute_.*")
set(kernel_sources ${kernel_sources} ${name}_${prev_arch}.ptx)
else()
set(kernel_sources ${kernel_sources} ${name}_${prev_arch}.cubin)
endif()
endif()
set(cuda_kernel_src "/${name}.cu")
set(cuda_flags ${flags}
-D CCL_NAMESPACE_BEGIN=
-D CCL_NAMESPACE_END=
-D NVCC
-D _ALLOW_COMPILER_AND_STL_VERSION_MISMATCH
-m ${CUDA_BITS}
-I ${CMAKE_CURRENT_SOURCE_DIR}/../../..
-o ${CMAKE_CURRENT_BINARY_DIR}/${cuda_file})
cuda_add_common_flags(${cuda_version} ${arch} "${cuda_flags}" cuda_flags)
set(_cuda_nvcc_args
-arch=${arch}
${CUDA_NVCC_FLAGS}
--${format}
${CMAKE_CURRENT_SOURCE_DIR}${cuda_kernel_src}
${cuda_flags}
)
if(WITH_COMPILER_CCACHE AND CCACHE_PROGRAM)
add_custom_command(
OUTPUT ${cuda_file}
COMMAND ${CCACHE_PROGRAM} ${cuda_nvcc_executable} ${_cuda_nvcc_args}
DEPENDS ${kernel_sources})
else()
add_custom_command(
OUTPUT ${cuda_file}
COMMAND ${cuda_nvcc_executable} ${_cuda_nvcc_args}
DEPENDS ${kernel_sources})
endif()
add_custom_command(
OUTPUT ${cuda_file_compressed}
COMMAND "$<TARGET_FILE:zstd_compress>" ${cuda_file} ${cuda_file_compressed}
DEPENDS ${cuda_file})
unset(_cuda_nvcc_args)
delayed_install("${CMAKE_CURRENT_BINARY_DIR}" "${cuda_file_compressed}" ${CYCLES_INSTALL_PATH}/lib)
list(APPEND cuda_cubins ${cuda_file_compressed})
set(cuda_cubins "${cuda_cubins}" PARENT_SCOPE)
endfunction()
set(prev_arch "none")
foreach(arch ${CYCLES_CUDA_BINARIES_ARCH})
if(${arch} MATCHES ".*_3.")
message(STATUS "CUDA binaries for ${arch} are no longer supported, skipped.")
elseif(${arch} MATCHES "compute_7." AND DEFINED CUDA11_NVCC_EXECUTABLE)
# Use CUDA 11 if available for the default PTX kernel. This allows us to
# keep the driver requirements for user machines low.
set(cuda_nvcc_executable ${CUDA11_NVCC_EXECUTABLE})
set(cuda_toolkit_root_dir ${CUDA11_TOOLKIT_ROOT_DIR})
set(cuda_version 110)
elseif((${arch} MATCHES ".*_5." OR ${arch} MATCHES ".*_6." OR ${arch} MATCHES ".*_70") AND "${CUDA_VERSION}" GREATER_EQUAL 130)
# Support for Maxwell, Pascal and Volta was dropped in CUDA 13
if(DEFINED CUDA11_NVCC_EXECUTABLE)
set(cuda_nvcc_executable ${CUDA11_NVCC_EXECUTABLE})
set(cuda_toolkit_root_dir ${CUDA11_TOOLKIT_ROOT_DIR})
set(cuda_version 110)
else()
message(STATUS "CUDA binaries for ${arch} are no longer supported with CUDA 13.0+, skipped.")
endif()
elseif(${arch} MATCHES ".*_7." AND "${CUDA_VERSION}" LESS 100)
message(STATUS "CUDA binaries for ${arch} require CUDA 10.0+, skipped.")
elseif(${arch} MATCHES ".*_8.")
if("${CUDA_VERSION}" GREATER_EQUAL 111) # Support for sm_86 was introduced in CUDA 11
set(cuda_nvcc_executable ${CUDA_NVCC_EXECUTABLE})
set(cuda_toolkit_root_dir ${CUDA_TOOLKIT_ROOT_DIR})
set(cuda_version ${CUDA_VERSION})
elseif(DEFINED CUDA11_NVCC_EXECUTABLE)
set(cuda_nvcc_executable ${CUDA11_NVCC_EXECUTABLE})
set(cuda_toolkit_root_dir ${CUDA11_TOOLKIT_ROOT_DIR})
set(cuda_version 110)
else()
message(STATUS "CUDA binaries for ${arch} require CUDA 11.1+, skipped.")
endif()
elseif(${arch} MATCHES ".*_10." OR ${arch} MATCHES ".*_120")
if("${CUDA_VERSION}" GREATER_EQUAL 128) # Support for sm_100, sm_101, sm_120 was introduced in CUDA 12.8
set(cuda_nvcc_executable ${CUDA_NVCC_EXECUTABLE})
set(cuda_toolkit_root_dir ${CUDA_TOOLKIT_ROOT_DIR})
set(cuda_version ${CUDA_VERSION})
else()
message(STATUS "CUDA binaries for ${arch} require CUDA 12.8+, skipped.")
endif()
else()
set(cuda_nvcc_executable ${CUDA_NVCC_EXECUTABLE})
set(cuda_toolkit_root_dir ${CUDA_TOOLKIT_ROOT_DIR})
set(cuda_version ${CUDA_VERSION})
endif()
if(DEFINED cuda_nvcc_executable AND DEFINED cuda_toolkit_root_dir)
# Compile regular kernel
cycles_cuda_kernel_add(${arch} ${prev_arch} kernel "" "${cuda_sources}" FALSE)
if(WITH_CYCLES_CUDA_BUILD_SERIAL)
set(prev_arch ${arch})
endif()
unset(cuda_nvcc_executable)
unset(cuda_toolkit_root_dir)
endif()
endforeach()
add_custom_target(cycles_kernel_cuda
ALL
DEPENDS ${cuda_cubins}
SOURCES ${SRC_KERNEL_DEVICE_CUDA} ${SRC_KERNEL_DEVICE_CUDA_HEADERS}
)
cycles_set_solution_folder(cycles_kernel_cuda)
source_group("device\\cuda" FILES ${SRC_KERNEL_DEVICE_CUDA} ${SRC_KERNEL_DEVICE_CUDA_HEADERS})
add_dependencies(cycles_kernel cycles_kernel_cuda)
endif()
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_DEVICE_CUDA}" ${CYCLES_INSTALL_PATH}/source/kernel/device/cuda)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_DEVICE_CUDA_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/device/cuda)

View File

@@ -0,0 +1,119 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#define __KERNEL_GPU__
#define __KERNEL_CUDA__
#define CCL_NAMESPACE_BEGIN
#define CCL_NAMESPACE_END
#ifndef ATTR_FALLTHROUGH
# define ATTR_FALLTHROUGH
#endif
/* Manual definitions so we can compile without CUDA toolkit. */
#ifdef __CUDACC_RTC__
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
#else
# include <stdint.h>
#endif
#ifdef CYCLES_CUBIN_CC
# define FLT_MIN 1.175494350822287507969e-38f
# define FLT_MAX 340282346638528859811704183484516925440.0f
# define FLT_EPSILON 1.192092896e-07F
#endif
/* Qualifiers */
#define ccl_device __device__ __inline__
#define ccl_device_extern extern "C" __device__
#define ccl_device_inline __device__ __inline__
#define ccl_device_forceinline __device__ __forceinline__
#define ccl_device_noinline __device__ __noinline__
#define ccl_device_noinline_cpu ccl_device
#define ccl_device_inline_method ccl_device
#define ccl_device_template_spec template<> ccl_device_inline
#define ccl_global
#define ccl_inline_constant __constant__
#define ccl_device_constant __constant__ __device__
#define ccl_static_constexpr static constexpr
#define ccl_constant const
#define ccl_gpu_shared __shared__
#define ccl_private
#define ccl_ray_data ccl_private
#define ccl_may_alias
#define ccl_restrict __restrict__
#define ccl_align(n) __align__(n)
#define ccl_optional_struct_init
#define ccl_attr_maybe_unused [[maybe_unused]]
/* No assert supported for CUDA */
#define kernel_assert(cond)
/* GPU thread, block, grid size and index */
#define ccl_gpu_thread_idx_x (threadIdx.x)
#define ccl_gpu_block_dim_x (blockDim.x)
#define ccl_gpu_block_idx_x (blockIdx.x)
#define ccl_gpu_grid_dim_x (gridDim.x)
#define ccl_gpu_warp_size (warpSize)
#define ccl_gpu_thread_mask(thread_warp) uint(0xFFFFFFFF >> (ccl_gpu_warp_size - thread_warp))
#define ccl_gpu_global_id_x() (ccl_gpu_block_idx_x * ccl_gpu_block_dim_x + ccl_gpu_thread_idx_x)
#define ccl_gpu_global_size_x() (ccl_gpu_grid_dim_x * ccl_gpu_block_dim_x)
/* GPU warp synchronization. */
#define ccl_gpu_syncthreads() __syncthreads()
#define ccl_gpu_ballot(predicate) __ballot_sync(0xFFFFFFFF, predicate)
/* GPU texture objects */
typedef unsigned long long CUtexObject;
typedef CUtexObject ccl_gpu_image_object_2D;
template<typename T>
ccl_device_forceinline T ccl_gpu_image_object_read_2D(const ccl_gpu_image_object_2D texobj,
const float x,
const float y)
{
return tex2D<T>(texobj, x, y);
}
/* Use fast math functions */
#define cosf(x) __cosf(((float)(x)))
#define sinf(x) __sinf(((float)(x)))
#define powf(x, y) __powf(((float)(x)), ((float)(y)))
#define tanf(x) __tanf(((float)(x)))
#define logf(x) __logf(((float)(x)))
#define expf(x) __expf(((float)(x)))
/* Half */
typedef unsigned short half;
ccl_device_forceinline half __float2half(const float f)
{
half val;
asm("{ cvt.rn.f16.f32 %0, %1;}\n" : "=h"(val) : "f"(f));
return val;
}
ccl_device_forceinline float __half2float(const half h)
{
float val;
asm("{ cvt.f32.f16 %0, %1;}\n" : "=f"(val) : "h"(h));
return val;
}
/* Types */
#include "util/half.h"
#include "util/types.h"

View File

@@ -0,0 +1,88 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* Device data taken from CUDA occupancy calculator.
*
* Terminology
* - CUDA GPUs have multiple streaming multiprocessors
* - Each multiprocessor executes multiple thread blocks
* - Each thread block contains a number of threads, also known as the block size
* - Multiprocessors have a fixed number of registers, and the amount of registers
* used by each threads limits the number of threads per block.
*/
/* 5.x, 6.x */
#if __CUDA_ARCH__ <= 699
# define GPU_MULTIPRESSOR_MAX_REGISTERS 65536
# define GPU_MULTIPROCESSOR_MAX_BLOCKS 32
# define GPU_BLOCK_MAX_THREADS 1024
# define GPU_THREAD_MAX_REGISTERS 255
/* tunable parameters */
# define GPU_KERNEL_BLOCK_NUM_THREADS 256
/* CUDA 9.0 seems to cause slowdowns on high-end Pascal cards unless we increase the number of
* registers */
# if __CUDACC_VER_MAJOR__ >= 9 && __CUDA_ARCH__ >= 600
# define GPU_KERNEL_MAX_REGISTERS 64
# else
# define GPU_KERNEL_MAX_REGISTERS 48
# endif
/* 7.x, 8.x, 12.x */
#elif __CUDA_ARCH__ <= 1299
# define GPU_MULTIPRESSOR_MAX_REGISTERS 65536
# define GPU_MULTIPROCESSOR_MAX_BLOCKS 32
# define GPU_BLOCK_MAX_THREADS 1024
# define GPU_THREAD_MAX_REGISTERS 255
/* tunable parameters */
# define GPU_KERNEL_BLOCK_NUM_THREADS 384
# define GPU_KERNEL_MAX_REGISTERS 168
/* unknown architecture */
#else
# error "Unknown or unsupported CUDA architecture, can't determine launch bounds"
#endif
/* Compute number of threads per block and minimum blocks per multiprocessor
* given the maximum number of registers per thread. */
#define ccl_gpu_kernel(block_num_threads, thread_num_registers) \
extern "C" __global__ void __launch_bounds__(block_num_threads, \
GPU_MULTIPRESSOR_MAX_REGISTERS / \
(block_num_threads * thread_num_registers))
#define ccl_gpu_kernel_threads(block_num_threads) \
extern "C" __global__ void __launch_bounds__(block_num_threads)
#define ccl_gpu_kernel_signature(name, ...) kernel_gpu_##name(__VA_ARGS__)
#define ccl_gpu_kernel_postfix
#define ccl_gpu_kernel_call(x) x
#define ccl_gpu_kernel_within_bounds(i, n) ((i) < (n))
/* Define a function object where "func" is the lambda body, and additional parameters are used to
* specify captured state */
#define ccl_gpu_kernel_lambda(func, ...) \
struct KernelLambda { \
__VA_ARGS__; \
__device__ int operator()(const int state) \
{ \
return (func); \
} \
} ccl_gpu_kernel_lambda_pass
/* sanity checks */
#if GPU_KERNEL_BLOCK_NUM_THREADS > GPU_BLOCK_MAX_THREADS
# error "Maximum number of threads per block exceeded"
#endif
#if GPU_MULTIPRESSOR_MAX_REGISTERS / (GPU_KERNEL_BLOCK_NUM_THREADS * GPU_KERNEL_MAX_REGISTERS) > \
GPU_MULTIPROCESSOR_MAX_BLOCKS
# error "Maximum number of blocks per multiprocessor exceeded"
#endif
#if GPU_KERNEL_MAX_REGISTERS > GPU_THREAD_MAX_REGISTERS
# error "Maximum number of registers per thread exceeded"
#endif

View File

@@ -0,0 +1,48 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* Constant Globals */
#pragma once
#include "kernel/types.h"
#include "kernel/integrator/state.h"
#include "kernel/util/profiler.h"
#include "util/color.h"
#include "util/types_image.h"
CCL_NAMESPACE_BEGIN
/* Not actually used, just a nullptr pointer that gets passed everywhere, which we
* hope gets optimized out by the compiler. */
struct KernelGlobalsGPU {
int unused[1];
};
using KernelGlobals = const ccl_global KernelGlobalsGPU *ccl_restrict;
struct KernelParamsCUDA {
/* Global scene data and textures */
KernelData data;
#define KERNEL_DATA_ARRAY(type, name) const type *name;
#define KERNEL_DATA_ARRAY_WRITABLE(type, name) type *name;
#include "kernel/data_arrays.h"
/* Integrator state */
IntegratorStateGPU integrator_state;
};
#ifdef __KERNEL_GPU__
__constant__ KernelParamsCUDA kernel_params;
#endif
/* Abstraction macros */
#define kernel_data kernel_params.data
#define kernel_data_fetch(name, index) kernel_params.name[(index)]
#define kernel_data_write(name, index, value) kernel_params.name[(index)] = (value)
#define kernel_data_array(name) (kernel_params.name)
#define kernel_integrator_state kernel_params.integrator_state
CCL_NAMESPACE_END

View File

@@ -0,0 +1,16 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* CUDA kernel entry points */
#ifdef __CUDA_ARCH__
# include "kernel/device/cuda/compat.h"
# include "kernel/device/cuda/config.h"
# include "kernel/device/cuda/globals.h"
# include "kernel/device/gpu/image.h"
# include "kernel/device/gpu/kernel.h"
#endif

View File

@@ -0,0 +1,18 @@
/* SPDX-FileCopyrightText: 2017-2025 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef __HIP__
# define GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE 1024
# define GPU_PARALLEL_PREFIX_SUM_DEFAULT_BLOCK_SIZE 1024
# define GPU_PARALLEL_SORTED_INDEX_DEFAULT_BLOCK_SIZE 1024
#else
# define GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE 512
# define GPU_PARALLEL_PREFIX_SUM_DEFAULT_BLOCK_SIZE 512
# define GPU_PARALLEL_SORTED_INDEX_DEFAULT_BLOCK_SIZE 512
#endif
#define GPU_PARALLEL_SORTED_INDEX_INACTIVE_KEY (~0)
#define GPU_PARALLEL_SORT_BLOCK_SIZE 1024

View File

@@ -0,0 +1,173 @@
/* SPDX-FileCopyrightText: 2017-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/globals.h"
#include "kernel/util/image_2d.h"
#include "util/defines.h"
#include "util/types_image.h"
CCL_NAMESPACE_BEGIN
ccl_device_inline float frac(const float x, ccl_private int *ix)
{
int i = float_to_int(x) - ((x < 0.0f) ? 1 : 0);
*ix = i;
return x - (float)i;
}
/* w0, w1, w2, and w3 are the four cubic B-spline basis functions. */
ccl_device float cubic_w0(const float a)
{
return (1.0f / 6.0f) * (a * (a * (-a + 3.0f) - 3.0f) + 1.0f);
}
ccl_device float cubic_w1(const float a)
{
return (1.0f / 6.0f) * (a * a * (3.0f * a - 6.0f) + 4.0f);
}
ccl_device float cubic_w2(const float a)
{
return (1.0f / 6.0f) * (a * (a * (-3.0f * a + 3.0f) + 3.0f) + 1.0f);
}
ccl_device float cubic_w3(const float a)
{
return (1.0f / 6.0f) * (a * a * a);
}
/* g0 and g1 are the two amplitude functions. */
ccl_device float cubic_g0(const float a)
{
return cubic_w0(a) + cubic_w1(a);
}
ccl_device float cubic_g1(const float a)
{
return cubic_w2(a) + cubic_w3(a);
}
/* h0 and h1 are the two offset functions */
ccl_device float cubic_h0(const float a)
{
return (cubic_w1(a) / cubic_g0(a)) - 1.0f;
}
ccl_device float cubic_h1(const float a)
{
return (cubic_w3(a) / cubic_g1(a)) + 1.0f;
}
/* Fast bicubic texture lookup using 4 bilinear lookups, adapted from CUDA samples. */
template<typename T>
ccl_device_noinline T kernel_image_interp_bicubic(const ccl_global KernelImageInfo &info,
const float2 uv)
{
ccl_gpu_image_object_2D tex = (ccl_gpu_image_object_2D)info.data;
const float x = (uv.x * (float)info.width) - 0.5f;
const float y = (uv.y * (float)info.height) - 0.5f;
const float px = floorf(x);
const float py = floorf(y);
const float fx = x - px;
const float fy = y - py;
const float g0x = cubic_g0(fx);
const float g1x = cubic_g1(fx);
/* Note +0.5 offset to compensate for CUDA linear filtering convention. */
const float x0 = (px + cubic_h0(fx) + 0.5f) * info.inv_width;
const float x1 = (px + cubic_h1(fx) + 0.5f) * info.inv_width;
const float y0 = (py + cubic_h0(fy) + 0.5f) * info.inv_height;
const float y1 = (py + cubic_h1(fy) + 0.5f) * info.inv_height;
return cubic_g0(fy) * (g0x * ccl_gpu_image_object_read_2D<T>(tex, x0, y0) +
g1x * ccl_gpu_image_object_read_2D<T>(tex, x1, y0)) +
cubic_g1(fy) * (g0x * ccl_gpu_image_object_read_2D<T>(tex, x0, y1) +
g1x * ccl_gpu_image_object_read_2D<T>(tex, x1, y1));
}
ccl_device float4 kernel_image_interp(KernelGlobals kg,
ccl_private ShaderData *sd,
const int image_texture_id,
dual2 uv)
{
if (image_texture_id == KERNEL_IMAGE_NONE) {
return IMAGE_MISSING_RGBA;
}
const ccl_global KernelImageTexture &tex = kernel_data_fetch(image_textures, image_texture_id);
const ccl_global KernelImageInfo *info;
float2 sample_uv;
if (tex.tile_descriptor_offset != KERNEL_TILE_LOAD_NONE) {
/* Wrapping. */
if (!kernel_image_tile_wrap(ExtensionType(tex.extension), uv.val)) {
return zero_float4();
}
/* Tile mapping */
float2 xy = zero_float2();
const KernelTileDescriptor tile_descriptor = kernel_image_tile_map(
kg, sd, tex, image_texture_id, uv, xy);
if (!kernel_tile_descriptor_loaded(tile_descriptor)) {
return (tile_descriptor == KERNEL_TILE_LOAD_FAILED) ? IMAGE_MISSING_RGBA : tex.average_color;
}
info = &kernel_data_fetch(image_info, kernel_tile_descriptor_image_info_id(tile_descriptor));
/* Convert to normalized space again. */
sample_uv = make_float2(xy.x * info->inv_width, xy.y * info->inv_height);
}
else {
/* Full image sampling. */
if (tex.image_info_id == KERNEL_IMAGE_NONE) {
return IMAGE_MISSING_RGBA;
}
info = &kernel_data_fetch(image_info, tex.image_info_id);
sample_uv = uv.val;
}
/* float4, byte4, ushort4 and half4 */
const int texture_type = info->data_type;
if (texture_type == IMAGE_DATA_TYPE_FLOAT4 || texture_type == IMAGE_DATA_TYPE_BYTE4 ||
texture_type == IMAGE_DATA_TYPE_HALF4 || texture_type == IMAGE_DATA_TYPE_USHORT4)
{
if (info->interpolation == INTERPOLATION_CUBIC || info->interpolation == INTERPOLATION_SMART) {
return kernel_image_interp_bicubic<float4>(*info, sample_uv);
}
else {
ccl_gpu_image_object_2D tex = (ccl_gpu_image_object_2D)info->data;
return ccl_gpu_image_object_read_2D<float4>(tex, sample_uv.x, sample_uv.y);
}
}
/* float, byte and half */
else {
float f;
if (info->interpolation == INTERPOLATION_CUBIC || info->interpolation == INTERPOLATION_SMART) {
f = kernel_image_interp_bicubic<float>(*info, sample_uv);
}
else {
ccl_gpu_image_object_2D tex = (ccl_gpu_image_object_2D)info->data;
f = ccl_gpu_image_object_read_2D<float>(tex, sample_uv.x, sample_uv.y);
}
return make_float4(f, f, f, 1.0f);
}
}
ccl_device_forceinline float4 kernel_image_interp_with_udim(KernelGlobals kg,
ccl_private ShaderData *sd,
const int udim_id,
dual2 uv)
{
const int image_texture_id = kernel_image_udim_map(kg, udim_id, uv.val);
if (image_texture_id == KERNEL_IMAGE_NONE) {
return IMAGE_MISSING_RGBA;
}
return kernel_image_interp(kg, sd, image_texture_id, uv);
}
CCL_NAMESPACE_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,180 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
CCL_NAMESPACE_BEGIN
/* Given an array of states, build an array of indices for which the states
* are active.
*
* Shared memory requirement is `sizeof(int) * (number_of_warps + 1)`. */
#include "kernel/device/gpu/block_sizes.h"
#include "util/atomic.h"
/* TODO: abstract more device differences, define `ccl_gpu_local_syncthreads`,
* `ccl_gpu_thread_warp`, `ccl_gpu_warp_index`, `ccl_gpu_num_warps` for all devices
* and keep device specific code in `compat.h`. */
#ifdef __KERNEL_ONEAPI__
template<typename IsActiveOp>
void gpu_parallel_active_index_array_impl(const uint num_states,
ccl_global int *ccl_restrict indices,
ccl_global int *ccl_restrict num_indices,
IsActiveOp is_active_op)
{
const sycl::nd_item<1> &item_id = sycl::ext::oneapi::this_work_item::get_nd_item<1>();
const uint blocksize = item_id.get_local_range(0);
sycl::multi_ptr<int[GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE + 1],
sycl::access::address_space::local_space>
ptr = sycl::ext::oneapi::group_local_memory<
int[GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE + 1]>(item_id.get_group());
int *warp_offset = *ptr;
/* NOTE(@nsirgien): Here we calculate the same value as below but
* faster for DPC++ : seems CUDA converting "%", "/", "*" based calculations below into
* something faster already but DPC++ doesn't, so it's better to use
* direct request of needed parameters - switching from this computation to computation below
* will cause 2.5x performance slowdown. */
const uint thread_index = item_id.get_local_id(0);
const uint thread_warp = item_id.get_sub_group().get_local_id();
const uint warp_index = item_id.get_sub_group().get_group_id();
const uint num_warps = item_id.get_sub_group().get_group_range()[0];
const uint state_index = item_id.get_global_id(0);
/* Test if state corresponding to this thread is active. */
const uint is_active = (state_index < num_states) ? is_active_op(state_index) : 0;
#else /* !__KERNEL__ONEAPI__ */
# ifndef __KERNEL_METAL__
template<typename IsActiveOp>
__device__
# endif
void
gpu_parallel_active_index_array_impl(const uint num_states,
ccl_global int *indices,
ccl_global int *num_indices,
# ifdef __KERNEL_METAL__
const uint is_active,
const uint blocksize,
const int thread_index,
const uint state_index,
const int ccl_gpu_warp_size,
const int thread_warp,
const int warp_index,
const int num_warps,
threadgroup int *warp_offset)
{
# else
IsActiveOp is_active_op)
{
extern ccl_gpu_shared int warp_offset[];
# ifndef __KERNEL_METAL__
const uint blocksize = ccl_gpu_block_dim_x;
# endif
const uint thread_index = ccl_gpu_thread_idx_x;
const uint thread_warp = thread_index % ccl_gpu_warp_size;
const uint warp_index = thread_index / ccl_gpu_warp_size;
const uint num_warps = blocksize / ccl_gpu_warp_size;
const uint state_index = ccl_gpu_block_idx_x * blocksize + thread_index;
/* Test if state corresponding to this thread is active. */
const uint is_active = (state_index < num_states) ? is_active_op(state_index) : 0;
# endif
#endif /* !__KERNEL_ONEAPI__ */
/* For each thread within a warp compute how many other active states precede it. */
#ifdef __KERNEL_ONEAPI__
const uint thread_offset = sycl::exclusive_scan_over_group(
item_id.get_sub_group(), is_active, std::plus<>());
#else
const uint thread_offset = popcount(ccl_gpu_ballot(is_active) &
ccl_gpu_thread_mask(thread_warp));
#endif
/* Last thread in warp stores number of active states for each warp. */
#ifdef __KERNEL_ONEAPI__
if (thread_warp == item_id.get_sub_group().get_local_range()[0] - 1) {
#else
if (thread_warp == ccl_gpu_warp_size - 1) {
#endif
warp_offset[warp_index] = thread_offset + is_active;
}
#ifdef __KERNEL_ONEAPI__
/* NOTE(@nsirgien): For us here only local memory writing (warp_offset) is important,
* so faster local barriers can be used. */
ccl_gpu_local_syncthreads();
#else
ccl_gpu_syncthreads();
#endif
/* Last thread in block converts per-warp sizes to offsets, increments global size of
* index array and gets offset to write to. */
if (thread_index == blocksize - 1) {
/* TODO: parallelize this. */
int offset = 0;
for (int i = 0; i < num_warps; i++) {
int num_active = warp_offset[i];
warp_offset[i] = offset;
offset += num_active;
}
const uint block_num_active = warp_offset[warp_index] + thread_offset + is_active;
warp_offset[num_warps] = atomic_fetch_and_add_uint32(num_indices, block_num_active);
}
#ifdef __KERNEL_ONEAPI__
/* NOTE(@nsirgien): For us here only important local memory writing (warp_offset),
* so faster local barriers can be used. */
ccl_gpu_local_syncthreads();
#else
ccl_gpu_syncthreads();
#endif
/* Write to index array. */
if (is_active) {
const uint block_offset = warp_offset[num_warps];
indices[block_offset + warp_offset[warp_index] + thread_offset] = state_index;
}
}
#ifdef __KERNEL_METAL__
# define gpu_parallel_active_index_array(num_states, indices, num_indices, is_active_op) \
const uint is_active = (ccl_gpu_global_id_x() < num_states) ? \
is_active_op(ccl_gpu_global_id_x()) : \
0; \
gpu_parallel_active_index_array_impl(num_states, \
indices, \
num_indices, \
is_active, \
metal_local_size, \
metal_local_id, \
metal_global_id, \
simdgroup_size, \
simd_lane_index, \
simd_group_index, \
num_simd_groups, \
(threadgroup int *)threadgroup_array)
#elif defined(__KERNEL_ONEAPI__)
# define gpu_parallel_active_index_array(num_states, indices, num_indices, is_active_op) \
gpu_parallel_active_index_array_impl(num_states, indices, num_indices, is_active_op)
#else
# define gpu_parallel_active_index_array(num_states, indices, num_indices, is_active_op) \
gpu_parallel_active_index_array_impl(num_states, indices, num_indices, is_active_op)
#endif
CCL_NAMESPACE_END

View File

@@ -0,0 +1,36 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
CCL_NAMESPACE_BEGIN
/* Parallel prefix sum.
*
* TODO: actually make this work in parallel.
*
* This is used for an array the size of the number of shaders in the scene
* which is not usually huge, so might not be a significant bottleneck. */
#include "util/atomic.h"
__device__ void gpu_parallel_prefix_sum(const int global_id,
ccl_global int *counter,
ccl_global int *prefix_sum,
const int num_values)
{
if (global_id != 0) {
return;
}
int offset = 0;
for (int i = 0; i < num_values; i++) {
const int new_offset = offset + counter[i];
prefix_sum[i] = offset;
counter[i] = 0;
offset = new_offset;
}
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,165 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
CCL_NAMESPACE_BEGIN
/* Given an array of states, build an array of indices for which the states
* are active and sorted by a given key. The prefix sum of the number of active
* states per key must have already been computed.
*
* TODO: there may be ways to optimize this to avoid this many atomic ops? */
#include "kernel/device/gpu/block_sizes.h"
#include "util/atomic.h"
#if defined(__KERNEL_LOCAL_ATOMIC_SORT__)
ccl_device_inline void gpu_parallel_sort_bucket_pass(const uint num_states,
const uint partition_size,
const uint max_shaders,
const uint queued_kernel,
ccl_global ushort *d_queued_kernel,
ccl_global uint *d_shader_sort_key,
ccl_global int *partition_key_offsets,
ccl_gpu_shared int *buckets,
const ushort local_id,
const ushort local_size,
const uint grid_id)
{
/* Zero the bucket sizes. */
for (uint i = local_id; i < max_shaders; i += local_size) {
atomic_store_local(&buckets[i], 0);
}
# ifdef __KERNEL_ONEAPI__
/* NOTE(@nsirgien): For us here only local memory writing (buckets) is important,
* so faster local barriers can be used. */
ccl_gpu_local_syncthreads();
# else
ccl_gpu_syncthreads();
# endif
/* Determine bucket sizes within the partitions. */
const uint partition_start = partition_size * uint(grid_id);
const uint partition_end = min(num_states, partition_start + partition_size);
for (int state_index = partition_start + uint(local_id); state_index < partition_end;
state_index += uint(local_size))
{
ushort kernel_index = d_queued_kernel[state_index];
if (kernel_index == queued_kernel) {
uint key = d_shader_sort_key[state_index] % max_shaders;
atomic_fetch_and_add_uint32_shared(&buckets[key], 1);
}
}
# ifdef __KERNEL_ONEAPI__
/* NOTE(@nsirgien): For us here only local memory writing (buckets) is important,
* so faster local barriers can be used. */
ccl_gpu_local_syncthreads();
# else
ccl_gpu_syncthreads();
# endif
/* Calculate the partition's local offsets from the prefix sum of bucket sizes. */
if (local_id == 0) {
int offset = 0;
for (int i = 0; i < max_shaders; i++) {
partition_key_offsets[i + uint(grid_id) * (max_shaders + 1)] = offset;
offset = offset + atomic_load_local(&buckets[i]);
}
/* Store the number of active states in this partition. */
partition_key_offsets[max_shaders + uint(grid_id) * (max_shaders + 1)] = offset;
}
}
ccl_device_inline void gpu_parallel_sort_write_pass(const uint num_states,
const uint partition_size,
const uint max_shaders,
const uint queued_kernel,
const int num_states_limit,
ccl_global int *indices,
ccl_global ushort *d_queued_kernel,
ccl_global uint *d_shader_sort_key,
ccl_global int *partition_key_offsets,
ccl_gpu_shared int *local_offset,
const ushort local_id,
const ushort local_size,
const uint grid_id)
{
/* Calculate each partition's global offset from the prefix sum of the active state counts per
* partition. */
int partition_offset = 0;
for (uint i = 0; i < grid_id; i++) {
partition_offset += partition_key_offsets[max_shaders + i * (max_shaders + 1)];
}
ccl_global int *key_offsets = partition_key_offsets + grid_id * (max_shaders + 1);
for (uint i = local_id; i < max_shaders; i += local_size) {
atomic_store_local(&local_offset[i], key_offsets[i] + partition_offset);
}
# ifdef __KERNEL_ONEAPI__
/* NOTE(@nsirgien): For us here only local memory writing (local_offset) is important,
* so faster local barriers can be used. */
ccl_gpu_local_syncthreads();
# else
ccl_gpu_syncthreads();
# endif
/* Write the sorted active indices. */
const uint partition_start = partition_size * uint(grid_id);
const uint partition_end = min(num_states, partition_start + partition_size);
for (int state_index = partition_start + uint(local_id); state_index < partition_end;
state_index += uint(local_size))
{
ushort kernel_index = d_queued_kernel[state_index];
if (kernel_index == queued_kernel) {
uint key = d_shader_sort_key[state_index] % max_shaders;
int index = atomic_fetch_and_add_uint32_shared(&local_offset[key], 1);
if (index < num_states_limit) {
indices[index] = state_index;
}
}
}
}
#endif /* __KERNEL_LOCAL_ATOMIC_SORT__ */
template<typename GetKeyOp>
__device__ void gpu_parallel_sorted_index_array(const uint state_index,
const uint num_states,
const int num_states_limit,
ccl_global int *indices,
ccl_global int *num_indices,
ccl_global int *key_counter,
ccl_global int *key_prefix_sum,
GetKeyOp get_key_op)
{
const int key = (state_index < num_states) ? get_key_op(state_index) :
GPU_PARALLEL_SORTED_INDEX_INACTIVE_KEY;
if (key != GPU_PARALLEL_SORTED_INDEX_INACTIVE_KEY) {
const uint index = atomic_fetch_and_add_uint32(&key_prefix_sum[key], 1);
if (index < num_states_limit) {
/* Assign state index. */
indices[index] = state_index;
}
else {
/* Can't process this state now, increase the counter again so that
* it will be handled in another iteration. */
atomic_fetch_and_add_uint32(&key_counter[key], 1);
}
}
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,43 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
CCL_NAMESPACE_BEGIN
/*
* Utility functions for work stealing
*/
/* Map global work index to tile, pixel X/Y and sample. */
ccl_device_inline void get_work_pixel(const ccl_global KernelWorkTile *tile,
const uint global_work_index,
ccl_private uint *x,
ccl_private uint *y,
ccl_private uint *sample)
{
uint sample_offset, pixel_offset;
if (kernel_data.integrator.scrambling_distance < 0.9f) {
/* Keep threads for the same sample together. */
const uint tile_pixels = tile->w * tile->h;
sample_offset = global_work_index / tile_pixels;
pixel_offset = global_work_index - sample_offset * tile_pixels;
}
else {
/* Keeping threads for the same pixel together.
* Appears to improve performance by a few % on CUDA and OptiX. */
sample_offset = global_work_index % tile->num_samples;
pixel_offset = global_work_index / tile->num_samples;
}
const uint y_offset = pixel_offset / tile->w;
const uint x_offset = pixel_offset - y_offset * tile->w;
*x = tile->x + x_offset;
*y = tile->y + y_offset;
*sample = tile->start_sample + sample_offset;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,126 @@
# SPDX-FileCopyrightText: 2011-2026 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
set(INC
../../..
)
set(INC_SYS
)
set(SRC_KERNEL_DEVICE_HIP
kernel.cpp
)
set(SRC_KERNEL_DEVICE_HIP_HEADERS
compat.h
config.h
globals.h
)
set(LIB
)
if(WITH_CYCLES_HIP_BINARIES AND WITH_CYCLES_DEVICE_HIP)
# build for each arch
set(hip_sources
kernel.cpp
${SRC_KERNEL_DEVICE_HIP_HEADERS}
$<TARGET_PROPERTY:cycles_kernel,INTERFACE_SOURCES>
)
set(hip_fatbins "")
# Modifies in parent scope:
# - `hip_fatbins`: appended with compressed `fatbin` output.
function(CYCLES_HIP_KERNEL_ADD arch name flags sources experimental)
set(format "fatbin")
set(hip_file ${name}_${arch}.${format})
set(hip_file_compressed ${hip_file}.zst)
set(kernel_sources ${sources})
set(hip_kernel_src "/${name}.cpp")
if(WIN32)
set(hip_command ${CMAKE_COMMAND})
set(hip_flags
-E env "HIP_PATH=${HIP_ROOT_DIR}"
${HIP_HIPCC_EXECUTABLE})
else()
set(hip_command ${HIP_HIPCC_EXECUTABLE})
set(hip_flags "")
endif()
# There's a few bugs in the HIP compiler causing some scenes to fail to render,
# or render improperly with specific combinations of device and/or compiler.
# - Vega (gfx90x) fails to render some scenes
# - Other GPUs (E.g. RDNA3) render volumes improperly with HIP 6.1.40252
# A workaround is to set -O1 opt level during kernel compilation for these
# configurations.
if(WIN32 AND ((${arch} MATCHES "gfx90[a-z0-9]+") OR (${HIP_VERSION} STREQUAL "6.1.40252")))
set(hip_opt_flags "-O1")
else()
set(hip_opt_flags "")
endif()
set(hip_flags
${hip_flags}
--offload-arch=${arch}
${HIP_HIPCC_FLAGS}
--genco
${CMAKE_CURRENT_SOURCE_DIR}${hip_kernel_src}
${flags}
-D CCL_NAMESPACE_BEGIN=
-D CCL_NAMESPACE_END=
-D HIPCC
-I ${CMAKE_CURRENT_SOURCE_DIR}/../../..
-Wno-parentheses-equality
-Wno-unused-value
-ffast-math
-std=c++17
${hip_opt_flags}
-o ${CMAKE_CURRENT_BINARY_DIR}/${hip_file})
if(WITH_NANOVDB)
set(hip_flags ${hip_flags}
-D WITH_NANOVDB)
endif()
if(WITH_CYCLES_DEBUG)
set(hip_flags ${hip_flags} -D WITH_CYCLES_DEBUG)
endif()
add_custom_command(
OUTPUT ${hip_file}
COMMAND ${hip_command} ${hip_flags}
DEPENDS ${kernel_sources})
add_custom_command(
OUTPUT ${hip_file_compressed}
COMMAND "$<TARGET_FILE:zstd_compress>" ${hip_file} ${hip_file_compressed}
DEPENDS ${hip_file})
delayed_install("${CMAKE_CURRENT_BINARY_DIR}" "${hip_file_compressed}" ${CYCLES_INSTALL_PATH}/lib)
list(APPEND hip_fatbins ${hip_file_compressed})
set(hip_fatbins "${hip_fatbins}" PARENT_SCOPE)
endfunction()
foreach(arch ${CYCLES_HIP_BINARIES_ARCH})
# Compile regular kernel
cycles_hip_kernel_add(${arch} kernel "" "${hip_sources}" FALSE)
endforeach()
add_custom_target(cycles_kernel_hip
ALL
DEPENDS ${hip_fatbins}
SOURCES ${SRC_KERNEL_DEVICE_HIP} ${SRC_KERNEL_DEVICE_HIP_HEADERS}
)
cycles_set_solution_folder(cycles_kernel_hip)
source_group("device\\hip" FILES ${SRC_KERNEL_DEVICE_HIP} ${SRC_KERNEL_DEVICE_HIP_HEADERS})
add_dependencies(cycles_kernel cycles_kernel_hip)
endif()
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_DEVICE_HIP}" ${CYCLES_INSTALL_PATH}/source/kernel/device/hip)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_DEVICE_HIP_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/device/hip)

View File

@@ -0,0 +1,103 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#define __KERNEL_GPU__
#define __KERNEL_HIP__
#define CCL_NAMESPACE_BEGIN
#define CCL_NAMESPACE_END
#ifndef ATTR_FALLTHROUGH
# define ATTR_FALLTHROUGH
#endif
#ifdef __HIPCC_RTC__
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
#else
# include <stdint.h>
#endif
#ifdef CYCLES_HIPBIN_CC
# define FLT_MIN 1.175494350822287507969e-38f
# define FLT_MAX 340282346638528859811704183484516925440.0f
# define FLT_EPSILON 1.192092896e-07F
#endif
/* Qualifiers */
#define ccl_device __device__ __inline__
#define ccl_device_extern extern "C" __device__
#define ccl_device_inline __device__ __inline__
#define ccl_device_forceinline __device__ __forceinline__
#define ccl_device_noinline __device__ __noinline__
#define ccl_device_noinline_cpu ccl_device
#define ccl_device_inline_method ccl_device
#define ccl_device_template_spec template<> ccl_device_inline
#define ccl_global
#define ccl_inline_constant __constant__
#define ccl_device_constant __constant__ __device__
#define ccl_static_constexpr static constexpr
#define ccl_constant const
#define ccl_gpu_shared __shared__
#define ccl_private
#define ccl_ray_data ccl_private
#define ccl_may_alias
#define ccl_restrict __restrict__
#define ccl_align(n) __align__(n)
#define ccl_optional_struct_init
#define ccl_attr_maybe_unused [[maybe_unused]]
#define kernel_assert(cond)
/* Types */
#ifdef __HIP__
# include "hip/hip_fp16.h"
# include "hip/hip_runtime.h"
#endif
#ifdef _MSC_VER
# include <immintrin.h>
#endif
#define ccl_gpu_thread_idx_x (threadIdx.x)
#define ccl_gpu_block_dim_x (blockDim.x)
#define ccl_gpu_block_idx_x (blockIdx.x)
#define ccl_gpu_grid_dim_x (gridDim.x)
#define ccl_gpu_warp_size (warpSize)
#define ccl_gpu_thread_mask(thread_warp) uint64_t((1ull << thread_warp) - 1)
#define ccl_gpu_global_id_x() (ccl_gpu_block_idx_x * ccl_gpu_block_dim_x + ccl_gpu_thread_idx_x)
#define ccl_gpu_global_size_x() (ccl_gpu_grid_dim_x * ccl_gpu_block_dim_x)
/* GPU warp synchronization */
#define ccl_gpu_syncthreads() __syncthreads()
#define ccl_gpu_ballot(predicate) __ballot(predicate)
/* GPU texture objects */
typedef hipTextureObject_t ccl_gpu_image_object_2D;
template<typename T>
ccl_device_forceinline T ccl_gpu_image_object_read_2D(const ccl_gpu_image_object_2D texobj,
const float x,
const float y)
{
return tex2D<T>(texobj, x, y);
}
/* Use fast math functions */
#define cosf(x) __cosf(((float)(x)))
#define sinf(x) __sinf(((float)(x)))
#define powf(x, y) __powf(((float)(x)), ((float)(y)))
#define tanf(x) __tanf(((float)(x)))
#define logf(x) __logf(((float)(x)))
#define expf(x) __expf(((float)(x)))
/* Types */
#include "util/half.h"
#include "util/types.h"

View File

@@ -0,0 +1,68 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* Device data taken from HIP occupancy calculator.
*
* Terminology
* - HIP GPUs have multiple streaming multiprocessors
* - Each multiprocessor executes multiple thread blocks
* - Each thread block contains a number of threads, also known as the block size
* - Multiprocessors have a fixed number of registers, and the amount of registers
* used by each threads limits the number of threads per block.
*/
/* Launch Bound Definitions */
#define GPU_MULTIPRESSOR_MAX_REGISTERS 65536
#define GPU_MULTIPROCESSOR_MAX_BLOCKS 64
#define GPU_BLOCK_MAX_THREADS 1024
#define GPU_THREAD_MAX_REGISTERS 255
#define GPU_KERNEL_BLOCK_NUM_THREADS 1024
#define GPU_KERNEL_MAX_REGISTERS 64
/* For performance tuning of HIPRT kernels we might have to change the number
* that's why we don't use GPU_KERNEL_BLOCK_NUM_THREADS. */
#define GPU_HIPRT_KERNEL_BLOCK_NUM_THREADS 1024
/* Compute number of threads per block and minimum blocks per multiprocessor
* given the maximum number of registers per thread. */
#define ccl_gpu_kernel(block_num_threads, thread_num_registers) \
extern "C" __global__ void __launch_bounds__(block_num_threads, \
GPU_MULTIPRESSOR_MAX_REGISTERS / \
(block_num_threads * thread_num_registers))
#define ccl_gpu_kernel_threads(block_num_threads) \
extern "C" __global__ void __launch_bounds__(block_num_threads)
#define ccl_gpu_kernel_signature(name, ...) kernel_gpu_##name(__VA_ARGS__)
#define ccl_gpu_kernel_postfix
#define ccl_gpu_kernel_call(x) x
#define ccl_gpu_kernel_within_bounds(i, n) ((i) < (n))
/* Define a function object where "func" is the lambda body, and additional parameters are used to
* specify captured state */
#define ccl_gpu_kernel_lambda(func, ...) \
struct KernelLambda { \
__VA_ARGS__; \
__device__ int operator()(const int state) \
{ \
return (func); \
} \
} ccl_gpu_kernel_lambda_pass
/* sanity checks */
#if GPU_KERNEL_BLOCK_NUM_THREADS > GPU_BLOCK_MAX_THREADS
# error "Maximum number of threads per block exceeded"
#endif
#if GPU_MULTIPRESSOR_MAX_REGISTERS / (GPU_KERNEL_BLOCK_NUM_THREADS * GPU_KERNEL_MAX_REGISTERS) > \
GPU_MULTIPROCESSOR_MAX_BLOCKS
# error "Maximum number of blocks per multiprocessor exceeded"
#endif
#if GPU_KERNEL_MAX_REGISTERS > GPU_THREAD_MAX_REGISTERS
# error "Maximum number of registers per thread exceeded"
#endif

View File

@@ -0,0 +1,48 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* Constant Globals */
#pragma once
#include "kernel/types.h"
#include "kernel/integrator/state.h"
#include "kernel/util/profiler.h"
#include "util/color.h"
#include "util/types_image.h"
CCL_NAMESPACE_BEGIN
/* Not actually used, just a nullptr pointer that gets passed everywhere, which we
* hope gets optimized out by the compiler. */
struct KernelGlobalsGPU {
int unused[1];
};
using KernelGlobals = const ccl_global KernelGlobalsGPU *ccl_restrict;
struct KernelParamsHIP {
/* Global scene data and textures */
KernelData data;
#define KERNEL_DATA_ARRAY(type, name) const type *name;
#define KERNEL_DATA_ARRAY_WRITABLE(type, name) type *name;
#include "kernel/data_arrays.h"
/* Integrator state */
IntegratorStateGPU integrator_state;
};
#ifdef __KERNEL_GPU__
__constant__ KernelParamsHIP kernel_params;
#endif
/* Abstraction macros */
#define kernel_data kernel_params.data
#define kernel_data_fetch(name, index) kernel_params.name[(index)]
#define kernel_data_write(name, index, value) kernel_params.name[(index)] = (value)
#define kernel_data_array(name) (kernel_params.name)
#define kernel_integrator_state kernel_params.integrator_state
CCL_NAMESPACE_END

View File

@@ -0,0 +1,16 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* HIP kernel entry points */
#ifdef __HIP_DEVICE_COMPILE__
# include "kernel/device/hip/compat.h"
# include "kernel/device/hip/config.h"
# include "kernel/device/hip/globals.h"
# include "kernel/device/gpu/image.h"
# include "kernel/device/gpu/kernel.h"
#endif

View File

@@ -0,0 +1,144 @@
# SPDX-FileCopyrightText: 2011-2026 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
set(INC
../../..
)
set(INC_SYS
)
set(SRC_KERNEL_DEVICE_HIPRT
kernel.cpp
)
set(SRC_KERNEL_DEVICE_HIPRT_HEADERS
bvh.h
globals.h
)
set(SRC_KERNEL_DEVICE_HIPRT_SDK_HEADERS
hiprt/hiprt_common.h
hiprt/hiprt_device.h
hiprt/hiprt_types.h
hiprt/hiprt_vec.h
hiprt/hiprt_math.h
)
set(SRC_KERNEL_DEVICE_HIPRT_SDK
hiprt/impl/Aabb.h
hiprt/impl/BvhNode.h
hiprt/impl/Geometry.h
hiprt/impl/hiprt_device_impl.h
hiprt/impl/hiprt_kernels_bitcode.h
hiprt/impl/Instance.h
hiprt/impl/QrDecomposition.h
hiprt/impl/Quaternion.h
hiprt/impl/Scene.h
hiprt/impl/Transform.h
hiprt/impl/Triangle.h
)
set(LIB
)
if(WITH_CYCLES_HIP_BINARIES AND WITH_CYCLES_DEVICE_HIPRT)
set(HIPRT_COMPILER_PARALLEL_JOBS 1 CACHE STRING "Number of parallel compiler instances to use for HIP-RT kernels")
mark_as_advanced(HIPRT_COMPILER_PARALLEL_JOBS)
if(WIN32)
set(hiprt_compile_command ${CMAKE_COMMAND})
set(hiprt_compile_flags
-E env "HIP_PATH=${HIP_ROOT_DIR}"
${HIP_HIPCC_EXECUTABLE})
else()
set(hiprt_compile_command ${HIP_HIPCC_EXECUTABLE})
set(hiprt_compile_flags "")
endif()
if(WITH_NANOVDB)
set(hiprt_compile_flags ${hiprt_compile_flags} -D WITH_NANOVDB)
endif()
if(WITH_CYCLES_DEBUG)
set(hiprt_compile_flags ${hiprt_compile_flags} -D WITH_CYCLES_DEBUG)
endif()
if(WIN32 AND (${HIP_VERSION} STREQUAL "6.1.40252"))
message(WARNING "HIP SDK ${HIP_VERSION} has known rendering artifacts with HIPRT. 5.7 is recommended instead")
endif()
set(hiprt_sources
kernel.cpp
${SRC_KERNEL_DEVICE_HIPRT_HEADERS}
$<TARGET_PROPERTY:cycles_kernel,INTERFACE_SOURCES>
)
set(kernel_sources ${hiprt_sources})
set(hiprt_kernel_src "/kernel.cpp")
set(hiprt_compile_flags
${hiprt_compile_flags}
${HIP_HIPCC_FLAGS}
${CMAKE_CURRENT_SOURCE_DIR}${hiprt_kernel_src}
-D CCL_NAMESPACE_BEGIN=
-D CCL_NAMESPACE_END=
-D HIPCC
-D __KERNEL_HIPRT__
-std=c++17
-mllvm
-amdgpu-early-inline-all=false
-mllvm
-amdgpu-function-calls=true
-parallel-jobs=${HIPRT_COMPILER_PARALLEL_JOBS}
--genco
-I ${CMAKE_CURRENT_SOURCE_DIR}/../../..
-I ${HIPRT_INCLUDE_DIR}
-Wno-parentheses-equality
-Wno-unused-value
-ffast-math
)
set(hiprt_hipfb "")
foreach(arch ${CYCLES_HIP_BINARIES_ARCH})
set(hiprt_file ${CMAKE_CURRENT_BINARY_DIR}/kernel_rt_${arch}.hipfb)
set(hiprt_file_compressed ${hiprt_file}.zst)
set(hiprt_flags
${hiprt_compile_flags}
--offload-arch=${arch}
-o ${hiprt_file})
add_custom_command(
OUTPUT ${hiprt_file}
COMMAND ${hiprt_compile_command} ${hiprt_flags}
DEPENDS ${kernel_sources}
)
add_custom_command(
OUTPUT ${hiprt_file_compressed}
COMMAND "$<TARGET_FILE:zstd_compress>" ${hiprt_file} ${hiprt_file_compressed}
DEPENDS ${hiprt_file}
)
delayed_install("${CMAKE_CURRENT_BINARY_DIR}" "${hiprt_file_compressed}" ${CYCLES_INSTALL_PATH}/lib)
list(APPEND hiprt_hipfb ${hiprt_file_compressed})
endforeach()
add_custom_target(cycles_kernel_hiprt
ALL
DEPENDS ${hiprt_hipfb}
SOURCES ${SRC_KERNEL_DEVICE_HIPRT} ${SRC_KERNEL_DEVICE_HIPRT_HEADERS}
)
cycles_set_solution_folder(cycles_kernel_hiprt)
source_group("device\\hiprt" FILES ${SRC_KERNEL_DEVICE_HIPRT} ${SRC_KERNEL_DEVICE_HIPRT_HEADERS})
add_dependencies(cycles_kernel cycles_kernel_hip)
endif()
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_DEVICE_HIPRT}" ${CYCLES_INSTALL_PATH}/source/kernel/device/hiprt)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_DEVICE_HIPRT_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/device/hiprt)
if(WITH_CYCLES_DEVICE_HIPRT)
delayed_install(${HIPRT_INCLUDE_DIR} "${SRC_KERNEL_DEVICE_HIPRT_SDK_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/device/hiprt/hiprt)
delayed_install(${HIPRT_INCLUDE_DIR} "${SRC_KERNEL_DEVICE_HIPRT_SDK}" ${CYCLES_INSTALL_PATH}/source/kernel/device/hiprt/hiprt/impl)
endif()

View File

@@ -0,0 +1,740 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/bvh/intersect_filter.h"
CCL_NAMESPACE_BEGIN
struct LocalPayload {
RaySelfPrimitives self;
float ray_time;
int local_object;
uint max_hits;
uint *lcg_state;
LocalIntersection *local_isect;
};
/* --------------------------------------------------------------------
* Utilities.
*/
ccl_device_forceinline void set_hiprt_ray(const ccl_private Ray &ray,
ccl_private hiprtRay &ray_hip)
{
ray_hip.direction = ray.D;
ray_hip.origin = ray.P;
ray_hip.maxT = ray.tmax;
ray_hip.minT = ray.tmin;
}
ccl_device_inline void set_intersect_point(const hiprtHit &hit, ccl_private Intersection *isect)
{
const int object = kernel_data_fetch(user_instance_id, hit.instanceID);
isect->t = hit.t;
isect->u = hit.uv.x;
isect->v = hit.uv.y;
isect->object = object;
isect->type = kernel_data_fetch(objects, object).primitive_type;
if (isect->type & PRIMITIVE_CURVE) {
/* For curves the isect->type is a packed segment information, which is different from the
* primitive type associated with the object. */
/* TODO(sergey): Try to solve this with less fetches.
*
* Ideally avoid having HIP-RT specific custom_prim_info tables, allowing them to be removed
* in order to minimize the memory usage. */
const int2 data_offset = kernel_data_fetch(custom_prim_info_offset, object);
const int2 prim_info = kernel_data_fetch(custom_prim_info, hit.primID + data_offset.x);
isect->prim = prim_info.x + data_offset.y;
isect->type = prim_info.y;
}
else {
const int prim_offset = kernel_data_fetch(object_prim_offset, object);
isect->prim = hit.primID + prim_offset;
}
}
/* --------------------------------------------------------------------
* Custom intersection functions.
*/
ccl_device_inline bool curve_custom_intersect(const hiprtRay &ray,
BVHPayload *payload,
hiprtHit &hit)
{
/* Could also cast shadow payload to get the elements needed to do the intersection no need to
* write a separate function for shadow intersection. */
KernelGlobals kg = nullptr;
const int object_id = kernel_data_fetch(user_instance_id, hit.instanceID);
/* `data_offset.x`: where the data (prim id, type )for the geometry of the current object begins
* the prim_id that is in hiprtHit hit is local to the particular geometry so we add the above
* `ofstream` to map prim id in hiprtHit to the one compatible to what next stage expects
* `data_offset.y`: the offset that has to be added to a local primitive to get the global
* `primitive id = kernel_data_fetch(object_prim_offset, object_id);` */
const int2 data_offset = kernel_data_fetch(custom_prim_info_offset, object_id);
const int prim_offset = data_offset.y;
const int2 prim_info = kernel_data_fetch(custom_prim_info, hit.primID + data_offset.x);
const int curve_index = prim_info.x;
const int key_value = prim_info.y;
#ifdef __SHADOW_LINKING__
if (intersection_skip_shadow_link(kg, payload->ray_self, object_id)) {
return false; /* Ignore hit - continue traversal. */
}
#endif
if (intersection_skip_self_shadow(payload->ray_self, object_id, curve_index + prim_offset)) {
return false;
}
const float ray_time = payload->ray_time;
if ((key_value & PRIMITIVE_MOTION) && kernel_data.bvh.use_bvh_steps) {
const int time_offset = kernel_data_fetch(prim_time_offset, object_id);
const float2 prims_time = kernel_data_fetch(prims_time, hit.primID + time_offset);
if (ray_time < prims_time.x || ray_time > prims_time.y) {
return false;
}
}
Intersection isect;
const bool b_hit = curve_intersect(kg,
&isect,
ray.origin,
ray.direction,
ray.minT,
ray.maxT,
object_id,
curve_index + prim_offset,
ray_time,
key_value);
if (b_hit) {
hit.uv.x = isect.u;
hit.uv.y = isect.v;
hit.t = isect.t;
}
return b_hit;
}
ccl_device_inline bool motion_triangle_custom_intersect(const hiprtRay &ray,
BVHPayload *payload,
hiprtHit &hit)
{
KernelGlobals kg = nullptr;
const int object_id = kernel_data_fetch(user_instance_id, hit.instanceID);
const int2 data_offset = kernel_data_fetch(custom_prim_info_offset, object_id);
const int prim_offset = kernel_data_fetch(object_prim_offset, object_id);
const int prim_id_local = kernel_data_fetch(custom_prim_info, hit.primID + data_offset.x).x;
const int prim_id_global = prim_id_local + prim_offset;
if (intersection_skip_self_shadow(payload->ray_self, object_id, prim_id_global)) {
return false;
}
Intersection isect;
const bool b_hit = motion_triangle_intersect(kg,
&isect,
ray.origin,
ray.direction,
ray.minT,
ray.maxT,
payload->ray_time,
payload->ray_visibility,
object_id,
prim_id_global,
hit.instanceID);
if (b_hit) {
hit.uv.x = isect.u;
hit.uv.y = isect.v;
hit.t = isect.t;
}
return b_hit;
}
ccl_device_inline bool motion_triangle_custom_local_intersect(const hiprtRay &ray,
LocalPayload *payload,
hiprtHit &hit)
{
#ifdef __OBJECT_MOTION__
KernelGlobals kg = nullptr;
const int object_id = payload->local_object;
const int prim_offset = kernel_data_fetch(object_prim_offset, object_id);
const int2 data_offset = kernel_data_fetch(custom_prim_info_offset, object_id);
const int prim_id_local = kernel_data_fetch(custom_prim_info, hit.primID + data_offset.x).x;
const int prim_id_global = prim_id_local + prim_offset;
if (intersection_skip_self_local(payload->self, prim_id_global)) {
return false;
}
return motion_triangle_intersect_local(kg,
payload->local_isect,
ray.origin,
ray.direction,
payload->ray_time,
object_id,
prim_id_global,
ray.minT,
ray.maxT,
payload->lcg_state,
payload->max_hits);
#else
return false;
#endif
}
ccl_device_inline bool motion_triangle_custom_volume_intersect(const hiprtRay &ray,
BVHPayload *payload,
hiprtHit &hit)
{
#ifdef __OBJECT_MOTION__
KernelGlobals kg = nullptr;
const int object = kernel_data_fetch(user_instance_id, hit.instanceID);
const int2 data_offset = kernel_data_fetch(custom_prim_info_offset, object);
const int prim_offset = kernel_data_fetch(object_prim_offset, object);
const int prim_id_local = kernel_data_fetch(custom_prim_info, hit.primID + data_offset.x).x;
const int prim = prim_id_local + prim_offset;
if (bvh_volume_anyhit_triangle_filter(
kg, object, prim, payload->ray_self, payload->ray_visibility))
{
return false;
}
Intersection isect;
const bool b_hit = motion_triangle_intersect(kg,
&isect,
ray.origin,
ray.direction,
ray.minT,
ray.maxT,
payload->ray_time,
payload->ray_visibility,
object,
prim,
prim_id_local);
if (b_hit) {
hit.uv.x = isect.u;
hit.uv.y = isect.v;
hit.t = isect.t;
}
return b_hit;
#else
return false;
#endif
}
ccl_device_inline bool point_custom_intersect(const hiprtRay &ray,
BVHPayload *payload,
hiprtHit &hit)
{
#if defined(__POINTCLOUD__)
KernelGlobals kg = nullptr;
const int object_id = kernel_data_fetch(user_instance_id, hit.instanceID);
const int2 data_offset = kernel_data_fetch(custom_prim_info_offset, object_id);
const int prim_offset = kernel_data_fetch(object_prim_offset, object_id);
const int2 prim_info = kernel_data_fetch(custom_prim_info, hit.primID + data_offset.x);
const int prim_id_local = prim_info.x;
const int prim_id_global = prim_id_local + prim_offset;
const int primitive_type = prim_info.y;
# ifdef __SHADOW_LINKING__
if (intersection_skip_shadow_link(kg, payload->ray_self, object_id)) {
return false; /* Ignore hit - continue traversal */
}
# endif
if (intersection_skip_self_shadow(payload->ray_self, object_id, prim_id_global)) {
return false;
}
const float ray_time = payload->ray_time;
if ((primitive_type & PRIMITIVE_MOTION_POINT) && kernel_data.bvh.use_bvh_steps) {
const int time_offset = kernel_data_fetch(prim_time_offset, object_id);
const float2 prims_time = kernel_data_fetch(prims_time, hit.primID + time_offset);
if (ray_time < prims_time.x || ray_time > prims_time.y) {
return false;
}
}
Intersection isect;
const bool b_hit = point_intersect(kg,
&isect,
ray.origin,
ray.direction,
ray.minT,
ray.maxT,
object_id,
prim_id_global,
ray_time,
primitive_type);
if (b_hit) {
hit.uv.x = isect.u;
hit.uv.y = isect.v;
hit.t = isect.t;
}
return b_hit;
#else
return false;
#endif
}
/* --------------------------------------------------------------------
* Intersection filters.
*/
ccl_device_inline bool closest_intersection_filter(const hiprtRay &ray,
BVHPayload *payload,
const hiprtHit &hit)
{
KernelGlobals kg = nullptr;
const int object_id = kernel_data_fetch(user_instance_id, hit.instanceID);
const int prim_offset = kernel_data_fetch(object_prim_offset, object_id);
const int prim = hit.primID + prim_offset;
#ifdef __SHADOW_LINKING__
if (intersection_skip_shadow_link(kg, payload->ray_self, object_id)) {
return true; /* Ignore hit - continue traversal. */
}
#endif
if (intersection_skip_self_shadow(payload->ray_self, object_id, prim)) {
return true; /* Ignore hit - continue traversal. */
}
return false;
}
ccl_device_inline bool shadow_intersection_filter(const hiprtRay &ray,
BVHShadowAllPayload *payload,
const hiprtHit &hit)
{
KernelGlobals kg = nullptr;
Intersection isect;
set_intersect_point(hit, &isect);
return bvh_shadow_all_anyhit_filter<ISECT_TEST_ALL, PRIMITIVE_ALL & ~PRIMITIVE_CURVE>(
kg, payload->state, *payload, payload->ray_self, payload->ray_visibility, isect);
}
ccl_device_inline bool shadow_intersection_filter_curve(const hiprtRay &ray,
BVHShadowAllPayload *payload,
const hiprtHit &hit)
{
KernelGlobals kg = nullptr;
Intersection isect;
set_intersect_point(hit, &isect);
return bvh_shadow_all_anyhit_filter<ISECT_TEST_ALL, PRIMITIVE_CURVE>(
kg, payload->state, *payload, payload->ray_self, payload->ray_visibility, isect);
}
ccl_device_inline bool local_intersection_filter(const hiprtRay &ray,
LocalPayload *payload,
const hiprtHit &hit)
{
#ifdef __BVH_LOCAL__
KernelGlobals kg = nullptr;
const int object_id = payload->local_object;
const uint max_hits = payload->max_hits;
/* Triangle primitive uses hardware intersection, other primitives do custom intersection
* which does reservoir sampling for intersections. For the custom primitives only check
* whether we can stop traversal early on. The rest of the checks here only do for the
* regular triangles. */
const int primitive_type = kernel_data_fetch(objects, object_id).primitive_type;
if (primitive_type != PRIMITIVE_TRIANGLE) {
if (max_hits == 0) {
return false;
}
return true;
}
const int prim_offset = kernel_data_fetch(object_prim_offset, object_id);
const int prim = hit.primID + prim_offset;
# ifndef __RAY_OFFSET__
if (intersection_skip_self_local(payload->self, prim)) {
return true; /* Continue search. */
}
# endif
if (max_hits == 0) {
return false; /* Stop search. */
}
const int hit_index = local_intersect_get_record_index(
payload->local_isect, hit.t, payload->lcg_state, max_hits);
if (hit_index == -1) {
return true; /* Continue search. */
}
Intersection *isect = &payload->local_isect->hits[hit_index];
isect->t = hit.t;
isect->u = hit.uv.x;
isect->v = hit.uv.y;
isect->prim = prim;
isect->object = object_id;
isect->type = primitive_type;
payload->local_isect->Ng[hit_index] = hit.normal;
return true;
#else
return false;
#endif
}
ccl_device_inline bool volume_triangle_intersection_filter(const hiprtRay &ray,
BVHPayload *payload,
const hiprtHit &hit)
{
KernelGlobals kg = nullptr;
const int object = kernel_data_fetch(user_instance_id, hit.instanceID);
const int prim_offset = kernel_data_fetch(object_prim_offset, object);
const int prim = hit.primID + prim_offset;
if (bvh_volume_anyhit_triangle_filter(
kg, object, prim, payload->ray_self, payload->ray_visibility))
{
return true;
}
return false;
}
HIPRT_DEVICE bool intersectFunc(const uint geom_type,
const uint ray_type,
const hiprtFuncTableHeader &tableHeader,
const hiprtRay &ray,
void *payload,
hiprtHit &hit)
{
const uint index = tableHeader.numGeomTypes * ray_type + geom_type;
switch (index) {
case Curve_Intersect_Function:
case Curve_Intersect_Shadow:
return curve_custom_intersect(ray, (BVHPayload *)payload, hit);
case Motion_Triangle_Intersect_Function:
case Motion_Triangle_Intersect_Shadow:
return motion_triangle_custom_intersect(ray, (BVHPayload *)payload, hit);
case Motion_Triangle_Intersect_Local:
return motion_triangle_custom_local_intersect(ray, (LocalPayload *)payload, hit);
case Motion_Triangle_Intersect_Volume:
return motion_triangle_custom_volume_intersect(ray, (BVHPayload *)payload, hit);
case Point_Intersect_Function:
case Point_Intersect_Shadow:
return point_custom_intersect(ray, (BVHPayload *)payload, hit);
default:
break;
}
return false;
}
HIPRT_DEVICE bool filterFunc(const uint geom_type,
const uint ray_type,
const hiprtFuncTableHeader &tableHeader,
const hiprtRay &ray,
void *payload,
const hiprtHit &hit)
{
const uint index = tableHeader.numGeomTypes * ray_type + geom_type;
switch (index) {
case Triangle_Filter_Closest:
return closest_intersection_filter(ray, (BVHPayload *)payload, hit);
case Curve_Filter_Shadow:
return shadow_intersection_filter_curve(ray, (BVHShadowAllPayload *)payload, hit);
case Triangle_Filter_Shadow:
case Motion_Triangle_Filter_Shadow:
case Point_Filter_Shadow:
return shadow_intersection_filter(ray, (BVHShadowAllPayload *)payload, hit);
case Triangle_Filter_Local:
case Motion_Triangle_Filter_Local:
return local_intersection_filter(ray, (LocalPayload *)payload, hit);
case Triangle_Filter_Volume:
return volume_triangle_intersection_filter(ray, (BVHPayload *)payload, hit);
case Motion_Triangle_Filter_Volume:
/* Motion triangle volume filtering is done in the custom intersection function. */
return false;
default:
break;
}
return false;
}
/* --------------------------------------------------------------------
* BVH functions.
*/
ccl_device_intersect bool scene_intersect(KernelGlobals kg,
const ccl_private Ray *ray,
const uint visibility,
ccl_private Intersection *isect)
{
isect->t = ray->tmax;
isect->u = 0.0f;
isect->v = 0.0f;
isect->prim = PRIM_NONE;
isect->object = OBJECT_NONE;
isect->type = PRIMITIVE_NONE;
if (!intersection_ray_valid(ray)) {
isect->t = ray->tmax;
isect->type = PRIMITIVE_NONE;
return false;
}
if (kernel_data.device_bvh == 0) {
return false;
}
hiprtRay ray_hip;
set_hiprt_ray(*ray, ray_hip);
BVHPayload payload;
payload.ray_self = ray->self;
payload.ray_visibility = visibility;
payload.ray_time = ray->time;
Stack stack(kg->global_stack_buffer, kg->shared_stack);
Instance_Stack instance_stack;
hiprtHit hit;
if (visibility & PATH_RAY_VISIBILITY_SHADOW_OPAQUE) {
hiprtSceneTraversalAnyHitCustomStack traversal((hiprtScene)kernel_data.device_bvh,
ray_hip,
stack,
instance_stack,
visibility,
hiprtTraversalHintDefault,
&payload,
kernel_params.table_closest_intersect,
0 /* RAY_TYPE */,
ray->time);
hit = traversal.getNextHit();
}
else {
hiprtSceneTraversalClosestCustomStack traversal((hiprtScene)kernel_data.device_bvh,
ray_hip,
stack,
instance_stack,
visibility,
hiprtTraversalHintDefault,
&payload,
kernel_params.table_closest_intersect,
0 /* RAY_TYPE */,
ray->time);
hit = traversal.getNextHit();
}
if (hit.hasHit()) {
set_intersect_point(hit, isect);
return true;
}
return false;
}
ccl_device_intersect bool scene_intersect_shadow(KernelGlobals kg,
const ccl_private Ray *ray,
const uint visibility)
{
Intersection isect;
return scene_intersect(kg, ray, visibility, &isect);
}
#ifdef __BVH_LOCAL__
template<bool single_hit = false>
ccl_device_intersect bool scene_intersect_local(KernelGlobals kg,
const ccl_private Ray *ray,
ccl_private LocalIntersection *local_isect,
const int local_object,
ccl_private uint *lcg_state,
const int max_hits)
{
if (local_isect != nullptr) {
local_isect->num_hits = 0;
}
if (!intersection_ray_valid(ray)) {
return false;
}
const int primitive_type = kernel_data_fetch(objects, local_object).primitive_type;
if (!(primitive_type & PRIMITIVE_TRIANGLE)) {
/* Local intersection functions are only considering triangle and motion triangle primitives.
* If the local intersection is requested from other primitives (curve or point cloud) perform
* an early return to avoid tree traversal with no primitive intersection. */
return false;
}
float3 P = ray->P;
float3 dir = bvh_clamp_direction(ray->D);
float3 idir = bvh_inverse_direction(dir);
const uint object_flag = kernel_data_fetch(object_flag, local_object);
if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
# ifdef __OBJECT_MOTION__
bvh_instance_motion_push(kg, local_object, ray, &P, &dir, &idir);
# else
bvh_instance_push(kg, local_object, ray, &P, &dir, &idir);
# endif
}
hiprtRay ray_hip;
ray_hip.origin = P;
ray_hip.direction = dir;
ray_hip.maxT = ray->tmax;
ray_hip.minT = ray->tmin;
LocalPayload payload = {0};
payload.self = ray->self;
payload.ray_time = ray->time;
payload.local_object = local_object;
payload.max_hits = max_hits;
payload.lcg_state = lcg_state;
payload.local_isect = local_isect;
Stack stack(kg->global_stack_buffer, kg->shared_stack);
Instance_Stack instance_stack;
hiprtGeometry local_geom = (hiprtGeometry)(kernel_data_fetch(blas_ptr, local_object));
hiprtHit hit;
if (primitive_type == PRIMITIVE_MOTION_TRIANGLE) {
/* Motion triangle BVH uses custom primitives which requires custom traversal. */
hiprtGeomCustomTraversalAnyHitCustomStack traversal(local_geom,
ray_hip,
stack,
hiprtTraversalHintDefault,
&payload,
kernel_params.table_local_intersect,
2);
hit = traversal.getNextHit();
}
else {
hiprtGeomTraversalAnyHitCustomStack traversal(local_geom,
ray_hip,
stack,
hiprtTraversalHintDefault,
&payload,
kernel_params.table_local_intersect,
2);
hit = traversal.getNextHit();
}
return hit.hasHit();
}
#endif /*__BVH_LOCAL__ */
#ifdef __TRANSPARENT_SHADOWS__
ccl_device_inline void scene_intersect_shadow_all_hiprt(
KernelGlobals kg,
const ccl_private Ray *ccl_restrict ray,
ccl_private BVHShadowAllPayload &ccl_restrict payload)
{
hiprtRay ray_hip;
set_hiprt_ray(*ray, ray_hip);
Stack stack(kg->global_stack_buffer, kg->shared_stack);
Instance_Stack instance_stack;
hiprtSceneTraversalAnyHitCustomStack traversal((hiprtScene)kernel_data.device_bvh,
ray_hip,
stack,
instance_stack,
payload.ray_visibility,
hiprtTraversalHintDefault,
&payload,
kernel_params.table_shadow_intersect,
1 /* RAY_TYPE */,
ray->time);
const hiprtHit hit = traversal.getNextHit();
(void)hit;
}
#endif /* __TRANSPARENT_SHADOWS__ */
#ifdef __VOLUME__
ccl_device_intersect bool scene_intersect_volume(KernelGlobals kg,
const ccl_private Ray *ray,
ccl_private Intersection *isect,
const uint visibility)
{
isect->t = ray->tmax;
isect->u = 0.0f;
isect->v = 0.0f;
isect->prim = PRIM_NONE;
isect->object = OBJECT_NONE;
isect->type = PRIMITIVE_NONE;
if (!intersection_ray_valid(ray)) {
return false;
}
hiprtRay ray_hip;
set_hiprt_ray(*ray, ray_hip);
BVHPayload payload;
payload.ray_self = ray->self;
payload.ray_visibility = visibility;
payload.ray_time = ray->time;
Stack stack(kg->global_stack_buffer, kg->shared_stack);
Instance_Stack instance_stack;
hiprtSceneTraversalClosestCustomStack traversal((hiprtScene)kernel_data.device_bvh,
ray_hip,
stack,
instance_stack,
visibility,
hiprtTraversalHintDefault,
&payload,
kernel_params.table_volume_intersect,
3 /* RAY_TYPE */,
ray->time);
const hiprtHit hit = traversal.getNextHit();
if (hit.hasHit()) {
set_intersect_point(hit, isect);
return true;
}
return false;
}
#endif /* __VOLUME__ */
CCL_NAMESPACE_END

View File

@@ -0,0 +1,153 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* Constant Globals */
#pragma once
#include "kernel/types.h"
#include "kernel/integrator/state.h"
#include "kernel/util/profiler.h" // IWYU pragma: export
#include "util/color.h" // IWYU pragma: export
#include "util/types_image.h" // IWYU pragma: export
/* The size of global stack available to each thread (memory reserved for each thread in
* global_stack_buffer). */
#define HIPRT_THREAD_STACK_SIZE 64
/* LDS (Local Data Storage) allocation for each thread, the number is obtained empirically. */
#define HIPRT_SHARED_STACK_SIZE 24
/* HIPRT_THREAD_GROUP_SIZE is the number of threads per work group for intersection kernels
* The default number of threads per work-group is 1024, however, since HIP RT intersection kernels
* use local memory, and the local memory size in those kernels scales up with the number of
* threads, the number of threads to is scaled down to 256 to avoid going over maximum local memory
* and to strike a balance between memory access and the number of waves.
*
* Total local stack size would be number of threads * HIPRT_SHARED_STACK_SIZE. */
#define HIPRT_THREAD_GROUP_SIZE 256
CCL_NAMESPACE_BEGIN
struct KernelGlobalsGPU {
hiprtGlobalStackBuffer global_stack_buffer;
hiprtSharedStackBuffer shared_stack;
};
using KernelGlobals = ccl_global KernelGlobalsGPU *ccl_restrict;
/* This macro allocates shared memory and to pass the shared memory down to intersection functions
* KernelGlobals is used. */
#define HIPRT_INIT_KERNEL_GLOBAL() \
ccl_gpu_shared int shared_stack[HIPRT_SHARED_STACK_SIZE * HIPRT_THREAD_GROUP_SIZE]; \
ccl_global KernelGlobalsGPU kg_gpu; \
KernelGlobals kg = &kg_gpu; \
kg->shared_stack.stackData = &shared_stack[0]; \
kg->shared_stack.stackSize = HIPRT_SHARED_STACK_SIZE; \
kg->global_stack_buffer = stack_buffer;
struct KernelParamsHIPRT {
KernelData data;
#define KERNEL_DATA_ARRAY(type, name) const type *name;
#define KERNEL_DATA_ARRAY_WRITABLE(type, name) type *name;
KERNEL_DATA_ARRAY(int, user_instance_id)
KERNEL_DATA_ARRAY(uint64_t, blas_ptr)
KERNEL_DATA_ARRAY(int2, custom_prim_info)
KERNEL_DATA_ARRAY(int2, custom_prim_info_offset)
KERNEL_DATA_ARRAY(float2, prims_time)
KERNEL_DATA_ARRAY(int, prim_time_offset)
#include "kernel/data_arrays.h"
/* Integrator state */
IntegratorStateGPU integrator_state;
hiprtFuncTable table_closest_intersect;
hiprtFuncTable table_shadow_intersect;
hiprtFuncTable table_local_intersect;
hiprtFuncTable table_volume_intersect;
};
/* Intersection_Function_Table_Index defines index values to retrieve custom intersection
* functions from function table. */
enum Intersection_Function_Table_Index {
// Triangles use the intersection function provided by HIP RT and don't need custom intersection
// functions
// Custom intersection functions for closest intersect.
Curve_Intersect_Function = 1, // Custom intersection for curves
Motion_Triangle_Intersect_Function, // Custom intersection for triangles with vertex motion blur
// attributes.
Point_Intersect_Function, // Custom intersection for point cloud.
// Custom intersection functions for shadow rendering are the same as the function for closest
// intersect.
// However, the table indices are different
Triangle_Intersect_Shadow_None,
Curve_Intersect_Shadow,
Motion_Triangle_Intersect_Shadow,
Point_Intersect_Shadow,
// Custom intersection functions for subsurface scattering.
// Only motion triangles have valid custom intersection function
Triangle_Intersect_Local_None,
Curve_Intersect_Local_None,
Motion_Triangle_Intersect_Local,
Point_Intersect_Local_None,
// Custom intersection functions for volume rendering.
// Only motion triangles have valid custom intersection function
Triangle_Intersect_Volume_None,
Curve_Intersect_Volume_None,
Motion_Triangle_Intersect_Volume,
Point_Intersect_Volume_None,
};
// Filter functions, filter hits, i.e. test whether a hit should be accepted or not, and whether
// traversal should stop or continue.
enum Filter_Function_Table_Index {
Triangle_Filter_Closest = 0, // Filter function for triangles for closest intersect, no custom
// intersection function is needed.
Curve_Filter_Opaque_None, // No filter function is needed and everything is handled in the
// intersection function.
Motion_Triangle_Filter_Opaque_None, // No filter function is needed and everything is handled in
// intersection function.
Point_Filter_Opaque_Non, // No filter function is needed.
// Filter function for all primitives for shadow intersection.
// All primitives use the same function but each has a different index in the table.
Triangle_Filter_Shadow,
Curve_Filter_Shadow,
Motion_Triangle_Filter_Shadow,
Point_Filter_Shadow,
// Filter functions for subsurface scattering. Triangles and motion triangles need function
// assignment. They indices for triangles and motion triangles point to the same function. Points
// and curves dont need any function since subsurface scattering is not applied on either.
Triangle_Filter_Local, // Filter functions for triangles
Curve_Filter_Local_None, // Subsurface scattering is not applied on curves, no filter function
// is
// needed.
Motion_Triangle_Filter_Local,
Point_Filter_Local_None,
// Filter functions for volume rendering.
// Volume rendering only applies to triangles and motion triangles.
// Triangles and motion triangles use the same filter functions for volume rendering
Triangle_Filter_Volume,
Curve_Filter_Volume_None,
Motion_Triangle_Filter_Volume,
Point_Filter_Volume_None,
};
#ifdef __KERNEL_GPU__
__constant__ KernelParamsHIPRT kernel_params;
typedef hiprtGlobalStack Stack;
typedef hiprtEmptyInstanceStack Instance_Stack;
#endif
/* Abstraction macros */
#define kernel_data kernel_params.data
#define kernel_data_fetch(name, index) kernel_params.name[(index)]
#define kernel_data_write(name, index, value) kernel_params.name[(index)] = (value)
#define kernel_data_array(name) (kernel_params.name)
#define kernel_integrator_state kernel_params.integrator_state
CCL_NAMESPACE_END

View File

@@ -0,0 +1,140 @@
/* SPDX-FileCopyrightText: 2011-2023 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef __HIP_DEVICE_COMPILE__
# include "kernel/device/hip/compat.h"
# include "kernel/device/hip/config.h"
# include <hiprt/impl/hiprt_device_impl.h>
# include "kernel/device/hiprt/globals.h"
# include "kernel/device/gpu/image.h"
# include "kernel/tables.h"
# include "kernel/integrator/state.h"
# include "kernel/integrator/state_flow.h"
# include "kernel/integrator/state_util.h"
# include "kernel/integrator/intersect_closest.h"
# include "kernel/integrator/intersect_dedicated_light.h"
# include "kernel/integrator/intersect_mnee.h"
# include "kernel/integrator/intersect_shadow.h"
# include "kernel/integrator/intersect_subsurface.h"
# include "kernel/integrator/intersect_volume_stack.h"
# include "kernel/integrator/shade_surface.h"
ccl_gpu_kernel_threads(GPU_HIPRT_KERNEL_BLOCK_NUM_THREADS)
ccl_gpu_kernel_signature(integrator_intersect_closest,
const ccl_global int *path_index_array,
ccl_global float *render_buffer,
const int work_size,
ccl_global hiprtGlobalStackBuffer stack_buffer)
{
const int global_index = ccl_gpu_global_id_x();
if (global_index < work_size) {
HIPRT_INIT_KERNEL_GLOBAL()
const int state = (path_index_array) ? path_index_array[global_index] : global_index;
ccl_gpu_kernel_call(integrator_intersect_closest(kg, state, render_buffer));
}
}
ccl_gpu_kernel_postfix
ccl_gpu_kernel_threads(GPU_HIPRT_KERNEL_BLOCK_NUM_THREADS)
ccl_gpu_kernel_signature(integrator_intersect_shadow,
const ccl_global int *path_index_array,
const int work_size,
ccl_global hiprtGlobalStackBuffer stack_buffer)
{
const int global_index = ccl_gpu_global_id_x();
if (global_index < work_size) {
HIPRT_INIT_KERNEL_GLOBAL()
const int state = (path_index_array) ? path_index_array[global_index] : global_index;
ccl_gpu_kernel_call(integrator_intersect_shadow(kg, state));
}
}
ccl_gpu_kernel_postfix
ccl_gpu_kernel_threads(GPU_HIPRT_KERNEL_BLOCK_NUM_THREADS)
ccl_gpu_kernel_signature(integrator_intersect_subsurface,
const ccl_global int *path_index_array,
const int work_size,
ccl_global hiprtGlobalStackBuffer stack_buffer)
{
const int global_index = ccl_gpu_global_id_x();
if (global_index < work_size) {
HIPRT_INIT_KERNEL_GLOBAL()
const int state = (path_index_array) ? path_index_array[global_index] : global_index;
ccl_gpu_kernel_call(integrator_intersect_subsurface(kg, state));
}
}
ccl_gpu_kernel_postfix
ccl_gpu_kernel_threads(GPU_HIPRT_KERNEL_BLOCK_NUM_THREADS)
ccl_gpu_kernel_signature(integrator_intersect_volume_stack,
const ccl_global int *path_index_array,
const int work_size,
ccl_global hiprtGlobalStackBuffer stack_buffer)
{
const int global_index = ccl_gpu_global_id_x();
if (global_index < work_size) {
HIPRT_INIT_KERNEL_GLOBAL()
const int state = (path_index_array) ? path_index_array[global_index] : global_index;
ccl_gpu_kernel_call(integrator_intersect_volume_stack(kg, state));
}
}
ccl_gpu_kernel_threads(GPU_HIPRT_KERNEL_BLOCK_NUM_THREADS)
ccl_gpu_kernel_signature(integrator_intersect_dedicated_light,
const ccl_global int *path_index_array,
const int work_size,
ccl_global hiprtGlobalStackBuffer stack_buffer)
{
const int global_index = ccl_gpu_global_id_x();
if (global_index < work_size) {
HIPRT_INIT_KERNEL_GLOBAL()
const int state = (path_index_array) ? path_index_array[global_index] : global_index;
ccl_gpu_kernel_call(integrator_intersect_dedicated_light(kg, state));
}
}
ccl_gpu_kernel_postfix
ccl_gpu_kernel_threads(GPU_HIPRT_KERNEL_BLOCK_NUM_THREADS)
ccl_gpu_kernel_signature(integrator_shade_surface_raytrace,
const ccl_global int *path_index_array,
ccl_global float *render_buffer,
const int work_size,
ccl_global hiprtGlobalStackBuffer stack_buffer)
{
const int global_index = ccl_gpu_global_id_x();
if (global_index < work_size) {
HIPRT_INIT_KERNEL_GLOBAL()
const int state = (path_index_array) ? path_index_array[global_index] : global_index;
ccl_gpu_kernel_call(integrator_shade_surface_raytrace(kg, state, render_buffer));
}
}
ccl_gpu_kernel_postfix
ccl_gpu_kernel_threads(GPU_HIPRT_KERNEL_BLOCK_NUM_THREADS)
ccl_gpu_kernel_signature(integrator_intersect_mnee,
const ccl_global int *path_index_array,
const int work_size,
ccl_global hiprtGlobalStackBuffer stack_buffer)
{
const int global_index = ccl_gpu_global_id_x();
if (global_index < work_size) {
HIPRT_INIT_KERNEL_GLOBAL()
const int state = (path_index_array) ? path_index_array[global_index] : global_index;
ccl_gpu_kernel_call(integrator_intersect_mnee(kg, state));
}
}
ccl_gpu_kernel_postfix
#endif /* __HIP_DEVICE_COMPILE__ */

View File

@@ -0,0 +1,45 @@
# SPDX-FileCopyrightText: 2011-2026 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
set(INC
../../..
)
set(INC_SYS
)
set(SRC_KERNEL_DEVICE_METAL
kernel.metal
)
set(SRC_KERNEL_DEVICE_METAL_HEADERS
bvh.h
compat.h
context_begin.h
context_end.h
function_constants.h
globals.h
)
set(LIB
)
if(WITH_CYCLES_DEVICE_METAL)
add_library(cycles_kernel_metal INTERFACE)
target_sources(cycles_kernel_metal INTERFACE
${SRC_KERNEL_DEVICE_METAL}
${SRC_KERNEL_DEVICE_METAL_HEADERS}
)
cycles_set_solution_folder(cycles_kernel_metal)
source_group("device\\metal" FILES ${SRC_KERNEL_DEVICE_METAL} ${SRC_KERNEL_DEVICE_METAL_HEADERS})
add_dependencies(cycles_kernel cycles_kernel_metal)
endif()
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_DEVICE_METAL}" ${CYCLES_INSTALL_PATH}/source/kernel/device/metal)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_DEVICE_METAL_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/device/metal)

View File

@@ -0,0 +1,573 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* MetalRT implementation of ray-scene intersection. */
#pragma once
#include "kernel/bvh/types.h"
#include "kernel/bvh/util.h"
CCL_NAMESPACE_BEGIN
/* Payload types.
*
* Best practice is to minimize the size of MetalRT payloads to avoid heavy spilling during
* intersection tests.
*/
struct MetalRTIntersectionPayload {
int self_prim;
int self_object;
uint visibility;
};
struct MetalRTIntersectionLocalPayload_single_hit {
int self_prim;
#if defined(__METALRT_MOTION__)
int self_object;
#endif
};
struct MetalRTLocalHit {
uint prim;
float t, u, v;
};
/* Payload for the local intersection queries.
* It embeds a subset of storage that is typically found in the LocalIntersection. This is because
* it is not possible to store a pointer to the actual LocalIntersection in the payload. So some
* data is duplicated into the payload and then copied back to the LocalIntersection. */
struct MetalRTIntersectionLocalPayload {
int self_prim;
#if defined(__METALRT_MOTION__)
int self_object;
#endif
uint lcg_state;
MetalRTLocalHit hits[LOCAL_MAX_HITS];
int max_hits;
int num_hits;
bool has_lcg_state;
};
struct MetalRTIntersectionShadowPayload {
RaySelfPrimitives self;
uint visibility;
};
#ifdef __HAIR__
ccl_device_forceinline bool curve_ribbon_accept(KernelGlobals kg,
const float u,
float t,
const ccl_private Ray *ray,
const int object,
const int prim,
const int type)
{
KernelCurve kcurve = kernel_data_fetch(curves, prim);
int k0 = kcurve.first_key + PRIMITIVE_UNPACK_SEGMENT(type);
int k1 = k0 + 1;
int ka = max(k0 - 1, kcurve.first_key);
int kb = min(k1 + 1, kcurve.first_key + kcurve.num_keys - 1);
/* We can ignore motion blur here because we don't need the positions, and it doesn't affect the
* radius. */
const int position_offset = kernel_data_fetch(objects, object).position_offset;
float radius[4];
radius[0] = kernel_data_fetch(curve_keys, position_offset + ka).w;
radius[1] = kernel_data_fetch(curve_keys, position_offset + k0).w;
radius[2] = kernel_data_fetch(curve_keys, position_offset + k1).w;
radius[3] = kernel_data_fetch(curve_keys, position_offset + kb).w;
const float r = metal::catmull_rom(u, radius[0], radius[1], radius[2], radius[3]);
/* MPJ TODO: Can we ignore motion and/or object transforms here? Depends on scaling? */
float3 ray_P = ray->P;
float3 ray_D = ray->D;
if (!(kernel_data_fetch(object_flag, object) & SD_OBJECT_TRANSFORM_APPLIED)) {
float3 idir;
# if defined(__METALRT_MOTION__)
bvh_instance_motion_push(nullptr, object, ray, &ray_P, &ray_D, &idir);
# else
bvh_instance_push(nullptr, object, ray, &ray_P, &ray_D, &idir);
# endif
}
/* ignore self intersections */
const float avoidance_factor = 2.0f;
return t * len(ray_D) > avoidance_factor * r;
}
ccl_device_forceinline float curve_ribbon_v(KernelGlobals kg,
const float u,
float t,
const ccl_private Ray *ray,
const int object,
const int prim,
const int type)
{
# if defined(__METALRT_MOTION__)
float time = ray->time;
# else
float time = 0.0f;
# endif
const bool is_motion = (type & PRIMITIVE_MOTION);
KernelCurve kcurve = kernel_data_fetch(curves, prim);
int k0 = kcurve.first_key + PRIMITIVE_UNPACK_SEGMENT(type);
int k1 = k0 + 1;
int ka = max(k0 - 1, kcurve.first_key);
int kb = min(k1 + 1, kcurve.first_key + kcurve.num_keys - 1);
float4 curve[4];
if (!is_motion) {
const int position_offset = kernel_data_fetch(objects, object).position_offset;
curve[0] = kernel_data_fetch(curve_keys, position_offset + ka);
curve[1] = kernel_data_fetch(curve_keys, position_offset + k0);
curve[2] = kernel_data_fetch(curve_keys, position_offset + k1);
curve[3] = kernel_data_fetch(curve_keys, position_offset + kb);
}
else {
motion_curve_keys(kg, object, time, ka, k0, k1, kb, curve);
}
float3 ray_P = ray->P;
float3 ray_D = ray->D;
if (!(kernel_data_fetch(object_flag, object) & SD_OBJECT_TRANSFORM_APPLIED)) {
float3 idir;
# if defined(__METALRT_MOTION__)
bvh_instance_motion_push(nullptr, object, ray, &ray_P, &ray_D, &idir);
# else
bvh_instance_push(nullptr, object, ray, &ray_P, &ray_D, &idir);
# endif
}
const float4 P_curve4 = metal::catmull_rom(u, curve[0], curve[1], curve[2], curve[3]);
const float r_curve = P_curve4.w;
float3 P = ray_P + ray_D * t;
const float3 P_curve = make_float3(P_curve4);
const float4 dPdu4 = metal::catmull_rom_derivative(u, curve[0], curve[1], curve[2], curve[3]);
const float3 dPdu = make_float3(dPdu4);
const float3 tangent = normalize(dPdu);
const float3 bitangent = normalize(cross(tangent, -ray_D));
float v = dot(P - P_curve, bitangent) / r_curve;
return clamp(v, -1.0, 1.0f);
}
#endif /* __HAIR__ */
/* Scene intersection. */
ccl_device_intersect bool scene_intersect(KernelGlobals kg,
const ccl_private Ray *ray,
const uint visibility,
ccl_private Intersection *isect)
{
metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax);
metalrt_intersector_type metalrt_intersect;
metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque);
metalrt_intersect.assume_geometry_type(
metal::raytracing::geometry_type::triangle |
(kernel_data.bvh.have_curves ? metal::raytracing::geometry_type::curve :
metal::raytracing::geometry_type::none) |
(kernel_data.bvh.have_points ? metal::raytracing::geometry_type::bounding_box :
metal::raytracing::geometry_type::none));
typename metalrt_intersector_type::result_type intersection;
MetalRTIntersectionPayload payload;
payload.self_prim = ray->self.prim;
payload.self_object = ray->self.object;
payload.visibility = visibility;
uint ray_mask = visibility & 0xFF;
if (0 == ray_mask && (visibility & ~0xFF) != 0) {
ray_mask = 0xFF;
}
#if defined(__METALRT_MOTION__)
intersection = metalrt_intersect.intersect(r,
metal_ancillaries->accel_struct,
ray_mask,
ray->time,
metal_ancillaries->ift_default,
payload);
#else
intersection = metalrt_intersect.intersect(
r, metal_ancillaries->accel_struct, ray_mask, metal_ancillaries->ift_default, payload);
#endif
if (intersection.type == intersection_type::none) {
isect->t = ray->tmax;
isect->type = PRIMITIVE_NONE;
return false;
}
isect->object = intersection.instance_id;
isect->t = intersection.distance;
if (intersection.type == intersection_type::triangle) {
isect->prim = intersection.primitive_id + intersection.user_instance_id;
isect->type = kernel_data_fetch(objects, intersection.instance_id).primitive_type;
isect->u = intersection.triangle_barycentric_coord.x;
isect->v = intersection.triangle_barycentric_coord.y;
}
#ifdef __HAIR__
else if (kernel_data.bvh.have_curves && intersection.type == intersection_type::curve) {
int prim = intersection.primitive_id + intersection.user_instance_id;
const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim);
isect->prim = segment.prim;
isect->type = segment.type;
isect->u = intersection.curve_parameter;
if ((segment.type & PRIMITIVE_CURVE) == PRIMITIVE_CURVE_RIBBON) {
isect->v = curve_ribbon_v(kg,
intersection.curve_parameter,
intersection.distance,
ray,
intersection.instance_id,
segment.prim,
segment.type);
}
else {
isect->v = 0.0f;
}
}
#endif /* __HAIR__ */
#ifdef __POINTCLOUD__
else if (kernel_data.bvh.have_points && intersection.type == intersection_type::bounding_box) {
const int object = intersection.instance_id;
const uint prim = intersection.primitive_id + intersection.user_instance_id;
const int prim_type = kernel_data_fetch(objects, object).primitive_type;
if (!(kernel_data_fetch(object_flag, object) & SD_OBJECT_TRANSFORM_APPLIED)) {
float3 idir;
# if defined(__METALRT_MOTION__)
bvh_instance_motion_push(nullptr, object, ray, &r.origin, &r.direction, &idir);
# else
bvh_instance_push(nullptr, object, ray, &r.origin, &r.direction, &idir);
# endif
}
if (prim_type & PRIMITIVE_POINT) {
if (!point_intersect(nullptr,
isect,
r.origin,
r.direction,
ray->tmin,
ray->tmax,
object,
prim,
ray->time,
prim_type))
{
/* Shouldn't get here */
kernel_assert(!"Intersection mismatch");
isect->t = ray->tmax;
isect->type = PRIMITIVE_NONE;
return false;
}
return true;
}
}
#endif /* __POINTCLOUD__ */
return true;
}
ccl_device_intersect bool scene_intersect_shadow(KernelGlobals kg,
const ccl_private Ray *ray,
const uint visibility)
{
metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax);
metalrt_intersector_type metalrt_intersect;
metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque);
metalrt_intersect.assume_geometry_type(
metal::raytracing::geometry_type::triangle |
(kernel_data.bvh.have_curves ? metal::raytracing::geometry_type::curve :
metal::raytracing::geometry_type::none) |
(kernel_data.bvh.have_points ? metal::raytracing::geometry_type::bounding_box :
metal::raytracing::geometry_type::none));
typename metalrt_intersector_type::result_type intersection;
metalrt_intersect.accept_any_intersection(true);
MetalRTIntersectionShadowPayload payload;
payload.self = ray->self;
payload.visibility = visibility;
uint ray_mask = visibility & 0xFF;
if (0 == ray_mask && (visibility & ~0xFF) != 0) {
ray_mask = 0xFF;
}
#if defined(__METALRT_MOTION__)
intersection = metalrt_intersect.intersect(r,
metal_ancillaries->accel_struct,
ray_mask,
ray->time,
metal_ancillaries->ift_shadow,
payload);
#else
intersection = metalrt_intersect.intersect(
r, metal_ancillaries->accel_struct, ray_mask, metal_ancillaries->ift_shadow, payload);
#endif
return (intersection.type != intersection_type::none);
}
#ifdef __BVH_LOCAL__
template<bool single_hit = false>
ccl_device_intersect bool scene_intersect_local(KernelGlobals kg,
const ccl_private Ray *ray,
ccl_private LocalIntersection *local_isect,
const int local_object,
ccl_private uint *lcg_state,
const int max_hits)
{
uint primitive_id_offset = kernel_data_fetch(object_prim_offset, local_object);
metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax);
# if defined(__METALRT_MOTION__)
metalrt_intersector_type metalrt_intersect;
typename metalrt_intersector_type::result_type intersection;
# else
metalrt_blas_intersector_type metalrt_intersect;
typename metalrt_blas_intersector_type::result_type intersection;
if (!(kernel_data_fetch(object_flag, local_object) & SD_OBJECT_TRANSFORM_APPLIED)) {
/* Transform the ray into object's local space. */
Transform itfm = kernel_data_fetch(objects, local_object).itfm;
r.origin = transform_point(&itfm, r.origin);
r.direction = transform_direction(&itfm, r.direction);
}
# endif
metalrt_intersect.assume_geometry_type(metal::raytracing::geometry_type::triangle);
if (single_hit) {
MetalRTIntersectionLocalPayload_single_hit payload;
payload.self_prim = ray->self.prim - primitive_id_offset;
# if defined(__METALRT_MOTION__)
/* We can't skip over the top-level BVH in the motion blur case, so still need to do
* the self-object check. */
payload.self_object = local_object;
metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque);
intersection = metalrt_intersect.intersect(r,
metal_ancillaries->accel_struct,
~0,
ray->time,
metal_ancillaries->ift_local_single_hit_mblur,
payload);
# else
/* We only need custom intersection filtering (i.e. non_opaque) if we are performing a
* self-primitive intersection check. */
metalrt_intersect.force_opacity((ray->self.prim == PRIM_NONE) ?
metal::raytracing::forced_opacity::opaque :
metal::raytracing::forced_opacity::non_opaque);
intersection = metalrt_intersect.intersect(
r,
metal_ancillaries->blas_accel_structs[local_object].blas,
metal_ancillaries->ift_local_single_hit,
payload);
# endif
if (intersection.type == intersection_type::none) {
local_isect->num_hits = 0;
return false;
}
uint prim = intersection.primitive_id + primitive_id_offset;
int prim_type = kernel_data_fetch(objects, local_object).primitive_type;
local_isect->num_hits = 1;
local_isect->hits[0].prim = prim;
local_isect->hits[0].type = prim_type;
local_isect->hits[0].object = local_object;
local_isect->hits[0].u = intersection.triangle_barycentric_coord.x;
local_isect->hits[0].v = intersection.triangle_barycentric_coord.y;
local_isect->hits[0].t = intersection.distance;
const int position_offset = kernel_data_fetch(objects, local_object).position_offset;
const packed_uint3 tri_vindex = kernel_data_fetch(tri_vindex, prim);
const float3 tri_a = float3(kernel_data_fetch(tri_verts, position_offset + tri_vindex.x));
const float3 tri_b = float3(kernel_data_fetch(tri_verts, position_offset + tri_vindex.y));
const float3 tri_c = float3(kernel_data_fetch(tri_verts, position_offset + tri_vindex.z));
local_isect->Ng[0] = normalize(cross(tri_b - tri_a, tri_c - tri_a));
return true;
}
else {
MetalRTIntersectionLocalPayload payload;
payload.self_prim = ray->self.prim - primitive_id_offset;
payload.max_hits = max_hits;
payload.num_hits = 0;
if (lcg_state) {
payload.has_lcg_state = true;
payload.lcg_state = *lcg_state;
}
else {
payload.has_lcg_state = false;
}
metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque);
# if defined(__METALRT_MOTION__)
/* We can't skip over the top-level BVH in the motion blur case, so still need to do
* the self-object check. */
payload.self_object = local_object;
intersection = metalrt_intersect.intersect(r,
metal_ancillaries->accel_struct,
~0,
ray->time,
metal_ancillaries->ift_local_mblur,
payload);
# else
intersection = metalrt_intersect.intersect(
r,
metal_ancillaries->blas_accel_structs[local_object].blas,
metal_ancillaries->ift_local,
payload);
# endif
if (max_hits == 0) {
/* Special case for when no hit information is requested, just report that something was hit
*/
return (intersection.type != intersection_type::none);
}
if (lcg_state) {
*lcg_state = payload.lcg_state;
}
const int num_hits = payload.num_hits;
if (local_isect) {
/* Record geometric normal */
int prim_type = kernel_data_fetch(objects, local_object).primitive_type;
/* Number of hits counted can be higher than recorded due to reservoir sampling. */
local_isect->num_hits = num_hits;
const int num_recorded_hits = min(payload.num_hits, max_hits);
for (int hit = 0; hit < num_recorded_hits; hit++) {
const uint prim = payload.hits[hit].prim + primitive_id_offset;
local_isect->hits[hit].prim = prim;
local_isect->hits[hit].t = payload.hits[hit].t;
local_isect->hits[hit].u = payload.hits[hit].u;
local_isect->hits[hit].v = payload.hits[hit].v;
local_isect->hits[hit].object = local_object;
local_isect->hits[hit].type = prim_type;
const int position_offset = kernel_data_fetch(objects, local_object).position_offset;
const packed_uint3 tri_vindex = kernel_data_fetch(tri_vindex, prim);
const float3 tri_a = float3(kernel_data_fetch(tri_verts, position_offset + tri_vindex.x));
const float3 tri_b = float3(kernel_data_fetch(tri_verts, position_offset + tri_vindex.y));
const float3 tri_c = float3(kernel_data_fetch(tri_verts, position_offset + tri_vindex.z));
local_isect->Ng[hit] = normalize(cross(tri_b - tri_a, tri_c - tri_a));
}
}
return num_hits > 0;
}
}
#endif
#ifdef __TRANSPARENT_SHADOWS__
ccl_device_intersect void scene_intersect_shadow_all_metalrt(
const ccl_private Ray *ray, ccl_private BVHShadowAllPayload &ccl_restrict payload)
{
metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax);
metalrt_intersector_type metalrt_intersect;
metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque);
metalrt_intersect.assume_geometry_type(
metal::raytracing::geometry_type::triangle |
(kernel_data.bvh.have_curves ? metal::raytracing::geometry_type::curve :
metal::raytracing::geometry_type::none) |
(kernel_data.bvh.have_points ? metal::raytracing::geometry_type::bounding_box :
metal::raytracing::geometry_type::none));
uint ray_mask = payload.base.ray_visibility & 0xFF;
if (0 == ray_mask && (payload.base.ray_visibility & ~0xFF) != 0) {
ray_mask = 0xFF;
}
typename metalrt_intersector_type::result_type intersection;
# if defined(__METALRT_MOTION__)
intersection = metalrt_intersect.intersect(r,
metal_ancillaries->accel_struct,
ray_mask,
ray->time,
metal_ancillaries->ift_shadow_all,
payload);
# else
intersection = metalrt_intersect.intersect(
r, metal_ancillaries->accel_struct, ray_mask, metal_ancillaries->ift_shadow_all, payload);
# endif
(void)intersection;
}
#endif
#ifdef __VOLUME__
ccl_device_intersect bool scene_intersect_volume(KernelGlobals kg,
const ccl_private Ray *ray,
ccl_private Intersection *isect,
const uint visibility)
{
metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax);
metalrt_intersector_type metalrt_intersect;
metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque);
metalrt_intersect.set_geometry_cull_mode(metal::raytracing::geometry_cull_mode::bounding_box |
metal::raytracing::geometry_cull_mode::curve);
metalrt_intersect.assume_geometry_type(
metal::raytracing::geometry_type::triangle |
(kernel_data.bvh.have_curves ? metal::raytracing::geometry_type::curve :
metal::raytracing::geometry_type::none) |
(kernel_data.bvh.have_points ? metal::raytracing::geometry_type::bounding_box :
metal::raytracing::geometry_type::none));
MetalRTIntersectionShadowPayload payload;
payload.self = ray->self;
payload.visibility = visibility;
uint ray_mask = visibility & 0xFF;
if (0 == ray_mask && (visibility & ~0xFF) != 0) {
ray_mask = 0xFF;
}
typename metalrt_intersector_type::result_type intersection;
# if defined(__METALRT_MOTION__)
intersection = metalrt_intersect.intersect(r,
metal_ancillaries->accel_struct,
ray_mask,
ray->time,
metal_ancillaries->ift_volume,
payload);
# else
intersection = metalrt_intersect.intersect(
r, metal_ancillaries->accel_struct, ray_mask, metal_ancillaries->ift_volume, payload);
# endif
if (intersection.type == intersection_type::triangle) {
isect->prim = intersection.primitive_id + intersection.user_instance_id;
isect->type = kernel_data_fetch(objects, intersection.instance_id).primitive_type;
isect->u = intersection.triangle_barycentric_coord.x;
isect->v = intersection.triangle_barycentric_coord.y;
isect->object = intersection.instance_id;
isect->t = intersection.distance;
return true;
}
return false;
}
#endif
CCL_NAMESPACE_END

View File

@@ -0,0 +1,407 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#define __KERNEL_GPU__
#define __KERNEL_METAL__
#define CCL_NAMESPACE_BEGIN
#define CCL_NAMESPACE_END
#ifndef ATTR_FALLTHROUGH
# define ATTR_FALLTHROUGH
#endif
#include <metal_atomic>
#include <metal_pack>
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
#ifdef __KERNEL_METALRT__
using namespace metal::raytracing;
#endif
#pragma clang diagnostic ignored "-Wunused-variable"
#pragma clang diagnostic ignored "-Wsign-compare"
#pragma clang diagnostic ignored "-Wuninitialized"
#pragma clang diagnostic ignored "-Wc++17-extensions"
#pragma clang diagnostic ignored "-Wmacro-redefined"
/* Qualifiers */
#define ccl_device
#define ccl_device_inline ccl_device __attribute__((always_inline))
#define ccl_device_forceinline ccl_device __attribute__((always_inline))
#if defined(__KERNEL_METAL_APPLE__)
# define ccl_device_noinline ccl_device
#else
# define ccl_device_noinline ccl_device __attribute__((noinline))
#endif
#define ccl_device_extern extern "C"
#define ccl_device_noinline_cpu ccl_device
#define ccl_device_inline_method ccl_device
#define ccl_device_template_spec template<> ccl_device_inline
#define ccl_global device
#define ccl_inline_constant static constant constexpr
#define ccl_device_constant constant
#define ccl_static_constexpr static constant constexpr
#define ccl_constant constant
#define ccl_gpu_shared threadgroup
#define ccl_private thread
#ifdef __KERNEL_METALRT__
# define ccl_ray_data ray_data
#else
# define ccl_ray_data ccl_private
#endif
#define ccl_may_alias
#define ccl_restrict __restrict
#define ccl_align(n) alignas(n)
#define ccl_optional_struct_init
#define ccl_attr_maybe_unused
// Not supported by older MacOS versions (e.g., 13.0)
// #define ccl_attr_maybe_unused [[maybe_unused]]
/* No assert supported for Metal */
#define kernel_assert(cond)
#define offsetof(t, d) __builtin_offsetof(t, d)
#define ccl_gpu_global_id_x() metal_global_id
#define ccl_gpu_warp_size simdgroup_size
#define ccl_gpu_thread_idx_x simd_group_index
#define ccl_gpu_thread_mask(thread_warp) uint64_t((1ull << thread_warp) - 1)
#define ccl_gpu_ballot(predicate) ((uint64_t)((simd_vote::vote_t)simd_ballot(predicate)))
#define ccl_gpu_syncthreads() threadgroup_barrier(mem_flags::mem_threadgroup);
// clang-format off
/* kernel.h adapters */
#define ccl_gpu_kernel(block_num_threads, thread_num_registers)
#define ccl_gpu_kernel_threads(block_num_threads)
/* Convert a comma-separated list into a semicolon-separated list
* (so that we can generate a struct based on kernel entry-point parameters). */
#define FN0()
#define FN1(p1) p1;
#define FN2(p1, p2) p1; p2;
#define FN3(p1, p2, p3) p1; p2; p3;
#define FN4(p1, p2, p3, p4) p1; p2; p3; p4;
#define FN5(p1, p2, p3, p4, p5) p1; p2; p3; p4; p5;
#define FN6(p1, p2, p3, p4, p5, p6) p1; p2; p3; p4; p5; p6;
#define FN7(p1, p2, p3, p4, p5, p6, p7) p1; p2; p3; p4; p5; p6; p7;
#define FN8(p1, p2, p3, p4, p5, p6, p7, p8) p1; p2; p3; p4; p5; p6; p7; p8;
#define FN9(p1, p2, p3, p4, p5, p6, p7, p8, p9) p1; p2; p3; p4; p5; p6; p7; p8; p9;
#define FN10(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10) p1; p2; p3; p4; p5; p6; p7; p8; p9; p10;
#define FN11(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11) p1; p2; p3; p4; p5; p6; p7; p8; p9; p10; p11;
#define FN12(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12) p1; p2; p3; p4; p5; p6; p7; p8; p9; p10; p11; p12;
#define FN13(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13) p1; p2; p3; p4; p5; p6; p7; p8; p9; p10; p11; p12; p13;
#define FN14(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14) p1; p2; p3; p4; p5; p6; p7; p8; p9; p10; p11; p12; p13; p14;
#define FN15(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15) p1; p2; p3; p4; p5; p6; p7; p8; p9; p10; p11; p12; p13; p14; p15;
#define FN16(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16) p1; p2; p3; p4; p5; p6; p7; p8; p9; p10; p11; p12; p13; p14; p15; p16;
#define FN17(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17) p1; p2; p3; p4; p5; p6; p7; p8; p9; p10; p11; p12; p13; p14; p15; p16; p17;
#define FN18(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18) p1; p2; p3; p4; p5; p6; p7; p8; p9; p10; p11; p12; p13; p14; p15; p16; p17; p18;
#define FN19(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19) p1; p2; p3; p4; p5; p6; p7; p8; p9; p10; p11; p12; p13; p14; p15; p16; p17; p18; p19;
#define FN20(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20) p1; p2; p3; p4; p5; p6; p7; p8; p9; p10; p11; p12; p13; p14; p15; p16; p17; p18; p19; p20;
#define GET_LAST_ARG(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20, ...) p20
#define PARAMS_MAKER(...) GET_LAST_ARG(__VA_ARGS__, FN20, FN19, FN18, FN17, FN16, FN15, FN14, FN13, FN12, FN11, FN10, FN9, FN8, FN7, FN6, FN5, FN4, FN3, FN2, FN1, FN0)
/* Generate a struct containing the entry-point parameters and a "run"
* method which can access them implicitly via this-> */
#ifdef __METAL_GLOBAL_BUILTINS__
#define ccl_gpu_kernel_signature(name, ...) \
struct kernel_gpu_##name \
{ \
PARAMS_MAKER(__VA_ARGS__)(__VA_ARGS__) \
void run(thread MetalKernelContext& context, \
threadgroup atomic_int *threadgroup_array) ccl_global const; \
}; \
kernel void cycles_metal_##name(device const kernel_gpu_##name *params_struct, \
constant KernelParamsMetal &ccl_restrict _launch_params_metal, \
constant MetalAncillaries *_metal_ancillaries, \
threadgroup atomic_int *threadgroup_array[[ threadgroup(0) ]]) { \
MetalKernelContext context(_launch_params_metal, _metal_ancillaries); \
params_struct->run(context, threadgroup_array); \
} \
void kernel_gpu_##name::run(thread MetalKernelContext& context, \
threadgroup atomic_int *threadgroup_array) ccl_global const
#else
/* On macOS versions before 14.x, builtin constants (e.g. metal_global_id) must
* be accessed through attributed entry-point parameters. */
#define ccl_gpu_kernel_signature(name, ...) \
struct kernel_gpu_##name \
{ \
PARAMS_MAKER(__VA_ARGS__)(__VA_ARGS__) \
void run(thread MetalKernelContext& context, \
threadgroup atomic_int *threadgroup_array, \
const uint metal_global_id, \
const ushort metal_local_id, \
const ushort metal_local_size, \
const uint metal_grid_id, \
uint simdgroup_size, \
uint simd_lane_index, \
uint simd_group_index, \
uint num_simd_groups) ccl_global const; \
}; \
kernel void cycles_metal_##name(device const kernel_gpu_##name *params_struct, \
constant KernelParamsMetal &ccl_restrict _launch_params_metal, \
constant MetalAncillaries *_metal_ancillaries, \
threadgroup atomic_int *threadgroup_array[[ threadgroup(0) ]], \
const uint metal_global_id [[thread_position_in_grid]], \
const ushort metal_local_id [[thread_position_in_threadgroup]], \
const ushort metal_local_size [[threads_per_threadgroup]], \
const uint metal_grid_id [[threadgroup_position_in_grid]], \
uint simdgroup_size [[threads_per_simdgroup]], \
uint simd_lane_index [[thread_index_in_simdgroup]], \
uint simd_group_index [[simdgroup_index_in_threadgroup]], \
uint num_simd_groups [[simdgroups_per_threadgroup]]) { \
MetalKernelContext context(_launch_params_metal, _metal_ancillaries); \
params_struct->run(context, threadgroup_array, metal_global_id, metal_local_id, metal_local_size, metal_grid_id, simdgroup_size, simd_lane_index, simd_group_index, num_simd_groups); \
} \
void kernel_gpu_##name::run(thread MetalKernelContext& context, \
threadgroup atomic_int *threadgroup_array, \
const uint metal_global_id, \
const ushort metal_local_id, \
const ushort metal_local_size, \
const uint metal_grid_id, \
uint simdgroup_size, \
uint simd_lane_index, \
uint simd_group_index, \
uint num_simd_groups) ccl_global const
#endif /* __METAL_GLOBAL_BUILTINS__ */
#define ccl_gpu_kernel_postfix
#define ccl_gpu_kernel_call(x) context.x
#define ccl_gpu_kernel_within_bounds(i,n) true
/* define a function object where "func" is the lambda body, and additional parameters are used to specify captured state. */
#define ccl_gpu_kernel_lambda(func, ...) \
struct KernelLambda \
{ \
KernelLambda(ccl_private MetalKernelContext &_context) : context(_context) {} \
ccl_private MetalKernelContext &context; \
__VA_ARGS__; \
int operator()(const int state) const { return (func); } \
} ccl_gpu_kernel_lambda_pass(context)
// clang-format on
/* make_type definitions with Metal style element initializers */
ccl_device_forceinline float2 make_float2(const float x, const float y)
{
return float2(x, y);
}
ccl_device_forceinline float3 make_float3(const float x, const float y, const float z)
{
return float3(x, y, z);
}
ccl_device_forceinline float4 make_float4(const float x,
const float y,
const float z,
const float w)
{
return float4(x, y, z, w);
}
ccl_device_forceinline int2 make_int2(const int x, const int y)
{
return int2(x, y);
}
ccl_device_forceinline int3 make_int3(const int x, const int y, const int z)
{
return int3(x, y, z);
}
ccl_device_forceinline int4 make_int4(const int x, const int y, const int z, const int w)
{
return int4(x, y, z, w);
}
ccl_device_forceinline uint2 make_uint2(const uint x, const uint y)
{
return uint2(x, y);
}
ccl_device_forceinline uint3 make_uint3(const uint x, const uint y, const uint z)
{
return uint3(x, y, z);
}
ccl_device_forceinline uint4 make_uint4(const uint x, const uint y, const uint z, const uint w)
{
return uint4(x, y, z, w);
}
ccl_device_forceinline uchar4 make_uchar4(const uchar x,
const uchar y,
const uchar z,
const uchar w)
{
return uchar4(x, y, z, w);
}
/* Math functions */
#define __uint_as_float(x) as_type<float>(x)
#define __float_as_uint(x) as_type<uint>(x)
#define __int_as_float(x) as_type<float>(x)
#define __float_as_int(x) as_type<int>(x)
#define __float2half(x) half(x)
#define powf(x, y) pow(float(x), float(y))
#define fabsf(x) fabs(float(x))
#define copysignf(x, y) copysign(float(x), float(y))
#define asinf(x) asin(float(x))
#define acosf(x) acos(float(x))
#define atanf(x) atan(float(x))
#define floorf(x) floor(float(x))
#define ceilf(x) ceil(float(x))
#define roundf(x) round(float(x))
#define hypotf(x, y) hypot(float(x), float(y))
#define atan2f(x, y) atan2(float(x), float(y))
#define fmaxf(x, y) fmax(float(x), float(y))
#define fminf(x, y) fmin(float(x), float(y))
#define fmodf(x, y) fmod(float(x), float(y))
#define sinhf(x) sinh(float(x))
#define coshf(x) cosh(float(x))
#define tanhf(x) tanh(float(x))
#define saturatef(x) saturate(float(x))
#define ldexpf(x, y) ldexp(float(x), int(y))
/* Use native functions with possibly lower precision for performance,
* no issues found so far. */
#define trigmode fast
#define sinf(x) trigmode::sin(float(x))
#define cosf(x) trigmode::cos(float(x))
#define tanf(x) trigmode::tan(float(x))
#define expf(x) trigmode::exp(float(x))
#define sqrtf(x) trigmode::sqrt(float(x))
#define logf(x) trigmode::log(float(x))
#define __device__
#ifdef __KERNEL_METALRT__
# if defined(__METALRT_MOTION__)
# define METALRT_TAGS instancing, instance_motion, primitive_motion
# define METALRT_BLAS_TAGS , primitive_motion
# else
# define METALRT_TAGS instancing
# define METALRT_BLAS_TAGS
# endif /* __METALRT_MOTION__ */
# if defined(__METALRT_EXTENDED_LIMITS__)
# define METALRT_LIMITS , extended_limits
# else
# define METALRT_LIMITS
# endif /* __METALRT_MOTION__ */
typedef acceleration_structure<METALRT_TAGS> metalrt_as_type;
typedef intersection_function_table<triangle_data, curve_data, METALRT_TAGS METALRT_LIMITS>
metalrt_ift_type;
typedef metal::raytracing::intersector<triangle_data, curve_data, METALRT_TAGS METALRT_LIMITS>
metalrt_intersector_type;
# if defined(__METALRT_MOTION__)
typedef acceleration_structure<primitive_motion> metalrt_blas_as_type;
typedef intersection_function_table<triangle_data, curve_data, primitive_motion METALRT_LIMITS>
metalrt_blas_ift_type;
typedef metal::raytracing::intersector<triangle_data, curve_data, primitive_motion METALRT_LIMITS>
metalrt_blas_intersector_type;
# else
typedef acceleration_structure<> metalrt_blas_as_type;
typedef intersection_function_table<triangle_data, curve_data METALRT_LIMITS>
metalrt_blas_ift_type;
typedef metal::raytracing::intersector<triangle_data, curve_data METALRT_LIMITS>
metalrt_blas_intersector_type;
# endif
#endif /* __KERNEL_METALRT__ */
/* texture bindings and sampler setup */
/* TextureParamsMetal is reinterpreted as Texture2DParamsMetal. */
struct TextureParamsMetal {
uint64_t tex;
};
struct Texture2DParamsMetal {
texture2d<float, access::sample> tex;
};
#ifdef __KERNEL_METALRT__
struct MetalRTBlasWrapper {
metalrt_blas_as_type blas;
};
#endif
/* Additional Metal-specific resources which aren't encoded in KernelData.
* IMPORTANT: If this layout changes, ANCILLARY_SLOT_COUNT and the host-side encoding must change
* to match. */
struct MetalAncillaries {
device TextureParamsMetal *textures;
#ifdef __KERNEL_METALRT__
metalrt_as_type accel_struct;
constant MetalRTBlasWrapper *blas_accel_structs;
metalrt_ift_type ift_default;
metalrt_ift_type ift_shadow;
metalrt_ift_type ift_shadow_all;
metalrt_ift_type ift_volume;
metalrt_blas_ift_type ift_local;
metalrt_ift_type ift_local_mblur;
metalrt_blas_ift_type ift_local_single_hit;
metalrt_ift_type ift_local_single_hit_mblur;
#endif
};
#include "util/half.h"
#include "util/types.h"
enum SamplerType {
SamplerFilterNearest_AddressRepeat,
SamplerFilterNearest_AddressClampEdge,
SamplerFilterNearest_AddressClampZero,
SamplerFilterNearest_AddressMirroredRepeat,
SamplerFilterLinear_AddressRepeat,
SamplerFilterLinear_AddressClampEdge,
SamplerFilterLinear_AddressClampZero,
SamplerFilterLinear_AddressMirroredRepeat,
SamplerCount
};
constexpr constant array<sampler, SamplerCount> metal_samplers = {
sampler(address::repeat, filter::nearest),
sampler(address::clamp_to_edge, filter::nearest),
sampler(address::clamp_to_zero, filter::nearest),
sampler(address::mirrored_repeat, filter::nearest),
sampler(address::repeat, filter::linear),
sampler(address::clamp_to_edge, filter::linear),
sampler(address::clamp_to_zero, filter::linear),
sampler(address::mirrored_repeat, filter::linear),
};
#ifdef __METAL_GLOBAL_BUILTINS__
const uint metal_global_id [[thread_position_in_grid]];
const ushort metal_local_id [[thread_position_in_threadgroup]];
const ushort metal_local_size [[threads_per_threadgroup]];
const uint metal_grid_id [[threadgroup_position_in_grid]];
const uint simdgroup_size [[threads_per_simdgroup]];
const uint simd_lane_index [[thread_index_in_simdgroup]];
const uint simd_group_index [[simdgroup_index_in_threadgroup]];
const uint num_simd_groups [[simdgroups_per_threadgroup]];
#endif /* __METAL_GLOBAL_BUILTINS__ */

View File

@@ -0,0 +1,54 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
// clang-format off
#ifdef WITH_NANOVDB
# include "kernel/util/nanovdb.h"
#endif
/* Open the Metal kernel context class
* Necessary to access resource bindings */
class MetalKernelContext {
public:
constant KernelParamsMetal &launch_params_metal;
constant MetalAncillaries *metal_ancillaries;
MetalKernelContext(constant KernelParamsMetal &_launch_params_metal, constant MetalAncillaries * _metal_ancillaries)
: launch_params_metal(_launch_params_metal), metal_ancillaries(_metal_ancillaries)
{}
MetalKernelContext(constant KernelParamsMetal &_launch_params_metal)
: launch_params_metal(_launch_params_metal)
{}
/* texture fetch adapter functions */
using ccl_gpu_image_object_2D = uint64_t;
template<typename T>
inline __attribute__((__always_inline__))
T ccl_gpu_image_object_read_2D(ccl_gpu_image_object_2D tex, const float x, float y) const {
kernel_assert(0);
return 0;
}
// texture2d
template<>
inline __attribute__((__always_inline__))
float4 ccl_gpu_image_object_read_2D(ccl_gpu_image_object_2D tex, const float x, float y) const {
const uint tid(tex);
const uint sid(tex >> 32);
return ((ccl_global Texture2DParamsMetal*)metal_ancillaries->textures)[tid].tex.sample(metal_samplers[sid], float2(x, y));
}
template<>
inline __attribute__((__always_inline__))
float ccl_gpu_image_object_read_2D(ccl_gpu_image_object_2D tex, const float x, float y) const {
const uint tid(tex);
const uint sid(tex >> 32);
return ((ccl_global Texture2DParamsMetal*)metal_ancillaries->textures)[tid].tex.sample(metal_samplers[sid], float2(x, y)).x;
}
# include "kernel/device/gpu/image.h"
// clang-format on

View File

@@ -0,0 +1,11 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
}
; /* end of MetalKernelContext class definition */
/* Silently redirect into the MetalKernelContext instance */
/* NOTE: These macros will need maintaining as entry-points change. */
#undef kernel_integrator_state
#define kernel_integrator_state context.launch_params_metal.integrator_state

View File

@@ -0,0 +1,20 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
enum {
Kernel_DummyConstant,
#define KERNEL_STRUCT_MEMBER(parent, type, name) KernelData_##parent##_##name,
#include "kernel/data_template.h"
KernelData_kernel_features
};
#ifdef __KERNEL_METAL__
# define KERNEL_STRUCT_MEMBER(parent, type, name) \
constant type kernel_data_##parent##_##name \
[[function_constant(KernelData_##parent##_##name)]];
# include "kernel/data_template.h"
constant int kernel_data_kernel_features [[function_constant(KernelData_kernel_features)]];
#endif

View File

@@ -0,0 +1,40 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* Constant Globals */
#include "kernel/types.h"
#include "kernel/integrator/state.h"
#include "kernel/util/profiler.h"
#include "util/color.h"
#include "util/types_image.h"
CCL_NAMESPACE_BEGIN
struct KernelParamsMetal {
#define KERNEL_DATA_ARRAY(type, name) const ccl_global type *name;
#define KERNEL_DATA_ARRAY_WRITABLE(type, name) ccl_global type *name;
#include "kernel/data_arrays.h"
const IntegratorStateGPU integrator_state;
const KernelData data;
};
struct KernelGlobalsGPU {
int unused[1];
};
using KernelGlobals = const ccl_global KernelGlobalsGPU *ccl_restrict;
/* Abstraction macros */
#define kernel_data launch_params_metal.data
#define kernel_data_fetch(name, index) launch_params_metal.name[index]
#define kernel_data_write(name, index, value) launch_params_metal.name[index] = (value)
#define kernel_data_array(name) launch_params_metal.name
#define kernel_integrator_state launch_params_metal.integrator_state
CCL_NAMESPACE_END

View File

@@ -0,0 +1,746 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* Metal kernel entry points. */
/* NOTE: Must come prior to other includes. */
#include "kernel/device/metal/compat.h"
#include "kernel/device/metal/globals.h"
/* NOTE: Must come prior to the kernel.h. */
#include "kernel/device/metal/function_constants.h"
/* NOTE: Must come prior to the rest of the includes. */
#include "kernel/device/gpu/kernel.h"
/* The rest of the includes. */
#include "kernel/bvh/intersect_filter.h"
#include "kernel/geom/geom_intersect.h"
/* MetalRT intersection handlers. */
#ifdef __KERNEL_METALRT__
/* Intersection return types. */
/* For a bounding box intersection function. */
struct BoundingBoxIntersectionResult {
bool accept [[accept_intersection]];
bool continue_search [[continue_search]];
float distance [[distance]];
};
/* For a primitive intersection function. */
struct PrimitiveIntersectionResult {
bool accept [[accept_intersection]];
bool continue_search [[continue_search]];
};
enum { METALRT_HIT_TRIANGLE, METALRT_HIT_CURVE, METALRT_HIT_BOUNDING_BOX };
/* Hit functions. */
[[intersection(triangle, triangle_data, curve_data)]] PrimitiveIntersectionResult
__intersection__local_tri_single_hit(
ray_data MetalKernelContext::MetalRTIntersectionLocalPayload_single_hit &payload [[payload]],
uint primitive_id [[primitive_id]])
{
PrimitiveIntersectionResult result;
result.continue_search = true;
result.accept = (payload.self_prim != primitive_id);
return result;
}
[[intersection(
triangle, triangle_data, curve_data, METALRT_TAGS METALRT_LIMITS)]] PrimitiveIntersectionResult
__intersection__local_tri_single_hit_mblur(
ray_data MetalKernelContext::MetalRTIntersectionLocalPayload_single_hit &payload [[payload]],
# if defined(__METALRT_MOTION__)
uint object [[instance_id]],
# endif
uint primitive_id [[primitive_id]])
{
PrimitiveIntersectionResult result;
result.continue_search = true;
# if defined(__METALRT_MOTION__)
result.accept = (payload.self_prim != primitive_id) && (payload.self_object == object);
# else
result.accept = (payload.self_prim != primitive_id);
# endif
return result;
}
template<typename TReturn, uint intersection_type>
TReturn metalrt_local_hit(constant KernelParamsMetal &launch_params_metal,
ray_data MetalKernelContext::MetalRTIntersectionLocalPayload &payload,
const uint prim,
const float2 barycentrics,
const float ray_tmax)
{
TReturn result;
# ifdef __BVH_LOCAL__
if (payload.self_prim == prim) {
/* Only intersect with matching object and skip self-intersection. */
result.accept = false;
result.continue_search = true;
return result;
}
const int max_hits = payload.max_hits;
if (max_hits == 0) {
/* Special case for when no hit information is requested, just report that something was hit.
*/
result.accept = true;
result.continue_search = false;
return result;
}
/* Make a copy of the lcg_state in the private address space, allowing to use utility function
* to find the hit index to write the intersection to. This function is used from both HW-RT
* code-path and non-HW-RT, making it hard to deal with the address spaces in the function
* signature. Hopefully, compiler is smart enough to eliminate this temporary copy. */
uint lcg_state = payload.lcg_state;
MetalKernelContext context(launch_params_metal);
const int hit_index = context.local_intersect_get_record_index(
&payload, ray_tmax, payload.has_lcg_state ? &lcg_state : nullptr, max_hits);
payload.lcg_state = lcg_state;
if (hit_index == -1) {
result.accept = false;
result.continue_search = true;
return result;
}
payload.hits[hit_index].prim = prim;
payload.hits[hit_index].t = ray_tmax;
payload.hits[hit_index].u = barycentrics.x;
payload.hits[hit_index].v = barycentrics.y;
/* Continue tracing (without this the trace call would return after the first hit). */
result.accept = false;
result.continue_search = true;
# endif
return result;
}
[[intersection(triangle, triangle_data, curve_data)]] PrimitiveIntersectionResult
__intersection__local_tri(constant KernelParamsMetal &launch_params_metal [[buffer(1)]],
ray_data MetalKernelContext::MetalRTIntersectionLocalPayload &payload
[[payload]],
uint primitive_id [[primitive_id]],
float2 barycentrics [[barycentric_coord]],
float ray_tmax [[distance]])
{
/* instance_id, aka the user_id has been removed. If we take this function we optimized the
* SSS for starting traversal from a primitive acceleration structure instead of the root of the
* global AS. this means we will always be intersecting the correct object no need for the
* user-id to check */
return metalrt_local_hit<PrimitiveIntersectionResult, METALRT_HIT_TRIANGLE>(
launch_params_metal, payload, primitive_id, barycentrics, ray_tmax);
}
[[intersection(
triangle, triangle_data, curve_data, METALRT_TAGS METALRT_LIMITS)]] PrimitiveIntersectionResult
__intersection__local_tri_mblur(
constant KernelParamsMetal &launch_params_metal [[buffer(1)]],
ray_data MetalKernelContext::MetalRTIntersectionLocalPayload &payload [[payload]],
uint primitive_id [[primitive_id]],
# if defined(__METALRT_MOTION__)
uint object [[instance_id]],
# endif
float2 barycentrics [[barycentric_coord]],
float ray_tmax [[distance]])
{
# if defined(__METALRT_MOTION__)
if (payload.self_object != object) {
PrimitiveIntersectionResult result;
result.continue_search = true;
result.accept = false;
return result;
}
# endif
return metalrt_local_hit<PrimitiveIntersectionResult, METALRT_HIT_TRIANGLE>(
launch_params_metal, payload, primitive_id, barycentrics, ray_tmax);
}
inline bool metalrt_curve_skip_end_cap(const int type, const float u)
{
return ((u == 0.0f || u == 1.0f) && (type & PRIMITIVE_CURVE) != PRIMITIVE_CURVE_THICK_LINEAR);
}
inline Intersection get_intersection(constant KernelParamsMetal &launch_params_metal,
const float t,
const float2 uv,
uint object,
uint prim)
{
Intersection isect;
isect.t = t;
isect.u = uv.x;
isect.v = uv.y;
isect.prim = prim;
isect.object = object;
isect.type = kernel_data_fetch(objects, object).primitive_type;
# ifdef __HAIR__
if (isect.type & PRIMITIVE_CURVE) {
const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim);
isect.type = segment.type;
isect.prim = segment.prim;
}
# endif
if (isect.type & PRIMITIVE_POINT) {
isect.u = 0.0f;
isect.v = 0.0f;
}
return isect;
}
template<uint intersection_type>
bool metalrt_shadow_all_hit(constant KernelParamsMetal &launch_params_metal,
ray_data MetalKernelContext::BVHShadowAllPayload &payload,
uint object,
uint prim,
const float2 uv,
const float t,
const ccl_private Ray *ray = nullptr)
{
# if defined(__TRANSPARENT_SHADOWS__)
MetalKernelContext context(launch_params_metal);
KernelGlobals kg = nullptr;
const Intersection isect = get_intersection(launch_params_metal, t, uv, object, prim);
# ifdef __HAIR__
if constexpr (intersection_type == METALRT_HIT_CURVE) {
/* Filter out curve end-caps. */
if (metalrt_curve_skip_end_cap(isect.type, isect.u)) {
return true;
}
if ((isect.type & PRIMITIVE_CURVE) == PRIMITIVE_CURVE_RIBBON) {
if (!context.curve_ribbon_accept(
nullptr, isect.u, isect.t, ray, object, isect.prim, isect.type))
{
return true;
}
}
}
# endif /* __HAIR__ */
constexpr uint enabled_primitive_types = (intersection_type == METALRT_HIT_CURVE) ?
PRIMITIVE_CURVE :
(PRIMITIVE_ALL & ~PRIMITIVE_CURVE);
return context
.bvh_shadow_all_anyhit_filter<MetalKernelContext::ISECT_TEST_ALL, enabled_primitive_types>(
kg, payload.state, payload, payload.base.ray_self, payload.base.ray_visibility, isect);
# else /* __TRANSPARENT_SHADOWS__ */
payload.throughput = 0.0f;
return false;
# endif /* __TRANSPARENT_SHADOWS__ */
}
[[intersection(
triangle, triangle_data, curve_data, METALRT_TAGS METALRT_LIMITS)]] PrimitiveIntersectionResult
__intersection__tri_shadow_all(constant KernelParamsMetal &launch_params_metal [[buffer(1)]],
ray_data MetalKernelContext::BVHShadowAllPayload &payload
[[payload]],
const unsigned int object [[instance_id]],
const unsigned int primitive_id [[primitive_id]],
const uint primitive_id_offset [[user_instance_id]],
const float2 uv [[barycentric_coord]],
const float t [[distance]])
{
uint prim = primitive_id + primitive_id_offset;
PrimitiveIntersectionResult result;
result.continue_search = metalrt_shadow_all_hit<METALRT_HIT_TRIANGLE>(
launch_params_metal, payload, object, prim, uv, t);
result.accept = !result.continue_search;
return result;
}
[[intersection(
triangle, triangle_data, curve_data, METALRT_TAGS METALRT_LIMITS)]] PrimitiveIntersectionResult
__intersection__volume_tri(constant KernelParamsMetal &launch_params_metal [[buffer(1)]],
ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload
[[payload]],
const unsigned int object [[instance_id]],
const unsigned int primitive_id [[primitive_id]],
const uint primitive_id_offset [[user_instance_id]])
{
PrimitiveIntersectionResult result;
result.continue_search = true;
KernelGlobals kg = nullptr;
MetalKernelContext context(launch_params_metal);
uint prim = primitive_id + primitive_id_offset;
if (context.bvh_volume_anyhit_triangle_filter(
kg, object, prim, payload.self, payload.visibility))
{
result.accept = false;
return result;
}
result.accept = true;
return result;
}
template<typename TReturnType, uint intersection_type>
inline TReturnType metalrt_visibility_test(
constant KernelParamsMetal &launch_params_metal,
ray_data MetalKernelContext::MetalRTIntersectionPayload &payload,
const uint object,
uint prim,
const float u,
const float t = 0.0f,
const ccl_private Ray *ray = nullptr)
{
TReturnType result;
if ((kernel_data_fetch(objects, object).visibility & payload.visibility) == 0) {
result.accept = false;
result.continue_search = true;
return result;
}
# ifdef __HAIR__
if constexpr (intersection_type == METALRT_HIT_CURVE) {
const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim);
int type = segment.type;
prim = segment.prim;
/* Filter out curve end-caps. */
if (metalrt_curve_skip_end_cap(type, u)) {
result.accept = false;
result.continue_search = true;
return result;
}
if ((type & PRIMITIVE_CURVE) == PRIMITIVE_CURVE_RIBBON) {
MetalKernelContext context(launch_params_metal);
if (!context.curve_ribbon_accept(nullptr, u, t, ray, object, prim, type)) {
result.accept = false;
result.continue_search = true;
return result;
}
}
}
# endif
if (payload.self_object == object && payload.self_prim == prim) {
result.accept = false;
result.continue_search = true;
return result;
}
result.accept = true;
result.continue_search = true;
return result;
}
template<typename TReturnType, uint intersection_type>
inline TReturnType metalrt_visibility_test_shadow(
constant KernelParamsMetal &launch_params_metal,
ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload,
const uint object,
uint prim,
const float u,
const float t = 0.0f,
const ccl_private Ray *ray = nullptr)
{
TReturnType result;
if ((kernel_data_fetch(objects, object).visibility & payload.visibility) == 0) {
result.accept = false;
return result;
}
# ifdef __HAIR__
if constexpr (intersection_type == METALRT_HIT_CURVE) {
const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim);
int type = segment.type;
prim = segment.prim;
/* Filter out curve end-caps. */
if (metalrt_curve_skip_end_cap(type, u)) {
result.accept = false;
result.continue_search = true;
return result;
}
if ((type & PRIMITIVE_CURVE) == PRIMITIVE_CURVE_RIBBON) {
MetalKernelContext context(launch_params_metal);
if (!context.curve_ribbon_accept(nullptr, u, t, ray, object, prim, type)) {
result.accept = false;
result.continue_search = true;
return result;
}
}
}
# endif
MetalKernelContext context(launch_params_metal);
/* Shadow ray early termination. */
# ifdef __SHADOW_LINKING__
if (context.intersection_skip_shadow_link(nullptr, payload.self, object)) {
result.accept = false;
result.continue_search = true;
return result;
}
# endif
if (context.intersection_skip_self_shadow(payload.self, object, prim)) {
result.accept = false;
result.continue_search = true;
return result;
}
else {
result.accept = true;
result.continue_search = false;
return result;
}
result.accept = true;
result.continue_search = true;
return result;
}
[[intersection(
triangle, triangle_data, curve_data, METALRT_TAGS METALRT_LIMITS)]] PrimitiveIntersectionResult
__intersection__tri(constant KernelParamsMetal &launch_params_metal [[buffer(1)]],
ray_data MetalKernelContext::MetalRTIntersectionPayload &payload [[payload]],
const unsigned int object [[instance_id]],
const uint primitive_id_offset [[user_instance_id]],
const unsigned int primitive_id [[primitive_id]])
{
PrimitiveIntersectionResult result;
result.continue_search = true;
if ((kernel_data_fetch(objects, object).visibility & payload.visibility) == 0) {
result.accept = false;
return result;
}
result.accept = (payload.self_object != object ||
payload.self_prim != (primitive_id + primitive_id_offset));
return result;
}
[[intersection(
triangle, triangle_data, curve_data, METALRT_TAGS METALRT_LIMITS)]] PrimitiveIntersectionResult
__intersection__tri_shadow(constant KernelParamsMetal &launch_params_metal [[buffer(1)]],
ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload
[[payload]],
const unsigned int object [[instance_id]],
const uint primitive_id_offset [[user_instance_id]],
const unsigned int primitive_id [[primitive_id]])
{
uint prim = primitive_id + primitive_id_offset;
PrimitiveIntersectionResult result =
metalrt_visibility_test_shadow<PrimitiveIntersectionResult, METALRT_HIT_TRIANGLE>(
launch_params_metal, payload, object, prim, 0.0f);
return result;
}
/* Primitive intersection functions. */
[[intersection(
curve, triangle_data, curve_data, METALRT_TAGS METALRT_LIMITS)]] PrimitiveIntersectionResult
__intersection__curve(constant KernelParamsMetal &launch_params_metal [[buffer(1)]],
ray_data MetalKernelContext::MetalRTIntersectionPayload &payload [[payload]],
const uint object [[instance_id]],
const uint primitive_id [[primitive_id]],
const uint primitive_id_offset [[user_instance_id]],
float distance [[distance]],
const float3 ray_P [[origin]],
const float3 ray_D [[direction]],
float u [[curve_parameter]],
const float ray_tmin [[min_distance]],
const float ray_tmax [[max_distance]]
# if defined(__METALRT_MOTION__)
,
const float time [[time]]
# endif
)
{
uint prim = primitive_id + primitive_id_offset;
Ray ray;
ray.P = ray_P;
ray.D = ray_D;
# if defined(__METALRT_MOTION__)
ray.time = time;
# endif
PrimitiveIntersectionResult result =
metalrt_visibility_test<PrimitiveIntersectionResult, METALRT_HIT_CURVE>(
launch_params_metal, payload, object, prim, u, distance, &ray);
return result;
}
[[intersection(
curve, triangle_data, curve_data, METALRT_TAGS METALRT_LIMITS)]] PrimitiveIntersectionResult
__intersection__curve_shadow(constant KernelParamsMetal &launch_params_metal [[buffer(1)]],
ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload
[[payload]],
const uint object [[instance_id]],
const uint primitive_id [[primitive_id]],
const uint primitive_id_offset [[user_instance_id]],
float distance [[distance]],
const float3 ray_P [[origin]],
const float3 ray_D [[direction]],
float u [[curve_parameter]],
const float ray_tmin [[min_distance]],
const float ray_tmax [[max_distance]]
# if defined(__METALRT_MOTION__)
,
const float time [[time]]
# endif
)
{
uint prim = primitive_id + primitive_id_offset;
Ray ray;
ray.P = ray_P;
ray.D = ray_D;
# if defined(__METALRT_MOTION__)
ray.time = time;
# endif
PrimitiveIntersectionResult result =
metalrt_visibility_test_shadow<PrimitiveIntersectionResult, METALRT_HIT_CURVE>(
launch_params_metal, payload, object, prim, u, distance, &ray);
return result;
}
[[intersection(
curve, triangle_data, curve_data, METALRT_TAGS METALRT_LIMITS)]] PrimitiveIntersectionResult
__intersection__curve_shadow_all(constant KernelParamsMetal &launch_params_metal [[buffer(1)]],
ray_data MetalKernelContext::BVHShadowAllPayload &payload
[[payload]],
const uint object [[instance_id]],
const uint primitive_id [[primitive_id]],
const uint primitive_id_offset [[user_instance_id]],
const float3 ray_P [[origin]],
const float3 ray_D [[direction]],
# if defined(__METALRT_MOTION__)
const float time [[time]],
# endif
float u [[curve_parameter]],
float t [[distance]])
{
uint prim = primitive_id + primitive_id_offset;
PrimitiveIntersectionResult result;
Ray ray;
ray.P = ray_P;
ray.D = ray_D;
# if defined(__METALRT_MOTION__)
/* TODO(sergey): The time is not really needed.
* Only ray direction and origin are needed in curve_ribbon_accept(), so there might be a room
* for cleanup here. */
ray.time = time;
# endif
result.continue_search = metalrt_shadow_all_hit<METALRT_HIT_CURVE>(
launch_params_metal, payload, object, prim, float2(u, 0), t, &ray);
result.accept = !result.continue_search;
return result;
}
# ifdef __POINTCLOUD__
ccl_device_inline void metalrt_intersection_point_shadow_all(
constant KernelParamsMetal &launch_params_metal,
ray_data MetalKernelContext::BVHShadowAllPayload &payload,
const uint object,
const uint prim,
const uint type,
const float3 ray_P,
const float3 ray_D,
float time,
const float ray_tmin,
const float ray_tmax,
thread BoundingBoxIntersectionResult &result)
{
Intersection isect;
isect.t = ray_tmax;
MetalKernelContext context(launch_params_metal);
if (context.point_intersect(
nullptr, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type))
{
result.continue_search = metalrt_shadow_all_hit<METALRT_HIT_BOUNDING_BOX>(
launch_params_metal, payload, object, prim, float2(isect.u, isect.v), isect.t);
result.accept = !result.continue_search;
if (result.accept) {
result.distance = isect.t;
}
}
}
[[intersection(bounding_box,
triangle_data,
curve_data,
METALRT_TAGS METALRT_LIMITS)]] BoundingBoxIntersectionResult
__intersection__point(constant KernelParamsMetal &launch_params_metal [[buffer(1)]],
ray_data MetalKernelContext::MetalRTIntersectionPayload &payload [[payload]],
const uint object [[instance_id]],
const uint primitive_id [[primitive_id]],
const uint primitive_id_offset [[user_instance_id]],
const float3 ray_origin [[origin]],
const float3 ray_direction [[direction]],
# if defined(__METALRT_MOTION__)
const float time [[time]],
# endif
const float ray_tmin [[min_distance]],
const float ray_tmax [[max_distance]])
{
const uint prim = primitive_id + primitive_id_offset;
const int type = kernel_data_fetch(objects, object).primitive_type;
BoundingBoxIntersectionResult result;
result.accept = false;
result.continue_search = true;
result.distance = ray_tmax;
Intersection isect;
isect.t = ray_tmax;
# ifndef __METALRT_MOTION__
const float time = 0.0f;
# endif
MetalKernelContext context(launch_params_metal);
if (context.point_intersect(
nullptr, &isect, ray_origin, ray_direction, ray_tmin, isect.t, object, prim, time, type))
{
result = metalrt_visibility_test<BoundingBoxIntersectionResult, METALRT_HIT_BOUNDING_BOX>(
launch_params_metal, payload, object, prim, isect.u);
if (result.accept) {
result.distance = isect.t;
}
}
return result;
}
# endif /* __POINTCLOUD__ */
[[intersection(bounding_box,
triangle_data,
curve_data,
METALRT_TAGS METALRT_LIMITS)]] BoundingBoxIntersectionResult
__intersection__point_shadow(constant KernelParamsMetal &launch_params_metal [[buffer(1)]],
ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload
[[payload]],
const uint object [[instance_id]],
const uint primitive_id [[primitive_id]],
const uint primitive_id_offset [[user_instance_id]],
const float3 ray_origin [[origin]],
const float3 ray_direction [[direction]],
# if defined(__METALRT_MOTION__)
const float time [[time]],
# endif
const float ray_tmin [[min_distance]],
const float ray_tmax [[max_distance]])
{
const uint prim = primitive_id + primitive_id_offset;
const int type = kernel_data_fetch(objects, object).primitive_type;
BoundingBoxIntersectionResult result;
result.accept = false;
result.continue_search = true;
result.distance = ray_tmax;
# ifdef __POINTCLOUD__
Intersection isect;
isect.t = ray_tmax;
# ifndef __METALRT_MOTION__
const float time = 0.0f;
# endif
MetalKernelContext context(launch_params_metal);
if (context.point_intersect(
nullptr, &isect, ray_origin, ray_direction, ray_tmin, isect.t, object, prim, time, type))
{
result =
metalrt_visibility_test_shadow<BoundingBoxIntersectionResult, METALRT_HIT_BOUNDING_BOX>(
launch_params_metal, payload, object, prim, isect.u);
if (result.accept) {
result.distance = isect.t;
}
}
# endif /* __POINTCLOUD__ */
return result;
}
[[intersection(bounding_box,
triangle_data,
curve_data,
METALRT_TAGS METALRT_LIMITS)]] BoundingBoxIntersectionResult
__intersection__point_shadow_all(constant KernelParamsMetal &launch_params_metal [[buffer(1)]],
ray_data MetalKernelContext::BVHShadowAllPayload &payload
[[payload]],
const uint object [[instance_id]],
const uint primitive_id [[primitive_id]],
const uint primitive_id_offset [[user_instance_id]],
const float3 ray_origin [[origin]],
const float3 ray_direction [[direction]],
# if defined(__METALRT_MOTION__)
const float time [[time]],
# endif
const float ray_tmin [[min_distance]],
const float ray_tmax [[max_distance]])
{
const uint prim = primitive_id + primitive_id_offset;
const int type = kernel_data_fetch(objects, object).primitive_type;
BoundingBoxIntersectionResult result;
result.accept = false;
result.continue_search = true;
result.distance = ray_tmax;
# ifdef __POINTCLOUD__
metalrt_intersection_point_shadow_all(launch_params_metal,
payload,
object,
prim,
type,
ray_origin,
ray_direction,
# if defined(__METALRT_MOTION__)
time,
# else
0.0f,
# endif
ray_tmin,
ray_tmax,
result);
# endif /* __POINTCLOUD__ */
return result;
}
#endif /* __KERNEL_METALRT__ */

View File

@@ -0,0 +1,296 @@
# SPDX-FileCopyrightText: 2011-2026 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
set(INC
../../..
)
set(INC_SYS
)
set(SRC_KERNEL_DEVICE_ONEAPI
kernel.cpp
)
set(SRC_KERNEL_DEVICE_ONEAPI_HEADERS
compat.h
context_begin.h
context_end.h
context_intersect_begin.h
context_intersect_end.h
globals.h
kernel.h
kernel_templates.h
../cpu/bvh.h
)
set(LIB
)
if(WITH_CYCLES_DEVICE_ONEAPI)
if(WITH_CYCLES_ONEAPI_BINARIES)
set(cycles_kernel_oneapi_lib_suffix "_aot")
else()
set(cycles_kernel_oneapi_lib_suffix "_jit")
endif()
if(WIN32)
set(cycles_kernel_oneapi_lib ${CMAKE_CURRENT_BINARY_DIR}/cycles_kernel_oneapi${cycles_kernel_oneapi_lib_suffix}.dll)
set(cycles_kernel_oneapi_linker_lib ${CMAKE_CURRENT_BINARY_DIR}/cycles_kernel_oneapi${cycles_kernel_oneapi_lib_suffix}.lib)
else()
set(cycles_kernel_oneapi_lib ${CMAKE_CURRENT_BINARY_DIR}/libcycles_kernel_oneapi${cycles_kernel_oneapi_lib_suffix}.so)
endif()
set(cycles_oneapi_kernel_sources
${SRC_KERNEL_DEVICE_ONEAPI}
${SRC_KERNEL_DEVICE_ONEAPI_HEADERS}
$<TARGET_PROPERTY:cycles_kernel,INTERFACE_SOURCES>
)
set(SYCL_OFFLINE_COMPILER_PARALLEL_JOBS 1 CACHE STRING "Number of parallel compiler instances to use for device binaries compilation (expect ~8GB peak memory usage per instance).")
mark_as_advanced(SYCL_OFFLINE_COMPILER_PARALLEL_JOBS)
if(WITH_CYCLES_ONEAPI_BINARIES)
message(STATUS "${SYCL_OFFLINE_COMPILER_PARALLEL_JOBS} instance(s) of oneAPI offline compiler will be used.")
endif()
set(sycl_compiler_flags
${CMAKE_CURRENT_SOURCE_DIR}/${SRC_KERNEL_DEVICE_ONEAPI}
-fsycl
-fsycl-unnamed-lambda
-fdelayed-template-parsing
-fsycl-device-code-split=per_kernel
-fsycl-max-parallel-link-jobs=${SYCL_OFFLINE_COMPILER_PARALLEL_JOBS}
--offload-compress
--offload-compression-level=19
-shared
-DWITH_ONEAPI
-O2
-ffast-math
-D__KERNEL_LOCAL_ATOMIC_SORT__
-o"${cycles_kernel_oneapi_lib}"
-I"${CMAKE_CURRENT_SOURCE_DIR}/../../.."
)
# SYCL_CPP_FLAGS is a variable that the user can set to pass extra compiler options.
if(DEFINED SYCL_CPP_FLAGS)
list(APPEND sycl_compiler_flags ${SYCL_CPP_FLAGS})
endif()
# Set defaults for spir64 and spir64_gen options
if(NOT DEFINED CYCLES_ONEAPI_SYCL_OPTIONS_spir64)
set(CYCLES_ONEAPI_SYCL_OPTIONS_spir64 "-options '-cl-fast-relaxed-math -ze-intel-enable-auto-large-GRF-mode -ze-opt-regular-grf-kernel integrator_intersect -ze-opt-large-grf-kernel shade_surface -ze-opt-no-local-to-generic'")
endif()
if(NOT DEFINED CYCLES_ONEAPI_SYCL_OPTIONS_spir64_gen)
set(CYCLES_ONEAPI_SYCL_OPTIONS_spir64_gen "${CYCLES_ONEAPI_SYCL_OPTIONS_spir64}" CACHE STRING "Extra build options for spir64_gen target")
mark_as_advanced(CYCLES_ONEAPI_SYCL_OPTIONS_spir64_gen)
endif()
# Enable `zebin`, a graphics binary format with improved compatibility.
string(PREPEND CYCLES_ONEAPI_SYCL_OPTIONS_spir64_gen "--format zebin ")
# Host execution won't use GPU binaries, no need to compile them.
if(WITH_CYCLES_ONEAPI_BINARIES)
# Add the list of Intel devices to build binaries for.
foreach(device ${CYCLES_ONEAPI_INTEL_BINARIES_ARCH})
# Run `ocloc` ids to test if the device is supported.
execute_process(
COMMAND ${OCLOC_ENV_COMMAND} ${OCLOC_BINARY_FULL_FILEPATH} ids ${device}
RESULT_VARIABLE oclocids_ret
OUTPUT_QUIET
ERROR_QUIET
)
if(NOT oclocids_ret EQUAL 0)
list(REMOVE_ITEM CYCLES_ONEAPI_INTEL_BINARIES_ARCH ${device})
message(STATUS
"Cycles oneAPI: "
"binaries for ${device} not supported by Intel Graphics Compiler/ocloc, skipped."
)
endif()
endforeach()
list(JOIN CYCLES_ONEAPI_INTEL_BINARIES_ARCH "," gen_devices_string)
if("${gen_devices_string}" STREQUAL "")
# Don't compile spir64_gen if no device is targeted
message(STATUS "Cycles oneAPI: skipping spir64_gen compilation as no devices are targeted.")
list(REMOVE_ITEM CYCLES_ONEAPI_SYCL_TARGETS spir64_gen)
else()
string(PREPEND CYCLES_ONEAPI_SYCL_OPTIONS_spir64_gen "-device ${gen_devices_string} ")
endif()
else()
list(REMOVE_ITEM CYCLES_ONEAPI_SYCL_TARGETS spir64_gen)
endif()
# Iterate over all targets and their options.
list(JOIN CYCLES_ONEAPI_SYCL_TARGETS "," targets_string)
list(APPEND sycl_compiler_flags -fsycl-targets=${targets_string})
foreach(target ${CYCLES_ONEAPI_SYCL_TARGETS})
if(DEFINED CYCLES_ONEAPI_SYCL_OPTIONS_${target})
list(APPEND sycl_compiler_flags
"-Xsycl-target-backend=${target} \"${CYCLES_ONEAPI_SYCL_OPTIONS_${target}}\""
)
endif()
endforeach()
if(WITH_NANOVDB)
list(APPEND sycl_compiler_flags
-DWITH_NANOVDB)
endif()
if(WITH_CYCLES_EMBREE AND EMBREE_SYCL_SUPPORT)
list(APPEND sycl_compiler_flags
-DWITH_EMBREE
-DWITH_EMBREE_GPU
-DEMBREE_MAJOR_VERSION=${EMBREE_MAJOR_VERSION}
-I"${EMBREE_INCLUDE_DIRS}")
if(WIN32)
list(APPEND sycl_compiler_flags
-ladvapi32.lib
)
endif()
set(next_library_mode "")
foreach(library ${EMBREE_LIBRARIES})
string(TOLOWER "${library}" library_lower)
if(("${library_lower}" STREQUAL "optimized") OR
("${library_lower}" STREQUAL "debug"))
set(next_library_mode "${library_lower}")
else()
if(next_library_mode STREQUAL "")
list(APPEND EMBREE_TBB_LIBRARIES_optimized ${library})
list(APPEND EMBREE_TBB_LIBRARIES_debug ${library})
else()
list(APPEND EMBREE_TBB_LIBRARIES_${next_library_mode} ${library})
endif()
set(next_library_mode "")
endif()
endforeach()
foreach(library ${TBB_LIBRARIES})
string(TOLOWER "${library}" library_lower)
if(("${library_lower}" STREQUAL "optimized") OR
("${library_lower}" STREQUAL "debug"))
set(next_library_mode "${library_lower}")
else()
if(next_library_mode STREQUAL "")
list(APPEND EMBREE_TBB_LIBRARIES_optimized ${library})
list(APPEND EMBREE_TBB_LIBRARIES_debug ${library})
else()
list(APPEND EMBREE_TBB_LIBRARIES_${next_library_mode} ${library})
endif()
set(next_library_mode "")
endif()
endforeach()
list(APPEND sycl_compiler_flags
"$<$<CONFIG:Release>:${EMBREE_TBB_LIBRARIES_optimized}>"
"$<$<CONFIG:RelWithDebInfo>:${EMBREE_TBB_LIBRARIES_optimized}>"
"$<$<CONFIG:MinSizeRel>:${EMBREE_TBB_LIBRARIES_optimized}>"
"$<$<CONFIG:Debug>:${EMBREE_TBB_LIBRARIES_debug}>"
)
endif()
if(WITH_CYCLES_DEBUG)
list(APPEND sycl_compiler_flags -DWITH_CYCLES_DEBUG)
endif()
get_filename_component(sycl_compiler_root ${SYCL_COMPILER} DIRECTORY)
if(WIN32) # Add Windows specific compiler flags.
list(APPEND sycl_compiler_flags
-fms-extensions
-fms-compatibility
-D_WINDLL
-D_MBCS
-DWIN32
-D_WINDOWS
-D_CRT_NONSTDC_NO_DEPRECATE
-D_CRT_SECURE_NO_DEPRECATE
-DONEAPI_EXPORT
)
else() # Add Linux specific compiler flags.
list(APPEND sycl_compiler_flags -fPIC)
list(APPEND sycl_compiler_flags -fvisibility=hidden)
# Add $ORIGIN to `cycles_kernel_oneapi.so` RPATH so `libsycl.so` and
# `libpi_level_zero.so` can be placed next to it and get found.
list(APPEND sycl_compiler_flags -Wl,-rpath,'$$ORIGIN')
endif()
# Create CONFIG specific compiler flags.
set(sycl_compiler_flags_Release ${sycl_compiler_flags})
set(sycl_compiler_flags_Debug ${sycl_compiler_flags})
set(sycl_compiler_flags_RelWithDebInfo ${sycl_compiler_flags})
list(APPEND sycl_compiler_flags_Release
-DNDEBUG
)
list(APPEND sycl_compiler_flags_RelWithDebInfo
-DNDEBUG
-g
)
list(APPEND sycl_compiler_flags_Debug
-g
)
if(WIN32)
list(APPEND sycl_compiler_flags_Debug
-D_DEBUG
-nostdlib
-Xclang --dependent-lib=msvcrtd
)
list(APPEND sycl_compiler_flags
-L"${sycl_compiler_root}/../lib" # To find sycl.lib
-L"${sycl_compiler_root}/../compiler/lib/intel64_win" # To find libircmt.lib (when using `icpx`)
)
add_custom_command(
OUTPUT ${cycles_kernel_oneapi_lib} ${cycles_kernel_oneapi_linker_lib}
COMMAND ${CMAKE_COMMAND} -E env
"PATH=${OCLOC_INSTALL_DIR}\;${sycl_compiler_root}"
${SYCL_COMPILER}
"$<$<CONFIG:Release>:${sycl_compiler_flags_Release}>"
"$<$<CONFIG:RelWithDebInfo>:${sycl_compiler_flags_RelWithDebInfo}>"
"$<$<CONFIG:Debug>:${sycl_compiler_flags_Debug}>"
"$<$<CONFIG:MinSizeRel>:${sycl_compiler_flags_Release}>"
COMMAND_EXPAND_LISTS
DEPENDS ${cycles_oneapi_kernel_sources} ${SYCL_COMPILER})
else()
# The following join/replace operations are to prevent cmake from
# escaping space chars with backslashes in add_custom_command.
list(JOIN sycl_compiler_flags_Release " " sycl_compiler_flags_Release_str)
string(REPLACE " " ";" sycl_compiler_flags_Release_str ${sycl_compiler_flags_Release_str})
list(JOIN sycl_compiler_flags_RelWithDebInfo " " sycl_compiler_flags_RelWithDebInfo_str)
string(REPLACE " " ";" sycl_compiler_flags_RelWithDebInfo_str ${sycl_compiler_flags_RelWithDebInfo_str})
list(JOIN sycl_compiler_flags_Debug " " sycl_compiler_flags_Debug_str)
string(REPLACE " " ";" sycl_compiler_flags_Debug_str ${sycl_compiler_flags_Debug_str})
add_custom_command(
OUTPUT ${cycles_kernel_oneapi_lib}
COMMAND
${CMAKE_COMMAND} -E env
"LD_LIBRARY_PATH=${sycl_compiler_root}/../lib:${OCLOC_LD_LIBRARY_PATH}"
# `$ENV{PATH}` is for compiler to find `ld`.
"PATH=${OCLOC_INSTALL_DIR}/bin:${sycl_compiler_root}:$ENV{PATH}"
${SYCL_COMPILER}
"$<$<CONFIG:Release>:${sycl_compiler_flags_Release_str}>"
"$<$<CONFIG:RelWithDebInfo>:${sycl_compiler_flags_RelWithDebInfo_str}>"
"$<$<CONFIG:Debug>:${sycl_compiler_flags_Debug_str}>"
"$<$<CONFIG:MinSizeRel>:${sycl_compiler_flags_Release_str}>"
COMMAND_EXPAND_LISTS
DEPENDS ${cycles_oneapi_kernel_sources} ${SYCL_COMPILER})
endif()
# install dynamic libraries required at runtime
delayed_install("" "${cycles_kernel_oneapi_lib}" ${cycles_kernel_runtime_lib_target_path})
add_custom_target(cycles_kernel_oneapi
ALL
DEPENDS ${cycles_kernel_oneapi_lib}
SOURCES ${SRC_KERNEL_DEVICE_ONEAPI} ${SRC_KERNEL_DEVICE_ONEAPI_HEADERS}
)
cycles_set_solution_folder(cycles_kernel_oneapi)
source_group("device\\oneapi" FILES ${SRC_KERNEL_DEVICE_ONEAPI} ${SRC_KERNEL_DEVICE_ONEAPI_HEADERS})
add_dependencies(cycles_kernel cycles_kernel_oneapi)
endif()

View File

@@ -0,0 +1,270 @@
/* SPDX-FileCopyrightText: 2021-2022 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#define __KERNEL_GPU__
#define __KERNEL_ONEAPI__
#define __KERNEL_64_BIT__
#ifdef WITH_EMBREE_GPU
# define __KERNEL_GPU_RAYTRACING__
#endif
#define CCL_NAMESPACE_BEGIN
#define CCL_NAMESPACE_END
#include <cstdint>
#include <math.h>
#ifndef __NODES_MAX_GROUP__
# define __NODES_MAX_GROUP__ NODE_GROUP_LEVEL_MAX
#endif
#ifndef __NODES_FEATURES__
# define __NODES_FEATURES__ NODE_FEATURE_ALL
#endif
/* This one does not have an abstraction.
* It's used by other devices directly.
*/
#define __device__
/* Qualifier wrappers for different names on different devices */
#define ccl_device inline
#define ccl_device_extern extern "C"
#define ccl_global
#define ccl_always_inline __attribute__((always_inline))
#define ccl_device_inline __attribute__((always_inline))
#define ccl_noinline __attribute__((noinline))
#define ccl_inline_constant const constexpr
#define ccl_device_constant static constexpr
#define ccl_static_constexpr static constexpr
#define ccl_device_forceinline __attribute__((always_inline))
#define ccl_device_noinline __attribute__((noinline))
#define ccl_device_noinline_cpu ccl_device
#define ccl_device_inline_method ccl_device
#define ccl_device_template_spec template<> ccl_device_inline
#define ccl_restrict __restrict__
#define ccl_optional_struct_init
#define ccl_private
#define ccl_ray_data ccl_private
#define ccl_gpu_shared
#define ATTR_FALLTHROUGH __attribute__((fallthrough))
#define ccl_constant const
#define ccl_try_align(...) __attribute__((aligned(__VA_ARGS__)))
#define ccl_align(n) __attribute__((aligned(n)))
#define kernel_assert(cond)
#define ccl_may_alias
#define ccl_attr_maybe_unused [[maybe_unused]]
/* clang-format off */
/* kernel.h adapters */
#define ccl_gpu_kernel(block_num_threads, thread_num_registers)
#define ccl_gpu_kernel_threads(block_num_threads)
# define __ccl_gpu_kernel_signature(name, ...) \
void oneapi_kernel_##name(KernelGlobalsGPU *ccl_restrict kg, \
size_t kernel_global_size, \
size_t kernel_local_size, \
sycl::handler &cgh, \
__VA_ARGS__) { \
(void)(kg); \
cgh.parallel_for( \
sycl::nd_range<1>(kernel_global_size, kernel_local_size), \
[=](sycl::nd_item<1> item) {
# define ccl_gpu_kernel_signature __ccl_gpu_kernel_signature
# define ccl_gpu_kernel_postfix \
}); \
}
#define ccl_gpu_kernel_call(x) ((ONEAPIKernelContext*)kg)->x
#define ccl_gpu_kernel_within_bounds(i, n) ((i) < (n))
#define ccl_gpu_kernel_lambda(func, ...) \
struct KernelLambda \
{ \
KernelLambda(const ONEAPIKernelContext *_kg) : kg(_kg) {} \
ccl_private const ONEAPIKernelContext *kg; \
__VA_ARGS__; \
int operator()(const int state) const { return (func); } \
} ccl_gpu_kernel_lambda_pass((ONEAPIKernelContext *)kg)
/* GPU thread, block, grid size and index */
# define ccl_gpu_thread_idx_x (sycl::ext::oneapi::this_work_item::get_nd_item<1>().get_local_id(0))
# define ccl_gpu_block_dim_x (sycl::ext::oneapi::this_work_item::get_nd_item<1>().get_local_range(0))
# define ccl_gpu_block_idx_x (sycl::ext::oneapi::this_work_item::get_nd_item<1>().get_group(0))
# define ccl_gpu_grid_dim_x (sycl::ext::oneapi::this_work_item::get_nd_item<1>().get_group_range(0))
# define ccl_gpu_warp_size (sycl::ext::oneapi::this_work_item::get_sub_group().get_local_range()[0])
# define ccl_gpu_thread_mask(thread_warp) uint(0xFFFFFFFF >> (ccl_gpu_warp_size - thread_warp))
# define ccl_gpu_global_id_x() (sycl::ext::oneapi::this_work_item::get_nd_item<1>().get_global_id(0))
# define ccl_gpu_global_size_x() (sycl::ext::oneapi::this_work_item::get_nd_item<1>().get_global_range(0))
/* GPU warp synchronization */
# define ccl_gpu_syncthreads() sycl::ext::oneapi::this_work_item::get_nd_item<1>().barrier()
# define ccl_gpu_local_syncthreads() sycl::ext::oneapi::this_work_item::get_nd_item<1>().barrier(sycl::access::fence_space::local_space)
/* A ballot in SYCL is only available as an Intel extension and its DPC++ v6.3 implementation
* does not support devices with sub-group sizes above 64. Summing values (of any type) within
* sub-groups can be achieved with the SYCL core feature inclusive_scan_over_group, which has
* better support on non-Intel devices. */
# define ccl_gpu_ballot(predicate) 0; static_assert(false, "Use sycl::inclusive_scan_over_group on oneAPI device instead of ccl_gpu_ballot")
/* Debug defines */
#if defined(__SYCL_DEVICE_ONLY__)
# define CCL_ONEAPI_CONSTANT __attribute__((opencl_constant))
#else
# define CCL_ONEAPI_CONSTANT
#endif
#define sycl_printf(format, ...) { \
static const CCL_ONEAPI_CONSTANT char fmt[] = format; \
sycl::ext::oneapi::experimental::printf(fmt, __VA_ARGS__ ); \
}
#define sycl_printf_(format) { \
static const CCL_ONEAPI_CONSTANT char fmt[] = format; \
sycl::ext::oneapi::experimental::printf(fmt); \
}
/* GPU texture objects */
/* clang-format on */
/* Types */
/* It's not possible to use sycl types like sycl::float3, sycl::int3, etc
* because these types have different interfaces from blender version. */
using uchar = unsigned char;
using sycl::half;
/* math functions */
ccl_device_forceinline float __uint_as_float(unsigned int x)
{
return sycl::bit_cast<float>(x);
}
ccl_device_forceinline unsigned int __float_as_uint(const float x)
{
return sycl::bit_cast<unsigned int>(x);
}
ccl_device_forceinline float __int_as_float(const int x)
{
return sycl::bit_cast<float>(x);
}
ccl_device_forceinline int __float_as_int(const float x)
{
return sycl::bit_cast<int>(x);
}
#define fabsf(x) sycl::fabs((x))
#define copysignf(x, y) sycl::copysign((x), (y))
#define asinf(x) sycl::asin((x))
#define acosf(x) sycl::acos((x))
#define atanf(x) sycl::atan((x))
#define floorf(x) sycl::floor((x))
#define ceilf(x) sycl::ceil((x))
#define roundf(x) sycl::round((x))
#define sinhf(x) sycl::sinh((x))
#define coshf(x) sycl::cosh((x))
#define tanhf(x) sycl::tanh((x))
#define hypotf(x, y) sycl::hypot((x), (y))
#define atan2f(x, y) sycl::atan2((x), (y))
#define fmaxf(x, y) sycl::fmax((x), (y))
#define fminf(x, y) sycl::fmin((x), (y))
#define fmodf(x, y) sycl::fmod((x), (y))
#define lgammaf(x) sycl::lgamma((x))
#define ldexpf(x, y) sycl::ldexp((x), (y))
#define cosf(x) sycl::native::cos(((float)(x)))
#define sinf(x) sycl::native::sin(((float)(x)))
#define powf(x, y) sycl::native::powr(((float)(x)), ((float)(y)))
#define tanf(x) sycl::native::tan(((float)(x)))
#define logf(x) sycl::native::log(((float)(x)))
#define expf(x) sycl::native::exp(((float)(x)))
#define sqrtf(x) sycl::native::sqrt(((float)(x)))
#define __forceinline __attribute__((always_inline))
/* Types */
#include "util/half.h"
#include "util/types.h"
static_assert(
sizeof(sycl::ext::oneapi::experimental::sampled_image_handle::raw_image_handle_type) ==
sizeof(uint64_t));
typedef uint64_t ccl_gpu_image_object_2D;
typedef uint64_t ccl_gpu_image_object_3D;
template<typename T>
ccl_device_forceinline T ccl_gpu_image_object_read_2D(const ccl_gpu_image_object_2D texobj,
const float x,
const float y)
{
/* Generic implementation not possible due to limitation with SYCL bindless sampled images
* not being able to read in a format, which is different from the supported data type of
* the texture.
* But looks it looks like this is not a problem at the moment. */
static_assert(false);
return T();
}
template<>
ccl_device_forceinline float ccl_gpu_image_object_read_2D<float>(
const ccl_gpu_image_object_2D texobj, const float x, const float y)
{
sycl::ext::oneapi::experimental::sampled_image_handle image(
(sycl::ext::oneapi::experimental::sampled_image_handle::raw_image_handle_type)texobj);
return sycl::ext::oneapi::experimental::sample_image<float>(image, sycl::float2{x, y});
}
template<>
ccl_device_forceinline float4 ccl_gpu_image_object_read_2D<float4>(
const ccl_gpu_image_object_2D texobj, const float x, const float y)
{
sycl::ext::oneapi::experimental::sampled_image_handle image(
(sycl::ext::oneapi::experimental::sampled_image_handle::raw_image_handle_type)texobj);
return sycl::ext::oneapi::experimental::sample_image<float4, sycl::vec<float, 4>>(
image, sycl::float2{x, y});
}
template<typename T>
ccl_device_forceinline T ccl_gpu_image_object_read_3D(const ccl_gpu_image_object_3D texobj,
const float x,
const float y,
const float z)
{
/* A generic implementation is not possible due to limitations with SYCL bindless sampled images
* not being able to read in a format that is different from the supported data type of
* the texture.
* However, it looks like this is not a problem at the moment, but I am leaving a static
* assert in order to easily detect if it becomes a problem in the future. */
static_assert(false);
return T();
}
template<>
ccl_device_forceinline float ccl_gpu_image_object_read_3D<float>(
const ccl_gpu_image_object_3D texobj, const float x, const float y, const float z)
{
sycl::ext::oneapi::experimental::sampled_image_handle image(
(sycl::ext::oneapi::experimental::sampled_image_handle::raw_image_handle_type)texobj);
return sycl::ext::oneapi::experimental::sample_image<float>(image, sycl::float3{x, y, z});
}
template<>
ccl_device_forceinline float4 ccl_gpu_image_object_read_3D<float4>(
const ccl_gpu_image_object_3D texobj, const float x, const float y, const float z)
{
sycl::ext::oneapi::experimental::sampled_image_handle image(
(sycl::ext::oneapi::experimental::sampled_image_handle::raw_image_handle_type)texobj);
return sycl::ext::oneapi::experimental::sample_image<float4, sycl::vec<float, 4>>(
image, sycl::float3{x, y, z});
}

View File

@@ -0,0 +1,11 @@
/* SPDX-FileCopyrightText: 2021-2022 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "kernel/util/nanovdb.h"
/* clang-format off */
struct ONEAPIKernelContext : public KernelGlobalsGPU {
public:
# include "kernel/device/gpu/image.h"
/* clang-format on */

View File

@@ -0,0 +1,8 @@
/* SPDX-FileCopyrightText: 2021-2022 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0 */
}
; /* end of ONEAPIKernelContext class definition */
#undef kernel_integrator_state
#define kernel_integrator_state (*(kg->integrator_state))

View File

@@ -0,0 +1,19 @@
/* SPDX-FileCopyrightText: 2023 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0 */
#if defined(WITH_EMBREE_GPU)
# undef ccl_gpu_kernel_signature
# define ccl_gpu_kernel_signature(name, ...) \
void oneapi_kernel_##name(KernelGlobalsGPU *ccl_restrict kg, \
size_t kernel_global_size, \
size_t kernel_local_size, \
sycl::handler &cgh, \
__VA_ARGS__) \
{ \
(void)(kg); \
cgh.parallel_for( \
sycl::nd_range<1>(kernel_global_size, kernel_local_size), \
[=](sycl::nd_item<1> item, sycl::kernel_handler oneapi_kernel_handler) { \
((ONEAPIKernelContext*)kg)->kernel_handler = oneapi_kernel_handler;
#endif

View File

@@ -0,0 +1,8 @@
/* SPDX-FileCopyrightText: 2023 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0 */
#if defined(WITH_EMBREE_GPU)
# undef ccl_gpu_kernel_signature
# define ccl_gpu_kernel_signature __ccl_gpu_kernel_signature
#endif

View File

@@ -0,0 +1,47 @@
/* SPDX-FileCopyrightText: 2021-2022 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0 */
/* Constant Globals */
#pragma once
#include "kernel/types.h"
#include "kernel/integrator/state.h"
#include "kernel/util/profiler.h"
#include "util/color.h"
#include "util/types_image.h"
CCL_NAMESPACE_BEGIN
/* NOTE(@nsirgien): With SYCL we can't declare __constant__ global variable, which will be
* accessible from device code, like it has been done for Cycles CUDA backend. So, the backend will
* allocate this "constant" memory regions and store pointers to them in oneAPI context class */
struct IntegratorStateGPU;
struct IntegratorQueueCounter;
struct KernelGlobalsGPU {
#define KERNEL_DATA_ARRAY(type, name) const type *__##name = nullptr;
#define KERNEL_DATA_ARRAY_WRITABLE(type, name) type *__##name = nullptr;
#include "kernel/data_arrays.h"
IntegratorStateGPU *integrator_state;
const KernelData *__data;
sycl::kernel_handler kernel_handler;
};
using KernelGlobals = ccl_global KernelGlobalsGPU *ccl_restrict;
#define kernel_data (*(__data))
#define kernel_integrator_state (*(integrator_state))
/* data lookup defines */
#define kernel_data_fetch(name, index) __##name[(index)]
#define kernel_data_write(name, index, value) __##name[(index)] = (value)
#define kernel_data_array(name) __##name
CCL_NAMESPACE_END

View File

@@ -0,0 +1,758 @@
/* SPDX-FileCopyrightText: 2021-2022 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_ONEAPI
# include "kernel.h"
# include <iostream>
# include <map>
# include <set>
/* <algorithm> is needed until included upstream in sycl/detail/property_list_base.hpp */
# include <algorithm>
# include <sycl/sycl.hpp>
# include "kernel/device/oneapi/compat.h"
# include "kernel/device/oneapi/globals.h"
# include "kernel/device/oneapi/kernel_templates.h"
# include "kernel/device/gpu/kernel.h"
# include "device/kernel.cpp"
static OneAPIErrorCallback s_error_cb = nullptr;
static void *s_error_user_ptr = nullptr;
# ifdef WITH_EMBREE_GPU
static RTCFeatureFlags oneapi_embree_features_from_kernel_features(const uint kernel_features)
{
unsigned int feature_flags = RTC_FEATURE_FLAG_TRIANGLE | RTC_FEATURE_FLAG_INSTANCE |
RTC_FEATURE_FLAG_FILTER_FUNCTION_IN_ARGUMENTS;
if (kernel_features & KERNEL_FEATURE_HAIR_THICK) {
feature_flags |= RTC_FEATURE_FLAG_ROUND_CATMULL_ROM_CURVE |
RTC_FEATURE_FLAG_ROUND_LINEAR_CURVE;
}
if (kernel_features & KERNEL_FEATURE_HAIR) {
feature_flags |= RTC_FEATURE_FLAG_FLAT_CATMULL_ROM_CURVE;
}
if (kernel_features & KERNEL_FEATURE_POINTCLOUD) {
feature_flags |= RTC_FEATURE_FLAG_POINT;
}
if (kernel_features & KERNEL_FEATURE_OBJECT_MOTION) {
feature_flags |= RTC_FEATURE_FLAG_MOTION_BLUR;
}
return (RTCFeatureFlags)feature_flags;
}
# endif
void oneapi_set_error_cb(OneAPIErrorCallback cb, void *user_ptr)
{
s_error_cb = cb;
s_error_user_ptr = user_ptr;
}
size_t oneapi_suggested_gpu_kernel_size(const DeviceKernel kernel)
{
/* This defines are available only to the device code, so making this function
* seems to be the most reasonable way to provide access to them for the host code. */
switch (kernel) {
case DEVICE_KERNEL_INTEGRATOR_QUEUED_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_QUEUED_SHADOW_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_ACTIVE_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_TERMINATED_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_TERMINATED_SHADOW_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_COMPACT_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_COMPACT_SHADOW_PATHS_ARRAY:
return GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE;
case DEVICE_KERNEL_INTEGRATOR_SORTED_PATHS_ARRAY:
case DEVICE_KERNEL_INTEGRATOR_COMPACT_STATES:
case DEVICE_KERNEL_INTEGRATOR_COMPACT_SHADOW_STATES:
return GPU_PARALLEL_SORTED_INDEX_DEFAULT_BLOCK_SIZE;
case DEVICE_KERNEL_INTEGRATOR_SORT_BUCKET_PASS:
case DEVICE_KERNEL_INTEGRATOR_SORT_WRITE_PASS:
return GPU_PARALLEL_SORT_BLOCK_SIZE;
case DEVICE_KERNEL_PREFIX_SUM:
return GPU_PARALLEL_PREFIX_SUM_DEFAULT_BLOCK_SIZE;
default:
return (size_t)0;
}
}
/* NOTE(@nsirgien): Execution of this simple kernel will check basic functionality like
* memory allocations, memory transfers and execution of kernel with USM memory. */
bool oneapi_run_test_kernel(SyclQueue *queue_)
{
assert(queue_);
sycl::queue *queue = reinterpret_cast<sycl::queue *>(queue_);
const size_t N = 8;
const size_t memory_byte_size = sizeof(int) * N;
bool is_computation_correct = true;
try {
int *A_host = (int *)sycl::aligned_alloc_host(16, memory_byte_size, *queue);
for (size_t i = (size_t)0; i < N; i++) {
A_host[i] = rand() % 32;
}
int *A_device = (int *)sycl::malloc_device(memory_byte_size, *queue);
int *B_device = (int *)sycl::malloc_device(memory_byte_size, *queue);
queue->memcpy(A_device, A_host, memory_byte_size);
queue->wait_and_throw();
queue->submit([&](sycl::handler &cgh) {
cgh.parallel_for(N, [=](sycl::id<1> idx) { B_device[idx] = A_device[idx] + idx.get(0); });
});
queue->wait_and_throw();
int *B_host = (int *)sycl::aligned_alloc_host(16, memory_byte_size, *queue);
queue->memcpy(B_host, B_device, memory_byte_size);
queue->wait_and_throw();
for (size_t i = (size_t)0; i < N; i++) {
const int expected_result = i + A_host[i];
if (B_host[i] != expected_result) {
is_computation_correct = false;
if (s_error_cb) {
s_error_cb(("Incorrect result in test kernel execution - expected " +
std::to_string(expected_result) + ", got " + std::to_string(B_host[i]))
.c_str(),
s_error_user_ptr);
}
}
}
sycl::free(A_host, *queue);
sycl::free(B_host, *queue);
sycl::free(A_device, *queue);
sycl::free(B_device, *queue);
queue->wait_and_throw();
}
catch (const sycl::exception &e) {
if (s_error_cb) {
s_error_cb(e.what(), s_error_user_ptr);
}
return false;
}
return is_computation_correct;
}
bool oneapi_zero_memory_on_device(SyclQueue *queue_, void *device_pointer, const size_t num_bytes)
{
assert(queue_);
sycl::queue *queue = reinterpret_cast<sycl::queue *>(queue_);
try {
queue->memset(device_pointer, 0, num_bytes);
queue->wait_and_throw();
return true;
}
catch (const sycl::exception &e) {
if (s_error_cb) {
s_error_cb(e.what(), s_error_user_ptr);
}
return false;
}
}
bool oneapi_kernel_is_required_for_features(const std::string &kernel_name,
const uint kernel_features)
{
/* Skip all non-Cycles kernels */
if (kernel_name.find("oneapi_kernel_") == std::string::npos) {
return false;
}
if ((kernel_features & KERNEL_FEATURE_NODE_RAYTRACE) == 0 &&
kernel_name.find(device_kernel_as_string(DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE)) !=
std::string::npos)
{
return false;
}
if ((kernel_features & KERNEL_FEATURE_MNEE) == 0 &&
kernel_name.find(device_kernel_as_string(DEVICE_KERNEL_INTEGRATOR_INTERSECT_MNEE)) !=
std::string::npos)
{
return false;
}
if ((kernel_features & KERNEL_FEATURE_VOLUME) == 0 &&
kernel_name.find(device_kernel_as_string(DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK)) !=
std::string::npos)
{
return false;
}
if (((kernel_features & (KERNEL_FEATURE_PATH_TRACING | KERNEL_FEATURE_BAKING)) == 0) &&
((kernel_name.find(device_kernel_as_string(DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST)) !=
std::string::npos) ||
(kernel_name.find(device_kernel_as_string(DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW)) !=
std::string::npos) ||
(kernel_name.find(device_kernel_as_string(DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE)) !=
std::string::npos) ||
(kernel_name.find(device_kernel_as_string(DEVICE_KERNEL_INTEGRATOR_INTERSECT_MNEE)) !=
std::string::npos) ||
(kernel_name.find(device_kernel_as_string(
DEVICE_KERNEL_INTEGRATOR_INTERSECT_DEDICATED_LIGHT)) != std::string::npos)))
{
return false;
}
return true;
}
bool oneapi_kernel_is_compatible_with_hardware_raytracing(const std::string &kernel_name)
{
/* MNEE and Ray-trace kernels work correctly with Hardware Ray-tracing starting with Embree 4.1.
*/
# if defined(RTC_VERSION) && RTC_VERSION < 40100
return (kernel_name.find(device_kernel_as_string(DEVICE_KERNEL_INTEGRATOR_INTERSECT_MNEE)) ==
std::string::npos) &&
(kernel_name.find(device_kernel_as_string(
DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE)) == std::string::npos);
# else
return true;
# endif
}
bool oneapi_kernel_has_intersections(const std::string &kernel_name)
{
for (int i = 0; i < (int)DEVICE_KERNEL_NUM; i++) {
DeviceKernel kernel = (DeviceKernel)i;
if (device_kernel_has_intersection(kernel)) {
if (kernel_name.find(device_kernel_as_string(kernel)) != std::string::npos) {
return true;
}
}
}
return false;
}
bool oneapi_load_kernels(SyclQueue *queue_,
const uint kernel_features,
bool use_hardware_raytracing)
{
assert(queue_);
sycl::queue *queue = reinterpret_cast<sycl::queue *>(queue_);
# ifdef WITH_EMBREE_GPU
/* For best performance, we always JIT compile the kernels that are using Embree. */
if (use_hardware_raytracing) {
try {
sycl::kernel_bundle<sycl::bundle_state::input> all_kernels_bundle =
sycl::get_kernel_bundle<sycl::bundle_state::input>(queue->get_context(),
{queue->get_device()});
for (const sycl::kernel_id &kernel_id : all_kernels_bundle.get_kernel_ids()) {
const std::string &kernel_name = kernel_id.get_name();
if (!oneapi_kernel_is_required_for_features(kernel_name, kernel_features) ||
!(oneapi_kernel_has_intersections(kernel_name) &&
oneapi_kernel_is_compatible_with_hardware_raytracing(kernel_name)))
{
continue;
}
sycl::kernel_bundle<sycl::bundle_state::input> one_kernel_bundle_input =
sycl::get_kernel_bundle<sycl::bundle_state::input>(
queue->get_context(), {queue->get_device()}, {kernel_id});
const RTCFeatureFlags embree_features = oneapi_embree_features_from_kernel_features(
kernel_features);
one_kernel_bundle_input
.set_specialization_constant<ONEAPIKernelContext::oneapi_embree_features>(
embree_features);
sycl::build(one_kernel_bundle_input);
}
}
catch (const sycl::exception &e) {
if (s_error_cb) {
s_error_cb(e.what(), s_error_user_ptr);
}
return false;
}
}
# endif
try {
sycl::kernel_bundle<sycl::bundle_state::input> all_kernels_bundle =
sycl::get_kernel_bundle<sycl::bundle_state::input>(queue->get_context(),
{queue->get_device()});
for (const sycl::kernel_id &kernel_id : all_kernels_bundle.get_kernel_ids()) {
const std::string &kernel_name = kernel_id.get_name();
/* In case HWRT is on, compilation of kernels using Embree is already handled in previous
* block. */
if (!oneapi_kernel_is_required_for_features(kernel_name, kernel_features) ||
(use_hardware_raytracing && oneapi_kernel_has_intersections(kernel_name) &&
oneapi_kernel_is_compatible_with_hardware_raytracing(kernel_name)))
{
continue;
}
# ifdef WITH_EMBREE_GPU
if (oneapi_kernel_has_intersections(kernel_name)) {
sycl::kernel_bundle<sycl::bundle_state::input> one_kernel_bundle_input =
sycl::get_kernel_bundle<sycl::bundle_state::input>(
queue->get_context(), {queue->get_device()}, {kernel_id});
one_kernel_bundle_input
.set_specialization_constant<ONEAPIKernelContext::oneapi_embree_features>(
RTC_FEATURE_FLAG_NONE);
sycl::build(one_kernel_bundle_input);
continue;
}
# endif
/* This call will ensure that AoT or cached JIT binaries are available
* for execution. It will trigger compilation if it is not already the case. */
(void)sycl::get_kernel_bundle<sycl::bundle_state::executable>(
queue->get_context(), {queue->get_device()}, {kernel_id});
}
}
catch (const sycl::exception &e) {
if (s_error_cb) {
s_error_cb(e.what(), s_error_user_ptr);
}
return false;
}
return true;
}
bool oneapi_enqueue_kernel(KernelContext *kernel_context,
const int kernel,
const size_t global_size,
const size_t local_size,
const uint kernel_features,
bool use_hardware_raytracing,
void **args)
{
bool success = true;
::DeviceKernel device_kernel = (::DeviceKernel)kernel;
KernelGlobalsGPU *kg = (KernelGlobalsGPU *)kernel_context->kernel_globals;
sycl::queue *queue = reinterpret_cast<sycl::queue *>(kernel_context->queue);
assert(queue);
if (!queue) {
return false;
}
/* Let the compiler throw an error if there are any kernels missing in this implementation. */
# if defined(_WIN32)
# pragma warning(error : 4062)
# elif defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic error "-Wswitch"
# endif
int max_shaders = 0;
if (device_kernel == DEVICE_KERNEL_INTEGRATOR_SORT_BUCKET_PASS ||
device_kernel == DEVICE_KERNEL_INTEGRATOR_SORT_WRITE_PASS)
{
max_shaders = (kernel_context->scene_max_shaders);
}
try {
queue->submit([&](sycl::handler &cgh) {
# ifdef WITH_EMBREE_GPU
/* Spec says it has no effect if the called kernel doesn't support the below specialization
* constant but it can still trigger a recompilation, so we set it only if needed. */
if (device_kernel_has_intersection(device_kernel)) {
const RTCFeatureFlags embree_features = use_hardware_raytracing ?
oneapi_embree_features_from_kernel_features(
kernel_features) :
RTC_FEATURE_FLAG_NONE;
cgh.set_specialization_constant<ONEAPIKernelContext::oneapi_embree_features>(
embree_features);
}
# else
(void)kernel_features;
# endif
switch (device_kernel) {
case DEVICE_KERNEL_INTEGRATOR_RESET: {
oneapi_call(kg, cgh, global_size, local_size, args, oneapi_kernel_integrator_reset);
break;
}
case DEVICE_KERNEL_INTEGRATOR_INIT_FROM_CAMERA: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_integrator_init_from_camera);
break;
}
case DEVICE_KERNEL_INTEGRATOR_INIT_FROM_BAKE: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_integrator_init_from_bake);
break;
}
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_integrator_intersect_closest);
break;
}
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_integrator_intersect_shadow);
break;
}
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE: {
oneapi_call(kg,
cgh,
global_size,
local_size,
args,
oneapi_kernel_integrator_intersect_subsurface);
break;
}
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK: {
oneapi_call(kg,
cgh,
global_size,
local_size,
args,
oneapi_kernel_integrator_intersect_volume_stack);
break;
}
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_DEDICATED_LIGHT: {
oneapi_call(kg,
cgh,
global_size,
local_size,
args,
oneapi_kernel_integrator_intersect_dedicated_light);
break;
}
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_MNEE: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_integrator_intersect_mnee);
break;
}
case DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_integrator_shade_background);
break;
}
case DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_NEE: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_integrator_shade_light_nee);
break;
}
case DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_FORWARD: {
oneapi_call(kg,
cgh,
global_size,
local_size,
args,
oneapi_kernel_integrator_shade_light_forward);
break;
}
case DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_integrator_shade_shadow);
break;
}
case DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_integrator_shade_surface);
break;
}
case DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE: {
oneapi_call(kg,
cgh,
global_size,
local_size,
args,
oneapi_kernel_integrator_shade_surface_raytrace);
break;
}
case DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_integrator_shade_volume);
break;
}
case DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME_RAY_MARCHING: {
oneapi_call(kg,
cgh,
global_size,
local_size,
args,
oneapi_kernel_integrator_shade_volume_ray_marching);
break;
}
case DEVICE_KERNEL_INTEGRATOR_SHADE_DEDICATED_LIGHT: {
oneapi_call(kg,
cgh,
global_size,
local_size,
args,
oneapi_kernel_integrator_shade_dedicated_light);
break;
}
case DEVICE_KERNEL_INTEGRATOR_QUEUED_PATHS_ARRAY: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_integrator_queued_paths_array);
break;
}
case DEVICE_KERNEL_INTEGRATOR_QUEUED_SHADOW_PATHS_ARRAY: {
oneapi_call(kg,
cgh,
global_size,
local_size,
args,
oneapi_kernel_integrator_queued_shadow_paths_array);
break;
}
case DEVICE_KERNEL_INTEGRATOR_ACTIVE_PATHS_ARRAY: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_integrator_active_paths_array);
break;
}
case DEVICE_KERNEL_INTEGRATOR_TERMINATED_PATHS_ARRAY: {
oneapi_call(kg,
cgh,
global_size,
local_size,
args,
oneapi_kernel_integrator_terminated_paths_array);
break;
}
case DEVICE_KERNEL_INTEGRATOR_TERMINATED_SHADOW_PATHS_ARRAY: {
oneapi_call(kg,
cgh,
global_size,
local_size,
args,
oneapi_kernel_integrator_terminated_shadow_paths_array);
break;
}
case DEVICE_KERNEL_INTEGRATOR_SORTED_PATHS_ARRAY: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_integrator_sorted_paths_array);
break;
}
case DEVICE_KERNEL_INTEGRATOR_SORT_BUCKET_PASS: {
sycl::local_accessor<int> local_mem(max_shaders, cgh);
oneapi_kernel_integrator_sort_bucket_pass(kg,
global_size,
local_size,
cgh,
*(int *)(args[0]),
*(int *)(args[1]),
*(int *)(args[2]),
*(int **)(args[3]),
*(int *)(args[4]),
local_mem);
break;
}
case DEVICE_KERNEL_INTEGRATOR_SORT_WRITE_PASS: {
sycl::local_accessor<int> local_mem(max_shaders, cgh);
oneapi_kernel_integrator_sort_write_pass(kg,
global_size,
local_size,
cgh,
*(int *)(args[0]),
*(int *)(args[1]),
*(int *)(args[2]),
*(int **)(args[3]),
*(int *)(args[4]),
local_mem);
break;
}
case DEVICE_KERNEL_INTEGRATOR_COMPACT_PATHS_ARRAY: {
oneapi_call(kg,
cgh,
global_size,
local_size,
args,
oneapi_kernel_integrator_compact_paths_array);
break;
}
case DEVICE_KERNEL_INTEGRATOR_COMPACT_SHADOW_PATHS_ARRAY: {
oneapi_call(kg,
cgh,
global_size,
local_size,
args,
oneapi_kernel_integrator_compact_shadow_paths_array);
break;
}
case DEVICE_KERNEL_ADAPTIVE_SAMPLING_CONVERGENCE_CHECK: {
oneapi_call(kg,
cgh,
global_size,
local_size,
args,
oneapi_kernel_adaptive_sampling_convergence_check);
break;
}
case DEVICE_KERNEL_ADAPTIVE_SAMPLING_CONVERGENCE_FILTER_X: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_adaptive_sampling_filter_x);
break;
}
case DEVICE_KERNEL_ADAPTIVE_SAMPLING_CONVERGENCE_FILTER_Y: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_adaptive_sampling_filter_y);
break;
}
case DEVICE_KERNEL_SHADER_EVAL_DISPLACE: {
oneapi_call(kg, cgh, global_size, local_size, args, oneapi_kernel_shader_eval_displace);
break;
}
case DEVICE_KERNEL_SHADER_EVAL_BACKGROUND: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_shader_eval_background);
break;
}
case DEVICE_KERNEL_SHADER_EVAL_CURVE_SHADOW_TRANSPARENCY: {
oneapi_call(kg,
cgh,
global_size,
local_size,
args,
oneapi_kernel_shader_eval_curve_shadow_transparency);
break;
}
case DEVICE_KERNEL_SHADER_EVAL_VOLUME_DENSITY: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_shader_eval_volume_density);
break;
}
case DEVICE_KERNEL_PREFIX_SUM: {
oneapi_call(kg, cgh, global_size, local_size, args, oneapi_kernel_prefix_sum);
break;
}
case DEVICE_KERNEL_VOLUME_GUIDING_FILTER_X: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_volume_guiding_filter_x);
break;
}
case DEVICE_KERNEL_VOLUME_GUIDING_FILTER_Y: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_volume_guiding_filter_y);
break;
}
/* clang-format off */
# define DEVICE_KERNEL_FILM_CONVERT_PARTIAL(VARIANT, variant) \
case DEVICE_KERNEL_FILM_CONVERT_##VARIANT: { \
oneapi_call(kg, cgh, \
global_size, \
local_size, \
args, \
oneapi_kernel_film_convert_##variant); \
break; \
}
# define DEVICE_KERNEL_FILM_CONVERT(variant, VARIANT) \
DEVICE_KERNEL_FILM_CONVERT_PARTIAL(VARIANT, variant) \
DEVICE_KERNEL_FILM_CONVERT_PARTIAL(VARIANT##_HALF_RGBA, variant##_half_rgba)
DEVICE_KERNEL_FILM_CONVERT(depth, DEPTH);
DEVICE_KERNEL_FILM_CONVERT(mist, MIST);
DEVICE_KERNEL_FILM_CONVERT(volume_majorant, VOLUME_MAJORANT);
DEVICE_KERNEL_FILM_CONVERT(sample_count, SAMPLE_COUNT);
DEVICE_KERNEL_FILM_CONVERT(float, FLOAT);
DEVICE_KERNEL_FILM_CONVERT(light_path, LIGHT_PATH);
DEVICE_KERNEL_FILM_CONVERT(rgbe, RGBE);
DEVICE_KERNEL_FILM_CONVERT(float3, FLOAT3);
DEVICE_KERNEL_FILM_CONVERT(motion, MOTION);
DEVICE_KERNEL_FILM_CONVERT(cryptomatte, CRYPTOMATTE);
DEVICE_KERNEL_FILM_CONVERT(shadow_catcher, SHADOW_CATCHER);
DEVICE_KERNEL_FILM_CONVERT(shadow_catcher_matte_with_shadow,
SHADOW_CATCHER_MATTE_WITH_SHADOW);
DEVICE_KERNEL_FILM_CONVERT(combined, COMBINED);
DEVICE_KERNEL_FILM_CONVERT(float4, FLOAT4);
# undef DEVICE_KERNEL_FILM_CONVERT
# undef DEVICE_KERNEL_FILM_CONVERT_PARTIAL
/* clang-format on */
case DEVICE_KERNEL_FILTER_GUIDING_PREPROCESS: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_filter_guiding_preprocess);
break;
}
case DEVICE_KERNEL_FILTER_GUIDING_SET_FAKE_ALBEDO: {
oneapi_call(kg,
cgh,
global_size,
local_size,
args,
oneapi_kernel_filter_guiding_set_fake_albedo);
break;
}
case DEVICE_KERNEL_FILTER_COLOR_PREPROCESS: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_filter_color_preprocess);
break;
}
case DEVICE_KERNEL_FILTER_COLOR_POSTPROCESS: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_filter_color_postprocess);
break;
}
case DEVICE_KERNEL_FILTER_COLOR_FLIP_Y: {
oneapi_call(kg, cgh, global_size, local_size, args, oneapi_kernel_filter_color_flip_y);
break;
}
case DEVICE_KERNEL_CRYPTOMATTE_POSTPROCESS: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_cryptomatte_postprocess);
break;
}
case DEVICE_KERNEL_INTEGRATOR_COMPACT_STATES: {
oneapi_call(
kg, cgh, global_size, local_size, args, oneapi_kernel_integrator_compact_states);
break;
}
case DEVICE_KERNEL_INTEGRATOR_COMPACT_SHADOW_STATES: {
oneapi_call(kg,
cgh,
global_size,
local_size,
args,
oneapi_kernel_integrator_compact_shadow_states);
break;
}
case DEVICE_KERNEL_INTEGRATOR_SHADOW_CATCHER_COUNT_POSSIBLE_SPLITS: {
oneapi_call(kg,
cgh,
global_size,
local_size,
args,
oneapi_kernel_integrator_shadow_catcher_count_possible_splits);
break;
}
/* Unsupported kernels */
case DEVICE_KERNEL_NUM:
case DEVICE_KERNEL_INTEGRATOR_MEGAKERNEL:
case DEVICE_KERNEL_INTEGRATOR_SHADOW_PATH_MNEE_PENDING:
kernel_assert(0);
break;
}
});
}
catch (const sycl::exception &e) {
if (s_error_cb) {
s_error_cb(e.what(), s_error_user_ptr);
success = false;
}
}
# if defined(_WIN32)
# pragma warning(default : 4062)
# elif defined(__GNUC__)
# pragma GCC diagnostic pop
# endif
return success;
}
#endif /* WITH_ONEAPI */

View File

@@ -0,0 +1,67 @@
/* SPDX-FileCopyrightText: 2021-2022 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_ONEAPI
# include <stddef.h>
/* NOTE(@nsirgien): Should match underlying type in the declaration inside "kernel/types.h"
* TODO: use kernel/types.h directly. */
enum DeviceKernel : int;
# ifndef CYCLES_KERNEL_ONEAPI_EXPORT
# ifdef _WIN32
# if defined(ONEAPI_EXPORT)
# define CYCLES_KERNEL_ONEAPI_EXPORT extern __declspec(dllexport)
# else
# define CYCLES_KERNEL_ONEAPI_EXPORT extern __declspec(dllimport)
# endif
# else
# define CYCLES_KERNEL_ONEAPI_EXPORT extern __attribute__((visibility("default")))
# endif
# endif
class SyclQueue;
class SyclDevice;
typedef void (*OneAPIErrorCallback)(const char *error, void *user_ptr);
struct KernelContext {
/* Queue, associated with selected device */
SyclQueue *queue = nullptr;
/* Pointer to USM device memory with all global/constant allocation on this device */
void *kernel_globals = nullptr;
/* We needs this additional data for some kernels. */
int scene_max_shaders = 0;
};
/* Use extern C linking so that the symbols can be easily load from the dynamic library at runtime.
*/
# ifdef __cplusplus
extern "C" {
# endif
CYCLES_KERNEL_ONEAPI_EXPORT bool oneapi_run_test_kernel(SyclQueue *queue_);
CYCLES_KERNEL_ONEAPI_EXPORT bool oneapi_zero_memory_on_device(SyclQueue *queue_,
void *device_pointer,
size_t num_bytes);
CYCLES_KERNEL_ONEAPI_EXPORT void oneapi_set_error_cb(OneAPIErrorCallback cb, void *user_ptr);
CYCLES_KERNEL_ONEAPI_EXPORT size_t oneapi_suggested_gpu_kernel_size(const DeviceKernel kernel);
CYCLES_KERNEL_ONEAPI_EXPORT bool oneapi_enqueue_kernel(KernelContext *context,
const int kernel,
const size_t global_size,
const size_t local_size,
const unsigned int kernel_features,
bool use_hardware_raytracing,
void **args);
CYCLES_KERNEL_ONEAPI_EXPORT bool oneapi_load_kernels(SyclQueue *queue,
const unsigned int kernel_features,
bool use_hardware_raytracing);
# ifdef __cplusplus
}
# endif
#endif /* WITH_ONEAPI */

View File

@@ -0,0 +1,124 @@
/* SPDX-FileCopyrightText: 2021-2022 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
/* Some macro magic to generate templates for kernel arguments.
* The resulting oneapi_call() template allows to call a SYCL/C++ kernel
* with typed arguments by only giving it a void `**args` as given by Cycles.
* The template will automatically cast from void* to the expected type. */
/* When expanded by the preprocessor, the generated templates will look like this example: */
#if 0
template<typename T0, typename T1, typename T2>
void oneapi_call(
KernelGlobalsGPU *kg,
sycl::handler &cgh,
const size_t global_size,
const size_t local_size,
void **args,
void (*func)(const KernelGlobalsGPU *, size_t, size_t, sycl::handler &, T0, T1, T2))
{
func(kg, global_size, local_size, cgh, *(T0 *)(args[0]), *(T1 *)(args[1]), *(T2 *)(args[2]));
}
#endif
/* clang-format off */
#define ONEAPI_TYP(x) typename T##x
#define ONEAPI_CAST(x) *(T##x *)(args[x])
#define ONEAPI_T(x) T##x
#define ONEAPI_GET_NTH_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, N, ...) N
#define ONEAPI_0(_call, ...)
#define ONEAPI_1(_call, x) _call(x)
#define ONEAPI_2(_call, x, ...) _call(x), ONEAPI_1(_call, __VA_ARGS__)
#define ONEAPI_3(_call, x, ...) _call(x), ONEAPI_2(_call, __VA_ARGS__)
#define ONEAPI_4(_call, x, ...) _call(x), ONEAPI_3(_call, __VA_ARGS__)
#define ONEAPI_5(_call, x, ...) _call(x), ONEAPI_4(_call, __VA_ARGS__)
#define ONEAPI_6(_call, x, ...) _call(x), ONEAPI_5(_call, __VA_ARGS__)
#define ONEAPI_7(_call, x, ...) _call(x), ONEAPI_6(_call, __VA_ARGS__)
#define ONEAPI_8(_call, x, ...) _call(x), ONEAPI_7(_call, __VA_ARGS__)
#define ONEAPI_9(_call, x, ...) _call(x), ONEAPI_8(_call, __VA_ARGS__)
#define ONEAPI_10(_call, x, ...) _call(x), ONEAPI_9(_call, __VA_ARGS__)
#define ONEAPI_11(_call, x, ...) _call(x), ONEAPI_10(_call, __VA_ARGS__)
#define ONEAPI_12(_call, x, ...) _call(x), ONEAPI_11(_call, __VA_ARGS__)
#define ONEAPI_13(_call, x, ...) _call(x), ONEAPI_12(_call, __VA_ARGS__)
#define ONEAPI_14(_call, x, ...) _call(x), ONEAPI_13(_call, __VA_ARGS__)
#define ONEAPI_15(_call, x, ...) _call(x), ONEAPI_14(_call, __VA_ARGS__)
#define ONEAPI_16(_call, x, ...) _call(x), ONEAPI_15(_call, __VA_ARGS__)
#define ONEAPI_17(_call, x, ...) _call(x), ONEAPI_16(_call, __VA_ARGS__)
#define ONEAPI_18(_call, x, ...) _call(x), ONEAPI_17(_call, __VA_ARGS__)
#define ONEAPI_19(_call, x, ...) _call(x), ONEAPI_18(_call, __VA_ARGS__)
#define ONEAPI_20(_call, x, ...) _call(x), ONEAPI_19(_call, __VA_ARGS__)
#define ONEAPI_21(_call, x, ...) _call(x), ONEAPI_20(_call, __VA_ARGS__)
#define ONEAPI_CALL_FOR(x, ...) \
ONEAPI_GET_NTH_ARG("ignored", \
##__VA_ARGS__, \
ONEAPI_21, \
ONEAPI_20, \
ONEAPI_19, \
ONEAPI_18, \
ONEAPI_17, \
ONEAPI_16, \
ONEAPI_15, \
ONEAPI_14, \
ONEAPI_13, \
ONEAPI_12, \
ONEAPI_11, \
ONEAPI_10, \
ONEAPI_9, \
ONEAPI_8, \
ONEAPI_7, \
ONEAPI_6, \
ONEAPI_5, \
ONEAPI_4, \
ONEAPI_3, \
ONEAPI_2, \
ONEAPI_1, \
ONEAPI_0) \
(x, ##__VA_ARGS__)
/* This template automatically casts entries in the void **args array to the types requested by the kernel func.
* Since kernel parameters are passed as void ** to the device, this is the closest that we have to type safety. */
#define oneapi_template(...) \
template<ONEAPI_CALL_FOR(ONEAPI_TYP, __VA_ARGS__)> \
void oneapi_call( \
KernelGlobalsGPU *kg, \
sycl::handler &cgh, \
size_t global_size, \
size_t local_size, \
void **args, \
void (*func)(KernelGlobalsGPU*, size_t, size_t, sycl::handler &, ONEAPI_CALL_FOR(ONEAPI_T, __VA_ARGS__))) \
{ \
func(kg, \
global_size, \
local_size, \
cgh, \
ONEAPI_CALL_FOR(ONEAPI_CAST, __VA_ARGS__)); \
}
oneapi_template(0)
oneapi_template(0, 1)
oneapi_template(0, 1, 2)
oneapi_template(0, 1, 2, 3)
oneapi_template(0, 1, 2, 3, 4)
oneapi_template(0, 1, 2, 3, 4, 5)
oneapi_template(0, 1, 2, 3, 4, 5, 6)
oneapi_template(0, 1, 2, 3, 4, 5, 6, 7)
oneapi_template(0, 1, 2, 3, 4, 5, 6, 7, 8)
oneapi_template(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
oneapi_template(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
oneapi_template(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
oneapi_template(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
oneapi_template(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
oneapi_template(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
oneapi_template(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
oneapi_template(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
oneapi_template(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)
oneapi_template(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18)
oneapi_template(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
oneapi_template(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)
/* clang-format on */

View File

@@ -0,0 +1,160 @@
# SPDX-FileCopyrightText: 2011-2026 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
set(INC
../../..
)
set(INC_SYS
)
set(SRC_KERNEL_DEVICE_OPTIX
kernel.cu
kernel_mnee.cu
kernel_shader_raytrace.cu
)
if(WITH_CYCLES_OSL)
math(EXPR OSL_LIBRARY_VERSION_CODE "${OSL_VERSION_MAJOR} * 10000 + ${OSL_VERSION_MINOR} * 100 + ${OSL_VERSION_PATCH}")
set(SRC_KERNEL_DEVICE_OPTIX
${SRC_KERNEL_DEVICE_OPTIX}
../../osl/services_optix.cu
kernel_osl.cu
kernel_osl_camera.cu
kernel_osl_mnee.cu
kernel_osl_shader_raytrace.cu
kernel_osl_volume.cu
)
endif()
set(SRC_KERNEL_DEVICE_OPTIX_HEADERS
bvh.h
compat.h
globals.h
)
set(LIB
)
if(WITH_CYCLES_CUDA_BINARIES AND WITH_CYCLES_DEVICE_OPTIX)
# CUDA version
cuda_get_version(CUDA_VERSION)
# Modifies in parent scope:
# - `optix_ptx`: appended with compressed ptx output.
function(cycles_optix_kernel_add name input flags)
set(output "${CMAKE_CURRENT_BINARY_DIR}/${name}.ptx")
set(output_compressed "${output}.zst")
set(cuda_flags ${flags}
-I "${OPTIX_INCLUDE_DIR}"
-I "${CMAKE_CURRENT_SOURCE_DIR}/../../.."
-o ${output})
if(WITH_CYCLES_OSL)
set(cuda_flags ${cuda_flags}
-D OSL_LIBRARY_VERSION_CODE=${OSL_LIBRARY_VERSION_CODE})
endif()
set(arch compute_50)
set(cuda_nvcc_executable ${CUDA_NVCC_EXECUTABLE})
set(cuda_version ${CUDA_VERSION})
if("${CUDA_VERSION}" GREATER_EQUAL 130) # Support for Maxwell, Pascal and Volta was dropped in CUDA 13
if(DEFINED CUDA11_NVCC_EXECUTABLE)
# Use CUDA 11 for the OptiX PTX kernel, to retain support for older architectures.
set(cuda_nvcc_executable ${CUDA11_NVCC_EXECUTABLE})
set(cuda_version 110)
else()
set(arch compute_75)
endif()
endif()
cuda_add_common_flags(${cuda_version} ${arch} "${cuda_flags}" cuda_flags)
add_custom_command(
OUTPUT
${output}
DEPENDS
${input}
${SRC_KERNEL_DEVICE_OPTIX_HEADERS}
$<TARGET_PROPERTY:cycles_kernel,INTERFACE_SOURCES>
COMMAND
${cuda_nvcc_executable}
--ptx
-arch=${arch}
${cuda_flags}
${input}
WORKING_DIRECTORY
"${CMAKE_CURRENT_SOURCE_DIR}"
)
unset(cuda_nvcc_executable)
add_custom_command(
OUTPUT ${output_compressed}
COMMAND "$<TARGET_FILE:zstd_compress>" ${output} ${output_compressed}
DEPENDS ${output})
list(APPEND optix_ptx ${output_compressed})
set(optix_ptx "${optix_ptx}" PARENT_SCOPE)
delayed_install("${CMAKE_CURRENT_BINARY_DIR}" "${output_compressed}" ${CYCLES_INSTALL_PATH}/lib)
endfunction()
cycles_optix_kernel_add(
kernel_optix
"kernel.cu"
"")
cycles_optix_kernel_add(
kernel_optix_mnee
"kernel_mnee.cu"
"")
cycles_optix_kernel_add(
kernel_optix_shader_raytrace
"kernel_shader_raytrace.cu"
"--keep-device-functions")
if(WITH_CYCLES_OSL)
cycles_optix_kernel_add(
kernel_optix_osl
"kernel_osl.cu"
"--relocatable-device-code=true")
cycles_optix_kernel_add(
kernel_optix_osl_shader_raytrace
"kernel_osl_shader_raytrace.cu"
"--relocatable-device-code=true")
cycles_optix_kernel_add(
kernel_optix_osl_mnee
"kernel_osl_mnee.cu"
"--relocatable-device-code=true")
cycles_optix_kernel_add(
kernel_optix_osl_volume
"kernel_osl_volume.cu"
"--relocatable-device-code=true")
cycles_optix_kernel_add(
kernel_optix_osl_camera
"kernel_osl_camera.cu"
"--relocatable-device-code=true")
cycles_optix_kernel_add(
kernel_optix_osl_services
"../../osl/services_optix.cu"
"--relocatable-device-code=true")
endif()
add_custom_target(cycles_kernel_optix
ALL
DEPENDS ${optix_ptx}
SOURCES ${SRC_KERNEL_DEVICE_OPTIX} ${SRC_KERNEL_DEVICE_OPTIX_HEADERS}
)
cycles_set_solution_folder(cycles_kernel_optix)
source_group("device\\optix" FILES ${SRC_KERNEL_DEVICE_OPTIX} ${SRC_KERNEL_DEVICE_OPTIX_HEADERS})
add_dependencies(cycles_kernel cycles_kernel_optix)
endif()
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_DEVICE_OPTIX}" ${CYCLES_INSTALL_PATH}/source/kernel/device/optix)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_DEVICE_OPTIX_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/device/optix)

View File

@@ -0,0 +1,644 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* OptiX implementation of ray-scene intersection.
*
* Note on the payload registers.
* Intersection and filtering functions might be sharing the same registers, even if it is not
* very obvious from the trace/traverse call. The registers that have special meaning and are to
* be kept "locked" to their meaning:
* uint p4 = visibility;
* uint p6 = pointer_pack_to_uint_0(ray);
* uint p7 = pointer_pack_to_uint_1(ray); */
#pragma once
#include "kernel/bvh/types.h"
#include "kernel/bvh/util.h"
#define OPTIX_DEFINE_ABI_VERSION_ONLY
#include <optix_function_table.h>
CCL_NAMESPACE_BEGIN
/* Utilities. */
template<typename T> ccl_device_forceinline T *get_payload_ptr_0()
{
return pointer_unpack_from_uint<T>(optixGetPayload_0(), optixGetPayload_1());
}
template<typename T> ccl_device_forceinline T *get_payload_ptr_2()
{
return pointer_unpack_from_uint<T>(optixGetPayload_2(), optixGetPayload_3());
}
template<typename T> ccl_device_forceinline T *get_payload_ptr_6()
{
return (T *)(((uint64_t)optixGetPayload_7() << 32) | optixGetPayload_6());
}
ccl_device_forceinline int get_object_id()
{
#ifdef __OBJECT_MOTION__
/* Always get the instance ID from the TLAS
* There might be a motion transform node between TLAS and BLAS which does not have one. */
return optixGetInstanceIdFromHandle(optixGetTransformListHandle(0));
#else
return optixGetInstanceId();
#endif
}
ccl_device_forceinline Intersection get_intersection()
{
Intersection isect;
isect.t = optixGetRayTmax();
isect.prim = optixGetPrimitiveIndex();
isect.object = get_object_id();
if (optixIsTriangleHit()) {
/* Triangle. */
const float2 barycentrics = optixGetTriangleBarycentrics();
isect.u = barycentrics.x;
isect.v = barycentrics.y;
isect.type = kernel_data_fetch(objects, isect.object).primitive_type;
}
#ifdef __HAIR__
else if ((optixGetHitKind() & (~PRIMITIVE_MOTION)) != PRIMITIVE_POINT) {
/* Curve. */
isect.u = __uint_as_float(optixGetAttribute_0());
isect.v = __uint_as_float(optixGetAttribute_1());
const KernelCurveSegment segment = kernel_data_fetch(curve_segments, isect.prim);
isect.type = segment.type;
isect.prim = segment.prim;
}
#endif
else {
/* Point. */
isect.u = 0.0f;
isect.v = 0.0f;
isect.type = kernel_data_fetch(objects, isect.object).primitive_type;
}
return isect;
}
/* Hit/miss functions. */
extern "C" __global__ void __miss__kernel_optix_miss()
{
/* 'kernel_path_lamp_emission' checks intersection distance, so need to set it even on a miss. */
optixSetPayload_0(__float_as_uint(optixGetRayTmax()));
optixSetPayload_5(PRIMITIVE_NONE);
}
extern "C" __global__ void __anyhit__kernel_optix_ignore()
{
return optixIgnoreIntersection();
}
extern "C" __global__ void __closesthit__kernel_optix_ignore() {}
extern "C" __global__ void __anyhit__kernel_optix_local_hit()
{
#if defined(__HAIR__) || defined(__POINTCLOUD__)
if (!optixIsTriangleHit()) {
/* Ignore curves and points. */
return optixIgnoreIntersection();
}
#endif
#ifdef __BVH_LOCAL__
const int object = get_object_id();
if (object != optixGetPayload_4() /* local_object */) {
/* Only intersect with matching object. */
return optixIgnoreIntersection();
}
const int prim = optixGetPrimitiveIndex();
ccl_private Ray *const ray = get_payload_ptr_6<Ray>();
if (intersection_skip_self_local(ray->self, prim)) {
return optixIgnoreIntersection();
}
const uint max_hits = optixGetPayload_5();
if (max_hits == 0) {
/* Special case for when no hit information is requested, just report that something was hit */
optixSetPayload_5(true);
return optixTerminateRay();
}
const float isect_t = optixGetRayTmax();
uint *const lcg_state = get_payload_ptr_0<uint>();
LocalIntersection *const local_isect = get_payload_ptr_2<LocalIntersection>();
const int hit_index = local_intersect_get_record_index(
local_isect, isect_t, lcg_state, max_hits);
if (hit_index == -1) {
return optixIgnoreIntersection();
}
Intersection *isect = &local_isect->hits[hit_index];
isect->t = isect_t;
isect->prim = prim;
isect->object = get_object_id();
isect->type = kernel_data_fetch(objects, isect->object).primitive_type;
const float2 barycentrics = optixGetTriangleBarycentrics();
isect->u = barycentrics.x;
isect->v = barycentrics.y;
/* Record geometric normal. */
const int position_offset = kernel_data_fetch(objects, object).position_offset;
const packed_uint3 tri_vindex = kernel_data_fetch(tri_vindex, prim);
const float3 tri_a = kernel_data_fetch(tri_verts, position_offset + tri_vindex.x);
const float3 tri_b = kernel_data_fetch(tri_verts, position_offset + tri_vindex.y);
const float3 tri_c = kernel_data_fetch(tri_verts, position_offset + tri_vindex.z);
local_isect->Ng[hit_index] = normalize(cross(tri_b - tri_a, tri_c - tri_a));
/* Continue tracing (without this the trace call would return after the first hit). */
optixIgnoreIntersection();
#endif
}
extern "C" __global__ void __anyhit__kernel_optix_shadow_all_hit()
{
#ifdef __TRANSPARENT_SHADOWS__
KernelGlobals kg = nullptr;
ccl_private BVHShadowAllPayload *payload = get_payload_ptr_0<BVHShadowAllPayload>();
const uint ray_visibility = optixGetPayload_4();
ccl_private Ray *const ray = get_payload_ptr_6<Ray>();
Intersection isect = get_intersection();
if (!bvh_shadow_all_anyhit_filter<ISECT_TEST_ALL>(
kg, payload->state, *payload, ray->self, ray_visibility, isect))
{
optixTerminateRay();
return;
}
/* The idea here is to accept the hit, so that traversal won't consider any more hits beyond the
* distance of the current hit anymore.
*
* We could accept the hit which is furthest away from the ones that are already recorded (for
* this `>` needs to be replaced with `>=`). However, doing so has a performance impact in the
* pabellon benchmark scene. The hypothesis here is that allowing to traverse one extra hit after
* the array is filled allows to hit an opaque surface and do early exit from the shadow shading.
*
* Similar to this logic (allowing an extra hit) was in the original OptiX integration, so we
* just keep following it to avoid performance regression. There is no the correct solution here,
* as it depends on the scene. For example, if there are many transparent surfaces with no opaque
* hit then it is faster to start accepting hits as soon as possible. However, if there are many
* transparent surfaces, followed up with an opaque surface, it is faster to not accept any hit
* and allow the opaque optimization to lead to an early output from the intersect-shade loop. */
if (isect.t > payload->max_record_isect_t) {
return;
}
optixIgnoreIntersection();
#endif
}
extern "C" __global__ void __anyhit__kernel_optix_volume_test()
{
#if defined(__HAIR__) || defined(__POINTCLOUD__)
if (!optixIsTriangleHit()) {
/* Ignore curves. */
return optixIgnoreIntersection();
}
#endif
KernelGlobals kg = nullptr;
const int object = get_object_id();
const int prim = optixGetPrimitiveIndex();
ccl_private Ray *const ray = get_payload_ptr_6<Ray>();
const uint ray_visibility = optixGetPayload_4();
if (bvh_volume_anyhit_triangle_filter(kg, object, prim, ray->self, ray_visibility)) {
return optixIgnoreIntersection();
}
}
extern "C" __global__ void __anyhit__kernel_optix_visibility_test()
{
const uint object = get_object_id();
const uint visibility = optixGetPayload_4();
#ifdef __VISIBILITY_FLAG__
if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) {
return optixIgnoreIntersection();
}
#endif
int prim = optixGetPrimitiveIndex();
if (optixIsTriangleHit()) {
/* Triangle. */
}
#ifdef __HAIR__
else if ((optixGetHitKind() & (~PRIMITIVE_MOTION)) != PRIMITIVE_POINT) {
/* Curve. */
prim = kernel_data_fetch(curve_segments, prim).prim;
}
#endif
ccl_private Ray *const ray = get_payload_ptr_6<Ray>();
if (visibility & PATH_RAY_VISIBILITY_SHADOW_OPAQUE) {
#ifdef __SHADOW_LINKING__
if (intersection_skip_shadow_link(nullptr, ray->self, object)) {
return optixIgnoreIntersection();
}
#endif
if (intersection_skip_self_shadow(ray->self, object, prim)) {
return optixIgnoreIntersection();
}
else {
/* Shadow ray early termination. */
return optixTerminateRay();
}
}
else {
if (intersection_skip_self(ray->self, object, prim)) {
return optixIgnoreIntersection();
}
}
}
extern "C" __global__ void __closesthit__kernel_optix_hit()
{
const int object = get_object_id();
const int prim = optixGetPrimitiveIndex();
optixSetPayload_0(__float_as_uint(optixGetRayTmax())); /* Intersection distance */
optixSetPayload_4(object);
if (optixIsTriangleHit()) {
const float2 barycentrics = optixGetTriangleBarycentrics();
optixSetPayload_1(__float_as_uint(barycentrics.x));
optixSetPayload_2(__float_as_uint(barycentrics.y));
optixSetPayload_3(prim);
optixSetPayload_5(kernel_data_fetch(objects, object).primitive_type);
}
else if ((optixGetHitKind() & (~PRIMITIVE_MOTION)) != PRIMITIVE_POINT) {
const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim);
optixSetPayload_1(optixGetAttribute_0()); /* Same as 'optixGetCurveParameter()' */
optixSetPayload_2(optixGetAttribute_1());
optixSetPayload_3(segment.prim);
optixSetPayload_5(segment.type);
}
else {
optixSetPayload_1(0);
optixSetPayload_2(0);
optixSetPayload_3(prim);
optixSetPayload_5(kernel_data_fetch(objects, object).primitive_type);
}
}
/* Custom primitive intersection functions. */
#ifdef __HAIR__
ccl_device_inline void optix_intersection_curve(const int prim, const int type)
{
const int object = get_object_id();
# ifdef __VISIBILITY_FLAG__
const uint visibility = optixGetPayload_4();
if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) {
return;
}
# endif
const float3 ray_P = optixGetObjectRayOrigin();
const float3 ray_D = optixGetObjectRayDirection();
const float ray_tmin = optixGetRayTmin();
# ifdef __OBJECT_MOTION__
const float time = optixGetRayTime();
# else
const float time = 0.0f;
# endif
Intersection isect;
isect.t = optixGetRayTmax();
if (curve_intersect(nullptr, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type))
{
static_assert(PRIMITIVE_ALL < 128, "Values >= 128 are reserved for OptiX internal use");
optixReportIntersection(isect.t,
type & PRIMITIVE_ALL,
__float_as_int(isect.u), /* Attribute_0 */
__float_as_int(isect.v)); /* Attribute_1 */
}
}
extern "C" __global__ void __intersection__curve_ribbon()
{
const KernelCurveSegment segment = kernel_data_fetch(curve_segments, optixGetPrimitiveIndex());
const int prim = segment.prim;
const int type = segment.type;
if ((type & PRIMITIVE_CURVE) == PRIMITIVE_CURVE_RIBBON) {
optix_intersection_curve(prim, type);
}
}
#endif
#ifdef __POINTCLOUD__
extern "C" __global__ void __intersection__point()
{
const int prim = optixGetPrimitiveIndex();
const int object = get_object_id();
const int type = kernel_data_fetch(objects, object).primitive_type;
# ifdef __VISIBILITY_FLAG__
const uint visibility = optixGetPayload_4();
if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) {
return;
}
# endif
const float3 ray_P = optixGetObjectRayOrigin();
const float3 ray_D = optixGetObjectRayDirection();
const float ray_tmin = optixGetRayTmin();
# ifdef __OBJECT_MOTION__
const float time = optixGetRayTime();
# else
const float time = 0.0f;
# endif
Intersection isect;
isect.t = optixGetRayTmax();
if (point_intersect(nullptr, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type))
{
static_assert(PRIMITIVE_ALL < 128, "Values >= 128 are reserved for OptiX internal use");
optixReportIntersection(isect.t, type & PRIMITIVE_ALL);
}
}
#endif
/* Scene intersection. */
ccl_device_intersect bool scene_intersect(KernelGlobals kg,
const ccl_private Ray *ray,
const uint visibility,
ccl_private Intersection *isect)
{
/* Note: some registers have hardcoded meaning.
* Be careful when changing the values here. See the note at the top of this file for more
* details. */
uint p0 = 0;
uint p1 = 0;
uint p2 = 0;
uint p3 = 0;
uint p4 = visibility;
uint p5 = PRIMITIVE_NONE;
uint p6 = pointer_pack_to_uint_0(ray);
uint p7 = pointer_pack_to_uint_1(ray);
uint ray_mask = visibility & 0xFF;
uint ray_flags = OPTIX_RAY_FLAG_ENFORCE_ANYHIT;
if (0 == ray_mask && (visibility & ~0xFF) != 0) {
ray_mask = 0xFF;
}
else if (visibility & PATH_RAY_VISIBILITY_SHADOW_OPAQUE) {
ray_flags |= OPTIX_RAY_FLAG_TERMINATE_ON_FIRST_HIT;
}
optixTrace(intersection_ray_valid(ray) ? kernel_data.device_bvh : 0,
ray->P,
ray->D,
ray->tmin,
ray->tmax,
ray->time,
ray_mask,
ray_flags,
0, /* SBT offset for PG_HITD */
0,
0,
p0,
p1,
p2,
p3,
p4,
p5,
p6,
p7);
isect->t = __uint_as_float(p0);
isect->u = __uint_as_float(p1);
isect->v = __uint_as_float(p2);
isect->prim = p3;
isect->object = p4;
isect->type = p5;
return p5 != PRIMITIVE_NONE;
}
ccl_device_intersect bool scene_intersect_shadow(KernelGlobals kg,
const ccl_private Ray *ray,
const uint visibility)
{
/* Note: some registers have hardcoded meaning.
* Be careful when changing the values here. See the note at the top of this file for more
* details. */
uint p0 = 0;
uint p1 = 0;
uint p2 = 0;
uint p3 = 0;
uint p4 = visibility;
uint p5 = PRIMITIVE_NONE;
uint p6 = pointer_pack_to_uint_0(ray);
uint p7 = pointer_pack_to_uint_1(ray);
uint ray_mask = visibility & 0xFF;
uint ray_flags = OPTIX_RAY_FLAG_ENFORCE_ANYHIT;
if (0 == ray_mask && (visibility & ~0xFF) != 0) {
ray_mask = 0xFF;
}
else if (visibility & PATH_RAY_VISIBILITY_SHADOW_OPAQUE) {
ray_flags |= OPTIX_RAY_FLAG_TERMINATE_ON_FIRST_HIT;
}
optixTraverse(intersection_ray_valid(ray) ? kernel_data.device_bvh : 0,
ray->P,
ray->D,
ray->tmin,
ray->tmax,
ray->time,
ray_mask,
ray_flags,
0, /* SBT offset for PG_HITD */
0,
0,
p0,
p1,
p2,
p3,
p4,
p5,
p6,
p7);
return optixHitObjectIsHit();
}
#ifdef __BVH_LOCAL__
template<bool single_hit = false>
ccl_device_intersect bool scene_intersect_local(KernelGlobals kg,
const ccl_private Ray *ray,
ccl_private LocalIntersection *local_isect,
const int local_object,
ccl_private uint *lcg_state,
const int max_hits)
{
/* Note: some registers have hardcoded meaning.
* Be careful when changing the values here. See the note at the top of this file for more
* details. */
uint p0 = pointer_pack_to_uint_0(lcg_state);
uint p1 = pointer_pack_to_uint_1(lcg_state);
uint p2 = pointer_pack_to_uint_0(local_isect);
uint p3 = pointer_pack_to_uint_1(local_isect);
uint p4 = local_object;
uint p6 = pointer_pack_to_uint_0(ray);
uint p7 = pointer_pack_to_uint_1(ray);
/* Is set to zero on miss or if ray is aborted, so can be used as return value. */
uint p5 = max_hits;
if (local_isect) {
local_isect->num_hits = 0; /* Initialize hit count to zero. */
}
optixTraverse(intersection_ray_valid(ray) ? kernel_data.device_bvh : 0,
ray->P,
ray->D,
ray->tmin,
ray->tmax,
ray->time,
0xFF,
/* Need to always call into __anyhit__kernel_optix_local_hit. */
OPTIX_RAY_FLAG_ENFORCE_ANYHIT,
2, /* SBT offset for PG_HITL */
0,
0,
p0,
p1,
p2,
p3,
p4,
p5,
p6,
p7);
return p5;
}
#endif
#ifdef __TRANSPARENT_SHADOWS__
ccl_device_intersect void scene_intersect_shadow_all_optix(
const ccl_private Ray *ccl_restrict ray,
const uint ray_visibility,
ccl_private BVHShadowAllPayload &ccl_restrict payload)
{
/* Note: some registers have hardcoded meaning.
* Be careful when changing the values here. See the note at the top of this file for more
* details. */
uint p0 = pointer_pack_to_uint_0(&payload);
uint p1 = pointer_pack_to_uint_1(&payload);
uint p2 = 0;
uint p3 = 0;
uint p4 = ray_visibility;
uint p5 = 0;
uint p6 = pointer_pack_to_uint_0(ray);
uint p7 = pointer_pack_to_uint_1(ray);
uint ray_mask = ray_visibility & 0xFF;
if (0 == ray_mask && (ray_visibility & ~0xFF) != 0) {
ray_mask = 0xFF;
}
optixTraverse(intersection_ray_valid(ray) ? kernel_data.device_bvh : 0,
ray->P,
ray->D,
ray->tmin,
ray->tmax,
ray->time,
ray_mask,
/* Need to always call into __anyhit__kernel_optix_shadow_all_hit. */
OPTIX_RAY_FLAG_ENFORCE_ANYHIT,
1, /* SBT offset for PG_HITS */
0,
0,
p0,
p1,
p2,
p3,
p4,
p5,
p6,
p7);
}
#endif
#ifdef __VOLUME__
ccl_device_intersect bool scene_intersect_volume(KernelGlobals kg,
const ccl_private Ray *ray,
ccl_private Intersection *isect,
const uint visibility)
{
/* Note: some registers have hardcoded meaning.
* Be careful when changing the values here. See the note at the top of this file for more
* details. */
uint p0 = 0;
uint p1 = 0;
uint p2 = 0;
uint p3 = 0;
uint p4 = visibility;
uint p5 = PRIMITIVE_NONE;
uint p6 = pointer_pack_to_uint_0(ray);
uint p7 = pointer_pack_to_uint_1(ray);
uint ray_mask = visibility & 0xFF;
if (0 == ray_mask && (visibility & ~0xFF) != 0) {
ray_mask = 0xFF;
}
optixTrace(intersection_ray_valid(ray) ? kernel_data.device_bvh : 0,
ray->P,
ray->D,
ray->tmin,
ray->tmax,
ray->time,
ray_mask,
/* Need to always call into __anyhit__kernel_optix_volume_test. */
OPTIX_RAY_FLAG_ENFORCE_ANYHIT,
3, /* SBT offset for PG_HITV */
0,
0,
p0,
p1,
p2,
p3,
p4,
p5,
p6,
p7);
isect->t = __uint_as_float(p0);
isect->u = __uint_as_float(p1);
isect->v = __uint_as_float(p2);
isect->prim = p3;
isect->object = p4;
isect->type = p5;
return p5 != PRIMITIVE_NONE;
}
#endif
CCL_NAMESPACE_END

View File

@@ -0,0 +1,100 @@
/* SPDX-FileCopyrightText: 2019 NVIDIA Corporation
* SPDX-FileCopyrightText: 2019-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#define __KERNEL_GPU__
#define __KERNEL_CUDA__ /* OptiX kernels are implicitly CUDA kernels too */
#define __KERNEL_OPTIX__
#define CCL_NAMESPACE_BEGIN
#define CCL_NAMESPACE_END
#ifndef ATTR_FALLTHROUGH
# define ATTR_FALLTHROUGH
#endif
/* Manual definitions so we can compile without CUDA toolkit. */
#ifdef __CUDACC_RTC__
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
#else
# include <stdint.h>
#endif
#ifdef CYCLES_CUBIN_CC
# define FLT_MIN 1.175494350822287507969e-38f
# define FLT_MAX 340282346638528859811704183484516925440.0f
# define FLT_EPSILON 1.192092896e-07F
#endif
#define ccl_device \
static __device__ \
__forceinline__ // Function calls are bad for OptiX performance, so inline everything
#define ccl_device_extern extern "C" __device__
#define ccl_device_inline ccl_device
#define ccl_device_forceinline ccl_device
#define ccl_device_inline_method __device__ __forceinline__
#define ccl_device_template_spec template<> __device__ __forceinline__
#define ccl_device_noinline static __device__ __noinline__
#define ccl_device_noinline_cpu ccl_device
#define ccl_global
#define ccl_inline_constant static __constant__
#define ccl_device_constant __constant__ __device__
#define ccl_static_constexpr static constexpr
#define ccl_constant const
#define ccl_gpu_shared __shared__
#define ccl_private
#define ccl_ray_data ccl_private
#define ccl_may_alias
#define ccl_restrict __restrict__
#define ccl_align(n) __align__(n)
#define ccl_attr_maybe_unused [[maybe_unused]]
/* Zero initialize structs to help the compiler figure out scoping */
#define ccl_optional_struct_init = {}
/* No assert supported for CUDA */
#define kernel_assert(cond)
/* GPU texture objects */
typedef unsigned long long CUtexObject;
typedef CUtexObject ccl_gpu_image_object_2D;
template<typename T>
ccl_device_forceinline T ccl_gpu_image_object_read_2D(const ccl_gpu_image_object_2D texobj,
const float x,
const float y)
{
return tex2D<T>(texobj, x, y);
}
/* Half */
typedef unsigned short half;
ccl_device_forceinline half __float2half(const float f)
{
half val;
asm("{ cvt.rn.f16.f32 %0, %1;}\n" : "=h"(val) : "f"(f));
return val;
}
ccl_device_forceinline float __half2float(const half h)
{
float val;
asm("{ cvt.f32.f16 %0, %1;}\n" : "=f"(val) : "h"(h));
return val;
}
/* Types */
#include "util/half.h"
#include "util/types.h"
#define OPTIX_DONT_INCLUDE_CUDA
#include <optix.h>

View File

@@ -0,0 +1,65 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* Constant Globals */
#pragma once
#include "kernel/types.h"
#include "kernel/integrator/state.h"
#include "kernel/util/profiler.h"
#include "util/color.h"
#include "util/types_image.h"
CCL_NAMESPACE_BEGIN
/* Not actually used, just a nullptr pointer that gets passed everywhere, which we
* hope gets optimized out by the compiler. */
struct KernelGlobalsGPU {
int unused[1];
};
using KernelGlobals = const ccl_global KernelGlobalsGPU *ccl_restrict;
/* Launch parameters */
struct KernelParamsOptiX {
/* Kernel arguments */
const int *path_index_array;
float *render_buffer;
uint *shader_eval_cache_miss;
int shader_eval_offset;
/* Init kernel arguments */
int num_tiles;
int max_tile_work_size;
/* Global scene data and textures */
KernelData data;
#define KERNEL_DATA_ARRAY(type, name) const type *name;
#define KERNEL_DATA_ARRAY_WRITABLE(type, name) type *name;
#include "kernel/data_arrays.h"
/* Integrator state */
IntegratorStateGPU integrator_state;
void *osl_colorsystem;
};
#ifdef __NVCC__
extern "C"
# ifndef __CUDACC_RDC__
static
# endif
__constant__ KernelParamsOptiX kernel_params;
#endif
/* Abstraction macros */
#define kernel_data kernel_params.data
#define kernel_data_array(name) kernel_params.name
#define kernel_data_fetch(name, index) kernel_params.name[(index)]
#define kernel_data_write(name, index, value) kernel_params.name[(index)] = (value)
#define kernel_integrator_state kernel_params.integrator_state
CCL_NAMESPACE_END

View File

@@ -0,0 +1,68 @@
/* SPDX-FileCopyrightText: 2019, NVIDIA Corporation
* SPDX-FileCopyrightText: 2019-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
// clang-format off
#include "kernel/device/optix/compat.h"
#include "kernel/device/optix/globals.h"
#include "kernel/device/gpu/image.h" /* Texture lookup uses normal CUDA intrinsics. */
#include "kernel/tables.h"
#include "kernel/integrator/state.h"
#include "kernel/integrator/state_flow.h"
#include "kernel/integrator/state_util.h"
#include "kernel/integrator/intersect_closest.h"
#include "kernel/integrator/intersect_shadow.h"
#include "kernel/integrator/intersect_subsurface.h"
#include "kernel/integrator/intersect_volume_stack.h"
#include "kernel/integrator/intersect_dedicated_light.h"
// clang-format on
extern "C" __global__ void __raygen__kernel_optix_integrator_intersect_closest()
{
const int global_index = optixGetLaunchIndex().x;
const int path_index = (kernel_params.path_index_array) ?
kernel_params.path_index_array[global_index] :
global_index;
integrator_intersect_closest(nullptr, path_index, kernel_params.render_buffer);
}
extern "C" __global__ void __raygen__kernel_optix_integrator_intersect_shadow()
{
const int global_index = optixGetLaunchIndex().x;
const int path_index = (kernel_params.path_index_array) ?
kernel_params.path_index_array[global_index] :
global_index;
integrator_intersect_shadow(nullptr, path_index);
}
extern "C" __global__ void __raygen__kernel_optix_integrator_intersect_subsurface()
{
const int global_index = optixGetLaunchIndex().x;
const int path_index = (kernel_params.path_index_array) ?
kernel_params.path_index_array[global_index] :
global_index;
integrator_intersect_subsurface(nullptr, path_index);
}
extern "C" __global__ void __raygen__kernel_optix_integrator_intersect_volume_stack()
{
const int global_index = optixGetLaunchIndex().x;
const int path_index = (kernel_params.path_index_array) ?
kernel_params.path_index_array[global_index] :
global_index;
integrator_intersect_volume_stack(nullptr, path_index);
}
extern "C" __global__ void __raygen__kernel_optix_integrator_intersect_dedicated_light()
{
const int global_index = optixGetLaunchIndex().x;
const int path_index = (kernel_params.path_index_array) ?
kernel_params.path_index_array[global_index] :
global_index;
integrator_intersect_dedicated_light(nullptr, path_index);
}

View File

@@ -0,0 +1,19 @@
/* SPDX-FileCopyrightText: 2011-2026 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "kernel/device/optix/compat.h"
#include "kernel/device/optix/globals.h"
#include "kernel/device/gpu/image.h" /* Texture lookup uses normal CUDA intrinsics. */
#include "kernel/integrator/intersect_mnee.h"
extern "C" __global__ void __raygen__kernel_optix_integrator_intersect_mnee()
{
const int global_index = optixGetLaunchIndex().x;
const int path_index = (kernel_params.path_index_array) ?
kernel_params.path_index_array[global_index] :
global_index;
integrator_intersect_mnee(nullptr, path_index);
}

View File

@@ -0,0 +1,108 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#define WITH_OSL
/* Copy of the regular OptiX kernels with additional OSL support. */
#include "kernel/device/optix/kernel.cu"
#include "kernel/bake/bake.h"
#include "kernel/integrator/shade_background.h"
#include "kernel/integrator/shade_dedicated_light.h"
#include "kernel/integrator/shade_light.h"
#include "kernel/integrator/shade_shadow.h"
#include "kernel/integrator/shade_surface.h"
#include "kernel/device/gpu/work_stealing.h"
extern "C" __global__ void __raygen__kernel_optix_integrator_shade_background()
{
const int global_index = optixGetLaunchIndex().x;
const int path_index = (kernel_params.path_index_array) ?
kernel_params.path_index_array[global_index] :
global_index;
integrator_shade_background(nullptr, path_index, kernel_params.render_buffer);
}
extern "C" __global__ void __raygen__kernel_optix_integrator_shade_light_nee()
{
const int global_index = optixGetLaunchIndex().x;
const int path_index = (kernel_params.path_index_array) ?
kernel_params.path_index_array[global_index] :
global_index;
integrator_shade_light_nee(nullptr, path_index, kernel_params.render_buffer);
}
extern "C" __global__ void __raygen__kernel_optix_integrator_shade_light_forward()
{
const int global_index = optixGetLaunchIndex().x;
const int path_index = (kernel_params.path_index_array) ?
kernel_params.path_index_array[global_index] :
global_index;
integrator_shade_light_forward(nullptr, path_index, kernel_params.render_buffer);
}
extern "C" __global__ void __raygen__kernel_optix_integrator_shade_surface()
{
const int global_index = optixGetLaunchIndex().x;
const int path_index = (kernel_params.path_index_array) ?
kernel_params.path_index_array[global_index] :
global_index;
integrator_shade_surface(nullptr, path_index, kernel_params.render_buffer);
}
extern "C" __global__ void __raygen__kernel_optix_integrator_shade_shadow()
{
const int global_index = optixGetLaunchIndex().x;
const int path_index = (kernel_params.path_index_array) ?
kernel_params.path_index_array[global_index] :
global_index;
integrator_shade_shadow(nullptr, path_index, kernel_params.render_buffer);
}
extern "C" __global__ void __raygen__kernel_optix_integrator_shade_dedicated_light()
{
const int global_index = optixGetLaunchIndex().x;
const int path_index = (kernel_params.path_index_array) ?
kernel_params.path_index_array[global_index] :
global_index;
integrator_shade_dedicated_light(nullptr, path_index, kernel_params.render_buffer);
}
extern "C" __global__ void __raygen__kernel_optix_shader_eval_displace()
{
KernelShaderEvalInput *const input = (KernelShaderEvalInput *)kernel_params.path_index_array;
float *const output = kernel_params.render_buffer;
uint *const cache_miss = kernel_params.shader_eval_cache_miss;
const int global_index = kernel_params.shader_eval_offset + optixGetLaunchIndex().x;
kernel_displace_evaluate(nullptr, input, output, cache_miss, global_index);
}
extern "C" __global__ void __raygen__kernel_optix_shader_eval_background()
{
KernelShaderEvalInput *const input = (KernelShaderEvalInput *)kernel_params.path_index_array;
float *const output = kernel_params.render_buffer;
uint *const cache_miss = kernel_params.shader_eval_cache_miss;
const int global_index = kernel_params.shader_eval_offset + optixGetLaunchIndex().x;
kernel_background_evaluate(nullptr, input, output, cache_miss, global_index);
}
extern "C" __global__ void __raygen__kernel_optix_shader_eval_curve_shadow_transparency()
{
KernelShaderEvalInput *const input = (KernelShaderEvalInput *)kernel_params.path_index_array;
float *const output = kernel_params.render_buffer;
uint *const cache_miss = kernel_params.shader_eval_cache_miss;
const int global_index = kernel_params.shader_eval_offset + optixGetLaunchIndex().x;
kernel_curve_shadow_transparency_evaluate(nullptr, input, output, cache_miss, global_index);
}
extern "C" __global__ void __raygen__kernel_optix_shader_eval_volume_density()
{
KernelShaderEvalInput *const input = (KernelShaderEvalInput *)kernel_params.path_index_array;
float *const output = kernel_params.render_buffer;
uint *const cache_miss = kernel_params.shader_eval_cache_miss;
const int global_index = kernel_params.shader_eval_offset + optixGetLaunchIndex().x;
kernel_volume_density_evaluate(nullptr, input, output, cache_miss, global_index);
}

View File

@@ -0,0 +1,35 @@
/* SPDX-FileCopyrightText: 2011-2025 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#define WITH_OSL
#include "kernel/device/optix/compat.h"
#include "kernel/device/optix/globals.h"
#include "kernel/integrator/init_from_camera.h"
#include "kernel/device/gpu/work_stealing.h"
extern "C" __global__ void __raygen__kernel_optix_integrator_init_from_camera()
{
const int global_index = optixGetLaunchIndex().x;
const KernelWorkTile *tiles = (const KernelWorkTile *)kernel_params.path_index_array;
const int tile_index = global_index / kernel_params.max_tile_work_size;
const int tile_work_index = global_index - tile_index * kernel_params.max_tile_work_size;
const KernelWorkTile *tile = &tiles[tile_index];
if (tile_work_index >= tile->work_size) {
return;
}
const int path_index = tile->path_index_offset + tile_work_index;
uint x, y, sample;
get_work_pixel(tile, tile_work_index, &x, &y, &sample);
integrator_init_from_camera(nullptr, path_index, tile, kernel_params.render_buffer, x, y, sample);
}

Some files were not shown because too many files have changed in this diff Show More