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
515 lines
14 KiB
C++
515 lines
14 KiB
C++
/* SPDX-License-Identifier: Apache-2.0
|
|
* Copyright 2022 NVIDIA Corporation
|
|
* Copyright 2022 Blender Foundation */
|
|
|
|
#include "hydra/render_delegate.h"
|
|
#include "hydra/camera.h"
|
|
#include "hydra/curves.h"
|
|
#include "hydra/field.h"
|
|
#include "hydra/instancer.h"
|
|
#include "hydra/light.h"
|
|
#include "hydra/material.h"
|
|
#include "hydra/mesh.h"
|
|
#include "hydra/node_util.h"
|
|
#include "hydra/pointcloud.h"
|
|
#include "hydra/render_buffer.h"
|
|
#include "hydra/render_pass.h"
|
|
#include "hydra/session.h"
|
|
#include "hydra/volume.h"
|
|
#include "scene/integrator.h"
|
|
#include "scene/scene.h"
|
|
#include "session/session.h"
|
|
|
|
#include <pxr/base/tf/getenv.h>
|
|
#include <pxr/imaging/hd/extComputation.h>
|
|
#include <pxr/imaging/hgi/tokens.h>
|
|
|
|
HDCYCLES_NAMESPACE_OPEN_SCOPE
|
|
|
|
// clang-format off
|
|
TF_DEFINE_PRIVATE_TOKENS(_tokens,
|
|
(cycles)
|
|
(openvdbAsset)
|
|
);
|
|
|
|
TF_DEFINE_PRIVATE_TOKENS(HdCyclesRenderSettingsTokens,
|
|
((device, "cycles:device"))
|
|
((threads, "cycles:threads"))
|
|
((time_limit, "cycles:time_limit"))
|
|
((samples, "cycles:samples"))
|
|
((sample_offset, "cycles:sample_offset"))
|
|
);
|
|
// clang-format on
|
|
|
|
namespace {
|
|
|
|
const TfTokenVector kSupportedRPrimTypes = {
|
|
HdPrimTypeTokens->basisCurves,
|
|
HdPrimTypeTokens->mesh,
|
|
HdPrimTypeTokens->points,
|
|
#ifdef WITH_OPENVDB
|
|
HdPrimTypeTokens->volume,
|
|
#endif
|
|
};
|
|
|
|
const TfTokenVector kSupportedSPrimTypes = {
|
|
HdPrimTypeTokens->camera,
|
|
HdPrimTypeTokens->material,
|
|
HdPrimTypeTokens->diskLight,
|
|
HdPrimTypeTokens->distantLight,
|
|
HdPrimTypeTokens->domeLight,
|
|
HdPrimTypeTokens->rectLight,
|
|
HdPrimTypeTokens->sphereLight,
|
|
HdPrimTypeTokens->extComputation,
|
|
};
|
|
|
|
const TfTokenVector kSupportedBPrimTypes = {
|
|
HdPrimTypeTokens->renderBuffer,
|
|
#ifdef WITH_OPENVDB
|
|
_tokens->openvdbAsset,
|
|
#endif
|
|
};
|
|
|
|
SessionParams GetSessionParams(const HdRenderSettingsMap &settings)
|
|
{
|
|
SessionParams params;
|
|
params.threads = 0;
|
|
params.background = false;
|
|
params.use_resolution_divider = false;
|
|
|
|
HdRenderSettingsMap::const_iterator it;
|
|
|
|
// Pull all setting that contribute to device creation first
|
|
it = settings.find(HdCyclesRenderSettingsTokens->threads);
|
|
if (it != settings.end()) {
|
|
params.threads = VtValue::Cast<int>(it->second).GetWithDefault(params.threads);
|
|
}
|
|
|
|
// Get the Cycles device from settings or environment, falling back to CPU
|
|
std::string deviceType = Device::string_from_type(DEVICE_CPU);
|
|
it = settings.find(HdCyclesRenderSettingsTokens->device);
|
|
if (it != settings.end()) {
|
|
deviceType = VtValue::Cast<std::string>(it->second).GetWithDefault(deviceType);
|
|
}
|
|
else {
|
|
const std::string deviceTypeEnv = TfGetenv("CYCLES_DEVICE");
|
|
if (!deviceTypeEnv.empty()) {
|
|
deviceType = deviceTypeEnv;
|
|
}
|
|
}
|
|
|
|
// Move to all uppercase for Device::type_from_string
|
|
std::transform(deviceType.begin(), deviceType.end(), deviceType.begin(), ::toupper);
|
|
|
|
vector<DeviceInfo> devices = Device::available_devices(
|
|
DEVICE_MASK(Device::type_from_string(deviceType.c_str())));
|
|
if (devices.empty()) {
|
|
devices = Device::available_devices(DEVICE_MASK_CPU);
|
|
if (!devices.empty()) {
|
|
params.device = devices.front();
|
|
}
|
|
}
|
|
else {
|
|
params.device = Device::get_multi_device(devices, params.threads, params.background);
|
|
}
|
|
|
|
return params;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
HdCyclesDelegate::HdCyclesDelegate(const HdRenderSettingsMap &settingsMap, Session *session_)
|
|
: HdRenderDelegate()
|
|
{
|
|
_renderParam = session_ ? std::make_unique<HdCyclesSession>(session_) :
|
|
std::make_unique<HdCyclesSession>(GetSessionParams(settingsMap));
|
|
|
|
// If the delegate owns the session, pull any remaining settings
|
|
if (!session_) {
|
|
for (const auto &setting : settingsMap) {
|
|
// Skip over the settings known to be used for initialization only
|
|
if (setting.first == HdCyclesRenderSettingsTokens->device ||
|
|
setting.first == HdCyclesRenderSettingsTokens->threads) {
|
|
continue;
|
|
}
|
|
|
|
SetRenderSetting(setting.first, setting.second);
|
|
}
|
|
}
|
|
}
|
|
|
|
HdCyclesDelegate::~HdCyclesDelegate()
|
|
{
|
|
}
|
|
|
|
void HdCyclesDelegate::SetDrivers(const HdDriverVector &drivers)
|
|
{
|
|
for (HdDriver *hdDriver : drivers) {
|
|
if (hdDriver->name == HgiTokens->renderDriver && hdDriver->driver.IsHolding<Hgi *>()) {
|
|
_hgi = hdDriver->driver.UncheckedGet<Hgi *>();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool HdCyclesDelegate::IsDisplaySupported() const
|
|
{
|
|
#ifdef _WIN32
|
|
return _hgi && _hgi->GetAPIName() == HgiTokens->OpenGL;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
const TfTokenVector &HdCyclesDelegate::GetSupportedRprimTypes() const
|
|
{
|
|
return kSupportedRPrimTypes;
|
|
}
|
|
|
|
const TfTokenVector &HdCyclesDelegate::GetSupportedSprimTypes() const
|
|
{
|
|
return kSupportedSPrimTypes;
|
|
}
|
|
|
|
const TfTokenVector &HdCyclesDelegate::GetSupportedBprimTypes() const
|
|
{
|
|
return kSupportedBPrimTypes;
|
|
}
|
|
|
|
HdRenderParam *HdCyclesDelegate::GetRenderParam() const
|
|
{
|
|
return _renderParam.get();
|
|
}
|
|
|
|
HdResourceRegistrySharedPtr HdCyclesDelegate::GetResourceRegistry() const
|
|
{
|
|
return HdResourceRegistrySharedPtr();
|
|
}
|
|
|
|
bool HdCyclesDelegate::IsPauseSupported() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool HdCyclesDelegate::Pause()
|
|
{
|
|
_renderParam->session->set_pause(true);
|
|
return true;
|
|
}
|
|
|
|
bool HdCyclesDelegate::Resume()
|
|
{
|
|
_renderParam->session->set_pause(false);
|
|
return true;
|
|
}
|
|
|
|
HdRenderPassSharedPtr HdCyclesDelegate::CreateRenderPass(HdRenderIndex *index,
|
|
const HdRprimCollection &collection)
|
|
{
|
|
return HdRenderPassSharedPtr(new HdCyclesRenderPass(index, collection, _renderParam.get()));
|
|
}
|
|
|
|
HdInstancer *HdCyclesDelegate::CreateInstancer(HdSceneDelegate *delegate,
|
|
const SdfPath &instancerId
|
|
#if PXR_VERSION < 2102
|
|
,
|
|
const SdfPath &parentId
|
|
#endif
|
|
)
|
|
{
|
|
return new HdCyclesInstancer(delegate,
|
|
instancerId
|
|
#if PXR_VERSION < 2102
|
|
,
|
|
parentId
|
|
#endif
|
|
);
|
|
}
|
|
|
|
void HdCyclesDelegate::DestroyInstancer(HdInstancer *instancer)
|
|
{
|
|
delete instancer;
|
|
}
|
|
|
|
HdRprim *HdCyclesDelegate::CreateRprim(const TfToken &typeId,
|
|
const SdfPath &rprimId
|
|
#if PXR_VERSION < 2102
|
|
,
|
|
const SdfPath &instancerId
|
|
#endif
|
|
)
|
|
{
|
|
if (typeId == HdPrimTypeTokens->mesh) {
|
|
return new HdCyclesMesh(rprimId
|
|
#if PXR_VERSION < 2102
|
|
,
|
|
instancerId
|
|
#endif
|
|
);
|
|
}
|
|
if (typeId == HdPrimTypeTokens->basisCurves) {
|
|
return new HdCyclesCurves(rprimId
|
|
#if PXR_VERSION < 2102
|
|
,
|
|
instancerId
|
|
#endif
|
|
);
|
|
}
|
|
if (typeId == HdPrimTypeTokens->points) {
|
|
return new HdCyclesPoints(rprimId
|
|
#if PXR_VERSION < 2102
|
|
,
|
|
instancerId
|
|
#endif
|
|
);
|
|
}
|
|
#ifdef WITH_OPENVDB
|
|
if (typeId == HdPrimTypeTokens->volume) {
|
|
return new HdCyclesVolume(rprimId
|
|
# if PXR_VERSION < 2102
|
|
,
|
|
instancerId
|
|
# endif
|
|
);
|
|
}
|
|
#endif
|
|
|
|
TF_CODING_ERROR("Unknown Rprim type %s", typeId.GetText());
|
|
return nullptr;
|
|
}
|
|
|
|
void HdCyclesDelegate::DestroyRprim(HdRprim *rPrim)
|
|
{
|
|
delete rPrim;
|
|
}
|
|
|
|
HdSprim *HdCyclesDelegate::CreateSprim(const TfToken &typeId, const SdfPath &sprimId)
|
|
{
|
|
if (typeId == HdPrimTypeTokens->camera) {
|
|
return new HdCyclesCamera(sprimId);
|
|
}
|
|
if (typeId == HdPrimTypeTokens->material) {
|
|
return new HdCyclesMaterial(sprimId);
|
|
}
|
|
if (typeId == HdPrimTypeTokens->diskLight || typeId == HdPrimTypeTokens->distantLight ||
|
|
typeId == HdPrimTypeTokens->domeLight || typeId == HdPrimTypeTokens->rectLight ||
|
|
typeId == HdPrimTypeTokens->sphereLight) {
|
|
return new HdCyclesLight(sprimId, typeId);
|
|
}
|
|
if (typeId == HdPrimTypeTokens->extComputation) {
|
|
return new HdExtComputation(sprimId);
|
|
}
|
|
|
|
TF_CODING_ERROR("Unknown Sprim type %s", typeId.GetText());
|
|
return nullptr;
|
|
}
|
|
|
|
HdSprim *HdCyclesDelegate::CreateFallbackSprim(const TfToken &typeId)
|
|
{
|
|
return CreateSprim(typeId, SdfPath::EmptyPath());
|
|
}
|
|
|
|
void HdCyclesDelegate::DestroySprim(HdSprim *sPrim)
|
|
{
|
|
delete sPrim;
|
|
}
|
|
|
|
HdBprim *HdCyclesDelegate::CreateBprim(const TfToken &typeId, const SdfPath &bprimId)
|
|
{
|
|
if (typeId == HdPrimTypeTokens->renderBuffer) {
|
|
return new HdCyclesRenderBuffer(bprimId);
|
|
}
|
|
#ifdef WITH_OPENVDB
|
|
if (typeId == _tokens->openvdbAsset) {
|
|
return new HdCyclesField(bprimId, typeId);
|
|
}
|
|
#endif
|
|
|
|
TF_RUNTIME_ERROR("Unknown Bprim type %s", typeId.GetText());
|
|
return nullptr;
|
|
}
|
|
|
|
HdBprim *HdCyclesDelegate::CreateFallbackBprim(const TfToken &typeId)
|
|
{
|
|
return CreateBprim(typeId, SdfPath::EmptyPath());
|
|
}
|
|
|
|
void HdCyclesDelegate::DestroyBprim(HdBprim *bPrim)
|
|
{
|
|
delete bPrim;
|
|
}
|
|
|
|
void HdCyclesDelegate::CommitResources(HdChangeTracker *tracker)
|
|
{
|
|
TF_UNUSED(tracker);
|
|
|
|
const SceneLock lock(_renderParam.get());
|
|
|
|
_renderParam->UpdateScene();
|
|
}
|
|
|
|
TfToken HdCyclesDelegate::GetMaterialBindingPurpose() const
|
|
{
|
|
return HdTokens->full;
|
|
}
|
|
|
|
#if HD_API_VERSION < 41
|
|
TfToken HdCyclesDelegate::GetMaterialNetworkSelector() const
|
|
{
|
|
return _tokens->cycles;
|
|
}
|
|
#else
|
|
TfTokenVector HdCyclesDelegate::GetMaterialRenderContexts() const
|
|
{
|
|
return {_tokens->cycles};
|
|
}
|
|
#endif
|
|
|
|
VtDictionary HdCyclesDelegate::GetRenderStats() const
|
|
{
|
|
const Stats &stats = _renderParam->session->stats;
|
|
const Progress &progress = _renderParam->session->progress;
|
|
|
|
double totalTime, renderTime;
|
|
progress.get_time(totalTime, renderTime);
|
|
double fractionDone = progress.get_progress();
|
|
|
|
std::string status, substatus;
|
|
progress.get_status(status, substatus);
|
|
if (!substatus.empty()) {
|
|
status += " | " + substatus;
|
|
}
|
|
|
|
return {{"rendererName", VtValue("Cycles")},
|
|
{"rendererVersion", VtValue(GfVec3i(0, 0, 0))},
|
|
{"percentDone", VtValue(floor_to_int(fractionDone * 100))},
|
|
{"fractionDone", VtValue(fractionDone)},
|
|
{"loadClockTime", VtValue(totalTime - renderTime)},
|
|
{"peakMemory", VtValue(stats.mem_peak)},
|
|
{"totalClockTime", VtValue(totalTime)},
|
|
{"totalMemory", VtValue(stats.mem_used)},
|
|
{"renderProgressAnnotation", VtValue(status)}};
|
|
}
|
|
|
|
HdAovDescriptor HdCyclesDelegate::GetDefaultAovDescriptor(const TfToken &name) const
|
|
{
|
|
if (name == HdAovTokens->color) {
|
|
HdFormat colorFormat = HdFormatFloat32Vec4;
|
|
if (IsDisplaySupported()) {
|
|
// Can use Cycles 'DisplayDriver' in OpenGL, but it only supports 'half4' format
|
|
colorFormat = HdFormatFloat16Vec4;
|
|
}
|
|
|
|
return HdAovDescriptor(colorFormat, false, VtValue(GfVec4f(0.0f)));
|
|
}
|
|
if (name == HdAovTokens->depth) {
|
|
return HdAovDescriptor(HdFormatFloat32, false, VtValue(1.0f));
|
|
}
|
|
if (name == HdAovTokens->normal) {
|
|
return HdAovDescriptor(HdFormatFloat32Vec3, false, VtValue(GfVec3f(0.0f)));
|
|
}
|
|
if (name == HdAovTokens->primId || name == HdAovTokens->instanceId ||
|
|
name == HdAovTokens->elementId) {
|
|
return HdAovDescriptor(HdFormatInt32, false, VtValue(-1));
|
|
}
|
|
|
|
return HdAovDescriptor();
|
|
}
|
|
|
|
HdRenderSettingDescriptorList HdCyclesDelegate::GetRenderSettingDescriptors() const
|
|
{
|
|
Scene *const scene = _renderParam->session->scene;
|
|
|
|
HdRenderSettingDescriptorList descriptors;
|
|
|
|
descriptors.push_back({
|
|
"Time Limit",
|
|
HdCyclesRenderSettingsTokens->time_limit,
|
|
VtValue(0.0),
|
|
});
|
|
descriptors.push_back({
|
|
"Sample Count",
|
|
HdCyclesRenderSettingsTokens->samples,
|
|
VtValue(1024),
|
|
});
|
|
descriptors.push_back({
|
|
"Sample Offset",
|
|
HdCyclesRenderSettingsTokens->sample_offset,
|
|
VtValue(0),
|
|
});
|
|
|
|
for (const SocketType &socket : scene->integrator->type->inputs) {
|
|
descriptors.push_back({socket.ui_name.string(),
|
|
TfToken("cycles:integrator:" + socket.name.string()),
|
|
GetNodeValue(scene->integrator, socket)});
|
|
}
|
|
|
|
return descriptors;
|
|
}
|
|
|
|
void HdCyclesDelegate::SetRenderSetting(const PXR_NS::TfToken &key, const PXR_NS::VtValue &value)
|
|
{
|
|
Scene *const scene = _renderParam->session->scene;
|
|
Session *const session = _renderParam->session;
|
|
|
|
if (key == HdCyclesRenderSettingsTokens->time_limit) {
|
|
session->set_time_limit(
|
|
VtValue::Cast<double>(value).GetWithDefault(session->params.time_limit));
|
|
}
|
|
else if (key == HdCyclesRenderSettingsTokens->samples) {
|
|
int samples = VtValue::Cast<int>(value).GetWithDefault(session->params.samples);
|
|
samples = std::min(std::max(1, samples), Integrator::MAX_SAMPLES);
|
|
session->set_samples(samples);
|
|
}
|
|
else if (key == HdCyclesRenderSettingsTokens->sample_offset) {
|
|
session->params.sample_offset = VtValue::Cast<int>(value).GetWithDefault(
|
|
session->params.sample_offset);
|
|
++_settingsVersion;
|
|
}
|
|
else {
|
|
const std::string &keyString = key.GetString();
|
|
if (keyString.rfind("cycles:integrator:", 0) == 0) {
|
|
ustring socketName(keyString, sizeof("cycles:integrator:") - 1);
|
|
if (const SocketType *socket = scene->integrator->type->find_input(socketName)) {
|
|
SetNodeValue(scene->integrator, *socket, value);
|
|
++_settingsVersion;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
VtValue HdCyclesDelegate::GetRenderSetting(const TfToken &key) const
|
|
{
|
|
Scene *const scene = _renderParam->session->scene;
|
|
Session *const session = _renderParam->session;
|
|
|
|
if (key == HdCyclesRenderSettingsTokens->device) {
|
|
return VtValue(TfToken(Device::string_from_type(session->params.device.type)));
|
|
}
|
|
else if (key == HdCyclesRenderSettingsTokens->threads) {
|
|
return VtValue(session->params.threads);
|
|
}
|
|
else if (key == HdCyclesRenderSettingsTokens->time_limit) {
|
|
return VtValue(session->params.time_limit);
|
|
}
|
|
else if (key == HdCyclesRenderSettingsTokens->samples) {
|
|
return VtValue(session->params.samples);
|
|
}
|
|
else if (key == HdCyclesRenderSettingsTokens->sample_offset) {
|
|
return VtValue(session->params.sample_offset);
|
|
}
|
|
else {
|
|
const std::string &keyString = key.GetString();
|
|
if (keyString.rfind("cycles:integrator:", 0) == 0) {
|
|
ustring socketName(keyString, sizeof("cycles:integrator:") - 1);
|
|
if (const SocketType *socket = scene->integrator->type->find_input(socketName)) {
|
|
return GetNodeValue(scene->integrator, *socket);
|
|
}
|
|
}
|
|
}
|
|
|
|
return VtValue();
|
|
}
|
|
|
|
HDCYCLES_NAMESPACE_CLOSE_SCOPE
|