Cycles: Add fundamental support for upscaling denoisers

Adds basic infrastructure for denoisers that can also upscale for
viewport rendering, by taking advantage of the existing resolution
divider functionality.

The implementation basically tracks two resolution dividers, the normal
one and an additional one that also has a denoiser upscale factor
applied. It then uses the latter one for rendering and processing on
noisy render buffers, and the former one for any processing happening
after denoising. This has the advantage of allowing an additional
resolution divider on top of denoiser upscaling (so can also set the
pixel size to something other than 1x and that is still being
respected). The resolution divider was made into a floating point value
to allow fractional scale factors.

Pull Request: https://projects.blender.org/blender/blender/pulls/151133
This commit is contained in:
Patrick Mours 2026-03-31 14:05:28 +02:00 • committed by Patrick Mours
parent 6062450617
commit 02171cc356
31 changed files with 432 additions and 327 deletions

View file

@ -560,6 +560,7 @@ void BlenderSync::sync_integrator(blender::ViewLayer &b_view_layer,
integrator->set_use_denoise_pass_motion(denoise_params.temporally_stable);
integrator->set_denoiser_prefilter(denoise_params.prefilter);
integrator->set_denoiser_quality(denoise_params.quality);
integrator->set_denoiser_upscale_factor(denoise_params.upscale_factor);
}
/* UPDATE_NONE as we don't want to tag the integrator as modified (this was done by the

View file

@ -82,6 +82,7 @@ NODE_DEFINE(DenoiseParams)
SOCKET_ENUM(prefilter, "Prefilter", *prefilter_enum, DENOISER_PREFILTER_FAST);
SOCKET_ENUM(quality, "Quality", *quality_enum, DENOISER_QUALITY_HIGH);
SOCKET_FLOAT(upscale_factor, "Upscale Factor", 1.0f);
return type;
}

View file

@ -77,6 +77,7 @@ class DenoiseParams : public Node {
DenoiserPrefilter prefilter = DENOISER_PREFILTER_FAST;
DenoiserQuality quality = DENOISER_QUALITY_HIGH;
float upscale_factor = 1.0f;
static const NodeEnum *get_type_enum();
static const NodeEnum *get_prefilter_enum();

View file

@ -30,7 +30,7 @@ struct DeviceKernelArguments {
HIPRT_GLOBAL_STACK,
};
static const int MAX_ARGS = 18;
static const int MAX_ARGS = 19;
Type types[MAX_ARGS];
void *values[MAX_ARGS];
size_t sizes[MAX_ARGS];

View file

@ -141,6 +141,7 @@ DenoiseParams get_effective_denoise_params(Device *denoiser_device,
/* 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;
}

View file

@ -90,8 +90,9 @@ class Denoiser {
* 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,
const int num_samples,
int num_samples,
bool allow_inplace_modification) = 0;
/* Get a device which is used to perform actual denoising.

View file

@ -30,28 +30,22 @@ DenoiserGPU::~DenoiserGPU() // NOLINT
}
bool DenoiserGPU::denoise_buffer(const BufferParams &buffer_params,
const BufferParams &denoised_buffer_params,
RenderBuffers *render_buffers,
const int num_samples,
bool allow_inplace_modification)
const bool allow_inplace_modification)
{
Device *denoiser_device = get_denoiser_device();
if (!denoiser_device) {
return false;
}
DenoiseTask task;
task.params = params_;
task.num_samples = num_samples;
task.buffer_params = buffer_params;
task.allow_inplace_modification = allow_inplace_modification;
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;
task.render_buffers = render_buffers;
}
else {
LOG_DEBUG << "Creating temporary buffer on denoiser device.";
@ -65,7 +59,7 @@ bool DenoiserGPU::denoise_buffer(const BufferParams &buffer_params,
render_buffers->copy_from_device();
local_render_buffers.reset(buffer_params);
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
@ -75,45 +69,51 @@ bool DenoiserGPU::denoise_buffer(const BufferParams &buffer_params,
sizeof(float) * local_render_buffers.buffer.size());
denoiser_queue_->copy_to_device(local_render_buffers.buffer);
task.render_buffers = &local_render_buffers;
task.allow_inplace_modification = true;
}
const bool denoise_result = denoise_buffer(task);
{
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);
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, buffer_params, &local_render_buffers, local_render_buffers.params);
render_buffers_host_copy_denoised(render_buffers,
denoised_buffer_params,
&local_render_buffers,
local_render_buffers.params);
render_buffers->copy_to_device();
}
return denoise_result;
}
bool DenoiserGPU::denoise_buffer(const DenoiseTask &task)
{
DenoiseContext context(denoiser_device_, task);
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. */
denoise_pass(context, PASS_COMBINED);
denoise_pass(context, PASS_SHADOW_CATCHER_MATTE);
/* Passes which do not need albedo and hence if real is present it needs to become fake. */
denoise_pass(context, PASS_SHADOW_CATCHER);
return true;
}
@ -161,42 +161,43 @@ bool DenoiserGPU::denoise_filter_guiding_preprocess(const DenoiseContext &contex
denoise_filter_guiding_flip_y(context);
}
DenoiserGPU::DenoiseContext::DenoiseContext(Device *device, const DenoiseTask &task)
: denoise_params(task.params),
render_buffers(task.render_buffers),
buffer_params(task.buffer_params),
DenoiserGPU::DenoiseContext::DenoiseContext(Device *device,
const DenoiseParams &params,
const BufferParams &buffer_params,
const BufferParams &denoised_buffer_params,
RenderBuffers *render_buffers,
const int num_samples,
const bool allow_inplace_modification)
: 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),
num_samples(task.num_samples)
use_guiding_passes(params.use_pass_albedo || params.use_pass_normal ||
params.temporally_stable),
num_samples(num_samples)
{
num_input_passes = 1;
if (denoise_params.use_pass_albedo) {
num_input_passes += 1;
use_pass_albedo = true;
pass_motion = buffer_params.get_pass_offset(PASS_MOTION);
pass_sample_count = buffer_params.get_pass_offset(PASS_SAMPLE_COUNT);
if (params.use_pass_albedo) {
pass_denoising_albedo = buffer_params.get_pass_offset(PASS_DENOISING_ALBEDO);
if (denoise_params.use_pass_normal) {
num_input_passes += 1;
use_pass_normal = true;
pass_denoising_normal = buffer_params.get_pass_offset(PASS_DENOISING_NORMAL);
}
}
if (params.use_pass_normal) {
pass_denoising_normal = buffer_params.get_pass_offset(PASS_DENOISING_NORMAL);
}
if (denoise_params.temporally_stable) {
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;
num_input_passes += 1;
use_pass_motion = true;
pass_motion = buffer_params.get_pass_offset(PASS_MOTION);
}
use_guiding_passes = (num_input_passes - 1) > 0;
if (use_guiding_passes) {
if (task.allow_inplace_modification) {
if (allow_inplace_modification) {
guiding_params.device_pointer = render_buffers->buffer.device_pointer;
guiding_params.pass_albedo = pass_denoising_albedo;
@ -208,15 +209,15 @@ DenoiserGPU::DenoiseContext::DenoiseContext(Device *device, const DenoiseTask &t
}
else {
guiding_params.pass_stride = 0;
if (use_pass_albedo) {
if (params.use_pass_albedo) {
guiding_params.pass_albedo = guiding_params.pass_stride;
guiding_params.pass_stride += 3;
}
if (use_pass_normal) {
if (params.use_pass_normal) {
guiding_params.pass_normal = guiding_params.pass_stride;
guiding_params.pass_stride += 3;
}
if (use_pass_motion) {
if (params.temporally_stable) {
guiding_params.pass_flow = guiding_params.pass_stride;
guiding_params.pass_stride += 2;
}
@ -228,18 +229,16 @@ DenoiserGPU::DenoiseContext::DenoiseContext(Device *device, const DenoiseTask &t
guiding_params.device_pointer = guiding_buffer.device_pointer;
}
}
pass_sample_count = buffer_params.get_pass_offset(PASS_SAMPLE_COUNT);
}
bool DenoiserGPU::denoise_filter_color_postprocess(const DenoiseContext &context,
const DenoisePass &pass)
{
if (!denoise_filter_color_flip_y(context, pass)) {
if (!denoise_filter_color_flip_y(context, context.denoised_buffer_params, pass)) {
return false;
}
const BufferParams &buffer_params = context.buffer_params;
const BufferParams &buffer_params = context.denoised_buffer_params;
const int work_size = buffer_params.width * buffer_params.height;
@ -250,13 +249,18 @@ bool DenoiserGPU::denoise_filter_color_postprocess(const DenoiseContext &context
&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);
&pass.use_compositing,
&params_.upscale_factor);
return denoiser_queue_->enqueue(DEVICE_KERNEL_FILTER_COLOR_POSTPROCESS, work_size, args);
}
@ -270,7 +274,7 @@ bool DenoiserGPU::denoise_filter_color_preprocess(const DenoiseContext &context,
return true;
}
if (!denoise_filter_color_flip_y(context, pass)) {
if (!denoise_filter_color_flip_y(context, context.buffer_params, pass)) {
return false;
}
@ -292,6 +296,7 @@ bool DenoiserGPU::denoise_filter_color_preprocess(const DenoiseContext &context,
}
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) {
@ -300,8 +305,6 @@ bool DenoiserGPU::denoise_filter_color_flip_y(const DenoiseContext &context,
return true;
}
const BufferParams &buffer_params = context.buffer_params;
const int work_size = buffer_params.width * buffer_params.height / 2;
const DeviceKernelArguments args(&context.render_buffers->buffer.device_pointer,
@ -404,31 +407,31 @@ void DenoiserGPU::denoise_color_read(const DenoiseContext &context, const Denois
pass_accessor.get_render_tile_pixels(context.render_buffers, buffer_params, destination);
}
void DenoiserGPU::denoise_pass(DenoiseContext &context, PassType pass_type)
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;
return true;
}
if (pass.denoised_offset == PASS_UNUSED) {
LOG_DFATAL << "Missing denoised pass " << pass_type_as_string(pass_type);
return;
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;
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;
return false;
}
}
@ -436,12 +439,12 @@ void DenoiserGPU::denoise_pass(DenoiseContext &context, PassType pass_type)
denoise_color_read(context, pass);
if (!denoise_filter_color_preprocess(context, pass)) {
LOG_ERROR << "Error converting denoising passes to RGB buffer.";
return;
return false;
}
if (!denoise_run(context, pass)) {
LOG_ERROR << "Error running denoiser.";
return;
return false;
}
/* Store result in the combined pass of the render buffer.
@ -449,10 +452,10 @@ void DenoiserGPU::denoise_pass(DenoiseContext &context, PassType pass_type)
* 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;
return false;
}
denoiser_queue_->synchronize();
return denoiser_queue_->synchronize();
}
CCL_NAMESPACE_END

View file

@ -19,34 +19,15 @@ class DenoiserGPU : public Denoiser {
~DenoiserGPU() override;
bool denoise_buffer(const BufferParams &buffer_params,
const BufferParams &denoised_buffer_params,
RenderBuffers *render_buffers,
const int num_samples,
int num_samples,
bool allow_inplace_modification) override;
protected:
class DenoisePass;
class DenoiseContext;
/* All the parameters needed to perform buffer denoising on a device.
* Is not really a task in its canonical terms (as in, is not an asynchronous running task). Is
* more like a wrapper for all the arguments and parameters needed to perform denoising. Is a
* single place where they are all listed, so that it's not required to modify all device methods
* when these parameters do change. */
class DenoiseTask {
public:
DenoiseParams params;
int num_samples;
RenderBuffers *render_buffers;
BufferParams buffer_params;
/* Allow 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. */
bool allow_inplace_modification;
};
/* Make sure the GPU denoiser is created and configured. */
virtual bool denoise_ensure(DenoiseContext &context);
@ -67,7 +48,9 @@ class DenoiserGPU : public Denoiser {
* 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 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);
@ -78,10 +61,9 @@ class DenoiserGPU : public Denoiser {
* preprocess them for every pass which is being denoised. */
bool denoise_filter_guiding_preprocess(const DenoiseContext &context);
void denoise_pass(DenoiseContext &context, PassType pass_type);
bool denoise_pass(DenoiseContext &context, PassType pass_type);
/* Returns true if task is fully handled. */
virtual bool denoise_buffer(const DenoiseTask &task);
virtual bool denoise_run(const DenoiseContext &context, const DenoisePass &pass) = 0;
unique_ptr<DeviceQueue> denoiser_queue_;
@ -111,12 +93,19 @@ class DenoiserGPU : public Denoiser {
class DenoiseContext {
public:
explicit DenoiseContext(Device *device, const DenoiseTask &task);
explicit DenoiseContext(Device *device,
const DenoiseParams &params,
const BufferParams &buffer_params,
const BufferParams &denoised_buffer_params,
RenderBuffers *render_buffers,
int num_samples,
bool allow_inplace_modification);
const DenoiseParams &denoise_params;
RenderBuffers *render_buffers = nullptr;
const BufferParams &buffer_params;
const BufferParams &denoised_buffer_params;
/* Previous output. */
struct {
@ -143,12 +132,7 @@ class DenoiserGPU : public Denoiser {
int pass_stride = -1;
} guiding_params;
/* Number of input passes. Including the color and extra auxiliary passes. */
int num_input_passes = 0;
bool use_guiding_passes = false;
bool use_pass_albedo = false;
bool use_pass_normal = false;
bool use_pass_motion = false;
const bool use_guiding_passes = false;
int num_samples = 0;

View file

@ -679,9 +679,10 @@ static void copy_render_buffers_to_device(unique_ptr<DeviceQueue> &queue,
#endif
bool OIDNDenoiser::denoise_buffer(const BufferParams &buffer_params,
const BufferParams & /*denoised_buffer_params*/,
RenderBuffers *render_buffers,
const int num_samples,
bool allow_inplace_modification)
const bool allow_inplace_modification)
{
DCHECK(openimagedenoise_supported())
<< "OpenImageDenoise is not supported on this platform or build.";

View file

@ -21,8 +21,9 @@ class OIDNDenoiser : public Denoiser {
OIDNDenoiser(Device *denoiser_device, const DenoiseParams &params);
bool denoise_buffer(const BufferParams &buffer_params,
const BufferParams &denoised_buffer_params,
RenderBuffers *render_buffers,
const int num_samples,
int num_samples,
bool allow_inplace_modification) override;
#ifdef WITH_OPENIMAGEDENOISE

View file

@ -160,15 +160,6 @@ OIDNDenoiserGPU::OIDNDenoiserGPU(Device *denoiser_device, const DenoiseParams &p
DCHECK_EQ(params.type, DENOISER_OPENIMAGEDENOISE);
}
bool OIDNDenoiserGPU::denoise_buffer(const BufferParams &buffer_params,
RenderBuffers *render_buffers,
const int num_samples,
bool allow_inplace_modification)
{
return DenoiserGPU::denoise_buffer(
buffer_params, render_buffers, num_samples, allow_inplace_modification);
}
uint OIDNDenoiserGPU::get_device_type_mask() const
{
uint device_mask = 0;
@ -224,11 +215,11 @@ bool OIDNDenoiserGPU::commit_and_execute_filter(OIDNFilter filter, ExecMode mode
bool OIDNDenoiserGPU::denoise_create_if_needed(DenoiseContext &context)
{
const bool recreate_denoiser = (base_.oidn_device_ == nullptr) ||
(base_.oidn_filter_ == nullptr) ||
(base_.use_pass_albedo_ != context.use_pass_albedo) ||
(base_.use_pass_normal_ != context.use_pass_normal) ||
(base_.quality_ != params_.quality);
const bool recreate_denoiser =
(base_.oidn_device_ == nullptr) || (base_.oidn_filter_ == nullptr) ||
(base_.use_pass_albedo_ != context.denoise_params.use_pass_albedo) ||
(base_.use_pass_normal_ != context.denoise_params.use_pass_normal) ||
(base_.quality_ != params_.quality);
if (!recreate_denoiser) {
return true;
}
@ -282,7 +273,9 @@ bool OIDNDenoiserGPU::denoise_create_if_needed(DenoiseContext &context)
oidnCommitDevice(base_.oidn_device_);
base_.load_custom_weights();
return base_.create_filters(params_.quality, context.use_pass_albedo, context.use_pass_normal);
return base_.create_filters(params_.quality,
context.denoise_params.use_pass_albedo,
context.denoise_params.use_pass_normal);
}
bool OIDNDenoiserGPU::denoise_configure_if_needed(DenoiseContext &context)
@ -318,13 +311,23 @@ bool OIDNDenoiserGPU::denoise_run(const DenoiseContext &context, const DenoisePa
pass_stride_in_bytes * context.buffer_params.stride);
/* Optional albedo and color passes. */
if (context.num_input_passes > 1) {
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;
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 (context.use_pass_albedo) {
set_filter_pass(base_.oidn_filter_,
if (context.denoise_params.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,
@ -334,35 +337,35 @@ bool OIDNDenoiserGPU::denoise_run(const DenoiseContext &context, const DenoisePa
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);
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 (!commit_and_execute_filter(base_.albedo_filter_, ExecMode::ASYNC)) {
return false;
}
}
}
if (context.use_pass_normal) {
set_filter_pass(base_.oidn_filter_,
if (context.denoise_params.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,
@ -372,30 +375,18 @@ bool OIDNDenoiserGPU::denoise_run(const DenoiseContext &context, const DenoisePa
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);
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;
}
if (!commit_and_execute_filter(base_.normal_filter_, ExecMode::ASYNC)) {
return false;
}
}
}

View file

@ -19,11 +19,6 @@ class OIDNDenoiserGPU : public DenoiserGPU {
OIDNDenoiserGPU(Device *denoiser_device, const DenoiseParams &params);
bool denoise_buffer(const BufferParams &buffer_params,
RenderBuffers *render_buffers,
const int num_samples,
bool allow_inplace_modification) override;
static bool is_device_supported(const DeviceInfo &device);
protected:

View file

@ -42,21 +42,31 @@ bool OptiXDenoiser::is_device_supported(const DeviceInfo &device)
return false;
}
bool OptiXDenoiser::denoise_buffer(const DenoiseTask &task)
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)
{
OptiXDevice *const optix_device = static_cast<OptiXDevice *>(denoiser_device_);
const CUDAContextScope scope(optix_device);
return DenoiserGPU::denoise_buffer(task);
return DenoiserGPU::denoise_buffer(buffer_params,
denoised_buffer_params,
render_buffers,
num_samples,
allow_inplace_modification);
}
bool OptiXDenoiser::denoise_create_if_needed(DenoiseContext &context)
{
const bool use_upscale_model = context.denoise_params.upscale_factor == 2.0f;
const bool recreate_denoiser = (optix_denoiser_ == nullptr) ||
(use_pass_albedo_ != context.use_pass_albedo) ||
(use_pass_normal_ != context.use_pass_normal) ||
(use_pass_motion_ != context.use_pass_motion);
(use_pass_albedo_ != context.denoise_params.use_pass_albedo) ||
(use_pass_normal_ != context.denoise_params.use_pass_normal) ||
(use_pass_motion_ != context.denoise_params.temporally_stable) ||
(use_upscale_model_ != use_upscale_model);
if (!recreate_denoiser) {
return true;
}
@ -68,12 +78,22 @@ bool OptiXDenoiser::denoise_create_if_needed(DenoiseContext &context)
/* Create OptiX denoiser handle on demand when it is first used. */
OptixDenoiserOptions denoiser_options = {};
denoiser_options.guideAlbedo = context.use_pass_albedo;
denoiser_options.guideNormal = context.use_pass_normal;
denoiser_options.guideAlbedo = context.denoise_params.use_pass_albedo;
denoiser_options.guideNormal = context.denoise_params.use_pass_normal;
OptixDenoiserModelKind model = OPTIX_DENOISER_MODEL_KIND_AOV;
if (context.use_pass_motion) {
model = OPTIX_DENOISER_MODEL_KIND_TEMPORAL;
if (context.denoise_params.temporally_stable) {
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(
@ -88,9 +108,10 @@ bool OptiXDenoiser::denoise_create_if_needed(DenoiseContext &context)
}
/* OptiX denoiser handle was created with the requested number of input passes. */
use_pass_albedo_ = context.use_pass_albedo;
use_pass_normal_ = context.use_pass_normal;
use_pass_motion_ = context.use_pass_motion;
use_pass_albedo_ = context.denoise_params.use_pass_albedo;
use_pass_normal_ = context.denoise_params.use_pass_normal;
use_pass_motion_ = context.denoise_params.temporally_stable;
use_upscale_model_ = use_upscale_model;
/* OptiX denoiser has been created, but it needs configuration. */
is_configured_ = false;
@ -145,10 +166,6 @@ bool OptiXDenoiser::denoise_configure_if_needed(DenoiseContext &context)
bool OptiXDenoiser::denoise_run(const DenoiseContext &context, const DenoisePass &pass)
{
const BufferParams &buffer_params = context.buffer_params;
const int width = buffer_params.width;
const int height = buffer_params.height;
/* Set up input and output layer information. */
OptixImage2D color_layer = {0};
OptixImage2D albedo_layer = {0};
@ -165,62 +182,66 @@ bool OptiXDenoiser::denoise_run(const DenoiseContext &context, const DenoisePass
color_layer.data = context.render_buffers->buffer.device_pointer +
pass_denoised * sizeof(float);
color_layer.width = width;
color_layer.height = height;
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 (context.prev_output.offset != PASS_UNUSED) {
if (context.denoise_params.temporally_stable && 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 = width;
prev_output_layer.height = height;
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. */
if (context.num_input_passes > 1) {
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;
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 (context.use_pass_albedo) {
albedo_layer.data = d_guiding_buffer + context.guiding_params.pass_albedo * sizeof(float);
albedo_layer.width = width;
albedo_layer.height = height;
albedo_layer.rowStrideInBytes = row_stride_in_bytes;
albedo_layer.pixelStrideInBytes = pixel_stride_in_bytes;
albedo_layer.format = OPTIX_PIXEL_FORMAT_FLOAT3;
}
if (context.denoise_params.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 (context.use_pass_normal) {
normal_layer.data = d_guiding_buffer + context.guiding_params.pass_normal * sizeof(float);
normal_layer.width = width;
normal_layer.height = height;
normal_layer.rowStrideInBytes = row_stride_in_bytes;
normal_layer.pixelStrideInBytes = pixel_stride_in_bytes;
normal_layer.format = OPTIX_PIXEL_FORMAT_FLOAT3;
}
if (context.denoise_params.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 (context.use_pass_motion) {
flow_layer.data = d_guiding_buffer + context.guiding_params.pass_flow * sizeof(float);
flow_layer.width = width;
flow_layer.height = height;
flow_layer.rowStrideInBytes = row_stride_in_bytes;
flow_layer.pixelStrideInBytes = pixel_stride_in_bytes;
flow_layer.format = OPTIX_PIXEL_FORMAT_FLOAT2;
}
if (context.denoise_params.temporally_stable) {
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 = 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;

View file

@ -18,14 +18,18 @@ class OptiXDenoiser : public DenoiserGPU {
OptiXDenoiser(Device *denoiser_device, const DenoiseParams &params);
~OptiXDenoiser();
virtual bool denoise_buffer(const BufferParams &buffer_params,
const BufferParams &denoised_buffer_params,
RenderBuffers *render_buffers,
int num_samples,
bool allow_inplace_modification) override;
static bool is_device_supported(const DeviceInfo &device);
protected:
virtual uint get_device_type_mask() const override;
private:
virtual bool denoise_buffer(const DenoiseTask &task) override;
/* 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);
@ -57,6 +61,7 @@ class OptiXDenoiser : public DenoiserGPU {
bool use_pass_albedo_ = false;
bool use_pass_normal_ = false;
bool use_pass_motion_ = false;
bool use_upscale_model_ = false;
};
CCL_NAMESPACE_END

View file

@ -231,7 +231,7 @@ void PassAccessor::init_kernel_film_convert(KernelFilmConvert *kfilm_convert,
{
const PassType type = pass_access_info_.type;
const PassMode mode = pass_access_info_.mode;
const PassInfo &pass_info = Pass::get_info(
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;
@ -270,6 +270,10 @@ void PassAccessor::init_kernel_film_convert(KernelFilmConvert *kfilm_convert,
}
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) {

View file

@ -30,6 +30,8 @@ class PassAccessor {
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.
*/

View file

@ -329,22 +329,22 @@ void PathTrace::update_allocated_work_buffer_params()
});
}
static BufferParams scale_buffer_params(const BufferParams &params, const int resolution_divider)
static BufferParams scale_buffer_params(const BufferParams &params, const float resolution_divider)
{
BufferParams scaled_params = params;
scaled_params.width = max(1, params.width / resolution_divider);
scaled_params.height = max(1, params.height / resolution_divider);
scaled_params.width = max(1, int(params.width / resolution_divider));
scaled_params.height = max(1, int(params.height / resolution_divider));
scaled_params.window_x = params.window_x / resolution_divider;
scaled_params.window_y = params.window_y / resolution_divider;
scaled_params.window_width = max(1, params.window_width / resolution_divider);
scaled_params.window_height = max(1, params.window_height / resolution_divider);
scaled_params.window_x = int(params.window_x / resolution_divider);
scaled_params.window_y = int(params.window_y / resolution_divider);
scaled_params.window_width = max(1, int(params.window_width / resolution_divider));
scaled_params.window_height = max(1, int(params.window_height / resolution_divider));
scaled_params.full_x = params.full_x / resolution_divider;
scaled_params.full_y = params.full_y / resolution_divider;
scaled_params.full_width = max(1, params.full_width / resolution_divider);
scaled_params.full_height = max(1, params.full_height / resolution_divider);
scaled_params.full_x = int(params.full_x / resolution_divider);
scaled_params.full_y = int(params.full_y / resolution_divider);
scaled_params.full_width = max(1, int(params.full_width / resolution_divider));
scaled_params.full_height = max(1, int(params.full_height / resolution_divider));
scaled_params.update_offset_stride();
@ -353,24 +353,32 @@ static BufferParams scale_buffer_params(const BufferParams &params, const int re
void PathTrace::update_effective_work_buffer_params(const RenderWork &render_work)
{
const int resolution_divider = render_work.resolution_divider;
const float denoised_resolution_divider = render_work.denoised_resolution_divider;
const float resolution_divider = render_work.resolution_divider / denoised_resolution_divider;
const BufferParams scaled_full_params = scale_buffer_params(full_params_, resolution_divider);
const BufferParams scaled_big_tile_params = scale_buffer_params(big_tile_params_,
const BufferParams denoised_big_tile_params = scale_buffer_params(big_tile_params_,
denoised_resolution_divider);
const BufferParams scaled_big_tile_params = scale_buffer_params(denoised_big_tile_params,
resolution_divider);
const int overscan = tile_manager_.get_tile_overscan();
foreach_sliced_buffer_params(path_trace_works_,
work_balance_infos_,
scaled_big_tile_params,
overscan,
[&](PathTraceWork *path_trace_work, const BufferParams params) {
path_trace_work->set_effective_buffer_params(
scaled_full_params, scaled_big_tile_params, params);
});
foreach_sliced_buffer_params(
path_trace_works_,
work_balance_infos_,
denoised_big_tile_params,
overscan,
[&](PathTraceWork *path_trace_work, const BufferParams params) {
/* Scale down the sliced buffer parameters again that were scaled by denoising upscale
* factor above. This should match the values that would occur when slicing
* 'scaled_big_tile_params' directly. */
const BufferParams scaled_params = scale_buffer_params(params, resolution_divider);
path_trace_work->set_effective_buffer_params(
scaled_big_tile_params, scaled_params, denoised_big_tile_params, params);
});
render_state_.effective_big_tile_params = scaled_big_tile_params;
render_state_.effective_denoised_big_tile_params = denoised_big_tile_params;
}
void PathTrace::update_work_buffer_params_if_needed(const RenderWork &render_work)
@ -518,6 +526,7 @@ void PathTrace::set_denoiser_params(const DenoiseParams &params)
{
if (!params.use) {
denoiser_.reset();
render_scheduler_.set_denoiser_params(params);
return;
}
@ -630,12 +639,14 @@ void PathTrace::denoise(const RenderWork &render_work)
}
if (big_tile_denoise_work_) {
big_tile_denoise_work_->set_effective_buffer_params(render_state_.effective_big_tile_params,
render_state_.effective_big_tile_params,
render_state_.effective_big_tile_params);
big_tile_denoise_work_->set_effective_buffer_params(
render_state_.effective_big_tile_params,
render_state_.effective_big_tile_params,
render_state_.effective_denoised_big_tile_params,
render_state_.effective_denoised_big_tile_params);
buffer_to_denoise = big_tile_denoise_work_->get_render_buffers();
buffer_to_denoise->reset(render_state_.effective_big_tile_params);
buffer_to_denoise->reset(render_state_.effective_denoised_big_tile_params);
copy_to_render_buffers(buffer_to_denoise);
@ -648,6 +659,7 @@ void PathTrace::denoise(const RenderWork &render_work)
}
if (denoiser_->denoise_buffer(render_state_.effective_big_tile_params,
render_state_.effective_denoised_big_tile_params,
buffer_to_denoise,
get_num_samples_in_buffer(),
allow_inplace_modification))
@ -751,18 +763,23 @@ void PathTrace::update_display(const RenderWork &render_work)
if (display_) {
LOG_DEBUG << "Perform copy to GPUDisplay work.";
const int texture_width = render_state_.effective_big_tile_params.window_width;
const int texture_height = render_state_.effective_big_tile_params.window_height;
const PassType pass_type = film_->get_display_pass();
const bool show_denoised =
((render_work.display.use_denoised_result && has_denoised_result() &&
big_tile_params_.get_pass_offset(pass_type, PassMode::DENOISED) != PASS_UNUSED) ||
is_volume_guiding_pass(pass_type));
const int texture_width = show_denoised ?
render_state_.effective_denoised_big_tile_params.window_width :
render_state_.effective_big_tile_params.window_width;
const int texture_height = show_denoised ?
render_state_.effective_denoised_big_tile_params.window_height :
render_state_.effective_big_tile_params.window_height;
if (!display_->update_begin(texture_width, texture_height)) {
LOG_ERROR << "Error beginning GPUDisplay update.";
return;
}
const PassType pass_type = film_->get_display_pass();
const bool show_denoised = (render_work.display.use_denoised_result &&
has_denoised_result()) ||
is_volume_guiding_pass(pass_type);
const PassMode pass_mode = show_denoised ? PassMode::DENOISED : PassMode::NOISY;
/* TODO(sergey): When using multi-device rendering map the GPUDisplay once and copy data from
@ -1114,7 +1131,8 @@ void PathTrace::process_full_buffer_from_disk(string_view filename)
set_denoiser_params(denoise_params);
/* Number of samples doesn't matter too much, since the samples count pass will be used. */
denoiser_->denoise_buffer(full_frame_buffers.params, &full_frame_buffers, 0, false);
denoiser_->denoise_buffer(
full_frame_buffers.params, full_frame_buffers.params, &full_frame_buffers, 0, false);
render_state_.has_denoised_result = true;
}

View file

@ -322,10 +322,11 @@ class PathTrace {
* 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. */
int resolution_divider = 0;
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;

View file

@ -43,6 +43,7 @@ PathTraceWork::PathTraceWork(Device *device,
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)
{
}
@ -54,13 +55,16 @@ RenderBuffers *PathTraceWork::get_render_buffers()
return buffers_.get();
}
void PathTraceWork::set_effective_buffer_params(const BufferParams &effective_full_params,
const BufferParams &effective_big_tile_params,
const BufferParams &effective_buffer_params)
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_full_params_ = effective_full_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
@ -113,12 +117,16 @@ void PathTraceWork::copy_from_render_buffers(const RenderBuffers *render_buffers
void PathTraceWork::copy_from_denoised_render_buffers(const RenderBuffers *render_buffers)
{
const int64_t width = effective_buffer_params_.width;
const int64_t offset_y = effective_buffer_params_.full_y - effective_big_tile_params_.full_y;
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_buffer_params_, render_buffers, effective_buffer_params_, offset);
render_buffers_host_copy_denoised(buffers_.get(),
effective_denoised_buffer_params_,
render_buffers,
effective_denoised_buffer_params_,
offset);
copy_render_buffers_to_device();
}
@ -180,6 +188,16 @@ PassAccessor::PassAccessInfo PathTraceWork::get_display_pass_access_info(PassMod
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;
@ -190,11 +208,18 @@ PassAccessor::Destination PathTraceWork::get_display_destination_template(
{
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;
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;

View file

@ -43,9 +43,10 @@ class PathTraceWork {
RenderBuffers *get_render_buffers();
/* Set effective parameters of the big tile and the work itself. */
void set_effective_buffer_params(const BufferParams &effective_full_params,
const BufferParams &effective_big_tile_params,
const BufferParams &effective_buffer_params);
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;
@ -187,12 +188,13 @@ class PathTraceWork {
* It also defines possible subset of a big tile in the case of multi-device rendering. */
unique_ptr<RenderBuffers> buffers_;
/* Effective parameters of the full, big tile, and current work render buffer.
/* 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_full_params_;
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;
};

View file

@ -213,6 +213,10 @@ void PathTraceWorkCPU::copy_to_display(PathTraceDisplay *display,
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);
@ -220,7 +224,7 @@ void PathTraceWorkCPU::copy_to_display(PathTraceDisplay *display,
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);
pass_accessor.get_render_tile_pixels(buffers_.get(), effective_buffer_params, destination);
});
display->unmap_texture_buffer();

View file

@ -1036,17 +1036,24 @@ void PathTraceWorkGPU::copy_to_display_naive(PathTraceDisplay *display,
PassMode pass_mode,
const int num_samples)
{
const int full_x = effective_buffer_params_.full_x;
const int full_y = effective_buffer_params_.full_y;
const int width = effective_buffer_params_.window_width;
const int height = effective_buffer_params_.window_height;
const BufferParams &effective_big_tile_params = (pass_mode == PassMode::DENOISED) ?
effective_denoised_big_tile_params_ :
effective_big_tile_params_;
const BufferParams &effective_buffer_params = (pass_mode == PassMode::DENOISED) ?
effective_denoised_buffer_params_ :
effective_buffer_params_;
const int full_x = effective_buffer_params.full_x;
const int full_y = effective_buffer_params.full_y;
const int width = effective_buffer_params.window_width;
const int height = effective_buffer_params.window_height;
const int final_width = buffers_->params.window_width;
const int final_height = buffers_->params.window_height;
const int texture_x = full_x - effective_big_tile_params_.full_x +
effective_buffer_params_.window_x - effective_big_tile_params_.window_x;
const int texture_y = full_y - effective_big_tile_params_.full_y +
effective_buffer_params_.window_y - effective_big_tile_params_.window_y;
const int texture_x = full_x - effective_big_tile_params.full_x +
effective_buffer_params.window_x - effective_big_tile_params.window_x;
const int texture_y = full_y - effective_big_tile_params.full_y +
effective_buffer_params.window_y - effective_big_tile_params.window_y;
/* Re-allocate display memory if needed, and make sure the device pointer is allocated.
*
@ -1120,9 +1127,13 @@ void PathTraceWorkGPU::get_render_tile_film_pixels(const PassAccessor::Destinati
return;
}
const BufferParams &effective_buffer_params = (pass_mode == PassMode::DENOISED) ?
effective_denoised_buffer_params_ :
effective_buffer_params_;
const PassAccessorGPU pass_accessor(queue_.get(), pass_access_info, kfilm.exposure, num_samples);
pass_accessor.get_render_tile_pixels(buffers_.get(), effective_buffer_params_, destination);
pass_accessor.get_render_tile_pixels(buffers_.get(), effective_buffer_params, destination);
}
int PathTraceWorkGPU::adaptive_sampling_converge_filter_count_active(const float threshold,

View file

@ -300,6 +300,10 @@ RenderWork RenderScheduler::get_render_work()
if (done()) {
RenderWork render_work;
render_work.resolution_divider = state_.resolution_divider;
render_work.denoised_resolution_divider = state_.resolution_divider;
if (denoiser_params_.use) {
render_work.resolution_divider *= denoiser_params_.upscale_factor;
}
if (!set_postprocess_render_work(&render_work)) {
set_full_frame_render_work(&render_work);
@ -321,7 +325,7 @@ RenderWork RenderScheduler::get_render_work()
/* Don't progress the resolution divider as the user is currently navigating in the scene. */
state_.user_is_navigating = false;
}
else {
else if (default_start_resolution_divider_ != 0) {
/* If the resolution divider is greater than or equal to default_start_resolution_divider_,
* drop the resolution divider down to 4. This is so users with slow hardware and thus high
* resolution dividers (E.G. 16), get an update to let them know something is happening
@ -337,6 +341,10 @@ RenderWork RenderScheduler::get_render_work()
}
render_work.resolution_divider = state_.resolution_divider;
render_work.denoised_resolution_divider = state_.resolution_divider;
if (denoiser_params_.use) {
render_work.resolution_divider *= denoiser_params_.upscale_factor;
}
render_work.path_trace.start_sample = get_start_sample_to_path_trace();
render_work.path_trace.num_samples = get_num_samples_to_path_trace();

View file

@ -18,7 +18,8 @@ class TileManager;
class RenderWork {
public:
int resolution_divider = 1;
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

View file

@ -1173,13 +1173,18 @@ ccl_gpu_kernel(GPU_KERNEL_BLOCK_NUM_THREADS, GPU_KERNEL_MAX_REGISTERS)
const int height,
const int offset,
const int stride,
const int render_full_x,
const int render_full_y,
const int render_offset,
const int render_stride,
const int pass_stride,
const int num_samples,
const int pass_noisy,
const int pass_denoised,
const int pass_sample_count,
const int num_components,
const int use_compositing)
const int use_compositing,
const float upscale_factor)
{
const int work_index = ccl_gpu_global_id_x();
const int y = work_index / width;
@ -1189,7 +1194,8 @@ ccl_gpu_kernel(GPU_KERNEL_BLOCK_NUM_THREADS, GPU_KERNEL_MAX_REGISTERS)
return;
}
const uint64_t render_pixel_index = offset + (x + full_x) + (y + full_y) * stride;
const uint64_t render_pixel_index = render_offset + (int(x / upscale_factor) + render_full_x) +
(int(y / upscale_factor) + render_full_y) * render_stride;
ccl_global float *buffer = render_buffer + render_pixel_index * pass_stride;
float pixel_scale;
@ -1200,11 +1206,15 @@ ccl_gpu_kernel(GPU_KERNEL_BLOCK_NUM_THREADS, GPU_KERNEL_MAX_REGISTERS)
pixel_scale = __float_as_uint(buffer[pass_sample_count]);
}
ccl_global float *denoised_pixel = buffer + pass_denoised;
const uint64_t denoised_pixel_index = offset + (x + full_x) + (y + full_y) * stride;
ccl_global float *denoised_pixel = render_buffer + denoised_pixel_index * pass_stride +
pass_denoised;
denoised_pixel[0] *= pixel_scale;
denoised_pixel[1] *= pixel_scale;
denoised_pixel[2] *= pixel_scale;
if (pass_sample_count == PASS_UNUSED || upscale_factor == 1.0f) {
denoised_pixel[0] *= pixel_scale;
denoised_pixel[1] *= pixel_scale;
denoised_pixel[2] *= pixel_scale;
}
if (num_components == 3) {
/* Pass without alpha channel. */
@ -1215,6 +1225,10 @@ ccl_gpu_kernel(GPU_KERNEL_BLOCK_NUM_THREADS, GPU_KERNEL_MAX_REGISTERS)
* simplifies logic and avoids extra memory allocation. */
const ccl_global float *noisy_pixel = buffer + pass_noisy;
denoised_pixel[3] = noisy_pixel[3];
if (pass_sample_count != PASS_UNUSED && upscale_factor != 1.0f) {
denoised_pixel[3] /= pixel_scale;
}
}
else {
/* Assigning to zero since this is a default alpha value for 3-component passes, and it

View file

@ -72,15 +72,15 @@ ccl_device_inline bool film_get_scale_and_scale_exposure(
return true;
}
const uint sample_count = *(
(const ccl_global uint *)(buffer + kfilm_convert->pass_sample_count));
if (!sample_count) {
*scale = 0.0f;
*scale_exposure = 0.0f;
return false;
}
if (kfilm_convert->pass_use_filter) {
const uint sample_count = *(
(const ccl_global uint *)(buffer + kfilm_convert->pass_sample_count));
if (!sample_count) {
*scale = 0.0f;
*scale_exposure = 0.0f;
return false;
}
*scale = kfilm_convert->scale / sample_count;
}
else {

View file

@ -166,6 +166,7 @@ NODE_DEFINE(Integrator)
DENOISER_PREFILTER_ACCURATE);
SOCKET_BOOLEAN(denoise_use_gpu, "Denoise on GPU", true);
SOCKET_ENUM(denoiser_quality, "Denoiser Quality", denoiser_quality_enum, DENOISER_QUALITY_HIGH);
SOCKET_FLOAT(denoiser_upscale_factor, "Denoiser Upscale Factor", 1.0f);
return type;
}
@ -421,6 +422,11 @@ AdaptiveSampling Integrator::get_adaptive_sampling() const
adaptive_sampling.use = use_adaptive_sampling;
/* Disable sample count pass with upscaling. */
if (use_denoise && denoiser_upscale_factor != 1.0f) {
adaptive_sampling.use = false;
}
if (!adaptive_sampling.use) {
return adaptive_sampling;
}
@ -489,6 +495,7 @@ DenoiseParams Integrator::get_denoise_params() const
denoise_params.prefilter = denoiser_prefilter;
denoise_params.quality = denoiser_quality;
denoise_params.upscale_factor = denoiser_upscale_factor;
return denoise_params;
}

View file

@ -107,6 +107,7 @@ class Integrator : public Node {
NODE_SOCKET_API(DenoiserPrefilter, denoiser_prefilter);
NODE_SOCKET_API(bool, denoise_use_gpu);
NODE_SOCKET_API(DenoiserQuality, denoiser_quality);
NODE_SOCKET_API(float, denoiser_upscale_factor);
enum : uint32_t {
AO_PASS_MODIFIED = (1 << 0),

View file

@ -271,7 +271,7 @@ bool DenoiseTask::exec()
}
/* Run task on device. */
denoiser->denoiser->denoise_buffer(buffers.params, &buffers, 1, true);
denoiser->denoiser->denoise_buffer(buffers.params, buffers.params, &buffers, 1, true);
/* Copy denoised pixels from device. */
buffers.buffer.copy_from_device();

View file

@ -398,9 +398,9 @@ RenderWork Session::run_update_for_next_iteration()
/* Update camera if dimensions changed for progressive render. the camera
* knows nothing about progressive or cropped rendering, it just gets the
* image dimensions passed in. */
const int resolution = render_work.resolution_divider;
const int width = max(1, buffer_params_.full_width / resolution);
const int height = max(1, buffer_params_.full_height / resolution);
const float resolution = render_work.resolution_divider;
const int width = max(1, int(buffer_params_.full_width / resolution));
const int height = max(1, int(buffer_params_.full_height / resolution));
scene->update_camera_resolution(progress, width, height);

View file

@ -97,8 +97,9 @@ class SessionParams {
return !(device == params.device && headless == params.headless &&
background == params.background && pixel_size == params.pixel_size &&
threads == params.threads && use_profiling == params.use_profiling &&
shadingsystem == params.shadingsystem && use_auto_tile == params.use_auto_tile &&
tile_size == params.tile_size);
use_auto_tile == params.use_auto_tile && tile_size == params.tile_size &&
use_resolution_divider == params.use_resolution_divider &&
shadingsystem == params.shadingsystem);
}
};