Cycles: add build option to enable a debugging feature for MIS

This patch adds a CMake option "WITH_CYCLES_DEBUG" which builds cycles with
a feature that allows debugging/selecting the direct-light sampling strategy.
The same option may later be used to add other debugging features that could
affect performance in release builds.

The three options are:
* Forward path tracing (e.g., via BSDF or phase function)
* Next-event estimation
* Multiple importance sampling combination of the previous two methods

Such a feature is useful for debugging light different sampling, evaluation,
and pdf methods (e.g., for light sources and BSDFs).

Differential Revision: https://developer.blender.org/D13152
This commit is contained in:
Sebastian Herholz 2021-11-17 17:26:46 +01:00 committed by Brecht Van Lommel
parent 063ad8635e
commit d9bc8f189c
14 changed files with 111 additions and 25 deletions

@ -411,6 +411,7 @@ option(WITH_CYCLES "Enable Cycles Render Engine" ON)
option(WITH_CYCLES_OSL "Build Cycles with OpenShadingLanguage support" ON) option(WITH_CYCLES_OSL "Build Cycles with OpenShadingLanguage support" ON)
option(WITH_CYCLES_EMBREE "Build Cycles with Embree support" ON) option(WITH_CYCLES_EMBREE "Build Cycles with Embree support" ON)
option(WITH_CYCLES_LOGGING "Build Cycles with logging support" ON) option(WITH_CYCLES_LOGGING "Build Cycles with logging support" ON)
option(WITH_CYCLES_DEBUG "Build Cycles with options useful for debugging (e.g., MIS)" OFF)
option(WITH_CYCLES_STANDALONE "Build Cycles standalone application" OFF) option(WITH_CYCLES_STANDALONE "Build Cycles standalone application" OFF)
option(WITH_CYCLES_STANDALONE_GUI "Build Cycles standalone with GUI" OFF) option(WITH_CYCLES_STANDALONE_GUI "Build Cycles standalone with GUI" OFF)

@ -226,6 +226,9 @@ add_definitions(
-DCCL_NAMESPACE_END=} -DCCL_NAMESPACE_END=}
) )
if(WITH_CYCLES_DEBUG)
add_definitions(-DWITH_CYCLES_DEBUG)
endif()
if(WITH_CYCLES_STANDALONE_GUI) if(WITH_CYCLES_STANDALONE_GUI)
add_definitions(-DWITH_CYCLES_STANDALONE_GUI) add_definitions(-DWITH_CYCLES_STANDALONE_GUI)
endif() endif()

@ -218,6 +218,12 @@ enum_denoising_prefilter = (
('ACCURATE', "Accurate", "Prefilter noisy guiding passes before denoising color. Improves quality when guiding passes are noisy using extra processing time", 3), ('ACCURATE', "Accurate", "Prefilter noisy guiding passes before denoising color. Improves quality when guiding passes are noisy using extra processing time", 3),
) )
enum_direct_light_sampling_type = (
('MULTIPLE_IMPORTANCE_SAMPLING', "Multiple Importance Sampling", "Multiple importance sampling is used to combine direct light contributions from next-event estimation and forward path tracing", 0),
('FORWARD_PATH_TRACING', "Forward Path Tracing", "Direct light contributions are only sampled using forward path tracing", 1),
('NEXT_EVENT_ESTIMATION', "Next-Event Estimation", "Direct light contributions are only sampled using next-event estimation", 2),
)
def update_render_passes(self, context): def update_render_passes(self, context):
scene = context.scene scene = context.scene
view_layer = context.view_layer view_layer = context.view_layer
@ -422,6 +428,13 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
default=0, default=0,
) )
direct_light_sampling_type: EnumProperty(
name="Direct Light Sampling Type",
description="The type of strategy used for sampling direct light contributions",
items=enum_direct_light_sampling_type,
default='MULTIPLE_IMPORTANCE_SAMPLING',
)
min_light_bounces: IntProperty( min_light_bounces: IntProperty(
name="Min Light Bounces", name="Min Light Bounces",
description="Minimum number of light bounces. Setting this higher reduces noise in the first bounces, " description="Minimum number of light bounces. Setting this higher reduces noise in the first bounces, "

@ -392,6 +392,12 @@ void BlenderSync::sync_integrator(BL::ViewLayer &b_view_layer, bool background)
integrator->set_ao_bounces(0); integrator->set_ao_bounces(0);
} }
#ifdef WITH_CYCLES_DEBUG
DirectLightSamplingType direct_light_sampling_type = (DirectLightSamplingType)get_enum(
cscene, "direct_light_sampling_type", DIRECT_LIGHT_SAMPLING_NUM, DIRECT_LIGHT_SAMPLING_MIS);
integrator->set_direct_light_sampling_type(direct_light_sampling_type);
#endif
const DenoiseParams denoise_params = get_denoise_params(b_scene, b_view_layer, background); const DenoiseParams denoise_params = get_denoise_params(b_scene, b_view_layer, background);
integrator->set_use_denoise(denoise_params.use); integrator->set_use_denoise(denoise_params.use);

