Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--intern/cycles/blender/CMakeLists.txt1
-rw-r--r--intern/cycles/blender/addon/__init__.py6
-rw-r--r--intern/cycles/blender/addon/operators.py133
-rw-r--r--intern/cycles/blender/addon/properties.py6
-rw-r--r--intern/cycles/blender/addon/ui.py23
-rw-r--r--intern/cycles/blender/blender_python.cpp121
-rw-r--r--intern/cycles/blender/blender_session.cpp8
-rw-r--r--intern/cycles/device/device_denoising.cpp10
-rw-r--r--intern/cycles/device/device_task.h31
-rw-r--r--intern/cycles/render/CMakeLists.txt2
-rw-r--r--intern/cycles/render/denoising.cpp887
-rw-r--r--intern/cycles/render/denoising.h201
-rw-r--r--intern/cycles/render/session.cpp5
-rw-r--r--intern/cycles/render/session.h9
-rw-r--r--intern/cycles/util/util_system.cpp20
-rw-r--r--intern/cycles/util/util_system.h3
16 files changed, 1418 insertions, 48 deletions
diff --git a/intern/cycles/blender/CMakeLists.txt b/intern/cycles/blender/CMakeLists.txt
index 769bc788ab5..84e2690333e 100644
--- a/intern/cycles/blender/CMakeLists.txt
+++ b/intern/cycles/blender/CMakeLists.txt
@@ -41,6 +41,7 @@ set(SRC
set(ADDON_FILES
addon/__init__.py
addon/engine.py
+ addon/operators.py
addon/osl.py
addon/presets.py
addon/properties.py
diff --git a/intern/cycles/blender/addon/__init__.py b/intern/cycles/blender/addon/__init__.py
index 038126278aa..1f148538328 100644
--- a/intern/cycles/blender/addon/__init__.py
+++ b/intern/cycles/blender/addon/__init__.py
@@ -37,6 +37,8 @@ if "bpy" in locals():
importlib.reload(version_update)
if "ui" in locals():
importlib.reload(ui)
+ if "operators" in locals():
+ importlib.reload(operators)
if "properties" in locals():
importlib.reload(properties)
if "presets" in locals():
@@ -118,6 +120,7 @@ classes = (
def register():
from bpy.utils import register_class
from . import ui
+ from . import operators
from . import properties
from . import presets
import atexit
@@ -130,6 +133,7 @@ def register():
properties.register()
ui.register()
+ operators.register()
presets.register()
for cls in classes:
@@ -141,6 +145,7 @@ def register():
def unregister():
from bpy.utils import unregister_class
from . import ui
+ from . import operators
from . import properties
from . import presets
import atexit
@@ -148,6 +153,7 @@ def unregister():
bpy.app.handlers.version_update.remove(version_update.do_versions)
ui.unregister()
+ operators.unregister()
properties.unregister()
presets.unregister()
diff --git a/intern/cycles/blender/addon/operators.py b/intern/cycles/blender/addon/operators.py
new file mode 100644
index 00000000000..c39aa386203
--- /dev/null
+++ b/intern/cycles/blender/addon/operators.py
@@ -0,0 +1,133 @@
+#
+# Copyright 2011-2019 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.
+#
+
+# <pep8 compliant>
+
+import bpy
+from bpy.types import Operator
+from bpy.props import StringProperty
+
+
+class CYCLES_OT_use_shading_nodes(Operator):
+ """Enable nodes on a material, world or light"""
+ bl_idname = "cycles.use_shading_nodes"
+ bl_label = "Use Nodes"
+
+ @classmethod
+ def poll(cls, context):
+ return (getattr(context, "material", False) or getattr(context, "world", False) or
+ getattr(context, "light", False))
+
+ def execute(self, context):
+ if context.material:
+ context.material.use_nodes = True
+ elif context.world:
+ context.world.use_nodes = True
+ elif context.light:
+ context.light.use_nodes = True
+
+ return {'FINISHED'}
+
+
+class CYCLES_OT_denoise_animation(Operator):
+ """Denoise rendered animation sequence using current scene and view """ \
+ """layer settings. Requires denoising data passes and output to """ \
+ """OpenEXR multilayer files"""
+ bl_idname = "cycles.denoise_animation"
+ bl_label = "Denoise Animation"
+
+ input_filepath = StringProperty(
+ name='Input Filepath',
+ description='File path for frames to denoise. If not specified, uses the render file path from the scene',
+ default='',
+ subtype='FILE_PATH')
+
+ output_filepath = StringProperty(
+ name='Output Filepath',
+ description='If not specified, renders will be denoised in-place',
+ default='',
+ subtype='FILE_PATH')
+
+ def execute(self, context):
+ import os
+
+ preferences = context.user_preferences
+ scene = context.scene
+ render_layer = scene.render.layers.active
+
+ in_filepath = self.input_filepath
+ out_filepath = self.output_filepath
+
+ if in_filepath == '':
+ in_filepath = scene.render.filepath
+ if out_filepath == '':
+ out_filepath = in_filepath
+
+ # Backup since we will overwrite the scene path temporarily
+ original_filepath = scene.render.filepath
+
+ # Expand filepaths for each frame so we match Blender render output exactly.
+ in_filepaths = []
+ out_filepaths = []
+
+ for frame in range(scene.frame_start, scene.frame_end + 1):
+ scene.render.filepath = in_filepath
+ filepath = scene.render.frame_path(frame=frame)
+ in_filepaths.append(filepath)
+
+ if not os.path.isfile(filepath):
+ scene.render.filepath = original_filepath
+ self.report({'ERROR'}, f"Frame '{filepath}' not found, animation must be complete.")
+ return {'CANCELLED'}
+
+ scene.render.filepath = out_filepath
+ filepath = scene.render.frame_path(frame=frame)
+ out_filepaths.append(filepath)
+
+ scene.render.filepath = original_filepath
+
+ # Run denoiser
+ # TODO: support cancel and progress reports.
+ import _cycles
+ try:
+ _cycles.denoise(preferences.as_pointer(),
+ scene.as_pointer(),
+ render_layer.as_pointer(),
+ input=in_filepaths,
+ output=out_filepaths)
+ except Exception as e:
+ self.report({'ERROR'}, str(e))
+ return {'FINISHED'}
+
+ self.report({'INFO'}, "Denoising completed.")
+ return {'FINISHED'}
+
+
+classes = (
+ CYCLES_OT_use_shading_nodes,
+ CYCLES_OT_denoise_animation
+)
+
+def register():
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
+
+
+def unregister():
+ from bpy.utils import unregister_class
+ for cls in classes:
+ unregister_class(cls)
diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py
index 23ab1cf6a30..1106923f529 100644
--- a/intern/cycles/blender/addon/properties.py
+++ b/intern/cycles/blender/addon/properties.py
@@ -1344,6 +1344,12 @@ class CyclesRenderLayerSettings(bpy.types.PropertyGroup):
default=False,
update=update_render_passes,
)
+ denoising_neighbor_frames: IntProperty(
+ name="Neighbor Frames",
+ description="Number of neighboring frames to use for denoising animations (more frames produce smoother results at the cost of performance)",
+ min=0, max=7,
+ default=0,
+ )
cls.use_pass_crypto_object = BoolProperty(
name="Cryptomatte Object",
description="Render cryptomatte object pass, for isolating objects in compositing",
diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py
index 2f1adfe4178..e372843d763 100644
--- a/intern/cycles/blender/addon/ui.py
+++ b/intern/cycles/blender/addon/ui.py
@@ -22,7 +22,6 @@ import _cycles
from bpy.types import (
Panel,
Menu,
- Operator,
)
@@ -912,27 +911,6 @@ class CYCLES_OBJECT_PT_cycles_settings(CyclesButtonsPanel, Panel):
sub.prop(cob, "use_distance_cull")
-class CYCLES_OT_use_shading_nodes(Operator):
- """Enable nodes on a material, world or lamp"""
- bl_idname = "cycles.use_shading_nodes"
- bl_label = "Use Nodes"
-
- @classmethod
- def poll(cls, context):
- return (getattr(context, "material", False) or getattr(context, "world", False) or
- getattr(context, "lamp", False))
-
- def execute(self, context):
- if context.material:
- context.material.use_nodes = True
- elif context.world:
- context.world.use_nodes = True
- elif context.lamp:
- context.lamp.use_nodes = True
-
- return {'FINISHED'}
-
-
def find_node(material, nodetype):
if material and material.node_tree:
ntree = material.node_tree
@@ -1870,7 +1848,6 @@ classes = (
CYCLES_PT_context_material,
CYCLES_OBJECT_PT_motion_blur,
CYCLES_OBJECT_PT_cycles_settings,
- CYCLES_OT_use_shading_nodes,
CYCLES_LAMP_PT_preview,
CYCLES_LAMP_PT_lamp,
CYCLES_LAMP_PT_nodes,
diff --git a/intern/cycles/blender/blender_python.cpp b/intern/cycles/blender/blender_python.cpp
index 9dd0cd4c0bc..bf7605ed5b1 100644
--- a/intern/cycles/blender/blender_python.cpp
+++ b/intern/cycles/blender/blender_python.cpp
@@ -18,9 +18,12 @@
#include "blender/CCL_api.h"
+#include "blender/blender_device.h"
#include "blender/blender_sync.h"
#include "blender/blender_session.h"
+#include "render/denoising.h"
+
#include "util/util_debug.h"
#include "util/util_foreach.h"
#include "util/util_logging.h"
@@ -623,6 +626,121 @@ static PyObject *opencl_disable_func(PyObject * /*self*/, PyObject * /*value*/)
}
#endif
+static bool denoise_parse_filepaths(PyObject *pyfilepaths, vector<string>& filepaths)
+{
+
+ if(PyUnicode_Check(pyfilepaths)) {
+ const char *filepath = PyUnicode_AsUTF8(pyfilepaths);
+ filepaths.push_back(filepath);
+ return true;
+ }
+
+ PyObject *sequence = PySequence_Fast(pyfilepaths, "File paths must be a string or sequence of strings");
+ if(sequence == NULL) {
+ return false;
+ }
+
+ for(Py_ssize_t i = 0; i < PySequence_Fast_GET_SIZE(sequence); i++) {
+ PyObject *item = PySequence_Fast_GET_ITEM(sequence, i);
+ const char *filepath = PyUnicode_AsUTF8(item);
+ if(filepath == NULL) {
+ PyErr_SetString(PyExc_ValueError, "File paths must be a string or sequence of strings.");
+ Py_DECREF(sequence);
+ return false;
+ }
+ filepaths.push_back(filepath);
+ }
+ Py_DECREF(sequence);
+
+ return true;
+}
+
+static PyObject *denoise_func(PyObject * /*self*/, PyObject *args, PyObject *keywords)
+{
+ static const char *keyword_list[] = {"preferences", "scene", "view_layer",
+ "input", "output",
+ "tile_size", "samples", NULL};
+ PyObject *pypreferences, *pyscene, *pyrenderlayer;
+ PyObject *pyinput, *pyoutput = NULL;
+ int tile_size = 0, samples = 0;
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords, "OOOO|Oii", (char**)keyword_list,
+ &pypreferences, &pyscene, &pyrenderlayer,
+ &pyinput, &pyoutput,
+ &tile_size, &samples)) {
+ return NULL;
+ }
+
+ /* Get device specification from preferences and scene. */
+ PointerRNA preferencesptr;
+ RNA_pointer_create(NULL, &RNA_UserPreferences, (void*)PyLong_AsVoidPtr(pypreferences), &preferencesptr);
+ BL::UserPreferences b_preferences(preferencesptr);
+
+ PointerRNA sceneptr;
+ RNA_id_pointer_create((ID*)PyLong_AsVoidPtr(pyscene), &sceneptr);
+ BL::Scene b_scene(sceneptr);
+
+ DeviceInfo device = blender_device_info(b_preferences, b_scene, true);
+
+ /* Get denoising parameters from view layer. */
+ PointerRNA renderlayerptr;
+ RNA_pointer_create((ID*)PyLong_AsVoidPtr(pyscene), &RNA_SceneRenderLayer, PyLong_AsVoidPtr(pyrenderlayer), &renderlayerptr);
+ PointerRNA crenderlayer = RNA_pointer_get(&renderlayerptr, "cycles");
+
+ DenoiseParams params;
+ params.radius = get_int(crenderlayer, "denoising_radius");
+ params.strength = get_float(crenderlayer, "denoising_strength");
+ params.feature_strength = get_float(crenderlayer, "denoising_feature_strength");
+ params.relative_pca = get_boolean(crenderlayer, "denoising_relative_pca");
+ params.neighbor_frames = get_int(crenderlayer, "denoising_neighbor_frames");
+
+ /* Parse file paths list. */
+ vector<string> input, output;
+
+ if(!denoise_parse_filepaths(pyinput, input)) {
+ return NULL;
+ }
+
+ if(pyoutput) {
+ if(!denoise_parse_filepaths(pyoutput, output)) {
+ return NULL;
+ }
+ }
+ else {
+ output = input;
+ }
+
+ if(input.empty()) {
+ PyErr_SetString(PyExc_ValueError, "No input file paths specified.");
+ return NULL;
+ }
+ if(input.size() != output.size()) {
+ PyErr_SetString(PyExc_ValueError, "Number of input and output file paths does not match.");
+ return NULL;
+ }
+
+ /* Create denoiser. */
+ Denoiser denoiser(device);
+ denoiser.params = params;
+ denoiser.input = input;
+ denoiser.output = output;
+
+ if (tile_size > 0) {
+ denoiser.tile_size = make_int2(tile_size, tile_size);
+ }
+ if (samples > 0) {
+ denoiser.samples_override = samples;
+ }
+
+ /* Run denoiser. */
+ if(!denoiser.run()) {
+ PyErr_SetString(PyExc_ValueError, denoiser.error.c_str());
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
static PyObject *debug_flags_update_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pyscene;
@@ -783,6 +901,9 @@ static PyMethodDef methods[] = {
{"opencl_disable", opencl_disable_func, METH_NOARGS, ""},
#endif
+ /* Standalone denoising */
+ {"denoise", (PyCFunction)denoise_func, METH_VARARGS|METH_KEYWORDS, ""},
+
/* Debugging routines */
{"debug_flags_update", debug_flags_update_func, METH_VARARGS, ""},
{"debug_flags_reset", debug_flags_reset_func, METH_NOARGS, ""},
diff --git a/intern/cycles/blender/blender_session.cpp b/intern/cycles/blender/blender_session.cpp
index 50ac35069a9..43d7ff49a3b 100644
--- a/intern/cycles/blender/blender_session.cpp
+++ b/intern/cycles/blender/blender_session.cpp
@@ -431,10 +431,10 @@ void BlenderSession::render()
session->params.run_denoising = run_denoising;
session->params.full_denoising = full_denoising;
session->params.write_denoising_passes = write_denoising_passes;
- session->params.denoising_radius = get_int(crl, "denoising_radius");
- session->params.denoising_strength = get_float(crl, "denoising_strength");
- session->params.denoising_feature_strength = get_float(crl, "denoising_feature_strength");
- session->params.denoising_relative_pca = get_boolean(crl, "denoising_relative_pca");
+ session->params.denoising.radius = get_int(crl, "denoising_radius");
+ session->params.denoising.strength = get_float(crl, "denoising_strength");
+ session->params.denoising.feature_strength = get_float(crl, "denoising_feature_strength");
+ session->params.denoising.relative_pca = get_boolean(crl, "denoising_relative_pca");
scene->film->denoising_data_pass = buffer_params.denoising_data_pass;
scene->film->denoising_clean_pass = buffer_params.denoising_clean_pass;
diff --git a/intern/cycles/device/device_denoising.cpp b/intern/cycles/device/device_denoising.cpp
index 61e0ba47ab8..1bb144ef85a 100644
--- a/intern/cycles/device/device_denoising.cpp
+++ b/intern/cycles/device/device_denoising.cpp
@@ -27,13 +27,13 @@ DenoisingTask::DenoisingTask(Device *device, const DeviceTask &task)
buffer(device),
device(device)
{
- radius = task.denoising_radius;
- nlm_k_2 = powf(2.0f, lerp(-5.0f, 3.0f, task.denoising_strength));
- if(task.denoising_relative_pca) {
- pca_threshold = -powf(10.0f, lerp(-8.0f, 0.0f, task.denoising_feature_strength));
+ radius = task.denoising.radius;
+ nlm_k_2 = powf(2.0f, lerp(-5.0f, 3.0f, task.denoising.strength));
+ if(task.denoising.relative_pca) {
+ pca_threshold = -powf(10.0f, lerp(-8.0f, 0.0f, task.denoising.feature_strength));
}
else {
- pca_threshold = powf(10.0f, lerp(-5.0f, 3.0f, task.denoising_feature_strength));
+ pca_threshold = powf(10.0f, lerp(-5.0f, 3.0f, task.denoising.feature_strength));
}
render_buffer.frame_stride = task.frame_stride;
diff --git a/intern/cycles/device/device_task.h b/intern/cycles/device/device_task.h
index 2871bc5761a..f1fd4246868 100644
--- a/intern/cycles/device/device_task.h
+++ b/intern/cycles/device/device_task.h
@@ -32,6 +32,32 @@ class RenderBuffers;
class RenderTile;
class Tile;
+class DenoiseParams {
+public:
+ /* Pixel radius for neighbouring pixels to take into account. */
+ int radius;
+ /* Controls neighbor pixel weighting for the denoising filter. */
+ float strength;
+ /* Preserve more or less detail based on feature passes. */
+ float feature_strength;
+ /* When removing pixels that don't carry information, use a relative threshold instead of an absolute one. */
+ bool relative_pca;
+ /* How many frames before and after the current center frame are included. */
+ int neighbor_frames;
+ /* Clamp the input to the range of +-1e8. Should be enough for any legitimate data. */
+ bool clamp_input;
+
+ DenoiseParams()
+ {
+ radius = 8;
+ strength = 0.5f;
+ feature_strength = 0.5f;
+ relative_pca = false;
+ neighbor_frames = 2;
+ clamp_input = true;
+ }
+};
+
class DeviceTask : public Task {
public:
typedef enum { RENDER, FILM_CONVERT, SHADER } Type;
@@ -68,10 +94,7 @@ public:
function<void(RenderTile*, Device*)> map_neighbor_tiles;
function<void(RenderTile*, Device*)> unmap_neighbor_tiles;
- int denoising_radius;
- float denoising_strength;
- float denoising_feature_strength;
- bool denoising_relative_pca;
+ DenoiseParams denoising;
bool denoising_from_render;
vector<int> denoising_frames;
diff --git a/intern/cycles/render/CMakeLists.txt b/intern/cycles/render/CMakeLists.txt
index c0ce7368771..e6afbc50463 100644
--- a/intern/cycles/render/CMakeLists.txt
+++ b/intern/cycles/render/CMakeLists.txt
@@ -16,6 +16,7 @@ set(SRC
camera.cpp
constant_fold.cpp
coverage.cpp
+ denoising.cpp
film.cpp
graph.cpp
image.cpp
@@ -48,6 +49,7 @@ set(SRC_HEADERS
camera.h
constant_fold.h
coverage.h
+ denoising.h
film.h
graph.h
image.h
diff --git a/intern/cycles/render/denoising.cpp b/intern/cycles/render/denoising.cpp
new file mode 100644
index 00000000000..0016a067cf9
--- /dev/null
+++ b/intern/cycles/render/denoising.cpp
@@ -0,0 +1,887 @@
+/*
+ * Copyright 2011-2018 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.
+ */
+
+#include "render/denoising.h"
+
+#include "kernel/filter/filter_defines.h"
+
+#include "util/util_foreach.h"
+#include "util/util_map.h"
+#include "util/util_system.h"
+#include "util/util_time.h"
+
+#include <OpenImageIO/filesystem.h>
+
+CCL_NAMESPACE_BEGIN
+
+/* Utility Functions */
+
+static void print_progress(int num, int total, int frame, int num_frames)
+{
+ const char *label = "Denoise Frame ";
+ int cols = system_console_width();
+
+ cols -= strlen(label);
+
+ int len = 1;
+ for(int x = total; x > 9; x /= 10) {
+ len++;
+ }
+
+ int bars = cols - 2*len - 6;
+
+ printf("\r%s", label);
+
+ if(num_frames > 1) {
+ int frame_len = 1;
+ for(int x = num_frames - 1; x > 9; x /= 10) {
+ frame_len++;
+ }
+ bars -= frame_len + 2;
+ printf("%*d ", frame_len, frame);
+ }
+
+ int v = int(float(num)*bars/total);
+ printf("[");
+ for(int i = 0; i < v; i++) {
+ printf("=");
+ }
+ if(v < bars) {
+ printf(">");
+ }
+ for(int i = v+1; i < bars; i++) {
+ printf(" ");
+ }
+ printf(string_printf("] %%%dd / %d", len, total).c_str(), num);
+ fflush(stdout);
+}
+
+/* Splits in at its last dot, setting suffix to the part after the dot and in to the part before it.
+ * Returns whether a dot was found. */
+static bool split_last_dot(string &in, string &suffix)
+{
+ size_t pos = in.rfind(".");
+ if(pos == string::npos) {
+ return false;
+ }
+ suffix = in.substr(pos+1);
+ in = in.substr(0, pos);
+ return true;
+}
+
+/* Separate channel names as generated by Blender.
+ * If views is true:
+ * Inputs are expected in the form RenderLayer.Pass.View.Channel, sets renderlayer to "RenderLayer.View"
+ * Otherwise:
+ * Inputs are expected in the form RenderLayer.Pass.Channel */
+static bool parse_channel_name(string name, string &renderlayer, string &pass, string &channel, bool multiview_channels)
+{
+ if(!split_last_dot(name, channel)) {
+ return false;
+ }
+ string view;
+ if(multiview_channels && !split_last_dot(name, view)) {
+ return false;
+ }
+ if(!split_last_dot(name, pass)) {
+ return false;
+ }
+ renderlayer = name;
+
+ if(multiview_channels) {
+ renderlayer += "." + view;
+ }
+
+ return true;
+}
+
+/* Channel Mapping */
+
+struct ChannelMapping {
+ int channel;
+ string name;
+};
+
+static void fill_mapping(vector<ChannelMapping> &map, int pos, string name, string channels)
+{
+ for(const char *chan = channels.c_str(); *chan; chan++) {
+ map.push_back({pos++, name + "." + *chan});
+ }
+}
+
+static const int INPUT_NUM_CHANNELS = 15;
+static vector<ChannelMapping> init_input_channels()
+{
+ vector<ChannelMapping> map;
+ fill_mapping(map, 0, "Denoising Depth", "Z");
+ fill_mapping(map, 1, "Denoising Normal", "XYZ");
+ fill_mapping(map, 4, "Denoising Shadowing", "X");
+ fill_mapping(map, 5, "Denoising Albedo", "RGB");
+ fill_mapping(map, 8, "Noisy Image", "RGB");
+ fill_mapping(map, 11, "Denoising Variance", "RGB");
+ fill_mapping(map, 14, "Denoising Intensity", "X");
+ return map;
+}
+
+static const int OUTPUT_NUM_CHANNELS = 3;
+static vector<ChannelMapping> init_output_channels()
+{
+ vector<ChannelMapping> map;
+ fill_mapping(map, 0, "Combined", "RGB");
+ return map;
+}
+
+static const vector<ChannelMapping> input_channels = init_input_channels();
+static const vector<ChannelMapping> output_channels = init_output_channels();
+
+/* Renderlayer Handling */
+
+bool DenoiseImageLayer::detect_denoising_channels()
+{
+ /* Map device input to image channels. */
+ input_to_image_channel.clear();
+ input_to_image_channel.resize(INPUT_NUM_CHANNELS, -1);
+
+ foreach(const ChannelMapping& mapping, input_channels) {
+ vector<string>::iterator i = find(channels.begin(), channels.end(), mapping.name);
+ if(i == channels.end()) {
+ return false;
+ }
+
+ size_t input_channel = mapping.channel;
+ size_t layer_channel = i - channels.begin();
+ input_to_image_channel[input_channel] = layer_to_image_channel[layer_channel];
+ }
+
+ /* Map device output to image channels. */
+ output_to_image_channel.clear();
+ output_to_image_channel.resize(OUTPUT_NUM_CHANNELS, -1);
+
+ foreach(const ChannelMapping& mapping, output_channels) {
+ vector<string>::iterator i = find(channels.begin(), channels.end(), mapping.name);
+ if(i == channels.end()) {
+ return false;
+ }
+
+ size_t output_channel = mapping.channel;
+ size_t layer_channel = i - channels.begin();
+ output_to_image_channel[output_channel] = layer_to_image_channel[layer_channel];
+ }
+
+ /* Check that all buffer channels are correctly set. */
+ for(int i = 0; i < INPUT_NUM_CHANNELS; i++) {
+ assert(input_to_image_channel[i] >= 0);
+ }
+ for(int i = 0; i < OUTPUT_NUM_CHANNELS; i++) {
+ assert(output_to_image_channel[i] >= 0);
+ }
+
+ return true;
+}
+
+bool DenoiseImageLayer::match_channels(int neighbor,
+ const std::vector<string> &channelnames,
+ const std::vector<string> &neighbor_channelnames)
+{
+ neighbor_input_to_image_channel.resize(neighbor + 1);
+ vector<int>& mapping = neighbor_input_to_image_channel[neighbor];
+
+ assert(mapping.size() == 0);
+ mapping.resize(input_to_image_channel.size(), -1);
+
+ for(int i = 0; i < input_to_image_channel.size(); i++) {
+ const string& channel = channelnames[input_to_image_channel[i]];
+ std::vector<string>::const_iterator frame_channel = find(neighbor_channelnames.begin(), neighbor_channelnames.end(), channel);
+
+ if(frame_channel == neighbor_channelnames.end()) {
+ return false;
+ }
+
+ mapping[i] = frame_channel - neighbor_channelnames.begin();
+ }
+
+ return true;
+}
+
+/* Denoise Task */
+
+DenoiseTask::DenoiseTask(Device *device, Denoiser *denoiser, int frame, const vector<int>& neighbor_frames)
+: denoiser(denoiser),
+ device(device),
+ frame(frame),
+ neighbor_frames(neighbor_frames),
+ current_layer(0),
+ input_pixels(device, "filter input buffer", MEM_READ_ONLY),
+ num_tiles(0)
+{
+ image.samples = denoiser->samples_override;
+}
+
+DenoiseTask::~DenoiseTask()
+{
+ free();
+}
+
+/* Device callbacks */
+
+bool DenoiseTask::acquire_tile(Device *device, Device *tile_device, RenderTile &tile)
+{
+ thread_scoped_lock tile_lock(tiles_mutex);
+
+ if(tiles.empty()) {
+ return false;
+ }
+
+ tile = tiles.front();
+ tiles.pop_front();
+
+ device->map_tile(tile_device, tile);
+
+ print_progress(num_tiles - tiles.size(), num_tiles, frame, denoiser->num_frames);
+
+ return true;
+}
+
+/* Mapping tiles is required for regular rendering since each tile has its separate memory
+ * which may be allocated on a different device.
+ * For standalone denoising, there is a single memory that is present on all devices, so the only
+ * thing that needs to be done here is to specify the surrounding tile geometry.
+ *
+ * However, since there is only one large memory, the denoised result has to be written to
+ * a different buffer to avoid having to copy an entire horizontal slice of the image. */
+void DenoiseTask::map_neighboring_tiles(RenderTile *tiles, Device *tile_device)
+{
+ for(int i = 0; i < 9; i++) {
+ if(i == 4) {
+ continue;
+ }
+
+ int dx = (i%3)-1;
+ int dy = (i/3)-1;
+ tiles[i].x = clamp(tiles[4].x + dx *denoiser->tile_size.x, 0, image.width);
+ tiles[i].w = clamp(tiles[4].x + (dx+1)*denoiser->tile_size.x, 0, image.width) - tiles[i].x;
+ tiles[i].y = clamp(tiles[4].y + dy *denoiser->tile_size.y, 0, image.height);
+ tiles[i].h = clamp(tiles[4].y + (dy+1)*denoiser->tile_size.y, 0, image.height) - tiles[i].y;
+
+ tiles[i].buffer = tiles[4].buffer;
+ tiles[i].offset = tiles[4].offset;
+ tiles[i].stride = image.width;
+ }
+
+ device_vector<float> *output_mem = new device_vector<float>(tile_device, "denoising_output", MEM_READ_WRITE);
+ output_mem->alloc(OUTPUT_NUM_CHANNELS*tiles[4].w*tiles[4].h);
+ output_mem->zero_to_device();
+
+ tiles[9] = tiles[4];
+ tiles[9].buffer = output_mem->device_pointer;
+ tiles[9].stride = tiles[9].w;
+ tiles[9].offset -= tiles[9].x + tiles[9].y*tiles[9].stride;
+
+ thread_scoped_lock output_lock(output_mutex);
+ assert(output_pixels.count(tiles[4].tile_index) == 0);
+ output_pixels[tiles[9].tile_index] = output_mem;
+}
+
+void DenoiseTask::unmap_neighboring_tiles(RenderTile *tiles)
+{
+ thread_scoped_lock output_lock(output_mutex);
+ assert(output_pixels.count(tiles[4].tile_index) == 1);
+ device_vector<float> *output_mem = output_pixels[tiles[9].tile_index];
+ output_pixels.erase(tiles[4].tile_index);
+ output_lock.unlock();
+
+ output_mem->copy_from_device(0, OUTPUT_NUM_CHANNELS*tiles[9].w, tiles[9].h);
+
+ float *result = output_mem->data();
+ float *out = &image.pixels[image.num_channels*(tiles[9].y*image.width + tiles[9].x)];
+
+ const DenoiseImageLayer& layer = image.layers[current_layer];
+ const int *output_to_image_channel = layer.output_to_image_channel.data();
+
+ for(int y = 0; y < tiles[9].h; y++) {
+ for(int x = 0; x < tiles[9].w; x++, result += OUTPUT_NUM_CHANNELS) {
+ for(int i = 0; i < OUTPUT_NUM_CHANNELS; i++) {
+ out[image.num_channels*x + output_to_image_channel[i]] = result[i];
+ }
+ }
+ out += image.num_channels * image.width;
+ }
+
+ output_mem->free();
+ delete output_mem;
+}
+
+void DenoiseTask::release_tile()
+{
+}
+
+bool DenoiseTask::get_cancel()
+{
+ return false;
+}
+
+void DenoiseTask::create_task(DeviceTask& task)
+{
+ /* Callback functions. */
+ task.acquire_tile = function_bind(&DenoiseTask::acquire_tile, this, device, _1, _2);
+ task.map_neighbor_tiles = function_bind(&DenoiseTask::map_neighboring_tiles, this, _1, _2);
+ task.unmap_neighbor_tiles = function_bind(&DenoiseTask::unmap_neighboring_tiles, this, _1);
+ task.release_tile = function_bind(&DenoiseTask::release_tile, this);
+ task.get_cancel = function_bind(&DenoiseTask::get_cancel, this);
+
+ /* Denoising parameters. */
+ task.denoising = denoiser->params;
+ task.denoising_do_filter = true;
+ task.denoising_write_passes = false;
+ task.denoising_from_render = false;
+
+ task.denoising_frames.resize(neighbor_frames.size());
+ for(int i = 0; i < neighbor_frames.size(); i++) {
+ task.denoising_frames[i] = neighbor_frames[i] - frame;
+ }
+
+ /* Buffer parameters. */
+ task.pass_stride = INPUT_NUM_CHANNELS;
+ task.target_pass_stride = OUTPUT_NUM_CHANNELS;
+ task.pass_denoising_data = 0;
+ task.pass_denoising_clean = -1;
+ task.frame_stride = image.width * image.height * INPUT_NUM_CHANNELS;
+
+ /* Create tiles. */
+ thread_scoped_lock tile_lock(tiles_mutex);
+ thread_scoped_lock output_lock(output_mutex);
+
+ tiles.clear();
+ assert(output_pixels.empty());
+ output_pixels.clear();
+
+ int tiles_x = divide_up(image.width, denoiser->tile_size.x);
+ int tiles_y = divide_up(image.height, denoiser->tile_size.y);
+
+ for(int ty = 0; ty < tiles_y; ty++) {
+ for(int tx = 0; tx < tiles_x; tx++) {
+ RenderTile tile;
+ tile.x = tx * denoiser->tile_size.x;
+ tile.y = ty * denoiser->tile_size.y;
+ tile.w = min(image.width - tile.x, denoiser->tile_size.x);
+ tile.h = min(image.height - tile.y, denoiser->tile_size.y);
+ tile.start_sample = 0;
+ tile.num_samples = image.layers[current_layer].samples;
+ tile.sample = 0;
+ tile.offset = 0;
+ tile.stride = image.width;
+ tile.tile_index = ty*tiles_x + tx;
+ tile.task = RenderTile::DENOISE;
+ tile.buffers = NULL;
+ tile.buffer = input_pixels.device_pointer;
+ tiles.push_back(tile);
+ }
+ }
+
+ num_tiles = tiles.size();
+}
+
+/* Denoiser Operations */
+
+bool DenoiseTask::load_input_pixels(int layer)
+{
+ int w = image.width;
+ int h = image.height;
+ int num_pixels = image.width * image.height;
+ int frame_stride = num_pixels * INPUT_NUM_CHANNELS;
+
+ /* Load center image */
+ DenoiseImageLayer& image_layer = image.layers[layer];
+
+ float *buffer_data = input_pixels.data();
+ image.read_pixels(image_layer, buffer_data);
+ buffer_data += frame_stride;
+
+ /* Load neighbor images */
+ for(int i = 0; i < image.in_neighbors.size(); i++) {
+ if(!image.read_neighbor_pixels(i, image_layer, buffer_data)) {
+ error = "Failed to read neighbor frame pixels";
+ return false;
+ }
+ buffer_data += frame_stride;
+ }
+
+ /* Preprocess */
+ buffer_data = input_pixels.data();
+ for(int neighbor = 0; neighbor < image.in_neighbors.size() + 1; neighbor++) {
+ /* Clamp */
+ if(denoiser->params.clamp_input) {
+ for(int i = 0; i < num_pixels*INPUT_NUM_CHANNELS; i++) {
+ buffer_data[i] = clamp(buffer_data[i], -1e8f, 1e8f);
+ }
+ }
+
+ /* Box blur */
+ int r = 5 * denoiser->params.radius;
+ float *data = buffer_data + 14;
+ array<float> temp(num_pixels);
+
+ for(int y = 0; y < h; y++) {
+ for(int x = 0; x < w; x++) {
+ int n = 0;
+ float sum = 0.0f;
+ for(int dx = max(x - r, 0); dx < min(x + r + 1, w); dx++, n++) {
+ sum += data[INPUT_NUM_CHANNELS * (y * w + dx)];
+ }
+ temp[y * w + x] = sum/n;
+ }
+ }
+
+ for(int y = 0; y < h; y++) {
+ for(int x = 0; x < w; x++) {
+ int n = 0;
+ float sum = 0.0f;
+
+ for(int dy = max(y - r, 0); dy < min(y + r + 1, h); dy++, n++) {
+ sum += temp[dy * w + x];
+ }
+
+ data[INPUT_NUM_CHANNELS * (y * w + x)] = sum/n;
+ }
+ }
+
+ buffer_data += frame_stride;
+ }
+
+ /* Copy to device */
+ input_pixels.copy_to_device();
+
+ return true;
+}
+
+/* Task stages */
+
+bool DenoiseTask::load()
+{
+ string center_filepath = denoiser->input[frame];
+ if(!image.load(center_filepath, error)) {
+ return false;
+ }
+
+ if(!image.load_neighbors(denoiser->input, neighbor_frames, error)) {
+ return false;
+ }
+
+ if(image.layers.empty()) {
+ error = "No image layers found to denoise in " + center_filepath;
+ return false;
+ }
+
+ /* Allocate device buffer. */
+ int num_frames = image.in_neighbors.size() + 1;
+ input_pixels.alloc(image.width * INPUT_NUM_CHANNELS, image.height * num_frames);
+ input_pixels.zero_to_device();
+
+ /* Read pixels for first layer. */
+ current_layer = 0;
+ if(!load_input_pixels(current_layer)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool DenoiseTask::exec()
+{
+ for(current_layer = 0; current_layer < image.layers.size(); current_layer++) {
+ /* Read pixels for secondary layers, first was already loaded. */
+ if(current_layer > 0) {
+ if(!load_input_pixels(current_layer)) {
+ return false;
+ }
+ }
+
+ /* Run task on device. */
+ DeviceTask task(DeviceTask::RENDER);
+ create_task(task);
+ device->task_add(task);
+ device->task_wait();
+
+ printf("\n");
+ }
+
+ return true;
+}
+
+bool DenoiseTask::save()
+{
+ bool ok = image.save_output(denoiser->output[frame], error);
+ free();
+ return ok;
+}
+
+void DenoiseTask::free()
+{
+ image.free();
+ input_pixels.free();
+ assert(output_pixels.empty());
+}
+
+/* Denoise Image Storage */
+
+DenoiseImage::DenoiseImage()
+{
+ in = NULL;
+
+ width = 0;
+ height = 0;
+ num_channels = 0;
+ samples = 0;
+}
+
+DenoiseImage::~DenoiseImage()
+{
+ free();
+}
+
+void DenoiseImage::close_input()
+{
+ foreach(ImageInput *i, in_neighbors) {
+ i->close();
+ ImageInput::destroy(i);
+ }
+
+ in_neighbors.clear();
+
+ if(in) {
+ in->close();
+ ImageInput::destroy(in);
+ in = NULL;
+ }
+}
+
+void DenoiseImage::free()
+{
+ close_input();
+ pixels.clear();
+}
+
+bool DenoiseImage::parse_channels(const ImageSpec &in_spec, string& error)
+{
+ const std::vector<string> &channels = in_spec.channelnames;
+ const ParamValue *multiview = in_spec.find_attribute("multiView");
+ const bool multiview_channels = (multiview &&
+ multiview->type().basetype == TypeDesc::STRING &&
+ multiview->type().arraylen >= 2);
+
+ layers.clear();
+
+ /* Loop over all the channels in the file, parse their name and sort them
+ * by RenderLayer.
+ * Channels that can't be parsed are directly passed through to the output. */
+ map<string, DenoiseImageLayer> file_layers;
+ for(int i = 0; i < channels.size(); i++) {
+ string layer, pass, channel;
+ if(parse_channel_name(channels[i], layer, pass, channel, multiview_channels)) {
+ file_layers[layer].channels.push_back(pass + "." + channel);
+ file_layers[layer].layer_to_image_channel.push_back(i);
+ }
+ }
+
+ /* Loop over all detected RenderLayers, check whether they contain a full set of input channels.
+ * Any channels that won't be processed internally are also passed through. */
+ for(map<string, DenoiseImageLayer>::iterator i = file_layers.begin(); i != file_layers.end(); ++i) {
+ const string& name = i->first;
+ DenoiseImageLayer& layer = i->second;
+
+ /* Check for full pass set. */
+ if(!layer.detect_denoising_channels()) {
+ continue;
+ }
+
+ layer.name = name;
+ layer.samples = samples;
+
+ /* If the sample value isn't set yet, check if there is a layer-specific one in the input file. */
+ if(layer.samples < 1) {
+ string sample_string = in_spec.get_string_attribute("cycles." + name + ".samples", "");
+ if(sample_string != "") {
+ if(!sscanf(sample_string.c_str(), "%d", &layer.samples)) {
+ error = "Failed to parse samples metadata: " + sample_string;
+ return false;
+ }
+ }
+ }
+
+ if(layer.samples < 1) {
+ error = string_printf("No sample number specified in the file for layer %s or on the command line", name.c_str());
+ return false;
+ }
+
+ layers.push_back(layer);
+ }
+
+ return true;
+}
+
+void DenoiseImage::read_pixels(const DenoiseImageLayer& layer, float *input_pixels)
+{
+ /* Pixels from center file have already been loaded into pixels.
+ * We copy a subset into the device input buffer with channels reshuffled. */
+ const int *input_to_image_channel = layer.input_to_image_channel.data();
+
+ for(int i = 0; i < width * height; i++) {
+ for(int j = 0; j < INPUT_NUM_CHANNELS; j++) {
+ int image_channel = input_to_image_channel[j];
+ input_pixels[i*INPUT_NUM_CHANNELS + j] = pixels[((size_t)i)*num_channels + image_channel];
+ }
+ }
+}
+
+bool DenoiseImage::read_neighbor_pixels(int neighbor, const DenoiseImageLayer& layer, float *input_pixels)
+{
+ /* Load pixels from neighboring frames, and copy them into device buffer
+ * with channels reshuffled. */
+ size_t num_pixels = (size_t)width * (size_t)height;
+ array<float> neighbor_pixels(num_pixels * num_channels);
+ if(!in_neighbors[neighbor]->read_image(TypeDesc::FLOAT, neighbor_pixels.data())) {
+ return false;
+ }
+
+ const int *input_to_image_channel = layer.neighbor_input_to_image_channel[neighbor].data();
+
+ for(int i = 0; i < width * height; i++) {
+ for(int j = 0; j < INPUT_NUM_CHANNELS; j++) {
+ int image_channel = input_to_image_channel[j];
+ input_pixels[i*INPUT_NUM_CHANNELS + j] = neighbor_pixels[((size_t)i)*num_channels + image_channel];
+ }
+ }
+
+ return true;
+}
+
+bool DenoiseImage::load(const string& in_filepath, string& error)
+{
+ if(!Filesystem::is_regular(in_filepath)) {
+ error = "Couldn't find file: " + in_filepath;
+ return false;
+ }
+
+ in = ImageInput::open(in_filepath);
+ if(!in) {
+ error = "Couldn't open file: " + in_filepath;
+ return false;
+ }
+
+ const ImageSpec &in_spec = in->spec();
+ width = in_spec.width;
+ height = in_spec.height;
+ num_channels = in_spec.nchannels;
+
+ if(!parse_channels(in_spec, error)) {
+ return false;
+ }
+
+ if(layers.size() == 0) {
+ error = "Could not find a render layer containing denoising info";
+ return false;
+ }
+
+ size_t num_pixels = (size_t)width * (size_t)height;
+ pixels.resize(num_pixels * num_channels);
+
+ /* Read all channels into buffer. Reading all channels at once is faster
+ * than individually due to interleaved EXR channel storage. */
+ if(!in->read_image(TypeDesc::FLOAT, pixels.data())) {
+ error = "Failed to read image: " + in_filepath;
+ return false;
+ }
+
+ return true;
+}
+
+bool DenoiseImage::load_neighbors(const vector<string>& filepaths, const vector<int>& frames, string& error)
+{
+ if(frames.size() > DENOISE_MAX_FRAMES - 1) {
+ error = string_printf("Maximum number of neighbors (%d) exceeded\n", DENOISE_MAX_FRAMES - 1);
+ return false;
+ }
+
+ for(int neighbor = 0; neighbor < frames.size(); neighbor++) {
+ int frame = frames[neighbor];
+ const string& filepath = filepaths[frame];
+
+ if(!Filesystem::is_regular(filepath)) {
+ error = "Couldn't find neighbor frame: " + filepath;
+ return false;
+ }
+
+ ImageInput *in_neighbor = ImageInput::open(filepath);
+ if(!in_neighbor) {
+ error = "Couldn't open neighbor frame: " + filepath;
+ return false;
+ }
+
+ const ImageSpec &neighbor_spec = in_neighbor->spec();
+ if(neighbor_spec.width != width || neighbor_spec.height != height) {
+ error = "Neighbor frame has different dimensions: " + filepath;
+ in_neighbor->close();
+ ImageInput::destroy(in_neighbor);
+ return false;
+ }
+
+ foreach(DenoiseImageLayer& layer, layers) {
+ if(!layer.match_channels(neighbor,
+ in->spec().channelnames,
+ neighbor_spec.channelnames))
+ {
+ error = "Neighbor frame misses denoising data passes: " + filepath;
+ in_neighbor->close();
+ ImageInput::destroy(in_neighbor);
+ return false;
+ }
+ }
+
+ in_neighbors.push_back(in_neighbor);
+ }
+
+ return true;
+}
+
+bool DenoiseImage::save_output(const string& out_filepath, string& error)
+{
+ /* Save image with identical dimensions, channels and metadata. */
+ ImageSpec out_spec = in->spec();
+
+ /* Ensure that the output frame contains sample information even if the input didn't. */
+ for(int i = 0; i < layers.size(); i++) {
+ string name = "cycles." + layers[i].name + ".samples";
+ if(!out_spec.find_attribute(name, TypeDesc::STRING)) {
+ out_spec.attribute(name, TypeDesc::STRING, string_printf("%d", layers[i].samples));
+ }
+ }
+
+ /* We don't need input anymore at this point, and will possibly
+ * overwrite the same file. */
+ close_input();
+
+ /* Write to temporary file path, so we denoise images in place and don't
+ * risk destroying files when something goes wrong in file saving. */
+ string tmp_filepath = OIIO::Filesystem::temp_directory_path() + "/" + OIIO::Filesystem::unique_path() + ".exr";
+ ImageOutput *out = ImageOutput::create(tmp_filepath);
+
+ if(!out) {
+ error = "Failed to open temporary file " + tmp_filepath + " for writing";
+ return false;
+ }
+
+ /* Open temporary file and write image buffers. */
+ if(!out->open(tmp_filepath, out_spec)) {
+ error = "Failed to open file " + tmp_filepath + " for writing: " + out->geterror();
+ ImageOutput::destroy(out);
+ return false;
+ }
+
+ bool ok = true;
+ if(!out->write_image(TypeDesc::FLOAT, pixels.data())) {
+ error = "Failed to write to file " + tmp_filepath + ": " + out->geterror();
+ ok = false;
+ }
+
+ if(!out->close()) {
+ error = "Failed to save to file " + tmp_filepath + ": " + out->geterror();
+ ok = false;
+ }
+
+ ImageOutput::destroy(out);
+
+ /* Copy temporary file to outputput filepath. */
+ if(ok && !OIIO::Filesystem::rename(tmp_filepath, out_filepath)) {
+ error = "Failed to save to file " + out_filepath + ": " + out->geterror();
+ ok = false;
+ }
+
+ if(!ok) {
+ OIIO::Filesystem::remove(tmp_filepath);
+ return false;
+ }
+
+ return true;
+}
+
+/* File pattern handling and outer loop over frames */
+
+Denoiser::Denoiser(DeviceInfo& device_info)
+{
+ samples_override = 0;
+ tile_size = make_int2(64, 64);
+
+ num_frames = 0;
+
+ /* Initialize task scheduler. */
+ TaskScheduler::init();
+
+ /* Initialize device. */
+ DeviceRequestedFeatures req;
+ device = Device::create(device_info, stats, profiler, true);
+ device->load_kernels(req);
+}
+
+Denoiser::~Denoiser()
+{
+ delete device;
+ TaskScheduler::exit();
+}
+
+bool Denoiser::run()
+{
+ assert(input.size() == output.size());
+
+ num_frames = output.size();
+
+ for(int frame = 0; frame < num_frames; frame++) {
+ /* Skip empty output paths. */
+ if(output[frame].empty()) {
+ continue;
+ }
+
+ /* Determine neighbor frame numbers that should be used for filtering. */
+ vector<int> neighbor_frames;
+ for(int f = frame - params.neighbor_frames; f <= frame + params.neighbor_frames; f++) {
+ if (f >= 0 && f < num_frames && f != frame) {
+ neighbor_frames.push_back(f);
+ }
+ }
+
+ /* Execute task. */
+ DenoiseTask task(device, this, frame, neighbor_frames);
+ if(!task.load()) {
+ error = task.error;
+ return false;
+ }
+
+ if(!task.exec()) {
+ error = task.error;
+ return false;
+ }
+
+ if(!task.save()) {
+ error = task.error;
+ return false;
+ }
+
+ task.free();
+ }
+
+ return true;
+}
+
+CCL_NAMESPACE_END
diff --git a/intern/cycles/render/denoising.h b/intern/cycles/render/denoising.h
new file mode 100644
index 00000000000..15e690a2f45
--- /dev/null
+++ b/intern/cycles/render/denoising.h
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2011-2018 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 __DENOISING_H__
+#define __DENOISING_H__
+
+#include "device/device.h"
+#include "device/device_denoising.h"
+
+#include "render/buffers.h"
+
+#include "util/util_string.h"
+#include "util/util_vector.h"
+
+#include <OpenImageIO/imageio.h>
+
+OIIO_NAMESPACE_USING
+
+CCL_NAMESPACE_BEGIN
+
+/* Denoiser */
+
+class Denoiser {
+public:
+ Denoiser(DeviceInfo& device_info);
+ ~Denoiser();
+
+ bool run();
+
+ /* Error message after running, in case of failure. */
+ string error;
+
+ /* Sequential list of frame filepaths to denoise. */
+ vector<string> input;
+ /* Sequential list of frame filepaths to write result to. Empty entries
+ * are skipped, so only a subset of the sequence can be denoised while
+ * taking into account all input frames. */
+ vector<string> output;
+
+ /* Sample number override, takes precedence over values from input frames. */
+ int samples_override;
+ /* Tile size for processing on device. */
+ int2 tile_size;
+
+ /* Equivalent to the settings in the regular denoiser. */
+ DenoiseParams params;
+
+protected:
+ friend class DenoiseTask;
+
+ Stats stats;
+ Profiler profiler;
+ Device *device;
+
+ int num_frames;
+};
+
+/* Denoise Image Layer */
+
+struct DenoiseImageLayer {
+ string name;
+ /* All channels belonging to this DenoiseImageLayer. */
+ vector<string> channels;
+ /* Layer to image channel mapping. */
+ vector<int> layer_to_image_channel;
+
+ /* Sample amount that was used for rendering this layer. */
+ int samples;
+
+ /* Device input channel will be copied from image channel input_to_image_channel[i]. */
+ vector<int> input_to_image_channel;
+
+ /* input_to_image_channel of the secondary frames, if any are used. */
+ vector<vector<int> > neighbor_input_to_image_channel;
+
+ /* Write i-th channel of the processing output to output_to_image_channel[i]-th channel of the file. */
+ vector<int> output_to_image_channel;
+
+ /* Detect whether this layer contains a full set of channels and set up the offsets accordingly. */
+ bool detect_denoising_channels();
+
+ /* Map the channels of a secondary frame to the channels that are required for processing,
+ * fill neighbor_input_to_image_channel if all are present or return false if a channel are missing. */
+ bool match_channels(int neighbor,
+ const std::vector<string> &channelnames,
+ const std::vector<string> &neighbor_channelnames);
+};
+
+/* Denoise Image Data */
+
+class DenoiseImage {
+public:
+ DenoiseImage();
+ ~DenoiseImage();
+
+ /* Dimensions */
+ int width, height, num_channels;
+
+ /* Samples */
+ int samples;
+
+ /* Pixel buffer with interleaved channels. */
+ array<float> pixels;
+
+ /* Image file handles */
+ ImageInput *in;
+ vector<ImageInput*> in_neighbors;
+
+ /* Render layers */
+ vector<DenoiseImageLayer> layers;
+
+ void free();
+
+ /* Open the input image, parse its channels, open the output image and allocate the output buffer. */
+ bool load(const string& in_filepath, string& error);
+
+ /* Load neighboring frames. */
+ bool load_neighbors(const vector<string>& filepaths, const vector<int>& frames, string& error);
+
+ /* Load subset of pixels from file buffer into input buffer, as needed for denoising
+ * on the device. Channels are reshuffled following the provided mapping. */
+ void read_pixels(const DenoiseImageLayer& layer, float *input_pixels);
+ bool read_neighbor_pixels(int neighbor, const DenoiseImageLayer& layer, float *input_pixels);
+
+ bool save_output(const string& out_filepath, string& error);
+
+protected:
+ /* Parse input file channels, separate them into DenoiseImageLayers, detect DenoiseImageLayers with full channel sets,
+ * fill layers and set up the output channels and passthrough map. */
+ bool parse_channels(const ImageSpec &in_spec, string& error);
+
+ void close_input();
+};
+
+/* Denoise Task */
+
+class DenoiseTask {
+public:
+ DenoiseTask(Device *device, Denoiser *denoiser, int frame, const vector<int>& neighbor_frames);
+ ~DenoiseTask();
+
+ /* Task stages */
+ bool load();
+ bool exec();
+ bool save();
+ void free();
+
+ string error;
+
+protected:
+ /* Denoiser parameters and device */
+ Denoiser *denoiser;
+ Device *device;
+
+ /* Frame number to be denoised */
+ int frame;
+ vector<int> neighbor_frames;
+
+ /* Image file data */
+ DenoiseImage image;
+ int current_layer;
+
+ /* Device input buffer */
+ device_vector<float> input_pixels;
+
+ /* Tiles */
+ thread_mutex tiles_mutex;
+ list<RenderTile> tiles;
+ int num_tiles;
+
+ thread_mutex output_mutex;
+ map<int, device_vector<float>*> output_pixels;
+
+ /* Task handling */
+ bool load_input_pixels(int layer);
+ void create_task(DeviceTask& task);
+
+ /* Device task callbacks */
+ bool acquire_tile(Device *device, Device *tile_device, RenderTile &tile);
+ void map_neighboring_tiles(RenderTile *tiles, Device *tile_device);
+ void unmap_neighboring_tiles(RenderTile *tiles);
+ void release_tile();
+ bool get_cancel();
+};
+
+CCL_NAMESPACE_END
+
+#endif /* __DENOISING_H__ */
diff --git a/intern/cycles/render/session.cpp b/intern/cycles/render/session.cpp
index abed5c13f9b..ac69251c908 100644
--- a/intern/cycles/render/session.cpp
+++ b/intern/cycles/render/session.cpp
@@ -979,10 +979,7 @@ void Session::render()
task.passes_size = tile_manager.params.get_passes_size();
if(params.run_denoising) {
- task.denoising_radius = params.denoising_radius;
- task.denoising_strength = params.denoising_strength;
- task.denoising_feature_strength = params.denoising_feature_strength;
- task.denoising_relative_pca = params.denoising_relative_pca;
+ task.denoising = params.denoising;
assert(!scene->film->need_update);
task.pass_stride = scene->film->pass_stride;
diff --git a/intern/cycles/render/session.h b/intern/cycles/render/session.h
index cb1d8fed68f..e3cccbb9fcf 100644
--- a/intern/cycles/render/session.h
+++ b/intern/cycles/render/session.h
@@ -63,10 +63,7 @@ public:
bool run_denoising;
bool write_denoising_passes;
bool full_denoising;
- int denoising_radius;
- float denoising_strength;
- float denoising_feature_strength;
- bool denoising_relative_pca;
+ DenoiseParams denoising;
double cancel_timeout;
double reset_timeout;
@@ -98,10 +95,6 @@ public:
run_denoising = false;
write_denoising_passes = false;
full_denoising = false;
- denoising_radius = 8;
- denoising_strength = 0.0f;
- denoising_feature_strength = 0.0f;
- denoising_relative_pca = false;
display_buffer_linear = false;
diff --git a/intern/cycles/util/util_system.cpp b/intern/cycles/util/util_system.cpp
index a22bd25ce77..6255596cd06 100644
--- a/intern/cycles/util/util_system.cpp
+++ b/intern/cycles/util/util_system.cpp
@@ -32,6 +32,7 @@
# include <sys/types.h>
#else
# include <unistd.h>
+# include <sys/ioctl.h>
#endif
CCL_NAMESPACE_BEGIN
@@ -113,6 +114,25 @@ bool system_cpu_run_thread_on_node(int node)
return numaAPI_RunThreadOnNode(node);
}
+int system_console_width()
+{
+ int columns = 0;
+
+#ifdef _WIN32
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+ if(GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
+ columns = csbi.dwSize.X;
+ }
+#else
+ struct winsize w;
+ if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0) {
+ columns = w.ws_col;
+ }
+#endif
+
+ return (columns > 0) ? columns : 80;
+}
+
int system_cpu_num_active_group_processors()
{
if(!system_cpu_ensure_initialized()) {
diff --git a/intern/cycles/util/util_system.h b/intern/cycles/util/util_system.h
index 0c001f11f0e..1e7cf1d9f2a 100644
--- a/intern/cycles/util/util_system.h
+++ b/intern/cycles/util/util_system.h
@@ -27,6 +27,9 @@ bool system_cpu_ensure_initialized();
/* Get total number of threads in all NUMA nodes / CPU groups. */
int system_cpu_thread_count();
+/* Get width in characters of the current console output. */
+int system_console_width();
+
/* Get number of available nodes.
*
* This is in fact an index of last node plus one and it's not guaranteed