Merge branch 'blender2.7'
authorBrecht Van Lommel <brechtvanlommel@gmail.com>
Wed, 6 Feb 2019 14:22:53 +0000 (15:22 +0100)
committerBrecht Van Lommel <brechtvanlommel@gmail.com>
Wed, 6 Feb 2019 14:22:53 +0000 (15:22 +0100)
1  2 
intern/cycles/blender/addon/engine.py
intern/cycles/blender/blender_session.cpp
intern/cycles/blender/blender_sync.cpp
intern/cycles/device/device_cpu.cpp
intern/cycles/device/device_cuda.cpp
intern/cycles/render/buffers.cpp
intern/cycles/render/film.cpp
intern/cycles/render/session.cpp

@@@ -81,7 -81,7 +81,7 @@@ def _parse_command_line()
          return
  
      parser = _configure_argument_parser()
 -    args, unknown = parser.parse_known_args(argv[argv.index("--") + 1:])
 +    args, _ = parser.parse_known_args(argv[argv.index("--") + 1:])
  
      if args.cycles_resumable_num_chunks is not None:
          if args.cycles_resumable_current_chunk is not None:
@@@ -133,12 -133,13 +133,12 @@@ def exit()
      _cycles.exit()
  
  
 -def create(engine, data, scene, region=None, v3d=None, rv3d=None, preview_osl=False):
 -    import bpy
 +def create(engine, data, region=None, v3d=None, rv3d=None, preview_osl=False):
      import _cycles
 +    import bpy
  
      data = data.as_pointer()
 -    userpref = bpy.context.user_preferences.as_pointer()
 -    scene = scene.as_pointer()
 +    prefs = bpy.context.preferences.as_pointer()
      if region:
          region = region.as_pointer()
      if v3d:
      if rv3d:
          rv3d = rv3d.as_pointer()
  
 -    if bpy.app.debug_value == 256:
 -        _cycles.debug_flags_update(scene)
 -    else:
 -        _cycles.debug_flags_reset()
 -
 -    engine.session = _cycles.create(engine.as_pointer(), userpref, data, scene, region, v3d, rv3d, preview_osl)
 +    engine.session = _cycles.create(
 +            engine.as_pointer(), prefs, data, region, v3d, rv3d, preview_osl)
  
  
  def free(engine):
          del engine.session
  
  
 -def render(engine):
 +def render(engine, depsgraph):
      import _cycles
      if hasattr(engine, "session"):
 -        _cycles.render(engine.session)
 +        _cycles.render(engine.session, depsgraph.as_pointer())
  
  
 -def bake(engine, obj, pass_type, pass_filter, object_id, pixel_array, num_pixels, depth, result):
 +def bake(engine, depsgraph, obj, pass_type, pass_filter, object_id, pixel_array, num_pixels, depth, result):
      import _cycles
      session = getattr(engine, "session", None)
      if session is not None:
 -        _cycles.bake(engine.session, obj.as_pointer(), pass_type, pass_filter, object_id, pixel_array.as_pointer(), num_pixels, depth, result.as_pointer())
 +        _cycles.bake(engine.session, depsgraph.as_pointer(), obj.as_pointer(), pass_type, pass_filter, object_id, pixel_array.as_pointer(), num_pixels, depth, result.as_pointer())
  
  
 -def reset(engine, data, scene):
 +def reset(engine, data, depsgraph):
      import _cycles
 +    import bpy
 +
 +    if bpy.app.debug_value == 256:
 +        _cycles.debug_flags_update(depsgraph.scene.as_pointer())
 +    else:
 +        _cycles.debug_flags_reset()
 +
      data = data.as_pointer()
 -    scene = scene.as_pointer()
 -    _cycles.reset(engine.session, data, scene)
 +    depsgraph = depsgraph.as_pointer()
 +    _cycles.reset(engine.session, data, depsgraph)
  
  
 -def update(engine, data, scene):
 +def sync(engine, depsgraph, data):
      import _cycles
 -    _cycles.sync(engine.session)
 +    _cycles.sync(engine.session, depsgraph.as_pointer())
  
  
 -def draw(engine, region, v3d, rv3d):
 +def draw(engine, depsgraph, region, v3d, rv3d):
      import _cycles
 +    depsgraph = depsgraph.as_pointer()
      v3d = v3d.as_pointer()
      rv3d = rv3d.as_pointer()
  
      # draw render image
 -    _cycles.draw(engine.session, v3d, rv3d)
 +    _cycles.draw(engine.session, depsgraph, v3d, rv3d)
  
  
  def available_devices():
@@@ -256,6 -253,8 +256,6 @@@ def register_passes(engine, scene, srl)
      if crl.use_pass_volume_direct:             engine.register_pass(scene, srl, "VolumeDir",                     3, "RGB", 'COLOR')
      if crl.use_pass_volume_indirect:           engine.register_pass(scene, srl, "VolumeInd",                     3, "RGB", 'COLOR')
  
 -    cscene = scene.cycles
 -
      if crl.use_pass_crypto_object:
          for i in range(0, crl.pass_crypto_depth, 2):
              engine.register_pass(scene, srl, "CryptoObject" + '{:02d}'.format(i), 4, "RGBA", 'COLOR')
          engine.register_pass(scene, srl, "Noisy Image", 4, "RGBA", 'COLOR')
          if crl.denoising_store_passes:
              engine.register_pass(scene, srl, "Denoising Normal",          3, "XYZ", 'VECTOR')
-             engine.register_pass(scene, srl, "Denoising Normal Variance", 3, "XYZ", 'VECTOR')
              engine.register_pass(scene, srl, "Denoising Albedo",          3, "RGB", 'COLOR')
-             engine.register_pass(scene, srl, "Denoising Albedo Variance", 3, "RGB", 'COLOR')
              engine.register_pass(scene, srl, "Denoising Depth",           1, "Z",   'VALUE')