@ -236,6 +236,10 @@ string CUDADevice::compile_kernel_get_common_cflags(const uint kernel_features)
cflags += " -DWITH_NANOVDB"; cflags += " -DWITH_NANOVDB";
# endif # endif
# ifdef WITH_CYCLES_DEBUG
cflags += " -DWITH_CYCLES_DEBUG";
# endif
return cflags; return cflags;
} }

@ -412,17 +412,16 @@ if(WITH_CYCLES_CUDA_BINARIES)
--use_fast_math --use_fast_math
-o ${CMAKE_CURRENT_BINARY_DIR}/${cuda_file}) -o ${CMAKE_CURRENT_BINARY_DIR}/${cuda_file})
if(${experimental})
set(cuda_flags ${cuda_flags} -D __KERNEL_EXPERIMENTAL__)
set(name ${name}_experimental)
endif()
if(WITH_NANOVDB) if(WITH_NANOVDB)
set(cuda_flags ${cuda_flags} set(cuda_flags ${cuda_flags}
-D WITH_NANOVDB -D WITH_NANOVDB
-I "${NANOVDB_INCLUDE_DIR}") -I "${NANOVDB_INCLUDE_DIR}")
endif() endif()
if(WITH_CYCLES_DEBUG)
set(cuda_flags ${cuda_flags} -D WITH_CYCLES_DEBUG)
endif()
if(WITH_CYCLES_CUBIN_COMPILER) if(WITH_CYCLES_CUBIN_COMPILER)
string(SUBSTRING ${arch} 3 -1 CUDA_ARCH) string(SUBSTRING ${arch} 3 -1 CUDA_ARCH)
@ -571,13 +570,8 @@ if(WITH_CYCLES_HIP_BINARIES AND WITH_CYCLES_DEVICE_HIP)
-ffast-math -ffast-math
-o ${CMAKE_CURRENT_BINARY_DIR}/${hip_file}) -o ${CMAKE_CURRENT_BINARY_DIR}/${hip_file})
if(${experimental})
set(hip_flags ${hip_flags} -D __KERNEL_EXPERIMENTAL__)
set(name ${name}_experimental)
endif()
if(WITH_CYCLES_DEBUG) if(WITH_CYCLES_DEBUG)
set(hip_flags ${hip_flags} -D __KERNEL_DEBUG__) set(hip_flags ${hip_flags} -D WITH_CYCLES_DEBUG)
endif() endif()
add_custom_command( add_custom_command(
@ -618,6 +612,10 @@ if(WITH_CYCLES_DEVICE_OPTIX AND WITH_CYCLES_CUDA_BINARIES)
-I "${NANOVDB_INCLUDE_DIR}") -I "${NANOVDB_INCLUDE_DIR}")
endif() endif()
if(WITH_CYCLES_DEBUG)
set(cuda_flags ${cuda_flags} -D WITH_CYCLES_DEBUG)
endif()
if(WITH_CYCLES_CUBIN_COMPILER) if(WITH_CYCLES_CUBIN_COMPILER)
# Needed to find libnvrtc-builtins.so. Can't do it from inside # Needed to find libnvrtc-builtins.so. Can't do it from inside
# cycles_cubin_cc since the env variable is read before main() # cycles_cubin_cc since the env variable is read before main()

