This patch adds a Hydra render delegate to Cycles, allowing Cycles to be used for rendering in applications that provide a Hydra viewport. The implementation was written from scratch against Cycles X, for integration into the Blender repository to make it possible to continue developing it in step with the rest of Cycles. For this purpose it follows the style of the rest of the Cycles code and can be built with a CMake option (`WITH_CYCLES_HYDRA_RENDER_DELEGATE=1`) similar to the existing standalone version of Cycles. Since Hydra render delegates need to be built against the exact USD version and other dependencies as the target application is using, this is intended to be built separate from Blender (`WITH_BLENDER=0` CMake option) and with support for library versions different from what Blender is using. As such the CMake build scripts for Windows had to be modified slightly, so that the Cycles Hydra render delegate can e.g. be built with MSVC 2017 again even though Blender requires MSVC 2019 now, and it's possible to specify custom paths to the USD SDK etc. The codebase supports building against the latest USD release 22.03 and all the way back to USD 20.08 (with some limitations). Reviewed By: brecht, LazyDodo Differential Revision: https://developer.blender.org/D14398
277 lines
6.8 KiB
C++
277 lines
6.8 KiB
C++
/* SPDX-License-Identifier: Apache-2.0
|
|
* Copyright 2022 NVIDIA Corporation
|
|
* Copyright 2022 Blender Foundation */
|
|
|
|
#include "hydra/render_buffer.h"
|
|
#include "hydra/session.h"
|
|
#include "util/half.h"
|
|
|
|
#include <pxr/base/gf/vec3i.h>
|
|
#include <pxr/base/gf/vec4f.h>
|
|
|
|
HDCYCLES_NAMESPACE_OPEN_SCOPE
|
|
|
|
HdCyclesRenderBuffer::HdCyclesRenderBuffer(const SdfPath &bprimId) : HdRenderBuffer(bprimId)
|
|
{
|
|
}
|
|
|
|
HdCyclesRenderBuffer::~HdCyclesRenderBuffer()
|
|
{
|
|
}
|
|
|
|
void HdCyclesRenderBuffer::Finalize(HdRenderParam *renderParam)
|
|
{
|
|
// Remove this render buffer from AOV bindings
|
|
// This ensures that 'OutputDriver' does not attempt to write to it anymore
|
|
static_cast<HdCyclesSession *>(renderParam)->RemoveAovBinding(this);
|
|
|
|
HdRenderBuffer::Finalize(renderParam);
|
|
}
|
|
|
|
bool HdCyclesRenderBuffer::Allocate(const GfVec3i &dimensions, HdFormat format, bool multiSampled)
|
|
{
|
|
if (dimensions[2] != 1) {
|
|
TF_RUNTIME_ERROR("HdCyclesRenderBuffer::Allocate called with dimensions that are not 2D.");
|
|
return false;
|
|
}
|
|
|
|
const size_t oldSize = _data.size();
|
|
const size_t newSize = dimensions[0] * dimensions[1] * HdDataSizeOfFormat(format);
|
|
if (oldSize == newSize) {
|
|
return true;
|
|
}
|
|
|
|
if (IsMapped()) {
|
|
TF_RUNTIME_ERROR("HdCyclesRenderBuffer::Allocate called while buffer is mapped.");
|
|
return false;
|
|
}
|
|
|
|
_width = dimensions[0];
|
|
_height = dimensions[1];
|
|
_format = format;
|
|
|
|
_data.resize(newSize);
|
|
|
|
return true;
|
|
}
|
|
|
|
void HdCyclesRenderBuffer::_Deallocate()
|
|
{
|
|
_width = 0u;
|
|
_height = 0u;
|
|
_format = HdFormatInvalid;
|
|
|
|
_data.clear();
|
|
_data.shrink_to_fit();
|
|
|
|
_resource = VtValue();
|
|
}
|
|
|
|
void *HdCyclesRenderBuffer::Map()
|
|
{
|
|
// Mapping is not implemented when a resource is set
|
|
if (!_resource.IsEmpty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
++_mapped;
|
|
|
|
return _data.data();
|
|
}
|
|
|
|
void HdCyclesRenderBuffer::Unmap()
|
|
{
|
|
--_mapped;
|
|
}
|
|
|
|
bool HdCyclesRenderBuffer::IsMapped() const
|
|
{
|
|
return _mapped != 0;
|
|
}
|
|
|
|
void HdCyclesRenderBuffer::Resolve()
|
|
{
|
|
}
|
|
|
|
bool HdCyclesRenderBuffer::IsConverged() const
|
|
{
|
|
return _converged;
|
|
}
|
|
|
|
void HdCyclesRenderBuffer::SetConverged(bool converged)
|
|
{
|
|
_converged = converged;
|
|
}
|
|
|
|
VtValue HdCyclesRenderBuffer::GetResource(bool multiSampled) const
|
|
{
|
|
TF_UNUSED(multiSampled);
|
|
|
|
return _resource;
|
|
}
|
|
|
|
void HdCyclesRenderBuffer::SetResource(const VtValue &resource)
|
|
{
|
|
_resource = resource;
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct SimpleConversion {
|
|
static float convert(float value)
|
|
{
|
|
return value;
|
|
}
|
|
};
|
|
struct IdConversion {
|
|
static int32_t convert(float value)
|
|
{
|
|
return static_cast<int32_t>(value) - 1;
|
|
}
|
|
};
|
|
struct UInt8Conversion {
|
|
static uint8_t convert(float value)
|
|
{
|
|
return static_cast<uint8_t>(value * 255.f);
|
|
}
|
|
};
|
|
struct SInt8Conversion {
|
|
static int8_t convert(float value)
|
|
{
|
|
return static_cast<int8_t>(value * 127.f);
|
|
}
|
|
};
|
|
struct HalfConversion {
|
|
static half convert(float value)
|
|
{
|
|
return float_to_half_image(value);
|
|
}
|
|
};
|
|
|
|
template<typename SrcT, typename DstT, typename Convertor = SimpleConversion>
|
|
void writePixels(const SrcT *srcPtr,
|
|
const GfVec2i &srcSize,
|
|
int srcChannelCount,
|
|
DstT *dstPtr,
|
|
const GfVec2i &dstSize,
|
|
int dstChannelCount,
|
|
const Convertor &convertor = {})
|
|
{
|
|
const auto writeSize = GfVec2i(GfMin(srcSize[0], dstSize[0]), GfMin(srcSize[1], dstSize[1]));
|
|
const auto writeChannelCount = GfMin(srcChannelCount, dstChannelCount);
|
|
|
|
for (int y = 0; y < writeSize[1]; ++y) {
|
|
for (int x = 0; x < writeSize[0]; ++x) {
|
|
for (int c = 0; c < writeChannelCount; ++c) {
|
|
dstPtr[x * dstChannelCount + c] = convertor.convert(srcPtr[x * srcChannelCount + c]);
|
|
}
|
|
}
|
|
srcPtr += srcSize[0] * srcChannelCount;
|
|
dstPtr += dstSize[0] * dstChannelCount;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void HdCyclesRenderBuffer::WritePixels(const float *srcPixels,
|
|
const PXR_NS::GfVec2i &srcOffset,
|
|
const GfVec2i &srcDims,
|
|
int srcChannels,
|
|
bool isId)
|
|
{
|
|
uint8_t *dstPixels = _data.data();
|
|
|
|
const size_t formatSize = HdDataSizeOfFormat(_format);
|
|
dstPixels += srcOffset[1] * (formatSize * _width) + srcOffset[0] * formatSize;
|
|
|
|
switch (_format) {
|
|
case HdFormatUNorm8:
|
|
case HdFormatUNorm8Vec2:
|
|
case HdFormatUNorm8Vec3:
|
|
case HdFormatUNorm8Vec4:
|
|
writePixels(srcPixels,
|
|
srcDims,
|
|
srcChannels,
|
|
dstPixels,
|
|
GfVec2i(_width, _height),
|
|
1 + (_format - HdFormatUNorm8),
|
|
UInt8Conversion());
|
|
break;
|
|
|
|
case HdFormatSNorm8:
|
|
case HdFormatSNorm8Vec2:
|
|
case HdFormatSNorm8Vec3:
|
|
case HdFormatSNorm8Vec4:
|
|
writePixels(srcPixels,
|
|
srcDims,
|
|
srcChannels,
|
|
dstPixels,
|
|
GfVec2i(_width, _height),
|
|
1 + (_format - HdFormatSNorm8),
|
|
SInt8Conversion());
|
|
break;
|
|
|
|
case HdFormatFloat16:
|
|
case HdFormatFloat16Vec2:
|
|
case HdFormatFloat16Vec3:
|
|
case HdFormatFloat16Vec4:
|
|
writePixels(srcPixels,
|
|
srcDims,
|
|
srcChannels,
|
|
reinterpret_cast<half *>(dstPixels),
|
|
GfVec2i(_width, _height),
|
|
1 + (_format - HdFormatFloat16),
|
|
HalfConversion());
|
|
break;
|
|
|
|
case HdFormatFloat32:
|
|
case HdFormatFloat32Vec2:
|
|
case HdFormatFloat32Vec3:
|
|
case HdFormatFloat32Vec4:
|
|
writePixels(srcPixels,
|
|
srcDims,
|
|
srcChannels,
|
|
reinterpret_cast<float *>(dstPixels),
|
|
GfVec2i(_width, _height),
|
|
1 + (_format - HdFormatFloat32));
|
|
break;
|
|
|
|
case HdFormatInt32:
|
|
// Special case for ID AOVs (see 'HdCyclesMesh::Sync')
|
|
if (isId) {
|
|
writePixels(srcPixels,
|
|
srcDims,
|
|
srcChannels,
|
|
reinterpret_cast<int *>(dstPixels),
|
|
GfVec2i(_width, _height),
|
|
1,
|
|
IdConversion());
|
|
}
|
|
else {
|
|
writePixels(srcPixels,
|
|
srcDims,
|
|
srcChannels,
|
|
reinterpret_cast<int *>(dstPixels),
|
|
GfVec2i(_width, _height),
|
|
1);
|
|
}
|
|
break;
|
|
case HdFormatInt32Vec2:
|
|
case HdFormatInt32Vec3:
|
|
case HdFormatInt32Vec4:
|
|
writePixels(srcPixels,
|
|
srcDims,
|
|
srcChannels,
|
|
reinterpret_cast<int *>(dstPixels),
|
|
GfVec2i(_width, _height),
|
|
1 + (_format - HdFormatInt32));
|
|
break;
|
|
|
|
default:
|
|
TF_RUNTIME_ERROR("HdCyclesRenderBuffer::WritePixels called with unsupported format.");
|
|
break;
|
|
}
|
|
}
|
|
|
|
HDCYCLES_NAMESPACE_CLOSE_SCOPE
|