diff options
-rw-r--r-- | intern/cycles/blender/CMakeLists.txt | 1 | ||||
-rw-r--r-- | intern/cycles/blender/addon/__init__.py | 6 | ||||
-rw-r--r-- | intern/cycles/blender/addon/operators.py | 133 | ||||
-rw-r--r-- | intern/cycles/blender/addon/properties.py | 6 | ||||
-rw-r--r-- | intern/cycles/blender/addon/ui.py | 23 | ||||
-rw-r--r-- | intern/cycles/blender/blender_python.cpp | 121 | ||||
-rw-r--r-- | intern/cycles/blender/blender_session.cpp | 8 | ||||
-rw-r--r-- | intern/cycles/device/device_denoising.cpp | 10 | ||||
-rw-r--r-- | intern/cycles/device/device_task.h | 31 | ||||
-rw-r--r-- | intern/cycles/render/CMakeLists.txt | 2 | ||||
-rw-r--r-- | intern/cycles/render/denoising.cpp | 887 | ||||
-rw-r--r-- | intern/cycles/render/denoising.h | 201 | ||||
-rw-r--r-- | intern/cycles/render/session.cpp | 5 | ||||
-rw-r--r-- | intern/cycles/render/session.h | 9 | ||||
-rw-r--r-- | intern/cycles/util/util_system.cpp | 20 | ||||
-rw-r--r-- | intern/cycles/util/util_system.h | 3 |
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 |