diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py index ce9c9ca7d08..01440ce95e9 100644 --- a/intern/cycles/blender/addon/properties.py +++ b/intern/cycles/blender/addon/properties.py @@ -223,7 +223,7 @@ def enum_openimagedenoise_denoiser(self, context): def enum_optix_denoiser(self, context): if not context or bool(context.preferences.addons[__package__].preferences.get_devices_for_type('OPTIX')): - return [('OPTIX', "OptiX", "Use the OptiX AI denoiser with GPU acceleration, only available on NVIDIA GPUs", 2)] + return [('OPTIX', "OptiX", "Use the OptiX AI denoiser with GPU acceleration, only available on NVIDIA GPUs when configured in the system tab in the user preferences", 2)] return [] @@ -380,7 +380,7 @@ class CyclesRenderSettings(bpy.types.PropertyGroup): ) denoising_use_gpu: BoolProperty( name="Denoise on GPU", - description="Perform denoising on GPU devices, if available. This is significantly faster than on CPU, but requires additional GPU memory. When large scenes need more GPU memory, this option can be disabled", + description="Perform denoising on GPU devices configured in the system tab in the user preferences. This is significantly faster than on CPU, but requires additional GPU memory. When large scenes need more GPU memory, this option can be disabled", default=False, ) @@ -421,7 +421,7 @@ class CyclesRenderSettings(bpy.types.PropertyGroup): ) preview_denoising_use_gpu: BoolProperty( name="Denoise Preview on GPU", - description="Perform denoising on GPU devices, if available. This is significantly faster than on CPU, but requires additional GPU memory. When large scenes need more GPU memory, this option can be disabled", + description="Perform denoising on GPU devices configured in the system tab in the user preferences. This is significantly faster than on CPU, but requires additional GPU memory. When large scenes need more GPU memory, this option can be disabled", default=True, ) @@ -1643,6 +1643,22 @@ class CyclesPreferences(bpy.types.AddonPreferences): return False + def has_optixdenoiser_gpu_devices(self): + import _cycles + compute_device_type = self.get_compute_device_type() + + # We need any OptiX devices, used for rendering + for device in _cycles.available_devices(compute_device_type): + device_type = device[1] + if device_type == 'CPU': + continue + + has_device_optixdenoiser_support = device[6] + if has_device_optixdenoiser_support and self.find_existing_device_entry(device).use: + return True + + return False + def _draw_devices(self, layout, device_type, devices): box = layout.box() diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index a0764b5e0b5..65cff6fcc8b 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -143,6 +143,28 @@ def show_device_active(context): return True return backend_has_active_gpu(context) +def show_preview_denoise_active(context): + cscene = context.scene.cycles + if not cscene.use_preview_denoising: + return False + + if cscene.preview_denoiser == 'OPTIX': + return has_optixdenoiser_gpu_devices(context) + + # OIDN is always available, thanks to CPU support + return True + +def show_denoise_active(context): + cscene = context.scene.cycles + if not cscene.use_denoising: + return False + + if cscene.denoiser == 'OPTIX': + return has_optixdenoiser_gpu_devices(context) + + # OIDN is always available, thanks to CPU support + return True + def get_effective_preview_denoiser(context, has_oidn_gpu): scene = context.scene @@ -163,6 +185,9 @@ def get_effective_preview_denoiser(context, has_oidn_gpu): def has_oidn_gpu_devices(context): return context.preferences.addons[__package__].preferences.has_oidn_gpu_devices() +def has_optixdenoiser_gpu_devices(context): + return context.preferences.addons[__package__].preferences.has_optixdenoiser_gpu_devices() + def use_mnee(context): # The MNEE kernel doesn't compile on macOS < 13. @@ -234,7 +259,11 @@ class CYCLES_RENDER_PT_sampling_viewport_denoise(CyclesButtonsPanel, Panel): col = layout.column() col.active = cscene.use_preview_denoising - col.prop(cscene, "preview_denoiser", text="Denoiser") + + sub = col.column() + sub.active = show_preview_denoise_active(context) + sub.prop(cscene, "preview_denoiser", text="Denoiser") + col.prop(cscene, "preview_denoising_input_passes", text="Passes") has_oidn_gpu = has_oidn_gpu_devices(context) @@ -247,7 +276,7 @@ class CYCLES_RENDER_PT_sampling_viewport_denoise(CyclesButtonsPanel, Panel): if effective_preview_denoiser == 'OPENIMAGEDENOISE': row = col.row() - row.active = not use_cpu(context) and has_oidn_gpu + row.active = has_oidn_gpu_devices(context) row.prop(cscene, "preview_denoising_use_gpu", text="Use GPU") @@ -304,7 +333,11 @@ class CYCLES_RENDER_PT_sampling_render_denoise(CyclesButtonsPanel, Panel): col = layout.column() col.active = cscene.use_denoising - col.prop(cscene, "denoiser", text="Denoiser") + + sub = col.column() + sub.active = show_denoise_active(context) + sub.prop(cscene, "denoiser", text="Denoiser") + col.prop(cscene, "denoising_input_passes", text="Passes") if cscene.denoiser == 'OPENIMAGEDENOISE': col.prop(cscene, "denoising_prefilter", text="Prefilter") @@ -312,7 +345,7 @@ class CYCLES_RENDER_PT_sampling_render_denoise(CyclesButtonsPanel, Panel): if cscene.denoiser == 'OPENIMAGEDENOISE': row = col.row() - row.active = not use_cpu(context) and has_oidn_gpu_devices(context) + row.active = has_oidn_gpu_devices(context) row.prop(cscene, "denoising_use_gpu", text="Use GPU") diff --git a/intern/cycles/blender/device.cpp b/intern/cycles/blender/device.cpp index 18c4026bffe..f39f90d5646 100644 --- a/intern/cycles/blender/device.cpp +++ b/intern/cycles/blender/device.cpp @@ -61,84 +61,8 @@ void static adjust_device_info_from_preferences(DeviceInfo &info, PointerRNA cpr } } -DeviceInfo blender_device_info(BL::Preferences &b_preferences, - BL::Scene &b_scene, - bool background, - bool preview) +void static adjust_device_info(DeviceInfo &device, PointerRNA cpreferences, bool preview) { - PointerRNA cscene = RNA_pointer_get(&b_scene.ptr, "cycles"); - - /* Find cycles preferences. */ - PointerRNA cpreferences; - for (BL::Addon &b_addon : b_preferences.addons) { - if (b_addon.module() == "cycles") { - cpreferences = b_addon.preferences().ptr; - break; - } - } - - /* Default to CPU device. */ - DeviceInfo device = Device::available_devices(DEVICE_MASK_CPU).front(); - - if (BlenderSession::device_override != DEVICE_MASK_ALL) { - vector devices = Device::available_devices(BlenderSession::device_override); - - if (devices.empty()) { - device = Device::dummy_device("Found no Cycles device of the specified type"); - } - else { - int threads = blender_device_threads(b_scene); - device = Device::get_multi_device(devices, threads, background); - } - } - else if (get_enum(cscene, "device") == 1) { - /* Test if we are using GPU devices. */ - ComputeDevice compute_device = (ComputeDevice)get_enum( - cpreferences, "compute_device_type", COMPUTE_DEVICE_NUM, COMPUTE_DEVICE_CPU); - - if (compute_device != COMPUTE_DEVICE_CPU) { - /* Query GPU devices with matching types. */ - uint mask = DEVICE_MASK_CPU; - if (compute_device == COMPUTE_DEVICE_CUDA) { - mask |= DEVICE_MASK_CUDA; - } - else if (compute_device == COMPUTE_DEVICE_OPTIX) { - mask |= DEVICE_MASK_OPTIX; - } - else if (compute_device == COMPUTE_DEVICE_HIP) { - mask |= DEVICE_MASK_HIP; - } - else if (compute_device == COMPUTE_DEVICE_METAL) { - mask |= DEVICE_MASK_METAL; - } - else if (compute_device == COMPUTE_DEVICE_ONEAPI) { - mask |= DEVICE_MASK_ONEAPI; - } - vector devices = Device::available_devices(mask); - - /* Match device preferences and available devices. */ - vector used_devices; - RNA_BEGIN (&cpreferences, device, "devices") { - if (get_boolean(device, "use")) { - string id = get_string(device, "id"); - foreach (DeviceInfo &info, devices) { - if (info.id == id) { - used_devices.push_back(info); - break; - } - } - } - } - RNA_END; - - if (!used_devices.empty()) { - int threads = blender_device_threads(b_scene); - device = Device::get_multi_device(used_devices, threads, background); - } - /* Else keep using the CPU device that was set before. */ - } - } - adjust_device_info_from_preferences(device, cpreferences); foreach (DeviceInfo &info, device.multi_devices) { adjust_device_info_from_preferences(info, cpreferences); @@ -162,6 +86,106 @@ DeviceInfo blender_device_info(BL::Preferences &b_preferences, KERNEL_OPTIMIZATION_NUM_LEVELS, KERNEL_OPTIMIZATION_LEVEL_FULL); } +} + +DeviceInfo blender_device_info(BL::Preferences &b_preferences, + BL::Scene &b_scene, + bool background, + bool preview, + DeviceInfo &preferences_device) +{ + PointerRNA cscene = RNA_pointer_get(&b_scene.ptr, "cycles"); + + /* Find cycles preferences. */ + PointerRNA cpreferences; + for (BL::Addon &b_addon : b_preferences.addons) { + if (b_addon.module() == "cycles") { + cpreferences = b_addon.preferences().ptr; + break; + } + } + + /* Default to CPU device. */ + DeviceInfo cpu_device = Device::available_devices(DEVICE_MASK_CPU).front(); + + /* Device, which is choosen in the Blender Preferences. */ + preferences_device = DeviceInfo(); + + /* Test if we are using GPU devices. */ + ComputeDevice compute_device = (ComputeDevice)get_enum( + cpreferences, "compute_device_type", COMPUTE_DEVICE_NUM, COMPUTE_DEVICE_CPU); + + if (compute_device != COMPUTE_DEVICE_CPU) { + /* Query GPU devices with matching types. */ + uint mask = DEVICE_MASK_CPU; + if (compute_device == COMPUTE_DEVICE_CUDA) { + mask |= DEVICE_MASK_CUDA; + } + else if (compute_device == COMPUTE_DEVICE_OPTIX) { + mask |= DEVICE_MASK_OPTIX; + } + else if (compute_device == COMPUTE_DEVICE_HIP) { + mask |= DEVICE_MASK_HIP; + } + else if (compute_device == COMPUTE_DEVICE_METAL) { + mask |= DEVICE_MASK_METAL; + } + else if (compute_device == COMPUTE_DEVICE_ONEAPI) { + mask |= DEVICE_MASK_ONEAPI; + } + vector devices = Device::available_devices(mask); + + /* Match device preferences and available devices. */ + vector used_devices; + RNA_BEGIN (&cpreferences, device, "devices") { + if (get_boolean(device, "use")) { + string id = get_string(device, "id"); + foreach (DeviceInfo &info, devices) { + if (info.id == id) { + used_devices.push_back(info); + break; + } + } + } + } + RNA_END; + + if (!used_devices.empty()) { + int threads = blender_device_threads(b_scene); + preferences_device = Device::get_multi_device(used_devices, threads, background); + } + } + else { + preferences_device = cpu_device; + } + adjust_device_info(preferences_device, cpreferences, preview); + adjust_device_info(cpu_device, cpreferences, preview); + + /* Device, which will be used, according to Settings, Scene preferences and command line + * parameters. */ + DeviceInfo device; + + if (BlenderSession::device_override != DEVICE_MASK_ALL) { + vector devices = Device::available_devices(BlenderSession::device_override); + + if (devices.empty()) { + device = Device::dummy_device("Found no Cycles device of the specified type"); + } + else { + int threads = blender_device_threads(b_scene); + device = Device::get_multi_device(devices, threads, background); + } + adjust_device_info(device, cpreferences, preview); + } + else { + /* 1 is a "GPU compute" in properties.py for Scene settings. */ + if (get_enum(cscene, "device") == 1) { + device = preferences_device; + } + else { + device = cpu_device; + } + } return device; } diff --git a/intern/cycles/blender/device.h b/intern/cycles/blender/device.h index e6adbb2dc6c..fbe9e22fcb9 100644 --- a/intern/cycles/blender/device.h +++ b/intern/cycles/blender/device.h @@ -17,11 +17,14 @@ CCL_NAMESPACE_BEGIN /* Get number of threads to use for rendering. */ int blender_device_threads(BL::Scene &b_scene); -/* Convert Blender settings to device specification. */ +/* Convert Blender settings to device specification. In addition, preferences_device contains the + * device chosen in Cycles global preferences, which is useful for the denoiser device selection. + */ DeviceInfo blender_device_info(BL::Preferences &b_preferences, BL::Scene &b_scene, bool background, - bool preview); + bool preview, + DeviceInfo &preferences_device); CCL_NAMESPACE_END diff --git a/intern/cycles/blender/python.cpp b/intern/cycles/blender/python.cpp index fa930171eef..bab6abd3130 100644 --- a/intern/cycles/blender/python.cpp +++ b/intern/cycles/blender/python.cpp @@ -416,7 +416,7 @@ static PyObject *available_devices_func(PyObject * /*self*/, PyObject *args) for (size_t i = 0; i < devices.size(); i++) { DeviceInfo &device = devices[i]; string type_name = Device::string_from_type(device.type); - PyObject *device_tuple = PyTuple_New(6); + PyObject *device_tuple = PyTuple_New(7); PyTuple_SET_ITEM(device_tuple, 0, pyunicode_from_string(device.description.c_str())); PyTuple_SET_ITEM(device_tuple, 1, pyunicode_from_string(type_name.c_str())); PyTuple_SET_ITEM(device_tuple, 2, pyunicode_from_string(device.id.c_str())); @@ -424,6 +424,7 @@ static PyObject *available_devices_func(PyObject * /*self*/, PyObject *args) PyTuple_SET_ITEM(device_tuple, 4, PyBool_FromLong(device.use_hardware_raytracing)); PyTuple_SET_ITEM( device_tuple, 5, PyBool_FromLong(device.denoisers & DENOISER_OPENIMAGEDENOISE)); + PyTuple_SET_ITEM(device_tuple, 6, PyBool_FromLong(device.denoisers & DENOISER_OPTIX)); PyTuple_SET_ITEM(ret, i, device_tuple); } @@ -751,14 +752,17 @@ static PyObject *denoise_func(PyObject * /*self*/, PyObject *args, PyObject *key PointerRNA sceneptr = RNA_id_pointer_create((ID *)PyLong_AsVoidPtr(pyscene)); BL::Scene b_scene(sceneptr); - DeviceInfo device = blender_device_info(b_preferences, b_scene, true, true); + DeviceInfo preferences_device; + DeviceInfo pathtrace_device = blender_device_info( + b_preferences, b_scene, true, true, preferences_device); /* Get denoising parameters from view layer. */ PointerRNA viewlayerptr = RNA_pointer_create( (ID *)PyLong_AsVoidPtr(pyscene), &RNA_ViewLayer, PyLong_AsVoidPtr(pyviewlayer)); BL::ViewLayer b_view_layer(viewlayerptr); - DenoiseParams params = BlenderSync::get_denoise_params(b_scene, b_view_layer, true, device); + DenoiseParams params = BlenderSync::get_denoise_params( + b_scene, b_view_layer, true, preferences_device); params.use = true; /* Parse file paths list. */ @@ -787,7 +791,10 @@ static PyObject *denoise_func(PyObject * /*self*/, PyObject *args, PyObject *key } /* Create denoiser. */ - DenoiserPipeline denoiser(device, params); + /* We are using preference device here, because path trace device will be identical to it unless + * scene is setting CPU render or command line override render device. But both of this options + * are for render, not for denoising. */ + DenoiserPipeline denoiser(preferences_device, params); denoiser.input = input; denoiser.output = output; diff --git a/intern/cycles/blender/sync.cpp b/intern/cycles/blender/sync.cpp index 84bb1588cd7..11faef434b0 100644 --- a/intern/cycles/blender/sync.cpp +++ b/intern/cycles/blender/sync.cpp @@ -893,7 +893,7 @@ SessionParams BlenderSync::get_session_params(BL::RenderEngine &b_engine, /* Device */ params.threads = blender_device_threads(b_scene); params.device = blender_device_info( - b_preferences, b_scene, params.background, b_engine.is_preview()); + b_preferences, b_scene, params.background, b_engine.is_preview(), params.denoise_device); /* samples */ int samples = get_int(cscene, "samples"); diff --git a/intern/cycles/integrator/denoiser.cpp b/intern/cycles/integrator/denoiser.cpp index b59b0bdbd36..75bc4016dc6 100644 --- a/intern/cycles/integrator/denoiser.cpp +++ b/intern/cycles/integrator/denoiser.cpp @@ -19,31 +19,103 @@ CCL_NAMESPACE_BEGIN -unique_ptr Denoiser::create(Device *path_trace_device, const DenoiseParams ¶ms) +/* Check whether given device is single (not a MultiDevice). */ +static bool is_single_device(const Device *device) +{ + if (device->info.type == DEVICE_MULTI) { + /* Assume multi-device is never created with a single sub-device. + * If one requests such configuration it should be checked on the session level. */ + return false; + } + + if (!device->info.multi_devices.empty()) { + /* Some configurations will use multi_devices, but keep the type of an individual device. + * This does simplify checks for homogeneous setups, but here we really need a single device. + */ + return false; + } + + return true; +} + +/* Find best suitable device to perform denoiser on. Will iterate over possible sub-devices of + * multi-device. */ +static Device *find_best_device(Device *device, const DenoiserType type) +{ + Device *best_device = nullptr; + + device->foreach_device([&](Device *sub_device) { + if ((sub_device->info.denoisers & type) == 0) { + return; + } + + if (!best_device) { + best_device = sub_device; + } + else { + /* Prefer non-CPU devices over CPU for performance reasons. */ + if (sub_device->info.type != DEVICE_CPU && best_device->info.type == DEVICE_CPU) { + best_device = sub_device; + } + + /* Prefer a device that can use graphics interop for faster display update. */ + if (sub_device->should_use_graphics_interop() && !best_device->should_use_graphics_interop()) + { + best_device = sub_device; + } + + /* TODO(sergey): Choose fastest device from available ones. Taking into account performance + * of the device and data transfer cost. */ + } + }); + + return best_device; +} + +unique_ptr Denoiser::create(Device *denoiser_device, + Device *cpu_fallback_device, + const DenoiseParams ¶ms) { DCHECK(params.use); -#ifdef WITH_OPENIMAGEDENOISE - /* If available and allowed, then we will use OpenImageDenoise on GPU. */ - if (params.type == DENOISER_OPENIMAGEDENOISE && params.use_gpu && - path_trace_device->info.type != DEVICE_CPU && - OIDNDenoiserGPU::is_device_supported(path_trace_device->info)) - { - return make_unique(path_trace_device, params); + Device *single_denoiser_device = nullptr; + if (is_single_device(denoiser_device)) { + /* Simple case: denoising happens on a single device. */ + single_denoiser_device = denoiser_device; } + else { + /* Find best device from the ones which are proposed for denoising. */ + /* The choise is expected to be between few GPUs, or between GPU and a CPU + * or between few GPU and a CPU. */ + single_denoiser_device = find_best_device(denoiser_device, params.type); + } + + bool is_cpu_denoiser_device = single_denoiser_device->info.type == DEVICE_CPU; + if (is_cpu_denoiser_device == false) { +#ifdef WITH_OPTIX + if (params.type == DENOISER_OPTIX) { + return make_unique(single_denoiser_device, params); + } #endif -#ifdef WITH_OPTIX - /* Use OptiX on GPU if supported. */ - if (params.type == DENOISER_OPTIX && Device::available_devices(DEVICE_MASK_OPTIX).size()) { - return make_unique(path_trace_device, params); - } +#ifdef WITH_OPENIMAGEDENOISE + /* If available and allowed, then we will use OpenImageDenoise on GPU, otherwise on CPU. */ + if (params.type == DENOISER_OPENIMAGEDENOISE && params.use_gpu && + OIDNDenoiserGPU::is_device_supported(single_denoiser_device->info)) + { + return make_unique(single_denoiser_device, params); + } #endif + } /* Always fallback to OIDN on CPU. */ DenoiseParams oidn_params = params; oidn_params.type = DENOISER_OPENIMAGEDENOISE; - return make_unique(path_trace_device, oidn_params); + oidn_params.use_gpu = false; + + /* Used preference CPU when possible, and fallback on cpu fallback device otherwice. */ + return make_unique( + is_cpu_denoiser_device ? single_denoiser_device : cpu_fallback_device, oidn_params); } DenoiserType Denoiser::automatic_viewport_denoiser_type(const DeviceInfo &path_trace_device_info) @@ -71,9 +143,10 @@ DenoiserType Denoiser::automatic_viewport_denoiser_type(const DeviceInfo &path_t return DENOISER_NONE; } -Denoiser::Denoiser(Device *path_trace_device, const DenoiseParams ¶ms) - : path_trace_device_(path_trace_device), params_(params) +Denoiser::Denoiser(Device *denoiser_device, const DenoiseParams ¶ms) + : denoiser_device_(denoiser_device), params_(params) { + DCHECK(denoiser_device_); DCHECK(params.use); } @@ -96,15 +169,27 @@ const DenoiseParams &Denoiser::get_params() const bool Denoiser::load_kernels(Progress *progress) { - const Device *denoiser_device = ensure_denoiser_device(progress); + if (progress) { + progress->set_status("Loading denoising kernels (may take a few minutes the first time)"); + } - if (!denoiser_device) { + if (!denoiser_device_) { set_error("No device available to denoise on"); return false; } - VLOG_WORK << "Will denoise on " << denoiser_device->info.description << " (" - << denoiser_device->info.id << ")"; + /* Only need denoising feature, everything else is unused. */ + if (!denoiser_device_->load_kernels(KERNEL_FEATURE_DENOISING)) { + string message = denoiser_device_->error_message(); + if (message.empty()) { + message = "Failed loading denoising kernel, see console for errors"; + } + set_error(message); + return false; + } + + VLOG_WORK << "Will denoise on " << denoiser_device_->info.description << " (" + << denoiser_device_->info.id << ")"; return true; } @@ -114,156 +199,4 @@ Device *Denoiser::get_denoiser_device() const return denoiser_device_; } -/* Check whether given device is single (not a MultiDevice) and supports requested denoiser. */ -static bool is_single_supported_device(const Device *device, - const uint device_type_mask, - const DenoiserType type) -{ - if (!(device_type_mask & (1 << device->info.type))) { - return false; - } - - if (device->info.type == DEVICE_MULTI) { - /* Assume multi-device is never created with a single sub-device. - * If one requests such configuration it should be checked on the session level. */ - return false; - } - - if (!device->info.multi_devices.empty()) { - /* Some configurations will use multi_devices, but keep the type of an individual device. - * This does simplify checks for homogeneous setups, but here we really need a single device. - */ - return false; - } - - /* Check the denoiser type is supported. */ - return (device->info.denoisers & type); -} - -/* Find best suitable device to perform denoiser on. Will iterate over possible sub-devices of - * multi-device. - * - * If there is no device available which supports given denoiser type nullptr is returned. */ -static Device *find_best_device(Device *device, - const uint device_type_mask, - const DenoiserType type) -{ - Device *best_device = nullptr; - - device->foreach_device([&](Device *sub_device) { - if (!(device_type_mask & (1 << sub_device->info.type))) { - return; - } - if ((sub_device->info.denoisers & type) == 0) { - return; - } - - if (!best_device) { - best_device = sub_device; - } - else { - /* Prefer a device that can use graphics interop for faster display update. */ - if (sub_device->should_use_graphics_interop() && !best_device->should_use_graphics_interop()) - { - best_device = sub_device; - } - - /* TODO(sergey): Choose fastest device from available ones. Taking into account performance - * of the device and data transfer cost. */ - } - }); - - return best_device; -} - -static DeviceInfo find_best_denoiser_device_info(const vector &device_infos, - const DenoiserType denoiser_type) -{ - for (const DeviceInfo &device_info : device_infos) { - if ((device_info.denoisers & denoiser_type) == 0) { - continue; - } - - /* TODO(sergey): Use one of the already configured devices, so that GPU denoising can happen - * on a physical device which is already used for rendering. */ - - /* TODO(sergey): Choose fastest device for denoising. */ - - return device_info; - } - - DeviceInfo none_device; - none_device.type = DEVICE_NONE; - return none_device; -} - -static unique_ptr create_denoiser_device(Device *path_trace_device, - const uint device_type_mask, - const DenoiserType denoiser_type) -{ - const vector device_infos = Device::available_devices(device_type_mask); - if (device_infos.empty()) { - return nullptr; - } - - const DeviceInfo denoiser_device_info = find_best_denoiser_device_info(device_infos, - denoiser_type); - if (denoiser_device_info.type == DEVICE_NONE) { - return nullptr; - } - - unique_ptr denoiser_device( - Device::create(denoiser_device_info, path_trace_device->stats, path_trace_device->profiler)); - - if (!denoiser_device) { - return nullptr; - } - - if (denoiser_device->have_error()) { - return nullptr; - } - - /* Only need denoising feature, everything else is unused. */ - if (!denoiser_device->load_kernels(KERNEL_FEATURE_DENOISING)) { - return nullptr; - } - - return denoiser_device; -} - -Device *Denoiser::ensure_denoiser_device(Progress *progress) -{ - /* The best device has been found already, avoid sequential lookups. - * Additionally, avoid device re-creation if it has failed once. */ - if (denoiser_device_ || device_creation_attempted_) { - return denoiser_device_; - } - - const uint device_type_mask = get_device_type_mask(); - - /* Simple case: rendering happens on a single device which also supports denoiser. */ - if (is_single_supported_device(path_trace_device_, device_type_mask, params_.type)) { - denoiser_device_ = path_trace_device_; - return denoiser_device_; - } - - /* Find best device from the ones which are already used for rendering. */ - denoiser_device_ = find_best_device(path_trace_device_, device_type_mask, params_.type); - if (denoiser_device_) { - return denoiser_device_; - } - - if (progress) { - progress->set_status("Loading denoising kernels (may take a few minutes the first time)"); - } - - device_creation_attempted_ = true; - - local_denoiser_device_ = create_denoiser_device( - path_trace_device_, device_type_mask, params_.type); - denoiser_device_ = local_denoiser_device_.get(); - - return denoiser_device_; -} - CCL_NAMESPACE_END diff --git a/intern/cycles/integrator/denoiser.h b/intern/cycles/integrator/denoiser.h index 9de303944f1..ac34a473920 100644 --- a/intern/cycles/integrator/denoiser.h +++ b/intern/cycles/integrator/denoiser.h @@ -32,8 +32,12 @@ class Denoiser { * Notes: * - The denoiser must be configured. This means that `params.use` must be true. * This is checked in debug builds. - * - The device might be MultiDevice. */ - static unique_ptr create(Device *path_trace_device, const DenoiseParams ¶ms); + * - The device might be MultiDevice. + * - If Denoiser from params is not supported by provided denoise device, then Blender will + fallback on the OIDN CPU denoising and use provided cpu_fallback_device. */ + static unique_ptr create(Device *denoise_device, + Device *cpu_fallback_device, + const DenoiseParams ¶ms); virtual ~Denoiser() = default; @@ -80,8 +84,6 @@ class Denoiser { * * Notes: * - * - The device is lazily initialized via `load_kernels()`, so it will be nullptr until then, - * * - The device can be different from the path tracing device. This happens, for example, when * using OptiX denoiser and rendering on CPU. * @@ -102,30 +104,18 @@ class Denoiser { void set_error(const string &error) { - path_trace_device_->set_error(error); + denoiser_device_->set_error(error); } protected: - Denoiser(Device *path_trace_device, const DenoiseParams ¶ms); - - /* Make sure denoising device is initialized. */ - virtual Device *ensure_denoiser_device(Progress *progress); + Denoiser(Device *denoiser_device, const DenoiseParams ¶ms); /* Get device type mask which is used to filter available devices when new device needs to be * created. */ virtual uint get_device_type_mask() const = 0; - Device *path_trace_device_; + Device *denoiser_device_; DenoiseParams params_; - - /* Cached pointer to the device on which denoising will happen. - * Used to avoid lookup of a device for every denoising request. */ - Device *denoiser_device_ = nullptr; - - /* Denoiser device which was created to perform denoising in the case the none of the rendering - * devices are capable of denoising. */ - unique_ptr local_denoiser_device_; - bool device_creation_attempted_ = false; }; CCL_NAMESPACE_END diff --git a/intern/cycles/integrator/denoiser_gpu.cpp b/intern/cycles/integrator/denoiser_gpu.cpp index 4203990d18a..6c3a2f18a0d 100644 --- a/intern/cycles/integrator/denoiser_gpu.cpp +++ b/intern/cycles/integrator/denoiser_gpu.cpp @@ -15,9 +15,11 @@ CCL_NAMESPACE_BEGIN -DenoiserGPU::DenoiserGPU(Device *path_trace_device, const DenoiseParams ¶ms) - : Denoiser(path_trace_device, params) +DenoiserGPU::DenoiserGPU(Device *denoiser_device, const DenoiseParams ¶ms) + : Denoiser(denoiser_device, params) { + denoiser_queue_ = denoiser_device->gpu_queue_create(); + DCHECK(denoiser_queue_); } DenoiserGPU::~DenoiserGPU() @@ -156,23 +158,6 @@ bool DenoiserGPU::denoise_filter_guiding_preprocess(const DenoiseContext &contex return denoiser_queue_->enqueue(DEVICE_KERNEL_FILTER_GUIDING_PREPROCESS, work_size, args); } -Device *DenoiserGPU::ensure_denoiser_device(Progress *progress) -{ - Device *denoiser_device = Denoiser::ensure_denoiser_device(progress); - if (!denoiser_device) { - return nullptr; - } - - if (!denoiser_queue_) { - denoiser_queue_ = denoiser_device->gpu_queue_create(); - if (!denoiser_queue_) { - return nullptr; - } - } - - return denoiser_device; -} - DenoiserGPU::DenoiseContext::DenoiseContext(Device *device, const DenoiseTask &task) : denoise_params(task.params), render_buffers(task.render_buffers), diff --git a/intern/cycles/integrator/denoiser_gpu.h b/intern/cycles/integrator/denoiser_gpu.h index 095a7995288..6e4f62a4089 100644 --- a/intern/cycles/integrator/denoiser_gpu.h +++ b/intern/cycles/integrator/denoiser_gpu.h @@ -13,7 +13,7 @@ CCL_NAMESPACE_BEGIN * and invokes denoising kernels via the device queue API. */ class DenoiserGPU : public Denoiser { public: - DenoiserGPU(Device *path_trace_device, const DenoiseParams ¶ms); + DenoiserGPU(Device *denoiser_device, const DenoiseParams ¶ms); ~DenoiserGPU(); virtual bool denoise_buffer(const BufferParams &buffer_params, @@ -80,8 +80,6 @@ class DenoiserGPU : public Denoiser { virtual bool denoise_buffer(const DenoiseTask &task); virtual bool denoise_run(const DenoiseContext &context, const DenoisePass &pass) = 0; - virtual Device *ensure_denoiser_device(Progress *progress) override; - unique_ptr denoiser_queue_; class DenoisePass { diff --git a/intern/cycles/integrator/denoiser_oidn.cpp b/intern/cycles/integrator/denoiser_oidn.cpp index d3d533bc048..7fc2764e464 100644 --- a/intern/cycles/integrator/denoiser_oidn.cpp +++ b/intern/cycles/integrator/denoiser_oidn.cpp @@ -21,10 +21,20 @@ CCL_NAMESPACE_BEGIN thread_mutex OIDNDenoiser::mutex_; -OIDNDenoiser::OIDNDenoiser(Device *path_trace_device, const DenoiseParams ¶ms) - : Denoiser(path_trace_device, params) +OIDNDenoiser::OIDNDenoiser(Device *denoiser_device, const DenoiseParams ¶ms) + : Denoiser(denoiser_device, params) { DCHECK_EQ(params.type, DENOISER_OPENIMAGEDENOISE); + +#ifndef WITH_OPENIMAGEDENOISE + (void)progress; + set_error("Failed to denoise, build has no OpenImageDenoise support"); + return nullptr; +#else + if (!openimagedenoise_supported()) { + set_error("OpenImageDenoiser is not supported on this CPU: missing SSE 4.1 support"); + } +#endif } #ifdef WITH_OPENIMAGEDENOISE @@ -649,20 +659,4 @@ uint OIDNDenoiser::get_device_type_mask() const return DEVICE_MASK_CPU; } -Device *OIDNDenoiser::ensure_denoiser_device(Progress *progress) -{ -#ifndef WITH_OPENIMAGEDENOISE - (void)progress; - set_error("Failed to denoise, build has no OpenImageDenoise support"); - return nullptr; -#else - if (!openimagedenoise_supported()) { - set_error("OpenImageDenoise is not supported on this CPU: missing SSE 4.1 support"); - return nullptr; - } - - return Denoiser::ensure_denoiser_device(progress); -#endif -} - CCL_NAMESPACE_END diff --git a/intern/cycles/integrator/denoiser_oidn.h b/intern/cycles/integrator/denoiser_oidn.h index 3828735ab14..9f11b0f1d8f 100644 --- a/intern/cycles/integrator/denoiser_oidn.h +++ b/intern/cycles/integrator/denoiser_oidn.h @@ -17,7 +17,7 @@ class OIDNDenoiser : public Denoiser { * OpenImageDenoise device and filter handles. */ class State; - OIDNDenoiser(Device *path_trace_device, const DenoiseParams ¶ms); + OIDNDenoiser(Device *denoiser_device, const DenoiseParams ¶ms); virtual bool denoise_buffer(const BufferParams &buffer_params, RenderBuffers *render_buffers, @@ -26,7 +26,6 @@ class OIDNDenoiser : public Denoiser { protected: virtual uint get_device_type_mask() const override; - virtual Device *ensure_denoiser_device(Progress *progress) override; /* We only perform one denoising at a time, since OpenImageDenoise itself is multithreaded. * Use this mutex whenever images are passed to the OIDN and needs to be denoised. */ diff --git a/intern/cycles/integrator/denoiser_oidn_gpu.cpp b/intern/cycles/integrator/denoiser_oidn_gpu.cpp index 6cc9ba4614e..262ce465205 100644 --- a/intern/cycles/integrator/denoiser_oidn_gpu.cpp +++ b/intern/cycles/integrator/denoiser_oidn_gpu.cpp @@ -159,8 +159,8 @@ bool OIDNDenoiserGPU::is_device_supported(const DeviceInfo &device) # endif } -OIDNDenoiserGPU::OIDNDenoiserGPU(Device *path_trace_device, const DenoiseParams ¶ms) - : DenoiserGPU(path_trace_device, params) +OIDNDenoiserGPU::OIDNDenoiserGPU(Device *denoiser_device, const DenoiseParams ¶ms) + : DenoiserGPU(denoiser_device, params) { DCHECK_EQ(params.type, DENOISER_OPENIMAGEDENOISE); } diff --git a/intern/cycles/integrator/denoiser_oidn_gpu.h b/intern/cycles/integrator/denoiser_oidn_gpu.h index 0cea7a8a916..c3ae9f3affe 100644 --- a/intern/cycles/integrator/denoiser_oidn_gpu.h +++ b/intern/cycles/integrator/denoiser_oidn_gpu.h @@ -22,7 +22,7 @@ class OIDNDenoiserGPU : public DenoiserGPU { * OpenImageDenoise device and filter handles. */ class State; - OIDNDenoiserGPU(Device *path_trace_device, const DenoiseParams ¶ms); + OIDNDenoiserGPU(Device *denoiser_device, const DenoiseParams ¶ms); ~OIDNDenoiserGPU(); virtual bool denoise_buffer(const BufferParams &buffer_params, diff --git a/intern/cycles/integrator/denoiser_optix.cpp b/intern/cycles/integrator/denoiser_optix.cpp index 2175cff550e..955d370ea25 100644 --- a/intern/cycles/integrator/denoiser_optix.cpp +++ b/intern/cycles/integrator/denoiser_optix.cpp @@ -199,8 +199,8 @@ static OptixResult optixUtilDenoiserInvokeTiled(OptixDenoiser denoiser, } # endif -OptiXDenoiser::OptiXDenoiser(Device *path_trace_device, const DenoiseParams ¶ms) - : DenoiserGPU(path_trace_device, params), state_(path_trace_device, "__denoiser_state", true) +OptiXDenoiser::OptiXDenoiser(Device *denoiser_device, const DenoiseParams ¶ms) + : DenoiserGPU(denoiser_device, params), state_(denoiser_device, "__denoiser_state", true) { } diff --git a/intern/cycles/integrator/denoiser_optix.h b/intern/cycles/integrator/denoiser_optix.h index 67dfd39db0a..67b4788052c 100644 --- a/intern/cycles/integrator/denoiser_optix.h +++ b/intern/cycles/integrator/denoiser_optix.h @@ -15,7 +15,7 @@ CCL_NAMESPACE_BEGIN /* Implementation of denoising API which uses the OptiX denoiser. */ class OptiXDenoiser : public DenoiserGPU { public: - OptiXDenoiser(Device *path_trace_device, const DenoiseParams ¶ms); + OptiXDenoiser(Device *denoiser_device, const DenoiseParams ¶ms); ~OptiXDenoiser(); protected: diff --git a/intern/cycles/integrator/path_trace.cpp b/intern/cycles/integrator/path_trace.cpp index f627466c9a7..6b8bba58f76 100644 --- a/intern/cycles/integrator/path_trace.cpp +++ b/intern/cycles/integrator/path_trace.cpp @@ -22,11 +22,13 @@ CCL_NAMESPACE_BEGIN PathTrace::PathTrace(Device *device, + Device *denoise_device, Film *film, DeviceScene *device_scene, RenderScheduler &render_scheduler, TileManager &tile_manager) : device_(device), + denoise_device_(denoise_device), film_(film), device_scene_(device_scene), render_scheduler_(render_scheduler), @@ -483,15 +485,27 @@ void PathTrace::adaptive_sample(RenderWork &render_work) void PathTrace::set_denoiser_params(const DenoiseParams ¶ms) { - render_scheduler_.set_denoiser_params(params); - if (!params.use) { denoiser_.reset(); return; } + bool need_to_recreate_denoiser = false; if (denoiser_) { const DenoiseParams old_denoiser_params = denoiser_->get_params(); + + const bool is_cpu_denoising = old_denoiser_params.type == DENOISER_OPENIMAGEDENOISE && + old_denoiser_params.use_gpu == false; + const bool requested_gpu_denoising = params.type == DENOISER_OPTIX || + (params.type == DENOISER_OPENIMAGEDENOISE && + params.use_gpu == true); + if (requested_gpu_denoising && is_cpu_denoising && denoise_device_->info.type == DEVICE_CPU) { + /* It won't be possible to use GPU denoising when according to user settings we have + * only CPU as available denoising device. So we just exiting early to avoid + * unnecessary denoiser recreation or parameters update. */ + return; + } + const bool is_same_denoising_device_type = old_denoiser_params.use_gpu == params.use_gpu; /* Optix Denoiser is not supporting CPU devices, so use_gpu option is not * shown in the UI and changes in the option value should not be checked. */ @@ -499,16 +513,30 @@ void PathTrace::set_denoiser_params(const DenoiseParams ¶ms) (is_same_denoising_device_type || params.type == DENOISER_OPTIX)) { denoiser_->set_params(params); - return; + } + else { + need_to_recreate_denoiser = true; } } + else { + /* if there is no denoiser and param.use is true, then we need to create it. */ + need_to_recreate_denoiser = true; + } - denoiser_ = Denoiser::create(device_, params); + if (need_to_recreate_denoiser) { + denoiser_ = Denoiser::create(denoise_device_, cpu_device_.get(), params); - /* Only take into account the "immediate" cancel to have interactive rendering responding to - * navigation as quickly as possible, but allow to run denoiser after user hit Escape key while - * doing offline rendering. */ - denoiser_->is_cancelled_cb = [this]() { return render_cancel_.is_requested; }; + /* Only take into account the "immediate" cancel to have interactive rendering responding to + * navigation as quickly as possible, but allow to run denoiser after user hit Escape key while + * doing offline rendering. */ + denoiser_->is_cancelled_cb = [this]() { return render_cancel_.is_requested; }; + } + + /* Use actual parameters, if available */ + if (denoise_device_) + render_scheduler_.set_denoiser_params(denoiser_->get_params()); + else + render_scheduler_.set_denoiser_params(params); } void PathTrace::set_adaptive_sampling(const AdaptiveSampling &adaptive_sampling) diff --git a/intern/cycles/integrator/path_trace.h b/intern/cycles/integrator/path_trace.h index 5473235dc5a..0c880509c84 100644 --- a/intern/cycles/integrator/path_trace.h +++ b/intern/cycles/integrator/path_trace.h @@ -45,6 +45,7 @@ class PathTrace { /* Render scheduler is used to report timing information and access things like start/finish * sample. */ PathTrace(Device *device, + Device *denoiser_device, Film *film, DeviceScene *device_scene, RenderScheduler &render_scheduler, @@ -251,6 +252,10 @@ class PathTrace { * are configured this is a `MultiDevice`. */ Device *device_ = nullptr; + /* Pointer to a device which is configured to be used for denoising. Can be identical + * to the device */ + Device *denoise_device_ = nullptr; + /* CPU device for creating temporary render buffers on the CPU side. */ unique_ptr cpu_device_; diff --git a/intern/cycles/session/denoising.cpp b/intern/cycles/session/denoising.cpp index d4f14ccbe3a..7917e364d7b 100644 --- a/intern/cycles/session/denoising.cpp +++ b/intern/cycles/session/denoising.cpp @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ #include "session/denoising.h" +#include "device/cpu/device.h" #include "util/map.h" #include "util/system.h" @@ -599,16 +600,20 @@ bool DenoiseImage::save_output(const string &out_filepath, string &error) /* File pattern handling and outer loop over frames */ -DenoiserPipeline::DenoiserPipeline(DeviceInfo &device_info, const DenoiseParams ¶ms) +DenoiserPipeline::DenoiserPipeline(DeviceInfo &denoiser_device_info, const DenoiseParams ¶ms) { /* Initialize task scheduler. */ TaskScheduler::init(); /* Initialize device. */ - device = Device::create(device_info, stats, profiler); + device = Device::create(denoiser_device_info, stats, profiler); device->load_kernels(KERNEL_FEATURE_DENOISING); - denoiser = Denoiser::create(device, params); + vector cpu_devices; + device_cpu_info(cpu_devices); + cpu_device = device_cpu_create(cpu_devices[0], device->stats, device->profiler); + + denoiser = Denoiser::create(device, cpu_device, params); denoiser->load_kernels(nullptr); } diff --git a/intern/cycles/session/denoising.h b/intern/cycles/session/denoising.h index 12b32c1144c..71c5719fb03 100644 --- a/intern/cycles/session/denoising.h +++ b/intern/cycles/session/denoising.h @@ -25,7 +25,7 @@ CCL_NAMESPACE_BEGIN class DenoiserPipeline { public: - DenoiserPipeline(DeviceInfo &device_info, const DenoiseParams ¶ms); + DenoiserPipeline(DeviceInfo &denoiser_device_info, const DenoiseParams ¶ms); ~DenoiserPipeline(); bool run(); @@ -46,6 +46,7 @@ class DenoiserPipeline { Stats stats; Profiler profiler; Device *device; + Device *cpu_device; std::unique_ptr denoiser; }; diff --git a/intern/cycles/session/session.cpp b/intern/cycles/session/session.cpp index 81b507db2d3..1c0311e7d44 100644 --- a/intern/cycles/session/session.cpp +++ b/intern/cycles/session/session.cpp @@ -50,9 +50,20 @@ Session::Session(const SessionParams ¶ms_, const SceneParams &scene_params) scene = new Scene(scene_params, device); + if (params.device == params.denoise_device) { + denoise_device = device; + } + else { + denoise_device = Device::create(params.denoise_device, stats, profiler); + + if (denoise_device->have_error()) { + progress.set_error(denoise_device->error_message()); + } + } + /* Configure path tracer. */ path_trace_ = make_unique( - device, scene->film, &scene->dscene, render_scheduler_, tile_manager_); + device, denoise_device, scene->film, &scene->dscene, render_scheduler_, tile_manager_); path_trace_->set_progress(&progress); path_trace_->progress_update_cb = [&]() { update_status_time(); }; @@ -91,6 +102,9 @@ Session::~Session() /* Destroy scene and device. */ delete scene; + if (denoise_device != device) { + delete denoise_device; + } delete device; /* Stop task scheduler. */ diff --git a/intern/cycles/session/session.h b/intern/cycles/session/session.h index 2a8b2481b48..bfbfdf75661 100644 --- a/intern/cycles/session/session.h +++ b/intern/cycles/session/session.h @@ -35,7 +35,11 @@ class SceneParams; class SessionParams { public: + /* Device, which is choosen based on Blender Cycles preferences, as well as Scene settings and + * command line arguments. */ DeviceInfo device; + /* Device from Cycles preferences for denoising. */ + DeviceInfo denoise_device; bool headless; bool background; @@ -104,6 +108,8 @@ class SessionParams { class Session { public: Device *device; + /* Denoiser device. Could be the same as the path trace device. */ + Device *denoise_device; Scene *scene; Progress progress; SessionParams params;