-             engine.register_pass(scene, srl, "Denoising Depth Variance",  1, "Z",   'VALUE')
-             engine.register_pass(scene, srl, "Denoising Shadow A",        3, "XYV", 'VECTOR')
-             engine.register_pass(scene, srl, "Denoising Shadow B",        3, "XYV", 'VECTOR')
-             engine.register_pass(scene, srl, "Denoising Image Variance",  3, "RGB", 'COLOR')
+             engine.register_pass(scene, srl, "Denoising Shadowing",       1, "X",   'VALUE')
+             engine.register_pass(scene, srl, "Denoising Variance",        3, "RGB", 'COLOR')
+             engine.register_pass(scene, srl, "Denoising Intensity",       1, "X",   'VALUE')
              clean_options = ("denoising_diffuse_direct", "denoising_diffuse_indirect",
                               "denoising_glossy_direct", "denoising_glossy_indirect",
                               "denoising_transmission_direct", "denoising_transmission_indirect",
@@@ -30,7 -30,6 +30,7 @@@
  #include "render/shader.h"
  #include "render/stats.h"
  
 +#include "util/util_algorithm.h"
  #include "util/util_color.h"
  #include "util/util_foreach.h"
  #include "util/util_function.h"
@@@ -54,25 -53,23 +54,25 @@@ int BlenderSession::end_resumable_chun
  bool BlenderSession::print_render_stats = false;
  
  BlenderSession::BlenderSession(BL::RenderEngine& b_engine,
 -                               BL::UserPreferences& b_userpref,
 +                               BL::Preferences& b_userpref,
                                 BL::BlendData& b_data,
 -                               BL::Scene& b_scene)
 -: b_engine(b_engine),
 +                               bool preview_osl)
 +: session(NULL),
 +  sync(NULL),
 +  b_engine(b_engine),
    b_userpref(b_userpref),
    b_data(b_data),
    b_render(b_engine.render()),
 -  b_scene(b_scene),
 +  b_depsgraph(PointerRNA_NULL),
 +  b_scene(PointerRNA_NULL),
    b_v3d(PointerRNA_NULL),
    b_rv3d(PointerRNA_NULL),
 +  width(0),
 +  height(0),
 +  preview_osl(preview_osl),
    python_thread_state(NULL)
  {
        /* offline render */
 -
 -      width = render_resolution_x(b_render);
 -      height = render_resolution_y(b_render);
 -
        background = true;
        last_redraw_time = 0.0;
        start_resize_time = 0.0;
  }
  
  BlenderSession::BlenderSession(BL::RenderEngine& b_engine,
 -                               BL::UserPreferences& b_userpref,
 +                               BL::Preferences& b_userpref,
                                 BL::BlendData& b_data,
 -                               BL::Scene& b_scene,
                                 BL::SpaceView3D& b_v3d,
                                 BL::RegionView3D& b_rv3d,
                                 int width, int height)
 -: b_engine(b_engine),
 +: session(NULL),
 +  sync(NULL),
 +  b_engine(b_engine),
    b_userpref(b_userpref),
    b_data(b_data),
 -  b_render(b_scene.render()),
 -  b_scene(b_scene),
 +  b_render(b_engine.render()),
 +  b_depsgraph(PointerRNA_NULL),
 +  b_scene(PointerRNA_NULL),
    b_v3d(b_v3d),
    b_rv3d(b_rv3d),
    width(width),
    height(height),
 +  preview_osl(false),
    python_thread_state(NULL)
  {
        /* 3d view render */
 -
        background = false;
        last_redraw_time = 0.0;
        start_resize_time = 0.0;
@@@ -138,7 -133,6 +138,7 @@@ void BlenderSession::create_session(
  
        /* create scene */
        scene = new Scene(scene_params, session->device);
 +      scene->name = b_scene.name();
  
        /* setup callbacks for builtin image support */
        scene->image_manager->builtin_image_info_cb = function_bind(&BlenderSession::builtin_image_info, this, _1, _2, _3);
  
        session->scene = scene;
  
 +      /* There is no single depsgraph to use for the entire render.
 +       * So we need to handle this differently.
 +       *
 +       * We could loop over the final render result render layers in pipeline and keep Cycles unaware of multiple layers,
 +       * or perhaps move syncing further down in the pipeline.
 +       */
        /* create sync */
        sync = new BlenderSync(b_engine, b_data, b_scene, scene, !background, session->progress);
        BL::Object b_camera_override(b_engine.camera_override());
        if(b_v3d) {
 -              if(session_pause == false) {
 -                      /* full data sync */
 -                      sync->sync_view(b_v3d, b_rv3d, width, height);
 -                      sync->sync_data(b_render,
 -                                      b_v3d,
 -                                      b_camera_override,
 -                                      width, height,
 -                                      &python_thread_state,
 -                                      b_rlay_name.c_str());
 -              }
 +              sync->sync_view(b_v3d, b_rv3d, width, height);
        }
        else {
 -              /* for final render we will do full data sync per render layer, only
 -               * do some basic syncing here, no objects or materials for speed */
 -              sync->sync_render_layers(b_v3d, NULL);
 -              sync->sync_integrator();
                sync->sync_camera(b_render, b_camera_override, width, height, "");
        }
  
        update_resumable_tile_manager(session_params.samples);
  }
  
 -void BlenderSession::reset_session(BL::BlendData& b_data_, BL::Scene& b_scene_)
 +void BlenderSession::reset_session(BL::BlendData& b_data, BL::Depsgraph& b_depsgraph)
  {
 -      b_data = b_data_;
 -      b_render = b_engine.render();
 -      b_scene = b_scene_;
 +      this->b_data = b_data;
 +      this->b_depsgraph = b_depsgraph;
 +      this->b_scene = b_depsgraph.scene_eval();
 +
 +      if(preview_osl) {
 +              PointerRNA cscene = RNA_pointer_get(&b_scene.ptr, "cycles");
 +              RNA_boolean_set(&cscene, "shading_system", preview_osl);
 +      }
 +
 +      if(b_v3d) {
 +              this->b_render = b_scene.render();
 +      }
 +      else {
 +              this->b_render = b_engine.render();
 +              width = render_resolution_x(b_render);
 +              height = render_resolution_y(b_render);
 +      }
 +
 +      if(session == NULL) {
 +              create();
 +      }
 +
 +      if(b_v3d) {
 +              /* NOTE: We need to create session, but all the code from below
 +               * will make viewport render to stuck on initialization.
 +               */
 +              return;
 +      }
  
        SessionParams session_params = BlenderSync::get_session_params(b_engine, b_userpref, b_scene, background);
        SceneParams scene_params = BlenderSync::get_scene_params(b_scene, background);
  
 -      width = render_resolution_x(b_render);
 -      height = render_resolution_y(b_render);
 -
        if(scene->params.modified(scene_params) ||
           session->params.modified(session_params) ||
           !scene_params.persistent_data)
                /* if scene or session parameters changed, it's easier to simply re-create
                 * them rather than trying to distinguish which settings need to be updated
                 */
 -
 -              delete session;
 -
 +              free_session();
                create_session();
 -
                return;
        }
  
         */
        session->stats.mem_peak = session->stats.mem_used;
  
 +      /* There is no single depsgraph to use for the entire render.
 +       * See note on create_session().
 +       */
        /* sync object should be re-created */
        sync = new BlenderSync(b_engine, b_data, b_scene, scene, !background, session->progress);
  
 -      /* for final render we will do full data sync per render layer, only
 -       * do some basic syncing here, no objects or materials for speed */
 -      BL::Object b_camera_override(b_engine.camera_override());
 -      sync->sync_render_layers(b_v3d, NULL);
 -      sync->sync_integrator();
 -      sync->sync_camera(b_render, b_camera_override, width, height, "");
 -
        BL::SpaceView3D b_null_space_view3d(PointerRNA_NULL);
        BL::RegionView3D b_null_region_view3d(PointerRNA_NULL);
        BufferParams buffer_params = BlenderSync::get_buffer_params(b_render,
@@@ -393,10 -379,8 +393,10 @@@ static void add_cryptomatte_layer(BL::R
        render_add_metadata(b_rr, prefix+"manifest", manifest);
  }
  
 -void BlenderSession::render()
 +void BlenderSession::render(BL::Depsgraph& b_depsgraph_)
  {
 +      b_depsgraph = b_depsgraph_;
 +
        /* set callback to write out render results */
        session->write_render_tile_cb = function_bind(&BlenderSession::write_render_tile, this, _1);
        session->update_render_tile_cb = function_bind(&BlenderSession::update_render_tile, this, _1, _2);
        BufferParams buffer_params = BlenderSync::get_buffer_params(b_render, b_v3d, b_rv3d, scene->camera, width, height);
  
        /* render each layer */
 -      BL::RenderSettings r = b_scene.render();
 -      BL::RenderSettings::layers_iterator b_layer_iter;
 -      BL::RenderResult::views_iterator b_view_iter;
 +      BL::ViewLayer b_view_layer = b_depsgraph.view_layer_eval();
  
        /* We do some special meta attributes when we only have single layer. */
 -      const bool is_single_layer = (r.layers.length() == 1);
 +      const bool is_single_layer = (b_scene.view_layers.length() == 1);
  
 -      for(r.layers.begin(b_layer_iter); b_layer_iter != r.layers.end(); ++b_layer_iter) {
 -              b_rlay_name = b_layer_iter->name();
 -
 -              /* temporary render result to find needed passes and views */
 -              BL::RenderResult b_rr = begin_render_result(b_engine, 0, 0, 1, 1, b_rlay_name.c_str(), NULL);
 -              BL::RenderResult::layers_iterator b_single_rlay;
 -              b_rr.layers.begin(b_single_rlay);
 +      /* temporary render result to find needed passes and views */
 +      BL::RenderResult b_rr = begin_render_result(b_engine, 0, 0, 1, 1, b_view_layer.name().c_str(), NULL);
 +      BL::RenderResult::layers_iterator b_single_rlay;
 +      b_rr.layers.begin(b_single_rlay);
 +      BL::RenderLayer b_rlay = *b_single_rlay;
 +      b_rlay_name = b_view_layer.name();
 +
 +      /* add passes */
 +      vector<Pass> passes = sync->sync_render_passes(b_rlay, b_view_layer, session_params);
 +      buffer_params.passes = passes;
 +
 +      PointerRNA crl = RNA_pointer_get(&b_view_layer.ptr, "cycles");
-       bool use_denoising = get_boolean(crl, "use_denoising");
-       bool denoising_passes = use_denoising || get_boolean(crl, "denoising_store_passes");
++      bool full_denoising = get_boolean(crl, "use_denoising");
++      bool write_denoising_passes = get_boolean(crl, "denoising_store_passes");
 +
-       session->tile_manager.schedule_denoising = use_denoising;
-       buffer_params.denoising_data_pass = denoising_passes;
++      bool run_denoising = full_denoising || write_denoising_passes;
++
++      session->tile_manager.schedule_denoising = run_denoising;
++      buffer_params.denoising_data_pass = run_denoising;
 +      buffer_params.denoising_clean_pass = (scene->film->denoising_flags & DENOISING_CLEAN_ALL_PASSES);
++      buffer_params.denoising_prefiltered_pass = write_denoising_passes;
 +
-       session->params.use_denoising = use_denoising;
-       session->params.denoising_passes = denoising_passes;
++      session->params.run_denoising = run_denoising;
++      session->params.full_denoising = full_denoising;
++      session->params.write_denoising_passes = write_denoising_passes;
 +      session->params.denoising_radius = get_int(crl, "denoising_radius");
 +      session->params.denoising_strength = get_float(crl, "denoising_strength");
 +      session->params.denoising_feature_strength = get_float(crl, "denoising_feature_strength");
 +      session->params.denoising_relative_pca = get_boolean(crl, "denoising_relative_pca");
 +
 +      scene->film->denoising_data_pass = buffer_params.denoising_data_pass;
 +      scene->film->denoising_clean_pass = buffer_params.denoising_clean_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->tag_passes_update(scene, passes);
 +      scene->film->tag_update(scene);
 +      scene->integrator->tag_update(scene);
  
 -              /* layer will be missing if it was disabled in the UI */
 -              if(b_single_rlay == b_rr.layers.end()) {
 -                      end_render_result(b_engine, b_rr, true, true, false);
 -                      continue;
 -              }
 +      BL::RenderResult::views_iterator b_view_iter;
  
 -              BL::RenderLayer b_rlay = *b_single_rlay;
 -
 -              /* add passes */
 -              vector<Pass> passes = sync->sync_render_passes(b_rlay, *b_layer_iter, session_params);
 -              buffer_params.passes = passes;
 -
 -              PointerRNA crl = RNA_pointer_get(&b_layer_iter->ptr, "cycles");
 -              bool full_denoising = get_boolean(crl, "use_denoising");
 -              bool write_denoising_passes = get_boolean(crl, "denoising_store_passes");
 -
 -              bool run_denoising = full_denoising || write_denoising_passes;
 -
 -              session->tile_manager.schedule_denoising = run_denoising;
 -              buffer_params.denoising_data_pass = run_denoising;
 -              buffer_params.denoising_clean_pass = (scene->film->denoising_flags & DENOISING_CLEAN_ALL_PASSES);
 -              buffer_params.denoising_prefiltered_pass = write_denoising_passes;
 -
 -              session->params.run_denoising = run_denoising;
 -              session->params.full_denoising = full_denoising;
 -              session->params.write_denoising_passes = write_denoising_passes;
 -              session->params.denoising_radius = get_int(crl, "denoising_radius");
 -              session->params.denoising_strength = get_float(crl, "denoising_strength");
 -              session->params.denoising_feature_strength = get_float(crl, "denoising_feature_strength");
 -              session->params.denoising_relative_pca = get_boolean(crl, "denoising_relative_pca");
 -
 -              scene->film->denoising_data_pass = buffer_params.denoising_data_pass;
 -              scene->film->denoising_clean_pass = buffer_params.denoising_clean_pass;
 -              scene->film->denoising_prefiltered_pass = buffer_params.denoising_prefiltered_pass;
 -              scene->film->pass_alpha_threshold = b_layer_iter->pass_alpha_threshold();
 -              scene->film->tag_passes_update(scene, passes);
 -              scene->film->tag_update(scene);
 -              scene->integrator->tag_update(scene);
 -
 -              int view_index = 0;
 -              for(b_rr.views.begin(b_view_iter); b_view_iter != b_rr.views.end(); ++b_view_iter, ++view_index) {
 -                      b_rview_name = b_view_iter->name();
 -
 -                      /* set the current view */
 -                      b_engine.active_view_set(b_rview_name.c_str());
 -
 -                      /* update scene */
 -                      BL::Object b_camera_override(b_engine.camera_override());
 -                      sync->sync_camera(b_render, b_camera_override, width, height, b_rview_name.c_str());
 -                      sync->sync_data(b_render,
 -                                      b_v3d,
 -                                      b_camera_override,
 -                                      width, height,
 -                                      &python_thread_state,
 -                                      b_rlay_name.c_str());
 -
 -                      /* Make sure all views have different noise patterns. - hardcoded value just to make it random */
 -                      if(view_index != 0) {
 -                              scene->integrator->seed += hash_int_2d(scene->integrator->seed, hash_int(view_index * 0xdeadbeef));
 -                              scene->integrator->tag_update(scene);
 -                      }
 +      int num_views = 0;
 +      for(b_rr.views.begin(b_view_iter); b_view_iter != b_rr.views.end(); ++b_view_iter) {
 +              num_views++;
 +      }
  
 -                      /* Update number of samples per layer. */
 -                      int samples = sync->get_layer_samples();
 -                      bool bound_samples = sync->get_layer_bound_samples();
 -                      int effective_layer_samples;
 +      int view_index = 0;
 +      for(b_rr.views.begin(b_view_iter); b_view_iter != b_rr.views.end(); ++b_view_iter, ++view_index) {
 +              b_rview_name = b_view_iter->name();
  
 -                      if(samples != 0 && (!bound_samples || (samples < session_params.samples)))
 -                              effective_layer_samples = samples;
 -                      else
 -                              effective_layer_samples = session_params.samples;
 +              /* set the current view */
 +              b_engine.active_view_set(b_rview_name.c_str());
  
 -                      /* Update tile manager if we're doing resumable render. */
 -                      update_resumable_tile_manager(effective_layer_samples);
 +              /* update scene */
 +              BL::Object b_camera_override(b_engine.camera_override());
 +              sync->sync_camera(b_render, b_camera_override, width, height, b_rview_name.c_str());
 +              sync->sync_data(b_render,
 +                              b_depsgraph,
 +                              b_v3d,
 +                              b_camera_override,
 +                              width, height,
 +                              &python_thread_state);
 +              builtin_images_load();
 +
 +              /* Attempt to free all data which is held by Blender side, since at this
 +               * point we knwo that we've got everything to render current view layer.
 +               */
 +              /* At the moment we only free if we are not doing multi-view (or if we are rendering the last view).
 +               * See T58142/D4239 for discussion.
 +               */
 +              if(view_index == num_views - 1) {
 +                      free_blender_memory_if_possible();
 +              }
  
 -                      /* Update session itself. */
 -                      session->reset(buffer_params, effective_layer_samples);
 +              /* Make sure all views have different noise patterns. - hardcoded value just to make it random */
 +              if(view_index != 0) {
 +                      scene->integrator->seed += hash_int_2d(scene->integrator->seed, hash_int(view_index * 0xdeadbeef));
 +                      scene->integrator->tag_update(scene);
 +              }
  
 -                      /* render */
 -                      session->start();
 -                      session->wait();
 +              /* Update number of samples per layer. */
 +              int samples = sync->get_layer_samples();
 +              bool bound_samples = sync->get_layer_bound_samples();
 +              int effective_layer_samples;
  
 -                      if(session->progress.get_cancel())
 -                              break;
 -              }
 +              if(samples != 0 && (!bound_samples || (samples < session_params.samples)))
 +                      effective_layer_samples = samples;
 +              else
 +                      effective_layer_samples = session_params.samples;
  
 -              BL::RenderResult b_full_rr = b_engine.get_result();
 -              if(is_single_layer) {
 -                      string num_aa_samples = string_printf("%d", session->params.samples);
 -                      render_add_metadata(b_full_rr, "Cycles Samples", num_aa_samples);
 -                      /* TODO(sergey): Report whether we're doing resumable render
 -                       * and also start/end sample if so.
 -                       */
 -              }
 +              /* Update tile manager if we're doing resumable render. */
 +              update_resumable_tile_manager(effective_layer_samples);
  
 -              if(scene->film->cryptomatte_passes & CRYPT_OBJECT) {
 -                      add_cryptomatte_layer(b_full_rr, b_rlay_name+".CryptoObject",
 -                                            scene->object_manager->get_cryptomatte_objects(scene));
 -              }
 -              if(scene->film->cryptomatte_passes & CRYPT_MATERIAL) {
 -                      add_cryptomatte_layer(b_full_rr, b_rlay_name+".CryptoMaterial",
 -                                            scene->shader_manager->get_cryptomatte_materials(scene));
 -              }
 -              if(scene->film->cryptomatte_passes & CRYPT_ASSET) {
 -                      add_cryptomatte_layer(b_full_rr, b_rlay_name+".CryptoAsset",
 -                                            scene->object_manager->get_cryptomatte_assets(scene));
 -              }
 +              /* Update session itself. */
 +              session->reset(buffer_params, effective_layer_samples);
  
 -              /* free result without merging */
 -              end_render_result(b_engine, b_rr, true, true, false);
 +              /* render */
 +              session->start();
 +              session->wait();
  
                if(!b_engine.is_preview() && background && print_render_stats) {
                        RenderStats stats;
                        break;
        }
  
 +      if(is_single_layer) {
 +              BL::RenderResult b_rr = b_engine.get_result();
 +              string num_aa_samples = string_printf("%d", session->params.samples);
 +              b_rr.stamp_data_add_field("Cycles Samples", num_aa_samples.c_str());
 +              /* TODO(sergey): Report whether we're doing resumable render
 +               * and also start/end sample if so.
 +               */
 +      }
 +
 +      /* Write cryptomatte metadata. */
 +      if(scene->film->cryptomatte_passes & CRYPT_OBJECT) {
 +              add_cryptomatte_layer(b_rr, b_rlay_name+".CryptoObject",
 +                                                        scene->object_manager->get_cryptomatte_objects(scene));
 +      }
 +      if(scene->film->cryptomatte_passes & CRYPT_MATERIAL) {
 +              add_cryptomatte_layer(b_rr, b_rlay_name+".CryptoMaterial",
 +                                                        scene->shader_manager->get_cryptomatte_materials(scene));
 +      }
 +      if(scene->film->cryptomatte_passes & CRYPT_ASSET) {
 +              add_cryptomatte_layer(b_rr, b_rlay_name+".CryptoAsset",
 +                                                        scene->object_manager->get_cryptomatte_assets(scene));
 +      }
 +
 +      /* free result without merging */
 +      end_render_result(b_engine, b_rr, true, true, false);
 +
        double total_time, render_time;
        session->progress.get_time(total_time, render_time);
        VLOG(1) << "Total render time: " << total_time;
        session->write_render_tile_cb = function_null;
        session->update_render_tile_cb = function_null;
  
 +      /* TODO: find a way to clear this data for persistent data render */
 +#if 0
        /* free all memory used (host and device), so we wouldn't leave render
         * engine with extra memory allocated
         */
  
        delete sync;
        sync = NULL;
 +#endif
  }
  
  static void populate_bake_data(BakeData *data, const
@@@ -614,8 -591,7 +619,8 @@@ static int bake_pass_filter_get(const i
        return flag;
  }
  
 -void BlenderSession::bake(BL::Object& b_object,
 +void BlenderSession::bake(BL::Depsgraph& b_depsgraph_,
 +                          BL::Object& b_object,
                            const string& pass_type,
                            const int pass_filter,
                            const int object_id,
                            const int /*depth*/,
                            float result[])
  {
 +      b_depsgraph = b_depsgraph_;
 +
        ShaderEvalType shader_type = get_shader_type(pass_type);
  
        /* Set baking flag in advance, so kernel loading can check if we need
                BL::Object b_camera_override(b_engine.camera_override());
                sync->sync_camera(b_render, b_camera_override, width, height, "");
                sync->sync_data(b_render,
 -                                              b_v3d,
 -                                              b_camera_override,
 -                                              width, height,
 -                                              &python_thread_state,
 -                                              b_rlay_name.c_str());
 +                              b_depsgraph,
 +                              b_v3d,
 +                              b_camera_override,
 +                              width, height,
 +                              &python_thread_state);
 +              builtin_images_load();
        }
  
        BakeData *bake_data = NULL;
                        }
                }
  
 -              int object = object_index;
 +              /* Object might have been disabled for rendering or excluded in some
 +               * other way, in that case Blender will report a warning afterwards. */
 +              if (object_index != OBJECT_NONE) {
 +                      int object = object_index;
  
 -              bake_data = scene->bake_manager->init(object, tri_offset, num_pixels);
 -              populate_bake_data(bake_data, object_id, pixel_array, num_pixels);
 +                      bake_data = scene->bake_manager->init(object, tri_offset, num_pixels);
 +                      populate_bake_data(bake_data, object_id, pixel_array, num_pixels);
 +              }
  
                /* set number of samples */
                session->tile_manager.set_samples(session_params.samples);
        }
  
        /* Perform bake. Check cancel to avoid crash with incomplete scene data. */
 -      if(!session->progress.get_cancel()) {
 +      if(!session->progress.get_cancel() && bake_data) {
                scene->bake_manager->bake(scene->device, &scene->dscene, scene, session->progress, shader_type, bake_pass_filter, bake_data, result);
        }
  
@@@ -801,7 -770,7 +806,7 @@@ void BlenderSession::update_render_resu
        do_write_update_render_result(b_rr, b_rlay, rtile, true);
  }
  
 -void BlenderSession::synchronize()
 +void BlenderSession::synchronize(BL::Depsgraph& b_depsgraph_)
  {
        /* only used for viewport render */
        if(!b_v3d)
  
        /* copy recalc flags, outside of mutex so we can decide to do the real
         * synchronization at a later time to not block on running updates */
 -      sync->sync_recalc();
 +      sync->sync_recalc(b_depsgraph_);
  
        /* don't do synchronization if on pause */
        if(session_pause) {
        }
  
        /* data and camera synchronize */
 +      b_depsgraph = b_depsgraph_;
 +
        BL::Object b_camera_override(b_engine.camera_override());
        sync->sync_data(b_render,
 +                      b_depsgraph,
                        b_v3d,
                        b_camera_override,
                        width, height,
 -                      &python_thread_state,
 -                      b_rlay_name.c_str());
 +                      &python_thread_state);
  
        if(b_rv3d)
                sync->sync_view(b_v3d, b_rv3d, width, height);
        else
                sync->sync_camera(b_render, b_camera_override, width, height, "");
  
 +      builtin_images_load();
 +
        /* unlock */
        session->scene->mutex.unlock();
  
@@@ -974,7 -939,7 +979,7 @@@ void BlenderSession::update_bake_progre
  void BlenderSession::update_status_progress()
  {
        string timestatus, status, substatus;
 -      string scene = "";
 +      string scene_status = "";
        float progress;
        double total_time, remaining_time = 0, render_time;
        char time_str[128];
                remaining_time = (1.0 - (double)progress) * (render_time / (double)progress);
  
        if(background) {
 -              scene += " | " + b_scene.name();
 +              scene_status += " | " + scene->name;
                if(b_rlay_name != "")
 -                      scene += ", "  + b_rlay_name;
 +                      scene_status += ", "  + b_rlay_name;
  
                if(b_rview_name != "")
 -                      scene += ", " + b_rview_name;
 -      }
 -      else {
 -              BLI_timecode_string_from_time_simple(time_str, sizeof(time_str), total_time);
 -              timestatus = "Time:" + string(time_str) + " | ";
 -      }
 +                      scene_status += ", " + b_rview_name;
  
 -      if(remaining_time > 0) {
 -              BLI_timecode_string_from_time_simple(time_str, sizeof(time_str), remaining_time);
 -              timestatus += "Remaining:" + string(time_str) + " | ";
 -      }
 +              if(remaining_time > 0) {
 +                      BLI_timecode_string_from_time_simple(time_str, sizeof(time_str), remaining_time);
 +                      timestatus += "Remaining:" + string(time_str) + " | ";
 +              }
  
 -      timestatus += string_printf("Mem:%.2fM, Peak:%.2fM", (double)mem_used, (double)mem_peak);
 +              timestatus += string_printf("Mem:%.2fM, Peak:%.2fM", (double)mem_used, (double)mem_peak);
  
 -      if(status.size() > 0)
 -              status = " | " + status;
 -      if(substatus.size() > 0)
 -              status += " | " + substatus;
 +              if(status.size() > 0)
 +                      status = " | " + status;
 +              if(substatus.size() > 0)
 +                      status += " | " + substatus;
 +      }
  
        double current_time = time_dt();
        /* When rendering in a window, redraw the status at least once per second to keep the elapsed and remaining time up-to-date.
         * For headless rendering, only report when something significant changes to keep the console output readable. */
        if(status != last_status || (!headless && (current_time - last_status_time) > 1.0)) {
 -              b_engine.update_stats("", (timestatus + scene + status).c_str());
 +              b_engine.update_stats("", (timestatus + scene_status + status).c_str());
                b_engine.update_memory_stats(mem_used, mem_peak);
                last_status = status;
                last_status_time = current_time;
@@@ -1365,9 -1334,6 +1370,9 @@@ bool BlenderSession::builtin_image_floa
                fprintf(stderr, "Cycles error: unexpected smoke volume resolution, skipping\n");
        }
        else {
 +              /* We originally were passing view_layer here but in reality we need a
 +               * a depsgraph to pass to the RE_point_density_minmax() function.
 +               */
                /* TODO(sergey): Check we're indeed in shader node tree. */
                PointerRNA ptr;
                RNA_pointer_create(NULL, &RNA_Node, builtin_data, &ptr);
                if(b_node.is_a(&RNA_ShaderNodeTexPointDensity)) {
                        BL::ShaderNodeTexPointDensity b_point_density_node(b_node);
                        int length;
 -                      int settings = background ? 1 : 0;  /* 1 - render settings, 0 - vewport settings. */
 -                      b_point_density_node.calc_point_density(b_scene, settings, &length, &pixels);
 +                      b_point_density_node.calc_point_density(b_depsgraph, &length, &pixels);
                }
        }
  
        return false;
  }
  
 +void BlenderSession::builtin_images_load()
 +{
 +      /* Force builtin images to be loaded along with Blender data sync. This
 +       * is needed because we may be reading from depsgraph evaluated data which
 +       * can be freed by Blender before Cycles reads it. */
 +      ImageManager *manager = session->scene->image_manager;
 +      Device *device = session->device;
 +      manager->device_load_builtin(device, session->scene, session->progress);
 +}
 +
  void BlenderSession::update_resumable_tile_manager(int num_samples)
  {
        const int num_resumable_chunks = BlenderSession::num_resumable_chunks,
                return;
        }
  
 -      const int num_samples_per_chunk = (int)ceilf((float)num_samples / num_resumable_chunks);
 +      if (num_resumable_chunks > num_samples) {
 +              fprintf(stderr, "Cycles warning: more sample chunks (%d) than samples (%d), "
 +                      "this will cause some samples to be included in multiple chunks.\n",
 +                      num_resumable_chunks, num_samples);
 +      }
 +
 +      const float num_samples_per_chunk = (float)num_samples / num_resumable_chunks;
  
 -      int range_start_sample, range_num_samples;
 +      float range_start_sample, range_num_samples;
        if(current_resumable_chunk != 0) {
                /* Single chunk rendering. */
                range_start_sample = num_samples_per_chunk * (current_resumable_chunk - 1);
                range_start_sample = num_samples_per_chunk * (start_resumable_chunk - 1);
                range_num_samples = num_chunks * num_samples_per_chunk;
        }
 +
 +      /* Round after doing the multiplications with num_chunks and num_samples_per_chunk
 +       * to allow for many small chunks. */
 +      int rounded_range_start_sample = (int)floor(range_start_sample + 0.5f);
 +      int rounded_range_num_samples = max((int)floor(range_num_samples + 0.5f), 1);
 +
        /* Make sure we don't overshoot. */
 -      if(range_start_sample + range_num_samples > num_samples) {
 -              range_num_samples = num_samples - range_num_samples;
 +      if(rounded_range_start_sample + rounded_range_num_samples > num_samples) {
 +              rounded_range_num_samples = num_samples - rounded_range_num_samples;
        }
  
        VLOG(1) << "Samples range start is " << range_start_sample << ", "
                << "number of samples to render is " << range_num_samples;
  
 -      scene->integrator->start_sample = range_start_sample;
 +      scene->integrator->start_sample = rounded_range_start_sample;
        scene->integrator->tag_update(scene);
  
 -      session->tile_manager.range_start_sample = range_start_sample;
 -      session->tile_manager.range_num_samples = range_num_samples;
 +      session->tile_manager.range_start_sample = rounded_range_start_sample;
 +      session->tile_manager.range_num_samples = rounded_range_num_samples;
 +}
 +
 +void BlenderSession::free_blender_memory_if_possible()
 +{
 +      if(!background) {
 +              /* During interactive render we can not free anything: attempts to save
 +               * memory would cause things to be allocated and evaluated for every
 +               * updated sample.
 +               */
 +              return;
 +      }
 +      b_engine.free_blender_memory();
  }
  
  CCL_NAMESPACE_END
@@@ -78,12 -78,31 +78,12 @@@ BlenderSync::~BlenderSync(
  
  /* Sync */
  
 -bool BlenderSync::sync_recalc()
 +void BlenderSync::sync_recalc(BL::Depsgraph& b_depsgraph)
  {
 -      /* sync recalc flags from blender to cycles. actual update is done separate,
 -       * so we can do it later on if doing it immediate is not suitable */
 -
 -      BL::BlendData::materials_iterator b_mat;
 -      bool has_updated_objects = b_data.objects.is_updated();
 -      for(b_data.materials.begin(b_mat); b_mat != b_data.materials.end(); ++b_mat) {
 -              if(b_mat->is_updated() || (b_mat->node_tree() && b_mat->node_tree().is_updated())) {
 -                      shader_map.set_recalc(*b_mat);
 -              }
 -              else {
 -                      Shader *shader = shader_map.find(*b_mat);
 -                      if(has_updated_objects && shader != NULL && shader->has_object_dependency) {
 -                              shader_map.set_recalc(*b_mat);
 -                      }
 -              }
 -      }
 -
 -      BL::BlendData::lamps_iterator b_lamp;
 -
 -      for(b_data.lamps.begin(b_lamp); b_lamp != b_data.lamps.end(); ++b_lamp)
 -              if(b_lamp->is_updated() || (b_lamp->node_tree() && b_lamp->node_tree().is_updated()))
 -                      shader_map.set_recalc(*b_lamp);
 +      /* Sync recalc flags from blender to cycles. Actual update is done separate,
 +       * so we can do it later on if doing it immediate is not suitable. */
  
 +      bool has_updated_objects = b_depsgraph.id_type_updated(BL::DriverTarget::id_type_OBJECT);
        bool dicing_prop_changed = false;
  
        if(experimental) {
                }
        }
  
 -      BL::BlendData::objects_iterator b_ob;
 +      /* Iterate over all IDs in this depsgraph. */
 +      BL::Depsgraph::updates_iterator b_update;
 +      for(b_depsgraph.updates.begin(b_update); b_update != b_depsgraph.updates.end(); ++b_update) {
 +              BL::ID b_id(b_update->id());
  
 -      for(b_data.objects.begin(b_ob); b_ob != b_data.objects.end(); ++b_ob) {
 -              if(b_ob->is_updated()) {
 -                      object_map.set_recalc(*b_ob);
 -                      light_map.set_recalc(*b_ob);
 +              /* Material */
 +              if(b_id.is_a(&RNA_Material)) {
 +                      BL::Material b_mat(b_id);
 +                      shader_map.set_recalc(b_mat);
 +              }
 +              /* Light */
 +              else if(b_id.is_a(&RNA_Light)) {
 +                      BL::Light b_light(b_id);
 +                      shader_map.set_recalc(b_light);
                }
 +              /* Object */
 +              else if(b_id.is_a(&RNA_Object)) {
 +                      BL::Object b_ob(b_id);
 +                      const bool updated_geometry = b_update->is_updated_geometry();
 +
 +                      if(b_update->is_updated_transform()) {
 +                              object_map.set_recalc(b_ob);
 +                              light_map.set_recalc(b_ob);
 +                      }
  
 -              if(object_is_mesh(*b_ob)) {
 -                      if(b_ob->is_updated_data() || b_ob->data().is_updated() ||
 -                         (dicing_prop_changed && object_subdivision_type(*b_ob, preview, experimental) != Mesh::SUBDIVISION_NONE))
 -                      {
 -                              BL::ID key = BKE_object_is_modified(*b_ob)? *b_ob: b_ob->data();
 -                              mesh_map.set_recalc(key);
 +                      if(object_is_mesh(b_ob)) {
 +                              if(updated_geometry ||
 +                                 (dicing_prop_changed && object_subdivision_type(b_ob, preview, experimental) != Mesh::SUBDIVISION_NONE))
 +                              {
 +                                      BL::ID key = BKE_object_is_modified(b_ob)? b_ob: b_ob.data();
 +                                      mesh_map.set_recalc(key);
 +                              }
 +                      }
 +                      else if(object_is_light(b_ob)) {
 +                              if(updated_geometry) {
 +                                      light_map.set_recalc(b_ob);
 +                              }
 +                      }
 +
 +                      if(updated_geometry) {
 +                              BL::Object::particle_systems_iterator b_psys;
 +                              for(b_ob.particle_systems.begin(b_psys); b_psys != b_ob.particle_systems.end(); ++b_psys)
 +                                      particle_system_map.set_recalc(b_ob);
                        }
                }
 -              else if(object_is_light(*b_ob)) {
 -                      if(b_ob->is_updated_data() || b_ob->data().is_updated())
 -                              light_map.set_recalc(*b_ob);
 +              /* Mesh */
 +              else if(b_id.is_a(&RNA_Mesh)) {
 +                      BL::Mesh b_mesh(b_id);
 +                      mesh_map.set_recalc(b_mesh);
                }
 -
 -              if(b_ob->is_updated_data()) {
 -                      BL::Object::particle_systems_iterator b_psys;
 -                      for(b_ob->particle_systems.begin(b_psys); b_psys != b_ob->particle_systems.end(); ++b_psys)
 -                              particle_system_map.set_recalc(*b_ob);
 +              /* World */
 +              else if(b_id.is_a(&RNA_World)) {
 +                      BL::World b_world(b_id);
 +                      if(world_map == b_world.ptr.data) {
 +                              world_recalc = true;
 +                      }
                }
        }
  
 -      BL::BlendData::meshes_iterator b_mesh;
 -
 -      for(b_data.meshes.begin(b_mesh); b_mesh != b_data.meshes.end(); ++b_mesh) {
 -              if(b_mesh->is_updated()) {
 -                      mesh_map.set_recalc(*b_mesh);
 +      /* Updates shader with object dependency if objects changed. */
 +      if(has_updated_objects) {
 +              if(scene->default_background->has_object_dependency) {
 +                      world_recalc = true;
                }
 -      }
  
 -      BL::BlendData::worlds_iterator b_world;
 -
 -      for(b_data.worlds.begin(b_world); b_world != b_data.worlds.end(); ++b_world) {
 -              if(world_map == b_world->ptr.data) {
 -                      if(b_world->is_updated() ||
 -                         (b_world->node_tree() && b_world->node_tree().is_updated()))
 -                      {
 -                              world_recalc = true;
 -                      }
 -                      else if(b_world->node_tree() && b_world->use_nodes()) {
 -                              Shader *shader = scene->default_background;
 -                              if(has_updated_objects && shader->has_object_dependency) {
 -                                      world_recalc = true;
 -                              }
 +              foreach(Shader *shader, scene->shaders) {
 +                      if(shader->has_object_dependency) {
 +                              shader->need_sync_object = true;
                        }
                }
        }
 -
 -      bool recalc =
 -              shader_map.has_recalc() ||
 -              object_map.has_recalc() ||
 -              light_map.has_recalc() ||
 -              mesh_map.has_recalc() ||
 -              particle_system_map.has_recalc() ||
 -              BlendDataObjects_is_updated_get(&b_data.ptr) ||
 -              world_recalc;
 -
 -      return recalc;
  }
  
  void BlenderSync::sync_data(BL::RenderSettings& b_render,
 +                            BL::Depsgraph& b_depsgraph,
                              BL::SpaceView3D& b_v3d,
                              BL::Object& b_override,
                              int width, int height,
 -                            void **python_thread_state,
 -                            const char *layer)
 +                            void **python_thread_state)
  {
 -      sync_render_layers(b_v3d, layer);
 +      BL::ViewLayer b_view_layer = b_depsgraph.view_layer_eval();
 +
 +      sync_view_layer(b_v3d, b_view_layer);
        sync_integrator();
        sync_film();
 -      sync_shaders();
 +      sync_shaders(b_depsgraph);
        sync_images();
        sync_curve_settings();
  
           scene->need_motion() == Scene::MOTION_NONE ||
           scene->camera->motion_position == Camera::MOTION_POSITION_CENTER)
        {
 -              sync_objects();
 +              sync_objects(b_depsgraph);
        }
        sync_motion(b_render,
 +                  b_depsgraph,
                    b_override,
                    width, height,
                    python_thread_state);
  
        mesh_synced.clear();
 +
 +      free_data_after_sync(b_depsgraph);
  }
  
  /* Integrator */
@@@ -364,33 -371,75 +364,33 @@@ void BlenderSync::sync_film(
  
  /* Render Layer */
  
 -void BlenderSync::sync_render_layers(BL::SpaceView3D& b_v3d, const char *layer)
 +void BlenderSync::sync_view_layer(BL::SpaceView3D& /*b_v3d*/, BL::ViewLayer& b_view_layer)
  {
 -      PointerRNA cscene = RNA_pointer_get(&b_scene.ptr, "cycles");
 -      string layername;
 -
 -      /* 3d view */
 -      if(b_v3d) {
 -              if(RNA_boolean_get(&cscene, "preview_active_layer")) {
 -                      BL::RenderLayers layers(b_scene.render().ptr);
 -                      layername = layers.active().name();
 -                      layer = layername.c_str();
 -              }
 -              else {
 -                      render_layer.scene_layer = get_layer(b_v3d.layers(), b_v3d.layers_local_view());
 -                      render_layer.layer = render_layer.scene_layer;
 -                      render_layer.exclude_layer = 0;
 -                      render_layer.holdout_layer = 0;
 -                      render_layer.material_override = PointerRNA_NULL;
 -                      render_layer.use_background_shader = true;
 -                      render_layer.use_background_ao = true;
 -                      render_layer.use_hair = true;
 -                      render_layer.use_surfaces = true;
 -                      render_layer.use_viewport_visibility = true;
 -                      render_layer.samples = 0;
 -                      render_layer.bound_samples = false;
 -                      return;
 -              }
 -      }
 -
        /* render layer */
 -      BL::RenderSettings r = b_scene.render();
 -      BL::RenderSettings::layers_iterator b_rlay;
 +      view_layer.name = b_view_layer.name();
 +      view_layer.use_background_shader = b_view_layer.use_sky();
 +      view_layer.use_background_ao = b_view_layer.use_ao();
 +      view_layer.use_surfaces = b_view_layer.use_solid();
 +      view_layer.use_hair = b_view_layer.use_strand();
 +
 +      /* Material override. */
 +      view_layer.material_override = b_view_layer.material_override();
 +
 +      /* Sample override. */
 +      PointerRNA cscene = RNA_pointer_get(&b_scene.ptr, "cycles");
        int use_layer_samples = get_enum(cscene, "use_layer_samples");
 -      bool first_layer = true;
 -      uint layer_override = get_layer(b_engine.layer_override());
 -      uint scene_layers = layer_override ? layer_override : get_layer(b_scene.layers());
 -
 -      for(r.layers.begin(b_rlay); b_rlay != r.layers.end(); ++b_rlay) {
 -              if((!layer && first_layer) || (layer && b_rlay->name() == layer)) {
 -                      render_layer.name = b_rlay->name();
 -
 -                      render_layer.holdout_layer = get_layer(b_rlay->layers_zmask());
 -                      render_layer.exclude_layer = get_layer(b_rlay->layers_exclude());
 -
 -                      render_layer.scene_layer = scene_layers & ~render_layer.exclude_layer;
 -                      render_layer.scene_layer |= render_layer.exclude_layer & render_layer.holdout_layer;
 -
 -                      render_layer.layer = get_layer(b_rlay->layers());
 -                      render_layer.layer |= render_layer.holdout_layer;
 -
 -                      render_layer.material_override = b_rlay->material_override();
 -                      render_layer.use_background_shader = b_rlay->use_sky();
 -                      render_layer.use_background_ao = b_rlay->use_ao();
 -                      render_layer.use_surfaces = b_rlay->use_solid();
 -                      render_layer.use_hair = b_rlay->use_strand();
 -                      render_layer.use_viewport_visibility = false;
 -
 -                      render_layer.bound_samples = (use_layer_samples == 1);
 -                      if(use_layer_samples != 2) {
 -                              int samples = b_rlay->samples();
 -                              if(get_boolean(cscene, "use_square_samples"))
 -                                      render_layer.samples = samples * samples;
 -                              else
 -                                      render_layer.samples = samples;
 -                      }
 -              }
  
 -              first_layer = false;
 +      view_layer.bound_samples = (use_layer_samples == 1);
 +      view_layer.samples = 0;
 +
 +      if(use_layer_samples != 2) {
 +              int samples = b_view_layer.samples();
 +              if(get_boolean(cscene, "use_square_samples"))
 +                      view_layer.samples = samples * samples;
 +              else
 +                      view_layer.samples = samples;
        }
 +
  }
  
  /* Images */
@@@ -482,7 -531,7 +482,7 @@@ int BlenderSync::get_denoising_pass(BL:
  {
        string name = b_pass.name();
  
-       if(name == "Noisy Image") return DENOISING_PASS_COLOR;
+       if(name == "Noisy Image") return DENOISING_PASS_PREFILTERED_COLOR;
  
        if(name.substr(0, 10) != "Denoising ") {
                return -1;
        name = name.substr(10);
  
  #define MAP_PASS(passname, offset) if(name == passname) return offset;
-       MAP_PASS("Normal", DENOISING_PASS_NORMAL);
-       MAP_PASS("Normal Variance", DENOISING_PASS_NORMAL_VAR);
-       MAP_PASS("Albedo", DENOISING_PASS_ALBEDO);
-       MAP_PASS("Albedo Variance", DENOISING_PASS_ALBEDO_VAR);
-       MAP_PASS("Depth", DENOISING_PASS_DEPTH);
-       MAP_PASS("Depth Variance", DENOISING_PASS_DEPTH_VAR);
-       MAP_PASS("Shadow A", DENOISING_PASS_SHADOW_A);
-       MAP_PASS("Shadow B", DENOISING_PASS_SHADOW_B);
-       MAP_PASS("Image Variance", DENOISING_PASS_COLOR_VAR);
+       MAP_PASS("Normal", DENOISING_PASS_PREFILTERED_NORMAL);
+       MAP_PASS("Albedo", DENOISING_PASS_PREFILTERED_ALBEDO);
+       MAP_PASS("Depth", DENOISING_PASS_PREFILTERED_DEPTH);
+       MAP_PASS("Shadowing", DENOISING_PASS_PREFILTERED_SHADOWING);
+       MAP_PASS("Variance", DENOISING_PASS_PREFILTERED_VARIANCE);
+       MAP_PASS("Intensity", DENOISING_PASS_PREFILTERED_INTENSITY);
        MAP_PASS("Clean", DENOISING_PASS_CLEAN);
  #undef MAP_PASS
  
  }
  
  vector<Pass> BlenderSync::sync_render_passes(BL::RenderLayer& b_rlay,
 -                                             BL::SceneRenderLayer& b_srlay,
 +                                             BL::ViewLayer& b_view_layer,
                                               const SessionParams &session_params)
  {
        vector<Pass> passes;
                        Pass::add(pass_type, passes);
        }
  
 -      PointerRNA crp = RNA_pointer_get(&b_srlay.ptr, "cycles");
 +      PointerRNA crp = RNA_pointer_get(&b_view_layer.ptr, "cycles");
-       bool use_denoising = get_boolean(crp, "use_denoising");
-       bool store_denoising_passes = get_boolean(crp, "denoising_store_passes");
+       bool full_denoising = get_boolean(crp, "use_denoising");
+       bool write_denoising_passes = get_boolean(crp, "denoising_store_passes");
        scene->film->denoising_flags = 0;
-       if(use_denoising || store_denoising_passes) {
+       if(full_denoising || write_denoising_passes) {
  #define MAP_OPTION(name, flag) if(!get_boolean(crp, name)) scene->film->denoising_flags |= flag;
                MAP_OPTION("denoising_diffuse_direct",        DENOISING_CLEAN_DIFFUSE_DIR);
                MAP_OPTION("denoising_diffuse_indirect",      DENOISING_CLEAN_DIFFUSE_IND);
                MAP_OPTION("denoising_subsurface_direct",     DENOISING_CLEAN_SUBSURFACE_DIR);
                MAP_OPTION("denoising_subsurface_indirect",   DENOISING_CLEAN_SUBSURFACE_IND);
  #undef MAP_OPTION
 -              b_engine.add_pass("Noisy Image", 4, "RGBA", b_srlay.name().c_str());
 +              b_engine.add_pass("Noisy Image", 4, "RGBA", b_view_layer.name().c_str());
        }
  
-       if(store_denoising_passes) {
+       if(write_denoising_passes) {
 -              b_engine.add_pass("Denoising Normal",          3, "XYZ", b_srlay.name().c_str());
 -              b_engine.add_pass("Denoising Albedo",          3, "RGB", b_srlay.name().c_str());
 -              b_engine.add_pass("Denoising Depth",           1, "Z",   b_srlay.name().c_str());
 -              b_engine.add_pass("Denoising Shadowing",       1, "X",   b_srlay.name().c_str());
 -              b_engine.add_pass("Denoising Variance",        3, "RGB", b_srlay.name().c_str());
 -              b_engine.add_pass("Denoising Intensity",       1, "X",   b_srlay.name().c_str());
 +              b_engine.add_pass("Denoising Normal",          3, "XYZ", b_view_layer.name().c_str());
-               b_engine.add_pass("Denoising Normal Variance", 3, "XYZ", b_view_layer.name().c_str());
 +              b_engine.add_pass("Denoising Albedo",          3, "RGB", b_view_layer.name().c_str());
-               b_engine.add_pass("Denoising Albedo Variance", 3, "RGB", b_view_layer.name().c_str());
 +              b_engine.add_pass("Denoising Depth",           1, "Z",   b_view_layer.name().c_str());
-               b_engine.add_pass("Denoising Depth Variance",  1, "Z",   b_view_layer.name().c_str());
-               b_engine.add_pass("Denoising Shadow A",        3, "XYV", b_view_layer.name().c_str());
-               b_engine.add_pass("Denoising Shadow B",        3, "XYV", b_view_layer.name().c_str());
-               b_engine.add_pass("Denoising Image Variance",  3, "RGB", b_view_layer.name().c_str());
++              b_engine.add_pass("Denoising Shadowing",       1, "X",   b_view_layer.name().c_str());
++              b_engine.add_pass("Denoising Variance",        3, "RGB", b_view_layer.name().c_str());
++              b_engine.add_pass("Denoising Intensity",       1, "X",   b_view_layer.name().c_str());
  
                if(scene->film->denoising_flags & DENOISING_CLEAN_ALL_PASSES) {
 -                      b_engine.add_pass("Denoising Clean",   3, "RGB", b_srlay.name().c_str());
 +                      b_engine.add_pass("Denoising Clean",   3, "RGB", b_view_layer.name().c_str());
                }
        }
  #ifdef __KERNEL_DEBUG__
        if(get_boolean(crp, "pass_debug_bvh_traversed_nodes")) {
 -              b_engine.add_pass("Debug BVH Traversed Nodes", 1, "X", b_srlay.name().c_str());
 +              b_engine.add_pass("Debug BVH Traversed Nodes", 1, "X", b_view_layer.name().c_str());
                Pass::add(PASS_BVH_TRAVERSED_NODES, passes);
        }
        if(get_boolean(crp, "pass_debug_bvh_traversed_instances")) {
 -              b_engine.add_pass("Debug BVH Traversed Instances", 1, "X", b_srlay.name().c_str());
 +              b_engine.add_pass("Debug BVH Traversed Instances", 1, "X", b_view_layer.name().c_str());
                Pass::add(PASS_BVH_TRAVERSED_INSTANCES, passes);
        }
        if(get_boolean(crp, "pass_debug_bvh_intersections")) {
 -              b_engine.add_pass("Debug BVH Intersections", 1, "X", b_srlay.name().c_str());
 +              b_engine.add_pass("Debug BVH Intersections", 1, "X", b_view_layer.name().c_str());
                Pass::add(PASS_BVH_INTERSECTIONS, passes);
        }
        if(get_boolean(crp, "pass_debug_ray_bounces")) {
 -              b_engine.add_pass("Debug Ray Bounces", 1, "X", b_srlay.name().c_str());
 +              b_engine.add_pass("Debug Ray Bounces", 1, "X", b_view_layer.name().c_str());
                Pass::add(PASS_RAY_BOUNCES, passes);
        }
  #endif
        if(get_boolean(crp, "pass_debug_render_time")) {
 -              b_engine.add_pass("Debug Render Time", 1, "X", b_srlay.name().c_str());
 +              b_engine.add_pass("Debug Render Time", 1, "X", b_view_layer.name().c_str());
                Pass::add(PASS_RENDER_TIME, passes);
        }
        if(get_boolean(crp, "use_pass_volume_direct")) {
 -              b_engine.add_pass("VolumeDir", 3, "RGB", b_srlay.name().c_str());
 +              b_engine.add_pass("VolumeDir", 3, "RGB", b_view_layer.name().c_str());
                Pass::add(PASS_VOLUME_DIRECT, passes);
        }
        if(get_boolean(crp, "use_pass_volume_indirect")) {
 -              b_engine.add_pass("VolumeInd", 3, "RGB", b_srlay.name().c_str());
 +              b_engine.add_pass("VolumeInd", 3, "RGB", b_view_layer.name().c_str());
                Pass::add(PASS_VOLUME_INDIRECT, passes);
        }
  
        if(get_boolean(crp, "use_pass_crypto_object")) {
                for(int i = 0; i < crypto_depth; ++i) {
                        string passname = cryptomatte_prefix + string_printf("Object%02d", i);
 -                      b_engine.add_pass(passname.c_str(), 4, "RGBA", b_srlay.name().c_str());
 +                      b_engine.add_pass(passname.c_str(), 4, "RGBA", b_view_layer.name().c_str());
                        Pass::add(PASS_CRYPTOMATTE, passes, passname.c_str());
                }
                scene->film->cryptomatte_passes = (CryptomatteType)(scene->film->cryptomatte_passes | CRYPT_OBJECT);
        if(get_boolean(crp, "use_pass_crypto_material")) {
                for(int i = 0; i < crypto_depth; ++i) {
                        string passname = cryptomatte_prefix + string_printf("Material%02d", i);
 -                      b_engine.add_pass(passname.c_str(), 4, "RGBA", b_srlay.name().c_str());
 +                      b_engine.add_pass(passname.c_str(), 4, "RGBA", b_view_layer.name().c_str());
                        Pass::add(PASS_CRYPTOMATTE, passes, passname.c_str());
                }
                scene->film->cryptomatte_passes = (CryptomatteType)(scene->film->cryptomatte_passes | CRYPT_MATERIAL);
        if(get_boolean(crp, "use_pass_crypto_asset")) {
                for(int i = 0; i < crypto_depth; ++i) {
                        string passname = cryptomatte_prefix + string_printf("Asset%02d", i);
 -                      b_engine.add_pass(passname.c_str(), 4, "RGBA", b_srlay.name().c_str());
 +                      b_engine.add_pass(passname.c_str(), 4, "RGBA", b_view_layer.name().c_str());
                        Pass::add(PASS_CRYPTOMATTE, passes, passname.c_str());
                }
                scene->film->cryptomatte_passes = (CryptomatteType)(scene->film->cryptomatte_passes | CRYPT_ASSET);
        return passes;
  }
  
 +void BlenderSync::free_data_after_sync(BL::Depsgraph& b_depsgraph)
 +{
 +      /* When viewport display is not needed during render we can force some
 +       * caches to be releases from blender side in order to reduce peak memory
 +       * footprint during synchronization process.
 +       */
 +      const bool is_interface_locked = b_engine.render() &&
 +                                       b_engine.render().use_lock_interface();
 +      const bool can_free_caches = BlenderSession::headless || is_interface_locked;
 +      if(!can_free_caches) {
 +              return;
 +      }
 +      /* TODO(sergey): We can actually remove the whole dependency graph,
 +       * but that will need some API support first.
 +       */
 +      BL::Depsgraph::objects_iterator b_ob;
 +      for(b_depsgraph.objects.begin(b_ob);
 +          b_ob != b_depsgraph.objects.end();
 +          ++b_ob)
 +      {
 +              b_ob->cache_release();
 +      }
 +}
 +
  /* Scene Parameters */
  
  SceneParams BlenderSync::get_scene_params(BL::Scene& b_scene,
@@@ -721,7 -741,7 +716,7 @@@ bool BlenderSync::get_session_pause(BL:
  }
  
  SessionParams BlenderSync::get_session_params(BL::RenderEngine& b_engine,
 -                                              BL::UserPreferences& b_userpref,
 +                                              BL::Preferences& b_userpref,
                                                BL::Scene& b_scene,
                                                bool background)
  {
                /* Find cycles preferences. */
                PointerRNA b_preferences;
  
 -              BL::UserPreferences::addons_iterator b_addon_iter;
 +              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;
        params.text_timeout = (double)get_float(cscene, "debug_text_timeout");
  
        /* progressive refine */
 -      params.progressive_refine = get_boolean(cscene, "use_progressive_refine") &&
 +      params.progressive_refine = (b_engine.is_preview() ||
 +                                   get_boolean(cscene, "use_progressive_refine")) &&
                                    !b_r.use_save_buffers();
  
        if(params.progressive_refine) {
 -              BL::RenderSettings::layers_iterator b_rlay;
 -              for(b_r.layers.begin(b_rlay); b_rlay != b_r.layers.end(); ++b_rlay) {
 -                      PointerRNA crl = RNA_pointer_get(&b_rlay->ptr, "cycles");
 +              BL::Scene::view_layers_iterator b_view_layer;
 +              for(b_scene.view_layers.begin(b_view_layer); b_view_layer != b_scene.view_layers.end(); ++b_view_layer) {
 +                      PointerRNA crl = RNA_pointer_get(&b_view_layer->ptr, "cycles");
                        if(get_boolean(crl, "use_denoising")) {
                                params.progressive_refine = false;
                        }
                params.shadingsystem = SHADINGSYSTEM_OSL;
  
        /* color managagement */
 -#ifdef GLEW_MX
 -      /* When using GLEW MX we need to check whether we've got an OpenGL
 -       * context for current window. This is because command line rendering
 -       * doesn't have OpenGL context actually.
 -       */
 -      if(glewGetContext() != NULL)
 -#endif
 -      {
 -              params.display_buffer_linear = GLEW_ARB_half_float_pixel &&
 -                                             b_engine.support_display_space_shader(b_scene);
 -      }
 +      params.display_buffer_linear = b_engine.support_display_space_shader(b_scene);
  
        if(b_engine.is_preview()) {
                /* For preview rendering we're using same timeout as
@@@ -180,20 -180,21 +180,21 @@@ public
        KernelFunctions<void(*)(KernelGlobals *, uchar4 *, float *, float, int, int, int, int)> convert_to_byte_kernel;
        KernelFunctions<void(*)(KernelGlobals *, uint4 *, float4 *, int, int, int, int, int)>   shader_kernel;
  
-       KernelFunctions<void(*)(int, TileInfo*, int, int, float*, float*, float*, float*, float*, int*, int, int)> filter_divide_shadow_kernel;
-       KernelFunctions<void(*)(int, TileInfo*, int, int, int, int, float*, float*, int*, int, int)>               filter_get_feature_kernel;
+       KernelFunctions<void(*)(int, TileInfo*, int, int, float*, float*, float*, float*, float*, int*, int, int)>  filter_divide_shadow_kernel;
+       KernelFunctions<void(*)(int, TileInfo*, int, int, int, int, float*, float*, float, int*, int, int)>         filter_get_feature_kernel;
+       KernelFunctions<void(*)(int, int, int, int*, float*, float*, int, int*)>                                    filter_write_feature_kernel;
        KernelFunctions<void(*)(int, int, float*, float*, float*, float*, int*, int)>                               filter_detect_outliers_kernel;
        KernelFunctions<void(*)(int, int, float*, float*, float*, float*, int*, int)>                               filter_combine_halves_kernel;
  
-       KernelFunctions<void(*)(int, int, float*, float*, float*, int*, int, int, float, float)>   filter_nlm_calc_difference_kernel;
-       KernelFunctions<void(*)(float*, float*, int*, int, int)>                                   filter_nlm_blur_kernel;
-       KernelFunctions<void(*)(float*, float*, int*, int, int)>                                   filter_nlm_calc_weight_kernel;
-       KernelFunctions<void(*)(int, int, float*, float*, float*, float*, float*, int*, int, int)> filter_nlm_update_output_kernel;
-       KernelFunctions<void(*)(float*, float*, int*, int)>                                        filter_nlm_normalize_kernel;
+       KernelFunctions<void(*)(int, int, float*, float*, float*, float*, int*, int, int, int, float, float)> filter_nlm_calc_difference_kernel;
+       KernelFunctions<void(*)(float*, float*, int*, int, int)>                                              filter_nlm_blur_kernel;
+       KernelFunctions<void(*)(float*, float*, int*, int, int)>                                              filter_nlm_calc_weight_kernel;
+       KernelFunctions<void(*)(int, int, float*, float*, float*, float*, float*, int*, int, int, int)>       filter_nlm_update_output_kernel;
+       KernelFunctions<void(*)(float*, float*, int*, int)>                                                   filter_nlm_normalize_kernel;
  
-       KernelFunctions<void(*)(float*, int, int, int, float*, int*, int*, int, int, float)>                         filter_construct_transform_kernel;
-       KernelFunctions<void(*)(int, int, float*, float*, float*, int*, float*, float3*, int*, int*, int, int, int)> filter_nlm_construct_gramian_kernel;
-       KernelFunctions<void(*)(int, int, int, float*, int*, float*, float3*, int*, int)>                            filter_finalize_kernel;
+       KernelFunctions<void(*)(float*, TileInfo*, int, int, int, float*, int*, int*, int, int, bool, int, float)>                   filter_construct_transform_kernel;
+       KernelFunctions<void(*)(int, int, int, float*, float*, float*, int*, float*, float3*, int*, int*, int, int, int, int, bool)> filter_nlm_construct_gramian_kernel;
+       KernelFunctions<void(*)(int, int, int, float*, int*, float*, float3*, int*, int)>                                            filter_finalize_kernel;
  
        KernelFunctions<void(*)(KernelGlobals *, ccl_constant KernelData*, ccl_global void*, int, ccl_global char*,
                               int, int, int, int, int, int, int, int, ccl_global int*, int,
          REGISTER_KERNEL(shader),
          REGISTER_KERNEL(filter_divide_shadow),
          REGISTER_KERNEL(filter_get_feature),
+         REGISTER_KERNEL(filter_write_feature),
          REGISTER_KERNEL(filter_detect_outliers),
          REGISTER_KERNEL(filter_combine_halves),
          REGISTER_KERNEL(filter_nlm_calc_difference),
  
                int w = align_up(rect.z-rect.x, 4);
                int h = rect.w-rect.y;
+               int stride = task->buffer.stride;
+               int channel_offset = task->nlm_state.is_color? task->buffer.pass_stride : 0;
  
                float *temporary_mem = (float*) task->buffer.temporary_mem.device_pointer;
                float *blurDifference = temporary_mem;
                        filter_nlm_calc_difference_kernel()(dx, dy,
                                                            (float*) guide_ptr,
                                                            (float*) variance_ptr,
+                                                           NULL,
                                                            difference,
                                                            local_rect,
-                                                           w, 0,
-                                                           a, k_2);
+                                                           w, channel_offset,
+                                                           0, a, k_2);
  
                        filter_nlm_blur_kernel()       (difference, blurDifference, local_rect, w, f);
                        filter_nlm_calc_weight_kernel()(blurDifference, difference, local_rect, w, f);
                                                          (float*) out_ptr,
                                                          weightAccum,
                                                          local_rect,
-                                                         w, f);
+                                                         channel_offset,
+                                                         stride, f);
                }
  
                int local_rect[4] = {0, 0, rect.z-rect.x, rect.w-rect.y};
                for(int y = 0; y < task->filter_area.w; y++) {
                        for(int x = 0; x < task->filter_area.z; x++) {
                                filter_construct_transform_kernel()((float*) task->buffer.mem.device_pointer,
+                                                                   task->tile_info,
                                                                    x + task->filter_area.x,
                                                                    y + task->filter_area.y,
                                                                    y*task->filter_area.z + x,
                                                                    (int*)   task->storage.rank.device_pointer,
                                                                    &task->rect.x,
                                                                    task->buffer.pass_stride,
+                                                                   task->buffer.frame_stride,
+                                                                   task->buffer.use_time,
                                                                    task->radius,
                                                                    task->pca_threshold);
                        }
                return true;
        }
  
-       bool denoising_reconstruct(device_ptr color_ptr,
-                                  device_ptr color_variance_ptr,
-                                  device_ptr output_ptr,
-                                  DenoisingTask *task)
+       bool denoising_accumulate(device_ptr color_ptr,
+                                 device_ptr color_variance_ptr,
+                                 device_ptr scale_ptr,
+                                 int frame,
+                                 DenoisingTask *task)
        {
                ProfilingHelper profiling(task->profiler, PROFILING_DENOISING_RECONSTRUCT);
  
-               mem_zero(task->storage.XtWX);
-               mem_zero(task->storage.XtWY);
                float *temporary_mem = (float*) task->buffer.temporary_mem.device_pointer;
                float *difference     = temporary_mem;
                float *blurDifference = temporary_mem + task->buffer.pass_stride;
  
                int r = task->radius;
+               int frame_offset = frame * task->buffer.frame_stride;
                for(int i = 0; i < (2*r+1)*(2*r+1); i++) {
                        int dy = i / (2*r+1) - r;
                        int dx = i % (2*r+1) - r;
                        filter_nlm_calc_difference_kernel()(dx, dy,
                                                            (float*) color_ptr,
                                                            (float*) color_variance_ptr,
+                                                           (float*) scale_ptr,
                                                            difference,
                                                            local_rect,
                                                            task->buffer.stride,
                                                            task->buffer.pass_stride,
+                                                           frame_offset,
                                                            1.0f,
                                                            task->nlm_k_2);
                        filter_nlm_blur_kernel()(difference, blurDifference, local_rect, task->buffer.stride, 4);
                        filter_nlm_calc_weight_kernel()(blurDifference, difference, local_rect, task->buffer.stride, 4);
                        filter_nlm_blur_kernel()(difference, blurDifference, local_rect, task->buffer.stride, 4);
                        filter_nlm_construct_gramian_kernel()(dx, dy,
+                                                             task->tile_info->frames[frame],
                                                              blurDifference,
                                                              (float*)  task->buffer.mem.device_pointer,
                                                              (float*)  task->storage.transform.device_pointer,
                                                              &task->reconstruction_state.filter_window.x,
                                                              task->buffer.stride,
                                                              4,
-                                                             task->buffer.pass_stride);
+                                                             task->buffer.pass_stride,
+                                                             frame_offset,
+                                                             task->buffer.use_time);
                }
+               return true;
+       }
+       bool denoising_solve(device_ptr output_ptr,
+                            DenoisingTask *task)
+       {
                for(int y = 0; y < task->filter_area.w; y++) {
                        for(int x = 0; x < task->filter_area.z; x++) {
                                filter_finalize_kernel()(x,
                                   int variance_offset,
                                   device_ptr mean_ptr,
                                   device_ptr variance_ptr,
+                                  float scale,
                                   DenoisingTask *task)
        {
                ProfilingHelper profiling(task->profiler, PROFILING_DENOISING_GET_FEATURE);
                                                            x, y,
                                                            (float*) mean_ptr,
                                                            (float*) variance_ptr,
+                                                           scale,
                                                            &task->rect.x,
                                                            task->render_buffer.pass_stride,
                                                            task->render_buffer.offset);
                return true;
        }
  
+       bool denoising_write_feature(int out_offset,
+                                    device_ptr from_ptr,
+                                    device_ptr buffer_ptr,
+                                    DenoisingTask *task)
+       {
+               for(int y = 0; y < task->filter_area.w; y++) {
+                       for(int x = 0; x < task->filter_area.z; x++) {
+                               filter_write_feature_kernel()(task->render_buffer.samples,
+                                                             x + task->filter_area.x,
+                                                             y + task->filter_area.y,
+                                                             &task->reconstruction_state.buffer_params.x,
+                                                             (float*) from_ptr,
+                                                             (float*) buffer_ptr,
+                                                             out_offset,
+                                                             &task->rect.x);
+                       }
+               }
+               return true;
+       }
        bool denoising_detect_outliers(device_ptr image_ptr,
                                       device_ptr variance_ptr,
                                       device_ptr depth_ptr,
                tile.sample = tile.start_sample + tile.num_samples;
  
                denoising.functions.construct_transform = function_bind(&CPUDevice::denoising_construct_transform, this, &denoising);
-               denoising.functions.reconstruct = function_bind(&CPUDevice::denoising_reconstruct, this, _1, _2, _3, &denoising);
+               denoising.functions.accumulate = function_bind(&CPUDevice::denoising_accumulate, this, _1, _2, _3, _4, &denoising);
+               denoising.functions.solve = function_bind(&CPUDevice::denoising_solve, this, _1, &denoising);
                denoising.functions.divide_shadow = function_bind(&CPUDevice::denoising_divide_shadow, this, _1, _2, _3, _4, _5, &denoising);
                denoising.functions.non_local_means = function_bind(&CPUDevice::denoising_non_local_means, this, _1, _2, _3, _4, &denoising);
                denoising.functions.combine_halves = function_bind(&CPUDevice::denoising_combine_halves, this, _1, _2, _3, _4, _5, _6, &denoising);
-               denoising.functions.get_feature = function_bind(&CPUDevice::denoising_get_feature, this, _1, _2, _3, _4, &denoising);
+               denoising.functions.get_feature = function_bind(&CPUDevice::denoising_get_feature, this, _1, _2, _3, _4, _5, &denoising);
+               denoising.functions.write_feature = function_bind(&CPUDevice::denoising_write_feature, this, _1, _2, _3, &denoising);
                denoising.functions.detect_outliers = function_bind(&CPUDevice::denoising_detect_outliers, this, _1, _2, _3, _4, &denoising);
  
                denoising.filter_area = make_int4(tile.x, tile.y, tile.w, tile.h);
@@@ -924,7 -968,6 +968,7 @@@ protected
                        kg.decoupled_volume_steps[i] = NULL;
                }
                kg.decoupled_volume_steps_index = 0;
 +              kg.coverage_asset = kg.coverage_object = kg.coverage_material = NULL;
  #ifdef WITH_OSL
                OSLShader::thread_init(&kg, &kernel_globals, &osl_globals);
  #endif
@@@ -1300,7 -1300,8 +1300,8 @@@ public
  
                int pass_stride = task->buffer.pass_stride;
                int num_shifts = (2*r+1)*(2*r+1);
-               int channel_offset = 0;
+               int channel_offset = task->nlm_state.is_color? task->buffer.pass_stride : 0;
+               int frame_offset = 0;
  
                if(have_error())
                        return false;
                CUdeviceptr difference     = cuda_device_ptr(task->buffer.temporary_mem.device_pointer);
                CUdeviceptr blurDifference = difference + sizeof(float)*pass_stride*num_shifts;
                CUdeviceptr weightAccum = difference + 2*sizeof(float)*pass_stride*num_shifts;
+               CUdeviceptr scale_ptr = 0;
  
                cuda_assert(cuMemsetD8(weightAccum, 0, sizeof(float)*pass_stride));
                cuda_assert(cuMemsetD8(out_ptr, 0, sizeof(float)*pass_stride));
  
                        CUDA_GET_BLOCKSIZE_1D(cuNLMCalcDifference, w*h, num_shifts);
  
-                       void *calc_difference_args[] = {&guide_ptr, &variance_ptr, &difference, &w, &h, &stride, &pass_stride, &r, &channel_offset, &a, &k_2};
+                       void *calc_difference_args[] = {&guide_ptr, &variance_ptr, &scale_ptr, &difference, &w, &h, &stride, &pass_stride, &r, &channel_offset, &frame_offset, &a, &k_2};
                        void *blur_args[]            = {&difference, &blurDifference, &w, &h, &stride, &pass_stride, &r, &f};
                        void *calc_weight_args[]     = {&blurDifference, &difference, &w, &h, &stride, &pass_stride, &r, &f};
-                       void *update_output_args[]   = {&blurDifference, &image_ptr, &out_ptr, &weightAccum, &w, &h, &stride, &pass_stride, &r, &f};
+                       void *update_output_args[]   = {&blurDifference, &image_ptr, &out_ptr, &weightAccum, &w, &h, &stride, &pass_stride, &channel_offset, &r, &f};
  
                        CUDA_LAUNCH_KERNEL_1D(cuNLMCalcDifference, calc_difference_args);
                        CUDA_LAUNCH_KERNEL_1D(cuNLMBlur, blur_args);
                                   task->storage.h);
  
                void *args[] = {&task->buffer.mem.device_pointer,
+                               &task->tile_info_mem.device_pointer,
                                &task->storage.transform.device_pointer,
                                &task->storage.rank.device_pointer,
                                &task->filter_area,
                                &task->rect,
                                &task->radius,
                                &task->pca_threshold,
-                               &task->buffer.pass_stride};
+                               &task->buffer.pass_stride,
+                               &task->buffer.frame_stride,
+                               &task->buffer.use_time};
                CUDA_LAUNCH_KERNEL(cuFilterConstructTransform, args);
                cuda_assert(cuCtxSynchronize());
  
                return !have_error();
        }
  
-       bool denoising_reconstruct(device_ptr color_ptr,
-                                  device_ptr color_variance_ptr,
-                                  device_ptr output_ptr,
-                                  DenoisingTask *task)
+       bool denoising_accumulate(device_ptr color_ptr,
+                                 device_ptr color_variance_ptr,
+                                 device_ptr scale_ptr,
+                                 int frame,
+                                 DenoisingTask *task)
        {
                if(have_error())
                        return false;
  
                CUDAContextScope scope(this);
  
-               mem_zero(task->storage.XtWX);
-               mem_zero(task->storage.XtWY);
                int r = task->radius;
                int f = 4;
                float a = 1.0f;
                int w = task->reconstruction_state.source_w;
                int h = task->reconstruction_state.source_h;
                int stride = task->buffer.stride;
+               int frame_offset = frame * task->buffer.frame_stride;
+               int t = task->tile_info->frames[frame];
  
                int pass_stride = task->buffer.pass_stride;
                int num_shifts = (2*r+1)*(2*r+1);
                CUdeviceptr difference     = cuda_device_ptr(task->buffer.temporary_mem.device_pointer);
                CUdeviceptr blurDifference = difference + sizeof(float)*pass_stride*num_shifts;
  
-               {
-                       CUfunction cuNLMCalcDifference, cuNLMBlur, cuNLMCalcWeight, cuNLMConstructGramian;
-                       cuda_assert(cuModuleGetFunction(&cuNLMCalcDifference,   cuFilterModule, "kernel_cuda_filter_nlm_calc_difference"));
-                       cuda_assert(cuModuleGetFunction(&cuNLMBlur,             cuFilterModule, "kernel_cuda_filter_nlm_blur"));
-                       cuda_assert(cuModuleGetFunction(&cuNLMCalcWeight,       cuFilterModule, "kernel_cuda_filter_nlm_calc_weight"));
-                       cuda_assert(cuModuleGetFunction(&cuNLMConstructGramian, cuFilterModule, "kernel_cuda_filter_nlm_construct_gramian"));
-                       cuda_assert(cuFuncSetCacheConfig(cuNLMCalcDifference,   CU_FUNC_CACHE_PREFER_L1));
-                       cuda_assert(cuFuncSetCacheConfig(cuNLMBlur,             CU_FUNC_CACHE_PREFER_L1));
-                       cuda_assert(cuFuncSetCacheConfig(cuNLMCalcWeight,       CU_FUNC_CACHE_PREFER_L1));
-                       cuda_assert(cuFuncSetCacheConfig(cuNLMConstructGramian, CU_FUNC_CACHE_PREFER_SHARED));
-                       CUDA_GET_BLOCKSIZE_1D(cuNLMCalcDifference,
-                                            task->reconstruction_state.source_w * task->reconstruction_state.source_h,
-                                            num_shifts);
-                       void *calc_difference_args[] = {&color_ptr, &color_variance_ptr, &difference, &w, &h, &stride, &pass_stride, &r, &pass_stride, &a, &k_2};
-                       void *blur_args[]            = {&difference, &blurDifference, &w, &h, &stride, &pass_stride, &r, &f};
-                       void *calc_weight_args[]     = {&blurDifference, &difference, &w, &h, &stride, &pass_stride, &r, &f};
-                       void *construct_gramian_args[] = {&blurDifference,
-                                                         &task->buffer.mem.device_pointer,
-                                                         &task->storage.transform.device_pointer,
-                                                         &task->storage.rank.device_pointer,
-                                                         &task->storage.XtWX.device_pointer,
-                                                         &task->storage.XtWY.device_pointer,
-                                                         &task->reconstruction_state.filter_window,
-                                                         &w, &h, &stride,
-                                                         &pass_stride, &r,
-                                                         &f};
-                       CUDA_LAUNCH_KERNEL_1D(cuNLMCalcDifference, calc_difference_args);
-                       CUDA_LAUNCH_KERNEL_1D(cuNLMBlur, blur_args);
-                       CUDA_LAUNCH_KERNEL_1D(cuNLMCalcWeight, calc_weight_args);
-                       CUDA_LAUNCH_KERNEL_1D(cuNLMBlur, blur_args);
-                       CUDA_LAUNCH_KERNEL_1D(cuNLMConstructGramian, construct_gramian_args);
-               }
+               CUfunction cuNLMCalcDifference, cuNLMBlur, cuNLMCalcWeight, cuNLMConstructGramian;
+               cuda_assert(cuModuleGetFunction(&cuNLMCalcDifference,   cuFilterModule, "kernel_cuda_filter_nlm_calc_difference"));
+               cuda_assert(cuModuleGetFunction(&cuNLMBlur,             cuFilterModule, "kernel_cuda_filter_nlm_blur"));
+               cuda_assert(cuModuleGetFunction(&cuNLMCalcWeight,       cuFilterModule, "kernel_cuda_filter_nlm_calc_weight"));
+               cuda_assert(cuModuleGetFunction(&cuNLMConstructGramian, cuFilterModule, "kernel_cuda_filter_nlm_construct_gramian"));
+               cuda_assert(cuFuncSetCacheConfig(cuNLMCalcDifference,   CU_FUNC_CACHE_PREFER_L1));
+               cuda_assert(cuFuncSetCacheConfig(cuNLMBlur,             CU_FUNC_CACHE_PREFER_L1));
+               cuda_assert(cuFuncSetCacheConfig(cuNLMCalcWeight,       CU_FUNC_CACHE_PREFER_L1));
+               cuda_assert(cuFuncSetCacheConfig(cuNLMConstructGramian, CU_FUNC_CACHE_PREFER_SHARED));
+               CUDA_GET_BLOCKSIZE_1D(cuNLMCalcDifference,
+                                    task->reconstruction_state.source_w * task->reconstruction_state.source_h,
+                                    num_shifts);
+               void *calc_difference_args[] = {&color_ptr,
+                                               &color_variance_ptr,
+                                               &scale_ptr,
+                                               &difference,
+                                               &w, &h,
+                                               &stride, &pass_stride,
+                                               &r, &pass_stride,
+                                               &frame_offset,
+                                               &a, &k_2};
+               void *blur_args[]            = {&difference, &blurDifference, &w, &h, &stride, &pass_stride, &r, &f};
+               void *calc_weight_args[]     = {&blurDifference, &difference, &w, &h, &stride, &pass_stride, &r, &f};
+               void *construct_gramian_args[] = {&t,
+                                                 &blurDifference,
+                                                 &task->buffer.mem.device_pointer,
+                                                 &task->storage.transform.device_pointer,
+                                                 &task->storage.rank.device_pointer,
+                                                 &task->storage.XtWX.device_pointer,
+                                                 &task->storage.XtWY.device_pointer,
+                                                 &task->reconstruction_state.filter_window,
+                                                 &w, &h, &stride,
+                                                 &pass_stride, &r,
+                                                 &f,
+                                                 &frame_offset,
+                                                 &task->buffer.use_time};
+               CUDA_LAUNCH_KERNEL_1D(cuNLMCalcDifference, calc_difference_args);
+               CUDA_LAUNCH_KERNEL_1D(cuNLMBlur, blur_args);
+               CUDA_LAUNCH_KERNEL_1D(cuNLMCalcWeight, calc_weight_args);
+               CUDA_LAUNCH_KERNEL_1D(cuNLMBlur, blur_args);
+               CUDA_LAUNCH_KERNEL_1D(cuNLMConstructGramian, construct_gramian_args);
+               cuda_assert(cuCtxSynchronize());
  
-               {
-                       CUfunction cuFinalize;
-                       cuda_assert(cuModuleGetFunction(&cuFinalize, cuFilterModule, "kernel_cuda_filter_finalize"));
-                       cuda_assert(cuFuncSetCacheConfig(cuFinalize, CU_FUNC_CACHE_PREFER_L1));
-                       void *finalize_args[] = {&output_ptr,
-                                                        &task->storage.rank.device_pointer,
-                                                        &task->storage.XtWX.device_pointer,
-                                                        &task->storage.XtWY.device_pointer,
-                                                        &task->filter_area,
-                                                        &task->reconstruction_state.buffer_params.x,
-                                                        &task->render_buffer.samples};
-                       CUDA_GET_BLOCKSIZE(cuFinalize,
-                                          task->reconstruction_state.source_w,
-                                          task->reconstruction_state.source_h);
-                       CUDA_LAUNCH_KERNEL(cuFinalize, finalize_args);
-               }
+               return !have_error();
+       }
  
+       bool denoising_solve(device_ptr output_ptr,
+                            DenoisingTask *task)
+       {
+               CUfunction cuFinalize;
+               cuda_assert(cuModuleGetFunction(&cuFinalize, cuFilterModule, "kernel_cuda_filter_finalize"));
+               cuda_assert(cuFuncSetCacheConfig(cuFinalize, CU_FUNC_CACHE_PREFER_L1));
+               void *finalize_args[] = {&output_ptr,
+                                        &task->storage.rank.device_pointer,
+                                        &task->storage.XtWX.device_pointer,
+                                        &task->storage.XtWY.device_pointer,
+                                        &task->filter_area,
+                                        &task->reconstruction_state.buffer_params.x,
+                                        &task->render_buffer.samples};
+               CUDA_GET_BLOCKSIZE(cuFinalize,
+                                  task->reconstruction_state.source_w,
+                                  task->reconstruction_state.source_h);
+               CUDA_LAUNCH_KERNEL(cuFinalize, finalize_args);
                cuda_assert(cuCtxSynchronize());
  
                return !have_error();
                                   int variance_offset,
                                   device_ptr mean_ptr,
                                   device_ptr variance_ptr,
+                                  float scale,
                                   DenoisingTask *task)
        {
                if(have_error())
                                &variance_offset,
                                &mean_ptr,
                                &variance_ptr,
+                               &scale,
                                &task->rect,
                                &task->render_buffer.pass_stride,
                                &task->render_buffer.offset};
                return !have_error();
        }
  
+       bool denoising_write_feature(int out_offset,
+                                    device_ptr from_ptr,
+                                    device_ptr buffer_ptr,
+                                    DenoisingTask *task)
+       {
+               if(have_error())
+                       return false;
+               CUDAContextScope scope(this);
+               CUfunction cuFilterWriteFeature;
+               cuda_assert(cuModuleGetFunction(&cuFilterWriteFeature, cuFilterModule, "kernel_cuda_filter_write_feature"));
+               cuda_assert(cuFuncSetCacheConfig(cuFilterWriteFeature, CU_FUNC_CACHE_PREFER_L1));
+               CUDA_GET_BLOCKSIZE(cuFilterWriteFeature,
+                                  task->filter_area.z,
+                                  task->filter_area.w);
+               void *args[] = {&task->render_buffer.samples,
+                               &task->reconstruction_state.buffer_params,
+                               &task->filter_area,
+                               &from_ptr,
+                               &buffer_ptr,
+                               &out_offset,
+                               &task->rect};
+               CUDA_LAUNCH_KERNEL(cuFilterWriteFeature, args);
+               cuda_assert(cuCtxSynchronize());
+               return !have_error();
+       }
        bool denoising_detect_outliers(device_ptr image_ptr,
                                       device_ptr variance_ptr,
                                       device_ptr depth_ptr,
        void denoise(RenderTile &rtile, DenoisingTask& denoising)
        {
                denoising.functions.construct_transform = function_bind(&CUDADevice::denoising_construct_transform, this, &denoising);
-               denoising.functions.reconstruct = function_bind(&CUDADevice::denoising_reconstruct, this, _1, _2, _3, &denoising);
+               denoising.functions.accumulate = function_bind(&CUDADevice::denoising_accumulate, this, _1, _2, _3, _4, &denoising);
+               denoising.functions.solve = function_bind(&CUDADevice::denoising_solve, this, _1, &denoising);
                denoising.functions.divide_shadow = function_bind(&CUDADevice::denoising_divide_shadow, this, _1, _2, _3, _4, _5, &denoising);
                denoising.functions.non_local_means = function_bind(&CUDADevice::denoising_non_local_means, this, _1, _2, _3, _4, &denoising);
                denoising.functions.combine_halves = function_bind(&CUDADevice::denoising_combine_halves, this, _1, _2, _3, _4, _5, _6, &denoising);
-               denoising.functions.get_feature = function_bind(&CUDADevice::denoising_get_feature, this, _1, _2, _3, _4, &denoising);
+               denoising.functions.get_feature = function_bind(&CUDADevice::denoising_get_feature, this, _1, _2, _3, _4, _5, &denoising);
+               denoising.functions.write_feature = function_bind(&CUDADevice::denoising_write_feature, this, _1, _2, _3, &denoising);
                denoising.functions.detect_outliers = function_bind(&CUDADevice::denoising_detect_outliers, this, _1, _2, _3, _4, &denoising);
  
                denoising.filter_area = make_int4(rtile.x, rtile.y, rtile.w, rtile.h);
  
                glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
  
 +              glActiveTexture(GL_TEXTURE0);
                glGenTextures(1, &pmem.cuTexId);
                glBindTexture(GL_TEXTURE_2D, pmem.cuTexId);
                if(mem.data_type == TYPE_HALF)
 -                      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F_ARB, pmem.w, pmem.h, 0, GL_RGBA, GL_HALF_FLOAT, NULL);
 +                      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, pmem.w, pmem.h, 0, GL_RGBA, GL_HALF_FLOAT, NULL);
                else
                        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, pmem.w, pmem.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
                }
        }
  
 -      void draw_pixels(device_memory& mem, int y, int w, int h, int dx, int dy, int width, int height, bool transparent,
 +      void draw_pixels(
 +          device_memory& mem, int y,
 +          int w, int h, int width, int height,
 +          int dx, int dy, int dw, int dh, bool transparent,
                const DeviceDrawParams &draw_params)
        {
                assert(mem.type == MEM_PIXELS);
  
                if(!background) {
 +                      const bool use_fallback_shader = (draw_params.bind_display_space_shader_cb == NULL);
                        PixelMem pmem = pixel_mem_map[mem.device_pointer];
                        float *vpointer;
  
                                offset *= sizeof(uint8_t);
  
                        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pmem.cuPBO);
 +                      glActiveTexture(GL_TEXTURE0);
                        glBindTexture(GL_TEXTURE_2D, pmem.cuTexId);
 -                      if(mem.data_type == TYPE_HALF)
 +                      if(mem.data_type == TYPE_HALF) {
                                glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_HALF_FLOAT, (void*)offset);
 -                      else
 +                      }
 +                      else {
                                glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)offset);
 +                      }
                        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
  
 -                      glEnable(GL_TEXTURE_2D);
 -
                        if(transparent) {
                                glEnable(GL_BLEND);
                                glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
                        }
  
 -                      glColor3f(1.0f, 1.0f, 1.0f);
 -
 -                      if(draw_params.bind_display_space_shader_cb) {
 +                      GLint shader_program;
 +                      if(use_fallback_shader) {
 +                              if(!bind_fallback_display_space_shader(dw, dh)) {
 +                                      return;
 +                              }
 +                              shader_program = fallback_shader_program;
 +                      }
 +                      else {
                                draw_params.bind_display_space_shader_cb();
 +                              glGetIntegerv(GL_CURRENT_PROGRAM, &shader_program);
                        }
  
 -                      if(!vertex_buffer)
 +                      if(!vertex_buffer) {
                                glGenBuffers(1, &vertex_buffer);
 +                      }
  
                        glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
                        /* invalidate old contents - avoids stalling if buffer is still waiting in queue to be rendered */
                                glUnmapBuffer(GL_ARRAY_BUFFER);
                        }
  
 -                      glTexCoordPointer(2, GL_FLOAT, 4 * sizeof(float), 0);
 -                      glVertexPointer(2, GL_FLOAT, 4 * sizeof(float), (char *)NULL + 2 * sizeof(float));
 +                      GLuint vertex_array_object;
 +                      GLuint position_attribute, texcoord_attribute;
  
 -                      glEnableClientState(GL_VERTEX_ARRAY);
 -                      glEnableClientState(GL_TEXTURE_COORD_ARRAY);
 +                      glGenVertexArrays(1, &vertex_array_object);
 +                      glBindVertexArray(vertex_array_object);
  
 -                      glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 +                      texcoord_attribute = glGetAttribLocation(shader_program, "texCoord");
 +                      position_attribute = glGetAttribLocation(shader_program, "pos");
  
 -                      glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 -                      glDisableClientState(GL_VERTEX_ARRAY);
 +                      glEnableVertexAttribArray(texcoord_attribute);
 +                      glEnableVertexAttribArray(position_attribute);
  
 -                      glBindBuffer(GL_ARRAY_BUFFER, 0);
 +                      glVertexAttribPointer(texcoord_attribute, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (const GLvoid *)0);
 +                      glVertexAttribPointer(position_attribute, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (const GLvoid *)(sizeof(float) * 2));
 +
 +                      glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
  
 -                      if(draw_params.unbind_display_space_shader_cb) {
 +                      if(use_fallback_shader) {
 +                              glUseProgram(0);
 +                      }
 +                      else {
                                draw_params.unbind_display_space_shader_cb();
                        }
  
 -                      if(transparent)
 +                      if(transparent) {
                                glDisable(GL_BLEND);
 +                      }
  
                        glBindTexture(GL_TEXTURE_2D, 0);
 -                      glDisable(GL_TEXTURE_2D);
  
                        return;
                }
  
 -              Device::draw_pixels(mem, y, w, h, dx, dy, width, height, transparent, draw_params);
 +              Device::draw_pixels(mem, y, w, h, width, height, dx, dy, dw, dh, transparent, draw_params);
        }
  
        void thread_run(DeviceTask *task)
@@@ -42,6 -42,7 +42,7 @@@ BufferParams::BufferParams(
  
        denoising_data_pass = false;
        denoising_clean_pass = false;
+       denoising_prefiltered_pass = false;
  
        Pass::add(PASS_COMBINED, passes);
  }
@@@ -73,6 -74,7 +74,7 @@@ int BufferParams::get_passes_size(
        if(denoising_data_pass) {
                size += DENOISING_PASS_SIZE_BASE;
                if(denoising_clean_pass) size += DENOISING_PASS_SIZE_CLEAN;
+               if(denoising_prefiltered_pass) size += DENOISING_PASS_SIZE_PREFILTERED;
        }
  
        return align_up(size, 4);
@@@ -88,6 -90,20 +90,20 @@@ int BufferParams::get_denoising_offset(
        return offset;
  }
  
+ int BufferParams::get_denoising_prefiltered_offset()
+ {
+       assert(denoising_prefiltered_pass);
+       int offset = get_denoising_offset();
+       offset += DENOISING_PASS_SIZE_BASE;
+       if(denoising_clean_pass) {
+               offset += DENOISING_PASS_SIZE_CLEAN;
+       }
+       return offset;
+ }
  /* Render Buffer Task */
  
  RenderTile::RenderTile()
@@@ -153,81 -169,62 +169,62 @@@ bool RenderBuffers::get_denoising_pass_
                return false;
        }
  
-       float invsample = 1.0f/sample;
-       float scale = invsample;
-       bool variance = (type == DENOISING_PASS_NORMAL_VAR) ||
-                       (type == DENOISING_PASS_ALBEDO_VAR) ||
-                       (type == DENOISING_PASS_DEPTH_VAR) ||
-                       (type == DENOISING_PASS_COLOR_VAR);
+       float scale = 1.0f;
+       float alpha_scale = 1.0f/sample;
+       if(type == DENOISING_PASS_PREFILTERED_COLOR ||
+          type == DENOISING_PASS_CLEAN ||
+          type == DENOISING_PASS_PREFILTERED_INTENSITY) {
+               scale *= exposure;
+       }
+       else if(type == DENOISING_PASS_PREFILTERED_VARIANCE) {
+               scale *= exposure*exposure * (sample - 1);
+       }
  
-       float scale_exposure = scale;
-       if(type == DENOISING_PASS_COLOR || type == DENOISING_PASS_CLEAN) {
-               scale_exposure *= exposure;
+       int offset;
+       if(type == DENOISING_PASS_CLEAN) {
+               /* The clean pass isn't changed by prefiltering, so we use the original one there. */
+               offset = type + params.get_denoising_offset();
        }
-       else if(type == DENOISING_PASS_COLOR_VAR) {
-               scale_exposure *= exposure*exposure;
+       else if (type == DENOISING_PASS_PREFILTERED_COLOR && !params.denoising_prefiltered_pass) {
+               /* If we're not saving the prefiltering result, return the original noisy pass. */
+               offset = params.get_denoising_offset() + DENOISING_PASS_COLOR;
+               scale /= sample;
+       }
+       else {
+               offset = type + params.get_denoising_prefiltered_offset();
        }
  
-       int offset = type + params.get_denoising_offset();
        int pass_stride = params.get_passes_size();
        int size = params.width*params.height;
  
-       if(variance) {
-               /* Approximate variance as E[x^2] - 1/N * (E[x])^2, since online variance
-                * update does not work efficiently with atomics in the kernel. */
-               int mean_offset = offset - components;
-               float *mean = buffer.data() + mean_offset;
-               float *var = buffer.data() + offset;
-               assert(mean_offset >= 0);
-               if(components == 1) {
-                       for(int i = 0; i < size; i++, mean += pass_stride, var += pass_stride, pixels++) {
-                               pixels[0] = max(0.0f, var[0] - mean[0]*mean[0]*invsample)*scale_exposure;
-                       }
+       float *in = buffer.data() + offset;
+       if(components == 1) {
+               for(int i = 0; i < size; i++, in += pass_stride, pixels++) {
+                       pixels[0] = in[0]*scale;
                }
-               else if(components == 3) {
-                       for(int i = 0; i < size; i++, mean += pass_stride, var += pass_stride, pixels += 3) {
-                               pixels[0] = max(0.0f, var[0] - mean[0]*mean[0]*invsample)*scale_exposure;
-                               pixels[1] = max(0.0f, var[1] - mean[1]*mean[1]*invsample)*scale_exposure;
-                               pixels[2] = max(0.0f, var[2] - mean[2]*mean[2]*invsample)*scale_exposure;
-                       }
+       }
+       else if(components == 3) {
+               for(int i = 0; i < size; i++, in += pass_stride, pixels += 3) {
+                       pixels[0] = in[0]*scale;
+                       pixels[1] = in[1]*scale;
+                       pixels[2] = in[2]*scale;
                }
-               else {
-                       return false;
+       }
+       else if(components == 4) {
+               /* Since the alpha channel is not involved in denoising, output the Combined alpha channel. */
+               assert(params.passes[0].type == PASS_COMBINED);
+               float *in_combined = buffer.data();
+               for(int i = 0; i < size; i++, in += pass_stride, in_combined += pass_stride, pixels += 4) {
+                       pixels[0] = in[0]*scale;
+                       pixels[1] = in[1]*scale;
+                       pixels[2] = in[2]*scale;
+                       pixels[3] = saturate(in_combined[3]*alpha_scale);
                }
        }
        else {
-               float *in = buffer.data() + offset;
-               if(components == 1) {
-                       for(int i = 0; i < size; i++, in += pass_stride, pixels++) {
-                               pixels[0] = in[0]*scale_exposure;
-                       }
-               }
-               else if(components == 3) {
-                       for(int i = 0; i < size; i++, in += pass_stride, pixels += 3) {
-                               pixels[0] = in[0]*scale_exposure;
-                               pixels[1] = in[1]*scale_exposure;
-                               pixels[2] = in[2]*scale_exposure;
-                       }
-               }
-               else if(components == 4) {
-                       assert(type == DENOISING_PASS_COLOR);
-                       /* Since the alpha channel is not involved in denoising, output the Combined alpha channel. */
-                       assert(params.passes[0].type == PASS_COMBINED);
-                       float *in_combined = buffer.data();
-                       for(int i = 0; i < size; i++, in += pass_stride, in_combined += pass_stride, pixels += 4) {
-                               pixels[0] = in[0]*scale_exposure;
-                               pixels[1] = in[1]*scale_exposure;
-                               pixels[2] = in[2]*scale_exposure;
-                               pixels[3] = saturate(in_combined[3]*scale);
-                       }
-               }
-               else {
-                       return false;
-               }
+               return false;
        }
  
        return true;
@@@ -472,11 -469,7 +469,11 @@@ void DisplayBuffer::draw(Device *device
                device_memory& rgba = (half_float)? (device_memory&)rgba_half:
                                                    (device_memory&)rgba_byte;
  
 -              device->draw_pixels(rgba, 0, draw_width, draw_height, params.full_x, params.full_y, params.width, params.height, transparent, draw_params);
 +              device->draw_pixels(
 +                          rgba, 0,
 +                          draw_width, draw_height, params.width, params.height,
 +                          params.full_x, params.full_y, params.full_width, params.full_height,
 +                          transparent, draw_params);
        }
  }
  
@@@ -286,6 -286,7 +286,7 @@@ NODE_DEFINE(Film
  
        SOCKET_BOOLEAN(denoising_data_pass,  "Generate Denoising Data Pass",  false);
        SOCKET_BOOLEAN(denoising_clean_pass, "Generate Denoising Clean Pass", false);
+       SOCKET_BOOLEAN(denoising_prefiltered_pass, "Generate Denoising Prefiltered Pass", false);
        SOCKET_INT(denoising_flags, "Denoising Flags", 0);
  
        return type;
@@@ -328,17 -329,8 +329,17 @@@ void Film::device_update(Device *device
        for(size_t i = 0; i < passes.size(); i++) {
                Pass& pass = passes[i];
  
 -              if(pass.type == PASS_NONE)
 +              if(pass.type == PASS_NONE) {
                        continue;
 +              }
 +
 +              /* Can't do motion pass if no motion vectors are available. */
 +              if (pass.type == PASS_MOTION || pass.type == PASS_MOTION_WEIGHT) {
 +                      if (scene->need_motion() != Scene::MOTION_PASS) {
 +                              kfilm->pass_stride += pass.components;
 +                              continue;
 +                      }
 +              }
  
                int pass_flag = (1 << (pass.type % 32));
                if(pass.type <= PASS_CATEGORY_MAIN_END) {
                        kfilm->pass_stride += DENOISING_PASS_SIZE_CLEAN;
                        kfilm->use_light_pass = 1;
                }
+               if(denoising_prefiltered_pass) {
+                       kfilm->pass_stride += DENOISING_PASS_SIZE_PREFILTERED;
+               }
        }
  
        kfilm->pass_stride = align_up(kfilm->pass_stride, 4);
@@@ -689,7 -689,7 +689,7 @@@ DeviceRequestedFeatures Session::get_re
        BakeManager *bake_manager = scene->bake_manager;
        requested_features.use_baking = bake_manager->get_baking();
        requested_features.use_integrator_branched = (scene->integrator->method == Integrator::BRANCHED_PATH);
-       if(params.denoising_passes) {
+       if(params.run_denoising) {
                requested_features.use_denoising = true;
                requested_features.use_shadow_tricks = true;
        }
@@@ -927,7 -927,7 +927,7 @@@ void Session::update_status_time(bool s
                         */
                        substatus += string_printf(", Sample %d/%d", progress.get_current_sample(), num_samples);
                }
-               if(params.use_denoising) {
+               if(params.run_denoising) {
                        substatus += string_printf(", Denoised %d tiles", progress.get_denoised_tiles());
                }
        }
                                          num_samples);
  
        if(show_pause) {
 -              status = "Paused";
 +              status = "Rendering Paused";
        }
        else if(show_done) {
 -              status = "Done";
 +              status = "Rendering Done";
                progress.set_end_time(); /* Save end time so that further calls to get_time are accurate. */
        }
        else {
@@@ -975,7 -975,7 +975,7 @@@ void Session::render(
        task.requested_tile_size = params.tile_size;
        task.passes_size = tile_manager.params.get_passes_size();
  
-       if(params.use_denoising) {
+       if(params.run_denoising) {
                task.denoising_radius = params.denoising_radius;
                task.denoising_strength = params.denoising_strength;
                task.denoising_feature_strength = params.denoising_feature_strength;
  
                assert(!scene->film->need_update);
                task.pass_stride = scene->film->pass_stride;
+               task.target_pass_stride = task.pass_stride;
                task.pass_denoising_data = scene->film->denoising_data_offset;
                task.pass_denoising_clean = scene->film->denoising_clean_offset;
+               task.denoising_from_render = true;
+               task.denoising_do_filter = params.full_denoising;
+               task.denoising_write_passes = params.write_denoising_passes;
        }
  
        device->task_add(task);