Add Chromium-only Blender WebEngine parity work
This commit is contained in:
76
blender-5.2.0/intern/cycles/integrator/CMakeLists.txt
Normal file
76
blender-5.2.0/intern/cycles/integrator/CMakeLists.txt
Normal file
@@ -0,0 +1,76 @@
|
||||
# SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set(INC
|
||||
..
|
||||
)
|
||||
|
||||
set(INC_SYS
|
||||
)
|
||||
|
||||
set(SRC
|
||||
adaptive_sampling.cpp
|
||||
denoiser.cpp
|
||||
denoiser_gpu.cpp
|
||||
denoiser_oidn.cpp
|
||||
denoiser_oidn_base.cpp
|
||||
denoiser_oidn_gpu.cpp
|
||||
denoiser_optix.cpp
|
||||
path_trace.cpp
|
||||
tile.cpp
|
||||
pass_accessor.cpp
|
||||
pass_accessor_cpu.cpp
|
||||
pass_accessor_gpu.cpp
|
||||
path_trace_display.cpp
|
||||
path_trace_tile.cpp
|
||||
path_trace_work.cpp
|
||||
path_trace_work_cpu.cpp
|
||||
path_trace_work_gpu.cpp
|
||||
render_scheduler.cpp
|
||||
shader_eval.cpp
|
||||
work_balancer.cpp
|
||||
work_tile_scheduler.cpp
|
||||
)
|
||||
|
||||
set(SRC_HEADERS
|
||||
adaptive_sampling.h
|
||||
denoiser.h
|
||||
denoiser_gpu.h
|
||||
denoiser_oidn.h
|
||||
denoiser_oidn_base.h
|
||||
denoiser_oidn_gpu.h
|
||||
denoiser_optix.h
|
||||
guiding.h
|
||||
path_trace.h
|
||||
tile.h
|
||||
pass_accessor.h
|
||||
pass_accessor_cpu.h
|
||||
pass_accessor_gpu.h
|
||||
path_trace_display.h
|
||||
path_trace_tile.h
|
||||
path_trace_work.h
|
||||
path_trace_work_cpu.h
|
||||
path_trace_work_gpu.h
|
||||
render_scheduler.h
|
||||
shader_eval.h
|
||||
work_balancer.h
|
||||
work_tile_scheduler.h
|
||||
)
|
||||
|
||||
set(LIB
|
||||
PUBLIC cycles_device
|
||||
|
||||
# NOTE: Is required for RenderBuffers access. Might consider moving files around a bit to
|
||||
# avoid such cyclic dependency.
|
||||
PUBLIC cycles_session
|
||||
|
||||
PUBLIC cycles_util
|
||||
PRIVATE bf::dependencies::optional::openimagedenoise
|
||||
PRIVATE bf::dependencies::optional::openpgl
|
||||
)
|
||||
|
||||
include_directories(${INC})
|
||||
include_directories(SYSTEM ${INC_SYS})
|
||||
|
||||
cycles_add_library(cycles_integrator "${LIB}" ${SRC} ${SRC_HEADERS})
|
||||
57
blender-5.2.0/intern/cycles/integrator/adaptive_sampling.cpp
Normal file
57
blender-5.2.0/intern/cycles/integrator/adaptive_sampling.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "integrator/adaptive_sampling.h"
|
||||
|
||||
#include "util/math.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
AdaptiveSampling::AdaptiveSampling() = default;
|
||||
|
||||
int AdaptiveSampling::align_samples(const int start_sample, const int num_samples) const
|
||||
{
|
||||
if (!use) {
|
||||
return num_samples;
|
||||
}
|
||||
|
||||
/*
|
||||
* The naive implementation goes as following:
|
||||
*
|
||||
* int count = 1;
|
||||
* while (!need_filter(start_sample + count - 1) && count < num_samples) {
|
||||
* ++count;
|
||||
* }
|
||||
* return count;
|
||||
*/
|
||||
|
||||
/* 0-based sample index at which first filtering will happen. */
|
||||
const int first_filter_sample = (min_samples + 1) | (adaptive_step - 1);
|
||||
|
||||
/* Allow as many samples as possible until the first filter sample. */
|
||||
if (start_sample + num_samples <= first_filter_sample) {
|
||||
return num_samples;
|
||||
}
|
||||
|
||||
const int next_filter_sample = max(first_filter_sample, start_sample | (adaptive_step - 1));
|
||||
|
||||
const int num_samples_until_filter = next_filter_sample - start_sample + 1;
|
||||
|
||||
return min(num_samples_until_filter, num_samples);
|
||||
}
|
||||
|
||||
bool AdaptiveSampling::need_filter(const int sample) const
|
||||
{
|
||||
if (!use) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sample <= min_samples) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (sample & (adaptive_step - 1)) == (adaptive_step - 1);
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
43
blender-5.2.0/intern/cycles/integrator/adaptive_sampling.h
Normal file
43
blender-5.2.0/intern/cycles/integrator/adaptive_sampling.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
class AdaptiveSampling {
|
||||
public:
|
||||
AdaptiveSampling();
|
||||
|
||||
/* Align number of samples so that they align with the adaptive filtering.
|
||||
*
|
||||
* Returns the new value for the `num_samples` so that after rendering so many samples on top
|
||||
* of `start_sample` filtering is required.
|
||||
*
|
||||
* The alignment happens in a way that allows to render as many samples as possible without
|
||||
* missing any filtering point. This means that the result is "clamped" by the nearest sample
|
||||
* at which filtering is needed. This is part of mechanism which ensures that all devices will
|
||||
* perform same exact filtering and adaptive sampling, regardless of their performance.
|
||||
*
|
||||
* `start_sample` is the 0-based index of sample.
|
||||
*
|
||||
* NOTE: The start sample is included into the number of samples to render. This means that
|
||||
* if the number of samples is 1, then the path tracer will render samples [align_samples],
|
||||
* if the number of samples is 2, then the path tracer will render samples [align_samples,
|
||||
* align_samples + 1] and so on. */
|
||||
int align_samples(const int start_sample, const int num_samples) const;
|
||||
|
||||
/* Check whether adaptive sampling filter should happen at this sample.
|
||||
* Returns false if the adaptive sampling is not use.
|
||||
*
|
||||
* `sample` is the 0-based index of sample. */
|
||||
bool need_filter(const int sample) const;
|
||||
|
||||
bool use = false;
|
||||
int adaptive_step = 0;
|
||||
int min_samples = 0;
|
||||
float threshold = 0.0f;
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
274
blender-5.2.0/intern/cycles/integrator/denoiser.cpp
Normal file
274
blender-5.2.0/intern/cycles/integrator/denoiser.cpp
Normal file
@@ -0,0 +1,274 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "integrator/denoiser.h"
|
||||
|
||||
#include "device/device.h"
|
||||
|
||||
#include "integrator/denoiser_oidn.h"
|
||||
#include "session/display_driver.h"
|
||||
#ifdef WITH_OPENIMAGEDENOISE
|
||||
# include "integrator/denoiser_oidn_gpu.h"
|
||||
#endif
|
||||
#ifdef WITH_OPTIX
|
||||
# include "integrator/denoiser_optix.h"
|
||||
#endif
|
||||
#include "session/buffers.h"
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/openimagedenoise.h"
|
||||
#include "util/progress.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Check whether given device is single (not a MultiDevice). */
|
||||
static bool is_single_device(const Device *device)
|
||||
{
|
||||
if (device->info.type == DEVICE_MULTI) {
|
||||
/* Assume multi-device is never created with a single sub-device.
|
||||
* If one requests such configuration it should be checked on the session level. */
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!device->info.multi_devices.empty()) {
|
||||
/* Some configurations will use multi_devices, but keep the type of an individual device.
|
||||
* This does simplify checks for homogeneous setups, but here we really need a single device.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Find best suitable device to perform denoiser on. Will iterate over possible sub-devices of
|
||||
* multi-device. */
|
||||
static Device *find_best_device(Device *device,
|
||||
const DenoiserType type,
|
||||
const GraphicsInteropDevice &interop_device)
|
||||
{
|
||||
Device *best_device = nullptr;
|
||||
|
||||
device->foreach_device([&](Device *sub_device) {
|
||||
if ((sub_device->info.denoisers & type) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!best_device) {
|
||||
best_device = sub_device;
|
||||
}
|
||||
else {
|
||||
/* Prefer non-CPU devices over CPU for performance reasons. */
|
||||
if (sub_device->info.type != DEVICE_CPU && best_device->info.type == DEVICE_CPU) {
|
||||
best_device = sub_device;
|
||||
}
|
||||
|
||||
/* Prefer a device that can use graphics interop for faster display update. */
|
||||
if (sub_device->should_use_graphics_interop(interop_device) &&
|
||||
!best_device->should_use_graphics_interop(interop_device))
|
||||
{
|
||||
best_device = sub_device;
|
||||
}
|
||||
|
||||
/* TODO(sergey): Choose fastest device from available ones. Taking into account performance
|
||||
* of the device and data transfer cost. */
|
||||
}
|
||||
});
|
||||
|
||||
return best_device;
|
||||
}
|
||||
|
||||
bool use_optix_denoiser(Device *denoiser_device, const DenoiseParams ¶ms)
|
||||
{
|
||||
#ifdef WITH_OPTIX
|
||||
return (params.type == DENOISER_OPTIX &&
|
||||
OptiXDenoiser::is_device_supported(denoiser_device->info));
|
||||
#else
|
||||
(void)denoiser_device;
|
||||
(void)params;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool use_gpu_oidn_denoiser(Device *denoiser_device, const DenoiseParams ¶ms)
|
||||
{
|
||||
#ifdef WITH_OPENIMAGEDENOISE
|
||||
return (params.type == DENOISER_OPENIMAGEDENOISE && params.use_gpu &&
|
||||
OIDNDenoiserGPU::is_device_supported(denoiser_device->info));
|
||||
#else
|
||||
(void)denoiser_device;
|
||||
(void)params;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
DenoiseParams get_effective_denoise_params(Device *denoiser_device,
|
||||
Device *cpu_fallback_device,
|
||||
const DenoiseParams ¶ms,
|
||||
const GraphicsInteropDevice &interop_device,
|
||||
Device *&single_denoiser_device)
|
||||
{
|
||||
DCHECK(params.use);
|
||||
|
||||
DenoiseParams effective_denoise_params = params;
|
||||
|
||||
single_denoiser_device = nullptr;
|
||||
if (is_single_device(denoiser_device)) {
|
||||
/* Simple case: denoising happens on a single device. */
|
||||
single_denoiser_device = denoiser_device;
|
||||
}
|
||||
else {
|
||||
/* Find best device from the ones which are proposed for denoising.
|
||||
* The choice is expected to be between a few GPUs, or between a GPU and a CPU
|
||||
* or between a few GPUs and a CPU. */
|
||||
single_denoiser_device = find_best_device(denoiser_device, params.type, interop_device);
|
||||
}
|
||||
/* Ensure that we have a device to be used later in the code below. */
|
||||
if (single_denoiser_device == nullptr) {
|
||||
single_denoiser_device = cpu_fallback_device;
|
||||
}
|
||||
|
||||
const bool is_cpu_denoiser_device = single_denoiser_device->info.type == DEVICE_CPU;
|
||||
if (is_cpu_denoiser_device == false) {
|
||||
if (use_optix_denoiser(single_denoiser_device, effective_denoise_params) ||
|
||||
use_gpu_oidn_denoiser(single_denoiser_device, effective_denoise_params))
|
||||
{
|
||||
/* Denoising parameters are correct and there is no need to fall back to CPU OIDN. */
|
||||
return effective_denoise_params;
|
||||
}
|
||||
}
|
||||
|
||||
/* Always fallback to OIDN on CPU. */
|
||||
effective_denoise_params.type = DENOISER_OPENIMAGEDENOISE;
|
||||
effective_denoise_params.use_gpu = false;
|
||||
effective_denoise_params.upscale_factor = 1.0f;
|
||||
|
||||
return effective_denoise_params;
|
||||
}
|
||||
|
||||
unique_ptr<Denoiser> Denoiser::create(Device *denoiser_device,
|
||||
Device *cpu_fallback_device,
|
||||
const DenoiseParams ¶ms,
|
||||
const GraphicsInteropDevice &interop_device)
|
||||
{
|
||||
|
||||
Device *single_denoiser_device;
|
||||
const DenoiseParams effective_denoiser_params = get_effective_denoise_params(
|
||||
denoiser_device, cpu_fallback_device, params, interop_device, single_denoiser_device);
|
||||
|
||||
const bool is_cpu_denoiser_device = single_denoiser_device->info.type == DEVICE_CPU;
|
||||
if (is_cpu_denoiser_device == false) {
|
||||
#ifdef WITH_OPTIX
|
||||
if (use_optix_denoiser(single_denoiser_device, effective_denoiser_params)) {
|
||||
return make_unique<OptiXDenoiser>(single_denoiser_device, effective_denoiser_params);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WITH_OPENIMAGEDENOISE
|
||||
/* If available and allowed, then we will use OpenImageDenoise on GPU, otherwise on CPU. */
|
||||
if (use_gpu_oidn_denoiser(single_denoiser_device, effective_denoiser_params)) {
|
||||
return make_unique<OIDNDenoiserGPU>(single_denoiser_device, effective_denoiser_params);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!openimagedenoise_supported()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Used preference CPU when possible, and fallback on CPU fallback device otherwise. */
|
||||
return make_unique<OIDNDenoiser>(is_cpu_denoiser_device ? single_denoiser_device :
|
||||
cpu_fallback_device,
|
||||
effective_denoiser_params);
|
||||
}
|
||||
|
||||
DenoiserType Denoiser::automatic_viewport_denoiser_type(const DeviceInfo &denoise_device_info)
|
||||
{
|
||||
#ifdef WITH_OPENIMAGEDENOISE
|
||||
if (denoise_device_info.type != DEVICE_CPU &&
|
||||
OIDNDenoiserGPU::is_device_supported(denoise_device_info))
|
||||
{
|
||||
return DENOISER_OPENIMAGEDENOISE;
|
||||
}
|
||||
#else
|
||||
(void)denoise_device_info;
|
||||
#endif
|
||||
|
||||
#ifdef WITH_OPTIX
|
||||
if (OptiXDenoiser::is_device_supported(denoise_device_info)) {
|
||||
return DENOISER_OPTIX;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WITH_OPENIMAGEDENOISE
|
||||
if (openimagedenoise_supported()) {
|
||||
return DENOISER_OPENIMAGEDENOISE;
|
||||
}
|
||||
#endif
|
||||
|
||||
return DENOISER_NONE;
|
||||
}
|
||||
|
||||
Denoiser::Denoiser(Device *denoiser_device, const DenoiseParams ¶ms)
|
||||
: denoiser_device_(denoiser_device), denoise_kernels_are_loaded_(false), params_(params)
|
||||
{
|
||||
DCHECK(denoiser_device_);
|
||||
DCHECK(params.use);
|
||||
}
|
||||
|
||||
void Denoiser::set_params(const DenoiseParams ¶ms)
|
||||
{
|
||||
DCHECK_EQ(params.type, params_.type);
|
||||
|
||||
if (params.type == params_.type) {
|
||||
params_ = params;
|
||||
}
|
||||
else {
|
||||
LOG_ERROR << "Attempt to change denoiser type.";
|
||||
}
|
||||
}
|
||||
|
||||
const DenoiseParams &Denoiser::get_params() const
|
||||
{
|
||||
return params_;
|
||||
}
|
||||
|
||||
bool Denoiser::load_kernels(Progress *progress)
|
||||
{
|
||||
/* If we have successfully loaded kernels once, then there is no need to repeat this again. */
|
||||
if (denoise_kernels_are_loaded_) {
|
||||
return denoise_kernels_are_loaded_;
|
||||
}
|
||||
|
||||
if (progress) {
|
||||
progress->set_status("Loading denoising kernels (may take a few minutes the first time)");
|
||||
}
|
||||
|
||||
if (!denoiser_device_) {
|
||||
set_error("No device available to denoise on");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Only need denoising feature, everything else is unused. */
|
||||
if (!denoiser_device_->load_kernels(KERNEL_FEATURE_DENOISING)) {
|
||||
string message = denoiser_device_->error_message();
|
||||
if (message.empty()) {
|
||||
message = "Failed loading denoising kernel, see console for errors";
|
||||
}
|
||||
set_error(message);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_DEBUG << "Will denoise on " << denoiser_device_->info.description << " ("
|
||||
<< denoiser_device_->info.id << ")";
|
||||
|
||||
denoise_kernels_are_loaded_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
Device *Denoiser::get_denoiser_device() const
|
||||
{
|
||||
return denoiser_device_;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
134
blender-5.2.0/intern/cycles/integrator/denoiser.h
Normal file
134
blender-5.2.0/intern/cycles/integrator/denoiser.h
Normal file
@@ -0,0 +1,134 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
/* TODO(sergey): The integrator folder might not be the best. Is easy to move files around if the
|
||||
* better place is figured out. */
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "device/denoise.h"
|
||||
#include "device/device.h"
|
||||
#include "util/unique_ptr.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
class BufferParams;
|
||||
class Device;
|
||||
class GraphicsInteropDevice;
|
||||
class RenderBuffers;
|
||||
class Progress;
|
||||
|
||||
bool use_optix_denoiser(Device *denoiser_device, const DenoiseParams ¶ms);
|
||||
|
||||
bool use_gpu_oidn_denoiser(Device *denoiser_device, const DenoiseParams ¶ms);
|
||||
|
||||
DenoiseParams get_effective_denoise_params(Device *denoiser_device,
|
||||
Device *cpu_fallback_device,
|
||||
const DenoiseParams ¶ms,
|
||||
const GraphicsInteropDevice &interop_device,
|
||||
Device *&single_denoiser_device);
|
||||
|
||||
/* Implementation of a specific denoising algorithm.
|
||||
*
|
||||
* This class takes care of breaking down denoising algorithm into a series of device calls or to
|
||||
* calls of an external API to denoise given input.
|
||||
*
|
||||
* TODO(sergey): Are we better with device or a queue here? */
|
||||
class Denoiser {
|
||||
public:
|
||||
/* Create denoiser for the given path trace device.
|
||||
*
|
||||
* Notes:
|
||||
* - The denoiser must be configured. This means that `params.use` must be true.
|
||||
* This is checked in debug builds.
|
||||
* - The device might be MultiDevice.
|
||||
* - If Denoiser from params is not supported by provided denoise device, then Blender will
|
||||
* fallback on the OIDN CPU denoising and use provided cpu_fallback_device.
|
||||
* - Specifying the graphics interop device helps pick a more efficient denoising device.*/
|
||||
static unique_ptr<Denoiser> create(Device *denoiser_device,
|
||||
Device *cpu_fallback_device,
|
||||
const DenoiseParams ¶ms,
|
||||
const GraphicsInteropDevice &interop_device);
|
||||
|
||||
virtual ~Denoiser() = default;
|
||||
|
||||
void set_params(const DenoiseParams ¶ms);
|
||||
const DenoiseParams &get_params() const;
|
||||
|
||||
/* Recommended type for viewport denoising. */
|
||||
static DenoiserType automatic_viewport_denoiser_type(const DeviceInfo &denoise_device_info);
|
||||
|
||||
/* Create devices and load kernels needed for denoising.
|
||||
* The progress is used to communicate state when kernels actually needs to be loaded.
|
||||
*
|
||||
* NOTE: The `progress` is an optional argument, can be nullptr. */
|
||||
virtual bool load_kernels(Progress *progress);
|
||||
|
||||
/* Denoise the entire buffer.
|
||||
*
|
||||
* Buffer parameters denotes an effective parameters used during rendering. It could be
|
||||
* a lower resolution render into a bigger allocated buffer, which is used in viewport during
|
||||
* navigation and non-unit pixel size. Use that instead of render_buffers->params.
|
||||
*
|
||||
* The buffer might be coming from a "foreign" device from what this denoise is created for.
|
||||
* This means that in general case the denoiser will make sure the input data is available on
|
||||
* the denoiser device, perform denoising, and put data back to the device where the buffer
|
||||
* came from.
|
||||
*
|
||||
* The `num_samples` corresponds to the number of samples in the render buffers. It is used
|
||||
* to scale buffers down to the "final" value in algorithms which don't do automatic exposure,
|
||||
* or which needs "final" value for data passes.
|
||||
*
|
||||
* The `allow_inplace_modification` means that the denoiser is allowed to do in-place
|
||||
* modification of the input passes (scaling them down i.e.). This will lower the memory
|
||||
* footprint of the denoiser but will make input passes "invalid" (from path tracer) point of
|
||||
* view.
|
||||
*
|
||||
* Returns true when all passes are denoised. Will return false if there is a denoiser error (for
|
||||
* example, caused by misconfigured denoiser) or when user requested to cancel rendering. */
|
||||
virtual bool denoise_buffer(const BufferParams &buffer_params,
|
||||
const BufferParams &denoised_buffer_params,
|
||||
RenderBuffers *render_buffers,
|
||||
int num_samples,
|
||||
bool allow_inplace_modification,
|
||||
float2 pixel_jitter = {}) = 0;
|
||||
|
||||
/* Get a device which is used to perform actual denoising.
|
||||
*
|
||||
* Notes:
|
||||
*
|
||||
* - The device can be different from the path tracing device. This happens, for example, when
|
||||
* using OptiX denoiser and rendering on CPU.
|
||||
*
|
||||
* - No threading safety is ensured in this call. This means, that it is up to caller to ensure
|
||||
* that there is no threading-conflict between denoising task lazily initializing the device
|
||||
* and access to this device happen. */
|
||||
Device *get_denoiser_device() const;
|
||||
|
||||
std::function<bool(void)> is_cancelled_cb;
|
||||
|
||||
bool is_cancelled() const
|
||||
{
|
||||
if (!is_cancelled_cb) {
|
||||
return false;
|
||||
}
|
||||
return is_cancelled_cb();
|
||||
}
|
||||
|
||||
void set_error(const string &error)
|
||||
{
|
||||
denoiser_device_->set_error(error);
|
||||
}
|
||||
|
||||
protected:
|
||||
Denoiser(Device *denoiser_device, const DenoiseParams ¶ms);
|
||||
|
||||
Device *denoiser_device_;
|
||||
bool denoise_kernels_are_loaded_;
|
||||
DenoiseParams params_;
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
464
blender-5.2.0/intern/cycles/integrator/denoiser_gpu.cpp
Normal file
464
blender-5.2.0/intern/cycles/integrator/denoiser_gpu.cpp
Normal file
@@ -0,0 +1,464 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "integrator/denoiser_gpu.h"
|
||||
|
||||
#include "device/denoise.h"
|
||||
#include "device/device.h"
|
||||
#include "device/memory.h"
|
||||
#include "device/queue.h"
|
||||
|
||||
#include "integrator/pass_accessor_gpu.h"
|
||||
|
||||
#include "session/buffers.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
DenoiserGPU::DenoiserGPU(Device *denoiser_device, const DenoiseParams ¶ms)
|
||||
: Denoiser(denoiser_device, params)
|
||||
{
|
||||
denoiser_queue_ = denoiser_device->gpu_queue_create();
|
||||
DCHECK(denoiser_queue_);
|
||||
}
|
||||
|
||||
DenoiserGPU::~DenoiserGPU() // NOLINT
|
||||
{
|
||||
/* Explicit implementation, to allow forward declaration of Device in the header. */
|
||||
}
|
||||
|
||||
bool DenoiserGPU::denoise_buffer(const BufferParams &buffer_params,
|
||||
const BufferParams &denoised_buffer_params,
|
||||
RenderBuffers *render_buffers,
|
||||
const int num_samples,
|
||||
const bool allow_inplace_modification,
|
||||
const float2 pixel_jitter)
|
||||
{
|
||||
Device *denoiser_device = get_denoiser_device();
|
||||
if (!denoiser_device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RenderBuffers local_render_buffers(denoiser_device);
|
||||
bool local_buffer_used = false;
|
||||
|
||||
if (denoiser_device == render_buffers->buffer.device) {
|
||||
/* The device can access an existing buffer pointer. */
|
||||
local_buffer_used = false;
|
||||
}
|
||||
else {
|
||||
LOG_DEBUG << "Creating temporary buffer on denoiser device.";
|
||||
|
||||
/* Create buffer which is available by the device used by denoiser. */
|
||||
|
||||
/* TODO(sergey): Optimize data transfers. For example, only copy denoising related passes,
|
||||
* ignoring other light ad data passes. */
|
||||
|
||||
local_buffer_used = true;
|
||||
|
||||
render_buffers->copy_from_device();
|
||||
|
||||
local_render_buffers.reset(denoised_buffer_params);
|
||||
|
||||
/* NOTE: The local buffer is allocated for an exact size of the effective render size, while
|
||||
* the input render buffer is allocated for the lowest resolution divider possible. So it is
|
||||
* important to only copy actually needed part of the input buffer. */
|
||||
memcpy(local_render_buffers.buffer.data(),
|
||||
render_buffers->buffer.data(),
|
||||
sizeof(float) * local_render_buffers.buffer.size());
|
||||
|
||||
denoiser_queue_->copy_to_device(local_render_buffers.buffer);
|
||||
}
|
||||
|
||||
{
|
||||
DenoiseContext context(denoiser_device,
|
||||
params_,
|
||||
buffer_params,
|
||||
denoised_buffer_params,
|
||||
local_buffer_used ? &local_render_buffers : render_buffers,
|
||||
num_samples,
|
||||
local_buffer_used || allow_inplace_modification,
|
||||
pixel_jitter);
|
||||
|
||||
if (!denoise_ensure(context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!denoise_filter_guiding_preprocess(context)) {
|
||||
LOG_ERROR << "Error preprocessing guiding passes.";
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Passes which will use real albedo when it is available. */
|
||||
if (!denoise_pass(context, PASS_COMBINED)) {
|
||||
return false;
|
||||
}
|
||||
if (!denoise_pass(context, PASS_SHADOW_CATCHER_MATTE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Passes which do not need albedo and hence if real is present it needs to become fake. */
|
||||
if (!denoise_pass(context, PASS_SHADOW_CATCHER)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (local_buffer_used) {
|
||||
local_render_buffers.copy_from_device();
|
||||
|
||||
render_buffers_host_copy_denoised(render_buffers,
|
||||
denoised_buffer_params,
|
||||
&local_render_buffers,
|
||||
local_render_buffers.params);
|
||||
|
||||
render_buffers->copy_to_device();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DenoiserGPU::denoise_ensure(DenoiseContext &context)
|
||||
{
|
||||
if (!denoise_create_if_needed(context)) {
|
||||
LOG_ERROR << "GPU denoiser creation has failed.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!denoise_configure_if_needed(context)) {
|
||||
LOG_ERROR << "GPU denoiser configuration has failed.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DenoiserGPU::denoise_filter_guiding_preprocess(const DenoiseContext &context)
|
||||
{
|
||||
const BufferParams &buffer_params = context.buffer_params;
|
||||
|
||||
const int work_size = buffer_params.width * buffer_params.height;
|
||||
|
||||
const DeviceKernelArguments args(&context.guiding_params.device_pointer,
|
||||
&context.guiding_params.pass_stride,
|
||||
&context.guiding_params.pass_albedo,
|
||||
&context.guiding_params.pass_normal,
|
||||
&context.guiding_params.pass_flow,
|
||||
&context.render_buffers->buffer.device_pointer,
|
||||
&buffer_params.offset,
|
||||
&buffer_params.stride,
|
||||
&buffer_params.pass_stride,
|
||||
&context.pass_sample_count,
|
||||
&context.pass_denoising_albedo,
|
||||
&context.pass_denoising_normal,
|
||||
&context.pass_motion,
|
||||
&buffer_params.full_x,
|
||||
&buffer_params.full_y,
|
||||
&buffer_params.width,
|
||||
&buffer_params.height,
|
||||
&context.num_samples);
|
||||
|
||||
return denoiser_queue_->enqueue(DEVICE_KERNEL_FILTER_GUIDING_PREPROCESS, work_size, args) &&
|
||||
denoise_filter_guiding_flip_y(context);
|
||||
}
|
||||
|
||||
DenoiserGPU::DenoiseContext::DenoiseContext(Device *device,
|
||||
const DenoiseParams ¶ms,
|
||||
const BufferParams &buffer_params,
|
||||
const BufferParams &denoised_buffer_params,
|
||||
RenderBuffers *render_buffers,
|
||||
const int num_samples,
|
||||
const bool allow_inplace_modification,
|
||||
const float2 pixel_jitter)
|
||||
: denoise_params(params),
|
||||
render_buffers(render_buffers),
|
||||
buffer_params(buffer_params),
|
||||
denoised_buffer_params(denoised_buffer_params),
|
||||
guiding_buffer(device, "denoiser guiding passes buffer", true),
|
||||
use_guiding_passes(params.passes != DENOISER_PASS_NONE),
|
||||
num_samples(num_samples),
|
||||
pixel_jitter(pixel_jitter)
|
||||
{
|
||||
pass_motion = buffer_params.get_pass_offset(PASS_MOTION);
|
||||
pass_sample_count = buffer_params.get_pass_offset(PASS_SAMPLE_COUNT);
|
||||
|
||||
if (params.passes & DENOISER_PASS_ALBEDO) {
|
||||
pass_denoising_albedo = buffer_params.get_pass_offset(PASS_DENOISING_ALBEDO);
|
||||
}
|
||||
if (params.passes & DENOISER_PASS_NORMAL) {
|
||||
pass_denoising_normal = buffer_params.get_pass_offset(PASS_DENOISING_NORMAL);
|
||||
}
|
||||
|
||||
if (params.temporally_stable) {
|
||||
prev_output.device_pointer = render_buffers->buffer.device_pointer;
|
||||
|
||||
prev_output.offset = buffer_params.get_pass_offset(PASS_DENOISING_PREVIOUS);
|
||||
|
||||
prev_output.stride = buffer_params.stride;
|
||||
prev_output.pass_stride = buffer_params.pass_stride;
|
||||
}
|
||||
|
||||
if (use_guiding_passes) {
|
||||
if (allow_inplace_modification) {
|
||||
guiding_params.device_pointer = render_buffers->buffer.device_pointer;
|
||||
|
||||
guiding_params.pass_albedo = pass_denoising_albedo;
|
||||
guiding_params.pass_normal = pass_denoising_normal;
|
||||
guiding_params.pass_flow = pass_motion;
|
||||
|
||||
guiding_params.stride = buffer_params.stride;
|
||||
guiding_params.pass_stride = buffer_params.pass_stride;
|
||||
}
|
||||
else {
|
||||
guiding_params.pass_stride = 0;
|
||||
if (params.passes & DENOISER_PASS_ALBEDO) {
|
||||
guiding_params.pass_albedo = guiding_params.pass_stride;
|
||||
guiding_params.pass_stride += 3;
|
||||
}
|
||||
if (params.passes & DENOISER_PASS_NORMAL) {
|
||||
guiding_params.pass_normal = guiding_params.pass_stride;
|
||||
guiding_params.pass_stride += 3;
|
||||
}
|
||||
if (params.passes & DENOISER_PASS_MOTION) {
|
||||
guiding_params.pass_flow = guiding_params.pass_stride;
|
||||
guiding_params.pass_stride += 2;
|
||||
}
|
||||
|
||||
guiding_params.stride = buffer_params.width;
|
||||
|
||||
guiding_buffer.alloc_to_device(buffer_params.width * buffer_params.height *
|
||||
guiding_params.pass_stride);
|
||||
guiding_params.device_pointer = guiding_buffer.device_pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DenoiserGPU::denoise_filter_color_postprocess(const DenoiseContext &context,
|
||||
const DenoisePass &pass)
|
||||
{
|
||||
if (!denoise_filter_color_flip_y(context, context.denoised_buffer_params, pass)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const BufferParams &buffer_params = context.denoised_buffer_params;
|
||||
|
||||
const int work_size = buffer_params.width * buffer_params.height;
|
||||
|
||||
const DeviceKernelArguments args(&context.render_buffers->buffer.device_pointer,
|
||||
&buffer_params.full_x,
|
||||
&buffer_params.full_y,
|
||||
&buffer_params.width,
|
||||
&buffer_params.height,
|
||||
&buffer_params.offset,
|
||||
&buffer_params.stride,
|
||||
&context.buffer_params.full_x,
|
||||
&context.buffer_params.full_y,
|
||||
&context.buffer_params.offset,
|
||||
&context.buffer_params.stride,
|
||||
&buffer_params.pass_stride,
|
||||
&context.num_samples,
|
||||
&pass.noisy_offset,
|
||||
&pass.denoised_offset,
|
||||
&context.pass_sample_count,
|
||||
&pass.num_components,
|
||||
&pass.use_compositing,
|
||||
¶ms_.upscale_factor);
|
||||
|
||||
return denoiser_queue_->enqueue(DEVICE_KERNEL_FILTER_COLOR_POSTPROCESS, work_size, args);
|
||||
}
|
||||
|
||||
bool DenoiserGPU::denoise_filter_color_preprocess(const DenoiseContext &context,
|
||||
const DenoisePass &pass)
|
||||
{
|
||||
if (context.denoise_params.type != DENOISER_OPTIX) {
|
||||
/* Pass preprocessing is used to clamp values for the OptiX denoiser.
|
||||
* Clamping is not necessary for other denoisers, so just skip this preprocess step. */
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!denoise_filter_color_flip_y(context, context.buffer_params, pass)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const BufferParams &buffer_params = context.buffer_params;
|
||||
|
||||
const int work_size = buffer_params.width * buffer_params.height;
|
||||
|
||||
const DeviceKernelArguments args(&context.render_buffers->buffer.device_pointer,
|
||||
&buffer_params.full_x,
|
||||
&buffer_params.full_y,
|
||||
&buffer_params.width,
|
||||
&buffer_params.height,
|
||||
&buffer_params.offset,
|
||||
&buffer_params.stride,
|
||||
&buffer_params.pass_stride,
|
||||
&pass.denoised_offset);
|
||||
|
||||
return denoiser_queue_->enqueue(DEVICE_KERNEL_FILTER_COLOR_PREPROCESS, work_size, args);
|
||||
}
|
||||
|
||||
bool DenoiserGPU::denoise_filter_color_flip_y(const DenoiseContext &context,
|
||||
const BufferParams &buffer_params,
|
||||
const DenoisePass &pass)
|
||||
{
|
||||
if (context.denoise_params.type != DENOISER_OPTIX || context.denoise_params.temporally_stable) {
|
||||
/* Flipping the image is used to improve result quality with the OptiX denoiser.
|
||||
* It is not necessary for other denoisers, so just skip this preprocess step. */
|
||||
return true;
|
||||
}
|
||||
|
||||
const int work_size = buffer_params.width * buffer_params.height / 2;
|
||||
|
||||
const DeviceKernelArguments args(&context.render_buffers->buffer.device_pointer,
|
||||
&buffer_params.full_x,
|
||||
&buffer_params.full_y,
|
||||
&buffer_params.width,
|
||||
&buffer_params.height,
|
||||
&buffer_params.offset,
|
||||
&buffer_params.stride,
|
||||
&buffer_params.pass_stride,
|
||||
&pass.denoised_offset);
|
||||
|
||||
return denoiser_queue_->enqueue(DEVICE_KERNEL_FILTER_COLOR_FLIP_Y, work_size, args);
|
||||
}
|
||||
|
||||
bool DenoiserGPU::denoise_filter_guiding_flip_y(const DenoiseContext &context)
|
||||
{
|
||||
if (context.denoise_params.type != DENOISER_OPTIX || context.denoise_params.temporally_stable) {
|
||||
/* Flipping the image is used to improve result quality with the OptiX denoiser.
|
||||
* It is not necessary for other denoisers, so just skip this preprocess step. */
|
||||
return true;
|
||||
}
|
||||
|
||||
const BufferParams &buffer_params = context.buffer_params;
|
||||
|
||||
const int guiding_offset = 0;
|
||||
|
||||
const int work_size = buffer_params.width * buffer_params.height / 2;
|
||||
|
||||
const int guiding_passes[] = {context.guiding_params.pass_albedo,
|
||||
context.guiding_params.pass_normal};
|
||||
for (const int guiding_pass : guiding_passes) {
|
||||
if (guiding_pass == PASS_UNUSED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const DeviceKernelArguments args(&context.guiding_params.device_pointer,
|
||||
&guiding_offset,
|
||||
&guiding_offset,
|
||||
&buffer_params.width,
|
||||
&buffer_params.height,
|
||||
&guiding_offset,
|
||||
&context.guiding_params.stride,
|
||||
&context.guiding_params.pass_stride,
|
||||
&guiding_pass);
|
||||
|
||||
if (!denoiser_queue_->enqueue(DEVICE_KERNEL_FILTER_COLOR_FLIP_Y, work_size, args)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DenoiserGPU::denoise_filter_guiding_set_fake_albedo(const DenoiseContext &context)
|
||||
{
|
||||
const BufferParams &buffer_params = context.buffer_params;
|
||||
|
||||
const int work_size = buffer_params.width * buffer_params.height;
|
||||
|
||||
const DeviceKernelArguments args(&context.guiding_params.device_pointer,
|
||||
&context.guiding_params.pass_stride,
|
||||
&context.guiding_params.pass_albedo,
|
||||
&buffer_params.width,
|
||||
&buffer_params.height);
|
||||
|
||||
return denoiser_queue_->enqueue(DEVICE_KERNEL_FILTER_GUIDING_SET_FAKE_ALBEDO, work_size, args);
|
||||
}
|
||||
|
||||
void DenoiserGPU::denoise_color_read(const DenoiseContext &context, const DenoisePass &pass)
|
||||
{
|
||||
PassAccessor::PassAccessInfo pass_access_info;
|
||||
pass_access_info.type = pass.type;
|
||||
pass_access_info.mode = PassMode::NOISY;
|
||||
pass_access_info.offset = pass.noisy_offset;
|
||||
|
||||
/* Denoiser operates on passes which are used to calculate the approximation, and is never used
|
||||
* on the approximation. The latter is not even possible because OptiX does not support
|
||||
* denoising of semi-transparent pixels. */
|
||||
pass_access_info.use_approximate_shadow_catcher = false;
|
||||
pass_access_info.use_approximate_shadow_catcher_background = false;
|
||||
pass_access_info.show_active_pixels = false;
|
||||
|
||||
/* TODO(sergey): Consider adding support of actual exposure, to avoid clamping in extreme cases.
|
||||
*/
|
||||
const PassAccessorGPU pass_accessor(
|
||||
denoiser_queue_.get(), pass_access_info, 1.0f, context.num_samples);
|
||||
|
||||
PassAccessor::Destination destination(pass_access_info.type, pass_access_info.mode);
|
||||
destination.d_pixels = context.render_buffers->buffer.device_pointer;
|
||||
destination.num_components = 3;
|
||||
destination.pixel_offset = pass.denoised_offset;
|
||||
destination.pixel_stride = context.buffer_params.pass_stride;
|
||||
|
||||
BufferParams buffer_params = context.buffer_params;
|
||||
buffer_params.window_x = 0;
|
||||
buffer_params.window_y = 0;
|
||||
buffer_params.window_width = buffer_params.width;
|
||||
buffer_params.window_height = buffer_params.height;
|
||||
|
||||
pass_accessor.get_render_tile_pixels(context.render_buffers, buffer_params, destination);
|
||||
}
|
||||
|
||||
bool DenoiserGPU::denoise_pass(DenoiseContext &context, PassType pass_type)
|
||||
{
|
||||
const BufferParams &buffer_params = context.buffer_params;
|
||||
|
||||
const DenoisePass pass(pass_type, buffer_params);
|
||||
|
||||
if (pass.noisy_offset == PASS_UNUSED) {
|
||||
return true;
|
||||
}
|
||||
if (pass.denoised_offset == PASS_UNUSED) {
|
||||
LOG_DFATAL << "Missing denoised pass " << pass_type_as_string(pass_type);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pass.use_denoising_albedo) {
|
||||
if (context.albedo_replaced_with_fake) {
|
||||
LOG_ERROR << "Pass which requires albedo is denoised after fake albedo has been set.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (context.use_guiding_passes && !context.albedo_replaced_with_fake) {
|
||||
context.albedo_replaced_with_fake = true;
|
||||
if (!denoise_filter_guiding_set_fake_albedo(context)) {
|
||||
LOG_ERROR << "Error replacing real albedo with the fake one.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read and preprocess noisy color input pass. */
|
||||
denoise_color_read(context, pass);
|
||||
if (!denoise_filter_color_preprocess(context, pass)) {
|
||||
LOG_ERROR << "Error converting denoising passes to RGB buffer.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!denoise_run(context, pass)) {
|
||||
LOG_ERROR << "Error running denoiser.";
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Store result in the combined pass of the render buffer.
|
||||
*
|
||||
* This will scale the denoiser result up to match the number of, possibly per-pixel, samples. */
|
||||
if (!denoise_filter_color_postprocess(context, pass)) {
|
||||
LOG_ERROR << "Error copying denoiser result to the denoised pass.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return denoiser_queue_->synchronize();
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
158
blender-5.2.0/intern/cycles/integrator/denoiser_gpu.h
Normal file
158
blender-5.2.0/intern/cycles/integrator/denoiser_gpu.h
Normal file
@@ -0,0 +1,158 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "integrator/denoiser.h"
|
||||
|
||||
#include "session/buffers.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Implementation of Denoiser which uses a device-specific denoising implementation, running on a
|
||||
* GPU device queue. It makes sure the to-be-denoised buffer is available on the denoising device
|
||||
* and invokes denoising kernels via the device queue API. */
|
||||
class DenoiserGPU : public Denoiser {
|
||||
public:
|
||||
DenoiserGPU(Device *denoiser_device, const DenoiseParams ¶ms);
|
||||
~DenoiserGPU() override;
|
||||
|
||||
bool denoise_buffer(const BufferParams &buffer_params,
|
||||
const BufferParams &denoised_buffer_params,
|
||||
RenderBuffers *render_buffers,
|
||||
int num_samples,
|
||||
bool allow_inplace_modification,
|
||||
float2 pixel_jitter) override;
|
||||
|
||||
protected:
|
||||
class DenoisePass;
|
||||
class DenoiseContext;
|
||||
|
||||
/* Make sure the GPU denoiser is created and configured. */
|
||||
virtual bool denoise_ensure(DenoiseContext &context);
|
||||
|
||||
/* Create GPU denoiser descriptor if needed.
|
||||
* Will do nothing if the current GPU descriptor is usable for the given parameters.
|
||||
* If the GPU denoiser descriptor did re-allocate here it is left unconfigured. */
|
||||
virtual bool denoise_create_if_needed(DenoiseContext &context) = 0;
|
||||
|
||||
/* Configure existing GPU denoiser descriptor for the use for the given task. */
|
||||
virtual bool denoise_configure_if_needed(DenoiseContext &context) = 0;
|
||||
|
||||
/* Read input color pass from the render buffer into the memory which corresponds to the noisy
|
||||
* input within the given context. Pixels are scaled to the number of samples, but are not
|
||||
* preprocessed yet. */
|
||||
void denoise_color_read(const DenoiseContext &context, const DenoisePass &pass);
|
||||
|
||||
/* Run corresponding filter kernels, preparing data for the denoiser or copying data from the
|
||||
* denoiser result to the render buffer. */
|
||||
bool denoise_filter_color_preprocess(const DenoiseContext &context, const DenoisePass &pass);
|
||||
bool denoise_filter_color_postprocess(const DenoiseContext &context, const DenoisePass &pass);
|
||||
bool denoise_filter_color_flip_y(const DenoiseContext &context,
|
||||
const BufferParams &buffer_params,
|
||||
const DenoisePass &pass);
|
||||
bool denoise_filter_guiding_flip_y(const DenoiseContext &context);
|
||||
bool denoise_filter_guiding_set_fake_albedo(const DenoiseContext &context);
|
||||
|
||||
/* Read guiding passes from the render buffers, preprocess them in a way which is expected by
|
||||
* the GPU denoiser and store in the guiding passes memory within the given context.
|
||||
*
|
||||
* Pre-processing of the guiding passes is to only happen once per context lifetime. DO not
|
||||
* preprocess them for every pass which is being denoised. */
|
||||
bool denoise_filter_guiding_preprocess(const DenoiseContext &context);
|
||||
|
||||
bool denoise_pass(DenoiseContext &context, PassType pass_type);
|
||||
|
||||
/* Returns true if task is fully handled. */
|
||||
virtual bool denoise_run(const DenoiseContext &context, const DenoisePass &pass) = 0;
|
||||
|
||||
unique_ptr<DeviceQueue> denoiser_queue_;
|
||||
|
||||
class DenoisePass {
|
||||
public:
|
||||
DenoisePass(const PassType type, const BufferParams &buffer_params) : type(type)
|
||||
{
|
||||
noisy_offset = buffer_params.get_pass_offset(type, PassMode::NOISY);
|
||||
denoised_offset = buffer_params.get_pass_offset(type, PassMode::DENOISED);
|
||||
|
||||
const PassInfo pass_info = Pass::get_info(type);
|
||||
num_components = pass_info.num_components;
|
||||
use_compositing = pass_info.use_compositing;
|
||||
use_denoising_albedo = pass_info.use_denoising_albedo;
|
||||
}
|
||||
|
||||
PassType type;
|
||||
|
||||
int noisy_offset;
|
||||
int denoised_offset;
|
||||
|
||||
int num_components;
|
||||
int use_compositing;
|
||||
bool use_denoising_albedo;
|
||||
};
|
||||
|
||||
class DenoiseContext {
|
||||
public:
|
||||
explicit DenoiseContext(Device *device,
|
||||
const DenoiseParams ¶ms,
|
||||
const BufferParams &buffer_params,
|
||||
const BufferParams &denoised_buffer_params,
|
||||
RenderBuffers *render_buffers,
|
||||
int num_samples,
|
||||
bool allow_inplace_modification,
|
||||
float2 pixel_jitter);
|
||||
|
||||
const DenoiseParams &denoise_params;
|
||||
|
||||
RenderBuffers *render_buffers = nullptr;
|
||||
const BufferParams &buffer_params;
|
||||
const BufferParams &denoised_buffer_params;
|
||||
|
||||
/* Previous output. */
|
||||
struct {
|
||||
device_ptr device_pointer = 0;
|
||||
|
||||
int offset = PASS_UNUSED;
|
||||
|
||||
int stride = -1;
|
||||
int pass_stride = -1;
|
||||
} prev_output;
|
||||
|
||||
/* Device-side storage of the guiding passes. */
|
||||
device_only_memory<float> guiding_buffer;
|
||||
|
||||
struct {
|
||||
device_ptr device_pointer = 0;
|
||||
|
||||
/* NOTE: Are only initialized when the corresponding guiding pass is enabled. */
|
||||
int pass_albedo = PASS_UNUSED;
|
||||
int pass_normal = PASS_UNUSED;
|
||||
int pass_flow = PASS_UNUSED;
|
||||
|
||||
int stride = -1;
|
||||
int pass_stride = -1;
|
||||
} guiding_params;
|
||||
|
||||
const bool use_guiding_passes = false;
|
||||
|
||||
int num_samples = 0;
|
||||
|
||||
int pass_sample_count = PASS_UNUSED;
|
||||
|
||||
/* NOTE: Are only initialized when the corresponding guiding pass is enabled. */
|
||||
int pass_denoising_albedo = PASS_UNUSED;
|
||||
int pass_denoising_normal = PASS_UNUSED;
|
||||
int pass_motion = PASS_UNUSED;
|
||||
|
||||
/* For passes which don't need albedo channel for denoising we replace the actual albedo with
|
||||
* the (0.5, 0.5, 0.5). This flag indicates that the real albedo pass has been replaced with
|
||||
* the fake values and denoising of passes which do need albedo can no longer happen. */
|
||||
bool albedo_replaced_with_fake = false;
|
||||
|
||||
/* Sub-pixel jitter offset of the current frame. This can be used for upscaling. */
|
||||
float2 pixel_jitter;
|
||||
};
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
744
blender-5.2.0/intern/cycles/integrator/denoiser_oidn.cpp
Normal file
744
blender-5.2.0/intern/cycles/integrator/denoiser_oidn.cpp
Normal file
@@ -0,0 +1,744 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "integrator/denoiser_oidn.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "device/device.h"
|
||||
#include "device/queue.h"
|
||||
#include "integrator/pass_accessor_cpu.h"
|
||||
#include "session/buffers.h"
|
||||
#include "util/array.h"
|
||||
#include "util/log.h"
|
||||
#include "util/openimagedenoise.h"
|
||||
#include "util/path.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
thread_mutex OIDNDenoiser::mutex_;
|
||||
|
||||
OIDNDenoiser::OIDNDenoiser(Device *denoiser_device, const DenoiseParams ¶ms)
|
||||
: Denoiser(denoiser_device, params)
|
||||
#ifdef WITH_OPENIMAGEDENOISE
|
||||
,
|
||||
base_(this)
|
||||
#endif
|
||||
{
|
||||
DCHECK_EQ(params.type, DENOISER_OPENIMAGEDENOISE);
|
||||
|
||||
#ifndef WITH_OPENIMAGEDENOISE
|
||||
set_error("Failed to denoise, build has no OpenImageDenoise support");
|
||||
#else
|
||||
if (!openimagedenoise_supported()) {
|
||||
set_error("OpenImageDenoiser is not supported on this CPU: missing SSE 4.1 support");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WITH_OPENIMAGEDENOISE
|
||||
static bool oidn_progress_monitor_function(void *user_ptr, double /*n*/)
|
||||
{
|
||||
OIDNDenoiser *oidn_denoiser = reinterpret_cast<OIDNDenoiser *>(user_ptr);
|
||||
return !oidn_denoiser->is_cancelled();
|
||||
}
|
||||
|
||||
class OIDNPass {
|
||||
public:
|
||||
OIDNPass() = default;
|
||||
|
||||
OIDNPass(const BufferParams &buffer_params,
|
||||
const char *name,
|
||||
PassType type,
|
||||
PassMode mode = PassMode::NOISY)
|
||||
: name(name), type(type), mode(mode)
|
||||
{
|
||||
offset = buffer_params.get_pass_offset(type, mode);
|
||||
need_scale = (type == PASS_DENOISING_ALBEDO || type == PASS_DENOISING_NORMAL);
|
||||
|
||||
const PassInfo pass_info = Pass::get_info(type, mode);
|
||||
num_components = pass_info.num_components;
|
||||
use_compositing = pass_info.use_compositing;
|
||||
use_denoising_albedo = pass_info.use_denoising_albedo;
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return name[0] != '\0';
|
||||
}
|
||||
|
||||
/* Name of an image which will be passed to the OIDN library.
|
||||
* Should be one of the following: color, albedo, normal, output.
|
||||
* The albedo and normal images are optional. */
|
||||
const char *name = "";
|
||||
|
||||
PassType type = PASS_NONE;
|
||||
PassMode mode = PassMode::NOISY;
|
||||
int num_components = -1;
|
||||
bool use_compositing = false;
|
||||
bool use_denoising_albedo = true;
|
||||
|
||||
/* Offset of beginning of this pass in the render buffers. */
|
||||
int offset = -1;
|
||||
|
||||
/* Denotes whether the data is to be scaled down with the number of passes.
|
||||
* Is required for albedo and normal passes. The color pass OIDN will perform auto-exposure, so
|
||||
* scaling is not needed for the color pass unless adaptive sampling is used.
|
||||
*
|
||||
* NOTE: Do not scale the output pass, as that requires to be a pointer in the original buffer.
|
||||
* All the scaling on the output needed for integration with adaptive sampling will happen
|
||||
* outside of generic pass handling. */
|
||||
bool need_scale = false;
|
||||
|
||||
/* The content of the pass has been pre-filtered. */
|
||||
bool is_filtered = false;
|
||||
|
||||
/* For the scaled passes, the data which holds values of scaled pixels. */
|
||||
array<float> scaled_buffer;
|
||||
};
|
||||
|
||||
class OIDNDenoiseContext {
|
||||
friend class OIDNDenoiser;
|
||||
|
||||
public:
|
||||
OIDNDenoiseContext(OIDNDenoiser *denoiser,
|
||||
const DenoiseParams &denoise_params,
|
||||
const BufferParams &buffer_params,
|
||||
RenderBuffers *render_buffers,
|
||||
const int num_samples,
|
||||
const bool allow_inplace_modification)
|
||||
: denoiser_(denoiser),
|
||||
denoise_params_(denoise_params),
|
||||
buffer_params_(buffer_params),
|
||||
render_buffers_(render_buffers),
|
||||
num_samples_(num_samples),
|
||||
allow_inplace_modification_(allow_inplace_modification),
|
||||
pass_sample_count_(buffer_params_.get_pass_offset(PASS_SAMPLE_COUNT))
|
||||
{
|
||||
if (denoise_params_.passes & DENOISER_PASS_ALBEDO) {
|
||||
oidn_albedo_pass_ = OIDNPass(buffer_params_, "albedo", PASS_DENOISING_ALBEDO);
|
||||
}
|
||||
|
||||
if (denoise_params_.passes & DENOISER_PASS_NORMAL) {
|
||||
oidn_normal_pass_ = OIDNPass(buffer_params_, "normal", PASS_DENOISING_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
bool need_denoising() const
|
||||
{
|
||||
if (buffer_params_.width == 0 && buffer_params_.height == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Make the guiding passes available by a sequential denoising of various passes. */
|
||||
void read_guiding_passes()
|
||||
{
|
||||
read_guiding_pass(oidn_albedo_pass_);
|
||||
read_guiding_pass(oidn_normal_pass_);
|
||||
}
|
||||
|
||||
bool denoise_pass(const PassType pass_type)
|
||||
{
|
||||
OIDNPass oidn_color_pass(buffer_params_, "color", pass_type);
|
||||
if (oidn_color_pass.offset == PASS_UNUSED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (oidn_color_pass.use_denoising_albedo) {
|
||||
if (albedo_replaced_with_fake_) {
|
||||
LOG_ERROR << "Pass which requires albedo is denoised after fake albedo has been set.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
OIDNPass oidn_output_pass(buffer_params_, "output", pass_type, PassMode::DENOISED);
|
||||
if (oidn_output_pass.offset == PASS_UNUSED) {
|
||||
LOG_DFATAL << "Missing denoised pass " << pass_type_as_string(pass_type);
|
||||
return false;
|
||||
}
|
||||
|
||||
OIDNPass oidn_color_access_pass = read_input_pass(oidn_color_pass, oidn_output_pass);
|
||||
|
||||
if (!denoiser_->base_.oidn_filter_) {
|
||||
denoiser_->set_error("OpenImageDenoise filter is not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!filter_guiding_pass_if_needed(oidn_albedo_pass_)) {
|
||||
return false;
|
||||
}
|
||||
if (!filter_guiding_pass_if_needed(oidn_normal_pass_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
set_input_pass(denoiser_->base_.oidn_filter_, oidn_color_access_pass);
|
||||
set_guiding_passes(denoiser_->base_.oidn_filter_, oidn_color_pass);
|
||||
set_output_pass(denoiser_->base_.oidn_filter_, oidn_output_pass);
|
||||
|
||||
const bool clean_aux = denoise_params_.prefilter != DENOISER_PREFILTER_FAST;
|
||||
oidnSetFilterInt(denoiser_->base_.oidn_filter_, "cleanAux", clean_aux);
|
||||
|
||||
if (!denoiser_->commit_and_execute_filter(denoiser_->base_.oidn_filter_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
postprocess_output(oidn_color_pass, oidn_output_pass);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool filter_guiding_pass_if_needed(OIDNPass &oidn_pass)
|
||||
{
|
||||
if (denoise_params_.prefilter != DENOISER_PREFILTER_ACCURATE || !oidn_pass ||
|
||||
oidn_pass.is_filtered)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
OIDNFilter filter = (oidn_pass.type == PASS_DENOISING_ALBEDO) ?
|
||||
denoiser_->base_.albedo_filter_ :
|
||||
denoiser_->base_.normal_filter_;
|
||||
|
||||
if (!filter) {
|
||||
denoiser_->set_error("OpenImageDenoise guiding filter is not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
set_pass(filter, oidn_pass);
|
||||
set_output_pass(filter, oidn_pass);
|
||||
|
||||
if (!denoiser_->commit_and_execute_filter(filter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
oidn_pass.is_filtered = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Make pixels of a guiding pass available by the denoiser. */
|
||||
void read_guiding_pass(OIDNPass &oidn_pass)
|
||||
{
|
||||
if (!oidn_pass) {
|
||||
return;
|
||||
}
|
||||
|
||||
DCHECK(!oidn_pass.use_compositing);
|
||||
|
||||
if (denoise_params_.prefilter != DENOISER_PREFILTER_ACCURATE &&
|
||||
!is_pass_scale_needed(oidn_pass))
|
||||
{
|
||||
/* Pass data is available as-is from the render buffers. */
|
||||
return;
|
||||
}
|
||||
|
||||
if (allow_inplace_modification_) {
|
||||
scale_pass_in_render_buffers(oidn_pass);
|
||||
return;
|
||||
}
|
||||
|
||||
read_pass_pixels_into_buffer(oidn_pass);
|
||||
}
|
||||
|
||||
/* Special reader of the input pass.
|
||||
* To save memory it will read pixels into the output, and let the denoiser to perform an
|
||||
* in-place operation. */
|
||||
OIDNPass read_input_pass(OIDNPass &oidn_input_pass, const OIDNPass &oidn_output_pass)
|
||||
{
|
||||
const bool use_compositing = oidn_input_pass.use_compositing;
|
||||
|
||||
/* Simple case: no compositing is involved, no scaling is needed.
|
||||
* The pass pixels will be referenced as-is, without extra processing. */
|
||||
if (!use_compositing && !is_pass_scale_needed(oidn_input_pass)) {
|
||||
return oidn_input_pass;
|
||||
}
|
||||
|
||||
float *buffer_data = render_buffers_->buffer.data();
|
||||
float *pass_data = buffer_data + oidn_output_pass.offset;
|
||||
|
||||
PassAccessor::Destination destination(pass_data, 3);
|
||||
destination.pixel_stride = buffer_params_.pass_stride;
|
||||
|
||||
read_pass_pixels(oidn_input_pass, destination);
|
||||
|
||||
OIDNPass oidn_input_pass_at_output = oidn_input_pass;
|
||||
oidn_input_pass_at_output.offset = oidn_output_pass.offset;
|
||||
|
||||
return oidn_input_pass_at_output;
|
||||
}
|
||||
|
||||
/* Read pass pixels using PassAccessor into the given destination. */
|
||||
void read_pass_pixels(const OIDNPass &oidn_pass, const PassAccessor::Destination &destination)
|
||||
{
|
||||
PassAccessor::PassAccessInfo pass_access_info;
|
||||
pass_access_info.type = oidn_pass.type;
|
||||
pass_access_info.mode = oidn_pass.mode;
|
||||
pass_access_info.offset = oidn_pass.offset;
|
||||
|
||||
/* Denoiser operates on passes which are used to calculate the approximation, and is never used
|
||||
* on the approximation. The latter is not even possible because OIDN does not support
|
||||
* denoising of semi-transparent pixels. */
|
||||
pass_access_info.use_approximate_shadow_catcher = false;
|
||||
pass_access_info.use_approximate_shadow_catcher_background = false;
|
||||
pass_access_info.show_active_pixels = false;
|
||||
|
||||
/* OIDN will perform an auto-exposure, so it is not required to know exact exposure configured
|
||||
* by users. What is important is to use same exposure for read and write access of the pass
|
||||
* pixels. */
|
||||
const PassAccessorCPU pass_accessor(pass_access_info, 1.0f, num_samples_);
|
||||
|
||||
BufferParams buffer_params = buffer_params_;
|
||||
buffer_params.window_x = 0;
|
||||
buffer_params.window_y = 0;
|
||||
buffer_params.window_width = buffer_params.width;
|
||||
buffer_params.window_height = buffer_params.height;
|
||||
|
||||
pass_accessor.get_render_tile_pixels(render_buffers_, buffer_params, destination);
|
||||
}
|
||||
|
||||
/* Read pass pixels using PassAccessor into a temporary buffer which is owned by the pass.. */
|
||||
void read_pass_pixels_into_buffer(OIDNPass &oidn_pass)
|
||||
{
|
||||
LOG_DEBUG << "Allocating temporary buffer for pass " << oidn_pass.name << " ("
|
||||
<< pass_type_as_string(oidn_pass.type) << ")";
|
||||
|
||||
const int64_t width = buffer_params_.width;
|
||||
const int64_t height = buffer_params_.height;
|
||||
|
||||
array<float> &scaled_buffer = oidn_pass.scaled_buffer;
|
||||
scaled_buffer.resize(width * height * 3);
|
||||
|
||||
const PassAccessor::Destination destination(scaled_buffer.data(), 3);
|
||||
|
||||
read_pass_pixels(oidn_pass, destination);
|
||||
}
|
||||
|
||||
/* Set OIDN image to reference pixels from the given render buffer pass.
|
||||
* No transform to the pixels is done, no additional memory is used. */
|
||||
void set_pass_referenced(OIDNFilter oidn_filter, const char *name, const OIDNPass &oidn_pass)
|
||||
{
|
||||
const int64_t x = buffer_params_.full_x;
|
||||
const int64_t y = buffer_params_.full_y;
|
||||
const int64_t width = buffer_params_.width;
|
||||
const int64_t height = buffer_params_.height;
|
||||
const int64_t offset = buffer_params_.offset;
|
||||
const int64_t stride = buffer_params_.stride;
|
||||
const int64_t pass_stride = buffer_params_.pass_stride;
|
||||
|
||||
const int64_t pixel_index = offset + x + y * stride;
|
||||
const int64_t buffer_offset = pixel_index * pass_stride;
|
||||
|
||||
float *buffer_data = render_buffers_->buffer.data();
|
||||
|
||||
oidnSetSharedFilterImage(oidn_filter,
|
||||
name,
|
||||
buffer_data + buffer_offset + oidn_pass.offset,
|
||||
OIDN_FORMAT_FLOAT3,
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
pass_stride * sizeof(float),
|
||||
stride * pass_stride * sizeof(float));
|
||||
}
|
||||
|
||||
void set_pass_from_buffer(OIDNFilter oidn_filter, const char *name, OIDNPass &oidn_pass)
|
||||
{
|
||||
const int64_t width = buffer_params_.width;
|
||||
const int64_t height = buffer_params_.height;
|
||||
|
||||
oidnSetSharedFilterImage(oidn_filter,
|
||||
name,
|
||||
oidn_pass.scaled_buffer.data(),
|
||||
OIDN_FORMAT_FLOAT3,
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
0,
|
||||
0);
|
||||
}
|
||||
|
||||
void set_pass(OIDNFilter oidn_filter, OIDNPass &oidn_pass)
|
||||
{
|
||||
set_pass(oidn_filter, oidn_pass.name, oidn_pass);
|
||||
}
|
||||
void set_pass(OIDNFilter oidn_filter, const char *name, OIDNPass &oidn_pass)
|
||||
{
|
||||
if (oidn_pass.scaled_buffer.empty()) {
|
||||
set_pass_referenced(oidn_filter, name, oidn_pass);
|
||||
}
|
||||
else {
|
||||
set_pass_from_buffer(oidn_filter, name, oidn_pass);
|
||||
}
|
||||
}
|
||||
|
||||
void set_input_pass(OIDNFilter oidn_filter, OIDNPass &oidn_pass)
|
||||
{
|
||||
set_pass_referenced(oidn_filter, oidn_pass.name, oidn_pass);
|
||||
}
|
||||
|
||||
void set_guiding_passes(OIDNFilter oidn_filter, OIDNPass &oidn_pass)
|
||||
{
|
||||
if (oidn_albedo_pass_) {
|
||||
if (oidn_pass.use_denoising_albedo) {
|
||||
set_pass(oidn_filter, oidn_albedo_pass_);
|
||||
}
|
||||
else {
|
||||
/* NOTE: OpenImageDenoise library implicitly expects albedo pass when normal pass has been
|
||||
* provided. */
|
||||
set_fake_albedo_pass(oidn_filter);
|
||||
}
|
||||
}
|
||||
|
||||
if (oidn_normal_pass_) {
|
||||
set_pass(oidn_filter, oidn_normal_pass_);
|
||||
}
|
||||
}
|
||||
|
||||
void set_fake_albedo_pass(OIDNFilter oidn_filter)
|
||||
{
|
||||
const int64_t width = buffer_params_.width;
|
||||
const int64_t height = buffer_params_.height;
|
||||
|
||||
if (!albedo_replaced_with_fake_) {
|
||||
const int64_t num_pixel_components = width * height * 3;
|
||||
oidn_albedo_pass_.scaled_buffer.resize(num_pixel_components);
|
||||
|
||||
for (int i = 0; i < num_pixel_components; ++i) {
|
||||
oidn_albedo_pass_.scaled_buffer[i] = 0.5f;
|
||||
}
|
||||
|
||||
albedo_replaced_with_fake_ = true;
|
||||
}
|
||||
|
||||
set_pass(oidn_filter, oidn_albedo_pass_);
|
||||
}
|
||||
|
||||
void set_output_pass(OIDNFilter oidn_filter, OIDNPass &oidn_pass)
|
||||
{
|
||||
set_pass(oidn_filter, "output", oidn_pass);
|
||||
}
|
||||
|
||||
/* Scale output pass to match adaptive sampling per-pixel scale, as well as bring alpha channel
|
||||
* back. */
|
||||
void postprocess_output(const OIDNPass &oidn_input_pass, const OIDNPass &oidn_output_pass)
|
||||
{
|
||||
kernel_assert(oidn_input_pass.num_components == oidn_output_pass.num_components);
|
||||
|
||||
const int64_t x = buffer_params_.full_x;
|
||||
const int64_t y = buffer_params_.full_y;
|
||||
const int64_t width = buffer_params_.width;
|
||||
const int64_t height = buffer_params_.height;
|
||||
const int64_t offset = buffer_params_.offset;
|
||||
const int64_t stride = buffer_params_.stride;
|
||||
const int64_t pass_stride = buffer_params_.pass_stride;
|
||||
const int64_t row_stride = stride * pass_stride;
|
||||
|
||||
const int64_t pixel_offset = offset + x + y * stride;
|
||||
const int64_t buffer_offset = (pixel_offset * pass_stride);
|
||||
|
||||
float *buffer_data = render_buffers_->buffer.data();
|
||||
|
||||
const bool has_pass_sample_count = (pass_sample_count_ != PASS_UNUSED);
|
||||
const bool need_scale = has_pass_sample_count || oidn_input_pass.use_compositing;
|
||||
|
||||
for (int y = 0; y < height; ++y) {
|
||||
float *buffer_row = buffer_data + buffer_offset + y * row_stride;
|
||||
for (int x = 0; x < width; ++x) {
|
||||
float *buffer_pixel = buffer_row + x * pass_stride;
|
||||
float *denoised_pixel = buffer_pixel + oidn_output_pass.offset;
|
||||
|
||||
if (need_scale) {
|
||||
const float pixel_scale = has_pass_sample_count ?
|
||||
__float_as_uint(buffer_pixel[pass_sample_count_]) :
|
||||
num_samples_;
|
||||
|
||||
denoised_pixel[0] = denoised_pixel[0] * pixel_scale;
|
||||
denoised_pixel[1] = denoised_pixel[1] * pixel_scale;
|
||||
denoised_pixel[2] = denoised_pixel[2] * pixel_scale;
|
||||
}
|
||||
|
||||
if (oidn_output_pass.num_components == 3) {
|
||||
/* Pass without alpha channel. */
|
||||
}
|
||||
else if (!oidn_input_pass.use_compositing) {
|
||||
/* Currently compositing passes are either 3-component (derived by dividing light passes)
|
||||
* or do not have transparency (shadow catcher). Implicitly rely on this logic, as it
|
||||
* simplifies logic and avoids extra memory allocation. */
|
||||
const float *noisy_pixel = buffer_pixel + oidn_input_pass.offset;
|
||||
denoised_pixel[3] = noisy_pixel[3];
|
||||
}
|
||||
else {
|
||||
/* Assigning to zero since this is a default alpha value for 3-component passes, and it
|
||||
* is an opaque pixel for 4 component passes. */
|
||||
denoised_pixel[3] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool is_pass_scale_needed(OIDNPass &oidn_pass) const
|
||||
{
|
||||
if (pass_sample_count_ != PASS_UNUSED) {
|
||||
/* With adaptive sampling pixels will have different number of samples in them, so need to
|
||||
* always scale the pass to make pixels uniformly sampled. */
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!oidn_pass.need_scale) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (num_samples_ == 1) {
|
||||
/* If the avoid scaling if there is only one sample, to save up time (so we don't divide
|
||||
* buffer by 1). */
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void scale_pass_in_render_buffers(OIDNPass &oidn_pass)
|
||||
{
|
||||
const int64_t x = buffer_params_.full_x;
|
||||
const int64_t y = buffer_params_.full_y;
|
||||
const int64_t width = buffer_params_.width;
|
||||
const int64_t height = buffer_params_.height;
|
||||
const int64_t offset = buffer_params_.offset;
|
||||
const int64_t stride = buffer_params_.stride;
|
||||
const int64_t pass_stride = buffer_params_.pass_stride;
|
||||
const int64_t row_stride = stride * pass_stride;
|
||||
|
||||
const int64_t pixel_offset = offset + x + y * stride;
|
||||
const int64_t buffer_offset = (pixel_offset * pass_stride);
|
||||
|
||||
float *buffer_data = render_buffers_->buffer.data();
|
||||
|
||||
const bool has_pass_sample_count = (pass_sample_count_ != PASS_UNUSED);
|
||||
|
||||
for (int y = 0; y < height; ++y) {
|
||||
float *buffer_row = buffer_data + buffer_offset + y * row_stride;
|
||||
for (int x = 0; x < width; ++x) {
|
||||
float *buffer_pixel = buffer_row + x * pass_stride;
|
||||
float *pass_pixel = buffer_pixel + oidn_pass.offset;
|
||||
|
||||
const float pixel_scale = 1.0f / (has_pass_sample_count ?
|
||||
__float_as_uint(buffer_pixel[pass_sample_count_]) :
|
||||
num_samples_);
|
||||
|
||||
pass_pixel[0] = pass_pixel[0] * pixel_scale;
|
||||
pass_pixel[1] = pass_pixel[1] * pixel_scale;
|
||||
pass_pixel[2] = pass_pixel[2] * pixel_scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OIDNDenoiser *denoiser_ = nullptr;
|
||||
|
||||
const DenoiseParams &denoise_params_;
|
||||
const BufferParams &buffer_params_;
|
||||
RenderBuffers *render_buffers_ = nullptr;
|
||||
int num_samples_ = 0;
|
||||
bool allow_inplace_modification_ = false;
|
||||
int pass_sample_count_ = PASS_UNUSED;
|
||||
|
||||
/* Optional albedo and normal passes, reused by denoising of different pass types. */
|
||||
OIDNPass oidn_albedo_pass_;
|
||||
OIDNPass oidn_normal_pass_;
|
||||
|
||||
/* For passes which don't need albedo channel for denoising we replace the actual albedo with
|
||||
* the (0.5, 0.5, 0.5). This flag indicates that the real albedo pass has been replaced with
|
||||
* the fake values and denoising of passes which do need albedo can no longer happen. */
|
||||
bool albedo_replaced_with_fake_ = false;
|
||||
};
|
||||
|
||||
bool OIDNDenoiser::commit_and_execute_filter(OIDNFilter filter)
|
||||
{
|
||||
const char *error_message = nullptr;
|
||||
|
||||
oidnCommitFilter(filter);
|
||||
oidnExecuteFilter(filter);
|
||||
|
||||
const OIDNError err = oidnGetDeviceError(base_.oidn_device_, &error_message);
|
||||
if (err == OIDN_ERROR_NONE || err == OIDN_ERROR_CANCELLED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (error_message == nullptr) {
|
||||
error_message = "Unspecified OIDN error";
|
||||
}
|
||||
|
||||
LOG_ERROR << "OIDN error: " << error_message;
|
||||
set_error(error_message);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OIDNDenoiser::denoise_create_if_needed(const OIDNDenoiseContext &context)
|
||||
{
|
||||
/* Create device on first call if it doesn't exist yet. */
|
||||
if (!base_.oidn_device_) {
|
||||
base_.oidn_device_ = oidnNewDevice(OIDN_DEVICE_TYPE_CPU);
|
||||
if (!base_.oidn_device_) {
|
||||
set_error("Failed to create OIDN CPU device");
|
||||
return false;
|
||||
}
|
||||
oidnSetDeviceBool(base_.oidn_device_, "setAffinity", false);
|
||||
oidnCommitDevice(base_.oidn_device_);
|
||||
base_.load_custom_weights();
|
||||
}
|
||||
|
||||
const bool use_pass_albedo = params_.prefilter == DENOISER_PREFILTER_ACCURATE &&
|
||||
(context.denoise_params_.passes & DENOISER_PASS_ALBEDO) != 0;
|
||||
const bool use_pass_normal = params_.prefilter == DENOISER_PREFILTER_ACCURATE &&
|
||||
(context.denoise_params_.passes & DENOISER_PASS_NORMAL) != 0;
|
||||
|
||||
const bool recreate_filter = (base_.oidn_filter_ == nullptr) ||
|
||||
(base_.use_pass_albedo_ != use_pass_albedo) ||
|
||||
(base_.use_pass_normal_ != use_pass_normal) ||
|
||||
(base_.quality_ != params_.quality);
|
||||
|
||||
if (!recreate_filter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (base_.albedo_filter_) {
|
||||
oidnReleaseFilter(base_.albedo_filter_);
|
||||
base_.albedo_filter_ = nullptr;
|
||||
}
|
||||
|
||||
if (base_.normal_filter_) {
|
||||
oidnReleaseFilter(base_.normal_filter_);
|
||||
base_.normal_filter_ = nullptr;
|
||||
}
|
||||
|
||||
if (base_.oidn_filter_) {
|
||||
oidnReleaseFilter(base_.oidn_filter_);
|
||||
base_.oidn_filter_ = nullptr;
|
||||
}
|
||||
|
||||
if (!base_.create_filters(params_.quality, use_pass_albedo, use_pass_normal)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
oidnSetFilterProgressMonitorFunction(base_.oidn_filter_, oidn_progress_monitor_function, this);
|
||||
if (base_.albedo_filter_) {
|
||||
oidnSetFilterProgressMonitorFunction(
|
||||
base_.albedo_filter_, oidn_progress_monitor_function, this);
|
||||
}
|
||||
if (base_.normal_filter_) {
|
||||
oidnSetFilterProgressMonitorFunction(
|
||||
base_.normal_filter_, oidn_progress_monitor_function, this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OIDNDenoiser::denoise_run(OIDNDenoiseContext &context, const PassType pass_type)
|
||||
{
|
||||
return context.denoise_pass(pass_type);
|
||||
}
|
||||
|
||||
static unique_ptr<DeviceQueue> create_device_queue(const RenderBuffers *render_buffers)
|
||||
{
|
||||
Device *device = render_buffers->buffer.device;
|
||||
if (device->info.has_gpu_queue) {
|
||||
return device->gpu_queue_create();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void copy_render_buffers_from_device(unique_ptr<DeviceQueue> &queue,
|
||||
RenderBuffers *render_buffers)
|
||||
{
|
||||
if (queue) {
|
||||
queue->copy_from_device(render_buffers->buffer);
|
||||
queue->synchronize();
|
||||
}
|
||||
else {
|
||||
render_buffers->copy_from_device();
|
||||
}
|
||||
}
|
||||
|
||||
static void copy_render_buffers_to_device(unique_ptr<DeviceQueue> &queue,
|
||||
RenderBuffers *render_buffers)
|
||||
{
|
||||
if (queue) {
|
||||
queue->copy_to_device(render_buffers->buffer);
|
||||
queue->synchronize();
|
||||
}
|
||||
else {
|
||||
render_buffers->copy_to_device();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool OIDNDenoiser::denoise_buffer(const BufferParams &buffer_params,
|
||||
const BufferParams & /*denoised_buffer_params*/,
|
||||
RenderBuffers *render_buffers,
|
||||
const int num_samples,
|
||||
const bool allow_inplace_modification,
|
||||
const float2 /*pixel_jitter*/)
|
||||
{
|
||||
DCHECK(openimagedenoise_supported())
|
||||
<< "OpenImageDenoise is not supported on this platform or build.";
|
||||
|
||||
#ifdef WITH_OPENIMAGEDENOISE
|
||||
const thread_scoped_lock lock(mutex_);
|
||||
|
||||
/* Make sure the host-side data is available for denoising. */
|
||||
unique_ptr<DeviceQueue> queue = create_device_queue(render_buffers);
|
||||
copy_render_buffers_from_device(queue, render_buffers);
|
||||
|
||||
OIDNDenoiseContext context(
|
||||
this, params_, buffer_params, render_buffers, num_samples, allow_inplace_modification);
|
||||
|
||||
if (context.need_denoising()) {
|
||||
context.read_guiding_passes();
|
||||
|
||||
if (!denoise_create_if_needed(context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!base_.denoise_configure_if_needed(context.buffer_params_.width,
|
||||
context.buffer_params_.height))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::array<PassType, 3> passes = {
|
||||
{/* Passes which will use real albedo when it is available. */
|
||||
PASS_COMBINED,
|
||||
PASS_SHADOW_CATCHER_MATTE,
|
||||
|
||||
/* Passes which do not need albedo and hence if real is present it needs to become fake.
|
||||
*/
|
||||
PASS_SHADOW_CATCHER}};
|
||||
|
||||
for (const PassType pass_type : passes) {
|
||||
if (!denoise_run(context, pass_type)) {
|
||||
return false;
|
||||
}
|
||||
if (is_cancelled()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: It may be possible to avoid this copy, but we have to ensure that when other code
|
||||
* copies data from the device it doesn't overwrite the denoiser buffers. */
|
||||
copy_render_buffers_to_device(queue, render_buffers);
|
||||
}
|
||||
#else
|
||||
(void)buffer_params;
|
||||
(void)render_buffers;
|
||||
(void)num_samples;
|
||||
(void)allow_inplace_modification;
|
||||
#endif
|
||||
|
||||
/* This code is not supposed to run when compiled without OIDN support, so can assume if we made
|
||||
* it up here all passes are properly denoised. */
|
||||
return true;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
45
blender-5.2.0/intern/cycles/integrator/denoiser_oidn.h
Normal file
45
blender-5.2.0/intern/cycles/integrator/denoiser_oidn.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "integrator/denoiser.h"
|
||||
#include "integrator/denoiser_oidn_base.h"
|
||||
#include "util/openimagedenoise.h"
|
||||
#include "util/thread.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Implementation of a CPU based denoiser which uses OpenImageDenoise library. */
|
||||
class OIDNDenoiser : public Denoiser {
|
||||
public:
|
||||
/* Forwardly declared state which might be using compile-flag specific fields, such as
|
||||
* OpenImageDenoise device and filter handles. */
|
||||
class State;
|
||||
|
||||
OIDNDenoiser(Device *denoiser_device, const DenoiseParams ¶ms);
|
||||
|
||||
bool denoise_buffer(const BufferParams &buffer_params,
|
||||
const BufferParams &denoised_buffer_params,
|
||||
RenderBuffers *render_buffers,
|
||||
int num_samples,
|
||||
bool allow_inplace_modification,
|
||||
float2 pixel_jitter) override;
|
||||
|
||||
#ifdef WITH_OPENIMAGEDENOISE
|
||||
OIDNDenoiserBase base_;
|
||||
|
||||
bool denoise_create_if_needed(const class OIDNDenoiseContext &context);
|
||||
bool denoise_run(class OIDNDenoiseContext &context, const PassType pass_type);
|
||||
bool commit_and_execute_filter(OIDNFilter filter);
|
||||
void set_common_filter_params(OIDNFilter filter);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/* We only perform one denoising at a time, since OpenImageDenoise itself is multithreaded.
|
||||
* Use this mutex whenever images are passed to the OIDN and needs to be denoised. */
|
||||
static thread_mutex mutex_;
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
145
blender-5.2.0/intern/cycles/integrator/denoiser_oidn_base.cpp
Normal file
145
blender-5.2.0/intern/cycles/integrator/denoiser_oidn_base.cpp
Normal file
@@ -0,0 +1,145 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2026 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#ifdef WITH_OPENIMAGEDENOISE
|
||||
|
||||
# include "integrator/denoiser_oidn_base.h"
|
||||
|
||||
# include "util/log.h"
|
||||
# include "util/path.h"
|
||||
|
||||
# if OIDN_VERSION_MAJOR < 2
|
||||
# define oidnSetFilterInt oidnSetFilter1i
|
||||
# endif
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
OIDNDenoiserBase::OIDNDenoiserBase(Denoiser *denoiser) : denoiser_(denoiser) {}
|
||||
|
||||
OIDNFilter OIDNDenoiserBase::new_filter()
|
||||
{
|
||||
OIDNFilter filter = oidnNewFilter(oidn_device_, "RT");
|
||||
if (filter == nullptr) {
|
||||
const char *error_message = nullptr;
|
||||
const OIDNError err = oidnGetDeviceError(oidn_device_, &error_message);
|
||||
if (OIDN_ERROR_NONE != err) {
|
||||
LOG_ERROR << "OIDN error: " << error_message;
|
||||
denoiser_->set_error(error_message);
|
||||
}
|
||||
}
|
||||
else {
|
||||
# if OIDN_VERSION_MAJOR >= 2
|
||||
switch (quality_) {
|
||||
case DENOISER_QUALITY_FAST:
|
||||
# if OIDN_VERSION >= 20300
|
||||
oidnSetFilterInt(filter, "quality", OIDN_QUALITY_FAST);
|
||||
break;
|
||||
# endif
|
||||
case DENOISER_QUALITY_BALANCED:
|
||||
oidnSetFilterInt(filter, "quality", OIDN_QUALITY_BALANCED);
|
||||
break;
|
||||
case DENOISER_QUALITY_HIGH:
|
||||
default:
|
||||
oidnSetFilterInt(filter, "quality", OIDN_QUALITY_HIGH);
|
||||
}
|
||||
# endif
|
||||
|
||||
oidnSetFilterBool(filter, "srgb", false);
|
||||
|
||||
/* Set custom weights if available. */
|
||||
if (!custom_weights_.empty()) {
|
||||
oidnSetSharedFilterData(filter, "weights", custom_weights_.data(), custom_weights_.size());
|
||||
}
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
bool OIDNDenoiserBase::create_filters(DenoiserQuality quality, bool use_albedo, bool use_normal)
|
||||
{
|
||||
quality_ = quality;
|
||||
|
||||
oidn_filter_ = new_filter();
|
||||
if (oidn_filter_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
oidnSetFilterBool(oidn_filter_, "hdr", true);
|
||||
|
||||
if (use_albedo) {
|
||||
albedo_filter_ = new_filter();
|
||||
if (albedo_filter_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (use_normal) {
|
||||
normal_filter_ = new_filter();
|
||||
if (normal_filter_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* OIDN denoiser handle was created with the requested number of input passes. */
|
||||
use_pass_albedo_ = use_albedo;
|
||||
use_pass_normal_ = use_normal;
|
||||
|
||||
/* OIDN denoiser has been created, but it needs configuration. */
|
||||
is_configured_ = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OIDNDenoiserBase::load_custom_weights()
|
||||
{
|
||||
const char *custom_weight_path = getenv("CYCLES_OIDN_CUSTOM_WEIGHTS");
|
||||
if (!custom_weight_path) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!path_read_binary(custom_weight_path, custom_weights_)) {
|
||||
LOG_ERROR << "Failed to load custom OpenImageDenoise weights";
|
||||
}
|
||||
}
|
||||
|
||||
void OIDNDenoiserBase::release_all_resources()
|
||||
{
|
||||
if (albedo_filter_) {
|
||||
oidnReleaseFilter(albedo_filter_);
|
||||
albedo_filter_ = nullptr;
|
||||
}
|
||||
if (normal_filter_) {
|
||||
oidnReleaseFilter(normal_filter_);
|
||||
normal_filter_ = nullptr;
|
||||
}
|
||||
if (oidn_filter_) {
|
||||
oidnReleaseFilter(oidn_filter_);
|
||||
oidn_filter_ = nullptr;
|
||||
}
|
||||
if (oidn_device_) {
|
||||
oidnReleaseDevice(oidn_device_);
|
||||
oidn_device_ = nullptr;
|
||||
}
|
||||
|
||||
is_configured_ = false;
|
||||
use_pass_albedo_ = false;
|
||||
use_pass_normal_ = false;
|
||||
}
|
||||
|
||||
bool OIDNDenoiserBase::denoise_configure_if_needed(int width, int height)
|
||||
{
|
||||
const int2 size = make_int2(width, height);
|
||||
|
||||
if (is_configured_ && configured_size_.x == size.x && configured_size_.y == size.y) {
|
||||
return true;
|
||||
}
|
||||
|
||||
configured_size_ = size;
|
||||
is_configured_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
||||
#endif /* WITH_OPENIMAGEDENOISE */
|
||||
50
blender-5.2.0/intern/cycles/integrator/denoiser_oidn_base.h
Normal file
50
blender-5.2.0/intern/cycles/integrator/denoiser_oidn_base.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2026 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef WITH_OPENIMAGEDENOISE
|
||||
|
||||
# include "integrator/denoiser.h"
|
||||
# include "util/openimagedenoise.h"
|
||||
# include "util/vector.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Shared OIDN denoiser functionality for both CPU and GPU implementations.
|
||||
* Uses composition pattern to avoid multiple inheritance. */
|
||||
class OIDNDenoiserBase {
|
||||
public:
|
||||
explicit OIDNDenoiserBase(Denoiser *denoiser);
|
||||
~OIDNDenoiserBase()
|
||||
{
|
||||
release_all_resources();
|
||||
}
|
||||
|
||||
/* OIDN handles and state. */
|
||||
OIDNDevice oidn_device_ = nullptr;
|
||||
OIDNFilter oidn_filter_ = nullptr;
|
||||
OIDNFilter albedo_filter_ = nullptr;
|
||||
OIDNFilter normal_filter_ = nullptr;
|
||||
DenoiserQuality quality_ = DENOISER_QUALITY_HIGH;
|
||||
bool is_configured_ = false;
|
||||
int2 configured_size_ = make_int2(0, 0);
|
||||
vector<uint8_t> custom_weights_;
|
||||
bool use_pass_albedo_ = false;
|
||||
bool use_pass_normal_ = false;
|
||||
|
||||
/* Shared methods. */
|
||||
bool create_filters(DenoiserQuality quality, bool denoise_abledo, bool denoise_normal);
|
||||
void load_custom_weights();
|
||||
void release_all_resources();
|
||||
bool denoise_configure_if_needed(int width, int height);
|
||||
|
||||
private:
|
||||
OIDNFilter new_filter();
|
||||
Denoiser *denoiser_; /* Back-pointer for error reporting. */
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
||||
#endif /* WITH_OPENIMAGEDENOISE */
|
||||
424
blender-5.2.0/intern/cycles/integrator/denoiser_oidn_gpu.cpp
Normal file
424
blender-5.2.0/intern/cycles/integrator/denoiser_oidn_gpu.cpp
Normal file
@@ -0,0 +1,424 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2026 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#if defined(WITH_OPENIMAGEDENOISE)
|
||||
|
||||
# include "integrator/denoiser_oidn_gpu.h"
|
||||
|
||||
# include "device/device.h"
|
||||
# include "device/oneapi/device_impl.h"
|
||||
# include "device/queue.h"
|
||||
|
||||
# include "session/buffers.h"
|
||||
|
||||
# include "util/log.h"
|
||||
|
||||
# if OIDN_VERSION_MAJOR < 2
|
||||
# define oidnSetFilterBool oidnSetFilter1b
|
||||
# define oidnSetFilterInt oidnSetFilter1i
|
||||
# define oidnExecuteFilterAsync oidnExecuteFilter
|
||||
# endif
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
# if OIDN_VERSION < 20300
|
||||
static const char *oidn_device_type_to_string(const OIDNDeviceType type)
|
||||
{
|
||||
switch (type) {
|
||||
case OIDN_DEVICE_TYPE_DEFAULT:
|
||||
return "DEFAULT";
|
||||
case OIDN_DEVICE_TYPE_CPU:
|
||||
return "CPU";
|
||||
|
||||
/* The initial GPU support was added in OIDN 2.0. */
|
||||
# if OIDN_VERSION_MAJOR >= 2
|
||||
case OIDN_DEVICE_TYPE_SYCL:
|
||||
return "SYCL";
|
||||
case OIDN_DEVICE_TYPE_CUDA:
|
||||
return "CUDA";
|
||||
case OIDN_DEVICE_TYPE_HIP:
|
||||
return "HIP";
|
||||
# endif
|
||||
|
||||
/* The Metal support was added in OIDN 2.2. */
|
||||
# if (OIDN_VERSION_MAJOR > 2) || ((OIDN_VERSION_MAJOR == 2) && (OIDN_VERSION_MINOR >= 2))
|
||||
case OIDN_DEVICE_TYPE_METAL:
|
||||
return "METAL";
|
||||
# endif
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
# endif
|
||||
|
||||
bool OIDNDenoiserGPU::is_device_supported(const DeviceInfo &device)
|
||||
{
|
||||
# if OIDN_VERSION >= 20300
|
||||
if (device.type == DEVICE_MULTI) {
|
||||
for (const DeviceInfo &multi_device : device.multi_devices) {
|
||||
if (multi_device.type != DEVICE_CPU && multi_device.denoisers & DENOISER_OPENIMAGEDENOISE) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return device.denoisers & DENOISER_OPENIMAGEDENOISE;
|
||||
# else
|
||||
if (device.type == DEVICE_MULTI) {
|
||||
for (const DeviceInfo &multi_device : device.multi_devices) {
|
||||
if (multi_device.type != DEVICE_CPU && is_device_supported(multi_device)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_TRACE << "Checking device " << device.description << " (" << device.id
|
||||
<< ") for OIDN GPU support";
|
||||
|
||||
int device_type = OIDN_DEVICE_TYPE_DEFAULT;
|
||||
switch (device.type) {
|
||||
# ifdef OIDN_DEVICE_SYCL
|
||||
case DEVICE_ONEAPI:
|
||||
device_type = OIDN_DEVICE_TYPE_SYCL;
|
||||
break;
|
||||
# endif
|
||||
# ifdef OIDN_DEVICE_HIP
|
||||
case DEVICE_HIP:
|
||||
device_type = OIDN_DEVICE_TYPE_HIP;
|
||||
break;
|
||||
# endif
|
||||
# ifdef OIDN_DEVICE_CUDA
|
||||
case DEVICE_CUDA:
|
||||
case DEVICE_OPTIX:
|
||||
device_type = OIDN_DEVICE_TYPE_CUDA;
|
||||
break;
|
||||
# endif
|
||||
# ifdef OIDN_DEVICE_METAL
|
||||
case DEVICE_METAL: {
|
||||
const int num_devices = oidnGetNumPhysicalDevices();
|
||||
LOG_TRACE << "Found " << num_devices << " OIDN device(s)";
|
||||
for (int i = 0; i < num_devices; i++) {
|
||||
const int type = oidnGetPhysicalDeviceInt(i, "type");
|
||||
const char *name = oidnGetPhysicalDeviceString(i, "name");
|
||||
LOG_TRACE << "OIDN device " << i << ": name=\"" << name
|
||||
<< "\", type=" << oidn_device_type_to_string(OIDNDeviceType(type));
|
||||
if (type == OIDN_DEVICE_TYPE_METAL) {
|
||||
if (device.id.find(name) != std::string::npos) {
|
||||
LOG_TRACE << "OIDN device name matches the Cycles device name";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_TRACE << "No matched OIDN device found";
|
||||
return false;
|
||||
}
|
||||
# endif
|
||||
case DEVICE_CPU:
|
||||
/* This is the GPU denoiser - CPU devices shouldn't end up here. */
|
||||
assert(0);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Match GPUs by their PCI ID. */
|
||||
const int num_devices = oidnGetNumPhysicalDevices();
|
||||
LOG_TRACE << "Found " << num_devices << " OIDN device(s)";
|
||||
for (int i = 0; i < num_devices; i++) {
|
||||
const int type = oidnGetPhysicalDeviceInt(i, "type");
|
||||
const char *name = oidnGetPhysicalDeviceString(i, "name");
|
||||
LOG_TRACE << "OIDN device " << i << ": name=\"" << name
|
||||
<< "\" type=" << oidn_device_type_to_string(OIDNDeviceType(type));
|
||||
if (type == device_type) {
|
||||
if (oidnGetPhysicalDeviceBool(i, "pciAddressSupported")) {
|
||||
unsigned int pci_domain = oidnGetPhysicalDeviceInt(i, "pciDomain");
|
||||
unsigned int pci_bus = oidnGetPhysicalDeviceInt(i, "pciBus");
|
||||
unsigned int pci_device = oidnGetPhysicalDeviceInt(i, "pciDevice");
|
||||
string pci_id = string_printf("%04x:%02x:%02x", pci_domain, pci_bus, pci_device);
|
||||
LOG_INFO << "OIDN device PCI-e identifier: " << pci_id;
|
||||
if (device.id.find(pci_id) != string::npos) {
|
||||
LOG_TRACE << "OIDN device PCI-e identifier matches the Cycles device ID";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG_TRACE << "Device does not support pciAddressSupported";
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_TRACE << "No matched OIDN device found";
|
||||
return false;
|
||||
# endif
|
||||
}
|
||||
|
||||
OIDNDenoiserGPU::OIDNDenoiserGPU(Device *denoiser_device, const DenoiseParams ¶ms)
|
||||
: DenoiserGPU(denoiser_device, params), base_(this)
|
||||
{
|
||||
DCHECK_EQ(params.type, DENOISER_OPENIMAGEDENOISE);
|
||||
}
|
||||
|
||||
bool OIDNDenoiserGPU::commit_and_execute_filter(OIDNFilter filter, ExecMode mode)
|
||||
{
|
||||
const char *error_message = nullptr;
|
||||
OIDNError err = OIDN_ERROR_NONE;
|
||||
|
||||
for (;;) {
|
||||
oidnCommitFilter(filter);
|
||||
if (mode == ExecMode::ASYNC) {
|
||||
oidnExecuteFilterAsync(filter);
|
||||
}
|
||||
else {
|
||||
oidnExecuteFilter(filter);
|
||||
}
|
||||
|
||||
/* If OIDN runs out of memory, reduce mem limit and retry */
|
||||
err = oidnGetDeviceError(base_.oidn_device_, &error_message);
|
||||
if (err != OIDN_ERROR_OUT_OF_MEMORY || max_mem_ < 200) {
|
||||
break;
|
||||
}
|
||||
max_mem_ = max_mem_ / 2;
|
||||
oidnSetFilterInt(filter, "maxMemoryMB", max_mem_);
|
||||
}
|
||||
|
||||
if (err != OIDN_ERROR_NONE) {
|
||||
if (error_message == nullptr) {
|
||||
error_message = "Unspecified OIDN error";
|
||||
}
|
||||
LOG_ERROR << "OIDN error: " << error_message;
|
||||
set_error(error_message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OIDNDenoiserGPU::denoise_create_if_needed(DenoiseContext &context)
|
||||
{
|
||||
const bool use_pass_albedo = (context.denoise_params.passes & DENOISER_PASS_ALBEDO) != 0;
|
||||
const bool use_pass_normal = (context.denoise_params.passes & DENOISER_PASS_NORMAL) != 0;
|
||||
|
||||
const bool recreate_denoiser = (base_.oidn_device_ == nullptr) ||
|
||||
(base_.oidn_filter_ == nullptr) ||
|
||||
(base_.use_pass_albedo_ != use_pass_albedo) ||
|
||||
(base_.use_pass_normal_ != use_pass_normal) ||
|
||||
(base_.quality_ != params_.quality);
|
||||
if (!recreate_denoiser) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Destroy existing handles before creating new ones. */
|
||||
base_.release_all_resources();
|
||||
|
||||
switch (denoiser_device_->info.type) {
|
||||
# if defined(OIDN_DEVICE_SYCL) && defined(WITH_ONEAPI)
|
||||
case DEVICE_ONEAPI:
|
||||
base_.oidn_device_ = oidnNewSYCLDevice(
|
||||
(const sycl::queue *)reinterpret_cast<OneapiDevice *>(denoiser_device_)->sycl_queue(),
|
||||
1);
|
||||
break;
|
||||
# endif
|
||||
# if defined(OIDN_DEVICE_METAL) && defined(WITH_METAL)
|
||||
case DEVICE_METAL: {
|
||||
denoiser_queue_->init_execution();
|
||||
const MTLCommandQueue_id queue = (const MTLCommandQueue_id)denoiser_queue_->native_queue();
|
||||
base_.oidn_device_ = oidnNewMetalDevice(&queue, 1);
|
||||
} break;
|
||||
# endif
|
||||
# if defined(OIDN_DEVICE_CUDA) && defined(WITH_CUDA)
|
||||
case DEVICE_CUDA:
|
||||
case DEVICE_OPTIX: {
|
||||
/* Directly using the stream from the DeviceQueue returns "invalid resource handle". */
|
||||
cudaStream_t stream = nullptr;
|
||||
base_.oidn_device_ = oidnNewCUDADevice(&denoiser_device_->info.num, &stream, 1);
|
||||
break;
|
||||
}
|
||||
# endif
|
||||
# if defined(OIDN_DEVICE_HIP) && defined(WITH_HIP)
|
||||
case DEVICE_HIP: {
|
||||
hipStream_t stream = nullptr;
|
||||
base_.oidn_device_ = oidnNewHIPDevice(&denoiser_device_->info.num, &stream, 1);
|
||||
break;
|
||||
}
|
||||
# endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!base_.oidn_device_) {
|
||||
set_error("Failed to create OIDN device");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (denoiser_queue_) {
|
||||
denoiser_queue_->init_execution();
|
||||
}
|
||||
|
||||
oidnCommitDevice(base_.oidn_device_);
|
||||
base_.load_custom_weights();
|
||||
return base_.create_filters(params_.quality, use_pass_albedo, use_pass_normal);
|
||||
}
|
||||
|
||||
bool OIDNDenoiserGPU::denoise_configure_if_needed(DenoiseContext &context)
|
||||
{
|
||||
/* Limit maximum tile size denoiser can be invoked with. */
|
||||
return base_.denoise_configure_if_needed(context.buffer_params.width,
|
||||
context.buffer_params.height);
|
||||
}
|
||||
|
||||
bool OIDNDenoiserGPU::denoise_run(const DenoiseContext &context, const DenoisePass &pass)
|
||||
{
|
||||
/* Color pass. */
|
||||
const int64_t pass_stride_in_bytes = context.buffer_params.pass_stride * sizeof(float);
|
||||
|
||||
set_filter_pass(base_.oidn_filter_,
|
||||
"color",
|
||||
context.render_buffers->buffer.device_pointer,
|
||||
OIDN_FORMAT_FLOAT3,
|
||||
context.buffer_params.width,
|
||||
context.buffer_params.height,
|
||||
pass.denoised_offset * sizeof(float),
|
||||
pass_stride_in_bytes,
|
||||
pass_stride_in_bytes * context.buffer_params.stride);
|
||||
|
||||
set_filter_pass(base_.oidn_filter_,
|
||||
"output",
|
||||
context.render_buffers->buffer.device_pointer,
|
||||
OIDN_FORMAT_FLOAT3,
|
||||
context.buffer_params.width,
|
||||
context.buffer_params.height,
|
||||
pass.denoised_offset * sizeof(float),
|
||||
pass_stride_in_bytes,
|
||||
pass_stride_in_bytes * context.buffer_params.stride);
|
||||
|
||||
/* Optional albedo and color passes. */
|
||||
const device_ptr d_guiding_buffer = context.guiding_params.device_pointer;
|
||||
const int64_t pixel_stride_in_bytes = context.guiding_params.pass_stride * sizeof(float);
|
||||
const int64_t row_stride_in_bytes = context.guiding_params.stride * pixel_stride_in_bytes;
|
||||
|
||||
if (base_.use_pass_albedo_) {
|
||||
set_filter_pass(base_.oidn_filter_,
|
||||
"albedo",
|
||||
d_guiding_buffer,
|
||||
OIDN_FORMAT_FLOAT3,
|
||||
context.buffer_params.width,
|
||||
context.buffer_params.height,
|
||||
context.guiding_params.pass_albedo * sizeof(float),
|
||||
pixel_stride_in_bytes,
|
||||
row_stride_in_bytes);
|
||||
|
||||
if (params_.prefilter == DENOISER_PREFILTER_ACCURATE) {
|
||||
set_filter_pass(base_.albedo_filter_,
|
||||
"albedo",
|
||||
d_guiding_buffer,
|
||||
OIDN_FORMAT_FLOAT3,
|
||||
context.buffer_params.width,
|
||||
context.buffer_params.height,
|
||||
context.guiding_params.pass_albedo * sizeof(float),
|
||||
pixel_stride_in_bytes,
|
||||
row_stride_in_bytes);
|
||||
|
||||
set_filter_pass(base_.albedo_filter_,
|
||||
"output",
|
||||
d_guiding_buffer,
|
||||
OIDN_FORMAT_FLOAT3,
|
||||
context.buffer_params.width,
|
||||
context.buffer_params.height,
|
||||
context.guiding_params.pass_albedo * sizeof(float),
|
||||
pixel_stride_in_bytes,
|
||||
row_stride_in_bytes);
|
||||
|
||||
if (!commit_and_execute_filter(base_.albedo_filter_, ExecMode::ASYNC)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (base_.use_pass_normal_) {
|
||||
set_filter_pass(base_.oidn_filter_,
|
||||
"normal",
|
||||
d_guiding_buffer,
|
||||
OIDN_FORMAT_FLOAT3,
|
||||
context.buffer_params.width,
|
||||
context.buffer_params.height,
|
||||
context.guiding_params.pass_normal * sizeof(float),
|
||||
pixel_stride_in_bytes,
|
||||
row_stride_in_bytes);
|
||||
|
||||
if (params_.prefilter == DENOISER_PREFILTER_ACCURATE) {
|
||||
set_filter_pass(base_.normal_filter_,
|
||||
"normal",
|
||||
d_guiding_buffer,
|
||||
OIDN_FORMAT_FLOAT3,
|
||||
context.buffer_params.width,
|
||||
context.buffer_params.height,
|
||||
context.guiding_params.pass_normal * sizeof(float),
|
||||
pixel_stride_in_bytes,
|
||||
row_stride_in_bytes);
|
||||
|
||||
set_filter_pass(base_.normal_filter_,
|
||||
"output",
|
||||
d_guiding_buffer,
|
||||
OIDN_FORMAT_FLOAT3,
|
||||
context.buffer_params.width,
|
||||
context.buffer_params.height,
|
||||
context.guiding_params.pass_normal * sizeof(float),
|
||||
pixel_stride_in_bytes,
|
||||
row_stride_in_bytes);
|
||||
|
||||
if (!commit_and_execute_filter(base_.normal_filter_, ExecMode::ASYNC)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
oidnSetFilterInt(base_.oidn_filter_, "cleanAux", params_.prefilter != DENOISER_PREFILTER_FAST);
|
||||
return commit_and_execute_filter(base_.oidn_filter_);
|
||||
}
|
||||
|
||||
void OIDNDenoiserGPU::set_filter_pass(OIDNFilter filter,
|
||||
const char *name,
|
||||
device_ptr ptr,
|
||||
const int format,
|
||||
const int width,
|
||||
const int height,
|
||||
const size_t offset_in_bytes,
|
||||
const size_t pixel_stride_in_bytes,
|
||||
const size_t row_stride_in_bytes)
|
||||
{
|
||||
# if defined(OIDN_DEVICE_METAL) && defined(WITH_METAL)
|
||||
if (denoiser_device_->info.type == DEVICE_METAL) {
|
||||
void *mtl_buffer = denoiser_device_->get_native_buffer(ptr);
|
||||
OIDNBuffer oidn_buffer = oidnNewSharedBufferFromMetal(base_.oidn_device_, mtl_buffer);
|
||||
|
||||
oidnSetFilterImage(filter,
|
||||
name,
|
||||
oidn_buffer,
|
||||
(OIDNFormat)format,
|
||||
width,
|
||||
height,
|
||||
offset_in_bytes,
|
||||
pixel_stride_in_bytes,
|
||||
row_stride_in_bytes);
|
||||
|
||||
oidnReleaseBuffer(oidn_buffer);
|
||||
}
|
||||
else
|
||||
# endif
|
||||
{
|
||||
oidnSetSharedFilterImage(filter,
|
||||
name,
|
||||
(void *)ptr,
|
||||
(OIDNFormat)format,
|
||||
width,
|
||||
height,
|
||||
offset_in_bytes,
|
||||
pixel_stride_in_bytes,
|
||||
row_stride_in_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
61
blender-5.2.0/intern/cycles/integrator/denoiser_oidn_gpu.h
Normal file
61
blender-5.2.0/intern/cycles/integrator/denoiser_oidn_gpu.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(WITH_OPENIMAGEDENOISE)
|
||||
|
||||
# include "integrator/denoiser_gpu.h"
|
||||
# include "integrator/denoiser_oidn_base.h"
|
||||
# include "util/openimagedenoise.h" // IWYU pragma: keep
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Implementation of a GPU denoiser which uses OpenImageDenoise library. */
|
||||
class OIDNDenoiserGPU : public DenoiserGPU {
|
||||
public:
|
||||
class State;
|
||||
|
||||
OIDNDenoiserGPU(Device *denoiser_device, const DenoiseParams ¶ms);
|
||||
|
||||
static bool is_device_supported(const DeviceInfo &device);
|
||||
|
||||
protected:
|
||||
enum class ExecMode {
|
||||
SYNC,
|
||||
ASYNC,
|
||||
};
|
||||
|
||||
/* Create OIDN denoiser descriptor if needed.
|
||||
* Will do nothing if the current OIDN descriptor is usable for the given parameters.
|
||||
* If the OIDN denoiser descriptor did re-allocate here it is left unconfigured. */
|
||||
bool denoise_create_if_needed(DenoiseContext &context) override;
|
||||
|
||||
/* Configure existing OIDN denoiser descriptor for the use for the given task. */
|
||||
bool denoise_configure_if_needed(DenoiseContext &context) override;
|
||||
|
||||
/* Run configured denoiser. */
|
||||
bool denoise_run(const DenoiseContext &context, const DenoisePass &pass) override;
|
||||
|
||||
bool commit_and_execute_filter(OIDNFilter filter, ExecMode mode = ExecMode::SYNC);
|
||||
|
||||
void set_filter_pass(OIDNFilter filter,
|
||||
const char *name,
|
||||
device_ptr ptr,
|
||||
const int format,
|
||||
const int width,
|
||||
const int height,
|
||||
const size_t offset_in_bytes,
|
||||
const size_t pixel_stride_in_bytes,
|
||||
size_t row_stride_in_bytes);
|
||||
|
||||
OIDNDenoiserBase base_;
|
||||
|
||||
/* Filter memory usage limit if we ran out of memory with OIDN's default limit. */
|
||||
int max_mem_ = 768;
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
281
blender-5.2.0/intern/cycles/integrator/denoiser_optix.cpp
Normal file
281
blender-5.2.0/intern/cycles/integrator/denoiser_optix.cpp
Normal file
@@ -0,0 +1,281 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#ifdef WITH_OPTIX
|
||||
|
||||
# include "integrator/denoiser_optix.h"
|
||||
# include "integrator/pass_accessor_gpu.h"
|
||||
|
||||
# include "device/optix/device_impl.h"
|
||||
# include "device/optix/queue.h"
|
||||
|
||||
# include <optix_denoiser_tiling.h>
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
OptiXDenoiser::OptiXDenoiser(Device *denoiser_device, const DenoiseParams ¶ms)
|
||||
: DenoiserGPU(denoiser_device, params), state_(denoiser_device, "__denoiser_state", true)
|
||||
{
|
||||
}
|
||||
|
||||
OptiXDenoiser::~OptiXDenoiser()
|
||||
{
|
||||
/* It is important that the OptixDenoiser handle is destroyed before the OptixDeviceContext
|
||||
* handle, which is guaranteed since the local denoising device owning the OptiX device context
|
||||
* is deleted as part of the Denoiser class destructor call after this. */
|
||||
if (optix_denoiser_ != nullptr) {
|
||||
optixDenoiserDestroy(optix_denoiser_);
|
||||
}
|
||||
}
|
||||
|
||||
bool OptiXDenoiser::is_device_supported(const DeviceInfo &device)
|
||||
{
|
||||
if (device.type == DEVICE_OPTIX) {
|
||||
return device.denoisers & DENOISER_OPTIX;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OptiXDenoiser::denoise_buffer(const BufferParams &buffer_params,
|
||||
const BufferParams &denoised_buffer_params,
|
||||
RenderBuffers *render_buffers,
|
||||
const int num_samples,
|
||||
const bool allow_inplace_modification,
|
||||
const float2 pixel_jitter)
|
||||
{
|
||||
OptiXDevice *const optix_device = static_cast<OptiXDevice *>(denoiser_device_);
|
||||
const CUDAContextScope scope(optix_device);
|
||||
|
||||
return DenoiserGPU::denoise_buffer(buffer_params,
|
||||
denoised_buffer_params,
|
||||
render_buffers,
|
||||
num_samples,
|
||||
allow_inplace_modification,
|
||||
pixel_jitter);
|
||||
}
|
||||
|
||||
bool OptiXDenoiser::denoise_create_if_needed(DenoiseContext &context)
|
||||
{
|
||||
const bool use_pass_albedo = (context.denoise_params.passes & DENOISER_PASS_ALBEDO) != 0;
|
||||
const bool use_pass_normal = (context.denoise_params.passes & DENOISER_PASS_NORMAL) != 0;
|
||||
const bool use_pass_motion = context.denoise_params.temporally_stable &&
|
||||
(context.denoise_params.passes & DENOISER_PASS_MOTION) != 0;
|
||||
const bool use_upscale_model = context.denoise_params.upscale_factor == 2.0f;
|
||||
|
||||
const bool recreate_denoiser = (optix_denoiser_ == nullptr) ||
|
||||
(use_pass_albedo_ != use_pass_albedo) ||
|
||||
(use_pass_normal_ != use_pass_normal) ||
|
||||
(use_pass_motion_ != use_pass_motion) ||
|
||||
(use_upscale_model_ != use_upscale_model);
|
||||
if (!recreate_denoiser) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Destroy existing handle before creating new one. */
|
||||
if (optix_denoiser_) {
|
||||
optixDenoiserDestroy(optix_denoiser_);
|
||||
}
|
||||
|
||||
/* Create OptiX denoiser handle on demand when it is first used. */
|
||||
OptixDenoiserOptions denoiser_options = {};
|
||||
denoiser_options.guideAlbedo = use_pass_albedo;
|
||||
denoiser_options.guideNormal = use_pass_normal;
|
||||
|
||||
OptixDenoiserModelKind model = OPTIX_DENOISER_MODEL_KIND_AOV;
|
||||
if (use_pass_motion) {
|
||||
if (use_upscale_model) {
|
||||
model = OPTIX_DENOISER_MODEL_KIND_TEMPORAL_UPSCALE2X;
|
||||
}
|
||||
else {
|
||||
model = OPTIX_DENOISER_MODEL_KIND_TEMPORAL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (use_upscale_model) {
|
||||
model = OPTIX_DENOISER_MODEL_KIND_UPSCALE2X;
|
||||
}
|
||||
}
|
||||
|
||||
const OptixResult result = optixDenoiserCreate(
|
||||
static_cast<OptiXDevice *>(denoiser_device_)->context,
|
||||
model,
|
||||
&denoiser_options,
|
||||
&optix_denoiser_);
|
||||
|
||||
if (result != OPTIX_SUCCESS) {
|
||||
set_error("Failed to create OptiX denoiser");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* OptiX denoiser handle was created with the requested number of input passes. */
|
||||
use_pass_albedo_ = use_pass_albedo;
|
||||
use_pass_normal_ = use_pass_normal;
|
||||
use_pass_motion_ = use_pass_motion;
|
||||
use_upscale_model_ = use_upscale_model;
|
||||
|
||||
/* OptiX denoiser has been created, but it needs configuration. */
|
||||
is_configured_ = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OptiXDenoiser::denoise_configure_if_needed(DenoiseContext &context)
|
||||
{
|
||||
/* Limit maximum tile size denoiser can be invoked with. */
|
||||
const int2 tile_size = make_int2(min(context.buffer_params.width, 4096),
|
||||
min(context.buffer_params.height, 4096));
|
||||
|
||||
if (is_configured_ && (configured_size_.x == tile_size.x && configured_size_.y == tile_size.y)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
optix_device_assert(
|
||||
denoiser_device_,
|
||||
optixDenoiserComputeMemoryResources(optix_denoiser_, tile_size.x, tile_size.y, &sizes_));
|
||||
|
||||
const bool tiled = tile_size.x < context.buffer_params.width ||
|
||||
tile_size.y < context.buffer_params.height;
|
||||
|
||||
/* Allocate denoiser state if tile size has changed since last setup. */
|
||||
state_.device = denoiser_device_;
|
||||
state_.alloc_to_device(sizes_.stateSizeInBytes + sizes_.withOverlapScratchSizeInBytes);
|
||||
|
||||
/* Initialize denoiser state for the current tile size. */
|
||||
const OptixResult result = optixDenoiserSetup(
|
||||
optix_denoiser_,
|
||||
0, /* Work around bug in r495 drivers that causes artifacts when denoiser setup is called
|
||||
* on a stream that is not the default stream. */
|
||||
tile_size.x + (tiled ? sizes_.overlapWindowSizeInPixels * 2 : 0),
|
||||
tile_size.y + (tiled ? sizes_.overlapWindowSizeInPixels * 2 : 0),
|
||||
state_.device_pointer,
|
||||
sizes_.stateSizeInBytes,
|
||||
state_.device_pointer + sizes_.stateSizeInBytes,
|
||||
sizes_.withOverlapScratchSizeInBytes);
|
||||
if (result != OPTIX_SUCCESS) {
|
||||
set_error("Failed to set up OptiX denoiser");
|
||||
return false;
|
||||
}
|
||||
|
||||
cuda_device_assert(denoiser_device_, cuCtxSynchronize());
|
||||
|
||||
is_configured_ = true;
|
||||
configured_size_ = tile_size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OptiXDenoiser::denoise_run(const DenoiseContext &context, const DenoisePass &pass)
|
||||
{
|
||||
/* Set up input and output layer information. */
|
||||
OptixImage2D color_layer = {0};
|
||||
OptixImage2D albedo_layer = {0};
|
||||
OptixImage2D normal_layer = {0};
|
||||
OptixImage2D flow_layer = {0};
|
||||
|
||||
OptixImage2D output_layer = {0};
|
||||
OptixImage2D prev_output_layer = {0};
|
||||
|
||||
/* Color pass. */
|
||||
{
|
||||
const int pass_denoised = pass.denoised_offset;
|
||||
const int64_t pass_stride_in_bytes = context.buffer_params.pass_stride * sizeof(float);
|
||||
|
||||
color_layer.data = context.render_buffers->buffer.device_pointer +
|
||||
pass_denoised * sizeof(float);
|
||||
color_layer.width = context.buffer_params.width;
|
||||
color_layer.height = context.buffer_params.height;
|
||||
color_layer.rowStrideInBytes = pass_stride_in_bytes * context.buffer_params.stride;
|
||||
color_layer.pixelStrideInBytes = pass_stride_in_bytes;
|
||||
color_layer.format = OPTIX_PIXEL_FORMAT_FLOAT3;
|
||||
}
|
||||
|
||||
/* Previous output. */
|
||||
if (use_pass_motion_ && context.prev_output.offset != PASS_UNUSED) {
|
||||
const int64_t pass_stride_in_bytes = context.prev_output.pass_stride * sizeof(float);
|
||||
|
||||
prev_output_layer.data = context.prev_output.device_pointer +
|
||||
context.prev_output.offset * sizeof(float);
|
||||
prev_output_layer.width = context.denoised_buffer_params.width;
|
||||
prev_output_layer.height = context.denoised_buffer_params.height;
|
||||
prev_output_layer.rowStrideInBytes = pass_stride_in_bytes * context.prev_output.stride;
|
||||
prev_output_layer.pixelStrideInBytes = pass_stride_in_bytes;
|
||||
prev_output_layer.format = OPTIX_PIXEL_FORMAT_FLOAT3;
|
||||
}
|
||||
|
||||
/* Optional albedo and color passes. */
|
||||
const device_ptr d_guiding_buffer = context.guiding_params.device_pointer;
|
||||
const int64_t pixel_stride_in_bytes = context.guiding_params.pass_stride * sizeof(float);
|
||||
const int64_t row_stride_in_bytes = context.guiding_params.stride * pixel_stride_in_bytes;
|
||||
|
||||
if (use_pass_albedo_) {
|
||||
albedo_layer.data = d_guiding_buffer + context.guiding_params.pass_albedo * sizeof(float);
|
||||
albedo_layer.width = context.buffer_params.width;
|
||||
albedo_layer.height = context.buffer_params.height;
|
||||
albedo_layer.rowStrideInBytes = row_stride_in_bytes;
|
||||
albedo_layer.pixelStrideInBytes = pixel_stride_in_bytes;
|
||||
albedo_layer.format = OPTIX_PIXEL_FORMAT_FLOAT3;
|
||||
}
|
||||
|
||||
if (use_pass_normal_) {
|
||||
normal_layer.data = d_guiding_buffer + context.guiding_params.pass_normal * sizeof(float);
|
||||
normal_layer.width = context.buffer_params.width;
|
||||
normal_layer.height = context.buffer_params.height;
|
||||
normal_layer.rowStrideInBytes = row_stride_in_bytes;
|
||||
normal_layer.pixelStrideInBytes = pixel_stride_in_bytes;
|
||||
normal_layer.format = OPTIX_PIXEL_FORMAT_FLOAT3;
|
||||
}
|
||||
|
||||
if (use_pass_motion_) {
|
||||
flow_layer.data = d_guiding_buffer + context.guiding_params.pass_flow * sizeof(float);
|
||||
flow_layer.width = context.buffer_params.width;
|
||||
flow_layer.height = context.buffer_params.height;
|
||||
flow_layer.rowStrideInBytes = row_stride_in_bytes;
|
||||
flow_layer.pixelStrideInBytes = pixel_stride_in_bytes;
|
||||
flow_layer.format = OPTIX_PIXEL_FORMAT_FLOAT2;
|
||||
}
|
||||
|
||||
/* Denoise in-place of the noisy input in the render buffers. */
|
||||
{
|
||||
output_layer = color_layer;
|
||||
output_layer.width = context.denoised_buffer_params.width;
|
||||
output_layer.height = context.denoised_buffer_params.height;
|
||||
output_layer.rowStrideInBytes = output_layer.pixelStrideInBytes *
|
||||
context.denoised_buffer_params.stride;
|
||||
}
|
||||
|
||||
OptixDenoiserGuideLayer guide_layers = {};
|
||||
guide_layers.albedo = albedo_layer;
|
||||
guide_layers.normal = normal_layer;
|
||||
guide_layers.flow = flow_layer;
|
||||
|
||||
OptixDenoiserLayer image_layers = {};
|
||||
image_layers.input = color_layer;
|
||||
image_layers.previousOutput = prev_output_layer;
|
||||
image_layers.output = output_layer;
|
||||
|
||||
/* Finally run denoising. */
|
||||
OptixDenoiserParams params = {}; /* All parameters are disabled/zero. */
|
||||
|
||||
optix_device_assert(denoiser_device_,
|
||||
optixUtilDenoiserInvokeTiled(
|
||||
optix_denoiser_,
|
||||
static_cast<OptiXDeviceQueue *>(denoiser_queue_.get())->stream(),
|
||||
¶ms,
|
||||
state_.device_pointer,
|
||||
sizes_.stateSizeInBytes,
|
||||
&guide_layers,
|
||||
&image_layers,
|
||||
1,
|
||||
state_.device_pointer + sizes_.stateSizeInBytes,
|
||||
sizes_.withOverlapScratchSizeInBytes,
|
||||
sizes_.overlapWindowSizeInPixels,
|
||||
configured_size_.x,
|
||||
configured_size_.y));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
67
blender-5.2.0/intern/cycles/integrator/denoiser_optix.h
Normal file
67
blender-5.2.0/intern/cycles/integrator/denoiser_optix.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef WITH_OPTIX
|
||||
|
||||
# include "integrator/denoiser_gpu.h"
|
||||
|
||||
# include "device/optix/util.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Implementation of denoising API which uses the OptiX denoiser. */
|
||||
class OptiXDenoiser : public DenoiserGPU {
|
||||
public:
|
||||
OptiXDenoiser(Device *denoiser_device, const DenoiseParams ¶ms);
|
||||
~OptiXDenoiser();
|
||||
|
||||
virtual bool denoise_buffer(const BufferParams &buffer_params,
|
||||
const BufferParams &denoised_buffer_params,
|
||||
RenderBuffers *render_buffers,
|
||||
int num_samples,
|
||||
bool allow_inplace_modification,
|
||||
float2 pixel_jitter) override;
|
||||
|
||||
static bool is_device_supported(const DeviceInfo &device);
|
||||
|
||||
private:
|
||||
/* Set fake albedo pixels in the albedo guiding pass storage.
|
||||
* After this point only passes which do not need albedo for denoising can be processed. */
|
||||
bool denoise_filter_guiding_set_fake_albedo(const DenoiseContext &context);
|
||||
|
||||
/* Create OptiX denoiser descriptor if needed.
|
||||
* Will do nothing if the current OptiX descriptor is usable for the given parameters.
|
||||
* If the OptiX denoiser descriptor did re-allocate here it is left unconfigured. */
|
||||
virtual bool denoise_create_if_needed(DenoiseContext &context) override;
|
||||
|
||||
/* Configure existing OptiX denoiser descriptor for the use for the given task. */
|
||||
virtual bool denoise_configure_if_needed(DenoiseContext &context) override;
|
||||
|
||||
/* Run configured denoiser. */
|
||||
virtual bool denoise_run(const DenoiseContext &context, const DenoisePass &pass) override;
|
||||
|
||||
OptixDenoiser optix_denoiser_ = nullptr;
|
||||
|
||||
/* Configuration size, as provided to `optixDenoiserSetup`.
|
||||
* If the `optixDenoiserSetup()` was never used on the current `optix_denoiser` the
|
||||
* `is_configured` will be false. */
|
||||
bool is_configured_ = false;
|
||||
int2 configured_size_ = make_int2(0, 0);
|
||||
|
||||
/* OptiX denoiser state and scratch buffers, stored in a single memory buffer.
|
||||
* The memory layout goes as following: [denoiser state][scratch buffer]. */
|
||||
device_only_memory<unsigned char> state_;
|
||||
OptixDenoiserSizes sizes_ = {};
|
||||
|
||||
bool use_pass_albedo_ = false;
|
||||
bool use_pass_normal_ = false;
|
||||
bool use_pass_motion_ = false;
|
||||
bool use_upscale_model_ = false;
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
37
blender-5.2.0/intern/cycles/integrator/guiding.h
Normal file
37
blender-5.2.0/intern/cycles/integrator/guiding.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/types.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
struct GuidingParams {
|
||||
/* The subset of path guiding parameters that can trigger a creation/rebuild
|
||||
* of the guiding field. */
|
||||
bool use = false;
|
||||
bool use_surface_guiding = false;
|
||||
bool use_volume_guiding = false;
|
||||
|
||||
GuidingDistributionType type = GUIDING_TYPE_PARALLAX_AWARE_VMM;
|
||||
GuidingDirectionalSamplingType sampling_type = GUIDING_DIRECTIONAL_SAMPLING_TYPE_PRODUCT_MIS;
|
||||
float roughness_threshold = 0.05f;
|
||||
int training_samples = 128;
|
||||
bool deterministic = false;
|
||||
|
||||
GuidingParams() = default;
|
||||
|
||||
bool modified(const GuidingParams &other) const
|
||||
{
|
||||
return !((use == other.use) && (use_surface_guiding == other.use_surface_guiding) &&
|
||||
(use_volume_guiding == other.use_volume_guiding) && (type == other.type) &&
|
||||
(sampling_type == other.sampling_type) &&
|
||||
(training_samples == other.training_samples) &&
|
||||
(roughness_threshold == other.roughness_threshold) &&
|
||||
(deterministic == other.deterministic));
|
||||
}
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
330
blender-5.2.0/intern/cycles/integrator/pass_accessor.cpp
Normal file
330
blender-5.2.0/intern/cycles/integrator/pass_accessor.cpp
Normal file
@@ -0,0 +1,330 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "integrator/pass_accessor.h"
|
||||
|
||||
#include "kernel/types.h"
|
||||
#include "session/buffers.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Pass input information.
|
||||
*/
|
||||
|
||||
PassAccessor::PassAccessInfo::PassAccessInfo(const BufferPass &pass)
|
||||
: type(pass.type),
|
||||
mode(pass.mode),
|
||||
include_albedo(pass.include_albedo),
|
||||
is_lightgroup(!pass.lightgroup.empty()),
|
||||
offset(pass.offset)
|
||||
{
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Pass destination.
|
||||
*/
|
||||
|
||||
PassAccessor::Destination::Destination(float *pixels, const int num_components)
|
||||
: pixels(pixels), num_components(num_components)
|
||||
{
|
||||
}
|
||||
|
||||
PassAccessor::Destination::Destination(const PassType pass_type, const PassMode pass_mode)
|
||||
{
|
||||
const PassInfo pass_info = Pass::get_info(pass_type, pass_mode);
|
||||
num_components = pass_info.num_components;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Pass source.
|
||||
*/
|
||||
|
||||
PassAccessor::Source::Source(const float *pixels, const int num_components)
|
||||
: pixels(pixels), num_components(num_components)
|
||||
{
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Pass accessor.
|
||||
*/
|
||||
|
||||
PassAccessor::PassAccessor(const PassAccessInfo &pass_access_info,
|
||||
const float exposure,
|
||||
const int num_samples)
|
||||
: pass_access_info_(pass_access_info), exposure_(exposure), num_samples_(num_samples)
|
||||
{
|
||||
}
|
||||
|
||||
bool PassAccessor::get_render_tile_pixels(const RenderBuffers *render_buffers,
|
||||
const Destination &destination) const
|
||||
{
|
||||
if (render_buffers == nullptr || render_buffers->buffer.data() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return get_render_tile_pixels(render_buffers, render_buffers->params, destination);
|
||||
}
|
||||
|
||||
static void pad_pixels(const BufferParams &buffer_params,
|
||||
const PassAccessor::Destination &destination,
|
||||
const int src_num_components)
|
||||
{
|
||||
/* When requesting a single channel pass as RGBA, or RGB pass as RGBA,
|
||||
* fill in the additional components for convenience. */
|
||||
const int dest_num_components = destination.num_components;
|
||||
|
||||
if (src_num_components >= dest_num_components) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t size = static_cast<size_t>(buffer_params.width) * buffer_params.height;
|
||||
if (destination.pixels) {
|
||||
const size_t pixel_stride = destination.pixel_stride ? destination.pixel_stride :
|
||||
destination.num_components;
|
||||
|
||||
float *pixel = destination.pixels + pixel_stride * destination.offset;
|
||||
|
||||
for (size_t i = 0; i < size; i++, pixel += dest_num_components) {
|
||||
if (dest_num_components >= 3 && src_num_components == 1) {
|
||||
pixel[1] = pixel[0];
|
||||
pixel[2] = pixel[0];
|
||||
}
|
||||
if (dest_num_components >= 4) {
|
||||
pixel[3] = 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (destination.pixels_half_rgba) {
|
||||
const half one = float_to_half_display(1.0f);
|
||||
half4 *pixel = destination.pixels_half_rgba + destination.offset;
|
||||
|
||||
for (size_t i = 0; i < size; i++, pixel++) {
|
||||
if (dest_num_components >= 3 && src_num_components == 1) {
|
||||
pixel[0].y = pixel[0].x;
|
||||
pixel[0].z = pixel[0].x;
|
||||
}
|
||||
if (dest_num_components >= 4) {
|
||||
pixel[0].w = one;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PassAccessor::get_render_tile_pixels(const RenderBuffers *render_buffers,
|
||||
const BufferParams &buffer_params,
|
||||
const Destination &destination) const
|
||||
{
|
||||
if (render_buffers == nullptr || render_buffers->buffer.data() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const PassType type = pass_access_info_.type;
|
||||
const PassMode mode = pass_access_info_.mode;
|
||||
const PassInfo pass_info = Pass::get_info(
|
||||
type, mode, pass_access_info_.include_albedo, pass_access_info_.is_lightgroup);
|
||||
int num_written_components = pass_info.num_components;
|
||||
|
||||
if (pass_info.num_components == 1) {
|
||||
if (is_volume_guiding_pass(type)) {
|
||||
get_pass_rgbe(render_buffers, buffer_params, destination);
|
||||
num_written_components = 3;
|
||||
}
|
||||
/* Single channel passes. */
|
||||
else if (mode == PassMode::DENOISED) {
|
||||
/* Denoised passes store their final pixels, no need in special calculation. */
|
||||
get_pass_float(render_buffers, buffer_params, destination);
|
||||
}
|
||||
else if (type == PASS_DEPTH) {
|
||||
get_pass_depth(render_buffers, buffer_params, destination);
|
||||
}
|
||||
else if (type == PASS_MIST) {
|
||||
get_pass_mist(render_buffers, buffer_params, destination);
|
||||
}
|
||||
else if (type == PASS_VOLUME_MAJORANT) {
|
||||
get_pass_volume_majorant(render_buffers, buffer_params, destination);
|
||||
}
|
||||
else if (type == PASS_SAMPLE_COUNT) {
|
||||
get_pass_sample_count(render_buffers, buffer_params, destination);
|
||||
}
|
||||
else {
|
||||
get_pass_float(render_buffers, buffer_params, destination);
|
||||
}
|
||||
}
|
||||
else if (type == PASS_MOTION) {
|
||||
/* Motion pass. */
|
||||
DCHECK_EQ(destination.num_components, 4) << "Motion pass must have 4 components";
|
||||
get_pass_motion(render_buffers, buffer_params, destination);
|
||||
}
|
||||
else if (type == PASS_CRYPTOMATTE) {
|
||||
/* Cryptomatte pass. */
|
||||
DCHECK_EQ(destination.num_components, 4) << "Cryptomatte pass must have 4 components";
|
||||
get_pass_cryptomatte(render_buffers, buffer_params, destination);
|
||||
}
|
||||
else {
|
||||
/* RGB, RGBA and vector passes. */
|
||||
DCHECK(destination.num_components == 3 || destination.num_components == 4)
|
||||
<< pass_type_as_string(type) << " pass must have 3 or 4 components";
|
||||
|
||||
if (type == PASS_SHADOW_CATCHER_MATTE && pass_access_info_.use_approximate_shadow_catcher) {
|
||||
/* Denoised matte with shadow needs to do calculation (will use denoised shadow catcher pass
|
||||
* to approximate shadow with). */
|
||||
get_pass_shadow_catcher_matte_with_shadow(render_buffers, buffer_params, destination);
|
||||
}
|
||||
else if (type == PASS_SHADOW_CATCHER && mode != PassMode::DENOISED) {
|
||||
/* Shadow catcher pass. */
|
||||
get_pass_shadow_catcher(render_buffers, buffer_params, destination);
|
||||
}
|
||||
else if ((pass_info.divide_type != PASS_NONE || pass_info.direct_type != PASS_NONE ||
|
||||
pass_info.indirect_type != PASS_NONE) &&
|
||||
mode != PassMode::DENOISED)
|
||||
{
|
||||
/* RGB lighting passes that need to divide out color and/or sum direct and indirect.
|
||||
* These can also optionally write alpha like the combined pass. */
|
||||
get_pass_light_path(render_buffers, buffer_params, destination);
|
||||
num_written_components = 4;
|
||||
}
|
||||
else {
|
||||
/* Passes that need no special computation, or denoised passes that already
|
||||
* had the computation done. */
|
||||
if (pass_info.num_components == 3) {
|
||||
get_pass_float3(render_buffers, buffer_params, destination);
|
||||
|
||||
/* Use alpha for colors passes. */
|
||||
if (type == PASS_DIFFUSE_COLOR || type == PASS_GLOSSY_COLOR ||
|
||||
type == PASS_TRANSMISSION_COLOR)
|
||||
{
|
||||
num_written_components = destination.num_components;
|
||||
}
|
||||
}
|
||||
else if (pass_info.num_components == 4) {
|
||||
if (destination.num_components == 3) {
|
||||
/* Special case for denoiser access of RGBA passes ignoring alpha channel. */
|
||||
get_pass_float3(render_buffers, buffer_params, destination);
|
||||
}
|
||||
else if (type == PASS_COMBINED || type == PASS_SHADOW_CATCHER ||
|
||||
type == PASS_SHADOW_CATCHER_MATTE)
|
||||
{
|
||||
/* Passes with transparency as 4th component. */
|
||||
get_pass_combined(render_buffers, buffer_params, destination);
|
||||
}
|
||||
else {
|
||||
/* Passes with alpha as 4th component. */
|
||||
get_pass_float4(render_buffers, buffer_params, destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pad_pixels(buffer_params, destination, num_written_components);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PassAccessor::init_kernel_film_convert(KernelFilmConvert *kfilm_convert,
|
||||
const BufferParams &buffer_params,
|
||||
const Destination &destination) const
|
||||
{
|
||||
const PassType type = pass_access_info_.type;
|
||||
const PassMode mode = pass_access_info_.mode;
|
||||
const PassInfo pass_info = Pass::get_info(
|
||||
type, mode, pass_access_info_.include_albedo, pass_access_info_.is_lightgroup);
|
||||
|
||||
kfilm_convert->pass_offset = pass_access_info_.offset;
|
||||
kfilm_convert->pass_stride = buffer_params.pass_stride;
|
||||
|
||||
kfilm_convert->pass_use_exposure = pass_info.use_exposure;
|
||||
kfilm_convert->pass_use_filter = pass_info.use_filter;
|
||||
|
||||
/* TODO(sergey): Some of the passes needs to become denoised when denoised pass is accessed. */
|
||||
if (pass_info.direct_type != PASS_NONE) {
|
||||
kfilm_convert->pass_offset = buffer_params.get_pass_offset(pass_info.direct_type);
|
||||
}
|
||||
kfilm_convert->pass_indirect = buffer_params.get_pass_offset(pass_info.indirect_type);
|
||||
kfilm_convert->pass_divide = buffer_params.get_pass_offset(pass_info.divide_type);
|
||||
|
||||
kfilm_convert->pass_combined = buffer_params.get_pass_offset(PASS_COMBINED);
|
||||
kfilm_convert->pass_sample_count = buffer_params.get_pass_offset(PASS_SAMPLE_COUNT);
|
||||
kfilm_convert->pass_adaptive_aux_buffer = buffer_params.get_pass_offset(
|
||||
PASS_ADAPTIVE_AUX_BUFFER);
|
||||
kfilm_convert->pass_motion_weight = buffer_params.get_pass_offset(PASS_MOTION_WEIGHT);
|
||||
kfilm_convert->pass_shadow_catcher = buffer_params.get_pass_offset(PASS_SHADOW_CATCHER, mode);
|
||||
kfilm_convert->pass_shadow_catcher_sample_count = buffer_params.get_pass_offset(
|
||||
PASS_SHADOW_CATCHER_SAMPLE_COUNT);
|
||||
kfilm_convert->pass_shadow_catcher_matte = buffer_params.get_pass_offset(
|
||||
PASS_SHADOW_CATCHER_MATTE, mode);
|
||||
|
||||
/* Background is not denoised, so always use noisy pass. */
|
||||
kfilm_convert->pass_background = buffer_params.get_pass_offset(PASS_BACKGROUND);
|
||||
|
||||
/* If we have a sample count pass, we must perform the division in the kernel instead
|
||||
* (unless the sample count pass is the one being read). */
|
||||
const bool divide_by_samples = (type == PASS_SAMPLE_COUNT) ||
|
||||
(kfilm_convert->pass_sample_count == PASS_UNUSED);
|
||||
if (pass_info.use_filter && divide_by_samples) {
|
||||
kfilm_convert->scale = num_samples_ != 0 ? pass_info.scale / num_samples_ : 0.0f;
|
||||
}
|
||||
else {
|
||||
kfilm_convert->scale = pass_info.scale;
|
||||
|
||||
if (!pass_access_info_.use_sample_count) {
|
||||
kfilm_convert->pass_use_filter = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (pass_info.use_exposure) {
|
||||
kfilm_convert->exposure = exposure_;
|
||||
}
|
||||
else {
|
||||
kfilm_convert->exposure = 1.0f;
|
||||
}
|
||||
|
||||
kfilm_convert->scale_exposure = kfilm_convert->scale * kfilm_convert->exposure;
|
||||
|
||||
kfilm_convert->use_approximate_shadow_catcher = pass_access_info_.use_approximate_shadow_catcher;
|
||||
kfilm_convert->use_approximate_shadow_catcher_background =
|
||||
pass_access_info_.use_approximate_shadow_catcher_background;
|
||||
kfilm_convert->show_active_pixels = pass_access_info_.show_active_pixels;
|
||||
|
||||
kfilm_convert->num_components = destination.num_components;
|
||||
kfilm_convert->pixel_stride = destination.pixel_stride ? destination.pixel_stride :
|
||||
destination.num_components;
|
||||
|
||||
kfilm_convert->is_denoised = (mode == PassMode::DENOISED);
|
||||
}
|
||||
|
||||
bool PassAccessor::set_render_tile_pixels(RenderBuffers *render_buffers, const Source &source)
|
||||
{
|
||||
if (render_buffers == nullptr || render_buffers->buffer.data() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const PassInfo pass_info = Pass::get_info(pass_access_info_.type,
|
||||
pass_access_info_.mode,
|
||||
pass_access_info_.include_albedo,
|
||||
pass_access_info_.is_lightgroup);
|
||||
|
||||
const BufferParams &buffer_params = render_buffers->params;
|
||||
|
||||
float *buffer_data = render_buffers->buffer.data();
|
||||
const int size = buffer_params.width * buffer_params.height;
|
||||
|
||||
const int out_stride = buffer_params.pass_stride;
|
||||
const int in_stride = source.num_components;
|
||||
const int num_components_to_copy = min(source.num_components, pass_info.num_components);
|
||||
|
||||
float *out = buffer_data + pass_access_info_.offset;
|
||||
const float *in = source.pixels + source.offset * in_stride;
|
||||
|
||||
for (int i = 0; i < size; i++, out += out_stride, in += in_stride) {
|
||||
memcpy(out, in, sizeof(float) * num_components_to_copy);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
161
blender-5.2.0/intern/cycles/integrator/pass_accessor.h
Normal file
161
blender-5.2.0/intern/cycles/integrator/pass_accessor.h
Normal file
@@ -0,0 +1,161 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "scene/pass.h"
|
||||
#include "util/half.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
class RenderBuffers;
|
||||
class BufferPass;
|
||||
class BufferParams;
|
||||
struct KernelFilmConvert;
|
||||
|
||||
/* Helper class which allows to access pass data.
|
||||
* Is designed in a way that it is created once when the pass data is known, and then pixels gets
|
||||
* progressively update from various render buffers. */
|
||||
class PassAccessor {
|
||||
public:
|
||||
class PassAccessInfo {
|
||||
public:
|
||||
PassAccessInfo() = default;
|
||||
explicit PassAccessInfo(const BufferPass &pass);
|
||||
|
||||
PassType type = PASS_NONE;
|
||||
PassMode mode = PassMode::NOISY;
|
||||
bool include_albedo = false;
|
||||
bool is_lightgroup = false;
|
||||
int offset = -1;
|
||||
|
||||
bool use_sample_count = true;
|
||||
|
||||
/* For the shadow catcher matte pass: whether to approximate shadow catcher pass into its
|
||||
* matte pass, so that both artificial objects and shadows can be alpha-overed onto a backdrop.
|
||||
*/
|
||||
bool use_approximate_shadow_catcher = false;
|
||||
|
||||
/* When approximate shadow catcher matte is used alpha-over the result on top of background. */
|
||||
bool use_approximate_shadow_catcher_background = false;
|
||||
|
||||
bool show_active_pixels = false;
|
||||
};
|
||||
|
||||
class Destination {
|
||||
public:
|
||||
Destination() = default;
|
||||
Destination(float *pixels, const int num_components);
|
||||
|
||||
/* Destination will be initialized with the number of components which is native for the given
|
||||
* pass type. */
|
||||
explicit Destination(const PassType pass_type, const PassMode pass_mode);
|
||||
|
||||
/* CPU-side pointers. only usable by the `PassAccessorCPU`. */
|
||||
float *pixels = nullptr;
|
||||
half4 *pixels_half_rgba = nullptr;
|
||||
|
||||
/* Device-side pointers. */
|
||||
device_ptr d_pixels = 0;
|
||||
device_ptr d_pixels_half_rgba = 0;
|
||||
|
||||
/* Number of components per pixel in the floating-point destination.
|
||||
* Is ignored for half4 destination (where number of components is implied to be 4). */
|
||||
int num_components = 0;
|
||||
|
||||
/* Offset in pixels from the beginning of pixels storage.
|
||||
* Allows to get pixels of render buffer into a partial slice of the destination. */
|
||||
int offset = 0;
|
||||
|
||||
/* Offset in floats from the beginning of pixels storage.
|
||||
* Is ignored for half4 destination. */
|
||||
int pixel_offset = 0;
|
||||
|
||||
/* Number of floats per pixel. When zero is the same as `num_components`.
|
||||
*
|
||||
* NOTE: Is ignored for half4 destination, as the half4 pixels are always 4-component
|
||||
* half-floats. */
|
||||
int pixel_stride = 0;
|
||||
|
||||
/* Row stride in pixel elements:
|
||||
* - For the float destination stride is a number of floats per row.
|
||||
* - For the half4 destination stride is a number of half4 per row. */
|
||||
int stride = 0;
|
||||
};
|
||||
|
||||
class Source {
|
||||
public:
|
||||
Source() = default;
|
||||
Source(const float *pixels, const int num_components);
|
||||
|
||||
/* CPU-side pointers. only usable by the `PassAccessorCPU`. */
|
||||
const float *pixels = nullptr;
|
||||
int num_components = 0;
|
||||
|
||||
/* Offset in pixels from the beginning of pixels storage.
|
||||
* Allows to get pixels of render buffer into a partial slice of the destination. */
|
||||
int offset = 0;
|
||||
};
|
||||
|
||||
PassAccessor(const PassAccessInfo &pass_access_info,
|
||||
const float exposure,
|
||||
const int num_samples);
|
||||
|
||||
virtual ~PassAccessor() = default;
|
||||
|
||||
/* Get pass data from the given render buffers, perform needed filtering, and store result into
|
||||
* the pixels.
|
||||
* The result is stored sequentially starting from the very beginning of the pixels memory. */
|
||||
bool get_render_tile_pixels(const RenderBuffers *render_buffers,
|
||||
const Destination &destination) const;
|
||||
bool get_render_tile_pixels(const RenderBuffers *render_buffers,
|
||||
const BufferParams &buffer_params,
|
||||
const Destination &destination) const;
|
||||
/* Set pass data for the given render buffers. Used for baking to read from passes. */
|
||||
bool set_render_tile_pixels(RenderBuffers *render_buffers, const Source &source);
|
||||
|
||||
const PassAccessInfo &get_pass_access_info() const
|
||||
{
|
||||
return pass_access_info_;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void init_kernel_film_convert(KernelFilmConvert *kfilm_convert,
|
||||
const BufferParams &buffer_params,
|
||||
const Destination &destination) const;
|
||||
|
||||
#define DECLARE_PASS_ACCESSOR(pass) \
|
||||
virtual void get_pass_##pass(const RenderBuffers *render_buffers, \
|
||||
const BufferParams &buffer_params, \
|
||||
const Destination &destination) const = 0;
|
||||
|
||||
/* Float (scalar) passes. */
|
||||
DECLARE_PASS_ACCESSOR(depth)
|
||||
DECLARE_PASS_ACCESSOR(mist)
|
||||
DECLARE_PASS_ACCESSOR(volume_majorant)
|
||||
DECLARE_PASS_ACCESSOR(sample_count)
|
||||
DECLARE_PASS_ACCESSOR(float)
|
||||
|
||||
/* Float3 passes. */
|
||||
DECLARE_PASS_ACCESSOR(light_path)
|
||||
DECLARE_PASS_ACCESSOR(shadow_catcher)
|
||||
DECLARE_PASS_ACCESSOR(rgbe)
|
||||
DECLARE_PASS_ACCESSOR(float3)
|
||||
|
||||
/* Float4 passes. */
|
||||
DECLARE_PASS_ACCESSOR(motion)
|
||||
DECLARE_PASS_ACCESSOR(cryptomatte)
|
||||
DECLARE_PASS_ACCESSOR(shadow_catcher_matte_with_shadow)
|
||||
DECLARE_PASS_ACCESSOR(combined)
|
||||
DECLARE_PASS_ACCESSOR(float4)
|
||||
|
||||
#undef DECLARE_PASS_ACCESSOR
|
||||
|
||||
PassAccessInfo pass_access_info_;
|
||||
|
||||
float exposure_ = 0.0f;
|
||||
int num_samples_ = 0;
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
127
blender-5.2.0/intern/cycles/integrator/pass_accessor_cpu.cpp
Normal file
127
blender-5.2.0/intern/cycles/integrator/pass_accessor_cpu.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "device/device.h"
|
||||
|
||||
#include "integrator/pass_accessor_cpu.h"
|
||||
|
||||
#include "session/buffers.h"
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/tbb.h"
|
||||
|
||||
#include "kernel/types.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Kernel processing.
|
||||
*/
|
||||
|
||||
inline void PassAccessorCPU::run_get_pass_kernel_processor_float(
|
||||
const KernelFilmConvert *kfilm_convert,
|
||||
const RenderBuffers *render_buffers,
|
||||
const BufferParams &buffer_params,
|
||||
const Destination &destination,
|
||||
const CPUKernels::FilmConvertFunction func) const
|
||||
{
|
||||
/* NOTE: No overlays are applied since they are not used for final renders.
|
||||
* Can be supported via some sort of specialization to avoid code duplication. */
|
||||
|
||||
DCHECK_EQ(destination.stride, 0) << "Custom stride for float destination is not implemented.";
|
||||
|
||||
const int64_t pass_stride = buffer_params.pass_stride;
|
||||
const int64_t buffer_row_stride = buffer_params.stride * buffer_params.pass_stride;
|
||||
|
||||
const float *window_data = render_buffers->buffer.data() + buffer_params.window_x * pass_stride +
|
||||
buffer_params.window_y * buffer_row_stride;
|
||||
|
||||
const int pixel_stride = destination.pixel_stride ? destination.pixel_stride :
|
||||
destination.num_components;
|
||||
|
||||
parallel_for(0, buffer_params.window_height, [&](int64_t y) {
|
||||
const float *buffer = window_data + y * buffer_row_stride;
|
||||
float *pixel = destination.pixels + destination.pixel_offset +
|
||||
(y * buffer_params.window_width + destination.offset) * pixel_stride;
|
||||
func(kfilm_convert, buffer, pixel, buffer_params.window_width, pass_stride, pixel_stride);
|
||||
});
|
||||
}
|
||||
|
||||
inline void PassAccessorCPU::run_get_pass_kernel_processor_half_rgba(
|
||||
const KernelFilmConvert *kfilm_convert,
|
||||
const RenderBuffers *render_buffers,
|
||||
const BufferParams &buffer_params,
|
||||
const Destination &destination,
|
||||
const CPUKernels::FilmConvertHalfRGBAFunction func) const
|
||||
{
|
||||
const int64_t pass_stride = buffer_params.pass_stride;
|
||||
const int64_t buffer_row_stride = buffer_params.stride * buffer_params.pass_stride;
|
||||
|
||||
const float *window_data = render_buffers->buffer.data() + buffer_params.window_x * pass_stride +
|
||||
buffer_params.window_y * buffer_row_stride;
|
||||
|
||||
half4 *dst_start = destination.pixels_half_rgba + destination.offset;
|
||||
const int destination_stride = destination.stride != 0 ? destination.stride :
|
||||
buffer_params.window_width;
|
||||
|
||||
parallel_for(0, buffer_params.window_height, [&](int64_t y) {
|
||||
const float *buffer = window_data + y * buffer_row_stride;
|
||||
half4 *pixel = dst_start + y * destination_stride;
|
||||
func(kfilm_convert, buffer, pixel, buffer_params.window_width, pass_stride);
|
||||
});
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Pass accessors.
|
||||
*/
|
||||
|
||||
#define DEFINE_PASS_ACCESSOR(pass) \
|
||||
void PassAccessorCPU::get_pass_##pass(const RenderBuffers *render_buffers, \
|
||||
const BufferParams &buffer_params, \
|
||||
const Destination &destination) const \
|
||||
{ \
|
||||
const CPUKernels &kernels = Device::get_cpu_kernels(); \
|
||||
KernelFilmConvert kfilm_convert; \
|
||||
init_kernel_film_convert(&kfilm_convert, buffer_params, destination); \
|
||||
\
|
||||
if (destination.pixels) { \
|
||||
run_get_pass_kernel_processor_float(&kfilm_convert, \
|
||||
render_buffers, \
|
||||
buffer_params, \
|
||||
destination, \
|
||||
kernels.film_convert_##pass); \
|
||||
} \
|
||||
\
|
||||
if (destination.pixels_half_rgba) { \
|
||||
run_get_pass_kernel_processor_half_rgba(&kfilm_convert, \
|
||||
render_buffers, \
|
||||
buffer_params, \
|
||||
destination, \
|
||||
kernels.film_convert_half_rgba_##pass); \
|
||||
} \
|
||||
}
|
||||
|
||||
/* Float (scalar) passes. */
|
||||
DEFINE_PASS_ACCESSOR(depth)
|
||||
DEFINE_PASS_ACCESSOR(mist)
|
||||
DEFINE_PASS_ACCESSOR(volume_majorant)
|
||||
DEFINE_PASS_ACCESSOR(sample_count)
|
||||
DEFINE_PASS_ACCESSOR(float)
|
||||
|
||||
/* Float3 passes. */
|
||||
DEFINE_PASS_ACCESSOR(light_path)
|
||||
DEFINE_PASS_ACCESSOR(shadow_catcher)
|
||||
DEFINE_PASS_ACCESSOR(rgbe)
|
||||
DEFINE_PASS_ACCESSOR(float3)
|
||||
|
||||
/* Float4 passes. */
|
||||
DEFINE_PASS_ACCESSOR(motion)
|
||||
DEFINE_PASS_ACCESSOR(cryptomatte)
|
||||
DEFINE_PASS_ACCESSOR(shadow_catcher_matte_with_shadow)
|
||||
DEFINE_PASS_ACCESSOR(combined)
|
||||
DEFINE_PASS_ACCESSOR(float4)
|
||||
|
||||
#undef DEFINE_PASS_ACCESSOR
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
63
blender-5.2.0/intern/cycles/integrator/pass_accessor_cpu.h
Normal file
63
blender-5.2.0/intern/cycles/integrator/pass_accessor_cpu.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "device/cpu/kernel.h"
|
||||
|
||||
#include "integrator/pass_accessor.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
struct KernelFilmConvert;
|
||||
|
||||
/* Pass accessor implementation for CPU side. */
|
||||
class PassAccessorCPU : public PassAccessor {
|
||||
public:
|
||||
using PassAccessor::PassAccessor;
|
||||
|
||||
protected:
|
||||
inline void run_get_pass_kernel_processor_float(
|
||||
const KernelFilmConvert *kfilm_convert,
|
||||
const RenderBuffers *render_buffers,
|
||||
const BufferParams &buffer_params,
|
||||
const Destination &destination,
|
||||
const CPUKernels::FilmConvertFunction func) const;
|
||||
|
||||
inline void run_get_pass_kernel_processor_half_rgba(
|
||||
const KernelFilmConvert *kfilm_convert,
|
||||
const RenderBuffers *render_buffers,
|
||||
const BufferParams &buffer_params,
|
||||
const Destination &destination,
|
||||
const CPUKernels::FilmConvertHalfRGBAFunction func) const;
|
||||
|
||||
#define DECLARE_PASS_ACCESSOR(pass) \
|
||||
virtual void get_pass_##pass(const RenderBuffers *render_buffers, \
|
||||
const BufferParams &buffer_params, \
|
||||
const Destination &destination) const override;
|
||||
|
||||
/* Float (scalar) passes. */
|
||||
DECLARE_PASS_ACCESSOR(depth)
|
||||
DECLARE_PASS_ACCESSOR(mist)
|
||||
DECLARE_PASS_ACCESSOR(volume_majorant)
|
||||
DECLARE_PASS_ACCESSOR(sample_count)
|
||||
DECLARE_PASS_ACCESSOR(float)
|
||||
|
||||
/* Float3 passes. */
|
||||
DECLARE_PASS_ACCESSOR(light_path)
|
||||
DECLARE_PASS_ACCESSOR(shadow_catcher)
|
||||
DECLARE_PASS_ACCESSOR(rgbe)
|
||||
DECLARE_PASS_ACCESSOR(float3)
|
||||
|
||||
/* Float4 passes. */
|
||||
DECLARE_PASS_ACCESSOR(motion)
|
||||
DECLARE_PASS_ACCESSOR(cryptomatte)
|
||||
DECLARE_PASS_ACCESSOR(shadow_catcher_matte_with_shadow)
|
||||
DECLARE_PASS_ACCESSOR(combined)
|
||||
DECLARE_PASS_ACCESSOR(float4)
|
||||
|
||||
#undef DECLARE_PASS_ACCESSOR
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
112
blender-5.2.0/intern/cycles/integrator/pass_accessor_gpu.cpp
Normal file
112
blender-5.2.0/intern/cycles/integrator/pass_accessor_gpu.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "device/queue.h"
|
||||
|
||||
#include "integrator/pass_accessor_gpu.h"
|
||||
|
||||
#include "session/buffers.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
PassAccessorGPU::PassAccessorGPU(DeviceQueue *queue,
|
||||
const PassAccessInfo &pass_access_info,
|
||||
const float exposure,
|
||||
const int num_samples)
|
||||
: PassAccessor(pass_access_info, exposure, num_samples), queue_(queue)
|
||||
{
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Kernel execution.
|
||||
*/
|
||||
|
||||
void PassAccessorGPU::run_film_convert_kernels(DeviceKernel kernel,
|
||||
const RenderBuffers *render_buffers,
|
||||
const BufferParams &buffer_params,
|
||||
const Destination &destination) const
|
||||
{
|
||||
KernelFilmConvert kfilm_convert;
|
||||
init_kernel_film_convert(&kfilm_convert, buffer_params, destination);
|
||||
|
||||
const int work_size = buffer_params.window_width * buffer_params.window_height;
|
||||
|
||||
const int destination_stride = destination.stride != 0 ? destination.stride :
|
||||
buffer_params.window_width;
|
||||
|
||||
const int offset = buffer_params.window_x * buffer_params.pass_stride +
|
||||
buffer_params.window_y * buffer_params.stride * buffer_params.pass_stride;
|
||||
|
||||
queue_->init_execution();
|
||||
if (destination.d_pixels) {
|
||||
DCHECK_EQ(destination.stride, 0) << "Custom stride for float destination is not implemented.";
|
||||
|
||||
const DeviceKernelArguments args(&kfilm_convert,
|
||||
&destination.d_pixels,
|
||||
&render_buffers->buffer.device_pointer,
|
||||
&work_size,
|
||||
&buffer_params.window_width,
|
||||
&offset,
|
||||
&buffer_params.stride,
|
||||
&destination.pixel_offset,
|
||||
&destination.offset,
|
||||
&destination_stride);
|
||||
|
||||
queue_->enqueue(kernel, work_size, args);
|
||||
}
|
||||
if (destination.d_pixels_half_rgba) {
|
||||
const DeviceKernel kernel_half_float = static_cast<DeviceKernel>(kernel + 1);
|
||||
|
||||
const DeviceKernelArguments args(&kfilm_convert,
|
||||
&destination.d_pixels_half_rgba,
|
||||
&render_buffers->buffer.device_pointer,
|
||||
&work_size,
|
||||
&buffer_params.window_width,
|
||||
&offset,
|
||||
&buffer_params.stride,
|
||||
&destination.offset,
|
||||
&destination_stride);
|
||||
|
||||
queue_->enqueue(kernel_half_float, work_size, args);
|
||||
}
|
||||
|
||||
queue_->synchronize();
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Pass accessors.
|
||||
*/
|
||||
|
||||
#define DEFINE_PASS_ACCESSOR(pass, kernel_pass) \
|
||||
void PassAccessorGPU::get_pass_##pass(const RenderBuffers *render_buffers, \
|
||||
const BufferParams &buffer_params, \
|
||||
const Destination &destination) const \
|
||||
{ \
|
||||
run_film_convert_kernels( \
|
||||
DEVICE_KERNEL_FILM_CONVERT_##kernel_pass, render_buffers, buffer_params, destination); \
|
||||
}
|
||||
|
||||
/* Float (scalar) passes. */
|
||||
DEFINE_PASS_ACCESSOR(depth, DEPTH);
|
||||
DEFINE_PASS_ACCESSOR(mist, MIST);
|
||||
DEFINE_PASS_ACCESSOR(volume_majorant, VOLUME_MAJORANT);
|
||||
DEFINE_PASS_ACCESSOR(sample_count, SAMPLE_COUNT);
|
||||
DEFINE_PASS_ACCESSOR(float, FLOAT);
|
||||
|
||||
/* Float3 passes. */
|
||||
DEFINE_PASS_ACCESSOR(light_path, LIGHT_PATH);
|
||||
DEFINE_PASS_ACCESSOR(rgbe, RGBE);
|
||||
DEFINE_PASS_ACCESSOR(float3, FLOAT3);
|
||||
|
||||
/* Float4 passes. */
|
||||
DEFINE_PASS_ACCESSOR(motion, MOTION);
|
||||
DEFINE_PASS_ACCESSOR(cryptomatte, CRYPTOMATTE);
|
||||
DEFINE_PASS_ACCESSOR(shadow_catcher, SHADOW_CATCHER);
|
||||
DEFINE_PASS_ACCESSOR(shadow_catcher_matte_with_shadow, SHADOW_CATCHER_MATTE_WITH_SHADOW);
|
||||
DEFINE_PASS_ACCESSOR(combined, COMBINED);
|
||||
DEFINE_PASS_ACCESSOR(float4, FLOAT4);
|
||||
|
||||
#undef DEFINE_PASS_ACCESSOR
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
59
blender-5.2.0/intern/cycles/integrator/pass_accessor_gpu.h
Normal file
59
blender-5.2.0/intern/cycles/integrator/pass_accessor_gpu.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "integrator/pass_accessor.h"
|
||||
|
||||
#include "kernel/types.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
class DeviceQueue;
|
||||
|
||||
/* Pass accessor implementation for GPU side. */
|
||||
class PassAccessorGPU : public PassAccessor {
|
||||
public:
|
||||
PassAccessorGPU(DeviceQueue *queue,
|
||||
const PassAccessInfo &pass_access_info,
|
||||
const float exposure,
|
||||
int num_samples);
|
||||
|
||||
protected:
|
||||
void run_film_convert_kernels(DeviceKernel kernel,
|
||||
const RenderBuffers *render_buffers,
|
||||
const BufferParams &buffer_params,
|
||||
const Destination &destination) const;
|
||||
|
||||
#define DECLARE_PASS_ACCESSOR(pass) \
|
||||
virtual void get_pass_##pass(const RenderBuffers *render_buffers, \
|
||||
const BufferParams &buffer_params, \
|
||||
const Destination &destination) const override;
|
||||
|
||||
/* Float (scalar) passes. */
|
||||
DECLARE_PASS_ACCESSOR(depth);
|
||||
DECLARE_PASS_ACCESSOR(mist);
|
||||
DECLARE_PASS_ACCESSOR(volume_majorant);
|
||||
DECLARE_PASS_ACCESSOR(sample_count);
|
||||
DECLARE_PASS_ACCESSOR(float);
|
||||
|
||||
/* Float3 passes. */
|
||||
DECLARE_PASS_ACCESSOR(light_path);
|
||||
DECLARE_PASS_ACCESSOR(rgbe);
|
||||
DECLARE_PASS_ACCESSOR(float3);
|
||||
|
||||
/* Float4 passes. */
|
||||
DECLARE_PASS_ACCESSOR(motion);
|
||||
DECLARE_PASS_ACCESSOR(cryptomatte);
|
||||
DECLARE_PASS_ACCESSOR(shadow_catcher);
|
||||
DECLARE_PASS_ACCESSOR(shadow_catcher_matte_with_shadow);
|
||||
DECLARE_PASS_ACCESSOR(combined);
|
||||
DECLARE_PASS_ACCESSOR(float4);
|
||||
|
||||
#undef DECLARE_PASS_ACCESSOR
|
||||
|
||||
DeviceQueue *queue_;
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
1554
blender-5.2.0/intern/cycles/integrator/path_trace.cpp
Normal file
1554
blender-5.2.0/intern/cycles/integrator/path_trace.cpp
Normal file
File diff suppressed because it is too large
Load Diff
366
blender-5.2.0/intern/cycles/integrator/path_trace.h
Normal file
366
blender-5.2.0/intern/cycles/integrator/path_trace.h
Normal file
@@ -0,0 +1,366 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "integrator/denoiser.h"
|
||||
#include "integrator/guiding.h"
|
||||
#include "integrator/pass_accessor.h"
|
||||
#include "integrator/path_trace_work.h"
|
||||
#include "integrator/work_balancer.h"
|
||||
|
||||
#include "session/buffers.h"
|
||||
|
||||
#include "util/guiding.h" // IWYU pragma: keep
|
||||
#include "util/thread.h"
|
||||
#include "util/unique_ptr.h"
|
||||
#include "util/vector.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
class AdaptiveSampling;
|
||||
class Device;
|
||||
class DeviceScene;
|
||||
class DisplayDriver;
|
||||
class Film;
|
||||
class RenderBuffers;
|
||||
class RenderScheduler;
|
||||
class RenderWork;
|
||||
class PathTraceDisplay;
|
||||
class OutputDriver;
|
||||
class Progress;
|
||||
class TileManager;
|
||||
|
||||
/* PathTrace class takes care of kernel graph and scheduling on a (multi)device. It takes care of
|
||||
* all the common steps of path tracing which are not device-specific. The list of tasks includes
|
||||
* but is not limited to:
|
||||
* - Kernel graph.
|
||||
* - Scheduling logic.
|
||||
* - Queues management.
|
||||
* - Adaptive stopping. */
|
||||
class PathTrace {
|
||||
public:
|
||||
/* Render scheduler is used to report timing information and access things like start/finish
|
||||
* sample. */
|
||||
PathTrace(Device *device,
|
||||
Device *denoise_device,
|
||||
Film *film,
|
||||
DeviceScene *device_scene,
|
||||
RenderScheduler &render_scheduler,
|
||||
TileManager &tile_manager);
|
||||
~PathTrace();
|
||||
|
||||
/* Create devices and load kernels which are created on-demand (for example, denoising devices).
|
||||
* The progress is reported to the currently configure progress object (via `set_progress`). */
|
||||
void load_kernels();
|
||||
|
||||
/* Allocate working memory. This runs before allocating scene memory so that we can estimate
|
||||
* more accurately which scene device memory may need to allocated on the host. */
|
||||
void alloc_work_memory();
|
||||
|
||||
/* Check whether now it is a good time to reset rendering.
|
||||
* Used to avoid very often resets in the viewport, giving it a chance to draw intermediate
|
||||
* render result. */
|
||||
bool ready_to_reset();
|
||||
|
||||
void reset(const BufferParams &full_params,
|
||||
const BufferParams &big_tile_params,
|
||||
bool reset_rendering);
|
||||
|
||||
void device_free();
|
||||
|
||||
/* Set progress tracker.
|
||||
* Used to communicate details about the progress to the outer world, check whether rendering is
|
||||
* to be canceled.
|
||||
*
|
||||
* The path tracer writes to this object, and then at a convenient moment runs
|
||||
* progress_update_cb() callback. */
|
||||
void set_progress(Progress *progress);
|
||||
|
||||
/* NOTE: This is a blocking call. Meaning, it will not return until given number of samples are
|
||||
* rendered (or until rendering is requested to be canceled). */
|
||||
void render(const RenderWork &render_work);
|
||||
|
||||
/* TODO(sergey): Decide whether denoiser is really a part of path tracer. Currently it is
|
||||
* convenient to have it here because then its easy to access render buffer. But the downside is
|
||||
* that this adds too much of entities which can live separately with some clear API. */
|
||||
|
||||
/* Set denoiser parameters.
|
||||
* Use this to configure the denoiser before rendering any samples. */
|
||||
void set_denoiser_params(const DenoiseParams ¶ms);
|
||||
|
||||
/* Set parameters used for adaptive sampling.
|
||||
* Use this to configure the adaptive sampler before rendering any samples. */
|
||||
void set_adaptive_sampling(const AdaptiveSampling &adaptive_sampling);
|
||||
|
||||
/* Set the parameters for guiding.
|
||||
* Use to setup the guiding structures before each rendering iteration. */
|
||||
void set_guiding_params(const GuidingParams ¶ms, const bool reset);
|
||||
|
||||
/* Sets output driver for render buffer output. */
|
||||
void set_output_driver(unique_ptr<OutputDriver> driver);
|
||||
|
||||
/* Set display driver for interactive render buffer display. */
|
||||
void set_display_driver(unique_ptr<DisplayDriver> driver);
|
||||
|
||||
/* Clear the display buffer by filling it in with all zeroes. */
|
||||
void zero_display();
|
||||
|
||||
/* Perform drawing of the current state of the DisplayDriver. */
|
||||
void draw();
|
||||
|
||||
/* Flush outstanding display commands before ending the render loop. */
|
||||
void flush_display();
|
||||
|
||||
/* Cancel rendering process as soon as possible, without waiting for full tile to be sampled.
|
||||
* Used in cases like reset of render session.
|
||||
*
|
||||
* This is a blocking call, which returns as soon as there is no running `render_samples()` call.
|
||||
*/
|
||||
void cancel();
|
||||
|
||||
/* Copy an entire render buffer to/from the path trace. */
|
||||
|
||||
/* Copy happens via CPU side buffer: data will be copied from every device of the path trace, and
|
||||
* the data will be copied to the device of the given render buffers. */
|
||||
void copy_to_render_buffers(RenderBuffers *render_buffers);
|
||||
|
||||
/* Copy happens via CPU side buffer: data will be copied from the device of the given render
|
||||
* buffers and will be copied to all devices of the path trace. */
|
||||
void copy_from_render_buffers(RenderBuffers *render_buffers);
|
||||
|
||||
/* Copy render buffers of the big tile from the device to host.
|
||||
* Return true if all copies are successful. */
|
||||
bool copy_render_tile_from_device();
|
||||
|
||||
/* Read given full-frame file from disk, perform needed processing and write it to the software
|
||||
* via the write callback. */
|
||||
void process_full_buffer_from_disk(string_view filename);
|
||||
|
||||
/* Get number of samples in the current big tile render buffers. */
|
||||
int get_num_render_tile_samples() const;
|
||||
|
||||
/* Get pass data of the entire big tile.
|
||||
* This call puts pass render result from all devices into the final pixels storage.
|
||||
*
|
||||
* NOTE: Expects buffers to be copied to the host using `copy_render_tile_from_device()`.
|
||||
*
|
||||
* Returns false if any of the accessor's `get_render_tile_pixels()` returned false. */
|
||||
bool get_render_tile_pixels(const PassAccessor &pass_accessor,
|
||||
const PassAccessor::Destination &destination);
|
||||
|
||||
/* Set pass data for baking. */
|
||||
bool set_render_tile_pixels(PassAccessor &pass_accessor, const PassAccessor::Source &source);
|
||||
|
||||
/* Check whether denoiser was run and denoised passes are available. */
|
||||
bool has_denoised_result() const;
|
||||
|
||||
/* Get size and offset (relative to the buffer's full x/y) of the currently rendering tile.
|
||||
* In the case of tiled rendering this will return full-frame after all tiles has been rendered.
|
||||
*
|
||||
* NOTE: If the full-frame buffer processing is in progress, returns parameters of the full-frame
|
||||
* instead. */
|
||||
int2 get_render_tile_size() const;
|
||||
int2 get_render_tile_offset() const;
|
||||
int2 get_render_size() const;
|
||||
|
||||
/* Get buffer parameters of the current tile.
|
||||
*
|
||||
* NOTE: If the full-frame buffer processing is in progress, returns parameters of the full-frame
|
||||
* instead. */
|
||||
const BufferParams &get_render_tile_params() const;
|
||||
|
||||
/* Generate full multi-line report of the rendering process, including rendering parameters,
|
||||
* times, and so on. */
|
||||
string full_report() const;
|
||||
|
||||
/* Callback which is called to report current rendering progress.
|
||||
*
|
||||
* It is supposed to be cheaper than buffer update/write, hence can be called more often.
|
||||
* Additionally, it might be called form the middle of wavefront (meaning, it is not guaranteed
|
||||
* that the buffer is "uniformly" sampled at the moment of this callback). */
|
||||
std::function<void(void)> progress_update_cb;
|
||||
|
||||
protected:
|
||||
/* Actual implementation of the rendering pipeline.
|
||||
* Calls steps in order, checking for the cancel to be requested in between.
|
||||
*
|
||||
* Is separate from `render()` to simplify dealing with the early outputs and keeping
|
||||
* `render_cancel_` in the consistent state. */
|
||||
void render_pipeline(RenderWork render_work);
|
||||
|
||||
/* Initialize kernel execution on all integrator queues. */
|
||||
void render_init_kernel_execution();
|
||||
|
||||
/* Release kernel execution resources on all integrator queues. */
|
||||
void render_deinit_kernel_execution();
|
||||
|
||||
/* Make sure both allocated and effective buffer parameters of path tracer works are up to date
|
||||
* with the current big tile parameters, performance-dependent slicing, and resolution divider.
|
||||
*/
|
||||
void update_work_buffer_params_if_needed(const RenderWork &render_work);
|
||||
void update_allocated_work_buffer_params();
|
||||
void update_effective_work_buffer_params(const RenderWork &render_work);
|
||||
|
||||
/* Perform various steps of the render work.
|
||||
*
|
||||
* Note that some steps might modify the work, forcing some steps to happen within this iteration
|
||||
* of rendering. */
|
||||
void init_render_buffers(const RenderWork &render_work);
|
||||
void path_trace(RenderWork &render_work);
|
||||
void adaptive_sample(RenderWork &render_work);
|
||||
void denoise(const RenderWork &render_work);
|
||||
void denoise_volume_guiding_buffers(const RenderWork &render_work, const bool has_volume);
|
||||
void cryptomatte_postprocess(const RenderWork &render_work);
|
||||
void update_display(const RenderWork &render_work);
|
||||
void rebalance(const RenderWork &render_work);
|
||||
void write_tile_buffer(const RenderWork &render_work);
|
||||
void finalize_full_buffer_on_disk(const RenderWork &render_work);
|
||||
|
||||
/* Updates/initializes the guiding structures after a rendering iteration.
|
||||
* The structures are updated using the training data/samples generated during the previous
|
||||
* rendering iteration */
|
||||
void guiding_update_structures();
|
||||
|
||||
/* Prepares the per-kernel thread related guiding structures (e.g., PathSegmentStorage,
|
||||
* pointers to the global Field and SegmentStorage)*/
|
||||
void guiding_prepare_structures();
|
||||
|
||||
/* Get number of samples in the current state of the render buffers. */
|
||||
int get_num_samples_in_buffer();
|
||||
|
||||
/* Check whether user requested to cancel rendering, so that path tracing is to be finished as
|
||||
* soon as possible. */
|
||||
bool is_cancel_requested();
|
||||
|
||||
/* Write the big tile render buffer via the write callback. */
|
||||
void tile_buffer_write();
|
||||
|
||||
/* Read the big tile render buffer via the read callback. */
|
||||
void tile_buffer_read();
|
||||
|
||||
/* Write current tile into the file on disk. */
|
||||
void tile_buffer_write_to_disk();
|
||||
|
||||
/* Run the progress_update_cb callback if it is needed. */
|
||||
void progress_update_if_needed(const RenderWork &render_work);
|
||||
|
||||
void progress_set_status(const string &status, const string &substatus = "");
|
||||
|
||||
/* Destroy GPU resources (such as graphics interop) used by work. */
|
||||
void destroy_gpu_resources();
|
||||
|
||||
/* Pointer to a device which is configured to be used for path tracing. If multiple devices
|
||||
* are configured this is a `MultiDevice`. */
|
||||
Device *device_ = nullptr;
|
||||
|
||||
/* Pointer to a device which is configured to be used for denoising. Can be identical
|
||||
* to the device */
|
||||
Device *denoise_device_ = nullptr;
|
||||
|
||||
/* CPU device for creating temporary render buffers on the CPU side. */
|
||||
unique_ptr<Device> cpu_device_;
|
||||
|
||||
Film *film_;
|
||||
DeviceScene *device_scene_;
|
||||
|
||||
RenderScheduler &render_scheduler_;
|
||||
TileManager &tile_manager_;
|
||||
|
||||
/* Display driver for interactive render buffer display. */
|
||||
unique_ptr<PathTraceDisplay> display_;
|
||||
|
||||
/* Output driver to write render buffer to. */
|
||||
unique_ptr<OutputDriver> output_driver_;
|
||||
|
||||
/* Per-compute device descriptors of work which is responsible for path tracing on its configured
|
||||
* device. */
|
||||
vector<unique_ptr<PathTraceWork>> path_trace_works_;
|
||||
|
||||
/* Per-path trace work information needed for multi-device balancing. */
|
||||
vector<WorkBalanceInfo> work_balance_infos_;
|
||||
|
||||
/* Render buffer parameters of the full frame and current big tile. */
|
||||
BufferParams full_params_;
|
||||
BufferParams big_tile_params_;
|
||||
|
||||
/* Denoiser which takes care of denoising the big tile. */
|
||||
unique_ptr<Denoiser> denoiser_;
|
||||
|
||||
/* Denoiser device descriptor which holds the denoised big tile for multi-device workloads. */
|
||||
unique_ptr<PathTraceWork> big_tile_denoise_work_;
|
||||
|
||||
#if defined(WITH_PATH_GUIDING)
|
||||
/* Guiding related attributes */
|
||||
GuidingParams guiding_params_;
|
||||
|
||||
/* The guiding field which holds the representation of the incident radiance field for the
|
||||
* complete scene. */
|
||||
unique_ptr<openpgl::cpp::Field> guiding_field_;
|
||||
|
||||
/* The storage container which holds the training data/samples generated during the last
|
||||
* rendering iteration. */
|
||||
unique_ptr<openpgl::cpp::SampleStorage> guiding_sample_data_storage_;
|
||||
|
||||
/* The number of already performed training iterations for the guiding field. */
|
||||
int guiding_update_count = 0;
|
||||
#endif
|
||||
|
||||
/* State which is common for all the steps of the render work.
|
||||
* Is brought up to date in the `render()` call and is accessed from all the steps involved into
|
||||
* rendering the work. */
|
||||
struct {
|
||||
/* Denotes whether render buffers parameters of path trace works are to be reset for the new
|
||||
* value of the big tile parameters. */
|
||||
bool need_reset_params = false;
|
||||
|
||||
/* Divider of the resolution for faster previews.
|
||||
*
|
||||
* Allows to re-use same render buffer, but have less pixels rendered into in it. The way to
|
||||
* think of render buffer in this case is as an over-allocated array: the resolution divider
|
||||
* affects both resolution and stride as visible by the integrator kernels. */
|
||||
float resolution_divider = 0;
|
||||
|
||||
/* Parameters of the big tile with the current resolution divider applied. */
|
||||
BufferParams effective_big_tile_params;
|
||||
BufferParams effective_denoised_big_tile_params;
|
||||
|
||||
/* Denoiser was run and there are denoised versions of the passes in the render buffers. */
|
||||
bool has_denoised_result = false;
|
||||
|
||||
/* Current tile has been written (to either disk or callback.
|
||||
* Indicates that no more work will be done on this tile. */
|
||||
bool tile_written = false;
|
||||
} render_state_;
|
||||
|
||||
/* Progress object which is used to communicate sample progress. */
|
||||
Progress *progress_;
|
||||
|
||||
/* Fields required for canceling render on demand, as quickly as possible. */
|
||||
struct {
|
||||
/* Indicates whether there is an on-going `render_samples()` call. */
|
||||
bool is_rendering = false;
|
||||
|
||||
/* Indicates whether rendering is requested to be canceled by `cancel()`. */
|
||||
bool is_requested = false;
|
||||
|
||||
/* Synchronization between thread which does `render_samples()` and thread which does
|
||||
* `cancel()`. */
|
||||
thread_mutex mutex;
|
||||
thread_condition_variable condition;
|
||||
} render_cancel_;
|
||||
|
||||
/* Indicates whether a render result was drawn after latest session reset.
|
||||
* Used by `ready_to_reset()` to implement logic which feels the most interactive. */
|
||||
bool did_draw_after_reset_ = true;
|
||||
|
||||
/* State of the full frame processing and writing to the software. */
|
||||
struct {
|
||||
RenderBuffers *render_buffers = nullptr;
|
||||
} full_frame_state_;
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
264
blender-5.2.0/intern/cycles/integrator/path_trace_display.cpp
Normal file
264
blender-5.2.0/intern/cycles/integrator/path_trace_display.cpp
Normal file
@@ -0,0 +1,264 @@
|
||||
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "integrator/path_trace_display.h"
|
||||
|
||||
#include "session/buffers.h"
|
||||
#include "session/display_driver.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
PathTraceDisplay::PathTraceDisplay(unique_ptr<DisplayDriver> driver) : driver_(std::move(driver))
|
||||
{
|
||||
}
|
||||
|
||||
void PathTraceDisplay::reset(const BufferParams &buffer_params, const bool reset_rendering)
|
||||
{
|
||||
const thread_scoped_lock lock(mutex_);
|
||||
|
||||
params_.full_offset = make_int2(buffer_params.full_x + buffer_params.window_x,
|
||||
buffer_params.full_y + buffer_params.window_y);
|
||||
params_.full_size = make_int2(buffer_params.full_width, buffer_params.full_height);
|
||||
params_.size = make_int2(buffer_params.window_width, buffer_params.window_height);
|
||||
|
||||
texture_state_.is_outdated = true;
|
||||
|
||||
if (!reset_rendering) {
|
||||
driver_->next_tile_begin();
|
||||
}
|
||||
}
|
||||
|
||||
void PathTraceDisplay::mark_texture_updated()
|
||||
{
|
||||
texture_state_.is_outdated = false;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Update procedure.
|
||||
*/
|
||||
|
||||
bool PathTraceDisplay::update_begin(const int texture_width, const 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;
|
||||
{
|
||||
const 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,
|
||||
const int texture_x,
|
||||
const int texture_y,
|
||||
const int pixels_width,
|
||||
const 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.
|
||||
*/
|
||||
|
||||
GraphicsInteropDevice PathTraceDisplay::graphics_interop_get_device()
|
||||
{
|
||||
return driver_->graphics_interop_get_device();
|
||||
}
|
||||
|
||||
GraphicsInteropBuffer &PathTraceDisplay::graphics_interop_get_buffer()
|
||||
{
|
||||
GraphicsInteropBuffer &interop_buffer = driver_->graphics_interop_get_buffer();
|
||||
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.";
|
||||
interop_buffer.clear();
|
||||
return interop_buffer;
|
||||
}
|
||||
|
||||
if (!update_state_.is_active) {
|
||||
LOG_ERROR << "Attempt to use graphics interoperability outside of PathTraceDisplay update.";
|
||||
interop_buffer.clear();
|
||||
return interop_buffer;
|
||||
}
|
||||
|
||||
/* Assume that interop will write new values to the texture. */
|
||||
mark_texture_updated();
|
||||
|
||||
driver_->graphics_interop_update_buffer();
|
||||
return interop_buffer;
|
||||
}
|
||||
|
||||
void PathTraceDisplay::graphics_interop_activate()
|
||||
{
|
||||
driver_->graphics_interop_activate();
|
||||
}
|
||||
|
||||
void PathTraceDisplay::graphics_interop_deactivate()
|
||||
{
|
||||
driver_->graphics_interop_deactivate();
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Drawing.
|
||||
*/
|
||||
|
||||
void PathTraceDisplay::zero()
|
||||
{
|
||||
driver_->zero();
|
||||
}
|
||||
|
||||
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_outdated;
|
||||
|
||||
{
|
||||
const thread_scoped_lock lock(mutex_);
|
||||
params = params_;
|
||||
is_outdated = texture_state_.is_outdated;
|
||||
}
|
||||
|
||||
driver_->draw(params);
|
||||
|
||||
return !is_outdated;
|
||||
}
|
||||
|
||||
void PathTraceDisplay::flush()
|
||||
{
|
||||
driver_->flush();
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
189
blender-5.2.0/intern/cycles/integrator/path_trace_display.h
Normal file
189
blender-5.2.0/intern/cycles/integrator/path_trace_display.h
Normal file
@@ -0,0 +1,189 @@
|
||||
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "session/display_driver.h"
|
||||
|
||||
#include "util/half.h"
|
||||
#include "util/thread.h"
|
||||
#include "util/unique_ptr.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
class BufferParams;
|
||||
|
||||
/* PathTraceDisplay is used for efficient render buffer display.
|
||||
*
|
||||
* The host applications implements a DisplayDriver, storing a render pass in a GPU-side
|
||||
* textures. This texture is continuously updated by the path tracer and drawn by the host
|
||||
* application.
|
||||
*
|
||||
* PathTraceDisplay is a wrapper around the DisplayDriver, adding thread safety, state tracking
|
||||
* and error checking. */
|
||||
|
||||
class PathTraceDisplay {
|
||||
public:
|
||||
explicit PathTraceDisplay(unique_ptr<DisplayDriver> driver);
|
||||
virtual ~PathTraceDisplay() = 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.
|
||||
*
|
||||
* When the `reset_rendering` a complete display reset happens. When it is false reset happens
|
||||
* for a new state of the buffer parameters which is assumed to correspond to the next tile. */
|
||||
void reset(const BufferParams &buffer_params, bool reset_rendering);
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* 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(const int texture_width, const 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 PathTraceDisplay 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 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,
|
||||
const int texture_x,
|
||||
const int texture_y,
|
||||
const int pixels_width,
|
||||
const 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 PathTraceDisplay 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_buffer()` is not allowed between `map_texture_buffer()` and
|
||||
* `unmap_texture_buffer()` calls. */
|
||||
|
||||
/* Get PathTraceDisplay graphics interoperability information which acts as a destination for the
|
||||
* device API. */
|
||||
GraphicsInteropDevice graphics_interop_get_device();
|
||||
GraphicsInteropBuffer &graphics_interop_get_buffer();
|
||||
|
||||
/* (De)activate GPU display for graphics interoperability outside of regular display update
|
||||
* routines. */
|
||||
void graphics_interop_activate();
|
||||
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 `GraphicsInterop`. */
|
||||
void zero();
|
||||
|
||||
/* Draw the current state of the texture.
|
||||
*
|
||||
* Returns true if this call did draw an updated state of the texture. */
|
||||
bool draw();
|
||||
|
||||
/* Flush outstanding display commands before ending the render loop. */
|
||||
void flush();
|
||||
|
||||
private:
|
||||
/* Display driver implemented by the host application. */
|
||||
unique_ptr<DisplayDriver> driver_;
|
||||
|
||||
/* Current display parameters */
|
||||
thread_mutex mutex_;
|
||||
DisplayDriver::Params 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 {
|
||||
/* 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
|
||||
103
blender-5.2.0/intern/cycles/integrator/path_trace_tile.cpp
Normal file
103
blender-5.2.0/intern/cycles/integrator/path_trace_tile.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "integrator/path_trace_tile.h"
|
||||
#include "integrator/pass_accessor_cpu.h"
|
||||
#include "integrator/path_trace.h"
|
||||
|
||||
#include "scene/pass.h"
|
||||
#include "session/buffers.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
PathTraceTile::PathTraceTile(PathTrace &path_trace)
|
||||
: OutputDriver::Tile(path_trace.get_render_tile_offset(),
|
||||
path_trace.get_render_tile_size(),
|
||||
path_trace.get_render_size(),
|
||||
path_trace.get_render_tile_params().layer,
|
||||
path_trace.get_render_tile_params().view),
|
||||
path_trace_(path_trace),
|
||||
copied_from_device_(false)
|
||||
{
|
||||
}
|
||||
|
||||
bool PathTraceTile::get_pass_pixels(const string_view pass_name,
|
||||
const int num_channels,
|
||||
float *pixels) const
|
||||
{
|
||||
/* NOTE: The code relies on a fact that session is fully update and no scene/buffer modification
|
||||
* is happening while this function runs. */
|
||||
|
||||
if (!copied_from_device_) {
|
||||
/* Copy from device on demand. */
|
||||
path_trace_.copy_render_tile_from_device();
|
||||
copied_from_device_ = true;
|
||||
}
|
||||
|
||||
const BufferParams &buffer_params = path_trace_.get_render_tile_params();
|
||||
|
||||
const BufferPass *pass = buffer_params.find_pass(pass_name);
|
||||
if (pass == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool has_denoised_result = path_trace_.has_denoised_result() ||
|
||||
is_volume_guiding_pass(pass->type);
|
||||
if (pass->mode == PassMode::DENOISED && !has_denoised_result) {
|
||||
pass = buffer_params.find_pass(pass->type);
|
||||
if (pass == nullptr) {
|
||||
/* Happens when denoised result pass is requested but is never written by the kernel. */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
pass = buffer_params.get_actual_display_pass(pass);
|
||||
if (pass == nullptr) {
|
||||
/* Happens when interactive session changes display pass but render
|
||||
* buffer does not contain it yet. */
|
||||
return false;
|
||||
}
|
||||
|
||||
const float exposure = buffer_params.exposure;
|
||||
const int num_samples = path_trace_.get_num_render_tile_samples();
|
||||
|
||||
PassAccessor::PassAccessInfo pass_access_info(*pass);
|
||||
pass_access_info.use_approximate_shadow_catcher = buffer_params.use_approximate_shadow_catcher;
|
||||
pass_access_info.use_approximate_shadow_catcher_background =
|
||||
pass_access_info.use_approximate_shadow_catcher && !buffer_params.use_transparent_background;
|
||||
|
||||
const PassAccessorCPU pass_accessor(pass_access_info, exposure, num_samples);
|
||||
const PassAccessor::Destination destination(pixels, num_channels);
|
||||
|
||||
return path_trace_.get_render_tile_pixels(pass_accessor, destination);
|
||||
}
|
||||
|
||||
bool PathTraceTile::set_pass_pixels(const string_view pass_name,
|
||||
const int num_channels,
|
||||
const float *pixels) const
|
||||
{
|
||||
/* NOTE: The code relies on a fact that session is fully update and no scene/buffer modification
|
||||
* is happening while this function runs. */
|
||||
|
||||
const BufferParams &buffer_params = path_trace_.get_render_tile_params();
|
||||
const BufferPass *pass = buffer_params.find_pass(pass_name);
|
||||
if (!pass) {
|
||||
return false;
|
||||
}
|
||||
if (pass->offset == PASS_UNUSED) {
|
||||
/* Happens when attempting to set pixels of a pass with compositing when baking. */
|
||||
return false;
|
||||
}
|
||||
|
||||
const float exposure = buffer_params.exposure;
|
||||
const int num_samples = 1;
|
||||
|
||||
const PassAccessor::PassAccessInfo pass_access_info(*pass);
|
||||
PassAccessorCPU pass_accessor(pass_access_info, exposure, num_samples);
|
||||
const PassAccessor::Source source(pixels, num_channels);
|
||||
|
||||
return path_trace_.set_render_tile_pixels(pass_accessor, source);
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
33
blender-5.2.0/intern/cycles/integrator/path_trace_tile.h
Normal file
33
blender-5.2.0/intern/cycles/integrator/path_trace_tile.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "session/output_driver.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* PathTraceTile
|
||||
*
|
||||
* Implementation of OutputDriver::Tile interface for path tracer. */
|
||||
|
||||
class PathTrace;
|
||||
|
||||
class PathTraceTile : public OutputDriver::Tile {
|
||||
public:
|
||||
PathTraceTile(PathTrace &path_trace);
|
||||
|
||||
bool get_pass_pixels(const string_view pass_name,
|
||||
const int num_channels,
|
||||
float *pixels) const override;
|
||||
bool set_pass_pixels(const string_view pass_name,
|
||||
const int num_channels,
|
||||
const float *pixels) const override;
|
||||
|
||||
private:
|
||||
PathTrace &path_trace_;
|
||||
mutable bool copied_from_device_;
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
230
blender-5.2.0/intern/cycles/integrator/path_trace_work.cpp
Normal file
230
blender-5.2.0/intern/cycles/integrator/path_trace_work.cpp
Normal file
@@ -0,0 +1,230 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "device/device.h"
|
||||
|
||||
#include "integrator/path_trace_display.h"
|
||||
#include "integrator/path_trace_work.h"
|
||||
#include "integrator/path_trace_work_cpu.h"
|
||||
#include "integrator/path_trace_work_gpu.h"
|
||||
|
||||
#include "scene/film.h"
|
||||
#include "scene/scene.h"
|
||||
|
||||
#include "session/buffers.h"
|
||||
|
||||
#include "kernel/types.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
unique_ptr<PathTraceWork> PathTraceWork::create(Device *device,
|
||||
Film *film,
|
||||
DeviceScene *device_scene,
|
||||
const bool *cancel_requested_flag)
|
||||
{
|
||||
if (device->info.type == DEVICE_CPU) {
|
||||
return make_unique<PathTraceWorkCPU>(device, film, device_scene, cancel_requested_flag);
|
||||
}
|
||||
if (device->info.type == DEVICE_DUMMY) {
|
||||
/* Dummy devices can't perform any work. */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return make_unique<PathTraceWorkGPU>(device, film, device_scene, cancel_requested_flag);
|
||||
}
|
||||
|
||||
PathTraceWork::PathTraceWork(Device *device,
|
||||
Film *film,
|
||||
DeviceScene *device_scene,
|
||||
const bool *cancel_requested_flag)
|
||||
: device_(device),
|
||||
film_(film),
|
||||
device_scene_(device_scene),
|
||||
buffers_(make_unique<RenderBuffers>(device)),
|
||||
effective_buffer_params_(buffers_->params),
|
||||
effective_denoised_buffer_params_(buffers_->params),
|
||||
cancel_requested_flag_(cancel_requested_flag)
|
||||
{
|
||||
}
|
||||
|
||||
PathTraceWork::~PathTraceWork() = default;
|
||||
|
||||
RenderBuffers *PathTraceWork::get_render_buffers()
|
||||
{
|
||||
return buffers_.get();
|
||||
}
|
||||
|
||||
void PathTraceWork::set_effective_buffer_params(
|
||||
const BufferParams &effective_big_tile_params,
|
||||
const BufferParams &effective_buffer_params,
|
||||
const BufferParams &effective_denoised_big_tile_params,
|
||||
const BufferParams &effective_denoised_buffer_params)
|
||||
{
|
||||
effective_big_tile_params_ = effective_big_tile_params;
|
||||
effective_buffer_params_ = effective_buffer_params;
|
||||
effective_denoised_big_tile_params_ = effective_denoised_big_tile_params;
|
||||
effective_denoised_buffer_params_ = effective_denoised_buffer_params;
|
||||
}
|
||||
|
||||
bool PathTraceWork::has_multiple_works() const
|
||||
{
|
||||
/* Assume if there are multiple works working on the same big tile none of the works gets the
|
||||
* entire big tile to work on. */
|
||||
return !(effective_big_tile_params_.width == effective_buffer_params_.width &&
|
||||
effective_big_tile_params_.height == effective_buffer_params_.height &&
|
||||
effective_big_tile_params_.full_x == effective_buffer_params_.full_x &&
|
||||
effective_big_tile_params_.full_y == effective_buffer_params_.full_y);
|
||||
}
|
||||
|
||||
void PathTraceWork::copy_to_render_buffers(RenderBuffers *render_buffers)
|
||||
{
|
||||
copy_render_buffers_from_device();
|
||||
|
||||
const int64_t width = effective_buffer_params_.width;
|
||||
const int64_t height = effective_buffer_params_.height;
|
||||
const int64_t pass_stride = effective_buffer_params_.pass_stride;
|
||||
const int64_t row_stride = width * pass_stride;
|
||||
const int64_t data_size = row_stride * height * sizeof(float);
|
||||
|
||||
const int64_t offset_y = effective_buffer_params_.full_y - effective_big_tile_params_.full_y;
|
||||
const int64_t offset_in_floats = offset_y * row_stride;
|
||||
|
||||
const float *src = buffers_->buffer.data();
|
||||
float *dst = render_buffers->buffer.data() + offset_in_floats;
|
||||
|
||||
memcpy(dst, src, data_size);
|
||||
}
|
||||
|
||||
void PathTraceWork::copy_from_render_buffers(const RenderBuffers *render_buffers)
|
||||
{
|
||||
const int64_t width = effective_buffer_params_.width;
|
||||
const int64_t height = effective_buffer_params_.height;
|
||||
const int64_t pass_stride = effective_buffer_params_.pass_stride;
|
||||
const int64_t row_stride = width * pass_stride;
|
||||
const int64_t data_size = row_stride * height * sizeof(float);
|
||||
|
||||
const int64_t offset_y = effective_buffer_params_.full_y - effective_big_tile_params_.full_y;
|
||||
const int64_t offset_in_floats = offset_y * row_stride;
|
||||
|
||||
const float *src = render_buffers->buffer.data() + offset_in_floats;
|
||||
float *dst = buffers_->buffer.data();
|
||||
|
||||
memcpy(dst, src, data_size);
|
||||
|
||||
copy_render_buffers_to_device();
|
||||
}
|
||||
|
||||
void PathTraceWork::copy_from_denoised_render_buffers(const RenderBuffers *render_buffers)
|
||||
{
|
||||
const int64_t width = effective_denoised_buffer_params_.width;
|
||||
const int64_t offset_y = effective_denoised_buffer_params_.full_y -
|
||||
effective_denoised_big_tile_params_.full_y;
|
||||
const int64_t offset = offset_y * width;
|
||||
|
||||
render_buffers_host_copy_denoised(buffers_.get(),
|
||||
effective_denoised_buffer_params_,
|
||||
render_buffers,
|
||||
effective_denoised_buffer_params_,
|
||||
offset);
|
||||
|
||||
copy_render_buffers_to_device();
|
||||
}
|
||||
|
||||
bool PathTraceWork::get_render_tile_pixels(const PassAccessor &pass_accessor,
|
||||
const PassAccessor::Destination &destination)
|
||||
{
|
||||
const int offset_y = (effective_buffer_params_.full_y + effective_buffer_params_.window_y) -
|
||||
(effective_big_tile_params_.full_y + effective_big_tile_params_.window_y);
|
||||
const int width = effective_buffer_params_.width;
|
||||
|
||||
PassAccessor::Destination slice_destination = destination;
|
||||
slice_destination.offset += offset_y * width;
|
||||
|
||||
return pass_accessor.get_render_tile_pixels(buffers_.get(), slice_destination);
|
||||
}
|
||||
|
||||
bool PathTraceWork::set_render_tile_pixels(PassAccessor &pass_accessor,
|
||||
const PassAccessor::Source &source)
|
||||
{
|
||||
const int offset_y = effective_buffer_params_.full_y - effective_big_tile_params_.full_y;
|
||||
const int width = effective_buffer_params_.width;
|
||||
|
||||
PassAccessor::Source slice_source = source;
|
||||
slice_source.offset += offset_y * width;
|
||||
|
||||
return pass_accessor.set_render_tile_pixels(buffers_.get(), slice_source);
|
||||
}
|
||||
|
||||
PassAccessor::PassAccessInfo PathTraceWork::get_display_pass_access_info(PassMode pass_mode) const
|
||||
{
|
||||
const KernelFilm &kfilm = device_scene_->data.film;
|
||||
const KernelBackground &kbackground = device_scene_->data.background;
|
||||
|
||||
const BufferParams ¶ms = buffers_->params;
|
||||
|
||||
const BufferPass *display_pass = params.get_actual_display_pass(film_->get_display_pass());
|
||||
if (display_pass == nullptr) {
|
||||
/* Happens when interactive session changes display pass but render
|
||||
* buffer does not contain it yet. */
|
||||
return PassAccessor::PassAccessInfo();
|
||||
}
|
||||
|
||||
PassAccessor::PassAccessInfo pass_access_info;
|
||||
pass_access_info.type = display_pass->type;
|
||||
pass_access_info.offset = PASS_UNUSED;
|
||||
|
||||
if (pass_mode == PassMode::DENOISED) {
|
||||
pass_access_info.mode = PassMode::DENOISED;
|
||||
pass_access_info.offset = params.get_pass_offset(pass_access_info.type, PassMode::DENOISED);
|
||||
}
|
||||
|
||||
if (pass_access_info.offset == PASS_UNUSED) {
|
||||
pass_access_info.mode = PassMode::NOISY;
|
||||
pass_access_info.offset = params.get_pass_offset(pass_access_info.type);
|
||||
}
|
||||
|
||||
pass_access_info.use_approximate_shadow_catcher = kfilm.use_approximate_shadow_catcher;
|
||||
pass_access_info.use_approximate_shadow_catcher_background =
|
||||
kfilm.use_approximate_shadow_catcher && !kbackground.transparent;
|
||||
|
||||
if (pass_access_info.mode == PassMode::DENOISED &&
|
||||
(effective_denoised_buffer_params_.width != effective_buffer_params_.width ||
|
||||
effective_denoised_buffer_params_.height != effective_buffer_params_.height))
|
||||
{
|
||||
/* Avoid using sample count to filter pass after upscaling, since it is stored at a different
|
||||
* resolution. The denoiser should have applied scaling again in this case. */
|
||||
pass_access_info.use_sample_count = false;
|
||||
pass_access_info.use_approximate_shadow_catcher_background = false;
|
||||
}
|
||||
|
||||
pass_access_info.show_active_pixels = film_->get_show_active_pixels();
|
||||
|
||||
return pass_access_info;
|
||||
}
|
||||
|
||||
PassAccessor::Destination PathTraceWork::get_display_destination_template(
|
||||
const PathTraceDisplay *display, const PassMode mode) const
|
||||
{
|
||||
PassAccessor::Destination destination(film_->get_display_pass(), mode);
|
||||
|
||||
const BufferParams &effective_big_tile_params = (mode == PassMode::DENOISED) ?
|
||||
effective_denoised_big_tile_params_ :
|
||||
effective_big_tile_params_;
|
||||
const BufferParams &effective_buffer_params = (mode == PassMode::DENOISED) ?
|
||||
effective_denoised_buffer_params_ :
|
||||
effective_buffer_params_;
|
||||
|
||||
const int2 display_texture_size = display->get_texture_size();
|
||||
const int texture_x = effective_buffer_params.full_x - effective_big_tile_params.full_x +
|
||||
effective_buffer_params.window_x - effective_big_tile_params.window_x;
|
||||
const int texture_y = effective_buffer_params.full_y - effective_big_tile_params.full_y +
|
||||
effective_buffer_params.window_y - effective_big_tile_params.window_y;
|
||||
|
||||
destination.offset = texture_y * display_texture_size.x + texture_x;
|
||||
destination.stride = display_texture_size.x;
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
202
blender-5.2.0/intern/cycles/integrator/path_trace_work.h
Normal file
202
blender-5.2.0/intern/cycles/integrator/path_trace_work.h
Normal file
@@ -0,0 +1,202 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "integrator/pass_accessor.h"
|
||||
#include "scene/pass.h"
|
||||
#include "session/buffers.h"
|
||||
#include "util/unique_ptr.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
class BufferParams;
|
||||
class Device;
|
||||
class DeviceScene;
|
||||
class Film;
|
||||
class PathTraceDisplay;
|
||||
class RenderBuffers;
|
||||
|
||||
class PathTraceWork {
|
||||
public:
|
||||
struct RenderStatistics {
|
||||
float occupancy = 1.0f;
|
||||
};
|
||||
|
||||
/* Create path trace work which fits best the device.
|
||||
*
|
||||
* The cancel request flag is used for a cheap check whether cancel is to be performed as soon as
|
||||
* possible. This could be, for example, request to cancel rendering on camera navigation in
|
||||
* viewport. */
|
||||
static unique_ptr<PathTraceWork> create(Device *device,
|
||||
Film *film,
|
||||
DeviceScene *device_scene,
|
||||
const bool *cancel_requested_flag);
|
||||
|
||||
virtual ~PathTraceWork();
|
||||
|
||||
/* Access the render buffers.
|
||||
*
|
||||
* Is only supposed to be used by the PathTrace to update buffer allocation and slicing to
|
||||
* correspond to the big tile size and relative device performance. */
|
||||
RenderBuffers *get_render_buffers();
|
||||
|
||||
/* Set effective parameters of the big tile and the work itself. */
|
||||
void set_effective_buffer_params(const BufferParams &effective_big_tile_params,
|
||||
const BufferParams &effective_buffer_params,
|
||||
const BufferParams &effective_denoised_big_tile_params,
|
||||
const BufferParams &effective_denoised_buffer_params);
|
||||
|
||||
/* Check whether the big tile is being worked on by multiple path trace works. */
|
||||
bool has_multiple_works() const;
|
||||
|
||||
/* Allocate working memory for execution. Must be called before init_execution(). */
|
||||
virtual void alloc_work_memory() {};
|
||||
|
||||
/* Initialize execution of kernels.
|
||||
* Will ensure that all device queues are initialized for execution.
|
||||
*
|
||||
* This method is to be called after any change in the scene. It is not needed to call it prior
|
||||
* to an every call of the `render_samples()`. */
|
||||
virtual void init_execution() = 0;
|
||||
|
||||
/* Release resources acquired by init_execution(). */
|
||||
virtual void deinit_execution() {}
|
||||
|
||||
/* Render given number of samples as a synchronous blocking call.
|
||||
* The samples are added to the render buffer associated with this work. */
|
||||
virtual void render_samples(RenderStatistics &statistics,
|
||||
const int start_sample,
|
||||
const int samples_num,
|
||||
const int sample_offset) = 0;
|
||||
|
||||
/* Copy render result from this work to the corresponding place of the GPU display.
|
||||
*
|
||||
* The `pass_mode` indicates whether to access denoised or noisy version of the display pass. The
|
||||
* noisy pass mode will be passed here when it is known that the buffer does not have denoised
|
||||
* passes yet (because denoiser did not run). If the denoised pass is requested and denoiser is
|
||||
* not used then this function will fall-back to the noisy pass instead. */
|
||||
virtual void copy_to_display(PathTraceDisplay *display,
|
||||
PassMode pass_mode,
|
||||
const int num_samples) = 0;
|
||||
|
||||
virtual void destroy_gpu_resources(PathTraceDisplay *display) = 0;
|
||||
|
||||
/* Copy data from/to given render buffers.
|
||||
* Will copy pixels from a corresponding place (from multi-device point of view) of the render
|
||||
* buffers, and copy work's render buffers to the corresponding place of the destination. */
|
||||
|
||||
/* Notes:
|
||||
* - Copies work's render buffer from the device.
|
||||
* - Copies CPU-side buffer of the given buffer
|
||||
* - Does not copy the buffer to its device. */
|
||||
void copy_to_render_buffers(RenderBuffers *render_buffers);
|
||||
|
||||
/* Notes:
|
||||
* - Does not copy given render buffers from the device.
|
||||
* - Copies work's render buffer to its device. */
|
||||
void copy_from_render_buffers(const RenderBuffers *render_buffers);
|
||||
|
||||
/* Special version of the `copy_from_render_buffers()` which only copies denoised passes from the
|
||||
* given render buffers, leaving rest of the passes.
|
||||
*
|
||||
* Same notes about device copying applies to this call as well. */
|
||||
void copy_from_denoised_render_buffers(const RenderBuffers *render_buffers);
|
||||
|
||||
/* Copy render buffers to/from device using an appropriate device queue when needed so that
|
||||
* things are executed in order with the `render_samples()`. */
|
||||
virtual bool copy_render_buffers_from_device() = 0;
|
||||
virtual bool copy_render_buffers_to_device() = 0;
|
||||
|
||||
/* Zero render buffers to/from device using an appropriate device queue when needed so that
|
||||
* things are executed in order with the `render_samples()`. */
|
||||
virtual bool zero_render_buffers() = 0;
|
||||
|
||||
/* Access pixels rendered by this work and copy them to the corresponding location in the
|
||||
* destination.
|
||||
*
|
||||
* NOTE: Does not perform copy of buffers from the device. Use `copy_render_tile_from_device()`
|
||||
* to update host-side data. */
|
||||
bool get_render_tile_pixels(const PassAccessor &pass_accessor,
|
||||
const PassAccessor::Destination &destination);
|
||||
|
||||
/* Set pass data for baking. */
|
||||
bool set_render_tile_pixels(PassAccessor &pass_accessor, const PassAccessor::Source &source);
|
||||
|
||||
/* Perform convergence test on the render buffer, and filter the convergence mask.
|
||||
* Returns number of active pixels (the ones which did not converge yet). */
|
||||
virtual int adaptive_sampling_converge_filter_count_active(const float threshold,
|
||||
bool reset) = 0;
|
||||
|
||||
/* Denoise Volume Scattering Probability Guiding buffers. */
|
||||
virtual void denoise_volume_guiding_buffers() = 0;
|
||||
|
||||
/* Run cryptomatte pass post-processing kernels. */
|
||||
virtual void cryptomatte_postproces() = 0;
|
||||
|
||||
/* Cheap-ish request to see whether rendering is requested and is to be stopped as soon as
|
||||
* possible, without waiting for any samples to be finished. */
|
||||
bool is_cancel_requested() const
|
||||
{
|
||||
/* NOTE: Rely on the fact that on x86 CPU reading scalar can happen without atomic even in
|
||||
* threaded environment. */
|
||||
return *cancel_requested_flag_;
|
||||
}
|
||||
|
||||
/* Access to the device which is used to path trace this work on. */
|
||||
Device *get_device() const
|
||||
{
|
||||
return device_;
|
||||
}
|
||||
|
||||
#if defined(WITH_PATH_GUIDING)
|
||||
/* Initializes the per-thread guiding kernel data. */
|
||||
virtual void guiding_init_kernel_globals(void * /*unused*/,
|
||||
void * /*unused*/,
|
||||
const bool /*unused*/)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
PathTraceWork(Device *device,
|
||||
Film *film,
|
||||
DeviceScene *device_scene,
|
||||
const bool *cancel_requested_flag);
|
||||
|
||||
PassAccessor::PassAccessInfo get_display_pass_access_info(PassMode pass_mode) const;
|
||||
|
||||
/* Get destination which offset and stride are configured so that writing to it will write to a
|
||||
* proper location of GPU display texture, taking current tile and device slice into account. */
|
||||
PassAccessor::Destination get_display_destination_template(const PathTraceDisplay *display,
|
||||
const PassMode mode) const;
|
||||
|
||||
/* Device which will be used for path tracing.
|
||||
* Note that it is an actual render device (and never is a multi-device). */
|
||||
Device *device_;
|
||||
|
||||
/* Film is used to access display pass configuration for GPU display update.
|
||||
* Note that only fields which are not a part of kernel data can be accessed via the Film. */
|
||||
Film *film_;
|
||||
|
||||
/* Device side scene storage, that may be used for integrator logic. */
|
||||
DeviceScene *device_scene_;
|
||||
|
||||
/* Render buffers where sampling is being accumulated into, allocated for a fraction of the big
|
||||
* tile which is being rendered by this work.
|
||||
* It also defines possible subset of a big tile in the case of multi-device rendering. */
|
||||
unique_ptr<RenderBuffers> buffers_;
|
||||
|
||||
/* Effective parameters of the big tile, and current work render buffer.
|
||||
* The latter might be different from `buffers_->params` when there is a resolution divider
|
||||
* involved. */
|
||||
BufferParams effective_big_tile_params_;
|
||||
BufferParams effective_buffer_params_;
|
||||
BufferParams effective_denoised_big_tile_params_;
|
||||
BufferParams effective_denoised_buffer_params_;
|
||||
|
||||
const bool *cancel_requested_flag_ = nullptr;
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
467
blender-5.2.0/intern/cycles/integrator/path_trace_work_cpu.cpp
Normal file
467
blender-5.2.0/intern/cycles/integrator/path_trace_work_cpu.cpp
Normal file
@@ -0,0 +1,467 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "integrator/path_trace_work_cpu.h"
|
||||
|
||||
#include "device/cpu/kernel.h"
|
||||
#include "device/device.h"
|
||||
|
||||
#ifdef WITH_CYCLES_DEBUG
|
||||
# include "kernel/film/write.h"
|
||||
#endif
|
||||
|
||||
#include "kernel/integrator/path_state.h"
|
||||
|
||||
#include "integrator/pass_accessor_cpu.h"
|
||||
#include "integrator/path_trace_display.h"
|
||||
|
||||
#include "scene/scene.h"
|
||||
#include "session/buffers.h"
|
||||
|
||||
#include "util/tbb.h"
|
||||
#include "util/time.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Create TBB arena for execution of path tracing and rendering tasks. */
|
||||
static inline tbb::task_arena local_tbb_arena_create(const Device *device)
|
||||
{
|
||||
/* TODO: limit this to number of threads of CPU device, it may be smaller than
|
||||
* the system number of threads when we reduce the number of CPU threads in
|
||||
* CPU + GPU rendering to dedicate some cores to handling the GPU device. */
|
||||
return tbb::task_arena(device->info.cpu_threads);
|
||||
}
|
||||
|
||||
/* Get ThreadKernelGlobalsCPU for the current thread. */
|
||||
static inline ThreadKernelGlobalsCPU *kernel_thread_globals_get(
|
||||
vector<ThreadKernelGlobalsCPU> &kernel_thread_globals)
|
||||
{
|
||||
const int thread_index = tbb::this_task_arena::current_thread_index();
|
||||
DCHECK_GE(thread_index, 0);
|
||||
DCHECK_LE(thread_index, kernel_thread_globals.size());
|
||||
|
||||
return &kernel_thread_globals[thread_index];
|
||||
}
|
||||
|
||||
PathTraceWorkCPU::PathTraceWorkCPU(Device *device,
|
||||
Film *film,
|
||||
DeviceScene *device_scene,
|
||||
const bool *cancel_requested_flag)
|
||||
: PathTraceWork(device, film, device_scene, cancel_requested_flag),
|
||||
kernels_(Device::get_cpu_kernels())
|
||||
{
|
||||
DCHECK_EQ(device->info.type, DEVICE_CPU);
|
||||
}
|
||||
|
||||
void PathTraceWorkCPU::init_execution()
|
||||
{
|
||||
/* Acquire thread globals, updating all data pointers. */
|
||||
kernel_thread_globals_ = device_->acquire_cpu_kernel_thread_globals();
|
||||
}
|
||||
|
||||
void PathTraceWorkCPU::deinit_execution()
|
||||
{
|
||||
device_->release_cpu_kernel_thread_globals();
|
||||
kernel_thread_globals_ = nullptr;
|
||||
}
|
||||
|
||||
void PathTraceWorkCPU::render_samples(RenderStatistics &statistics,
|
||||
const int start_sample,
|
||||
const int samples_num,
|
||||
const int sample_offset)
|
||||
{
|
||||
const int64_t image_width = effective_buffer_params_.width;
|
||||
const int64_t image_height = effective_buffer_params_.height;
|
||||
const int64_t total_pixels_num = image_width * image_height;
|
||||
|
||||
if (device_->profiler.active()) {
|
||||
for (ThreadKernelGlobalsCPU &kernel_globals : *kernel_thread_globals_) {
|
||||
kernel_globals.start_profiling();
|
||||
}
|
||||
}
|
||||
|
||||
tbb::task_arena local_arena = local_tbb_arena_create(device_);
|
||||
local_arena.execute([&]() {
|
||||
parallel_for(int64_t(0), total_pixels_num, [&](int64_t work_index) {
|
||||
if (is_cancel_requested()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int y = work_index / image_width;
|
||||
const int x = work_index - y * image_width;
|
||||
|
||||
KernelWorkTile work_tile;
|
||||
work_tile.x = effective_buffer_params_.full_x + x;
|
||||
work_tile.y = effective_buffer_params_.full_y + y;
|
||||
work_tile.w = 1;
|
||||
work_tile.h = 1;
|
||||
work_tile.start_sample = start_sample;
|
||||
work_tile.sample_offset = sample_offset;
|
||||
work_tile.num_samples = 1;
|
||||
work_tile.offset = effective_buffer_params_.offset;
|
||||
work_tile.stride = effective_buffer_params_.stride;
|
||||
|
||||
ThreadKernelGlobalsCPU *kernel_globals = kernel_thread_globals_get(*kernel_thread_globals_);
|
||||
|
||||
render_samples_full_pipeline(kernel_globals, work_tile, samples_num);
|
||||
});
|
||||
});
|
||||
if (device_->profiler.active()) {
|
||||
for (ThreadKernelGlobalsCPU &kernel_globals : *kernel_thread_globals_) {
|
||||
kernel_globals.stop_profiling();
|
||||
}
|
||||
}
|
||||
|
||||
statistics.occupancy = 1.0f;
|
||||
}
|
||||
|
||||
void PathTraceWorkCPU::render_samples_full_pipeline(ThreadKernelGlobalsCPU *kernel_globals,
|
||||
const KernelWorkTile &work_tile,
|
||||
const int samples_num)
|
||||
{
|
||||
const bool has_bake = device_scene_->data.bake.use;
|
||||
|
||||
IntegratorStateCPU integrator_states[2];
|
||||
|
||||
IntegratorStateCPU *state = &integrator_states[0];
|
||||
IntegratorStateCPU *shadow_catcher_state = nullptr;
|
||||
|
||||
if (device_scene_->data.integrator.has_shadow_catcher) {
|
||||
shadow_catcher_state = &integrator_states[1];
|
||||
path_state_init_queues(shadow_catcher_state);
|
||||
}
|
||||
|
||||
KernelWorkTile sample_work_tile = work_tile;
|
||||
float *render_buffer = buffers_->buffer.data();
|
||||
|
||||
fast_timer render_timer;
|
||||
|
||||
for (int sample = 0; sample < samples_num; ++sample) {
|
||||
if (is_cancel_requested()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (has_bake) {
|
||||
if (!kernels_.integrator_init_from_bake(
|
||||
kernel_globals, state, &sample_work_tile, render_buffer))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!kernels_.integrator_init_from_camera(
|
||||
kernel_globals, state, &sample_work_tile, render_buffer))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(WITH_PATH_GUIDING)
|
||||
if (kernel_globals->data.integrator.train_guiding) {
|
||||
assert(kernel_globals->opgl_path_segment_storage);
|
||||
assert(kernel_globals->opgl_path_segment_storage->GetNumSegments() == 0);
|
||||
|
||||
kernels_.integrator_megakernel(kernel_globals, state, render_buffer);
|
||||
|
||||
/* Push the generated sample data to the global sample data storage. */
|
||||
guiding_push_sample_data_to_global_storage(kernel_globals, state, render_buffer);
|
||||
|
||||
/* No training for shadow catcher paths. */
|
||||
if (shadow_catcher_state) {
|
||||
kernel_globals->data.integrator.train_guiding = false;
|
||||
kernels_.integrator_megakernel(kernel_globals, shadow_catcher_state, render_buffer);
|
||||
kernel_globals->data.integrator.train_guiding = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
kernels_.integrator_megakernel(kernel_globals, state, render_buffer);
|
||||
if (shadow_catcher_state) {
|
||||
kernels_.integrator_megakernel(kernel_globals, shadow_catcher_state, render_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if (kernel_globals->data.film.pass_render_time != PASS_UNUSED) {
|
||||
uint64_t time;
|
||||
if (render_timer.lap(time)) {
|
||||
ccl_global float *buffer = render_buffer + (uint64_t)state->path.render_pixel_index *
|
||||
kernel_globals->data.film.pass_stride;
|
||||
*(buffer + kernel_globals->data.film.pass_render_time) += float(time);
|
||||
}
|
||||
}
|
||||
++sample_work_tile.start_sample;
|
||||
}
|
||||
}
|
||||
|
||||
void PathTraceWorkCPU::copy_to_display(PathTraceDisplay *display,
|
||||
PassMode pass_mode,
|
||||
const int num_samples)
|
||||
{
|
||||
half4 *rgba_half = display->map_texture_buffer();
|
||||
if (!rgba_half) {
|
||||
/* TODO(sergey): Look into using copy_to_display() if mapping failed. Might be needed for
|
||||
* some implementations of PathTraceDisplay which can not map memory? */
|
||||
return;
|
||||
}
|
||||
|
||||
const KernelFilm &kfilm = device_scene_->data.film;
|
||||
|
||||
const PassAccessor::PassAccessInfo pass_access_info = get_display_pass_access_info(pass_mode);
|
||||
if (pass_access_info.type == PASS_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
const BufferParams &effective_buffer_params = (pass_mode == PassMode::DENOISED) ?
|
||||
effective_denoised_buffer_params_ :
|
||||
effective_buffer_params_;
|
||||
|
||||
const PassAccessorCPU pass_accessor(pass_access_info, kfilm.exposure, num_samples);
|
||||
|
||||
PassAccessor::Destination destination = get_display_destination_template(display, pass_mode);
|
||||
destination.pixels_half_rgba = rgba_half;
|
||||
|
||||
tbb::task_arena local_arena = local_tbb_arena_create(device_);
|
||||
local_arena.execute([&]() {
|
||||
pass_accessor.get_render_tile_pixels(buffers_.get(), effective_buffer_params, destination);
|
||||
});
|
||||
|
||||
display->unmap_texture_buffer();
|
||||
}
|
||||
|
||||
void PathTraceWorkCPU::destroy_gpu_resources(PathTraceDisplay * /*display*/) {}
|
||||
|
||||
bool PathTraceWorkCPU::copy_render_buffers_from_device()
|
||||
{
|
||||
return buffers_->copy_from_device();
|
||||
}
|
||||
|
||||
bool PathTraceWorkCPU::copy_render_buffers_to_device()
|
||||
{
|
||||
buffers_->buffer.copy_to_device();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PathTraceWorkCPU::zero_render_buffers()
|
||||
{
|
||||
buffers_->zero();
|
||||
return true;
|
||||
}
|
||||
|
||||
int PathTraceWorkCPU::adaptive_sampling_converge_filter_count_active(const float threshold,
|
||||
bool reset)
|
||||
{
|
||||
const int full_x = effective_buffer_params_.full_x;
|
||||
const int full_y = effective_buffer_params_.full_y;
|
||||
const int width = effective_buffer_params_.width;
|
||||
const int height = effective_buffer_params_.height;
|
||||
const int offset = effective_buffer_params_.offset;
|
||||
const int stride = effective_buffer_params_.stride;
|
||||
|
||||
float *render_buffer = buffers_->buffer.data();
|
||||
|
||||
uint num_active_pixels = 0;
|
||||
|
||||
tbb::task_arena local_arena = local_tbb_arena_create(device_);
|
||||
|
||||
/* Check convergency and do x-filter in a single `parallel_for`, to reduce threading overhead. */
|
||||
local_arena.execute([&]() {
|
||||
parallel_for(full_y, full_y + height, [&](int y) {
|
||||
ThreadKernelGlobalsCPU *kernel_globals = kernel_thread_globals_->data();
|
||||
|
||||
bool row_converged = true;
|
||||
uint num_row_pixels_active = 0;
|
||||
for (int x = 0; x < width; ++x) {
|
||||
if (!kernels_.adaptive_sampling_convergence_check(
|
||||
kernel_globals, render_buffer, full_x + x, y, threshold, reset, offset, stride))
|
||||
{
|
||||
++num_row_pixels_active;
|
||||
row_converged = false;
|
||||
}
|
||||
}
|
||||
|
||||
atomic_fetch_and_add_uint32(&num_active_pixels, num_row_pixels_active);
|
||||
|
||||
if (!row_converged) {
|
||||
kernels_.adaptive_sampling_filter_x(
|
||||
kernel_globals, render_buffer, y, full_x, width, offset, stride);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (num_active_pixels) {
|
||||
local_arena.execute([&]() {
|
||||
parallel_for(full_x, full_x + width, [&](int x) {
|
||||
ThreadKernelGlobalsCPU *kernel_globals = kernel_thread_globals_->data();
|
||||
kernels_.adaptive_sampling_filter_y(
|
||||
kernel_globals, render_buffer, x, full_y, height, offset, stride);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return num_active_pixels;
|
||||
}
|
||||
|
||||
void PathTraceWorkCPU::cryptomatte_postproces()
|
||||
{
|
||||
const int width = effective_buffer_params_.width;
|
||||
const int height = effective_buffer_params_.height;
|
||||
|
||||
float *render_buffer = buffers_->buffer.data();
|
||||
|
||||
tbb::task_arena local_arena = local_tbb_arena_create(device_);
|
||||
|
||||
/* Check convergency and do x-filter in a single `parallel_for`, to reduce threading overhead. */
|
||||
local_arena.execute([&]() {
|
||||
parallel_for(0, height, [&](int y) {
|
||||
ThreadKernelGlobalsCPU *kernel_globals = kernel_thread_globals_->data();
|
||||
int pixel_index = y * width;
|
||||
|
||||
for (int x = 0; x < width; ++x, ++pixel_index) {
|
||||
kernels_.cryptomatte_postprocess(kernel_globals, render_buffer, pixel_index);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void PathTraceWorkCPU::denoise_volume_guiding_buffers()
|
||||
{
|
||||
const int min_x = effective_buffer_params_.full_x;
|
||||
const int min_y = effective_buffer_params_.full_y;
|
||||
const int max_x = effective_buffer_params_.width + min_x;
|
||||
const int max_y = effective_buffer_params_.height + min_y;
|
||||
const int offset = effective_buffer_params_.offset;
|
||||
const int stride = effective_buffer_params_.stride;
|
||||
|
||||
float *render_buffer = buffers_->buffer.data();
|
||||
|
||||
tbb::task_arena local_arena = local_tbb_arena_create(device_);
|
||||
|
||||
const blocked_range2d<int> range(min_x, max_x, min_y, max_y);
|
||||
|
||||
/* Filter in x direction. */
|
||||
local_arena.execute([&]() {
|
||||
parallel_for(range, [&](const blocked_range2d<int> r) {
|
||||
ThreadKernelGlobalsCPU *kernel_globals = kernel_thread_globals_->data();
|
||||
for (int y = r.cols().begin(); y < r.cols().end(); ++y) {
|
||||
for (int x = r.rows().begin(); x < r.rows().end(); ++x) {
|
||||
kernels_.volume_guiding_filter_x(
|
||||
kernel_globals, render_buffer, y, x, min_x, max_x, offset, stride);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* Filter in y direction. Unlike `filter_x`, the inner loop of `filter_y` is serially run inside
|
||||
* the kernel, to avoid the need of intermediate buffers. */
|
||||
local_arena.execute([&]() {
|
||||
parallel_for(min_x, max_x, [&](int x) {
|
||||
ThreadKernelGlobalsCPU *kernel_globals = kernel_thread_globals_->data();
|
||||
kernels_.volume_guiding_filter_y(
|
||||
kernel_globals, render_buffer, x, min_y, max_y, offset, stride);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#if defined(WITH_PATH_GUIDING)
|
||||
/* NOTE: It seems that this is called before every rendering iteration/progression and not once per
|
||||
* rendering. May be we find a way to call it only once per rendering. */
|
||||
void PathTraceWorkCPU::guiding_init_kernel_globals(void *guiding_field,
|
||||
void *sample_data_storage,
|
||||
const bool train)
|
||||
{
|
||||
/* Linking the global guiding structures (e.g., Field and SampleStorage) to the per-thread
|
||||
* kernel globals. */
|
||||
for (int thread_index = 0; thread_index < kernel_thread_globals_->size(); thread_index++) {
|
||||
ThreadKernelGlobalsCPU &kg = (*kernel_thread_globals_)[thread_index];
|
||||
openpgl::cpp::Field *field = (openpgl::cpp::Field *)guiding_field;
|
||||
|
||||
/* Allocate sampling distributions. */
|
||||
kg.opgl_guiding_field = field;
|
||||
|
||||
# if PATH_GUIDING_LEVEL >= 4
|
||||
if (kg.opgl_surface_sampling_distribution) {
|
||||
kg.opgl_surface_sampling_distribution.reset();
|
||||
}
|
||||
if (kg.opgl_volume_sampling_distribution) {
|
||||
kg.opgl_volume_sampling_distribution.reset();
|
||||
}
|
||||
|
||||
if (field) {
|
||||
kg.opgl_surface_sampling_distribution =
|
||||
make_unique<openpgl::cpp::SurfaceSamplingDistribution>(field);
|
||||
kg.opgl_volume_sampling_distribution = make_unique<openpgl::cpp::VolumeSamplingDistribution>(
|
||||
field);
|
||||
}
|
||||
# endif
|
||||
|
||||
/* Reserve storage for training. */
|
||||
kg.data.integrator.train_guiding = train;
|
||||
kg.opgl_sample_data_storage = (openpgl::cpp::SampleStorage *)sample_data_storage;
|
||||
|
||||
if (train) {
|
||||
kg.opgl_path_segment_storage->Reserve(kg.data.integrator.transparent_max_bounce +
|
||||
kg.data.integrator.max_bounce + 3);
|
||||
kg.opgl_path_segment_storage->Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PathTraceWorkCPU::guiding_push_sample_data_to_global_storage(ThreadKernelGlobalsCPU *kg,
|
||||
IntegratorStateCPU *state,
|
||||
ccl_global float *ccl_restrict
|
||||
render_buffer)
|
||||
{
|
||||
# ifdef WITH_CYCLES_DEBUG
|
||||
if (LOG_IS_ON(LOG_LEVEL_DEBUG)) {
|
||||
/* Check if the generated path segments contain valid values. */
|
||||
const bool validSegments = kg->opgl_path_segment_storage->ValidateSegments();
|
||||
if (!validSegments) {
|
||||
LOG_DEBUG << "Guiding: invalid path segments!";
|
||||
}
|
||||
}
|
||||
|
||||
/* Write debug render pass to validate it matches combined pass. */
|
||||
pgl_vec3f pgl_final_color = kg->opgl_path_segment_storage->CalculatePixelEstimate(false);
|
||||
ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
|
||||
float3 final_color = make_float3(pgl_final_color.x, pgl_final_color.y, pgl_final_color.z);
|
||||
if (kernel_data.film.pass_guiding_color != PASS_UNUSED) {
|
||||
film_write_pass_float3(buffer + kernel_data.film.pass_guiding_color, final_color);
|
||||
}
|
||||
# else
|
||||
(void)state;
|
||||
(void)render_buffer;
|
||||
# endif
|
||||
|
||||
/* Convert the path segment representation of the random walk into radiance samples. */
|
||||
# if PATH_GUIDING_LEVEL >= 2
|
||||
const bool use_direct_light = kernel_data.integrator.use_guiding_direct_light;
|
||||
const bool use_mis_weights = kernel_data.integrator.use_guiding_mis_weights;
|
||||
kg->opgl_path_segment_storage->PrepareSamples(use_mis_weights, use_direct_light, false);
|
||||
# endif
|
||||
|
||||
# ifdef WITH_CYCLES_DEBUG
|
||||
/* Check if the training/radiance samples generated by the path segment storage are valid. */
|
||||
if (LOG_IS_ON(LOG_LEVEL_DEBUG)) {
|
||||
const bool validSamples = kg->opgl_path_segment_storage->ValidateSamples();
|
||||
if (!validSamples) {
|
||||
LOG_DEBUG
|
||||
<< "Guiding: path segment storage generated/contains invalid radiance/training samples!";
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
# if PATH_GUIDING_LEVEL >= 3
|
||||
/* Push radiance samples from current random walk/path to the global sample storage. */
|
||||
size_t num_samples = 0;
|
||||
const openpgl::cpp::SampleData *samples = kg->opgl_path_segment_storage->GetSamples(num_samples);
|
||||
kg->opgl_sample_data_storage->AddSamples(samples, num_samples);
|
||||
# endif
|
||||
|
||||
/* Clear storage for the current path, to be ready for the next path. */
|
||||
kg->opgl_path_segment_storage->Clear();
|
||||
}
|
||||
#endif
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
87
blender-5.2.0/intern/cycles/integrator/path_trace_work_cpu.h
Normal file
87
blender-5.2.0/intern/cycles/integrator/path_trace_work_cpu.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/device/cpu/globals.h"
|
||||
#include "kernel/integrator/state.h"
|
||||
|
||||
#include "device/queue.h"
|
||||
|
||||
#include "integrator/path_trace_work.h"
|
||||
|
||||
#include "util/vector.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
struct KernelWorkTile;
|
||||
struct ThreadKernelGlobalsCPU;
|
||||
struct IntegratorStateCPU;
|
||||
|
||||
class CPUKernels;
|
||||
|
||||
/* Implementation of PathTraceWork which schedules work on to queues pixel-by-pixel,
|
||||
* for CPU devices.
|
||||
*
|
||||
* NOTE: For the CPU rendering there are assumptions about TBB arena size and number of concurrent
|
||||
* queues on the render device which makes this work be only usable on CPU. */
|
||||
class PathTraceWorkCPU : public PathTraceWork {
|
||||
public:
|
||||
PathTraceWorkCPU(Device *device,
|
||||
Film *film,
|
||||
DeviceScene *device_scene,
|
||||
const bool *cancel_requested_flag);
|
||||
|
||||
void init_execution() override;
|
||||
void deinit_execution() override;
|
||||
|
||||
void render_samples(RenderStatistics &statistics,
|
||||
const int start_sample,
|
||||
const int samples_num,
|
||||
const int sample_offset) override;
|
||||
|
||||
void copy_to_display(PathTraceDisplay *display,
|
||||
PassMode pass_mode,
|
||||
const int num_samples) override;
|
||||
void destroy_gpu_resources(PathTraceDisplay *display) override;
|
||||
|
||||
bool copy_render_buffers_from_device() override;
|
||||
bool copy_render_buffers_to_device() override;
|
||||
bool zero_render_buffers() override;
|
||||
|
||||
int adaptive_sampling_converge_filter_count_active(const float threshold, bool reset) override;
|
||||
void cryptomatte_postproces() override;
|
||||
void denoise_volume_guiding_buffers() override;
|
||||
|
||||
#if defined(WITH_PATH_GUIDING)
|
||||
/* Initializes the per-thread guiding kernel data. The function sets the pointers to the
|
||||
* global guiding field and the sample data storage as well es initializes the per-thread
|
||||
* guided sampling distributions (e.g., SurfaceSamplingDistribution and
|
||||
* VolumeSamplingDistribution). */
|
||||
void guiding_init_kernel_globals(void *guiding_field,
|
||||
void *sample_data_storage,
|
||||
const bool train) override;
|
||||
|
||||
/* Pushes the collected training data/samples of a path to the global sample storage.
|
||||
* This function is called at the end of a random walk/path generation. */
|
||||
void guiding_push_sample_data_to_global_storage(ThreadKernelGlobalsCPU *kg,
|
||||
IntegratorStateCPU *state,
|
||||
ccl_global float *ccl_restrict render_buffer);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/* Core path tracing routine. Renders given work time on the given queue. */
|
||||
void render_samples_full_pipeline(ThreadKernelGlobalsCPU *kernel_globals,
|
||||
const KernelWorkTile &work_tile,
|
||||
const int samples_num);
|
||||
|
||||
/* CPU kernels. */
|
||||
const CPUKernels &kernels_;
|
||||
|
||||
/* Pointer to device-owned kernel globals which is suitable for concurrent access from multiple
|
||||
* threads. This allows dynamic updates to image_info when textures are loaded on demand. */
|
||||
vector<ThreadKernelGlobalsCPU> *kernel_thread_globals_ = nullptr;
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
1344
blender-5.2.0/intern/cycles/integrator/path_trace_work_gpu.cpp
Normal file
1344
blender-5.2.0/intern/cycles/integrator/path_trace_work_gpu.cpp
Normal file
File diff suppressed because it is too large
Load Diff
179
blender-5.2.0/intern/cycles/integrator/path_trace_work_gpu.h
Normal file
179
blender-5.2.0/intern/cycles/integrator/path_trace_work_gpu.h
Normal file
@@ -0,0 +1,179 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/integrator/state.h"
|
||||
|
||||
#include "device/graphics_interop.h"
|
||||
#include "device/memory.h"
|
||||
#include "device/queue.h"
|
||||
|
||||
#include "integrator/path_trace_work.h"
|
||||
#include "integrator/work_tile_scheduler.h"
|
||||
|
||||
#include "util/vector.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
struct KernelWorkTile;
|
||||
|
||||
/* Implementation of PathTraceWork which schedules work to the device in tiles which are sized
|
||||
* to match device queue's number of path states.
|
||||
* This implementation suits best devices which have a lot of integrator states, such as GPU. */
|
||||
class PathTraceWorkGPU : public PathTraceWork {
|
||||
public:
|
||||
PathTraceWorkGPU(Device *device,
|
||||
Film *film,
|
||||
DeviceScene *device_scene,
|
||||
const bool *cancel_requested_flag);
|
||||
|
||||
void alloc_work_memory() override;
|
||||
void init_execution() override;
|
||||
|
||||
void render_samples(RenderStatistics &statistics,
|
||||
const int start_sample,
|
||||
const int samples_num,
|
||||
const int sample_offset) override;
|
||||
|
||||
void copy_to_display(PathTraceDisplay *display,
|
||||
PassMode pass_mode,
|
||||
const int num_samples) override;
|
||||
void destroy_gpu_resources(PathTraceDisplay *display) override;
|
||||
|
||||
bool copy_render_buffers_from_device() override;
|
||||
bool copy_render_buffers_to_device() override;
|
||||
bool zero_render_buffers() override;
|
||||
|
||||
int adaptive_sampling_converge_filter_count_active(const float threshold, bool reset) override;
|
||||
void cryptomatte_postproces() override;
|
||||
void denoise_volume_guiding_buffers() override;
|
||||
|
||||
protected:
|
||||
void alloc_integrator_soa();
|
||||
void alloc_integrator_queue();
|
||||
void alloc_integrator_sorting();
|
||||
void alloc_integrator_path_split();
|
||||
|
||||
/* Returns DEVICE_KERNEL_NUM if there are no scheduled kernels. */
|
||||
DeviceKernel get_most_queued_kernel() const;
|
||||
|
||||
void enqueue_reset();
|
||||
|
||||
bool enqueue_work_tiles(bool &finished);
|
||||
void enqueue_work_tiles(DeviceKernel kernel,
|
||||
const KernelWorkTile work_tiles[],
|
||||
const int num_work_tiles,
|
||||
const int num_active_paths,
|
||||
const int num_predicted_splits);
|
||||
|
||||
bool enqueue_path_iteration();
|
||||
void enqueue_path_iteration(DeviceKernel kernel, const int num_paths_limit = INT_MAX);
|
||||
|
||||
bool update_queue_counter_and_cache();
|
||||
|
||||
void compute_queued_paths(DeviceKernel kernel, DeviceKernel queued_kernel);
|
||||
void compute_sorted_queued_paths(DeviceKernel queued_kernel, const int num_paths_limit);
|
||||
|
||||
void compact_main_paths(const int num_active_paths);
|
||||
void compact_shadow_paths();
|
||||
void compact_paths(const int num_active_paths,
|
||||
const int max_active_path_index,
|
||||
DeviceKernel terminated_paths_kernel,
|
||||
DeviceKernel compact_paths_kernel,
|
||||
DeviceKernel compact_kernel);
|
||||
|
||||
int num_active_main_paths_paths();
|
||||
|
||||
/* Check whether graphics interop can be used for the PathTraceDisplay update. */
|
||||
bool should_use_graphics_interop(PathTraceDisplay *display);
|
||||
|
||||
/* Naive implementation of the `copy_to_display()` which performs film conversion on the
|
||||
* device, then copies pixels to the host and pushes them to the `display`. */
|
||||
void copy_to_display_naive(PathTraceDisplay *display, PassMode pass_mode, const int num_samples);
|
||||
|
||||
/* Implementation of `copy_to_display()` which uses driver's OpenGL/GPU interoperability
|
||||
* functionality, avoiding copy of pixels to the host. */
|
||||
bool copy_to_display_interop(PathTraceDisplay *display,
|
||||
PassMode pass_mode,
|
||||
const int num_samples);
|
||||
|
||||
/* Synchronously run film conversion kernel and store display result in the given destination. */
|
||||
void get_render_tile_film_pixels(const PassAccessor::Destination &destination,
|
||||
PassMode pass_mode,
|
||||
int num_samples);
|
||||
|
||||
int adaptive_sampling_convergence_check_count_active(const float threshold, bool reset);
|
||||
void enqueue_adaptive_sampling_filter_x();
|
||||
void enqueue_adaptive_sampling_filter_y();
|
||||
|
||||
bool has_shadow_catcher() const;
|
||||
|
||||
/* Count how many currently scheduled paths can still split. */
|
||||
int shadow_catcher_count_possible_splits();
|
||||
|
||||
/* Kernel properties. */
|
||||
bool kernel_uses_sorting(DeviceKernel kernel);
|
||||
bool kernel_creates_shadow_paths(DeviceKernel kernel);
|
||||
bool kernel_creates_ao_paths(DeviceKernel kernel);
|
||||
bool kernel_is_shadow_path(DeviceKernel kernel);
|
||||
int kernel_max_active_main_path_index(DeviceKernel kernel);
|
||||
|
||||
/* Integrator queue. */
|
||||
unique_ptr<DeviceQueue> queue_;
|
||||
|
||||
/* Scheduler which gives work to path tracing threads. */
|
||||
WorkTileScheduler work_tile_scheduler_;
|
||||
|
||||
/* Integrate state for paths. */
|
||||
IntegratorStateGPU integrator_state_gpu_;
|
||||
/* SoA arrays for integrator state. */
|
||||
vector<unique_ptr<device_memory>> integrator_state_soa_;
|
||||
uint integrator_state_soa_kernel_features_;
|
||||
int integrator_state_soa_volume_stack_size_ = 0;
|
||||
/* Keep track of number of queued kernels. */
|
||||
device_vector<IntegratorQueueCounter> integrator_queue_counter_;
|
||||
/* Shader sorting. */
|
||||
device_vector<int> integrator_shader_sort_counter_;
|
||||
device_vector<int> integrator_shader_raytrace_sort_counter_;
|
||||
device_vector<int> integrator_shader_sort_prefix_sum_;
|
||||
device_vector<int> integrator_shader_sort_partition_key_offsets_;
|
||||
/* Path split. */
|
||||
device_vector<int> integrator_next_main_path_index_;
|
||||
device_vector<int> integrator_next_shadow_path_index_;
|
||||
|
||||
/* Temporary buffer to get an array of queued path for a particular kernel. */
|
||||
device_vector<int> queued_paths_;
|
||||
device_vector<int> num_queued_paths_;
|
||||
|
||||
/* Temporary buffer for passing work tiles to kernel. */
|
||||
device_vector<KernelWorkTile> work_tiles_;
|
||||
|
||||
/* Temporary buffer used by the copy_to_display() whenever graphics interoperability is not
|
||||
* available. Is allocated on-demand. */
|
||||
device_vector<half4> display_rgba_half_;
|
||||
|
||||
unique_ptr<DeviceGraphicsInterop> device_graphics_interop_;
|
||||
|
||||
/* Cached result of device->should_use_graphics_interop(). */
|
||||
bool interop_use_checked_ = false;
|
||||
bool interop_use_ = false;
|
||||
|
||||
/* Number of partitions to sort state indices into prior to material sort. */
|
||||
int num_sort_partitions_;
|
||||
|
||||
/* Maximum number of concurrent integrator states. */
|
||||
int max_num_paths_;
|
||||
|
||||
/* Minimum number of paths which keeps the device bust. If the actual number of paths falls below
|
||||
* this value more work will be scheduled. */
|
||||
int min_num_active_main_paths_;
|
||||
|
||||
/* Maximum path index, effective number of paths used may be smaller than
|
||||
* the size of the integrator_state_ buffer so can avoid iterating over the
|
||||
* full buffer. */
|
||||
int max_active_main_path_index_;
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
1328
blender-5.2.0/intern/cycles/integrator/render_scheduler.cpp
Normal file
1328
blender-5.2.0/intern/cycles/integrator/render_scheduler.cpp
Normal file
File diff suppressed because it is too large
Load Diff
511
blender-5.2.0/intern/cycles/integrator/render_scheduler.h
Normal file
511
blender-5.2.0/intern/cycles/integrator/render_scheduler.h
Normal file
@@ -0,0 +1,511 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "integrator/adaptive_sampling.h"
|
||||
#include "integrator/denoiser.h"
|
||||
|
||||
#include "session/buffers.h"
|
||||
|
||||
#include "util/string.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
class SessionParams;
|
||||
class TileManager;
|
||||
|
||||
class RenderWork {
|
||||
public:
|
||||
float resolution_divider = 1;
|
||||
float denoised_resolution_divider = 1;
|
||||
|
||||
/* Initialize render buffers.
|
||||
* Includes steps like zeroing the buffer on the device, and optional reading of pixels from the
|
||||
* baking target. */
|
||||
bool init_render_buffers = false;
|
||||
|
||||
/* Path tracing samples information. */
|
||||
struct {
|
||||
int start_sample = 0;
|
||||
int num_samples = 0;
|
||||
int sample_offset = 0;
|
||||
} path_trace;
|
||||
|
||||
struct {
|
||||
/* Check for convergency and filter the mask. */
|
||||
bool filter = false;
|
||||
|
||||
float threshold = 0.0f;
|
||||
|
||||
/* Reset convergency flag when filtering, forcing a re-check of whether pixel did converge. */
|
||||
bool reset = false;
|
||||
} adaptive_sampling;
|
||||
|
||||
struct {
|
||||
bool postprocess = false;
|
||||
} cryptomatte;
|
||||
|
||||
/* Work related on the current tile. */
|
||||
struct {
|
||||
/* Write render buffers of the current tile.
|
||||
*
|
||||
* It is up to the path trace to decide whether writing should happen via user-provided
|
||||
* callback into the rendering software, or via tile manager into a partial file. */
|
||||
bool write = false;
|
||||
|
||||
bool denoise = false;
|
||||
} tile;
|
||||
|
||||
/* Work related on the full-frame render buffer. */
|
||||
struct {
|
||||
/* Write full render result.
|
||||
* Implies reading the partial file from disk. */
|
||||
bool write = false;
|
||||
} full;
|
||||
|
||||
/* Display which is used to visualize render result. */
|
||||
struct {
|
||||
/* Display needs to be updated for the new render. */
|
||||
bool update = false;
|
||||
|
||||
/* Display can use denoised result if available. */
|
||||
bool use_denoised_result = true;
|
||||
} display;
|
||||
|
||||
/* Re-balance multi-device scheduling after rendering this work.
|
||||
* Note that the scheduler does not know anything about devices, so if there is only a single
|
||||
* device used, then it is up for the PathTracer to ignore the balancing. */
|
||||
bool rebalance = false;
|
||||
|
||||
/* Perform volume guiding buffer denoise. */
|
||||
bool volume_guiding_denoise = false;
|
||||
|
||||
/* Conversion to bool, to simplify checks about whether there is anything to be done for this
|
||||
* work. */
|
||||
operator bool() const
|
||||
{
|
||||
return path_trace.num_samples || adaptive_sampling.filter || display.update || tile.denoise ||
|
||||
tile.write || full.write;
|
||||
}
|
||||
};
|
||||
|
||||
class RenderScheduler {
|
||||
public:
|
||||
RenderScheduler(TileManager &tile_manager, const SessionParams ¶ms);
|
||||
|
||||
/* Specify whether cryptomatte-related works are to be scheduled. */
|
||||
void set_need_schedule_cryptomatte(bool need_schedule_cryptomatte);
|
||||
|
||||
/* Allows to disable work re-balancing works, allowing to schedule as much to a single device
|
||||
* as possible. */
|
||||
void set_need_schedule_rebalance(bool need_schedule_rebalance);
|
||||
|
||||
bool is_background() const;
|
||||
|
||||
void set_denoiser_params(const DenoiseParams ¶ms);
|
||||
bool is_denoiser_gpu_used() const;
|
||||
|
||||
void set_adaptive_sampling(const AdaptiveSampling &adaptive_sampling);
|
||||
bool is_adaptive_sampling_used() const;
|
||||
|
||||
/* Setup parameters defining the sampling range.
|
||||
*
|
||||
* It is a single function setting up multiple parameters because there are inter-dependencies
|
||||
* between these parameters.
|
||||
*
|
||||
* In simple cases the subset is not used and the given num_samples samples is rendered, and the
|
||||
* subset length and offset are ignored.
|
||||
*
|
||||
* It is possible to render a subset of the overall samples. This is typically used to distribute
|
||||
* rendering of a single frame across multiple computers. This subset rendering is enabled by
|
||||
* setting use_sample_subset=true, and giving the desired offset and length of the subset. The
|
||||
* subset offset is a 0-based sample index to start sampling from, and the length is the number
|
||||
* of samples to render in this subset.
|
||||
*
|
||||
* When the subset rendering is enabled, num_samples is expected to be set to the overall number
|
||||
* of samples to be rendered, and it is internally used to clamp the number of samples rendered
|
||||
* by a subset. */
|
||||
void set_sample_params(const int num_samples,
|
||||
const bool use_sample_subset,
|
||||
const int sample_subset_offset,
|
||||
const int sample_subset_length);
|
||||
|
||||
/* Number of samples to render, starting from start sample.
|
||||
* The scheduler will schedule work in the range of
|
||||
* [start_sample, start_sample + num_samples - 1], inclusively. */
|
||||
int get_num_samples() const;
|
||||
|
||||
/* For sample subset rendering, extra offset to be added to sample index
|
||||
* for the sampling pattern to be shifted. */
|
||||
int get_sample_offset() const;
|
||||
|
||||
/* Time limit for the path tracing tasks, in minutes.
|
||||
* Zero disables the limit. */
|
||||
void set_time_limit(const double time_limit);
|
||||
double get_time_limit() const;
|
||||
|
||||
/* Get sample up to which rendering has been done.
|
||||
* This is an absolute 0-based value.
|
||||
*
|
||||
* For example, if start sample is 10 and 5 samples were rendered, then this call will
|
||||
* return 14.
|
||||
*
|
||||
* If there were no samples rendered, then the behavior is undefined. */
|
||||
int get_rendered_sample() const;
|
||||
|
||||
/* Get number of samples rendered within the current scheduling session.
|
||||
*
|
||||
* For example, if start sample is 10 and 5 samples were rendered, then this call will
|
||||
* return 5.
|
||||
*
|
||||
* Note that this is based on the scheduling information. In practice this means that if someone
|
||||
* requested for work to render the scheduler considers the work done. */
|
||||
int get_num_rendered_samples() const;
|
||||
|
||||
/* Reset scheduler, indicating that rendering will happen from scratch.
|
||||
* Resets current rendered state, as well as scheduling information. */
|
||||
void reset(const BufferParams &buffer_params);
|
||||
|
||||
/* Reset scheduler upon switching to a next tile.
|
||||
* Will keep the same number of samples and full-frame render parameters, but will reset progress
|
||||
* and allow schedule renders works from the beginning of the new tile. */
|
||||
void reset_for_next_tile();
|
||||
|
||||
/* Reschedule adaptive sampling work when all pixels did converge.
|
||||
* If there is nothing else to be done for the adaptive sampling (pixels did converge to the
|
||||
* final threshold) then false is returned and the render scheduler will stop scheduling path
|
||||
* tracing works. Otherwise will modify the work's adaptive sampling settings to continue with
|
||||
* a lower threshold. */
|
||||
bool render_work_reschedule_on_converge(RenderWork &render_work);
|
||||
|
||||
/* Reschedule adaptive sampling work when the device is mostly on idle, but not all pixels yet
|
||||
* converged.
|
||||
* If re-scheduling is not possible (adaptive sampling is happening with the final threshold, and
|
||||
* the path tracer is to finish the current pixels) then false is returned. */
|
||||
bool render_work_reschedule_on_idle(RenderWork &render_work);
|
||||
|
||||
/* Reschedule work when rendering has been requested to cancel.
|
||||
*
|
||||
* Will skip all work which is not needed anymore because no more samples will be added (for
|
||||
* example, adaptive sampling filtering and convergence check will be skipped).
|
||||
* Will enable all work needed to make sure all passes are communicated to the software.
|
||||
*
|
||||
* NOTE: Should be used before passing work to `PathTrace::render_samples()`. */
|
||||
void render_work_reschedule_on_cancel(RenderWork &render_work);
|
||||
|
||||
RenderWork get_render_work();
|
||||
|
||||
/* Report that the path tracer started to work, after scene update and loading kernels. */
|
||||
void report_work_begin(const RenderWork &render_work);
|
||||
|
||||
/* Report time (in seconds) which corresponding part of work took. */
|
||||
void report_path_trace_time(const RenderWork &render_work, const double time, bool is_cancelled);
|
||||
void report_path_trace_occupancy(const RenderWork &render_work, const float occupancy);
|
||||
void report_adaptive_filter_time(const RenderWork &render_work,
|
||||
const double time,
|
||||
bool is_cancelled);
|
||||
void report_denoise_time(const RenderWork &render_work, const double time);
|
||||
void report_display_update_time(const RenderWork &render_work, const double time);
|
||||
void report_rebalance_time(const RenderWork &render_work,
|
||||
const double time,
|
||||
bool balance_changed);
|
||||
void report_volume_guiding_denoise_time(const RenderWork &render_work, const double time);
|
||||
|
||||
/* Generate full multi-line report of the rendering process, including rendering parameters,
|
||||
* times, and so on. */
|
||||
string full_report() const;
|
||||
|
||||
void set_limit_samples_per_update(const int limit_samples);
|
||||
|
||||
protected:
|
||||
/* Check whether all work has been scheduled and time limit was not exceeded.
|
||||
*
|
||||
* NOTE: Tricky bit: if the time limit was reached the done() is considered to be true, but some
|
||||
* extra work needs to be scheduled to denoise and write final result. */
|
||||
bool done() const;
|
||||
|
||||
/* Update scheduling state for a newly scheduled work.
|
||||
* Takes care of things like checking whether work was ever denoised, tile was written and states
|
||||
* like that. */
|
||||
void update_state_for_render_work(const RenderWork &render_work);
|
||||
|
||||
/* Returns true if any work was scheduled. */
|
||||
bool set_postprocess_render_work(RenderWork *render_work);
|
||||
|
||||
/* Set work which is to be performed after all tiles has been rendered. */
|
||||
void set_full_frame_render_work(RenderWork *render_work);
|
||||
|
||||
/* Update start resolution divider based on the accumulated timing information, preserving nice
|
||||
* feeling navigation feel. */
|
||||
void update_start_resolution_divider();
|
||||
|
||||
/* Calculate desired update interval in seconds based on the current timings and settings.
|
||||
* Will give an interval which provides good feeling updates during viewport navigation. */
|
||||
double guess_viewport_navigation_update_interval_in_seconds() const;
|
||||
|
||||
/* Check whether denoising is active during interactive update while resolution divider is not
|
||||
* unit. */
|
||||
bool is_denoise_active_during_update() const;
|
||||
|
||||
/* Heuristic which aims to give perceptually pleasant update of display interval in a way that at
|
||||
* lower samples and near the beginning of rendering, updates happen more often, but with higher
|
||||
* number of samples and later in the render, updates happen less often but device occupancy
|
||||
* goes higher. */
|
||||
double guess_display_update_interval_in_seconds() const;
|
||||
double guess_display_update_interval_in_seconds_for_num_samples(
|
||||
const int num_rendered_samples) const;
|
||||
double guess_display_update_interval_in_seconds_for_num_samples_no_limit(
|
||||
int num_rendered_samples) const;
|
||||
|
||||
/* Calculate number of samples which can be rendered within current desired update interval which
|
||||
* is calculated by `guess_update_interval_in_seconds()`. */
|
||||
int calculate_num_samples_per_update() const;
|
||||
|
||||
/* Get start sample and the number of samples which are to be path traces in the current work. */
|
||||
int get_start_sample_to_path_trace() const;
|
||||
int get_num_samples_to_path_trace() const;
|
||||
|
||||
/* Calculate how many samples there are to be rendered for the very first path trace after reset.
|
||||
*/
|
||||
int get_num_samples_during_navigation(const int resolution_divider) const;
|
||||
|
||||
/* Whether adaptive sampling convergence check and filter is to happen. */
|
||||
bool work_need_adaptive_filter() const;
|
||||
|
||||
/* Calculate threshold for adaptive sampling. */
|
||||
float work_adaptive_threshold() const;
|
||||
|
||||
/* Check whether current work needs denoising.
|
||||
* Denoising is not needed if the denoiser is not configured, or when denoising is happening too
|
||||
* often.
|
||||
*
|
||||
* The delayed will be true when the denoiser is configured for use, but it was delayed for a
|
||||
* later sample, to reduce overhead.
|
||||
*
|
||||
* ready_to_display will be false if we may have a denoised result that is outdated due to
|
||||
* increased samples. */
|
||||
bool work_need_denoise(bool &delayed, bool &ready_to_display);
|
||||
|
||||
/* Check whether current work need to update display.
|
||||
*
|
||||
* The `denoiser_delayed` is what `work_need_denoise()` returned as delayed denoiser flag. */
|
||||
bool work_need_update_display(const bool denoiser_delayed);
|
||||
|
||||
/* Check whether it is time to perform rebalancing for the render work, */
|
||||
bool work_need_rebalance();
|
||||
|
||||
/* Check whether timing of the given work are usable to store timings in the `first_render_time_`
|
||||
* for the resolution divider calculation. */
|
||||
bool work_is_usable_for_first_render_estimation(const RenderWork &render_work);
|
||||
|
||||
/* Check whether timing report about the given work need to reset accumulated average time. */
|
||||
bool work_report_reset_average(const RenderWork &render_work);
|
||||
|
||||
/* Check whether render time limit has been reached (or exceeded), and if so store related
|
||||
* information in the state so that rendering is considered finished, and is possible to report
|
||||
* average render time information. */
|
||||
void check_time_limit_reached();
|
||||
|
||||
/* Helper class to keep track of task timing.
|
||||
*
|
||||
* Contains two parts: wall time and average. The wall time is an actual wall time of how long it
|
||||
* took to complete all tasks of a type. Is always advanced when PathTracer reports time update.
|
||||
*
|
||||
* The average time is used for scheduling purposes. It is estimated to be a time of how long it
|
||||
* takes to perform task on the final resolution. */
|
||||
class TimeWithAverage {
|
||||
public:
|
||||
void reset()
|
||||
{
|
||||
total_wall_time_ = 0.0;
|
||||
|
||||
average_time_accumulator_ = 0.0;
|
||||
num_average_times_ = 0;
|
||||
|
||||
last_sample_time_ = 0.0;
|
||||
}
|
||||
|
||||
void add_wall(const double time)
|
||||
{
|
||||
total_wall_time_ += time;
|
||||
}
|
||||
|
||||
void add_average(const double time, const int num_measurements = 1)
|
||||
{
|
||||
average_time_accumulator_ += time;
|
||||
num_average_times_ += num_measurements;
|
||||
last_sample_time_ = time / num_measurements;
|
||||
}
|
||||
|
||||
double get_wall() const
|
||||
{
|
||||
return total_wall_time_;
|
||||
}
|
||||
|
||||
double get_average() const
|
||||
{
|
||||
if (num_average_times_ == 0) {
|
||||
return 0;
|
||||
}
|
||||
return average_time_accumulator_ / num_average_times_;
|
||||
}
|
||||
|
||||
double get_last_sample_time() const
|
||||
{
|
||||
return last_sample_time_;
|
||||
}
|
||||
|
||||
void reset_average()
|
||||
{
|
||||
average_time_accumulator_ = 0.0;
|
||||
num_average_times_ = 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
double total_wall_time_ = 0.0;
|
||||
|
||||
double average_time_accumulator_ = 0.0;
|
||||
int num_average_times_ = 0;
|
||||
|
||||
double last_sample_time_ = 0.0;
|
||||
};
|
||||
|
||||
struct {
|
||||
bool user_is_navigating = false;
|
||||
|
||||
int resolution_divider = 1;
|
||||
|
||||
/* Number of rendered samples on top of the start sample. */
|
||||
int num_rendered_samples = 0;
|
||||
|
||||
/* Point in time the latest PathTraceDisplay work has been scheduled. */
|
||||
double last_display_update_time = 0.0;
|
||||
/* Value of -1 means display was never updated. */
|
||||
int last_display_update_sample = -1;
|
||||
|
||||
/* Point in time at which last rebalance has been performed. */
|
||||
double last_rebalance_time = 0.0;
|
||||
|
||||
/* Number of rebalance works which has been requested to be performed.
|
||||
* The path tracer might ignore the work if there is a single device rendering. */
|
||||
int num_rebalance_requested = 0;
|
||||
|
||||
/* Number of rebalance works handled which did change balance across devices. */
|
||||
int num_rebalance_changes = 0;
|
||||
|
||||
bool need_rebalance_at_next_work = false;
|
||||
|
||||
/* Denotes whether the latest performed rebalance work cause an actual rebalance of work across
|
||||
* devices. */
|
||||
bool last_rebalance_changed = false;
|
||||
|
||||
/* Threshold for adaptive sampling which will be scheduled to work when not using progressive
|
||||
* noise floor. */
|
||||
float adaptive_sampling_threshold = 0.0f;
|
||||
|
||||
bool last_work_tile_was_denoised = false;
|
||||
bool tile_result_was_written = false;
|
||||
bool postprocess_work_scheduled = false;
|
||||
bool full_frame_work_scheduled = false;
|
||||
bool full_frame_was_written = false;
|
||||
|
||||
bool path_trace_finished = false;
|
||||
bool time_limit_reached = false;
|
||||
|
||||
/* Time at which rendering started and finished. */
|
||||
double start_render_time = 0.0;
|
||||
double end_render_time = 0.0;
|
||||
|
||||
/* Measured occupancy of the render devices measured normalized to the number of samples.
|
||||
*
|
||||
* In a way it is "trailing": when scheduling new work this occupancy is measured when the
|
||||
* previous work was rendered. */
|
||||
int occupancy_num_samples = 0;
|
||||
float occupancy = 1.0f;
|
||||
} state_;
|
||||
|
||||
/* Timing of tasks which were performed at the very first render work at 100% of the
|
||||
* resolution. This timing information is used to estimate resolution divider for fats
|
||||
* navigation. */
|
||||
struct {
|
||||
double path_trace_per_sample;
|
||||
double denoise_time;
|
||||
double display_update_time;
|
||||
} first_render_time_;
|
||||
|
||||
TimeWithAverage path_trace_time_;
|
||||
TimeWithAverage adaptive_filter_time_;
|
||||
TimeWithAverage denoise_time_;
|
||||
TimeWithAverage display_update_time_;
|
||||
TimeWithAverage rebalance_time_;
|
||||
TimeWithAverage volume_guiding_denoise_time_;
|
||||
|
||||
/* Whether cryptomatte-related work will be scheduled. */
|
||||
bool need_schedule_cryptomatte_ = false;
|
||||
|
||||
/* Whether to schedule device load rebalance works.
|
||||
* Rebalancing requires some special treatment for update intervals and such, so if it's known
|
||||
* that the rebalance will be ignored (due to single-device rendering i.e.) is better to fully
|
||||
* ignore rebalancing logic. */
|
||||
bool need_schedule_rebalance_works_ = false;
|
||||
|
||||
/* Path tracing work will be scheduled for samples from within
|
||||
* [sample_offset_, sample_offset_ + num_samples_ - 1] range, inclusively. */
|
||||
int sample_offset_ = 0;
|
||||
int num_samples_ = 0;
|
||||
|
||||
/* Limit in seconds for how long path tracing is allowed to happen.
|
||||
* Zero means no limit is applied. */
|
||||
double time_limit_ = 0.0;
|
||||
|
||||
/* Headless rendering without interface. */
|
||||
bool headless_;
|
||||
|
||||
/* Background (offline) rendering. */
|
||||
bool background_;
|
||||
|
||||
/* Pixel size is used to force lower resolution render for final pass. Useful for retina or other
|
||||
* types of hi-dpi displays. */
|
||||
int pixel_size_ = 1;
|
||||
|
||||
TileManager &tile_manager_;
|
||||
|
||||
BufferParams buffer_params_;
|
||||
DenoiseParams denoiser_params_;
|
||||
|
||||
AdaptiveSampling adaptive_sampling_;
|
||||
|
||||
/* Progressively lower adaptive sampling threshold level, keeping the image at a uniform noise
|
||||
* level. */
|
||||
bool use_progressive_noise_floor_ = false;
|
||||
|
||||
/* Default value for the resolution divider which will be used when there is no render time
|
||||
* information available yet.
|
||||
* It is also what defines the upper limit of the automatically calculated resolution divider. */
|
||||
int default_start_resolution_divider_ = 1;
|
||||
|
||||
/* Initial resolution divider which will be used on render scheduler reset. */
|
||||
int start_resolution_divider_ = 0;
|
||||
|
||||
/* Calculate smallest resolution divider which will bring down actual rendering time below the
|
||||
* desired one. This call assumes linear dependency of render time from number of pixels
|
||||
* (quadratic dependency from the resolution divider): resolution divider of 2 brings render time
|
||||
* down by a factor of 4. */
|
||||
int calculate_resolution_divider_for_time(const double desired_time, const double actual_time);
|
||||
|
||||
/* If the number of samples per rendering progression should be limited because of path guiding
|
||||
* being activated or is still inside its training phase */
|
||||
int limit_samples_per_update_ = 0;
|
||||
};
|
||||
|
||||
int calculate_resolution_divider_for_resolution(const int width,
|
||||
const int height,
|
||||
const int resolution);
|
||||
|
||||
int calculate_resolution_for_divider(const int width,
|
||||
const int height,
|
||||
const int resolution_divider);
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
206
blender-5.2.0/intern/cycles/integrator/shader_eval.cpp
Normal file
206
blender-5.2.0/intern/cycles/integrator/shader_eval.cpp
Normal file
@@ -0,0 +1,206 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "kernel/device/cpu/globals.h"
|
||||
|
||||
#include "integrator/shader_eval.h"
|
||||
|
||||
#include "device/device.h"
|
||||
#include "device/queue.h"
|
||||
|
||||
#include "device/cpu/kernel.h"
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/progress.h"
|
||||
#include "util/scoped_defer.h"
|
||||
#include "util/tbb.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
ShaderEval::ShaderEval(Device *device, Progress &progress) : device_(device), progress_(progress)
|
||||
{
|
||||
DCHECK_NE(device_, nullptr);
|
||||
}
|
||||
|
||||
bool ShaderEval::eval(const ShaderEvalType type,
|
||||
const int max_num_inputs,
|
||||
const int num_channels,
|
||||
const std::function<int(device_vector<KernelShaderEvalInput> &)> &fill_input,
|
||||
const std::function<void(device_vector<float> &)> &read_output)
|
||||
{
|
||||
bool first_device = true;
|
||||
bool success = true;
|
||||
|
||||
device_->foreach_device([&](Device *device) {
|
||||
if (!first_device) {
|
||||
LOG_DEBUG << "Multi-devices are not yet fully implemented, will evaluate shader on a "
|
||||
"single device.";
|
||||
return;
|
||||
}
|
||||
first_device = false;
|
||||
|
||||
device_vector<KernelShaderEvalInput> input(device, "ShaderEval input", MEM_READ_ONLY);
|
||||
device_vector<float> output(device, "ShaderEval output", MEM_READ_WRITE);
|
||||
|
||||
/* Allocate and copy device buffers. */
|
||||
DCHECK_EQ(input.device, device);
|
||||
DCHECK_EQ(output.device, device);
|
||||
DCHECK_LE(output.size(), input.size());
|
||||
|
||||
input.alloc(max_num_inputs);
|
||||
int const num_points = fill_input(input);
|
||||
if (num_points == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
input.copy_to_device();
|
||||
output.alloc(num_points * num_channels);
|
||||
output.zero_to_device();
|
||||
|
||||
/* Evaluate on CPU or GPU. */
|
||||
success = (device->info.type == DEVICE_CPU) ?
|
||||
eval_cpu(device, type, input, output, num_points) :
|
||||
eval_gpu(device, type, input, output, num_points);
|
||||
|
||||
/* Copy data back from device if not canceled. */
|
||||
if (success) {
|
||||
output.copy_from_device(0, 1, output.size());
|
||||
read_output(output);
|
||||
}
|
||||
|
||||
input.free();
|
||||
output.free();
|
||||
});
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ShaderEval::eval_cpu(Device *device,
|
||||
const ShaderEvalType type,
|
||||
device_vector<KernelShaderEvalInput> &input,
|
||||
device_vector<float> &output,
|
||||
const int64_t work_size)
|
||||
{
|
||||
vector<ThreadKernelGlobalsCPU> *kernel_thread_globals =
|
||||
device->acquire_cpu_kernel_thread_globals();
|
||||
SCOPED_DEFER(device->release_cpu_kernel_thread_globals());
|
||||
|
||||
/* Find required kernel function. */
|
||||
const CPUKernels &kernels = Device::get_cpu_kernels();
|
||||
|
||||
/* Simple parallel_for over all work items. */
|
||||
KernelShaderEvalInput *input_data = input.data();
|
||||
float *output_data = output.data();
|
||||
bool success = true;
|
||||
|
||||
tbb::task_arena local_arena(device->info.cpu_threads);
|
||||
local_arena.execute([&]() {
|
||||
parallel_for(int64_t(0), work_size, [&](int64_t work_index) {
|
||||
/* TODO: is this fast enough? */
|
||||
if (progress_.get_cancel()) {
|
||||
success = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const int thread_index = tbb::this_task_arena::current_thread_index();
|
||||
const ThreadKernelGlobalsCPU *kg = &(*kernel_thread_globals)[thread_index];
|
||||
|
||||
switch (type) {
|
||||
case SHADER_EVAL_DISPLACE:
|
||||
kernels.shader_eval_displace(kg, input_data, output_data, work_index);
|
||||
break;
|
||||
case SHADER_EVAL_BACKGROUND:
|
||||
kernels.shader_eval_background(kg, input_data, output_data, work_index);
|
||||
break;
|
||||
case SHADER_EVAL_CURVE_SHADOW_TRANSPARENCY:
|
||||
kernels.shader_eval_curve_shadow_transparency(kg, input_data, output_data, work_index);
|
||||
break;
|
||||
case SHADER_EVAL_VOLUME_DENSITY:
|
||||
kernels.shader_eval_volume_density(kg, input_data, output_data, work_index);
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ShaderEval::eval_gpu(Device *device,
|
||||
const ShaderEvalType type,
|
||||
device_vector<KernelShaderEvalInput> &input,
|
||||
device_vector<float> &output,
|
||||
const int64_t work_size)
|
||||
{
|
||||
/* Find required kernel function. */
|
||||
DeviceKernel kernel;
|
||||
switch (type) {
|
||||
case SHADER_EVAL_DISPLACE:
|
||||
kernel = DEVICE_KERNEL_SHADER_EVAL_DISPLACE;
|
||||
break;
|
||||
case SHADER_EVAL_BACKGROUND:
|
||||
kernel = DEVICE_KERNEL_SHADER_EVAL_BACKGROUND;
|
||||
break;
|
||||
case SHADER_EVAL_CURVE_SHADOW_TRANSPARENCY:
|
||||
kernel = DEVICE_KERNEL_SHADER_EVAL_CURVE_SHADOW_TRANSPARENCY;
|
||||
break;
|
||||
case SHADER_EVAL_VOLUME_DENSITY:
|
||||
kernel = DEVICE_KERNEL_SHADER_EVAL_VOLUME_DENSITY;
|
||||
};
|
||||
|
||||
/* Create device queue. */
|
||||
unique_ptr<DeviceQueue> queue = device->gpu_queue_create();
|
||||
queue->init_execution();
|
||||
|
||||
device_vector<uint> cache_miss(device, "ShaderEval cache_miss", MEM_READ_WRITE);
|
||||
cache_miss.alloc(1);
|
||||
cache_miss[0] = false;
|
||||
|
||||
/* Execute work on GPU in chunk, so we can cancel.
|
||||
* TODO: query appropriate size from device. */
|
||||
const int32_t chunk_size = 1 << 21;
|
||||
|
||||
const device_ptr d_input = input.device_pointer;
|
||||
device_ptr d_output = output.device_pointer;
|
||||
|
||||
assert(work_size <= 0x7fffffff);
|
||||
for (int32_t d_offset = 0; d_offset < int32_t(work_size); d_offset += chunk_size) {
|
||||
int32_t d_work_size = std::min(chunk_size, int32_t(work_size) - d_offset);
|
||||
|
||||
do {
|
||||
if (device->have_error() || progress_.get_cancel()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cache_miss[0]) {
|
||||
/* Update image cache if needed. */
|
||||
device->image_load_requested_gpu(*queue);
|
||||
cache_miss[0] = false;
|
||||
|
||||
if (device->have_error() || progress_.get_cancel()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Execute shaders. */
|
||||
queue->copy_to_device(cache_miss);
|
||||
const DeviceKernelArguments args(
|
||||
&d_input, &d_output, &cache_miss.device_pointer, &d_offset, &d_work_size);
|
||||
queue->enqueue(kernel, d_work_size, args);
|
||||
queue->copy_from_device(cache_miss);
|
||||
|
||||
if (!queue->synchronize()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Keep trying until there is no more cache miss. We could try to only re-execute
|
||||
* items with cache misses, however all work items use the same shader and so
|
||||
* likely the same tiled images textures. So it's unlikely for there to be much
|
||||
* divergence as probably all or none have a cache miss. */
|
||||
} while (cache_miss[0]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
54
blender-5.2.0/intern/cycles/integrator/shader_eval.h
Normal file
54
blender-5.2.0/intern/cycles/integrator/shader_eval.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "device/memory.h"
|
||||
|
||||
#include "kernel/types.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
class Device;
|
||||
class Progress;
|
||||
|
||||
enum ShaderEvalType {
|
||||
SHADER_EVAL_DISPLACE,
|
||||
SHADER_EVAL_BACKGROUND,
|
||||
SHADER_EVAL_CURVE_SHADOW_TRANSPARENCY,
|
||||
SHADER_EVAL_VOLUME_DENSITY,
|
||||
};
|
||||
|
||||
/* ShaderEval class performs shader evaluation for background light and displacement. */
|
||||
class ShaderEval {
|
||||
public:
|
||||
ShaderEval(Device *device, Progress &progress);
|
||||
|
||||
/* Evaluate shader at points specified by KernelShaderEvalInput and write out
|
||||
* RGBA colors to output. */
|
||||
bool eval(const ShaderEvalType type,
|
||||
const int max_num_inputs,
|
||||
const int num_channels,
|
||||
const std::function<int(device_vector<KernelShaderEvalInput> &)> &fill_input,
|
||||
const std::function<void(device_vector<float> &)> &read_output);
|
||||
|
||||
protected:
|
||||
bool eval_cpu(Device *device,
|
||||
const ShaderEvalType type,
|
||||
device_vector<KernelShaderEvalInput> &input,
|
||||
device_vector<float> &output,
|
||||
const int64_t work_size);
|
||||
bool eval_gpu(Device *device,
|
||||
const ShaderEvalType type,
|
||||
device_vector<KernelShaderEvalInput> &input,
|
||||
device_vector<float> &output,
|
||||
const int64_t work_size);
|
||||
|
||||
Device *device_;
|
||||
Progress &progress_;
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
107
blender-5.2.0/intern/cycles/integrator/tile.cpp
Normal file
107
blender-5.2.0/intern/cycles/integrator/tile.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "integrator/tile.h"
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/math_base.h"
|
||||
|
||||
#include <ostream>
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, const TileSize &tile_size)
|
||||
{
|
||||
os << "size: (" << tile_size.width << ", " << tile_size.height << ")";
|
||||
os << ", num_samples: " << tile_size.num_samples;
|
||||
return os;
|
||||
}
|
||||
|
||||
ccl_device_inline uint round_down_to_power_of_two(const uint x)
|
||||
{
|
||||
if (is_power_of_two(x)) {
|
||||
return x;
|
||||
}
|
||||
|
||||
return prev_power_of_two(x);
|
||||
}
|
||||
|
||||
ccl_device_inline uint round_up_to_power_of_two(const uint x)
|
||||
{
|
||||
if (is_power_of_two(x)) {
|
||||
return x;
|
||||
}
|
||||
|
||||
return next_power_of_two(x);
|
||||
}
|
||||
|
||||
TileSize tile_calculate_best_size(const bool accel_rt,
|
||||
const int2 &image_size,
|
||||
const int num_samples,
|
||||
const int max_num_path_states,
|
||||
const float scrambling_distance)
|
||||
{
|
||||
if (max_num_path_states == 1) {
|
||||
/* Simple case: avoid any calculation, which could cause rounding issues. */
|
||||
return TileSize(1, 1, 1);
|
||||
}
|
||||
|
||||
const int64_t num_pixels = image_size.x * image_size.y;
|
||||
const int64_t num_pixel_samples = num_pixels * num_samples;
|
||||
|
||||
if (max_num_path_states >= num_pixel_samples) {
|
||||
/* Image fully fits into the state (could be border render, for example). */
|
||||
return TileSize(image_size.x, image_size.y, num_samples);
|
||||
}
|
||||
|
||||
/* The idea here is to keep number of samples per tile as much as possible to improve coherency
|
||||
* across threads.
|
||||
*
|
||||
* Some general ideas:
|
||||
* - Prefer smaller tiles with more samples, which improves spatial coherency of paths.
|
||||
* - Keep values a power of two, for more integer fit into the maximum number of paths. */
|
||||
|
||||
TileSize tile_size;
|
||||
const int num_path_states_per_sample = max_num_path_states / num_samples;
|
||||
if (scrambling_distance < 0.9f && accel_rt) {
|
||||
/* Prefer large tiles for scrambling distance, bounded by max num path states. */
|
||||
tile_size.width = min(image_size.x, max_num_path_states);
|
||||
tile_size.height = min(image_size.y, max(max_num_path_states / tile_size.width, 1));
|
||||
}
|
||||
else {
|
||||
/* Calculate tile size as if it is the most possible one to fit an entire range of samples.
|
||||
* The idea here is to keep tiles as small as possible, and keep device occupied by scheduling
|
||||
* multiple tiles with the same coordinates rendering different samples. */
|
||||
|
||||
if (num_path_states_per_sample != 0) {
|
||||
tile_size.width = round_down_to_power_of_two(lround(sqrt(num_path_states_per_sample)));
|
||||
tile_size.height = tile_size.width;
|
||||
}
|
||||
else {
|
||||
tile_size.width = tile_size.height = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_samples == 1) {
|
||||
tile_size.num_samples = 1;
|
||||
}
|
||||
else {
|
||||
/* Heuristic here is to have more uniform division of the sample range: for example prefer
|
||||
* [32 <38 times>, 8] over [1024, 200]. This allows to greedily add more tiles early on. */
|
||||
tile_size.num_samples = min(round_up_to_power_of_two(lround(sqrt(num_samples / 2))),
|
||||
static_cast<uint>(num_samples));
|
||||
|
||||
const int tile_area = tile_size.width * tile_size.height;
|
||||
tile_size.num_samples = min(tile_size.num_samples, max_num_path_states / tile_area);
|
||||
}
|
||||
|
||||
DCHECK_GE(tile_size.width, 1);
|
||||
DCHECK_GE(tile_size.height, 1);
|
||||
DCHECK_GE(tile_size.num_samples, 1);
|
||||
DCHECK_LE(tile_size.width * tile_size.height * tile_size.num_samples, max_num_path_states);
|
||||
|
||||
return tile_size;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
46
blender-5.2.0/intern/cycles/integrator/tile.h
Normal file
46
blender-5.2.0/intern/cycles/integrator/tile.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iosfwd>
|
||||
|
||||
#include "util/types_int2.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
struct TileSize {
|
||||
TileSize() = default;
|
||||
|
||||
TileSize(const int width, const int height, const int num_samples)
|
||||
: width(width), height(height), num_samples(num_samples)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const TileSize &other) const
|
||||
{
|
||||
return width == other.width && height == other.height && num_samples == other.num_samples;
|
||||
}
|
||||
bool operator!=(const TileSize &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
int width = 0, height = 0;
|
||||
int num_samples = 0;
|
||||
};
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, const TileSize &tile_size);
|
||||
|
||||
/* Calculate tile size which is best suitable for rendering image of a given size with given number
|
||||
* of active path states.
|
||||
* Will attempt to provide best guess to keep path tracing threads of a device as localized as
|
||||
* possible, and have as many threads active for every tile as possible. */
|
||||
TileSize tile_calculate_best_size(const bool accel_rt,
|
||||
const int2 &image_size,
|
||||
const int num_samples,
|
||||
const int max_num_path_states,
|
||||
const float scrambling_distance);
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
88
blender-5.2.0/intern/cycles/integrator/work_balancer.cpp
Normal file
88
blender-5.2.0/intern/cycles/integrator/work_balancer.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "integrator/work_balancer.h"
|
||||
|
||||
#include "util/math_base.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
void work_balance_do_initial(vector<WorkBalanceInfo> &work_balance_infos)
|
||||
{
|
||||
const int num_infos = work_balance_infos.size();
|
||||
|
||||
if (num_infos == 1) {
|
||||
work_balance_infos[0].weight = 1.0;
|
||||
return;
|
||||
}
|
||||
if (num_infos == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* There is no statistics available, so start with an equal distribution. */
|
||||
const double weight = 1.0 / num_infos;
|
||||
for (WorkBalanceInfo &balance_info : work_balance_infos) {
|
||||
balance_info.weight = weight;
|
||||
}
|
||||
}
|
||||
|
||||
static double calculate_total_time(const vector<WorkBalanceInfo> &work_balance_infos)
|
||||
{
|
||||
double total_time = 0;
|
||||
for (const WorkBalanceInfo &info : work_balance_infos) {
|
||||
total_time += info.time_spent;
|
||||
}
|
||||
return total_time;
|
||||
}
|
||||
|
||||
/* The balance is based on equalizing time which devices spent performing a task. Assume that
|
||||
* average of the observed times is usable for estimating whether more or less work is to be
|
||||
* scheduled, and how difference in the work scheduling is needed. */
|
||||
|
||||
bool work_balance_do_rebalance(vector<WorkBalanceInfo> &work_balance_infos)
|
||||
{
|
||||
const int num_infos = work_balance_infos.size();
|
||||
|
||||
const double total_time = calculate_total_time(work_balance_infos);
|
||||
const double time_average = total_time / num_infos;
|
||||
|
||||
double total_weight = 0;
|
||||
vector<double> new_weights;
|
||||
new_weights.reserve(num_infos);
|
||||
|
||||
/* Equalize the overall average time. This means that we don't make it so every work will perform
|
||||
* amount of work based on the current average, but that after the weights changes the time will
|
||||
* equalize.
|
||||
* Can think of it that if one of the devices is 10% faster than another, then one device needs
|
||||
* to do 5% less of the current work, and another needs to do 5% more. */
|
||||
const double lerp_weight = 1.0 / num_infos;
|
||||
|
||||
bool has_big_difference = false;
|
||||
|
||||
for (const WorkBalanceInfo &info : work_balance_infos) {
|
||||
const double time_target = mix(info.time_spent, time_average, lerp_weight);
|
||||
const double new_weight = info.weight * time_target / info.time_spent;
|
||||
new_weights.push_back(new_weight);
|
||||
total_weight += new_weight;
|
||||
|
||||
if (std::fabs(1.0 - time_target / time_average) > 0.02) {
|
||||
has_big_difference = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_big_difference) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const double total_weight_inv = 1.0 / total_weight;
|
||||
for (int i = 0; i < num_infos; ++i) {
|
||||
WorkBalanceInfo &info = work_balance_infos[i];
|
||||
info.weight = new_weights[i] * total_weight_inv;
|
||||
info.time_spent = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
30
blender-5.2.0/intern/cycles/integrator/work_balancer.h
Normal file
30
blender-5.2.0/intern/cycles/integrator/work_balancer.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/vector.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
struct WorkBalanceInfo {
|
||||
/* Time spent performing corresponding work. */
|
||||
double time_spent = 0;
|
||||
|
||||
/* Average occupancy of the device while performing the work. */
|
||||
float occupancy = 1.0f;
|
||||
|
||||
/* Normalized weight, which is ready to be used for work balancing (like calculating fraction of
|
||||
* the big tile which is to be rendered on the device). */
|
||||
double weight = 1.0;
|
||||
};
|
||||
|
||||
/* Balance work for an initial render integration, before any statistics is known. */
|
||||
void work_balance_do_initial(vector<WorkBalanceInfo> &work_balance_infos);
|
||||
|
||||
/* Rebalance work after statistics has been accumulated.
|
||||
* Returns true if the balancing did change. */
|
||||
bool work_balance_do_rebalance(vector<WorkBalanceInfo> &work_balance_infos);
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
139
blender-5.2.0/intern/cycles/integrator/work_tile_scheduler.cpp
Normal file
139
blender-5.2.0/intern/cycles/integrator/work_tile_scheduler.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "integrator/work_tile_scheduler.h"
|
||||
|
||||
#include "device/queue.h"
|
||||
#include "integrator/tile.h"
|
||||
#include "session/buffers.h"
|
||||
#include "util/log.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
WorkTileScheduler::WorkTileScheduler() = default;
|
||||
|
||||
void WorkTileScheduler::set_accelerated_rt(bool accelerated_rt)
|
||||
{
|
||||
accelerated_rt_ = accelerated_rt;
|
||||
}
|
||||
|
||||
void WorkTileScheduler::set_max_num_path_states(const int max_num_path_states)
|
||||
{
|
||||
max_num_path_states_ = max_num_path_states;
|
||||
}
|
||||
|
||||
void WorkTileScheduler::reset(const BufferParams &buffer_params,
|
||||
const int sample_start,
|
||||
const int samples_num,
|
||||
const int sample_offset,
|
||||
const float scrambling_distance)
|
||||
{
|
||||
/* Image buffer parameters. */
|
||||
image_full_offset_px_.x = buffer_params.full_x;
|
||||
image_full_offset_px_.y = buffer_params.full_y;
|
||||
|
||||
image_size_px_ = make_int2(buffer_params.width, buffer_params.height);
|
||||
scrambling_distance_ = scrambling_distance;
|
||||
|
||||
offset_ = buffer_params.offset;
|
||||
stride_ = buffer_params.stride;
|
||||
|
||||
/* Samples parameters. */
|
||||
sample_start_ = sample_start;
|
||||
samples_num_ = samples_num;
|
||||
sample_offset_ = sample_offset;
|
||||
|
||||
/* Initialize new scheduling. */
|
||||
reset_scheduler_state();
|
||||
}
|
||||
|
||||
void WorkTileScheduler::reset_scheduler_state()
|
||||
{
|
||||
tile_size_ = tile_calculate_best_size(
|
||||
accelerated_rt_, image_size_px_, samples_num_, max_num_path_states_, scrambling_distance_);
|
||||
|
||||
const int num_path_states_in_tile = tile_size_.width * tile_size_.height *
|
||||
tile_size_.num_samples;
|
||||
|
||||
if (num_path_states_in_tile == 0) {
|
||||
LOG_DEBUG << "Will not schedule any tiles: no work remained for the device";
|
||||
num_tiles_x_ = 0;
|
||||
num_tiles_y_ = 0;
|
||||
num_tiles_per_sample_range_ = 0;
|
||||
}
|
||||
else {
|
||||
const int num_tiles = max_num_path_states_ / num_path_states_in_tile;
|
||||
LOG_DEBUG << "Will schedule " << num_tiles << " tiles of " << tile_size_;
|
||||
|
||||
/* The logging is based on multiple tiles scheduled, ignoring overhead of multi-tile
|
||||
* scheduling and purely focusing on the number of used path states. */
|
||||
LOG_DEBUG << "Number of unused path states: "
|
||||
<< max_num_path_states_ - num_tiles * num_path_states_in_tile;
|
||||
|
||||
num_tiles_x_ = divide_up(image_size_px_.x, tile_size_.width);
|
||||
num_tiles_y_ = divide_up(image_size_px_.y, tile_size_.height);
|
||||
num_tiles_per_sample_range_ = divide_up(samples_num_, tile_size_.num_samples);
|
||||
}
|
||||
|
||||
total_tiles_num_ = num_tiles_x_ * num_tiles_y_;
|
||||
|
||||
next_work_index_ = 0;
|
||||
total_work_size_ = total_tiles_num_ * num_tiles_per_sample_range_;
|
||||
}
|
||||
|
||||
bool WorkTileScheduler::get_work(KernelWorkTile *work_tile_, const int max_work_size)
|
||||
{
|
||||
/* Note that the `max_work_size` can be higher than the `max_num_path_states_`: this is because
|
||||
* the path trace work can decide to use smaller tile sizes and greedily schedule multiple tiles,
|
||||
* improving overall device occupancy.
|
||||
* So the `max_num_path_states_` is a "scheduling unit", and the `max_work_size` is a "scheduling
|
||||
* limit". */
|
||||
|
||||
DCHECK_NE(max_num_path_states_, 0);
|
||||
|
||||
const int work_index = next_work_index_++;
|
||||
if (work_index >= total_work_size_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int sample_range_index = work_index % num_tiles_per_sample_range_;
|
||||
const int start_sample = sample_range_index * tile_size_.num_samples;
|
||||
const int tile_index = work_index / num_tiles_per_sample_range_;
|
||||
const int tile_y = tile_index / num_tiles_x_;
|
||||
const int tile_x = tile_index - tile_y * num_tiles_x_;
|
||||
|
||||
KernelWorkTile work_tile;
|
||||
work_tile.x = tile_x * tile_size_.width;
|
||||
work_tile.y = tile_y * tile_size_.height;
|
||||
work_tile.w = tile_size_.width;
|
||||
work_tile.h = tile_size_.height;
|
||||
work_tile.start_sample = sample_start_ + start_sample;
|
||||
work_tile.num_samples = min(tile_size_.num_samples, samples_num_ - start_sample);
|
||||
work_tile.sample_offset = sample_offset_;
|
||||
work_tile.offset = offset_;
|
||||
work_tile.stride = stride_;
|
||||
|
||||
work_tile.w = min(work_tile.w, image_size_px_.x - work_tile.x);
|
||||
work_tile.h = min(work_tile.h, image_size_px_.y - work_tile.y);
|
||||
|
||||
work_tile.x += image_full_offset_px_.x;
|
||||
work_tile.y += image_full_offset_px_.y;
|
||||
|
||||
const int tile_work_size = work_tile.w * work_tile.h * work_tile.num_samples;
|
||||
|
||||
DCHECK_GT(tile_work_size, 0);
|
||||
|
||||
if (max_work_size && tile_work_size > max_work_size) {
|
||||
/* The work did not fit into the requested limit of the work size. Unschedule the tile,
|
||||
* so it can be picked up again later. */
|
||||
next_work_index_--;
|
||||
return false;
|
||||
}
|
||||
|
||||
*work_tile_ = work_tile;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
101
blender-5.2.0/intern/cycles/integrator/work_tile_scheduler.h
Normal file
101
blender-5.2.0/intern/cycles/integrator/work_tile_scheduler.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "integrator/tile.h"
|
||||
|
||||
#include "util/types_int2.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
class BufferParams;
|
||||
|
||||
struct KernelWorkTile;
|
||||
|
||||
/* Scheduler of device work tiles.
|
||||
* Takes care of feeding multiple devices running in parallel a work which needs to be done. */
|
||||
class WorkTileScheduler {
|
||||
public:
|
||||
WorkTileScheduler();
|
||||
|
||||
/* To indicate if there is accelerated RT support. */
|
||||
void set_accelerated_rt(bool accelerated_rt);
|
||||
|
||||
/* MAximum path states which are allowed to be used by a single scheduled work tile.
|
||||
*
|
||||
* Affects the scheduled work size: the work size will be as big as possible, but will not exceed
|
||||
* this number of states. */
|
||||
void set_max_num_path_states(const int max_num_path_states);
|
||||
|
||||
/* Scheduling will happen for pixels within a big tile denotes by its parameters. */
|
||||
void reset(const BufferParams &buffer_params,
|
||||
const int sample_start,
|
||||
const int samples_num,
|
||||
const int sample_offset,
|
||||
float scrambling_distance);
|
||||
|
||||
/* Get work for a device.
|
||||
* Returns true if there is still work to be done and initialize the work tile to all
|
||||
* parameters of this work. If there is nothing remaining to be done, returns false and the
|
||||
* work tile is kept unchanged.
|
||||
*
|
||||
* Optionally pass max_work_size to do nothing if there is no tile small enough. */
|
||||
bool get_work(KernelWorkTile *work_tile, const int max_work_size = 0);
|
||||
|
||||
protected:
|
||||
void reset_scheduler_state();
|
||||
|
||||
/* Used to indicate if there is accelerated ray tracing. */
|
||||
bool accelerated_rt_ = false;
|
||||
|
||||
/* Maximum allowed path states to be used.
|
||||
*
|
||||
* TODO(sergey): Naming can be improved. The fact that this is a limiting factor based on the
|
||||
* number of path states is kind of a detail. Is there a more generic term from the scheduler
|
||||
* point of view? */
|
||||
int max_num_path_states_ = 0;
|
||||
|
||||
/* Offset in pixels within a global buffer. */
|
||||
int2 image_full_offset_px_ = make_int2(0, 0);
|
||||
|
||||
/* dimensions of the currently rendering image in pixels. */
|
||||
int2 image_size_px_ = make_int2(0, 0);
|
||||
|
||||
/* Offset and stride of the buffer within which scheduling is happening.
|
||||
* Will be passed over to the KernelWorkTile. */
|
||||
int offset_, stride_;
|
||||
|
||||
/* Scrambling Distance requires adapted tile size */
|
||||
float scrambling_distance_;
|
||||
|
||||
/* Start sample of index and number of samples which are to be rendered.
|
||||
* The scheduler will cover samples range of [start, start + num] over the entire image
|
||||
* (splitting into a smaller work tiles). */
|
||||
int sample_start_ = 0;
|
||||
int samples_num_ = 0;
|
||||
int sample_offset_ = 0;
|
||||
|
||||
/* Tile size which be scheduled for rendering. */
|
||||
TileSize tile_size_;
|
||||
|
||||
/* Number of tiles in X and Y axis of the image. */
|
||||
int num_tiles_x_, num_tiles_y_;
|
||||
|
||||
/* Total number of tiles on the image.
|
||||
* Pre-calculated as `num_tiles_x_ * num_tiles_y_` and re-used in the `get_work()`.
|
||||
*
|
||||
* TODO(sergey): Is this an over-optimization? Maybe it's unmeasurable to calculate the value
|
||||
* in the `get_work()`? */
|
||||
int total_tiles_num_ = 0;
|
||||
|
||||
/* In the case when the number of samples in the `tile_size_` is lower than samples_num_ denotes
|
||||
* how many tiles are to be "stacked" to cover the entire requested range of samples. */
|
||||
int num_tiles_per_sample_range_ = 0;
|
||||
|
||||
int next_work_index_ = 0;
|
||||
int total_work_size_ = 0;
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
Reference in New Issue
Block a user