@ -20,7 +20,6 @@
#include "kernel/integrator/shader_eval.h" #include "kernel/integrator/shader_eval.h"
#include "kernel/light/light.h" #include "kernel/light/light.h"
#include "kernel/light/sample.h" #include "kernel/light/sample.h"
#include "kernel/sample/mis.h"
CCL_NAMESPACE_BEGIN CCL_NAMESPACE_BEGIN
@ -81,8 +80,7 @@ ccl_device float3 integrator_eval_background_shader(KernelGlobals kg,
/* multiple importance sampling, get background light pdf for ray /* multiple importance sampling, get background light pdf for ray
* direction, and compute weight with respect to BSDF pdf */ * direction, and compute weight with respect to BSDF pdf */
const float pdf = background_light_pdf(kg, ray_P - ray_D * mis_ray_t, ray_D); const float pdf = background_light_pdf(kg, ray_P - ray_D * mis_ray_t, ray_D);
const float mis_weight = power_heuristic(mis_ray_pdf, pdf); const float mis_weight = light_sample_mis_weight_forward(kg, mis_ray_pdf, pdf);
L *= mis_weight; L *= mis_weight;
} }
# endif # endif
@ -169,7 +167,7 @@ ccl_device_inline void integrate_distant_lights(KernelGlobals kg,
/* multiple importance sampling, get regular light pdf, /* multiple importance sampling, get regular light pdf,
* and compute weight with respect to BSDF pdf */ * and compute weight with respect to BSDF pdf */
const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf); const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
const float mis_weight = power_heuristic(mis_ray_pdf, ls.pdf); const float mis_weight = light_sample_mis_weight_forward(kg, mis_ray_pdf, ls.pdf);
light_eval *= mis_weight; light_eval *= mis_weight;
} }

@ -84,7 +84,7 @@ ccl_device_inline void integrate_light(KernelGlobals kg,
/* multiple importance sampling, get regular light pdf, /* multiple importance sampling, get regular light pdf,
* and compute weight with respect to BSDF pdf */ * and compute weight with respect to BSDF pdf */
const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf); const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
const float mis_weight = power_heuristic(mis_ray_pdf, ls.pdf); const float mis_weight = light_sample_mis_weight_forward(kg, mis_ray_pdf, ls.pdf);
light_eval *= mis_weight; light_eval *= mis_weight;
} }

