Hydra render engine #104712

Closed
Bogdan Nagirniak wants to merge 142 commits from BogdanNagirniak/blender:hydra-render into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
7 changed files with 179 additions and 77 deletions
Showing only changes of commit a34976e949 - Show all commits

View File

@ -39,7 +39,9 @@ void PreviewEngine::update_render_result(std::vector<float> &pixels)
RenderLayer *layer = (RenderLayer *)result->layers.first;
RenderPass *pass = (RenderPass *)layer->passes.first;
memcpy(pass->buffer.data, pixels.data(), sizeof(float) * pass->rectx * pass->recty * pass->channels);
memcpy(pass->buffer.data,
pixels.data(),
sizeof(float) * pass->rectx * pass->recty * pass->channels);
BogdanNagirniak marked this conversation as resolved Outdated

This is still using a timer, it should not be used at all. Other render engines don't need one either to free data.

This is still using a timer, it should not be used at all. Other render engines don't need one either to free data.

We use timer because it is not efficient to recreate Preview render engine each time when material is changes, including render of material thumbnail. Therefore we decided to free Preview engine after some time when it wasn't used. Can you propose another solution without timer or maybe it should be moved to python part?

We use timer because it is not efficient to recreate Preview render engine each time when material is changes, including render of material thumbnail. Therefore we decided to free Preview engine after some time when it wasn't used. Can you propose another solution without timer or maybe it should be moved to python part?

Why is it inefficient, and how inefficient are we talking about? Is there anything that can be done to make it more efficient, maybe on the add-on side to reduce startup time or cache some slow library initialization? For Cycles and Eevee the startup time is not so much an issue.

Using a timer is and a single global instance is just not safe. There can be multiple previews at the same time, multiple threads can create a preview engine, the add-on may be disabled after which it's not longer safe to free the delegate, the instance is freed as part of static deinitialization which is too late, etc.

There are safer ways to do this, perhaps by Blender holding onto RenderEngine instances longer. But before we add that complexity, I'd like to understand why there is a performance issue in the first place.

Why is it inefficient, and how inefficient are we talking about? Is there anything that can be done to make it more efficient, maybe on the add-on side to reduce startup time or cache some slow library initialization? For Cycles and Eevee the startup time is not so much an issue. Using a timer is and a single global instance is just not safe. There can be multiple previews at the same time, multiple threads can create a preview engine, the add-on may be disabled after which it's not longer safe to free the delegate, the instance is freed as part of static deinitialization which is too late, etc. There are safer ways to do this, perhaps by Blender holding onto `RenderEngine` instances longer. But before we add that complexity, I'd like to understand why there is a performance issue in the first place.

Initializing of render delegate - it could be a complex task including GPU initializing and memory reservation etc and could take some time (till to several seconds). Therefore recreating render delegate each time when material is changed to render material preview and thumbnail takes some time, for example in RPR delegate.
Probably this optimization has to be moved to addon side, what do you think?

Initializing of render delegate - it could be a complex task including GPU initializing and memory reservation etc and could take some time (till to several seconds). Therefore recreating render delegate each time when material is changed to render material preview and thumbnail takes some time, for example in RPR delegate. Probably this optimization has to be moved to addon side, what do you think?

Yes, this seems like it can be handled in the add-on. And then enabling viewport rendering or pressing F12 to render would likely be faster too. In Cycles this doesn't take several seconds though, not sure why it would be so slow in Hydra or RPR.

Yes, this seems like it can be handled in the add-on. And then enabling viewport rendering or pressing F12 to render would likely be faster too. In Cycles this doesn't take several seconds though, not sure why it would be so slow in Hydra or RPR.

Yes, this seems like it can be handled in the add-on. And then enabling viewport rendering or pressing F12 to render would likely be faster too. In Cycles this doesn't take several seconds though, not sure why it would be so slow in Hydra or RPR.

@BogdanNagirniak if this is an hydra RPR issue (not an issue in HDStorm), it should not be worked around here.

> Yes, this seems like it can be handled in the add-on. And then enabling viewport rendering or pressing F12 to render would likely be faster too. In Cycles this doesn't take several seconds though, not sure why it would be so slow in Hydra or RPR. @BogdanNagirniak if this is an hydra RPR issue (not an issue in HDStorm), it should not be worked around here.
RE_engine_end_result(bl_engine_, result, false, false, false);
}

View File

