Cycles: Rework tile scheduling for denoising
authorPatrick Mours <pmours@nvidia.com>
Wed, 26 Feb 2020 15:30:42 +0000 (16:30 +0100)
committerPatrick Mours <pmours@nvidia.com>
Fri, 28 Feb 2020 15:12:29 +0000 (16:12 +0100)
This fixes denoising being delayed until after all rendering has finished. Instead, tile-based
denoising is now part of the "RENDER" task again, so that it is all in one task and does not
cause issues with dedicated task pools where tasks are serialized.

Reviewed By: brecht

Differential Revision: https://developer.blender.org/D6940

12 files changed:
intern/cycles/device/cuda/device_cuda_impl.cpp
intern/cycles/device/device_cpu.cpp
intern/cycles/device/device_multi.cpp
intern/cycles/device/device_optix.cpp
intern/cycles/device/device_task.cpp
intern/cycles/device/device_task.h
intern/cycles/device/opencl/device_opencl_impl.cpp
intern/cycles/render/buffers.h
intern/cycles/render/session.cpp
intern/cycles/render/session.h
intern/cycles/render/tile.cpp
intern/cycles/render/tile.h

index f6a4f93a69022164a0da1bf9b952ea9cc1cd379c..4a7c45d8b93b680a92ca04bec2d77ec767ba54df 100644 (file)
@@ -2144,7 +2144,7 @@ void CUDADevice::thread_run(DeviceTask *task)
 {
   CUDAContextScope scope(this);
 
-  if (task->type == DeviceTask::RENDER || task->type == DeviceTask::DENOISE) {
+  if (task->type == DeviceTask::RENDER) {
     DeviceRequestedFeatures requested_features;
     if (use_split_kernel()) {
       if (split_kernel == NULL) {
@@ -2159,7 +2159,7 @@ void CUDADevice::thread_run(DeviceTask *task)
     RenderTile tile;
     DenoisingTask denoising(this, *task);
 
-    while (task->acquire_tile(this, tile)) {
+    while (task->acquire_tile(this, tile, task->tile_types)) {
       if (tile.task == RenderTile::PATH_TRACE) {
         if (use_split_kernel()) {
           device_only_memory<uchar> void_buffer(this, "void_buffer");
index 795781ee072db39858db7fda23ebb9066f23b7c7..1c9d2227ac337842c164c07a12decc3c6527a643 100644 (file)
@@ -511,7 +511,7 @@ class CPUDevice : public Device {
 
   void thread_run(DeviceTask *task)
   {
-    if (task->type == DeviceTask::RENDER || task->type == DeviceTask::DENOISE)
+    if (task->type == DeviceTask::RENDER)
       thread_render(*task);
     else if (task->type == DeviceTask::SHADER)
       thread_shader(*task);
@@ -927,7 +927,7 @@ class CPUDevice : public Device {
     DenoisingTask denoising(this, task);
     denoising.profiler = &kg->profiler;
 
-    while (task.acquire_tile(this, tile)) {
+    while (task.acquire_tile(this, tile, task.tile_types)) {
       if (tile.task == RenderTile::PATH_TRACE) {
         if (use_split_kernel) {
           device_only_memory<uchar> void_buffer(this, "void_buffer");
index 8226221ea080e6f2612cff8368260cfc027bc61e..0044610eeb4b61bedc439f6892d8a9acabb1235e 100644 (file)
@@ -482,11 +482,24 @@ class MultiDevice : public Device {
 
   void task_add(DeviceTask &task)
   {
-    list<SubDevice> &task_devices = denoising_devices.empty() ||
-                                            (task.type != DeviceTask::DENOISE &&
-                                             task.type != DeviceTask::DENOISE_BUFFER) ?
-                                        devices :
-                                        denoising_devices;
+    list<SubDevice> task_devices = devices;
+    if (!denoising_devices.empty()) {
+      if (task.type == DeviceTask::DENOISE_BUFFER) {
+        /* Denoising tasks should be redirected to the denoising devices entirely. */
+        task_devices = denoising_devices;
+      }
+      else if (task.type == DeviceTask::RENDER && (task.tile_types & RenderTile::DENOISE)) {
+        const uint tile_types = task.tile_types;
+        /* For normal rendering tasks only redirect the denoising part to the denoising devices.
+         * Do not need to split the task here, since they all run through 'acquire_tile'. */
+        task.tile_types = RenderTile::DENOISE;
+        foreach (SubDevice &sub, denoising_devices) {
+          sub.device->task_add(task);
+        }
+        /* Rendering itself should still be executed on the rendering devices. */
+        task.tile_types = tile_types ^ RenderTile::DENOISE;
+      }
+    }
 
     list<DeviceTask> tasks;
     task.split(tasks, task_devices.size());
index 0121b89e9d8e50eaf2bad9b6b4d1e5e2045da2cb..61a5c74f69e7d7c77a5d9a8f0f09e7c9d1a9f160 100644 (file)
@@ -569,9 +569,14 @@ class OptiXDevice : public CUDADevice {
     if (have_error())
       return;  // Abort early if there was an error previously
 
-    if (task.type == DeviceTask::RENDER || task.type == DeviceTask::DENOISE) {
+    if (task.type == DeviceTask::RENDER) {
+      if (thread_index != 0) {
+        // Only execute denoising in a single thread (see also 'task_add')
+        task.tile_types &= ~RenderTile::DENOISE;
+      }
+
       RenderTile tile;
-      while (task.acquire_tile(this, tile)) {
+      while (task.acquire_tile(this, tile, task.tile_types)) {
         if (tile.task == RenderTile::PATH_TRACE)
           launch_render(task, tile, thread_index);
         else if (tile.task == RenderTile::DENOISE)
@@ -1451,7 +1456,7 @@ class OptiXDevice : public CUDADevice {
       return;
     }
 
-    if (task.type == DeviceTask::DENOISE || task.type == DeviceTask::DENOISE_BUFFER) {
+    if (task.type == DeviceTask::DENOISE_BUFFER) {
       // Execute denoising in a single thread (e.g. to avoid race conditions during creation)
       task_pool.push(new OptiXDeviceTask(this, task, 0));
       return;
index 8f15e8c8c1e71832e2f8180d8d418c717c13f443..36522b874ab21926844880242be2e94da807a656 100644 (file)
@@ -68,7 +68,7 @@ int DeviceTask::get_subtask_count(int num, int max_size)
   if (type == SHADER) {
     num = min(shader_w, num);
   }
-  else if (type == RENDER || type == DENOISE) {
+  else if (type == RENDER) {
   }
   else {
     num = min(h, num);
@@ -94,7 +94,7 @@ void DeviceTask::split(list<DeviceTask> &tasks, int num, int max_size)
       tasks.push_back(task);
     }
   }
-  else if (type == RENDER || type == DENOISE) {
+  else if (type == RENDER) {
     for (int i = 0; i < num; i++)
       tasks.push_back(*this);
   }
index 0f718528b86bb4332f629acafcbe70f460e0bdfd..972f61310923a2b9821e32d192107d0593ac96b8 100644 (file)
@@ -64,7 +64,7 @@ class DenoiseParams {
 
 class DeviceTask : public Task {
  public:
-  typedef enum { RENDER, DENOISE, DENOISE_BUFFER, FILM_CONVERT, SHADER } Type;
+  typedef enum { RENDER, FILM_CONVERT, SHADER, DENOISE_BUFFER } Type;
   Type type;
 
   int x, y, w, h;
@@ -90,7 +90,7 @@ class DeviceTask : public Task {
 
   void update_progress(RenderTile *rtile, int pixel_samples = -1);
 
-  function<bool(Device *device, RenderTile &)> acquire_tile;
+  function<bool(Device *device, RenderTile &, uint)> acquire_tile;
   function<void(long, int)> update_progress_sample;
   function<void(RenderTile &)> update_tile_sample;
   function<void(RenderTile &)> release_tile;
@@ -98,6 +98,7 @@ class DeviceTask : public Task {
   function<void(RenderTile *, Device *)> map_neighbor_tiles;
   function<void(RenderTile *, Device *)> unmap_neighbor_tiles;
 
+  uint tile_types;
   DenoiseParams denoising;
   bool denoising_from_render;
   vector<int> denoising_frames;
index 012f6dbe11479f62336e8adcfe7b48dbcd88a984..68cdfd5238ca4a9da4289c0e0b602e6f0158d2fa 100644 (file)
@@ -1308,7 +1308,7 @@ void OpenCLDevice::thread_run(DeviceTask *task)
 {
   flush_texture_buffers();
 
-  if (task->type == DeviceTask::RENDER || task->type == DeviceTask::DENOISE) {
+  if (task->type == DeviceTask::RENDER) {
     RenderTile tile;
     DenoisingTask denoising(this, *task);
 
@@ -1317,7 +1317,7 @@ void OpenCLDevice::thread_run(DeviceTask *task)
     kgbuffer.alloc_to_device(1);
 
     /* Keep rendering tiles until done. */
-    while (task->acquire_tile(this, tile)) {
+    while (task->acquire_tile(this, tile, task->tile_types)) {
       if (tile.task == RenderTile::PATH_TRACE) {
         assert(tile.task == RenderTile::PATH_TRACE);
         scoped_timer timer(&tile.buffers->render_time);
index 1042b42810f5d3f720616be87214c7b60b2d4fa7..42efb031843c15faf39a267663b514543df4a0ba 100644 (file)
@@ -130,7 +130,7 @@ class DisplayBuffer {
 
 class RenderTile {
  public:
-  typedef enum { PATH_TRACE, DENOISE } Task;
+  typedef enum { PATH_TRACE = (1 << 0), DENOISE = (1 << 1) } Task;
 
   Task task;
   int x, y, w, h;
index f45e6d68c97e026ef1c8bcab70077e4d9dadd0d1..0d1f8df3610a6afe902ba95bb0998673a9b5d283 100644 (file)
@@ -301,12 +301,7 @@ void Session::run_gpu()
       update_status_time();
 
       /* render */
-      render();
-
-      /* denoise */
-      if (need_denoise) {
-        denoise();
-      }
+      render(need_denoise);
 
       device->task_wait();
 
@@ -384,7 +379,7 @@ bool Session::draw_cpu(BufferParams &buffer_params, DeviceDrawParams &draw_param
   return false;
 }
 
-bool Session::acquire_tile(Device *tile_device, RenderTile &rtile, RenderTile::Task task)
+bool Session::acquire_tile(RenderTile &rtile, Device *tile_device, uint tile_types)
 {
   if (progress.get_cancel()) {
     if (params.progressive_refine == false) {
@@ -399,9 +394,9 @@ bool Session::acquire_tile(Device *tile_device, RenderTile &rtile, RenderTile::T
   Tile *tile;
   int device_num = device->device_number(tile_device);
 
-  while (!tile_manager.next_tile(tile, device_num, task == RenderTile::DENOISE)) {
+  while (!tile_manager.next_tile(tile, device_num, tile_types)) {
     /* Wait for denoising tiles to become available */
-    if (task == RenderTile::DENOISE && !progress.get_cancel() && tile_manager.has_tiles()) {
+    if ((tile_types & RenderTile::DENOISE) && !progress.get_cancel() && tile_manager.has_tiles()) {
       denoising_cond.wait(tile_lock);
       continue;
     }
@@ -417,7 +412,7 @@ bool Session::acquire_tile(Device *tile_device, RenderTile &rtile, RenderTile::T
   rtile.num_samples = tile_manager.state.num_samples;
   rtile.resolution = tile_manager.state.resolution_divider;
   rtile.tile_index = tile->index;
-  rtile.task = task;
+  rtile.task = tile->state == Tile::DENOISE ? RenderTile::DENOISE : RenderTile::PATH_TRACE;
 
   tile_lock.unlock();
 
@@ -700,12 +695,7 @@ void Session::run_cpu()
       update_status_time();
 
       /* render */
-      render();
-
-      /* denoise */
-      if (need_denoise) {
-        denoise();
-      }
+      render(need_denoise);
 
       /* update status and timing */
       update_status_time();
@@ -1089,100 +1079,91 @@ void Session::update_status_time(bool show_pause, bool show_done)
   progress.set_status(status, substatus);
 }
 
-void Session::render()
+void Session::render(bool with_denoising)
 {
-  /* Clear buffers. */
   if (buffers && tile_manager.state.sample == tile_manager.range_start_sample) {
+    /* Clear buffers. */
     buffers->zero();
   }
 
+  if (tile_manager.state.buffer.width == 0 || tile_manager.state.buffer.height == 0) {
+    return; /* Avoid empty launches. */
+  }
+
   /* Add path trace task. */
   DeviceTask task(DeviceTask::RENDER);
 
-  task.acquire_tile = function_bind(&Session::acquire_tile, this, _1, _2, RenderTile::PATH_TRACE);
+  task.acquire_tile = function_bind(&Session::acquire_tile, this, _2, _1, _3);
   task.release_tile = function_bind(&Session::release_tile, this, _1);
+  task.map_neighbor_tiles = function_bind(&Session::map_neighbor_tiles, this, _1, _2);
+  task.unmap_neighbor_tiles = function_bind(&Session::unmap_neighbor_tiles, this, _1, _2);
   task.get_cancel = function_bind(&Progress::get_cancel, &this->progress);
   task.update_tile_sample = function_bind(&Session::update_tile_sample, this, _1);
   task.update_progress_sample = function_bind(&Progress::add_samples, &this->progress, _1, _2);
   task.need_finish_queue = params.progressive_refine;
   task.integrator_branched = scene->integrator->method == Integrator::BRANCHED_PATH;
 
-  device->task_add(task);
-}
+  /* Acquire render tiles by default. */
+  task.tile_types = RenderTile::PATH_TRACE;
 
-void Session::denoise()
-{
-  if (!params.run_denoising) {
-    return;
-  }
+  with_denoising = params.run_denoising && with_denoising;
+  if (with_denoising) {
+    /* Do not denoise viewport until the sample at which denoising should start is reached. */
+    if (!params.background && tile_manager.state.sample < params.denoising_start_sample) {
+      with_denoising = false;
+    }
 
-  /* Do not denoise viewport until the sample at which denoising should start is reached. */
-  if (!params.background && tile_manager.state.sample < params.denoising_start_sample) {
-    return;
-  }
+    /* Cannot denoise with resolution divider and separate denoising devices.
+     * It breaks the copy in 'MultiDevice::map_neighbor_tiles' (which operates on the full buffer
+     * dimensions and not the scaled ones). */
+    if (!params.device.denoising_devices.empty() && tile_manager.state.resolution_divider > 1) {
+      with_denoising = false;
+    }
 
-  /* Cannot denoise with resolution divider and separate denoising devices.
-   * It breaks the copy in 'MultiDevice::map_neighbor_tiles' (which operates on the full buffer
-   * dimensions and not the scaled ones). */
-  if (!params.device.denoising_devices.empty() && tile_manager.state.resolution_divider > 1) {
-    return;
+    /* It can happen that denoising was already enabled, but the scene still needs an update. */
+    if (scene->film->need_update || !scene->film->denoising_data_offset) {
+      with_denoising = false;
+    }
   }
 
-  /* It can happen that denoising was already enabled, but the scene still needs an update. */
-  if (scene->film->need_update || !scene->film->denoising_data_offset) {
-    return;
-  }
+  if (with_denoising) {
+    task.denoising = params.denoising;
 
-  /* Add separate denoising task. */
-  DeviceTask task(DeviceTask::DENOISE);
+    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;
 
-  if (tile_manager.schedule_denoising) {
-    /* Run denoising on each tile. */
-    task.acquire_tile = function_bind(&Session::acquire_tile, this, _1, _2, RenderTile::DENOISE);
-    task.release_tile = function_bind(&Session::release_tile, this, _1);
-    task.update_tile_sample = function_bind(&Session::update_tile_sample, this, _1);
-    task.update_progress_sample = function_bind(&Progress::add_samples, &this->progress, _1, _2);
-  }
-  else {
-    assert(buffers);
+    task.denoising_from_render = true;
+    task.denoising_do_filter = params.full_denoising;
+    task.denoising_use_optix = params.optix_denoising;
+    task.denoising_write_passes = params.write_denoising_passes;
 
-    if (tile_manager.state.buffer.width == 0 || tile_manager.state.buffer.height == 0) {
-      return; /* Avoid empty launches. */
+    if (tile_manager.schedule_denoising) {
+      /* Acquire denoising tiles during rendering. */
+      task.tile_types |= RenderTile::DENOISE;
     }
+    else {
+      assert(buffers);
 
-    /* Wait for rendering to finish. */
-    device->task_wait();
+      /* Schedule rendering and wait for it to finish. */
+      device->task_add(task);
+      device->task_wait();
 
-    /* Run denoising on the whole image at once. */
-    task.type = DeviceTask::DENOISE_BUFFER;
-    task.x = tile_manager.state.buffer.full_x;
-    task.y = tile_manager.state.buffer.full_y;
-    task.w = tile_manager.state.buffer.width;
-    task.h = tile_manager.state.buffer.height;
-    task.buffer = buffers->buffer.device_pointer;
-    task.sample = tile_manager.state.sample;
-    task.num_samples = tile_manager.state.num_samples;
-    tile_manager.state.buffer.get_offset_stride(task.offset, task.stride);
-    task.buffers = buffers;
+      /* Then run denoising on the whole image at once. */
+      task.type = DeviceTask::DENOISE_BUFFER;
+      task.x = tile_manager.state.buffer.full_x;
+      task.y = tile_manager.state.buffer.full_y;
+      task.w = tile_manager.state.buffer.width;
+      task.h = tile_manager.state.buffer.height;
+      task.buffer = buffers->buffer.device_pointer;
+      task.sample = tile_manager.state.sample;
+      task.num_samples = tile_manager.state.num_samples;
+      tile_manager.state.buffer.get_offset_stride(task.offset, task.stride);
+      task.buffers = buffers;
+    }
   }
 
-  task.get_cancel = function_bind(&Progress::get_cancel, &this->progress);
-  task.need_finish_queue = params.progressive_refine;
-  task.map_neighbor_tiles = function_bind(&Session::map_neighbor_tiles, this, _1, _2);
-  task.unmap_neighbor_tiles = function_bind(&Session::unmap_neighbor_tiles, this, _1, _2);
-
-  task.denoising = params.denoising;
-
-  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_use_optix = params.optix_denoising;
-  task.denoising_write_passes = params.write_denoising_passes;
-
   device->task_add(task);
 }
 
index 40ec3979afd7b7f30d1ebb5917a68f73ad46b930..7f3614ccb190c888a0895e041d23d1c1dd306119 100644 (file)
@@ -183,8 +183,7 @@ class Session {
 
   void update_status_time(bool show_pause = false, bool show_done = false);
 
-  void render();
-  void denoise();
+  void render(bool with_denoising);
   void copy_to_display_buffer(int sample);
 
   void reset_(BufferParams &params, int samples);
@@ -197,7 +196,7 @@ class Session {
   bool draw_gpu(BufferParams &params, DeviceDrawParams &draw_params);
   void reset_gpu(BufferParams &params, int samples);
 
-  bool acquire_tile(Device *tile_device, RenderTile &tile, RenderTile::Task task);
+  bool acquire_tile(RenderTile &tile, Device *tile_device, uint tile_types);
   void update_tile_sample(RenderTile &tile);
   void release_tile(RenderTile &tile);
 
index 4ddfd56cd01b8ca1c9b8259eb8ee7c52dbf19f5e..1480b6d1aab49f827408c1e7a102477ce4fe0e95 100644 (file)
@@ -495,20 +495,20 @@ bool TileManager::finish_tile(int index, bool &delete_tile)
   }
 }
 
-bool TileManager::next_tile(Tile *&tile, int device, bool denoising)
+bool TileManager::next_tile(Tile *&tile, int device, uint tile_types)
 {
   /* Preserve device if requested, unless this is a separate denoising device that just wants to
    * grab any available tile. */
   const bool preserve_device = preserve_tile_device && device < num_devices;
 
-  int tile_index = -1;
-  int logical_device = preserve_device ? device : 0;
+  if (tile_types & RenderTile::DENOISE) {
+    int tile_index = -1;
+    int logical_device = preserve_device ? device : 0;
 
-  if (denoising) {
     while (logical_device < state.denoising_tiles.size()) {
       if (state.denoising_tiles[logical_device].empty()) {
         if (preserve_device) {
-          return false;
+          break;
         }
         else {
           logical_device++;
@@ -520,12 +520,21 @@ bool TileManager::next_tile(Tile *&tile, int device, bool denoising)
       state.denoising_tiles[logical_device].pop_front();
       break;
     }
+
+    if (tile_index >= 0) {
+      tile = &state.tiles[tile_index];
+      return true;
+    }
   }
-  else {
+
+  if (tile_types & RenderTile::PATH_TRACE) {
+    int tile_index = -1;
+    int logical_device = preserve_device ? device : 0;
+
     while (logical_device < state.render_tiles.size()) {
       if (state.render_tiles[logical_device].empty()) {
         if (preserve_device) {
-          return false;
+          break;
         }
         else {
           logical_device++;
@@ -537,11 +546,11 @@ bool TileManager::next_tile(Tile *&tile, int device, bool denoising)
       state.render_tiles[logical_device].pop_front();
       break;
     }
-  }
 
-  if (tile_index >= 0) {
-    tile = &state.tiles[tile_index];
-    return true;
+    if (tile_index >= 0) {
+      tile = &state.tiles[tile_index];
+      return true;
+    }
   }
 
   return false;
index 14c693683c4d2722199314543d9a39dbc86fed68..9fb9c1ca782e927bc267281063a881ebad51e9e4 100644 (file)
@@ -106,7 +106,7 @@ class TileManager {
   void reset(BufferParams &params, int num_samples);
   void set_samples(int num_samples);
   bool next();
-  bool next_tile(Tile *&tile, int device, bool denoising);
+  bool next_tile(Tile *&tile, int device, uint tile_types);
   bool finish_tile(int index, bool &delete_tile);
   bool done();
   bool has_tiles();