@ -27,8 +27,6 @@
#include "kernel/light/light.h" #include "kernel/light/light.h"
#include "kernel/light/sample.h" #include "kernel/light/sample.h"
#include "kernel/sample/mis.h"
CCL_NAMESPACE_BEGIN CCL_NAMESPACE_BEGIN
ccl_device_forceinline void integrate_surface_shader_setup(KernelGlobals kg, ccl_device_forceinline void integrate_surface_shader_setup(KernelGlobals kg,
@ -95,8 +93,7 @@ ccl_device_forceinline void integrate_surface_emission(KernelGlobals kg,
/* Multiple importance sampling, get triangle light pdf, /* Multiple importance sampling, get triangle light pdf,
* and compute weight with respect to BSDF pdf. */ * and compute weight with respect to BSDF pdf. */
float pdf = triangle_light_pdf(kg, sd, t); float pdf = triangle_light_pdf(kg, sd, t);
float mis_weight = power_heuristic(bsdf_pdf, pdf); float mis_weight = light_sample_mis_weight_forward(kg, bsdf_pdf, pdf);
L *= mis_weight; L *= mis_weight;
} }
@ -155,7 +152,7 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg,
bsdf_eval_mul3(&bsdf_eval, light_eval / ls.pdf); bsdf_eval_mul3(&bsdf_eval, light_eval / ls.pdf);
if (ls.shader & SHADER_USE_MIS) { if (ls.shader & SHADER_USE_MIS) {
const float mis_weight = power_heuristic(ls.pdf, bsdf_pdf); const float mis_weight = light_sample_mis_weight_nee(kg, ls.pdf, bsdf_pdf);
bsdf_eval_mul(&bsdf_eval, mis_weight); bsdf_eval_mul(&bsdf_eval, mis_weight);
} }

@ -27,8 +27,6 @@
#include "kernel/light/light.h" #include "kernel/light/light.h"
#include "kernel/light/sample.h" #include "kernel/light/sample.h"
#include "kernel/sample/mis.h"
CCL_NAMESPACE_BEGIN CCL_NAMESPACE_BEGIN
#ifdef __VOLUME__ #ifdef __VOLUME__
@ -759,7 +757,7 @@ ccl_device_forceinline void integrate_volume_direct_light(
const float phase_pdf = shader_volume_phase_eval(kg, sd, phases, ls->D, &phase_eval); const float phase_pdf = shader_volume_phase_eval(kg, sd, phases, ls->D, &phase_eval);
if (ls->shader & SHADER_USE_MIS) { if (ls->shader & SHADER_USE_MIS) {
float mis_weight = power_heuristic(ls->pdf, phase_pdf); float mis_weight = light_sample_mis_weight_nee(kg, ls->pdf, phase_pdf);
bsdf_eval_mul(&phase_eval, mis_weight); bsdf_eval_mul(&phase_eval, mis_weight);
} }

@ -22,6 +22,7 @@
#include "kernel/light/light.h" #include "kernel/light/light.h"
#include "kernel/sample/mapping.h" #include "kernel/sample/mapping.h"
#include "kernel/sample/mis.h"
CCL_NAMESPACE_BEGIN CCL_NAMESPACE_BEGIN
@ -268,4 +269,36 @@ ccl_device_inline void light_sample_to_volume_shadow_ray(
shadow_ray_setup(sd, ls, P, ray); shadow_ray_setup(sd, ls, P, ray);
} }
ccl_device_inline float light_sample_mis_weight_forward(KernelGlobals kg,
const float forward_pdf,
const float nee_pdf)
{
#ifdef WITH_CYCLES_DEBUG
if (kernel_data.integrator.direct_light_sampling_type == DIRECT_LIGHT_SAMPLING_FORWARD) {
return 1.0f;
}
else if (kernel_data.integrator.direct_light_sampling_type == DIRECT_LIGHT_SAMPLING_NEE) {
return 0.0f;
}
else
#endif
return power_heuristic(forward_pdf, nee_pdf);
}
ccl_device_inline float light_sample_mis_weight_nee(KernelGlobals kg,
const float nee_pdf,
const float forward_pdf)
{
#ifdef WITH_CYCLES_DEBUG
if (kernel_data.integrator.direct_light_sampling_type == DIRECT_LIGHT_SAMPLING_FORWARD) {
return 0.0f;
}
else if (kernel_data.integrator.direct_light_sampling_type == DIRECT_LIGHT_SAMPLING_NEE) {
return 1.0f;
}
else
#endif
return power_heuristic(nee_pdf, forward_pdf);
}
CCL_NAMESPACE_END CCL_NAMESPACE_END

@ -481,6 +481,16 @@ enum PanoramaType {
PANORAMA_NUM_TYPES, PANORAMA_NUM_TYPES,
}; };
/* Direct Light Sampling */
enum DirectLightSamplingType {
DIRECT_LIGHT_SAMPLING_MIS = 0,
DIRECT_LIGHT_SAMPLING_FORWARD = 1,
DIRECT_LIGHT_SAMPLING_NEE = 2,
DIRECT_LIGHT_SAMPLING_NUM,
};
/* Differential */ /* Differential */
typedef struct differential3 { typedef struct differential3 {
@ -1193,8 +1203,11 @@ typedef struct KernelIntegrator {
/* Closure filter. */ /* Closure filter. */
int filter_closures; int filter_closures;
/* MIS debuging */
int direct_light_sampling_type;
/* padding */ /* padding */
int pad1, pad2, pad3; int pad1, pad2;
} KernelIntegrator; } KernelIntegrator;
static_assert_align(KernelIntegrator, 16); static_assert_align(KernelIntegrator, 16);

@ -52,6 +52,18 @@ NODE_DEFINE(Integrator)
SOCKET_INT(transparent_min_bounce, "Transparent Min Bounce", 0); SOCKET_INT(transparent_min_bounce, "Transparent Min Bounce", 0);
SOCKET_INT(transparent_max_bounce, "Transparent Max Bounce", 7); SOCKET_INT(transparent_max_bounce, "Transparent Max Bounce", 7);
#ifdef WITH_CYCLES_DEBUG
static NodeEnum direct_light_sampling_type_enum;
direct_light_sampling_type_enum.insert("multiple_importance_sampling",
DIRECT_LIGHT_SAMPLING_MIS);
direct_light_sampling_type_enum.insert("forward_path_tracing", DIRECT_LIGHT_SAMPLING_FORWARD);
direct_light_sampling_type_enum.insert("next_event_estimation", DIRECT_LIGHT_SAMPLING_NEE);
SOCKET_ENUM(direct_light_sampling_type,
"Direct Light Sampling Type",
direct_light_sampling_type_enum,
DIRECT_LIGHT_SAMPLING_MIS);
#endif
SOCKET_INT(ao_bounces, "AO Bounces", 0); SOCKET_INT(ao_bounces, "AO Bounces", 0);
SOCKET_FLOAT(ao_factor, "AO Factor", 0.0f); SOCKET_FLOAT(ao_factor, "AO Factor", 0.0f);
SOCKET_FLOAT(ao_distance, "AO Distance", FLT_MAX); SOCKET_FLOAT(ao_distance, "AO Distance", FLT_MAX);
@ -171,6 +183,12 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene
kintegrator->ao_bounces_factor = ao_factor; kintegrator->ao_bounces_factor = ao_factor;
kintegrator->ao_additive_factor = ao_additive_factor; kintegrator->ao_additive_factor = ao_additive_factor;
#ifdef WITH_CYCLES_DEBUG
kintegrator->direct_light_sampling_type = direct_light_sampling_type;
#else
kintegrator->direct_light_sampling_type = DIRECT_LIGHT_SAMPLING_MIS;
#endif
/* Transparent Shadows /* Transparent Shadows
* We only need to enable transparent shadows, if we actually have * We only need to enable transparent shadows, if we actually have
* transparent shaders in the scene. Otherwise we can disable it * transparent shaders in the scene. Otherwise we can disable it

@ -41,6 +41,10 @@ class Integrator : public Node {
NODE_SOCKET_API(int, max_transmission_bounce) NODE_SOCKET_API(int, max_transmission_bounce)
NODE_SOCKET_API(int, max_volume_bounce) NODE_SOCKET_API(int, max_volume_bounce)
#ifdef WITH_CYCLES_DEBUG
NODE_SOCKET_API(DirectLightSamplingType, direct_light_sampling_type)
#endif
NODE_SOCKET_API(int, transparent_min_bounce) NODE_SOCKET_API(int, transparent_min_bounce)
NODE_SOCKET_API(int, transparent_max_bounce) NODE_SOCKET_API(int, transparent_max_bounce)