diff options
author | Brecht Van Lommel <brechtvanlommel@gmail.com> | 2019-02-11 15:37:45 +0300 |
---|---|---|
committer | Brecht Van Lommel <brechtvanlommel@gmail.com> | 2019-02-11 15:37:45 +0300 |
commit | 3f8e26370925c94e07f0bbdd51cddad6601fa125 (patch) | |
tree | 4a26576894a2f5d65fe56c1ea6c5a024a9a62099 /intern/cycles/render | |
parent | 9ec944bbab7a5ba75a526387f8eab52af7f6405e (diff) | |
parent | c10f5d15c25cbc5ee319835c90d5d2a4dda53497 (diff) |
Merge branch 'blender2.7'
Diffstat (limited to 'intern/cycles/render')
-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 | 10 | ||||
-rw-r--r-- | intern/cycles/render/session.h | 9 |
5 files changed, 1096 insertions, 13 deletions
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 4b8630af6d1..3283427011e 100644 --- a/intern/cycles/render/session.cpp +++ b/intern/cycles/render/session.cpp @@ -927,9 +927,12 @@ void Session::update_status_time(bool show_pause, bool show_done) */ substatus += string_printf(", Sample %d/%d", progress.get_current_sample(), num_samples); } - if(params.run_denoising) { + if(params.full_denoising) { substatus += string_printf(", Denoised %d tiles", progress.get_denoised_tiles()); } + else if(params.run_denoising) { + substatus += string_printf(", Prefiltered %d tiles", progress.get_denoised_tiles()); + } } else if(tile_manager.num_samples == INT_MAX) substatus = string_printf("Path Tracing Sample %d", progressive_sample+1); @@ -976,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; |