Skip to content

Commit

Permalink
Support dataWindowNDC (#1173)
Browse files Browse the repository at this point in the history
* Support dataWindowNDC in the delegate #1161

* Improving dataWindowNDC support in the procedural #1161

* Adjust pixel aspect ratio when the windowNDC is changed #1161

* Apply pixel aspect ratio for windowNDC in the procedural #1161
  • Loading branch information
sebastienblor authored Jun 14, 2022
1 parent c2adc27 commit 05fce4b
Show file tree
Hide file tree
Showing 16 changed files with 1,085 additions and 32 deletions.
21 changes: 19 additions & 2 deletions render_delegate/nodes/driver_aov.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const char* supportedExtensions[] = {nullptr};

struct DriverData {
HdArnoldRenderBuffer* renderBuffer;
int regionMinX = 0;
int regionMinY = 0;
};

HdFormat _GetFormatFromArnoldType(const int arnoldType)
Expand Down Expand Up @@ -58,6 +60,21 @@ node_update
{
auto* data = reinterpret_cast<DriverData*>(AiNodeGetLocalData(node));
data->renderBuffer = static_cast<HdArnoldRenderBuffer*>(AiNodeGetPtr(node, str::aov_pointer));

AtNode *options = AiUniverseGetOptions(AiNodeGetUniverse(node));
data->regionMinX = AiNodeGetInt(options, str::region_min_x);
data->regionMinY = AiNodeGetInt(options, str::region_min_y);

// Check the default value for "region_min". It should be INT_MI, but I'm testing it for safety.
const AtParamEntry* pentryMinx = AiNodeEntryLookUpParameter(AiNodeGetNodeEntry(options), str::region_min_x);
int defaultValue = (pentryMinx) ? AiParamGetDefault(pentryMinx)->INT() : INT_MIN;

// if the region min is left to default, we don't want to apply any offset
if (data->regionMinX == defaultValue)
data->regionMinX = 0;
if (data->regionMinY == defaultValue)
data->regionMinY = 0;

}

node_finish {}
Expand All @@ -77,14 +94,14 @@ driver_needs_bucket { return true; }
driver_prepare_bucket {}

driver_process_bucket
{
{
auto* driverData = reinterpret_cast<DriverData*>(AiNodeGetLocalData(node));
int pixelType = AI_TYPE_RGBA;
const void* bucketData = nullptr;
while (AiOutputIteratorGetNext(iterator, nullptr, &pixelType, &bucketData)) {
if (Ai_likely(driverData->renderBuffer != nullptr)) {
driverData->renderBuffer->WriteBucket(
bucket_xo, bucket_yo, bucket_size_x, bucket_size_y, _GetFormatFromArnoldType(pixelType), bucketData);
bucket_xo - driverData->regionMinX, bucket_yo - driverData->regionMinY, bucket_size_x, bucket_size_y, _GetFormatFromArnoldType(pixelType), bucketData);
}
// There will be only one aov assigned to each driver.
break;
Expand Down
31 changes: 27 additions & 4 deletions render_delegate/nodes/driver_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,23 @@ node_update
data->colorBuffer = static_cast<HdArnoldRenderBuffer*>(AiNodeGetPtr(node, str::color_pointer));
data->depthBuffer = static_cast<HdArnoldRenderBuffer*>(AiNodeGetPtr(node, str::depth_pointer));
data->idBuffer = static_cast<HdArnoldRenderBuffer*>(AiNodeGetPtr(node, str::id_pointer));

// Store the region min X/Y so that we can apply an offset when
// negative pixel coordinates are needed for overscan.
AtNode *options = AiUniverseGetOptions(AiNodeGetUniverse(node));
data->regionMinX = AiNodeGetInt(options, str::region_min_x);
data->regionMinY = AiNodeGetInt(options, str::region_min_y);

// Check the default value for "region_min". It should be INT_MI, but I'm testing it for safety.
const AtParamEntry* pentryMinx = AiNodeEntryLookUpParameter(AiNodeGetNodeEntry(options), str::region_min_x);
int defaultValue = (pentryMinx) ? AiParamGetDefault(pentryMinx)->INT() : INT_MIN;

// If the region min is left to default, we don't want to apply any offset
if (data->regionMinX == defaultValue)
data->regionMinX = 0;
if (data->regionMinY == defaultValue)
data->regionMinY = 0;

}

node_finish {}
Expand Down Expand Up @@ -100,6 +117,11 @@ driver_process_bucket
ids.clear();
const void* colorData = nullptr;
const void* positionData = nullptr;
// Apply an offset to the pixel coordinates based on the region_min,
// since we don't own the render buffer, which just knows the output resolution
const auto bucket_xo_start = bucket_xo - driverData->regionMinX;
const auto bucket_yo_start = bucket_yo - driverData->regionMinY;

auto checkOutputName = [&outputName](const AtString& name) -> bool {
#if ARNOLD_VERSION_NUMBER > 60201
return outputName == name;
Expand All @@ -116,7 +138,7 @@ driver_process_bucket
const auto* in = static_cast<const int*>(bucketData);
std::transform(in, in + pixelCount, ids.begin(), [](int id) -> int { return id < 0 ? -1 : id - 1; });
driverData->idBuffer->WriteBucket(
bucket_xo, bucket_yo, bucket_size_x, bucket_size_y, HdFormatInt32, ids.data());
bucket_xo_start, bucket_yo_start, bucket_size_x, bucket_size_y, HdFormatInt32, ids.data());
}
} else if (pixelType == AI_TYPE_RGBA && checkOutputName(str::RGBA)) {
colorData = bucketData;
Expand Down Expand Up @@ -149,13 +171,14 @@ driver_process_bucket
}
}
}

