Cycles: add Intel OpenImageDenoise support for viewport denoising
Compared to Optix denoise, this is usually slower since there is no GPU acceleration. Some optimizations may still be possible, in avoid copies to the GPU and/or denoising less often. The main thing is that this adds viewport denoising support for computers without an NVIDIA GPU (as long as the CPU supports SSE 4.1, which is nearly all of them). Ref T76259
This commit is contained in:
parent
0a3bde6300
commit
669befdfbe
Notes:
blender-bot
2023-02-13 22:41:09 +01:00
Referenced by issue #76259, Cycles OpenImageDenoise improvements
|
@ -102,6 +102,13 @@ if(WITH_OPENVDB)
|
|||
)
|
||||
endif()
|
||||
|
||||
if(WITH_OPENIMAGEDENOISE)
|
||||
add_definitions(-DWITH_OPENIMAGEDENOISE)
|
||||
list(APPEND INC_SYS
|
||||
${OPENIMAGEDENOISE_INCLUDE_DIRS}
|
||||
)
|
||||
endif()
|
||||
|
||||
blender_add_lib(bf_intern_cycles "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
|
||||
# avoid link failure with clang 3.4 debug
|
||||
|
|
|
@ -182,14 +182,30 @@ enum_aov_types = (
|
|||
('COLOR', "Color", "Write a Color pass", 1),
|
||||
)
|
||||
|
||||
def enum_openimagedenoise_denoiser(self, context):
|
||||
if _cycles.with_openimagedenoise:
|
||||
return [('OPENIMAGEDENOISE', "OpenImageDenoise", "Use Intel OpenImageDenoise AI denoiser running on the CPU", 4)]
|
||||
return []
|
||||
|
||||
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 []
|
||||
|
||||
def enum_preview_denoiser(self, context):
|
||||
items = [('AUTO', "Auto", "Use the fastest available denoiser for viewport rendering", 0)]
|
||||
items += enum_optix_denoiser(self, context)
|
||||
optix_items = enum_optix_denoiser(self, context)
|
||||
oidn_items = enum_openimagedenoise_denoiser(self, context)
|
||||
|
||||
if len(optix_items):
|
||||
auto_label = "Fastest (Optix)"
|
||||
elif len(oidn_items):
|
||||
auto_label = "Fatest (OpenImageDenoise)"
|
||||
else:
|
||||
auto_label = "None"
|
||||
|
||||
items = [('AUTO', auto_label, "Use the fastest available denoiser for viewport rendering", 0)]
|
||||
items += optix_items
|
||||
items += oidn_items
|
||||
return items
|
||||
|
||||
def enum_denoiser(self, context):
|
||||
|
|
|
@ -1006,6 +1006,8 @@ class CYCLES_RENDER_PT_denoising(CyclesButtonsPanel, Panel):
|
|||
if denoiser == 'OPTIX':
|
||||
col.prop(cycles_view_layer, "denoising_optix_input_passes")
|
||||
return
|
||||
elif denoiser == 'OPENIMAGEDENOISE':
|
||||
return
|
||||
|
||||
col.prop(cycles_view_layer, "denoising_radius", text="Radius")
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "util/util_logging.h"
|
||||
#include "util/util_md5.h"
|
||||
#include "util/util_opengl.h"
|
||||
#include "util/util_openimagedenoise.h"
|
||||
#include "util/util_path.h"
|
||||
#include "util/util_string.h"
|
||||
#include "util/util_task.h"
|
||||
|
@ -1076,5 +1077,14 @@ void *CCL_python_module_init()
|
|||
Py_INCREF(Py_False);
|
||||
#endif /* WITH_EMBREE */
|
||||
|
||||
if (ccl::openimagedenoise_supported()) {
|
||||
PyModule_AddObject(mod, "with_openimagedenoise", Py_True);
|
||||
Py_INCREF(Py_True);
|
||||
}
|
||||
else {
|
||||
PyModule_AddObject(mod, "with_openimagedenoise", Py_False);
|
||||
Py_INCREF(Py_False);
|
||||
}
|
||||
|
||||
return (void *)mod;
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include "util/util_foreach.h"
|
||||
#include "util/util_hash.h"
|
||||
#include "util/util_opengl.h"
|
||||
#include "util/util_openimagedenoise.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
|
@ -957,6 +958,9 @@ DenoiseParams BlenderSync::get_denoise_params(BL::Scene &b_scene,
|
|||
if (!Device::available_devices(DEVICE_MASK_OPTIX).empty()) {
|
||||
denoising.type = DENOISER_OPTIX;
|
||||
}
|
||||
else if (openimagedenoise_supported()) {
|
||||
denoising.type = DENOISER_OPENIMAGEDENOISE;
|
||||
}
|
||||
else {
|
||||
denoising.use = false;
|
||||
}
|
||||
|
|
|
@ -99,6 +99,18 @@ if(WITH_CYCLES_DEVICE_MULTI)
|
|||
add_definitions(-DWITH_MULTI)
|
||||
endif()
|
||||
|
||||
if(WITH_OPENIMAGEDENOISE)
|
||||
add_definitions(-DWITH_OPENIMAGEDENOISE)
|
||||
add_definitions(-DOIDN_STATIC_LIB)
|
||||
list(APPEND INC_SYS
|
||||
${OPENIMAGEDENOISE_INCLUDE_DIRS}
|
||||
)
|
||||
list(APPEND LIB
|
||||
${OPENIMAGEDENOISE_LIBRARIES}
|
||||
${TBB_LIBRARIES}
|
||||
)
|
||||
endif()
|
||||
|
||||
include_directories(${INC})
|
||||
include_directories(SYSTEM ${INC_SYS})
|
||||
|
||||
|
|
|
@ -706,6 +706,18 @@ void DeviceInfo::add_denoising_devices(DenoiserType denoiser_type)
|
|||
denoisers = denoiser_type;
|
||||
}
|
||||
}
|
||||
else if (denoiser_type == DENOISER_OPENIMAGEDENOISE && type != DEVICE_CPU) {
|
||||
/* Convert to a special multi device with separate denoising devices. */
|
||||
if (multi_devices.empty()) {
|
||||
multi_devices.push_back(*this);
|
||||
}
|
||||
|
||||
/* Add CPU denoising devices. */
|
||||
DeviceInfo cpu_device = Device::available_devices(DEVICE_MASK_CPU).front();
|
||||
denoising_devices.push_back(cpu_device);
|
||||
|
||||
denoisers = denoiser_type;
|
||||
}
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
#include "util/util_function.h"
|
||||
#include "util/util_logging.h"
|
||||
#include "util/util_map.h"
|
||||
#include "util/util_openimagedenoise.h"
|
||||
#include "util/util_opengl.h"
|
||||
#include "util/util_optimization.h"
|
||||
#include "util/util_progress.h"
|
||||
|
@ -177,6 +178,10 @@ class CPUDevice : public Device {
|
|||
#ifdef WITH_OSL
|
||||
OSLGlobals osl_globals;
|
||||
#endif
|
||||
#ifdef WITH_OPENIMAGEDENOISE
|
||||
oidn::DeviceRef oidn_device;
|
||||
oidn::FilterRef oidn_filter;
|
||||
#endif
|
||||
|
||||
bool use_split_kernel;
|
||||
|
||||
|
@ -943,6 +948,70 @@ class CPUDevice : public Device {
|
|||
}
|
||||
}
|
||||
|
||||
void denoise_openimagedenoise(DeviceTask &task, RenderTile &rtile)
|
||||
{
|
||||
#ifdef WITH_OPENIMAGEDENOISE
|
||||
assert(openimagedenoise_supported());
|
||||
|
||||
/* Only one at a time, since OpenImageDenoise itself is multithreaded. */
|
||||
static thread_mutex mutex;
|
||||
thread_scoped_lock lock(mutex);
|
||||
|
||||
/* Create device and filter, cached for reuse. */
|
||||
if (!oidn_device) {
|
||||
oidn_device = oidn::newDevice();
|
||||
oidn_device.commit();
|
||||
}
|
||||
if (!oidn_filter) {
|
||||
oidn_filter = oidn_device.newFilter("RT");
|
||||
}
|
||||
|
||||
/* Copy pixels from compute device to CPU (no-op for CPU device). */
|
||||
rtile.buffers->buffer.copy_from_device();
|
||||
|
||||
/* Set images with appropriate stride for our interleaved pass storage. */
|
||||
const struct {
|
||||
const char *name;
|
||||
int offset;
|
||||
} passes[] = {{"color", task.pass_denoising_data + DENOISING_PASS_COLOR},
|
||||
{"normal", task.pass_denoising_data + DENOISING_PASS_NORMAL},
|
||||
{"albedo", task.pass_denoising_data + DENOISING_PASS_ALBEDO},
|
||||
{"output", 0},
|
||||
{ NULL,
|
||||
0 }};
|
||||
|
||||
for (int i = 0; passes[i].name; i++) {
|
||||
const int64_t offset = rtile.offset + rtile.x + rtile.y * rtile.stride;
|
||||
const int64_t buffer_offset = (offset * task.pass_stride + passes[i].offset) * sizeof(float);
|
||||
const int64_t pixel_stride = task.pass_stride * sizeof(float);
|
||||
const int64_t row_stride = rtile.stride * pixel_stride;
|
||||
|
||||
oidn_filter.setImage(passes[i].name,
|
||||
(char *)rtile.buffer + buffer_offset,
|
||||
oidn::Format::Float3,
|
||||
rtile.w,
|
||||
rtile.h,
|
||||
0,
|
||||
pixel_stride,
|
||||
row_stride);
|
||||
}
|
||||
|
||||
/* Execute filter. */
|
||||
oidn_filter.set("hdr", true);
|
||||
oidn_filter.set("srgb", false);
|
||||
oidn_filter.commit();
|
||||
oidn_filter.execute();
|
||||
|
||||
/* todo: it may be possible to avoid this copy, but we have to ensure that
|
||||
* when other code copies data from the device it doesn't overwrite the
|
||||
* denoiser buffers. */
|
||||
rtile.buffers->buffer.copy_to_device();
|
||||
#else
|
||||
(void)task;
|
||||
(void)rtile;
|
||||
#endif
|
||||
}
|
||||
|
||||
void denoise_nlm(DenoisingTask &denoising, RenderTile &tile)
|
||||
{
|
||||
ProfilingHelper profiling(denoising.profiler, PROFILING_DENOISING);
|
||||
|
@ -1018,7 +1087,10 @@ class CPUDevice : public Device {
|
|||
render(task, tile, kg);
|
||||
}
|
||||
else if (tile.task == RenderTile::DENOISE) {
|
||||
if (task.denoising.type == DENOISER_NLM) {
|
||||
if (task.denoising.type == DENOISER_OPENIMAGEDENOISE) {
|
||||
denoise_openimagedenoise(task, tile);
|
||||
}
|
||||
else if (task.denoising.type == DENOISER_NLM) {
|
||||
if (denoising == NULL) {
|
||||
denoising = new DenoisingTask(this, task);
|
||||
denoising->profiler = &kg->profiler;
|
||||
|
@ -1060,16 +1132,22 @@ class CPUDevice : public Device {
|
|||
tile.stride = task.stride;
|
||||
tile.buffers = task.buffers;
|
||||
|
||||
DenoisingTask denoising(this, task);
|
||||
if (task.denoising.type == DENOISER_OPENIMAGEDENOISE) {
|
||||
denoise_openimagedenoise(task, tile);
|
||||
}
|
||||
else {
|
||||
DenoisingTask denoising(this, task);
|
||||
|
||||
ProfilingState denoising_profiler_state;
|
||||
profiler.add_state(&denoising_profiler_state);
|
||||
denoising.profiler = &denoising_profiler_state;
|
||||
ProfilingState denoising_profiler_state;
|
||||
profiler.add_state(&denoising_profiler_state);
|
||||
denoising.profiler = &denoising_profiler_state;
|
||||
|
||||
denoise_nlm(denoising, tile);
|
||||
|
||||
profiler.remove_state(&denoising_profiler_state);
|
||||
}
|
||||
|
||||
denoise_nlm(denoising, tile);
|
||||
task.update_progress(&tile, tile.w * tile.h);
|
||||
|
||||
profiler.remove_state(&denoising_profiler_state);
|
||||
}
|
||||
|
||||
void thread_film_convert(DeviceTask &task)
|
||||
|
@ -1143,10 +1221,17 @@ class CPUDevice : public Device {
|
|||
/* split task into smaller ones */
|
||||
list<DeviceTask> tasks;
|
||||
|
||||
if (task.type == DeviceTask::SHADER)
|
||||
if (task.type == DeviceTask::DENOISE_BUFFER &&
|
||||
task.denoising.type == DENOISER_OPENIMAGEDENOISE) {
|
||||
/* Denoise entire buffer at once with OIDN, it has own threading. */
|
||||
tasks.push_back(task);
|
||||
}
|
||||
else if (task.type == DeviceTask::SHADER) {
|
||||
task.split(tasks, info.cpu_threads, 256);
|
||||
else
|
||||
}
|
||||
else {
|
||||
task.split(tasks, info.cpu_threads);
|
||||
}
|
||||
|
||||
foreach (DeviceTask &task, tasks) {
|
||||
task_pool.push([=] {
|
||||
|
@ -1351,6 +1436,9 @@ void device_cpu_info(vector<DeviceInfo> &devices)
|
|||
info.has_half_images = true;
|
||||
info.has_profiling = true;
|
||||
info.denoisers = DENOISER_NLM;
|
||||
if (openimagedenoise_supported()) {
|
||||
info.denoisers |= DENOISER_OPENIMAGEDENOISE;
|
||||
}
|
||||
|
||||
devices.insert(devices.begin(), info);
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ class Tile;
|
|||
enum DenoiserType {
|
||||
DENOISER_NLM = 1,
|
||||
DENOISER_OPTIX = 2,
|
||||
DENOISER_OPENIMAGEDENOISE = 4,
|
||||
DENOISER_NUM,
|
||||
|
||||
DENOISER_NONE = 0,
|
||||
|
|
|
@ -1086,7 +1086,7 @@ void Session::update_status_time(bool show_pause, bool show_done)
|
|||
*/
|
||||
substatus += string_printf(", Sample %d/%d", progress.get_current_sample(), num_samples);
|
||||
}
|
||||
if (params.denoising.use) {
|
||||
if (params.denoising.use && params.denoising.type != DENOISER_OPENIMAGEDENOISE) {
|
||||
substatus += string_printf(", Denoised %d tiles", progress.get_denoised_tiles());
|
||||
}
|
||||
else if (params.denoising.store_passes && params.denoising.type == DENOISER_NLM) {
|
||||
|
|
|
@ -86,6 +86,7 @@ set(SRC_HEADERS
|
|||
util_math_matrix.h
|
||||
util_md5.h
|
||||
util_murmurhash.h
|
||||
util_openimagedenoise.h
|
||||
util_opengl.h
|
||||
util_optimization.h
|
||||
util_param.h
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright 2011-2013 Blender Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef __UTIL_OPENIMAGEDENOISE_H__
|
||||
#define __UTIL_OPENIMAGEDENOISE_H__
|
||||
|
||||
#ifdef WITH_OPENIMAGEDENOISE
|
||||
# include <OpenImageDenoise/oidn.hpp>
|
||||
#endif
|
||||
|
||||
#include "util_system.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
static inline bool openimagedenoise_supported()
|
||||
{
|
||||
#ifdef WITH_OPENIMAGEDENOISE
|
||||
return system_cpu_support_sse41();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
||||
#endif /* __UTIL_OPENIMAGEDENOISE_H__ */
|
Loading…
Reference in New Issue