diff options
author | Brecht Van Lommel <brecht@blender.org> | 2021-09-14 16:37:47 +0300 |
---|---|---|
committer | Brecht Van Lommel <brecht@blender.org> | 2021-09-30 21:48:08 +0300 |
commit | a754e35198d852ea34e2b82cd2b126538e6f5a3b (patch) | |
tree | 9118b3fa19ab70aa1b50440ce62e5d028d940cfd /intern/cycles/render | |
parent | ac582056e2e70f3b0d91ff69d0307dd357e2e2ed (diff) |
Cycles: refactor API for GPU display
* Split GPUDisplay into two classes. PathTraceDisplay to implement the Cycles side,
and DisplayDriver to implement the host application side. The DisplayDriver is now
a fully abstract base class, embedded in the PathTraceDisplay.
* Move copy_pixels_to_texture implementation out of the host side into the Cycles side,
since it can be implemented in terms of the texture buffer mapping.
* Move definition of DeviceGraphicsInteropDestination into display driver header, so
that we do not need to expose private device headers in the public API.
* Add more detailed comments about how the DisplayDriver should be implemented.
The "driver" terminology might not be obvious, but is also used in other renderers.
Differential Revision: https://developer.blender.org/D12626
Diffstat (limited to 'intern/cycles/render')
-rw-r--r-- | intern/cycles/render/CMakeLists.txt | 3 | ||||
-rw-r--r-- | intern/cycles/render/display_driver.h | 131 | ||||
-rw-r--r-- | intern/cycles/render/gpu_display.cpp | 227 | ||||
-rw-r--r-- | intern/cycles/render/gpu_display.h | 247 | ||||
-rw-r--r-- | intern/cycles/render/session.cpp | 8 | ||||
-rw-r--r-- | intern/cycles/render/session.h | 4 |
6 files changed, 138 insertions, 482 deletions
diff --git a/intern/cycles/render/CMakeLists.txt b/intern/cycles/render/CMakeLists.txt index 6edb5261b32..ce1a9e5f430 100644 --- a/intern/cycles/render/CMakeLists.txt +++ b/intern/cycles/render/CMakeLists.txt @@ -35,7 +35,6 @@ set(SRC denoising.cpp film.cpp geometry.cpp - gpu_display.cpp graph.cpp hair.cpp image.cpp @@ -78,9 +77,9 @@ set(SRC_HEADERS colorspace.h constant_fold.h denoising.h + display_driver.h film.h geometry.h - gpu_display.h graph.h hair.h image.h diff --git a/intern/cycles/render/display_driver.h b/intern/cycles/render/display_driver.h new file mode 100644 index 00000000000..85f305034d7 --- /dev/null +++ b/intern/cycles/render/display_driver.h @@ -0,0 +1,131 @@ +/* + * Copyright 2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "util/util_half.h" +#include "util/util_types.h" + +CCL_NAMESPACE_BEGIN + +/* Display driver for efficient interactive display of renders. + * + * Host applications implement this interface for viewport rendering. For best performance, we + * recommend: + * - Allocating a texture on the GPU to be interactively updated + * - Using the graphics interop mechanism to avoid CPU-GPU copying overhead + * - Using a dedicated or thread-safe graphics API context for updates, to avoid + * blocking the host application. + */ +class DisplayDriver { + public: + DisplayDriver() = default; + virtual ~DisplayDriver() = default; + + /* Render buffer parameters. */ + struct Params { + public: + /* Render resolution, ignoring progressive resolution changes. + * The texture buffer should be allocated with this size. */ + int2 size = make_int2(0, 0); + + /* For border rendering, the full resolution of the render, and the offset within that larger + * render. */ + int2 full_size = make_int2(0, 0); + int2 full_offset = make_int2(0, 0); + + bool modified(const Params &other) const + { + return !(full_offset == other.full_offset && full_size == other.full_size && + size == other.size); + } + }; + + /* Update the render from the rendering thread. + * + * Cycles periodically updates the render to be displayed. For multithreaded updates with + * potentially multiple rendering devices, it will call these methods as follows. + * + * if (driver.update_begin(params, width, height)) { + * parallel_for_each(rendering_device) { + * buffer = driver.map_texture_buffer(); + * if (buffer) { + * fill(buffer); + * driver.unmap_texture_buffer(); + * } + * } + * driver.update_end(); + * } + * + * The parameters may dynamically change due to camera changes in the scene, and resources should + * be re-allocated accordingly. + * + * The width and height passed to update_begin() are the effective render resolution taking into + * account progressive resolution changes, which may be equal to or smaller than the params.size. + * For efficiency, changes in this resolution should be handled without re-allocating resources, + * but rather by using a subset of the full resolution buffer. */ + virtual bool update_begin(const Params ¶ms, int width, int height) = 0; + virtual void update_end() = 0; + + virtual half4 *map_texture_buffer() = 0; + virtual void unmap_texture_buffer() = 0; + + /* Optionally return a handle to a native graphics API texture buffer. If supported, + * the rendering device may write directly to this buffer instead of calling + * map_texture_buffer() and unmap_texture_buffer(). */ + class GraphicsInterop { + public: + /* Dimensions of the buffer, in pixels. */ + int buffer_width = 0; + int buffer_height = 0; + + /* OpenGL pixel buffer object. */ + int opengl_pbo_id = 0; + + /* Clear the entire buffer before doing partial write to it. */ + bool need_clear = false; + }; + + virtual GraphicsInterop graphics_interop_get() + { + return GraphicsInterop(); + } + + /* (De)activate graphics context required for editing or deleting the graphics interop + * object. + * + * For example, destruction of the CUDA object associated with an OpenGL requires the + * OpenGL context to be active. */ + virtual void graphics_interop_activate(){}; + virtual void graphics_interop_deactivate(){}; + + /* Clear the display buffer by filling it with zeros. */ + virtual void clear() = 0; + + /* Draw the render using the native graphics API. + * + * Note that this may be called in parallel to updates. The implementation is responsible for + * mutex locking or other mechanisms to avoid conflicts. + * + * The parameters may have changed since the last update. The implementation is responsible for + * deciding to skip or adjust render display for such changes. + * + * Host application drawing the render buffer should use Session.draw(), which will + * call this method. */ + virtual void draw(const Params ¶ms) = 0; +}; + +CCL_NAMESPACE_END diff --git a/intern/cycles/render/gpu_display.cpp b/intern/cycles/render/gpu_display.cpp deleted file mode 100644 index a8f0cc50583..00000000000 --- a/intern/cycles/render/gpu_display.cpp +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2021 Blender Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "render/gpu_display.h" - -#include "render/buffers.h" -#include "util/util_logging.h" - -CCL_NAMESPACE_BEGIN - -void GPUDisplay::reset(const BufferParams &buffer_params) -{ - thread_scoped_lock lock(mutex_); - - const GPUDisplayParams old_params = params_; - - params_.offset = make_int2(buffer_params.full_x, buffer_params.full_y); - params_.full_size = make_int2(buffer_params.full_width, buffer_params.full_height); - params_.size = make_int2(buffer_params.width, buffer_params.height); - - /* If the parameters did change tag texture as unusable. This avoids drawing old texture content - * in an updated configuration of the viewport. For example, avoids drawing old frame when render - * border did change. - * If the parameters did not change, allow drawing the current state of the texture, which will - * not count as an up-to-date redraw. This will avoid flickering when doping camera navigation by - * showing a previously rendered frame for until the new one is ready. */ - if (old_params.modified(params_)) { - texture_state_.is_usable = false; - } - - texture_state_.is_outdated = true; -} - -void GPUDisplay::mark_texture_updated() -{ - texture_state_.is_outdated = false; - texture_state_.is_usable = true; -} - -/* -------------------------------------------------------------------- - * Update procedure. - */ - -bool GPUDisplay::update_begin(int texture_width, int texture_height) -{ - DCHECK(!update_state_.is_active); - - if (update_state_.is_active) { - LOG(ERROR) << "Attempt to re-activate update process."; - return false; - } - - /* Get parameters within a mutex lock, to avoid reset() modifying them at the same time. - * The update itself is non-blocking however, for better performance and to avoid - * potential deadlocks due to locks held by the subclass. */ - GPUDisplayParams params; - { - thread_scoped_lock lock(mutex_); - params = params_; - texture_state_.size = make_int2(texture_width, texture_height); - } - - if (!do_update_begin(params, texture_width, texture_height)) { - LOG(ERROR) << "GPUDisplay implementation could not begin update."; - return false; - } - - update_state_.is_active = true; - - return true; -} - -void GPUDisplay::update_end() -{ - DCHECK(update_state_.is_active); - - if (!update_state_.is_active) { - LOG(ERROR) << "Attempt to deactivate inactive update process."; - return; - } - - do_update_end(); - - update_state_.is_active = false; -} - -int2 GPUDisplay::get_texture_size() const -{ - return texture_state_.size; -} - -/* -------------------------------------------------------------------- - * Texture update from CPU buffer. - */ - -void GPUDisplay::copy_pixels_to_texture( - const half4 *rgba_pixels, int texture_x, int texture_y, int pixels_width, int pixels_height) -{ - DCHECK(update_state_.is_active); - - if (!update_state_.is_active) { - LOG(ERROR) << "Attempt to copy pixels data outside of GPUDisplay update."; - return; - } - - mark_texture_updated(); - do_copy_pixels_to_texture(rgba_pixels, texture_x, texture_y, pixels_width, pixels_height); -} - -/* -------------------------------------------------------------------- - * Texture buffer mapping. - */ - -half4 *GPUDisplay::map_texture_buffer() -{ - DCHECK(!texture_buffer_state_.is_mapped); - DCHECK(update_state_.is_active); - - if (texture_buffer_state_.is_mapped) { - LOG(ERROR) << "Attempt to re-map an already mapped texture buffer."; - return nullptr; - } - - if (!update_state_.is_active) { - LOG(ERROR) << "Attempt to copy pixels data outside of GPUDisplay update."; - return nullptr; - } - - half4 *mapped_rgba_pixels = do_map_texture_buffer(); - - if (mapped_rgba_pixels) { - texture_buffer_state_.is_mapped = true; - } - - return mapped_rgba_pixels; -} - -void GPUDisplay::unmap_texture_buffer() -{ - DCHECK(texture_buffer_state_.is_mapped); - - if (!texture_buffer_state_.is_mapped) { - LOG(ERROR) << "Attempt to unmap non-mapped texture buffer."; - return; - } - - texture_buffer_state_.is_mapped = false; - - mark_texture_updated(); - do_unmap_texture_buffer(); -} - -/* -------------------------------------------------------------------- - * Graphics interoperability. - */ - -DeviceGraphicsInteropDestination GPUDisplay::graphics_interop_get() -{ - DCHECK(!texture_buffer_state_.is_mapped); - DCHECK(update_state_.is_active); - - if (texture_buffer_state_.is_mapped) { - LOG(ERROR) - << "Attempt to use graphics interoperability mode while the texture buffer is mapped."; - return DeviceGraphicsInteropDestination(); - } - - if (!update_state_.is_active) { - LOG(ERROR) << "Attempt to use graphics interoperability outside of GPUDisplay update."; - return DeviceGraphicsInteropDestination(); - } - - /* Assume that interop will write new values to the texture. */ - mark_texture_updated(); - - return do_graphics_interop_get(); -} - -void GPUDisplay::graphics_interop_activate() -{ -} - -void GPUDisplay::graphics_interop_deactivate() -{ -} - -/* -------------------------------------------------------------------- - * Drawing. - */ - -bool GPUDisplay::draw() -{ - /* Get parameters within a mutex lock, to avoid reset() modifying them at the same time. - * The drawing itself is non-blocking however, for better performance and to avoid - * potential deadlocks due to locks held by the subclass. */ - GPUDisplayParams params; - bool is_usable; - bool is_outdated; - - { - thread_scoped_lock lock(mutex_); - params = params_; - is_usable = texture_state_.is_usable; - is_outdated = texture_state_.is_outdated; - } - - if (is_usable) { - do_draw(params); - } - - return !is_outdated; -} - -CCL_NAMESPACE_END diff --git a/intern/cycles/render/gpu_display.h b/intern/cycles/render/gpu_display.h deleted file mode 100644 index 3c3cfaea513..00000000000 --- a/intern/cycles/render/gpu_display.h +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright 2021 Blender Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "device/device_graphics_interop.h" -#include "util/util_half.h" -#include "util/util_thread.h" -#include "util/util_types.h" - -CCL_NAMESPACE_BEGIN - -class BufferParams; - -/* GPUDisplay class takes care of drawing render result in a viewport. The render result is stored - * in a GPU-side texture, which is updated from a path tracer and drawn by an application. - * - * The base GPUDisplay does some special texture state tracking, which allows render Session to - * make decisions on whether reset for an updated state is possible or not. This state should only - * be tracked in a base class and a particular implementation should not worry about it. - * - * The subclasses should only implement the pure virtual methods, which allows them to not worry - * about parent method calls, which helps them to be as small and reliable as possible. */ - -class GPUDisplayParams { - public: - /* Offset of the display within a viewport. - * For example, set to a lower-bottom corner of border render in Blender's viewport. */ - int2 offset = make_int2(0, 0); - - /* Full viewport size. - * - * NOTE: Is not affected by the resolution divider. */ - int2 full_size = make_int2(0, 0); - - /* Effective viewport size. - * In the case of border render, size of the border rectangle. - * - * NOTE: Is not affected by the resolution divider. */ - int2 size = make_int2(0, 0); - - bool modified(const GPUDisplayParams &other) const - { - return !(offset == other.offset && full_size == other.full_size && size == other.size); - } -}; - -class GPUDisplay { - public: - GPUDisplay() = default; - virtual ~GPUDisplay() = default; - - /* Reset the display for the new state of render session. Is called whenever session is reset, - * which happens on changes like viewport navigation or viewport dimension change. - * - * This call will configure parameters for a changed buffer and reset the texture state. */ - void reset(const BufferParams &buffer_params); - - const GPUDisplayParams &get_params() const - { - return params_; - } - - /* -------------------------------------------------------------------- - * Update procedure. - * - * These calls indicates a desire of the caller to update content of the displayed texture. */ - - /* Returns true when update is ready. Update should be finished with update_end(). - * - * If false is returned then no update is possible, and no update_end() call is needed. - * - * The texture width and height denotes an actual resolution of the underlying render result. */ - bool update_begin(int texture_width, int texture_height); - - void update_end(); - - /* Get currently configured texture size of the display (as configured by `update_begin()`. */ - int2 get_texture_size() const; - - /* -------------------------------------------------------------------- - * Texture update from CPU buffer. - * - * NOTE: The GPUDisplay should be marked for an update being in process with `update_begin()`. - * - * Most portable implementation, which must be supported by all platforms. Might not be the most - * efficient one. - */ - - /* Copy buffer of rendered pixels of a given size into a given position of the texture. - * - * This function does not acquire a lock. The reason for this is is to allow use of this function - * for partial updates from different devices. In this case the caller will acquire the lock - * once, update all the slices and release - * the lock once. This will ensure that draw() will never use partially updated texture. */ - void copy_pixels_to_texture( - const half4 *rgba_pixels, int texture_x, int texture_y, int pixels_width, int pixels_height); - - /* -------------------------------------------------------------------- - * Texture buffer mapping. - * - * This functionality is used to update GPU-side texture content without need to maintain CPU - * side buffer on the caller. - * - * NOTE: The GPUDisplay should be marked for an update being in process with `update_begin()`. - * - * NOTE: Texture buffer can not be mapped while graphics interoperability is active. This means - * that `map_texture_buffer()` is not allowed between `graphics_interop_begin()` and - * `graphics_interop_end()` calls. - */ - - /* Map pixels memory form texture to a buffer available for write from CPU. Width and height will - * define a requested size of the texture to write to. - * Upon success a non-null pointer is returned and the texture buffer is to be unmapped. - * If an error happens during mapping, or if mapping is not supported by this GPU display a - * null pointer is returned and the buffer is NOT to be unmapped. - * - * NOTE: Usually the implementation will rely on a GPU context of some sort, and the GPU context - * is often can not be bound to two threads simultaneously, and can not be released from a - * different thread. This means that the mapping API should be used from the single thread only, - */ - half4 *map_texture_buffer(); - void unmap_texture_buffer(); - - /* -------------------------------------------------------------------- - * Graphics interoperability. - * - * A special code path which allows to update texture content directly from the GPU compute - * device. Complementary part of DeviceGraphicsInterop. - * - * NOTE: Graphics interoperability can not be used while the texture buffer is mapped. This means - * that `graphics_interop_get()` is not allowed between `map_texture_buffer()` and - * `unmap_texture_buffer()` calls. */ - - /* Get GPUDisplay graphics interoperability information which acts as a destination for the - * device API. */ - DeviceGraphicsInteropDestination graphics_interop_get(); - - /* (De)activate GPU display for graphics interoperability outside of regular display update - * routines. */ - virtual void graphics_interop_activate(); - virtual void graphics_interop_deactivate(); - - /* -------------------------------------------------------------------- - * Drawing. - */ - - /* Clear the texture by filling it with all zeroes. - * - * This call might happen in parallel with draw, but can never happen in parallel with the - * update. - * - * The actual zeroing can be deferred to a later moment. What is important is that after clear - * and before pixels update the drawing texture will be fully empty, and that partial update - * after clear will write new pixel values for an updating area, leaving everything else zeroed. - * - * If the GPU display supports graphics interoperability then the zeroing the display is to be - * delegated to the device via the `DeviceGraphicsInteropDestination`. */ - virtual void clear() = 0; - - /* Draw the current state of the texture. - * - * Returns true if this call did draw an updated state of the texture. */ - bool draw(); - - protected: - /* Implementation-specific calls which subclasses are to implement. - * These `do_foo()` method corresponds to their `foo()` calls, but they are purely virtual to - * simplify their particular implementation. */ - virtual bool do_update_begin(const GPUDisplayParams ¶ms, - int texture_width, - int texture_height) = 0; - virtual void do_update_end() = 0; - - virtual void do_copy_pixels_to_texture(const half4 *rgba_pixels, - int texture_x, - int texture_y, - int pixels_width, - int pixels_height) = 0; - - virtual half4 *do_map_texture_buffer() = 0; - virtual void do_unmap_texture_buffer() = 0; - - /* Note that this might be called in parallel to do_update_begin() and do_update_end(), - * the subclass is responsible for appropriate mutex locks to avoid multiple threads - * editing and drawing the texture at the same time. */ - virtual void do_draw(const GPUDisplayParams ¶ms) = 0; - - virtual DeviceGraphicsInteropDestination do_graphics_interop_get() = 0; - - private: - thread_mutex mutex_; - GPUDisplayParams params_; - - /* Mark texture as its content has been updated. - * Used from places which knows that the texture content has been brought up-to-date, so that the - * drawing knows whether it can be performed, and whether drawing happened with an up-to-date - * texture state. */ - void mark_texture_updated(); - - /* State of the update process. */ - struct { - /* True when update is in process, indicated by `update_begin()` / `update_end()`. */ - bool is_active = false; - } update_state_; - - /* State of the texture, which is needed for an integration with render session and interactive - * updates and navigation. */ - struct { - /* Denotes whether possibly existing state of GPU side texture is still usable. - * It will not be usable in cases like render border did change (in this case we don't want - * previous texture to be rendered at all). - * - * However, if only navigation or object in scene did change, then the outdated state of the - * texture is still usable for draw, preventing display viewport flickering on navigation and - * object modifications. */ - bool is_usable = false; - - /* Texture is considered outdated after `reset()` until the next call of - * `copy_pixels_to_texture()`. */ - bool is_outdated = true; - - /* Texture size in pixels. */ - int2 size = make_int2(0, 0); - } texture_state_; - - /* State of the texture buffer. Is tracked to perform sanity checks. */ - struct { - /* True when the texture buffer is mapped with `map_texture_buffer()`. */ - bool is_mapped = false; - } texture_buffer_state_; -}; - -CCL_NAMESPACE_END diff --git a/intern/cycles/render/session.cpp b/intern/cycles/render/session.cpp index 269d67e8bda..c191b9a9b4a 100644 --- a/intern/cycles/render/session.cpp +++ b/intern/cycles/render/session.cpp @@ -25,7 +25,7 @@ #include "render/bake.h" #include "render/buffers.h" #include "render/camera.h" -#include "render/gpu_display.h" +#include "render/display_driver.h" #include "render/graph.h" #include "render/integrator.h" #include "render/light.h" @@ -162,7 +162,7 @@ bool Session::ready_to_reset() void Session::run_main_render_loop() { - path_trace_->clear_gpu_display(); + path_trace_->clear_display(); while (true) { RenderWork render_work = run_update_for_next_iteration(); @@ -514,9 +514,9 @@ void Session::set_pause(bool pause) } } -void Session::set_gpu_display(unique_ptr<GPUDisplay> gpu_display) +void Session::set_display_driver(unique_ptr<DisplayDriver> driver) { - path_trace_->set_gpu_display(move(gpu_display)); + path_trace_->set_display_driver(move(driver)); } double Session::get_estimated_remaining_time() const diff --git a/intern/cycles/render/session.h b/intern/cycles/render/session.h index e3056e7778b..607e40c47c1 100644 --- a/intern/cycles/render/session.h +++ b/intern/cycles/render/session.h @@ -35,9 +35,9 @@ CCL_NAMESPACE_BEGIN class BufferParams; class Device; class DeviceScene; +class DisplayDriver; class PathTrace; class Progress; -class GPUDisplay; class RenderBuffers; class Scene; class SceneParams; @@ -143,7 +143,7 @@ class Session { void set_samples(int samples); void set_time_limit(double time_limit); - void set_gpu_display(unique_ptr<GPUDisplay> gpu_display); + void set_display_driver(unique_ptr<DisplayDriver> driver); double get_estimated_remaining_time() const; |