driverData->depthBuffer->WriteBucket(
bucket_xo, bucket_yo, bucket_size_x, bucket_size_y, HdFormatFloat32, depth.data());
bucket_xo_start, bucket_yo_start, bucket_size_x, bucket_size_y, HdFormatFloat32, depth.data());
}
if (colorData != nullptr && driverData->colorBuffer) {
if (ids.empty()) {
driverData->colorBuffer->WriteBucket(
bucket_xo, bucket_yo, bucket_size_x, bucket_size_y, HdFormatFloat32Vec4, colorData);
bucket_xo_start, bucket_yo_start, bucket_size_x, bucket_size_y, HdFormatFloat32Vec4, colorData);
} else {
auto& color = driverData->colors[tid];
color.resize(pixelCount, AI_RGBA_ZERO);
Expand All @@ -167,7 +190,7 @@ driver_process_bucket
}
}
driverData->colorBuffer->WriteBucket(
bucket_xo, bucket_yo, bucket_size_x, bucket_size_y, HdFormatFloat32Vec4, color.data());
bucket_xo_start, bucket_yo_start, bucket_size_x, bucket_size_y, HdFormatFloat32Vec4, color.data());
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions render_delegate/nodes/nodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ struct DriverMainData {
std::vector<int> ids[AI_MAX_THREADS];
// Local storage for the color buffer.
std::vector<AtRGBA> colors[AI_MAX_THREADS];

// Store the region Min so that we apply an offset when negative
// pixel coordinates are needed for overscan
int regionMinX = 0;
int regionMinY = 0;
};

/// Installs Arnold nodes that are used by the Render Delegate.
Expand Down
6 changes: 6 additions & 0 deletions render_delegate/render_delegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ TF_DEFINE_PRIVATE_TOKENS(_tokens,
(instantaneousShutter)
((aovShadersArray, "aov_shaders:i"))
(GeometryLight)
(dataWindowNDC)
);
// clang-format on

