Merge branch 'blender2.7'
This commit is contained in:
@@ -17,6 +17,7 @@ set(INC_SYS
|
|||||||
|
|
||||||
set(SRC
|
set(SRC
|
||||||
blender_camera.cpp
|
blender_camera.cpp
|
||||||
|
blender_device.cpp
|
||||||
blender_mesh.cpp
|
blender_mesh.cpp
|
||||||
blender_object.cpp
|
blender_object.cpp
|
||||||
blender_object_cull.cpp
|
blender_object_cull.cpp
|
||||||
@@ -40,6 +41,7 @@ set(SRC
|
|||||||
set(ADDON_FILES
|
set(ADDON_FILES
|
||||||
addon/__init__.py
|
addon/__init__.py
|
||||||
addon/engine.py
|
addon/engine.py
|
||||||
|
addon/operators.py
|
||||||
addon/osl.py
|
addon/osl.py
|
||||||
addon/presets.py
|
addon/presets.py
|
||||||
addon/properties.py
|
addon/properties.py
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ if "bpy" in locals():
|
|||||||
importlib.reload(version_update)
|
importlib.reload(version_update)
|
||||||
if "ui" in locals():
|
if "ui" in locals():
|
||||||
importlib.reload(ui)
|
importlib.reload(ui)
|
||||||
|
if "operators" in locals():
|
||||||
|
importlib.reload(operators)
|
||||||
if "properties" in locals():
|
if "properties" in locals():
|
||||||
importlib.reload(properties)
|
importlib.reload(properties)
|
||||||
if "presets" in locals():
|
if "presets" in locals():
|
||||||
@@ -119,6 +121,7 @@ classes = (
|
|||||||
def register():
|
def register():
|
||||||
from bpy.utils import register_class
|
from bpy.utils import register_class
|
||||||
from . import ui
|
from . import ui
|
||||||
|
from . import operators
|
||||||
from . import properties
|
from . import properties
|
||||||
from . import presets
|
from . import presets
|
||||||
import atexit
|
import atexit
|
||||||
@@ -131,6 +134,7 @@ def register():
|
|||||||
|
|
||||||
properties.register()
|
properties.register()
|
||||||
ui.register()
|
ui.register()
|
||||||
|
operators.register()
|
||||||
presets.register()
|
presets.register()
|
||||||
|
|
||||||
for cls in classes:
|
for cls in classes:
|
||||||
@@ -142,6 +146,7 @@ def register():
|
|||||||
def unregister():
|
def unregister():
|
||||||
from bpy.utils import unregister_class
|
from bpy.utils import unregister_class
|
||||||
from . import ui
|
from . import ui
|
||||||
|
from . import operators
|
||||||
from . import properties
|
from . import properties
|
||||||
from . import presets
|
from . import presets
|
||||||
import atexit
|
import atexit
|
||||||
@@ -149,6 +154,7 @@ def unregister():
|
|||||||
bpy.app.handlers.version_update.remove(version_update.do_versions)
|
bpy.app.handlers.version_update.remove(version_update.do_versions)
|
||||||
|
|
||||||
ui.unregister()
|
ui.unregister()
|
||||||
|
operators.unregister()
|
||||||
properties.unregister()
|
properties.unregister()
|
||||||
presets.unregister()
|
presets.unregister()
|
||||||
|
|
||||||
|
|||||||
133
intern/cycles/blender/addon/operators.py
Normal file
133
intern/cycles/blender/addon/operators.py
Normal file
@@ -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.preferences
|
||||||
|
scene = context.scene
|
||||||
|
view_layer = context.view_layer
|
||||||
|
|
||||||
|
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(),
|
||||||
|
view_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)
|
||||||
@@ -1355,6 +1355,12 @@ class CyclesRenderLayerSettings(bpy.types.PropertyGroup):
|
|||||||
default=False,
|
default=False,
|
||||||
update=update_render_passes,
|
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,
|
||||||
|
)
|
||||||
use_pass_crypto_object: BoolProperty(
|
use_pass_crypto_object: BoolProperty(
|
||||||
name="Cryptomatte Object",
|
name="Cryptomatte Object",
|
||||||
description="Render cryptomatte object pass, for isolating objects in compositing",
|
description="Render cryptomatte object pass, for isolating objects in compositing",
|
||||||
|
|||||||
@@ -19,12 +19,8 @@
|
|||||||
import bpy
|
import bpy
|
||||||
from bpy_extras.node_utils import find_node_input
|
from bpy_extras.node_utils import find_node_input
|
||||||
from bl_operators.presets import PresetMenu
|
from bl_operators.presets import PresetMenu
|
||||||
import _cycles
|
|
||||||
|
|
||||||
from bpy.types import (
|
from bpy.types import Panel
|
||||||
Panel,
|
|
||||||
Operator,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CYCLES_PT_sampling_presets(PresetMenu):
|
class CYCLES_PT_sampling_presets(PresetMenu):
|
||||||
@@ -636,6 +632,8 @@ class CYCLES_RENDER_PT_performance_acceleration_structure(CyclesButtonsPanel, Pa
|
|||||||
bl_parent_id = "CYCLES_RENDER_PT_performance"
|
bl_parent_id = "CYCLES_RENDER_PT_performance"
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
|
import _cycles
|
||||||
|
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
layout.use_property_split = True
|
layout.use_property_split = True
|
||||||
layout.use_property_decorate = False
|
layout.use_property_decorate = False
|
||||||
@@ -1277,27 +1275,6 @@ class CYCLES_OBJECT_PT_cycles_settings_performance(CyclesButtonsPanel, Panel):
|
|||||||
col.prop(cob, "use_distance_cull")
|
col.prop(cob, "use_distance_cull")
|
||||||
|
|
||||||
|
|
||||||
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'}
|
|
||||||
|
|
||||||
|
|
||||||
def panel_node_draw(layout, id_data, output_type, input_name):
|
def panel_node_draw(layout, id_data, output_type, input_name):
|
||||||
if not id_data.use_nodes:
|
if not id_data.use_nodes:
|
||||||
layout.operator("cycles.use_shading_nodes", icon='NODETREE')
|
layout.operator("cycles.use_shading_nodes", icon='NODETREE')
|
||||||
@@ -2136,7 +2113,6 @@ classes = (
|
|||||||
CYCLES_OBJECT_PT_cycles_settings,
|
CYCLES_OBJECT_PT_cycles_settings,
|
||||||
CYCLES_OBJECT_PT_cycles_settings_ray_visibility,
|
CYCLES_OBJECT_PT_cycles_settings_ray_visibility,
|
||||||
CYCLES_OBJECT_PT_cycles_settings_performance,
|
CYCLES_OBJECT_PT_cycles_settings_performance,
|
||||||
CYCLES_OT_use_shading_nodes,
|
|
||||||
CYCLES_LIGHT_PT_preview,
|
CYCLES_LIGHT_PT_preview,
|
||||||
CYCLES_LIGHT_PT_light,
|
CYCLES_LIGHT_PT_light,
|
||||||
CYCLES_LIGHT_PT_nodes,
|
CYCLES_LIGHT_PT_nodes,
|
||||||
|
|||||||
109
intern/cycles/blender/blender_device.cpp
Normal file
109
intern/cycles/blender/blender_device.cpp
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011-2013 Blender Foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "blender/blender_device.h"
|
||||||
|
#include "blender/blender_util.h"
|
||||||
|
|
||||||
|
CCL_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
int blender_device_threads(BL::Scene& b_scene)
|
||||||
|
{
|
||||||
|
BL::RenderSettings b_r = b_scene.render();
|
||||||
|
|
||||||
|
if(b_r.threads_mode() == BL::RenderSettings::threads_mode_FIXED)
|
||||||
|
return b_r.threads();
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceInfo blender_device_info(BL::Preferences& b_preferences, BL::Scene& b_scene, bool background)
|
||||||
|
{
|
||||||
|
PointerRNA cscene = RNA_pointer_get(&b_scene.ptr, "cycles");
|
||||||
|
|
||||||
|
/* Default to CPU device. */
|
||||||
|
DeviceInfo device = Device::available_devices(DEVICE_MASK_CPU).front();
|
||||||
|
|
||||||
|
if(get_enum(cscene, "device") == 2) {
|
||||||
|
/* Find network device. */
|
||||||
|
vector<DeviceInfo> devices = Device::available_devices(DEVICE_MASK_NETWORK);
|
||||||
|
if(!devices.empty()) {
|
||||||
|
device = devices.front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(get_enum(cscene, "device") == 1) {
|
||||||
|
/* Find cycles preferences. */
|
||||||
|
PointerRNA cpreferences;
|
||||||
|
|
||||||
|
BL::Preferences::addons_iterator b_addon_iter;
|
||||||
|
for(b_preferences.addons.begin(b_addon_iter); b_addon_iter != b_preferences.addons.end(); ++b_addon_iter) {
|
||||||
|
if(b_addon_iter->module() == "cycles") {
|
||||||
|
cpreferences = b_addon_iter->preferences().ptr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test if we are using GPU devices. */
|
||||||
|
enum ComputeDevice {
|
||||||
|
COMPUTE_DEVICE_CPU = 0,
|
||||||
|
COMPUTE_DEVICE_CUDA = 1,
|
||||||
|
COMPUTE_DEVICE_OPENCL = 2,
|
||||||
|
COMPUTE_DEVICE_NUM = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
ComputeDevice compute_device = (ComputeDevice)get_enum(cpreferences,
|
||||||
|
"compute_device_type",
|
||||||
|
COMPUTE_DEVICE_NUM,
|
||||||
|
COMPUTE_DEVICE_CPU);
|
||||||
|
|
||||||
|
if(compute_device != COMPUTE_DEVICE_CPU) {
|
||||||
|
/* Query GPU devices with matching types. */
|
||||||
|
uint mask = DEVICE_MASK_CPU;
|
||||||
|
if(compute_device == COMPUTE_DEVICE_CUDA) {
|
||||||
|
mask |= DEVICE_MASK_CUDA;
|
||||||
|
}
|
||||||
|
else if(compute_device == COMPUTE_DEVICE_OPENCL) {
|
||||||
|
mask |= DEVICE_MASK_OPENCL;
|
||||||
|
}
|
||||||
|
vector<DeviceInfo> devices = Device::available_devices(mask);
|
||||||
|
|
||||||
|
/* Match device preferences and available devices. */
|
||||||
|
vector<DeviceInfo> used_devices;
|
||||||
|
RNA_BEGIN(&cpreferences, device, "devices") {
|
||||||
|
if(get_boolean(device, "use")) {
|
||||||
|
string id = get_string(device, "id");
|
||||||
|
foreach(DeviceInfo& info, devices) {
|
||||||
|
if(info.id == id) {
|
||||||
|
used_devices.push_back(info);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} RNA_END;
|
||||||
|
|
||||||
|
if(!used_devices.empty()) {
|
||||||
|
int threads = blender_device_threads(b_scene);
|
||||||
|
device = Device::get_multi_device(used_devices,
|
||||||
|
threads,
|
||||||
|
background);
|
||||||
|
}
|
||||||
|
/* Else keep using the CPU device that was set before. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
|
CCL_NAMESPACE_END
|
||||||
37
intern/cycles/blender/blender_device.h
Normal file
37
intern/cycles/blender/blender_device.h
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011-2013 Blender Foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __BLENDER_DEVICE_H__
|
||||||
|
#define __BLENDER_DEVICE_H__
|
||||||
|
|
||||||
|
#include "MEM_guardedalloc.h"
|
||||||
|
#include "RNA_types.h"
|
||||||
|
#include "RNA_access.h"
|
||||||
|
#include "RNA_blender_cpp.h"
|
||||||
|
|
||||||
|
#include "device/device.h"
|
||||||
|
|
||||||
|
CCL_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
/* Get number of threads to use for rendering. */
|
||||||
|
int blender_device_threads(BL::Scene& b_scene);
|
||||||
|
|
||||||
|
/* Convert Blender settings to device specification. */
|
||||||
|
DeviceInfo blender_device_info(BL::Preferences& b_preferences, BL::Scene& b_scene, bool background);
|
||||||
|
|
||||||
|
CCL_NAMESPACE_END
|
||||||
|
|
||||||
|
#endif /* __BLENDER_DEVICE_H__ */
|
||||||
@@ -18,9 +18,12 @@
|
|||||||
|
|
||||||
#include "blender/CCL_api.h"
|
#include "blender/CCL_api.h"
|
||||||
|
|
||||||
|
#include "blender/blender_device.h"
|
||||||
#include "blender/blender_sync.h"
|
#include "blender/blender_sync.h"
|
||||||
#include "blender/blender_session.h"
|
#include "blender/blender_session.h"
|
||||||
|
|
||||||
|
#include "render/denoising.h"
|
||||||
|
|
||||||
#include "util/util_debug.h"
|
#include "util/util_debug.h"
|
||||||
#include "util/util_foreach.h"
|
#include "util/util_foreach.h"
|
||||||
#include "util/util_logging.h"
|
#include "util/util_logging.h"
|
||||||
@@ -203,10 +206,10 @@ static PyObject *exit_func(PyObject * /*self*/, PyObject * /*args*/)
|
|||||||
|
|
||||||
static PyObject *create_func(PyObject * /*self*/, PyObject *args)
|
static PyObject *create_func(PyObject * /*self*/, PyObject *args)
|
||||||
{
|
{
|
||||||
PyObject *pyengine, *pyuserpref, *pydata, *pyregion, *pyv3d, *pyrv3d;
|
PyObject *pyengine, *pypreferences, *pydata, *pyregion, *pyv3d, *pyrv3d;
|
||||||
int preview_osl;
|
int preview_osl;
|
||||||
|
|
||||||
if(!PyArg_ParseTuple(args, "OOOOOOi", &pyengine, &pyuserpref, &pydata,
|
if(!PyArg_ParseTuple(args, "OOOOOOi", &pyengine, &pypreferences, &pydata,
|
||||||
&pyregion, &pyv3d, &pyrv3d, &preview_osl))
|
&pyregion, &pyv3d, &pyrv3d, &preview_osl))
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -217,9 +220,9 @@ static PyObject *create_func(PyObject * /*self*/, PyObject *args)
|
|||||||
RNA_pointer_create(NULL, &RNA_RenderEngine, (void*)PyLong_AsVoidPtr(pyengine), &engineptr);
|
RNA_pointer_create(NULL, &RNA_RenderEngine, (void*)PyLong_AsVoidPtr(pyengine), &engineptr);
|
||||||
BL::RenderEngine engine(engineptr);
|
BL::RenderEngine engine(engineptr);
|
||||||
|
|
||||||
PointerRNA userprefptr;
|
PointerRNA preferencesptr;
|
||||||
RNA_pointer_create(NULL, &RNA_Preferences, (void*)PyLong_AsVoidPtr(pyuserpref), &userprefptr);
|
RNA_pointer_create(NULL, &RNA_Preferences, (void*)PyLong_AsVoidPtr(pypreferences), &preferencesptr);
|
||||||
BL::Preferences userpref(userprefptr);
|
BL::Preferences preferences(preferencesptr);
|
||||||
|
|
||||||
PointerRNA dataptr;
|
PointerRNA dataptr;
|
||||||
RNA_main_pointer_create((Main*)PyLong_AsVoidPtr(pydata), &dataptr);
|
RNA_main_pointer_create((Main*)PyLong_AsVoidPtr(pydata), &dataptr);
|
||||||
@@ -245,11 +248,11 @@ static PyObject *create_func(PyObject * /*self*/, PyObject *args)
|
|||||||
int width = region.width();
|
int width = region.width();
|
||||||
int height = region.height();
|
int height = region.height();
|
||||||
|
|
||||||
session = new BlenderSession(engine, userpref, data, v3d, rv3d, width, height);
|
session = new BlenderSession(engine, preferences, data, v3d, rv3d, width, height);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
/* offline session or preview render */
|
/* offline session or preview render */
|
||||||
session = new BlenderSession(engine, userpref, data, preview_osl);
|
session = new BlenderSession(engine, preferences, data, preview_osl);
|
||||||
}
|
}
|
||||||
|
|
||||||
return PyLong_FromVoidPtr(session);
|
return PyLong_FromVoidPtr(session);
|
||||||
@@ -627,6 +630,121 @@ static PyObject *opencl_disable_func(PyObject * /*self*/, PyObject * /*value*/)
|
|||||||
}
|
}
|
||||||
#endif
|
#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, *pyviewlayer;
|
||||||
|
PyObject *pyinput, *pyoutput = NULL;
|
||||||
|
int tile_size = 0, samples = 0;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, keywords, "OOOO|Oii", (char**)keyword_list,
|
||||||
|
&pypreferences, &pyscene, &pyviewlayer,
|
||||||
|
&pyinput, &pyoutput,
|
||||||
|
&tile_size, &samples)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get device specification from preferences and scene. */
|
||||||
|
PointerRNA preferencesptr;
|
||||||
|
RNA_pointer_create(NULL, &RNA_Preferences, (void*)PyLong_AsVoidPtr(pypreferences), &preferencesptr);
|
||||||
|
BL::Preferences 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 viewlayerptr;
|
||||||
|
RNA_pointer_create((ID*)PyLong_AsVoidPtr(pyscene), &RNA_ViewLayer, PyLong_AsVoidPtr(pyviewlayer), &viewlayerptr);
|
||||||
|
PointerRNA cviewlayer = RNA_pointer_get(&viewlayerptr, "cycles");
|
||||||
|
|
||||||
|
DenoiseParams params;
|
||||||
|
params.radius = get_int(cviewlayer, "denoising_radius");
|
||||||
|
params.strength = get_float(cviewlayer, "denoising_strength");
|
||||||
|
params.feature_strength = get_float(cviewlayer, "denoising_feature_strength");
|
||||||
|
params.relative_pca = get_boolean(cviewlayer, "denoising_relative_pca");
|
||||||
|
params.neighbor_frames = get_int(cviewlayer, "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)
|
static PyObject *debug_flags_update_func(PyObject * /*self*/, PyObject *args)
|
||||||
{
|
{
|
||||||
PyObject *pyscene;
|
PyObject *pyscene;
|
||||||
@@ -787,6 +905,9 @@ static PyMethodDef methods[] = {
|
|||||||
{"opencl_disable", opencl_disable_func, METH_NOARGS, ""},
|
{"opencl_disable", opencl_disable_func, METH_NOARGS, ""},
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Standalone denoising */
|
||||||
|
{"denoise", (PyCFunction)denoise_func, METH_VARARGS|METH_KEYWORDS, ""},
|
||||||
|
|
||||||
/* Debugging routines */
|
/* Debugging routines */
|
||||||
{"debug_flags_update", debug_flags_update_func, METH_VARARGS, ""},
|
{"debug_flags_update", debug_flags_update_func, METH_VARARGS, ""},
|
||||||
{"debug_flags_reset", debug_flags_reset_func, METH_NOARGS, ""},
|
{"debug_flags_reset", debug_flags_reset_func, METH_NOARGS, ""},
|
||||||
|
|||||||
@@ -455,18 +455,14 @@ void BlenderSession::render(BL::Depsgraph& b_depsgraph_)
|
|||||||
session->params.run_denoising = run_denoising;
|
session->params.run_denoising = run_denoising;
|
||||||
session->params.full_denoising = full_denoising;
|
session->params.full_denoising = full_denoising;
|
||||||
session->params.write_denoising_passes = write_denoising_passes;
|
session->params.write_denoising_passes = write_denoising_passes;
|
||||||
session->params.denoising_radius = get_int(crl, "denoising_radius");
|
session->params.denoising.radius = get_int(crl, "denoising_radius");
|
||||||
session->params.denoising_strength = get_float(crl, "denoising_strength");
|
session->params.denoising.strength = get_float(crl, "denoising_strength");
|
||||||
session->params.denoising_feature_strength = get_float(crl, "denoising_feature_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.relative_pca = get_boolean(crl, "denoising_relative_pca");
|
||||||
|
|
||||||
scene->film->denoising_data_pass = buffer_params.denoising_data_pass;
|
scene->film->denoising_data_pass = buffer_params.denoising_data_pass;
|
||||||
scene->film->denoising_clean_pass = buffer_params.denoising_clean_pass;
|
scene->film->denoising_clean_pass = buffer_params.denoising_clean_pass;
|
||||||
scene->film->denoising_prefiltered_pass = buffer_params.denoising_prefiltered_pass;
|
scene->film->denoising_prefiltered_pass = buffer_params.denoising_prefiltered_pass;
|
||||||
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->pass_alpha_threshold = b_view_layer.pass_alpha_threshold();
|
scene->film->pass_alpha_threshold = b_view_layer.pass_alpha_threshold();
|
||||||
scene->film->tag_passes_update(scene, passes);
|
scene->film->tag_passes_update(scene, passes);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
|
|
||||||
#include "device/device.h"
|
#include "device/device.h"
|
||||||
|
|
||||||
|
#include "blender/blender_device.h"
|
||||||
#include "blender/blender_sync.h"
|
#include "blender/blender_sync.h"
|
||||||
#include "blender/blender_session.h"
|
#include "blender/blender_session.h"
|
||||||
#include "blender/blender_util.h"
|
#include "blender/blender_util.h"
|
||||||
@@ -716,7 +717,7 @@ bool BlenderSync::get_session_pause(BL::Scene& b_scene, bool background)
|
|||||||
}
|
}
|
||||||
|
|
||||||
SessionParams BlenderSync::get_session_params(BL::RenderEngine& b_engine,
|
SessionParams BlenderSync::get_session_params(BL::RenderEngine& b_engine,
|
||||||
BL::Preferences& b_userpref,
|
BL::Preferences& b_preferences,
|
||||||
BL::Scene& b_scene,
|
BL::Scene& b_scene,
|
||||||
bool background)
|
bool background)
|
||||||
{
|
{
|
||||||
@@ -726,84 +727,12 @@ SessionParams BlenderSync::get_session_params(BL::RenderEngine& b_engine,
|
|||||||
/* feature set */
|
/* feature set */
|
||||||
params.experimental = (get_enum(cscene, "feature_set") != 0);
|
params.experimental = (get_enum(cscene, "feature_set") != 0);
|
||||||
|
|
||||||
/* threads */
|
|
||||||
BL::RenderSettings b_r = b_scene.render();
|
|
||||||
if(b_r.threads_mode() == BL::RenderSettings::threads_mode_FIXED)
|
|
||||||
params.threads = b_r.threads();
|
|
||||||
else
|
|
||||||
params.threads = 0;
|
|
||||||
|
|
||||||
/* Background */
|
/* Background */
|
||||||
params.background = background;
|
params.background = background;
|
||||||
|
|
||||||
/* Default to CPU device. */
|
/* Device */
|
||||||
params.device = Device::available_devices(DEVICE_MASK_CPU).front();
|
params.threads = blender_device_threads(b_scene);
|
||||||
|
params.device = blender_device_info(b_preferences, b_scene, params.background);
|
||||||
if(get_enum(cscene, "device") == 2) {
|
|
||||||
/* Find network device. */
|
|
||||||
vector<DeviceInfo> devices = Device::available_devices(DEVICE_MASK_NETWORK);
|
|
||||||
if(!devices.empty()) {
|
|
||||||
params.device = devices.front();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(get_enum(cscene, "device") == 1) {
|
|
||||||
/* Find cycles preferences. */
|
|
||||||
PointerRNA b_preferences;
|
|
||||||
|
|
||||||
BL::Preferences::addons_iterator b_addon_iter;
|
|
||||||
for(b_userpref.addons.begin(b_addon_iter); b_addon_iter != b_userpref.addons.end(); ++b_addon_iter) {
|
|
||||||
if(b_addon_iter->module() == "cycles") {
|
|
||||||
b_preferences = b_addon_iter->preferences().ptr;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Test if we are using GPU devices. */
|
|
||||||
enum ComputeDevice {
|
|
||||||
COMPUTE_DEVICE_CPU = 0,
|
|
||||||
COMPUTE_DEVICE_CUDA = 1,
|
|
||||||
COMPUTE_DEVICE_OPENCL = 2,
|
|
||||||
COMPUTE_DEVICE_NUM = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
ComputeDevice compute_device = (ComputeDevice)get_enum(b_preferences,
|
|
||||||
"compute_device_type",
|
|
||||||
COMPUTE_DEVICE_NUM,
|
|
||||||
COMPUTE_DEVICE_CPU);
|
|
||||||
|
|
||||||
if(compute_device != COMPUTE_DEVICE_CPU) {
|
|
||||||
/* Query GPU devices with matching types. */
|
|
||||||
uint mask = DEVICE_MASK_CPU;
|
|
||||||
if(compute_device == COMPUTE_DEVICE_CUDA) {
|
|
||||||
mask |= DEVICE_MASK_CUDA;
|
|
||||||
}
|
|
||||||
else if(compute_device == COMPUTE_DEVICE_OPENCL) {
|
|
||||||
mask |= DEVICE_MASK_OPENCL;
|
|
||||||
}
|
|
||||||
vector<DeviceInfo> devices = Device::available_devices(mask);
|
|
||||||
|
|
||||||
/* Match device preferences and available devices. */
|
|
||||||
vector<DeviceInfo> used_devices;
|
|
||||||
RNA_BEGIN(&b_preferences, device, "devices") {
|
|
||||||
if(get_boolean(device, "use")) {
|
|
||||||
string id = get_string(device, "id");
|
|
||||||
foreach(DeviceInfo& info, devices) {
|
|
||||||
if(info.id == id) {
|
|
||||||
used_devices.push_back(info);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} RNA_END;
|
|
||||||
|
|
||||||
if(!used_devices.empty()) {
|
|
||||||
params.device = Device::get_multi_device(used_devices,
|
|
||||||
params.threads,
|
|
||||||
params.background);
|
|
||||||
}
|
|
||||||
/* Else keep using the CPU device that was set before. */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* samples */
|
/* samples */
|
||||||
int samples = get_int(cscene, "samples");
|
int samples = get_int(cscene, "samples");
|
||||||
@@ -875,6 +804,7 @@ SessionParams BlenderSync::get_session_params(BL::RenderEngine& b_engine,
|
|||||||
params.text_timeout = (double)get_float(cscene, "debug_text_timeout");
|
params.text_timeout = (double)get_float(cscene, "debug_text_timeout");
|
||||||
|
|
||||||
/* progressive refine */
|
/* progressive refine */
|
||||||
|
BL::RenderSettings b_r = b_scene.render();
|
||||||
params.progressive_refine = (b_engine.is_preview() ||
|
params.progressive_refine = (b_engine.is_preview() ||
|
||||||
get_boolean(cscene, "use_progressive_refine")) &&
|
get_boolean(cscene, "use_progressive_refine")) &&
|
||||||
!b_r.use_save_buffers();
|
!b_r.use_save_buffers();
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ DenoisingTask::DenoisingTask(Device *device, const DeviceTask &task)
|
|||||||
buffer(device),
|
buffer(device),
|
||||||
device(device)
|
device(device)
|
||||||
{
|
{
|
||||||
radius = task.denoising_radius;
|
radius = task.denoising.radius;
|
||||||
nlm_k_2 = powf(2.0f, lerp(-5.0f, 3.0f, task.denoising_strength));
|
nlm_k_2 = powf(2.0f, lerp(-5.0f, 3.0f, task.denoising.strength));
|
||||||
if(task.denoising_relative_pca) {
|
if(task.denoising.relative_pca) {
|
||||||
pca_threshold = -powf(10.0f, lerp(-8.0f, 0.0f, task.denoising_feature_strength));
|
pca_threshold = -powf(10.0f, lerp(-8.0f, 0.0f, task.denoising.feature_strength));
|
||||||
}
|
}
|
||||||
else {
|
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;
|
render_buffer.frame_stride = task.frame_stride;
|
||||||
|
|||||||
@@ -32,6 +32,32 @@ class RenderBuffers;
|
|||||||
class RenderTile;
|
class RenderTile;
|
||||||
class Tile;
|
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 {
|
class DeviceTask : public Task {
|
||||||
public:
|
public:
|
||||||
typedef enum { RENDER, FILM_CONVERT, SHADER } Type;
|
typedef enum { RENDER, FILM_CONVERT, SHADER } Type;
|
||||||
@@ -68,10 +94,7 @@ public:
|
|||||||
function<void(RenderTile*, Device*)> map_neighbor_tiles;
|
function<void(RenderTile*, Device*)> map_neighbor_tiles;
|
||||||
function<void(RenderTile*, Device*)> unmap_neighbor_tiles;
|
function<void(RenderTile*, Device*)> unmap_neighbor_tiles;
|
||||||
|
|
||||||
int denoising_radius;
|
DenoiseParams denoising;
|
||||||
float denoising_strength;
|
|
||||||
float denoising_feature_strength;
|
|
||||||
bool denoising_relative_pca;
|
|
||||||
bool denoising_from_render;
|
bool denoising_from_render;
|
||||||
vector<int> denoising_frames;
|
vector<int> denoising_frames;
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ set(SRC
|
|||||||
camera.cpp
|
camera.cpp
|
||||||
constant_fold.cpp
|
constant_fold.cpp
|
||||||
coverage.cpp
|
coverage.cpp
|
||||||
|
denoising.cpp
|
||||||
film.cpp
|
film.cpp
|
||||||
graph.cpp
|
graph.cpp
|
||||||
image.cpp
|
image.cpp
|
||||||
@@ -48,6 +49,7 @@ set(SRC_HEADERS
|
|||||||
camera.h
|
camera.h
|
||||||
constant_fold.h
|
constant_fold.h
|
||||||
coverage.h
|
coverage.h
|
||||||
|
denoising.h
|
||||||
film.h
|
film.h
|
||||||
graph.h
|
graph.h
|
||||||
image.h
|
image.h
|
||||||
|
|||||||
887
intern/cycles/render/denoising.cpp
Normal file
887
intern/cycles/render/denoising.cpp
Normal file
@@ -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
|
||||||
201
intern/cycles/render/denoising.h
Normal file
201
intern/cycles/render/denoising.h
Normal file
@@ -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__ */
|
||||||
@@ -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);
|
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());
|
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)
|
else if(tile_manager.num_samples == INT_MAX)
|
||||||
substatus = string_printf("Path Tracing Sample %d", progressive_sample+1);
|
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();
|
task.passes_size = tile_manager.params.get_passes_size();
|
||||||
|
|
||||||
if(params.run_denoising) {
|
if(params.run_denoising) {
|
||||||
task.denoising_radius = params.denoising_radius;
|
task.denoising = params.denoising;
|
||||||
task.denoising_strength = params.denoising_strength;
|
|
||||||
task.denoising_feature_strength = params.denoising_feature_strength;
|
|
||||||
task.denoising_relative_pca = params.denoising_relative_pca;
|
|
||||||
|
|
||||||
assert(!scene->film->need_update);
|
assert(!scene->film->need_update);
|
||||||
task.pass_stride = scene->film->pass_stride;
|
task.pass_stride = scene->film->pass_stride;
|
||||||
|
|||||||
@@ -63,10 +63,7 @@ public:
|
|||||||
bool run_denoising;
|
bool run_denoising;
|
||||||
bool write_denoising_passes;
|
bool write_denoising_passes;
|
||||||
bool full_denoising;
|
bool full_denoising;
|
||||||
int denoising_radius;
|
DenoiseParams denoising;
|
||||||
float denoising_strength;
|
|
||||||
float denoising_feature_strength;
|
|
||||||
bool denoising_relative_pca;
|
|
||||||
|
|
||||||
double cancel_timeout;
|
double cancel_timeout;
|
||||||
double reset_timeout;
|
double reset_timeout;
|
||||||
@@ -98,10 +95,6 @@ public:
|
|||||||
run_denoising = false;
|
run_denoising = false;
|
||||||
write_denoising_passes = false;
|
write_denoising_passes = false;
|
||||||
full_denoising = false;
|
full_denoising = false;
|
||||||
denoising_radius = 8;
|
|
||||||
denoising_strength = 0.0f;
|
|
||||||
denoising_feature_strength = 0.0f;
|
|
||||||
denoising_relative_pca = false;
|
|
||||||
|
|
||||||
display_buffer_linear = false;
|
display_buffer_linear = false;
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
# include <sys/types.h>
|
# include <sys/types.h>
|
||||||
#else
|
#else
|
||||||
# include <unistd.h>
|
# include <unistd.h>
|
||||||
|
# include <sys/ioctl.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
CCL_NAMESPACE_BEGIN
|
CCL_NAMESPACE_BEGIN
|
||||||
@@ -113,6 +114,25 @@ bool system_cpu_run_thread_on_node(int node)
|
|||||||
return numaAPI_RunThreadOnNode(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()
|
int system_cpu_num_active_group_processors()
|
||||||
{
|
{
|
||||||
if(!system_cpu_ensure_initialized()) {
|
if(!system_cpu_ensure_initialized()) {
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ bool system_cpu_ensure_initialized();
|
|||||||
/* Get total number of threads in all NUMA nodes / CPU groups. */
|
/* Get total number of threads in all NUMA nodes / CPU groups. */
|
||||||
int system_cpu_thread_count();
|
int system_cpu_thread_count();
|
||||||
|
|
||||||
|
/* Get width in characters of the current console output. */
|
||||||
|
int system_console_width();
|
||||||
|
|
||||||
/* Get number of available nodes.
|
/* Get number of available nodes.
|
||||||
*
|
*
|
||||||
* This is in fact an index of last node plus one and it's not guaranteed
|
* This is in fact an index of last node plus one and it's not guaranteed
|
||||||
|
|||||||
Reference in New Issue
Block a user