blender/intern/cycles/integrator/path_trace_display.cpp
Brecht Van Lommel a754e35198 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
2021-09-30 20:48:08 +02:00

269 lines
7.5 KiB
C++

/*
* 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 "integrator/path_trace_display.h"
#include "render/buffers.h"
#include "util/util_logging.h"
CCL_NAMESPACE_BEGIN
PathTraceDisplay::PathTraceDisplay(unique_ptr<DisplayDriver> driver) : driver_(move(driver))
{
}
void PathTraceDisplay::reset(const BufferParams &buffer_params)
{
thread_scoped_lock lock(mutex_);
const DisplayDriver::Params old_params = params_;
params_.full_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 PathTraceDisplay::mark_texture_updated()
{
texture_state_.is_outdated = false;
texture_state_.is_usable = true;
}
/* --------------------------------------------------------------------
* Update procedure.
*/
bool PathTraceDisplay::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. */
DisplayDriver::Params params;
{
thread_scoped_lock lock(mutex_);
params = params_;
texture_state_.size = make_int2(texture_width, texture_height);
}
if (!driver_->update_begin(params, texture_width, texture_height)) {
LOG(ERROR) << "PathTraceDisplay implementation could not begin update.";
return false;
}
update_state_.is_active = true;
return true;
}
void PathTraceDisplay::update_end()
{
DCHECK(update_state_.is_active);
if (!update_state_.is_active) {
LOG(ERROR) << "Attempt to deactivate inactive update process.";
return;
}
driver_->update_end();
update_state_.is_active = false;
}
int2 PathTraceDisplay::get_texture_size() const
{
return texture_state_.size;
}
/* --------------------------------------------------------------------
* Texture update from CPU buffer.
*/
void PathTraceDisplay::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 PathTraceDisplay update.";
return;
}
mark_texture_updated();
/* This call copies pixels to a mapped texture buffer which is typically much cheaper from CPU
* time point of view than to copy data directly to a texture.
*
* The possible downside of this approach is that it might require a higher peak memory when
* doing partial updates of the texture (although, in practice even partial updates might peak
* with a full-frame buffer stored on the CPU if the GPU is currently occupied). */
half4 *mapped_rgba_pixels = map_texture_buffer();
if (!mapped_rgba_pixels) {
return;
}
const int texture_width = texture_state_.size.x;
const int texture_height = texture_state_.size.y;
if (texture_x == 0 && texture_y == 0 && pixels_width == texture_width &&
pixels_height == texture_height) {
const size_t size_in_bytes = sizeof(half4) * texture_width * texture_height;
memcpy(mapped_rgba_pixels, rgba_pixels, size_in_bytes);
}
else {
const half4 *rgba_row = rgba_pixels;
half4 *mapped_rgba_row = mapped_rgba_pixels + texture_y * texture_width + texture_x;
for (int y = 0; y < pixels_height;
++y, rgba_row += pixels_width, mapped_rgba_row += texture_width) {
memcpy(mapped_rgba_row, rgba_row, sizeof(half4) * pixels_width);
}
}
unmap_texture_buffer();
}
/* --------------------------------------------------------------------
* Texture buffer mapping.
*/
half4 *PathTraceDisplay::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 PathTraceDisplay update.";
return nullptr;
}
half4 *mapped_rgba_pixels = driver_->map_texture_buffer();
if (mapped_rgba_pixels) {
texture_buffer_state_.is_mapped = true;
}
return mapped_rgba_pixels;
}
void PathTraceDisplay::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();
driver_->unmap_texture_buffer();
}
/* --------------------------------------------------------------------
* Graphics interoperability.
*/
DisplayDriver::GraphicsInterop PathTraceDisplay::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 DisplayDriver::GraphicsInterop();
}
if (!update_state_.is_active) {
LOG(ERROR) << "Attempt to use graphics interoperability outside of PathTraceDisplay update.";
return DisplayDriver::GraphicsInterop();
}
/* Assume that interop will write new values to the texture. */
mark_texture_updated();
return driver_->graphics_interop_get();
}
void PathTraceDisplay::graphics_interop_activate()
{
driver_->graphics_interop_activate();
}
void PathTraceDisplay::graphics_interop_deactivate()
{
driver_->graphics_interop_deactivate();
}
/* --------------------------------------------------------------------
* Drawing.
*/
void PathTraceDisplay::clear()
{
driver_->clear();
}
bool PathTraceDisplay::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. */
DisplayDriver::Params 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) {
driver_->draw(params);
}
return !is_outdated;
}
CCL_NAMESPACE_END