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:
authorBrecht Van Lommel <brechtvanlommel@gmail.com>2019-03-19 20:54:17 +0300
committerBrecht Van Lommel <brechtvanlommel@gmail.com>2019-03-19 20:54:17 +0300
commitfa59c6a3e7d72d57faadcf0795848b4c3b8381d9 (patch)
treeea668f42028f8cdf5a5968feeed27e8bd99b30fd /intern/cycles/render
parente7f535cd4cf919b7e479d20b8a1b6714f166151d (diff)
parent83de13f75aafca7d4e1d58ddd36cca19121aa84f (diff)
Merge branch 'blender2.7'
Diffstat (limited to 'intern/cycles/render')
-rw-r--r--intern/cycles/render/CMakeLists.txt2
-rw-r--r--intern/cycles/render/merge.cpp456
-rw-r--r--intern/cycles/render/merge.h43
3 files changed, 501 insertions, 0 deletions
diff --git a/intern/cycles/render/CMakeLists.txt b/intern/cycles/render/CMakeLists.txt
index e6afbc50463..b7c53f17c3d 100644
--- a/intern/cycles/render/CMakeLists.txt
+++ b/intern/cycles/render/CMakeLists.txt
@@ -22,6 +22,7 @@ set(SRC
image.cpp
integrator.cpp
light.cpp
+ merge.cpp
mesh.cpp
mesh_displace.cpp
mesh_subdivision.cpp
@@ -55,6 +56,7 @@ set(SRC_HEADERS
image.h
integrator.h
light.h
+ merge.h
mesh.h
nodes.h
object.h
diff --git a/intern/cycles/render/merge.cpp b/intern/cycles/render/merge.cpp
new file mode 100644
index 00000000000..af655417046
--- /dev/null
+++ b/intern/cycles/render/merge.cpp
@@ -0,0 +1,456 @@
+/*
+ * 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.
+ */
+
+#include "render/merge.h"
+
+#include "util/util_array.h"
+#include "util/util_map.h"
+#include "util/util_system.h"
+#include "util/util_time.h"
+#include "util/util_unique_ptr.h"
+
+#include <OpenImageIO/imageio.h>
+#include <OpenImageIO/filesystem.h>
+
+OIIO_NAMESPACE_USING
+
+CCL_NAMESPACE_BEGIN
+
+/* Merge Image Layer */
+
+enum MergeChannelOp {
+ MERGE_CHANNEL_COPY,
+ MERGE_CHANNEL_SUM,
+ MERGE_CHANNEL_AVERAGE
+};
+
+struct MergeImageLayer {
+ /* Layer name. */
+ string name;
+
+ /* All channels belonging to this MergeImageLayer. */
+ vector<string> channel_names;
+ /* Offsets of layer channels in image. */
+ vector<int> channel_offsets;
+ /* Type of operation to perform when merging. */
+ vector<MergeChannelOp> channel_ops;
+
+ /* Sample amount that was used for rendering this layer. */
+ int samples;
+};
+
+/* Merge Image */
+
+class MergeImage {
+public:
+ /* OIIO file handle. */
+ unique_ptr<ImageInput> in;
+
+ /* Image file path. */
+ string filepath;
+
+ /* Render layers. */
+ vector<MergeImageLayer> layers;
+};
+
+/* Channel Parsing */
+
+static MergeChannelOp parse_channel_operation(const string& pass_name)
+{
+ if(pass_name == "Depth" ||
+ pass_name == "IndexMA" ||
+ pass_name == "IndexOB" ||
+ string_startswith(pass_name, "Crypto"))
+ {
+ return MERGE_CHANNEL_COPY;
+ }
+ else if(string_startswith(pass_name, "Debug BVH") ||
+ string_startswith(pass_name, "Debug Ray") ||
+ string_startswith(pass_name, "Debug Render Time"))
+ {
+ return MERGE_CHANNEL_SUM;
+ }
+ else {
+ return MERGE_CHANNEL_AVERAGE;
+ }
+}
+
+/* Splits in at its last dot, setting suffix to the part after the dot and
+ * into 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.
+ * Multiview format: RenderLayer.Pass.View.Channel
+ * Otherwise: 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;
+}
+
+static bool parse_channels(const ImageSpec &in_spec,
+ vector<MergeImageLayer>& layers,
+ 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, MergeImageLayer> 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].channel_names.push_back(pass + "." + channel);
+ file_layers[layer].channel_offsets.push_back(i);
+ file_layers[layer].channel_ops.push_back(parse_channel_operation(pass));
+ }
+
+ /* Any unparsed channels are copied from the first image. */
+ }
+
+ /* 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, MergeImageLayer>::iterator i = file_layers.begin(); i != file_layers.end(); ++i) {
+ const string& name = i->first;
+ MergeImageLayer& layer = i->second;
+
+ layer.name = name;
+ layer.samples = 0;
+
+ /* 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;
+}
+
+static bool open_images(const vector<string>& filepaths,
+ vector<MergeImage>& images,
+ string& error)
+{
+ for(const string& filepath: filepaths) {
+ unique_ptr<ImageInput> in(ImageInput::open(filepath));
+ if(!in) {
+ error = "Couldn't open file: " + filepath;
+ return false;
+ }
+
+ MergeImage image;
+ image.in = std::move(in);
+ image.filepath = filepath;
+ if(!parse_channels(image.in->spec(), image.layers, error)) {
+ return false;
+ }
+
+ if(image.layers.size() == 0) {
+ error = "Could not find a render layer for merging";
+ return false;
+ }
+
+ if(image.in->spec().deep) {
+ error = "Merging deep images not supported.";
+ return false;
+ }
+
+ if(images.size() > 0) {
+ const ImageSpec& base_spec = images[0].in->spec();
+ const ImageSpec& spec = image.in->spec();
+
+ if(base_spec.width != spec.width ||
+ base_spec.height != spec.height ||
+ base_spec.depth != spec.depth ||
+ base_spec.nchannels != spec.nchannels ||
+ base_spec.format != spec.format ||
+ base_spec.channelformats != spec.channelformats ||
+ base_spec.channelnames != spec.channelnames ||
+ base_spec.deep != spec.deep)
+ {
+ error = "Images do not have exact matching data and channel layout.";
+ return false;
+ }
+ }
+
+ images.push_back(std::move(image));
+ }
+
+ return true;
+}
+
+static bool load_pixels(const MergeImage& image, array<float>& pixels, string& error)
+{
+ const ImageSpec& in_spec = image.in->spec();
+ const size_t width = in_spec.width;
+ const size_t height = in_spec.height;
+ const size_t num_channels = in_spec.nchannels;
+
+ const 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(!image.in->read_image(TypeDesc::FLOAT, pixels.data())) {
+ error = "Failed to read image: " + image.filepath;
+ return false;
+ }
+
+ return true;
+}
+
+static void merge_render_time(ImageSpec& spec,
+ const vector<MergeImage>& images,
+ const string& name,
+ const bool average)
+{
+ double time = 0.0;
+
+ for(const MergeImage& image: images) {
+ string time_str = image.in->spec().get_string_attribute(name, "");
+ time += time_human_readable_to_seconds(time_str);
+ }
+
+ if(average) {
+ time /= images.size();
+ }
+
+ spec.attribute(name, TypeDesc::STRING, time_human_readable_from_seconds(time));
+}
+
+static void merge_layer_render_time(ImageSpec& spec,
+ const vector<MergeImage>& images,
+ const string& time_name,
+ const bool average)
+{
+ for(size_t i = 0; i < images[0].layers.size(); i++) {
+ string name = "cycles." + images[0].layers[i].name + "." + time_name;
+ double time = 0.0;
+
+ for(const MergeImage& image: images) {
+ string time_str = image.in->spec().get_string_attribute(name, "");
+ time += time_human_readable_to_seconds(time_str);
+ }
+
+ if(average) {
+ time /= images.size();
+ }
+
+ spec.attribute(name, TypeDesc::STRING, time_human_readable_from_seconds(time));
+ }
+}
+
+static bool save_output(const string& filepath,
+ const ImageSpec& spec,
+ const array<float>& pixels,
+ string& error)
+{
+ /* Write to temporary file path, so we merge images in place and don't
+ * risk destroying files when something goes wrong in file saving. */
+ string extension = OIIO::Filesystem::extension(filepath);
+ string unique_name = ".merge-tmp-" + OIIO::Filesystem::unique_path();
+ string tmp_filepath = filepath + unique_name + extension;
+ unique_ptr<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, spec)) {
+ error = "Failed to open file " + tmp_filepath + " for writing: " + out->geterror();
+ 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;
+ }
+
+ out.reset();
+
+ /* Copy temporary file to outputput filepath. */
+ string rename_error;
+ if(ok && !OIIO::Filesystem::rename(tmp_filepath, filepath, rename_error)) {
+ error = "Failed to move merged image to " + filepath + ": " + rename_error;
+ ok = false;
+ }
+
+ if(!ok) {
+ OIIO::Filesystem::remove(tmp_filepath);
+ }
+
+ return ok;
+}
+
+/* Image Merger */
+
+ImageMerger::ImageMerger()
+{
+}
+
+bool ImageMerger::run()
+{
+ if(input.empty()) {
+ error = "No input file paths specified.";
+ return false;
+ }
+ if(output.empty()) {
+ error = "No output file path specified.";
+ return false;
+ }
+
+ /* Open images and verify they have matching layout. */
+ vector<MergeImage> images;
+ if(!open_images(input, images, error)) {
+ return false;
+ }
+
+ /* Merge pixels. */
+ array<float> merge_pixels;
+ vector<int> merge_samples;
+
+ /* Load first image. */
+ if(!load_pixels(images[0], merge_pixels, error)) {
+ return false;
+ }
+ for(size_t layer = 0; layer < images[0].layers.size(); layer++) {
+ merge_samples.push_back(images[0].layers[layer].samples);
+ }
+
+ /* Merge other images. */
+ for(size_t i = 1; i < images.size(); i++) {
+ const MergeImage& image = images[i];
+
+ array<float> pixels;
+ if(!load_pixels(image, pixels, error)) {
+ return false;
+ }
+
+ for(size_t li = 0; li < image.layers.size(); li++) {
+ const MergeImageLayer& layer = image.layers[li];
+
+ const int *offsets = layer.channel_offsets.data();
+ const MergeChannelOp *ops = layer.channel_ops.data();
+
+ const size_t stride = image.in->spec().nchannels;
+ const size_t num_channels = layer.channel_offsets.size();
+ const size_t num_pixels = pixels.size();
+
+ /* Weights based on sample metadata. */
+ const int sum_samples = merge_samples[li] + layer.samples;
+ const float t = (float)layer.samples / (float)sum_samples;
+
+ for(size_t pixel = 0; pixel < num_pixels; pixel += stride) {
+ for(size_t channel = 0; channel < num_channels; channel++) {
+ size_t offset = pixel + offsets[channel];
+
+ switch(ops[channel]) {
+ case MERGE_CHANNEL_COPY:
+ /* Already copied from first image. */
+ break;
+ case MERGE_CHANNEL_SUM:
+ merge_pixels[offset] += pixels[offset];
+ break;
+ case MERGE_CHANNEL_AVERAGE:
+ merge_pixels[offset] = (1.0f - t) * merge_pixels[offset] + t * pixels[offset];
+ break;
+ }
+ }
+ }
+
+ merge_samples[li] += layer.samples;
+ }
+ }
+
+ /* Save image with identical dimensions, channels and metadata. */
+ ImageSpec out_spec = images[0].in->spec();
+
+ /* Merge metadata. */
+ for(size_t i = 0; i < images[0].layers.size(); i++) {
+ string name = "cycles." + images[0].layers[i].name + ".samples";
+ out_spec.attribute(name, TypeDesc::STRING, string_printf("%d", merge_samples[i]));
+ }
+
+ merge_render_time(out_spec, images, "RenderTime", false);
+ merge_layer_render_time(out_spec, images, "total_time", false);
+ merge_layer_render_time(out_spec, images, "render_time", false);
+ merge_layer_render_time(out_spec, images, "synchronization_time", true);
+
+ /* We don't need input anymore at this point, and will possibly
+ * overwrite the same file. */
+ images.clear();
+
+ /* Save output file. */
+ return save_output(output, out_spec, merge_pixels, error);
+}
+
+CCL_NAMESPACE_END
diff --git a/intern/cycles/render/merge.h b/intern/cycles/render/merge.h
new file mode 100644
index 00000000000..48900a13c27
--- /dev/null
+++ b/intern/cycles/render/merge.h
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#ifndef __MERGE_H__
+#define __MERGE_H__
+
+#include "util/util_string.h"
+#include "util/util_vector.h"
+
+CCL_NAMESPACE_BEGIN
+
+/* Merge OpenEXR multilayer renders. */
+
+class ImageMerger {
+public:
+ ImageMerger();
+ bool run();
+
+ /* Error message after running, in case of failure. */
+ string error;
+
+ /* List of image filepaths to merge. */
+ vector<string> input;
+ /* Output filepath. */
+ string output;
+};
+
+CCL_NAMESPACE_END
+
+#endif /* __MERGE_H__ */