@ -14,6 +14,19 @@ namespace blender::render::hydra {
BogdanNagirniak marked this conversation as resolved Outdated

Use a less obscure name, something like:

LOG_RENDER_HYDRA_SCENE
render.hydra.scene

Use a less obscure name, something like: LOG_RENDER_HYDRA_SCENE render.hydra.scene
CLG_LOGREF_DECLARE_GLOBAL(LOG_RENDER_HYDRA_SCENE, "render.hydra.scene");
bool BlenderSceneDelegate::ShadingSettings::operator==(const ShadingSettings &other)
{
bool ret = use_scene_lights == other.use_scene_lights &&
use_scene_world == other.use_scene_world;
if (ret && !use_scene_world) {
/* compare studiolight settings when studiolight is using */
ret = studiolight_name == other.studiolight_name &&
BogdanNagirniak marked this conversation as resolved Outdated

Null points can be initialized in the class definition directly instead of here.

Null points can be initialized in the class definition directly instead of here.
studiolight_rotation == other.studiolight_rotation &&
studiolight_intensity == other.studiolight_intensity;
}
return ret;
}
BlenderSceneDelegate::BlenderSceneDelegate(pxr::HdRenderIndex *parent_index,
pxr::SdfPath const &delegate_id,
Engine *engine)
@ -205,6 +218,8 @@ void BlenderSceneDelegate::populate(Depsgraph *deps, bContext *cont)
check_updates();
}
else {
set_light_shading_settings();
set_world_shading_settings();
add_new_objects();
update_world();
}
@ -338,7 +353,9 @@ void BlenderSceneDelegate::update_objects(Object *object)
if (!ObjectData::is_supported(object)) {
return;
}
if (!shading_settings.use_scene_lights && object->type == OB_LAMP) {
return;
}
pxr::SdfPath id = object_prim_id(object);
ObjectData *obj_data = object_data(id);
if (obj_data) {
@ -397,17 +414,16 @@ void BlenderSceneDelegate::update_instancers(Object *object)
void BlenderSceneDelegate::update_world()
{
World *world = scene->world;
if (!world_data_) {
if (world) {
world_data_ = std::make_unique<WorldData>(this, world, world_prim_id());
if (!shading_settings.use_scene_world || (shading_settings.use_scene_world && scene->world)) {
world_data_ = std::make_unique<WorldData>(this, world_prim_id());
world_data_->init();
world_data_->insert();
}
}
else {
if (world) {
world_data_->update(world);
if (!shading_settings.use_scene_world || (shading_settings.use_scene_world && scene->world)) {
world_data_->update();
}
else {
world_data_->remove();
@ -422,6 +438,19 @@ void BlenderSceneDelegate::check_updates()
bool do_update_visibility = false;
bool do_update_world = false;
if (set_world_shading_settings()) {
do_update_world = true;
}
if (set_light_shading_settings()) {
if (shading_settings.use_scene_lights) {
add_new_objects();
}
else {
do_update_collection = true;
}
}
DEGIDIterData data = {0};
data.graph = depsgraph;
data.only_updated = true;
@ -455,7 +484,7 @@ void BlenderSceneDelegate::check_updates()
} break;
case ID_WO: {
if (id->recalc & ID_RECALC_SHADING) {
if (shading_settings.use_scene_world && id->recalc & ID_RECALC_SHADING) {
do_update_world = true;
}
} break;
@ -518,6 +547,9 @@ void BlenderSceneDelegate::add_new_objects()
"Visibility: %s [%s]",
object->id.name,
std::bitset<3>(BKE_object_visibility(object, deg_mode)).to_string().c_str());
if (object_data(object_prim_id(object))) {
continue;
}
update_objects(object);
update_instancers(object);
}
@ -544,10 +576,13 @@ void BlenderSceneDelegate::remove_unused_objects()
Object *,
object)
{
available_objects.add(instancer_prim_id(object).GetName());
if (ObjectData::is_supported(object)) {
if (!shading_settings.use_scene_lights && object->type == OB_LAMP) {
continue;
}
available_objects.add(object_prim_id(object).GetName());
}
available_objects.add(instancer_prim_id(object).GetName());
}
ITER_END;
@ -639,4 +674,27 @@ void BlenderSceneDelegate::update_visibility()
ITER_END;
}
bool BlenderSceneDelegate::set_light_shading_settings()
{
if (!view3d) {
return false;
}
ShadingSettings prev_settings(shading_settings);
shading_settings.use_scene_lights = V3D_USES_SCENE_LIGHTS(view3d);
return !(shading_settings == prev_settings);
}
bool BlenderSceneDelegate::set_world_shading_settings()
{
if (!view3d) {
return false;
}
ShadingSettings prev_settings(shading_settings);
shading_settings.use_scene_world = V3D_USES_SCENE_WORLD(view3d);
shading_settings.studiolight_name = view3d->shading.lookdev_light;
shading_settings.studiolight_rotation = view3d->shading.studiolight_rot_z;
shading_settings.studiolight_intensity = view3d->shading.studiolight_intensity;
return !(shading_settings == prev_settings);
}
} // namespace blender::render::hydra

View File

@ -7,11 +7,11 @@
#include <pxr/imaging/hd/sceneDelegate.h>
#include "BKE_context.h"
#include "BLI_map.hh"
#include "DEG_depsgraph.h"
#include "CLG_log.h"
#include "BLI_map.hh"
#include "curves.h"
#include "instancer.h"
#include "light.h"
@ -37,6 +37,16 @@ class BlenderSceneDelegate : public pxr::HdSceneDelegate {
Map<pxr::TfToken, pxr::VtValue> render_tokens;
};
struct ShadingSettings {
bool use_scene_lights = true;
bool use_scene_world = true;
std::string studiolight_name;
float studiolight_rotation;
float studiolight_intensity;
bool operator==(const ShadingSettings &other);
};
BlenderSceneDelegate(pxr::HdRenderIndex *parent_index,
pxr::SdfPath const &delegate_id,
Engine *engine);
@ -71,6 +81,7 @@ class BlenderSceneDelegate : public pxr::HdSceneDelegate {
Scene *scene = nullptr;
Engine *engine;
Settings settings;
ShadingSettings shading_settings;
private:
pxr::SdfPath prim_id(ID *id, const char *prefix) const;
@ -93,6 +104,8 @@ class BlenderSceneDelegate : public pxr::HdSceneDelegate {
void add_new_objects();
void remove_unused_objects();
void update_visibility();
bool set_light_shading_settings();
bool set_world_shading_settings();
ObjectDataMap objects_;
MaterialDataMap materials_;

View File

@ -76,8 +76,9 @@ std::string cache_or_get_image_file(Image *image, bContext *context, ImageUser *
}
else {
Main *main = CTX_data_main(context);
file_path.reserve(FILE_MAX);
BKE_image_user_file_path_ex(main, iuser, image, file_path.data(), false, true);
char str[FILE_MAX];
BKE_image_user_file_path_ex(main, iuser, image, str, false, true);
file_path = str;
if (!pxr::HioImageRegistry::GetInstance().IsSupportedImageFile(file_path)) {
file_path = cache_image_file(image, context, iuser, true);

View File

@ -353,6 +353,9 @@ void InstancerData::write_instances()
scene_delegate_->depsgraph, scene_delegate_->scene, (Object *)id);
BogdanNagirniak marked this conversation as resolved Outdated

Cycles and Eevee don't use this function directly, but instead use a depsgraph iterator over all instances in the scene. For Hydra it looks like a different mechanism is used, and most likely wrong in various cases.

If at all possible it should really use the iterator over all instances in the scene instead. It's very hard to verify that this generating the same instances and visibility.

Cycles and Eevee don't use this function directly, but instead use a depsgraph iterator over all instances in the scene. For Hydra it looks like a different mechanism is used, and most likely wrong in various cases. If at all possible it should really use the iterator over all instances in the scene instead. It's very hard to verify that this generating the same instances and visibility.
LISTBASE_FOREACH (DupliObject *, dupli, lb) {
Object *ob = dupli->ob;
if (!scene_delegate_->shading_settings.use_scene_lights && ob->type == OB_LAMP) {
continue;
}
if (!is_supported(ob) || !is_instance_visible(ob)) {
continue;
}

View File

@ -16,6 +16,8 @@
#include "BKE_node.h"
#include "BKE_node_runtime.hh"
#include "BKE_studiolight.h"
#include "BLI_math_rotation.h"
#include "BLI_path_util.h"
#include "NOD_shader.h"
@ -26,93 +28,122 @@
/* TODO : add custom tftoken "transparency"? */
/* NOTE: opacity and blur aren't supported by USD */
namespace blender::render::hydra {
WorldData::WorldData(BlenderSceneDelegate *scene_delegate,
World *world,
pxr::SdfPath const &prim_id)
: IdData(scene_delegate, (ID *)world, prim_id)
WorldData::WorldData(BlenderSceneDelegate *scene_delegate, pxr::SdfPath const &prim_id)
: IdData(scene_delegate, nullptr, prim_id)
{
}
void WorldData::init()
{
ID_LOG(1, "");
write_transform();
World *world = (World *)id;
data_.clear();
data_[pxr::UsdLuxTokens->orientToStageUpAxis] = true;
if (world->use_nodes) {
/* TODO: Create nodes parsing system */
float intensity = 1.0f;
float exposure = 1.0f;
pxr::GfVec3f color(1.0f, 1.0f, 1.0f);
pxr::SdfAssetPath texture_file;
bNode *output_node = ntreeShaderOutputNode(world->nodetree, SHD_OUTPUT_ALL);
blender::Span<bNodeSocket *> input_sockets = output_node->input_sockets();
bNodeSocket *input_socket = nullptr;
if (scene_delegate_->shading_settings.use_scene_world) {
World *world = scene_delegate_->scene->world;
CLOG_INFO(LOG_RENDER_HYDRA_SCENE, 1, "%s: %s", prim_id.GetText(), world->id.name);
for (auto socket : input_sockets) {
if (STREQ(socket->name, "Surface")) {
input_socket = socket;
break;
exposure = world->exposure;
if (world->use_nodes) {
/* TODO: Create nodes parsing system */
bNode *output_node = ntreeShaderOutputNode(world->nodetree, SHD_OUTPUT_ALL);
blender::Span<bNodeSocket *> input_sockets = output_node->input_sockets();
bNodeSocket *input_socket = nullptr;
for (auto socket : input_sockets) {
if (STREQ(socket->name, "Surface")) {
input_socket = socket;
break;
}
}
if (!input_socket) {
return;
}
bNodeLink const *link = input_socket->directly_linked_links()[0];
if (input_socket->directly_linked_links().is_empty()) {
return;
}
}
if (!input_socket) {
return;
}
bNodeLink const *link = input_socket->directly_linked_links()[0];
if (input_socket->directly_linked_links().is_empty()) {
return;
}
bNode *input_node = link->fromnode;
if (input_node->type != SH_NODE_BACKGROUND) {
return;
}
bNode *input_node = link->fromnode;
if (input_node->type != SH_NODE_BACKGROUND) {
return;
}
bNodeSocket color_input = input_node->input_by_identifier("Color");
bNodeSocket strength_input = input_node->input_by_identifier("Strength");
bNodeSocket color_input = input_node->input_by_identifier("Color");
bNodeSocket strength_input = input_node->input_by_identifier("Strength");
float const *strength = strength_input.default_value_typed<float>();
float const *color = color_input.default_value_typed<float>();
data_[pxr::HdLightTokens->intensity] = strength[1];
data_[pxr::HdLightTokens->exposure] = 1.0f;
data_[pxr::HdLightTokens->color] = pxr::GfVec3f(color[0], color[1], color[2]);
float const *strength = strength_input.default_value_typed<float>();
BogdanNagirniak marked this conversation as resolved Outdated

In Cycles and Eevee, the world typically has an Environment Texture node rather than an Image Texture node, to get the right image mapping. If this is going to be just an approximation, that may be a better guess. Or it could do both of course.

In Cycles and Eevee, the world typically has an Environment Texture node rather than an Image Texture node, to get the right image mapping. If this is going to be just an approximation, that may be a better guess. Or it could do both of course.

This is in our TODOs list to support more nodes for World including Environment Texture node

This is in our TODOs list to support more nodes for World including Environment Texture node
float const *input_color = color_input.default_value_typed<float>();
intensity = strength[1];
color = pxr::GfVec3f(input_color[0], input_color[1], input_color[2]);
if (!color_input.directly_linked_links().is_empty()) {
bNode *color_input_node = color_input.directly_linked_links()[0]->fromnode;
if (color_input_node->type == SH_NODE_TEX_IMAGE) {
NodeTexImage *tex = static_cast<NodeTexImage *>(color_input_node->storage);
Image *image = (Image *)color_input_node->id;
if (image) {
std::string image_path = cache_or_get_image_file(
image, scene_delegate_->context, &tex->iuser);
if (!image_path.empty()) {
data_[pxr::HdLightTokens->textureFile] = pxr::SdfAssetPath(image_path, image_path);
if (!color_input.directly_linked_links().is_empty()) {
bNode *color_input_node = color_input.directly_linked_links()[0]->fromnode;
if (color_input_node->type == SH_NODE_TEX_IMAGE) {
NodeTexImage *tex = static_cast<NodeTexImage *>(color_input_node->storage);
Image *image = (Image *)color_input_node->id;
if (image) {
std::string image_path = cache_or_get_image_file(
image, scene_delegate_->context, &tex->iuser);
if (!image_path.empty()) {
texture_file = pxr::SdfAssetPath(image_path, image_path);
}
}
}
}
}
else {
intensity = 1.0f;
color = pxr::GfVec3f(world->horr, world->horg, world->horb);
}
if (texture_file.GetAssetPath().empty()) {
float fill_color[4] = {color[0], color[1], color[2], 1.0f};
std::string image_path = cache_image_color(fill_color);
texture_file = pxr::SdfAssetPath(image_path, image_path);
}
}
else {
data_[pxr::HdLightTokens->intensity] = 1.0f;
data_[pxr::HdLightTokens->exposure] = world->exposure;
data_[pxr::HdLightTokens->color] = pxr::GfVec3f(world->horr, world->horg, world->horb);
CLOG_INFO(LOG_RENDER_HYDRA_SCENE,
1,
"%s: studiolight: %s",
prim_id.GetText(),
scene_delegate_->shading_settings.studiolight_name.c_str());
StudioLight *sl = BKE_studiolight_find(
scene_delegate_->shading_settings.studiolight_name.c_str(),
STUDIOLIGHT_ORIENTATIONS_MATERIAL_MODE);
if (sl != NULL && sl->flag & STUDIOLIGHT_TYPE_WORLD) {
texture_file = pxr::SdfAssetPath(sl->filepath, sl->filepath);
transform *= pxr::GfMatrix4d(
pxr::GfRotation(pxr::GfVec3d(0.0, 0.0, -1.0),
RAD2DEGF(scene_delegate_->shading_settings.studiolight_rotation)),
pxr::GfVec3d());
/* coefficient to follow Cycles result */
intensity = scene_delegate_->shading_settings.studiolight_intensity / 2;
}
}
if (data_.find(pxr::HdLightTokens->textureFile) == data_.end()) {
pxr::GfVec3f c = data_[pxr::HdLightTokens->color].Get<pxr::GfVec3f>();
float color[4] = {c[0], c[1], c[2], 1.0f};
std::string image_path = cache_image_color(color);
data_[pxr::HdLightTokens->textureFile] = pxr::SdfAssetPath(image_path, image_path);
}
data_[pxr::HdLightTokens->intensity] = intensity;
data_[pxr::HdLightTokens->exposure] = exposure;
data_[pxr::HdLightTokens->color] = color;
data_[pxr::HdLightTokens->textureFile] = texture_file;
}
void WorldData::insert()
{
ID_LOG(1, "");
CLOG_INFO(LOG_RENDER_HYDRA_SCENE, 1, "%s", prim_id.GetText());
scene_delegate_->GetRenderIndex().InsertSprim(
pxr::HdPrimTypeTokens->domeLight, scene_delegate_, prim_id);
}
@ -125,23 +156,17 @@ void WorldData::remove()
void WorldData::update()
{
ID_LOG(1, "");
CLOG_INFO(LOG_RENDER_HYDRA_SCENE, 1, "%s", prim_id.GetText());
init();
scene_delegate_->GetRenderIndex().GetChangeTracker().MarkSprimDirty(prim_id,
pxr::HdLight::AllDirty);
}
void WorldData::update(World *world)
{
id = (ID *)world;
update();
}
pxr::VtValue WorldData::get_data(pxr::TfToken const &key) const
{
auto it = data_.find(key);
if (it != data_.end()) {
ID_LOG(3, "%s", key.GetText());
CLOG_INFO(LOG_RENDER_HYDRA_SCENE, 3, "%s: %s", prim_id.GetText(), key.GetText());
return pxr::VtValue(it->second);
}
return pxr::VtValue();

View File

@ -20,7 +20,7 @@ namespace blender::render::hydra {
class WorldData : public IdData {
public:
WorldData(BlenderSceneDelegate *scene_delegate, World *world, pxr::SdfPath const &prim_id);
WorldData(BlenderSceneDelegate *scene_delegate, pxr::SdfPath const &prim_id);
void init() override;
void insert() override;