Expand Down Expand Up @@ -632,6 +633,11 @@ void HdArnoldRenderDelegate::_SetRenderSetting(const TfToken& _key, const VtValu
AtNode* colorManager = getOrCreateColorManager(_universe, _options);
AiNodeSetStr(colorManager, str::color_space_narrow, AtString(value.UncheckedGet<std::string>().c_str()));
}
} else if (key == _tokens->dataWindowNDC) {
if (value.IsHolding<GfVec4f>()) {
_windowNDC = value.UncheckedGet<GfVec4f>();
}

} else {
auto* optionsEntry = AiNodeGetNodeEntry(_options);
// Sometimes the Render Delegate receives parameters that don't exist
Expand Down
9 changes: 9 additions & 0 deletions render_delegate/render_delegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,13 @@ class HdArnoldRenderDelegate final : public HdRenderDelegate {
HDARNOLD_API
std::vector<AtNode*> GetAovShaders(HdRenderIndex* renderIndex);

/// Get the current Window NDC, as a resolution-independant value,
/// defaulting to (0,0,1,1)
///
/// @return Vector4 window relative to the resolution, as (minX, minY, maxX, maxY)
HDARNOLD_API
GfVec4f GetWindowNDC() const {return _windowNDC;}

private:
HdArnoldRenderDelegate(const HdArnoldRenderDelegate&) = delete;
HdArnoldRenderDelegate& operator=(const HdArnoldRenderDelegate&) = delete;
Expand Down Expand Up @@ -549,6 +556,8 @@ class HdArnoldRenderDelegate final : public HdRenderDelegate {
std::string _logFile;
/// FPS value from render settings.
float _fps;
// window used for overscan or to adjust the camera frustum
GfVec4f _windowNDC = GfVec4f(0, 0, 1, 1);
/// Top level render context using Hydra. Ie. Hydra, Solaris, Husk.
HdArnoldRenderContext _context = HdArnoldRenderContext::Hydra;
int _verbosityLogFlags = AI_LOG_WARNINGS | AI_LOG_ERRORS;
Expand Down
85 changes: 85 additions & 0 deletions render_delegate/render_pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -500,16 +500,101 @@ void HdArnoldRenderPass::_Execute(const HdRenderPassStateSharedPtr& renderPassSt
AiNodeSetMatrix(_camera, str::matrix, HdArnoldConvertMatrix(_viewMtx.GetInverse()));
}
}
GfVec4f windowNDC = _renderDelegate->GetWindowNDC();
// check if we have a non-default window
bool hasWindowNDC = (!GfIsClose(windowNDC[0], 0.0f, AI_EPSILON)) ||
(!GfIsClose(windowNDC[1], 0.0f, AI_EPSILON)) ||
(!GfIsClose(windowNDC[2], 1.0f, AI_EPSILON)) ||
(!GfIsClose(windowNDC[3], 1.0f, AI_EPSILON));
// check if the window has changed since the last _Execute
bool windowChanged = (!GfIsClose(windowNDC[0], _windowNDC[0], AI_EPSILON)) ||
(!GfIsClose(windowNDC[1], _windowNDC[1], AI_EPSILON)) ||
(!GfIsClose(windowNDC[2], _windowNDC[2], AI_EPSILON)) ||
(!GfIsClose(windowNDC[3], _windowNDC[3], AI_EPSILON));


const auto width = static_cast<int>(dataWindow.GetWidth());
const auto height = static_cast<int>(dataWindow.GetHeight());
if (width != _width || height != _height) {
// The render resolution has changed, we need to update the arnold options
renderParam->Interrupt(true, false);
_width = width;
_height = height;
auto* options = _renderDelegate->GetOptions();
AiNodeSetInt(options, str::xres, _width);
AiNodeSetInt(options, str::yres, _height);

// if we have a window, then we need to recompute it anyway
if (hasWindowNDC)
windowChanged = true;
}

if (windowChanged) {
renderParam->Interrupt(true, false);
AiNodeResetParameter(options, str::pixel_aspect_ratio);
if (hasWindowNDC) {
_windowNDC = windowNDC;

// Need to invert the window range in the Y axis
float minY = 1. - windowNDC[3];
float maxY = 1. - windowNDC[1];
windowNDC[1] = minY;
windowNDC[3] = maxY;

// Ensure the user isn't setting invalid ranges
if (windowNDC[0] > windowNDC[2])
std::swap(windowNDC[0], windowNDC[2]);
if (windowNDC[1] > windowNDC[3])
std::swap(windowNDC[1], windowNDC[3]);

// we want the output render buffer to have a resolution equal to
// _width/_height. This means we need to adjust xres/yres, so that
// region min/max corresponds to the render resolution
float xDelta = windowNDC[2] - windowNDC[0]; // maxX - minX
if (xDelta > AI_EPSILON) {
float xInvDelta = 1.f / xDelta;
// adjust the X resolution accordingly
AiNodeSetInt(options, str::xres, _width * (xInvDelta));
windowNDC[0] *= xInvDelta;
windowNDC[2] *= xInvDelta;
} else {
AiNodeSetInt(options, str::xres, _width);
}
// we want region_max_x - region_min_x to be equal to _width - 1
AiNodeSetInt(options, str::region_min_x, int(windowNDC[0] * _width));
AiNodeSetInt(options, str::region_max_x, int(windowNDC[2] * _width) - 1);

float yDelta = windowNDC[3] - windowNDC[1]; // maxY - minY
if (yDelta > AI_EPSILON) {
float yInvDelta = 1.f / yDelta;
// adjust the Y resolution accordingly
AiNodeSetInt(options, str::yres, _height * (yInvDelta));
windowNDC[1] *= yInvDelta;
windowNDC[3] *= yInvDelta;

// need to adjust the pixel aspect ratio to match the window NDC
float pixel_aspect_ratio = xDelta / yDelta;
AiNodeSetFlt(options, str::pixel_aspect_ratio, pixel_aspect_ratio);

} else {
AiNodeSetInt(options, str::yres, _height);
}

// we want region_max_y - region_min_y to be equal to _height - 1
AiNodeSetInt(options, str::region_min_y, int(windowNDC[1] * _height));
AiNodeSetInt(options, str::region_max_y, int(windowNDC[3] * _height) - 1);
} else {
// the window was restored to defaults, we need to reset the region
// attributes, as well as xres,yres, that could have been adjusted
// in previous iterations
AiNodeResetParameter(options, str::region_min_x);
AiNodeResetParameter(options, str::region_min_y);
AiNodeResetParameter(options, str::region_max_x);
AiNodeResetParameter(options, str::region_max_y);
AiNodeSetInt(options, str::xres, _width);
AiNodeSetInt(options, str::yres, _height);
_windowNDC = GfVec4f(0.f, 0.f, 1.f, 1.f);
}
}

auto checkShader = [&] (AtNode* shader, const AtString& paramName) {
Expand Down
3 changes: 3 additions & 0 deletions render_delegate/render_pass.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ class HdArnoldRenderPass : public HdRenderPass {
int _width = 0; ///< Width of the render buffer.
int _height = 0; ///< Height of the render buffer.

// Window NDC region, that can be used for overscan, or to adjust the frustum
GfVec4f _windowNDC = GfVec4f(0.f, 0.f, 1.f, 1.f);

bool _isConverged = false; ///< State of the render convergence.
bool _usingFallbackBuffers = false; ///< If the render pass is using the fallback buffers.
};
Expand Down
7 changes: 7 additions & 0 deletions testsuite/test_0233/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Overscan with dataWindowNDC

see #1161

author: sebastien.ortega

PARAMS: {'scene':'test.usda'}
Loading

0 comments on commit 05fce4b

Please sign in to comment.