Render: support USD Hydra render delegates

Hydra is a rendering architecture part of USD, designed to abstract the
host application from the renderer. A renderer implementing a Hydra
render delegate can run in any host application supporting Hydra, which
now includes Blender.

For external renderers this means less code to be written, and improved
performance due to a using a C++ API instead of a Python API.

Add-ons need to subclass bpy.types.HydraRenderEngine. See the example in
the Python API docs for details.

An add-on for Hydra Storm will be included as well. This is USD's
rasterizing renderer, used in other applications like usdview. For users
it can provide a preview of USD file export, and for developers it
serves a reference.

There are still limitations and missing features, especially around
materials. The remaining to do items are tracked in #110765.

This feature was contributed by AMD.

Ref #110765

Co-authored-by: Georgiy Markelov <georgiy.m.markelov@gmail.com>
Co-authored-by: Vasyl-Pidhirskyi <vpidhirskyi@gmail.com>
Co-authored-by: Brian Savery <brian.savery@gmail.com>
Co-authored-by: Brecht Van Lommel <brecht@blender.org>

Pull Request: https://projects.blender.org/blender/blender/pulls/104712
This commit is contained in:
Bogdan Nagirniak 2023-08-04 15:10:48 +02:00 committed by Brecht Van Lommel
parent 61f407d427
commit 04bb5f9995
54 changed files with 5144 additions and 55 deletions

@ -367,6 +367,9 @@ option(WITH_USD "Enable Universal Scene Description (USD) Suppor
# MaterialX
option(WITH_MATERIALX "Enable MaterialX Support" ON)
# Hydra render engine
option(WITH_HYDRA "Enable Hydra render engine" ON)
# 3D format support
# Disable opencollada when we don't have precompiled libs
option(WITH_OPENCOLLADA "Enable OpenCollada Support (http://www.opencollada.org)" ON)
@ -932,6 +935,9 @@ set_and_warn_dependency(WITH_IMAGE_OPENEXR WITH_OPENVDB OFF)
set_and_warn_dependency(WITH_IMAGE_OPENEXR WITH_ALEMBIC OFF)
set_and_warn_dependency(WITH_IMAGE_OPENEXR WITH_CYCLES_OSL OFF)
# Hydra requires USD.
set_and_warn_dependency(WITH_USD WITH_HYDRA OFF)
# auto enable openimageio for cycles
if(WITH_CYCLES)
# auto enable llvm for cycles_osl

@ -58,6 +58,7 @@ set(WITH_SDL ON CACHE BOOL "" FORCE)
set(WITH_TBB ON CACHE BOOL "" FORCE)
set(WITH_USD ON CACHE BOOL "" FORCE)
set(WITH_MATERIALX ON CACHE BOOL "" FORCE)
set(WITH_HYDRA ON CACHE BOOL "" FORCE)
set(WITH_MEM_JEMALLOC ON CACHE BOOL "" FORCE)

@ -59,6 +59,7 @@ set(WITH_SDL ON CACHE BOOL "" FORCE)
set(WITH_TBB ON CACHE BOOL "" FORCE)
set(WITH_USD ON CACHE BOOL "" FORCE)
set(WITH_MATERIALX ON CACHE BOOL "" FORCE)
set(WITH_HYDRA ON CACHE BOOL "" FORCE)
set(WITH_MEM_JEMALLOC ON CACHE BOOL "" FORCE)

@ -0,0 +1,57 @@
"""
Base class for integrating USD Hydra based renderers.
USD Hydra Based Renderer
++++++++++++++++++++++++
"""
import bpy
class CustomHydraRenderEngine(bpy.types.HydraRenderEngine):
# Identifier and name in the user interface.
bl_idname = "CUSTOM_HYDRA_RENDERER"
bl_label = "Custom Hydra Renderer"
# Name of the render plugin.
bl_delegate_id = "HdCustomRendererPlugin"
# Register path to plugin.
@classmethod
def register(cls):
super().register()
import pxr
pxr.Plug.Registry().RegisterPlugins(['/path/to/plugin'])
# Render settings that will be passed to the delegate.
def get_render_settings(self, engine_type):
return {
'myBoolean': True,
'myValue': 8,
'aovToken:Depth': "depth",
}
# RenderEngine methods for update, render and draw are implemented in
# HydraRenderEngine. Optionally extra work can be done before or after
# by implementing the methods like this.
def update(self, data, depsgraph):
super().update(data, depsgraph)
# Do extra work here
def update_render_passes(self, scene, render_layer):
if render_layer.use_pass_z:
self.register_pass(scene, render_layer, 'Depth', 1, 'Z', 'VALUE')
# Registration
def register():
bpy.utils.register_class(CustomHydraRenderEngine)
def unregister():
bpy.utils.unregister_class(CustomHydraRenderEngine)
if __name__ == "__main__":
register()

@ -938,10 +938,6 @@ class PropertyGroup(StructRNA, metaclass=RNAMetaPropGroup):
__slots__ = ()
class RenderEngine(StructRNA, metaclass=RNAMeta):
__slots__ = ()
class KeyingSetInfo(StructRNA, metaclass=RNAMeta):
__slots__ = ()
@ -1256,3 +1252,71 @@ class GeometryNode(NodeInternal):
@classmethod
def poll(cls, ntree):
return ntree.bl_idname == 'GeometryNodeTree'
class RenderEngine(StructRNA, metaclass=RNAMeta):
__slots__ = ()
class HydraRenderEngine(RenderEngine):
__slots__ = ()
bl_use_shading_nodes_custom = False
bl_delegate_id = 'HdStormRendererPlugin'
def __init__(self):
self.engine_ptr = None
def __del__(self):
if hasattr(self, 'engine_ptr'):
if self.engine_ptr:
import _bpy_hydra
_bpy_hydra.engine_free(self.engine_ptr)
def get_render_settings(self, engine_type: str):
"""
Provide render settings for `HdRenderDelegate`.
"""
return {}
# Final render.
def update(self, data, depsgraph):
import _bpy_hydra
engine_type = 'PREVIEW' if self.is_preview else 'FINAL'
if not self.engine_ptr:
self.engine_ptr = _bpy_hydra.engine_create(self, engine_type, self.bl_delegate_id)
if not self.engine_ptr:
return
_bpy_hydra.engine_update(self.engine_ptr, depsgraph, None)
for key, val in self.get_render_settings('PREVIEW' if self.is_preview else 'FINAL').items():
_bpy_hydra.engine_set_render_setting(self.engine_ptr, key, val)
def render(self, depsgraph):
if not self.engine_ptr:
return
import _bpy_hydra
_bpy_hydra.engine_render(self.engine_ptr)
# Viewport render.
def view_update(self, context, depsgraph):
import _bpy_hydra
if not self.engine_ptr:
self.engine_ptr = _bpy_hydra.engine_create(self, 'VIEWPORT', self.bl_delegate_id)
if not self.engine_ptr:
return
_bpy_hydra.engine_update(self.engine_ptr, depsgraph, context)
for key, val in self.get_render_settings('VIEWPORT').items():
_bpy_hydra.engine_set_render_setting(self.engine_ptr, key, val)
def view_draw(self, context, depsgraph):
if not self.engine_ptr:
return
import _bpy_hydra
_bpy_hydra.engine_view_draw(self.engine_ptr, context)

@ -14,6 +14,17 @@ if(WIN32)
endif()
add_definitions(-DBOOST_ALL_NO_LIB)
# Precompiled Linux libs are made with GCC, and USD uses some extensions
# which lead to an incompatible ABI for Clang. Using those extensions with
# Clang as well works around the issue.
if(UNIX AND NOT APPLE)
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
if(EXISTS ${LIBDIR})
add_definitions(-DARCH_HAS_GNU_STL_EXTENSIONS)
endif()
endif()
endif()
# USD headers use deprecated TBB headers, silence warning.
add_definitions(-DTBB_SUPPRESS_DEPRECATED_MESSAGES=1)
@ -54,8 +65,13 @@ set(INC
../../editors/include
../../imbuf
../../makesrna
../../nodes
../../python/intern
../../windowmanager
../../../../intern/utfconv
../../../../intern/clog
# RNA_prototypes.h
${CMAKE_BINARY_DIR}/source/blender/makesrna
)
set(INC_SYS
@ -94,6 +110,7 @@ set(SRC
intern/usd_reader_volume.cc
intern/usd_reader_xform.cc
usd.hh
usd.h
intern/usd_asset_utils.h
@ -124,6 +141,38 @@ set(SRC
intern/usd_reader_xform.h
)
if(WITH_HYDRA)
list(APPEND SRC
hydra/camera.cc
hydra/curves.cc
hydra/hydra_scene_delegate.cc
hydra/id.cc
hydra/image.cc
hydra/instancer.cc
hydra/light.cc
hydra/material.cc
hydra/mesh.cc
hydra/object.cc
hydra/volume.cc
hydra/volume_modifier.cc
hydra/world.cc
hydra/camera.h
hydra/curves.h
hydra/hydra_scene_delegate.h
hydra/id.h
hydra/image.h
hydra/instancer.h
hydra/light.h
hydra/material.h
hydra/mesh.h
hydra/object.h
hydra/volume.h
hydra/volume_modifier.h
hydra/world.h
)
endif()
set(LIB
bf_blenkernel
PRIVATE bf::blenlib
@ -153,6 +202,9 @@ endif()
blender_add_lib(bf_usd "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
# RNA_prototypes.h
add_dependencies(bf_usd bf_rna)
if(COMMAND target_precompile_headers)
target_precompile_headers(bf_usd PRIVATE intern/usd_precomp.h)
endif()

@ -0,0 +1,283 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "camera.h"
#include "DNA_camera_types.h"
#include "DNA_object_types.h"
#include "DNA_screen_types.h"
#include "DNA_view3d_types.h"
#include "hydra/object.h"
namespace blender::io::hydra {
CameraData::CameraData(View3D *v3d, ARegion *region)
{
RegionView3D *region_data = (RegionView3D *)region->regiondata;
/* TODO: refactor use BKE_camera_params API. */
float VIEWPORT_SENSOR_SIZE = DEFAULT_SENSOR_WIDTH * 2.0f;
pxr::GfVec2i res(region->winx, region->winy);
float ratio = (float)res[0] / res[1];
transform_ = gf_matrix_from_transform(region_data->viewmat).GetInverse();
switch (region_data->persp) {
case RV3D_PERSP: {
mode_ = CAM_PERSP;
clip_range_ = pxr::GfRange1f(v3d->clip_start, v3d->clip_end);
lens_shift_ = pxr::GfVec2f(0.0, 0.0);
focal_length_ = v3d->lens;
if (ratio > 1.0) {
sensor_size_ = pxr::GfVec2f(VIEWPORT_SENSOR_SIZE, VIEWPORT_SENSOR_SIZE / ratio);
}
else {
sensor_size_ = pxr::GfVec2f(VIEWPORT_SENSOR_SIZE * ratio, VIEWPORT_SENSOR_SIZE);
}
break;
}
case RV3D_ORTHO: {
mode_ = CAM_ORTHO;
lens_shift_ = pxr::GfVec2f(0.0f, 0.0f);
float o_size = region_data->dist * VIEWPORT_SENSOR_SIZE / v3d->lens;
float o_depth = v3d->clip_end;
clip_range_ = pxr::GfRange1f(-o_depth * 0.5, o_depth * 0.5);
if (ratio > 1.0f) {
ortho_size_ = pxr::GfVec2f(o_size, o_size / ratio);
}
else {
ortho_size_ = pxr::GfVec2f(o_size * ratio, o_size);
}
break;
}
case RV3D_CAMOB: {
pxr::GfMatrix4d mat = transform_;
*this = CameraData(v3d->camera, res, pxr::GfVec4f(0, 0, 1, 1));
transform_ = mat;
/* This formula was taken from previous plugin with corresponded comment.
* See blender/intern/cycles/blender/blender_camera.cpp:blender_camera_from_view (look
* for 1.41421f). */
float zoom = 4.0 / pow((pow(2.0, 0.5) + region_data->camzoom / 50.0), 2);
/* Updating l_shift due to viewport zoom and view_camera_offset
* view_camera_offset should be multiplied by 2. */
lens_shift_ = pxr::GfVec2f((lens_shift_[0] + region_data->camdx * 2) / zoom,
(lens_shift_[1] + region_data->camdy * 2) / zoom);
if (mode_ == CAM_ORTHO) {
ortho_size_ *= zoom;
}
else {
sensor_size_ *= zoom;
}
break;
}
default:
break;
}
}
CameraData::CameraData(Object *camera_obj, pxr::GfVec2i res, pxr::GfVec4f tile)
{
Camera *camera = (Camera *)camera_obj->data;
float t_pos[2] = {tile[0], tile[1]};
float t_size[2] = {tile[2], tile[3]};
transform_ = gf_matrix_from_transform(camera_obj->object_to_world);
clip_range_ = pxr::GfRange1f(camera->clip_start, camera->clip_end);
mode_ = camera->type;
if (camera->dof.flag & CAM_DOF_ENABLED) {
float focus_distance;
if (!camera->dof.focus_object) {
focus_distance = camera->dof.focus_distance;
}
else {
pxr::GfVec3f obj_pos(camera->dof.focus_object->object_to_world[0][3],
camera->dof.focus_object->object_to_world[1][3],
camera->dof.focus_object->object_to_world[2][3]);
pxr::GfVec3f cam_pos(transform_[0][3], transform_[1][3], transform_[2][3]);
focus_distance = (obj_pos - cam_pos).GetLength();
}
dof_data_ = std::tuple(
std::max(focus_distance, 0.001f), camera->dof.aperture_fstop, camera->dof.aperture_blades);
}
float ratio = (float)res[0] / res[1];
switch (camera->sensor_fit) {
case CAMERA_SENSOR_FIT_VERT:
lens_shift_ = pxr::GfVec2f(camera->shiftx / ratio, camera->shifty);
break;
case CAMERA_SENSOR_FIT_HOR:
lens_shift_ = pxr::GfVec2f(camera->shiftx, camera->shifty * ratio);
break;
case CAMERA_SENSOR_FIT_AUTO:
if (ratio > 1.0f) {
lens_shift_ = pxr::GfVec2f(camera->shiftx, camera->shifty * ratio);
}
else {
lens_shift_ = pxr::GfVec2f(camera->shiftx / ratio, camera->shifty);
}
break;
default:
lens_shift_ = pxr::GfVec2f(camera->shiftx, camera->shifty);
break;
}
lens_shift_ = pxr::GfVec2f(
lens_shift_[0] / t_size[0] + (t_pos[0] + t_size[0] * 0.5 - 0.5) / t_size[0],
lens_shift_[1] / t_size[1] + (t_pos[1] + t_size[1] * 0.5 - 0.5) / t_size[1]);
switch (camera->type) {
case CAM_PERSP: {
focal_length_ = camera->lens;
switch (camera->sensor_fit) {
case CAMERA_SENSOR_FIT_VERT:
sensor_size_ = pxr::GfVec2f(camera->sensor_y * ratio, camera->sensor_y);
break;
case CAMERA_SENSOR_FIT_HOR:
sensor_size_ = pxr::GfVec2f(camera->sensor_x, camera->sensor_x / ratio);
break;
case CAMERA_SENSOR_FIT_AUTO:
if (ratio > 1.0f) {
sensor_size_ = pxr::GfVec2f(camera->sensor_x, camera->sensor_x / ratio);
}
else {
sensor_size_ = pxr::GfVec2f(camera->sensor_x * ratio, camera->sensor_x);
}
break;
default:
sensor_size_ = pxr::GfVec2f(camera->sensor_x, camera->sensor_y);
break;
}
sensor_size_ = pxr::GfVec2f(sensor_size_[0] * t_size[0], sensor_size_[1] * t_size[1]);
break;
}
case CAM_ORTHO: {
focal_length_ = 0.0f;
switch (camera->sensor_fit) {
case CAMERA_SENSOR_FIT_VERT:
ortho_size_ = pxr::GfVec2f(camera->ortho_scale * ratio, camera->ortho_scale);
break;
case CAMERA_SENSOR_FIT_HOR:
ortho_size_ = pxr::GfVec2f(camera->ortho_scale, camera->ortho_scale / ratio);
break;
case CAMERA_SENSOR_FIT_AUTO:
if (ratio > 1.0f) {
ortho_size_ = pxr::GfVec2f(camera->ortho_scale, camera->ortho_scale / ratio);
}
else {
ortho_size_ = pxr::GfVec2f(camera->ortho_scale * ratio, camera->ortho_scale);
}
break;
default:
ortho_size_ = pxr::GfVec2f(camera->ortho_scale, camera->ortho_scale);
break;
}
ortho_size_ = pxr::GfVec2f(ortho_size_[0] * t_size[0], ortho_size_[1] * t_size[1]);
break;
}
case CAM_PANO: {
/* TODO: Recheck parameters for PANO camera */
focal_length_ = camera->lens;
switch (camera->sensor_fit) {
case CAMERA_SENSOR_FIT_VERT:
sensor_size_ = pxr::GfVec2f(camera->sensor_y * ratio, camera->sensor_y);
break;
case CAMERA_SENSOR_FIT_HOR:
sensor_size_ = pxr::GfVec2f(camera->sensor_x, camera->sensor_x / ratio);
break;
case CAMERA_SENSOR_FIT_AUTO:
if (ratio > 1.0f) {
sensor_size_ = pxr::GfVec2f(camera->sensor_x, camera->sensor_x / ratio);
}
else {
sensor_size_ = pxr::GfVec2f(camera->sensor_x * ratio, camera->sensor_x);
}
break;
default:
sensor_size_ = pxr::GfVec2f(camera->sensor_x, camera->sensor_y);
break;
}
sensor_size_ = pxr::GfVec2f(sensor_size_[0] * t_size[0], sensor_size_[1] * t_size[1]);
break;
}
default: {
focal_length_ = camera->lens;
sensor_size_ = pxr::GfVec2f(camera->sensor_y * ratio, camera->sensor_y);
break;
}
}
}
pxr::GfCamera CameraData::gf_camera()
{
return gf_camera(pxr::GfVec4f(0, 0, 1, 1));
}
pxr::GfCamera CameraData::gf_camera(pxr::GfVec4f tile)
{
float t_pos[2] = {tile[0], tile[1]}, t_size[2] = {tile[2], tile[3]};
pxr::GfCamera gf_camera = pxr::GfCamera();
gf_camera.SetClippingRange(clip_range_);
float l_shift[2] = {(lens_shift_[0] + t_pos[0] + t_size[0] * 0.5f - 0.5f) / t_size[0],
(lens_shift_[1] + t_pos[1] + t_size[1] * 0.5f - 0.5f) / t_size[1]};
switch (mode_) {
case CAM_PERSP:
case CAM_PANO: {
/* TODO: store panoramic camera settings */
gf_camera.SetProjection(pxr::GfCamera::Projection::Perspective);
gf_camera.SetFocalLength(focal_length_);
float s_size[2] = {sensor_size_[0] * t_size[0], sensor_size_[1] * t_size[1]};
gf_camera.SetHorizontalAperture(s_size[0]);
gf_camera.SetVerticalAperture(s_size[1]);
gf_camera.SetHorizontalApertureOffset(l_shift[0] * s_size[0]);
gf_camera.SetVerticalApertureOffset(l_shift[1] * s_size[1]);
break;
}
case CAM_ORTHO: {
gf_camera.SetProjection(pxr::GfCamera::Projection::Orthographic);
/* Use tenths of a world unit accorging to USD docs
* https://graphics.pixar.com/usd/docs/api/class_gf_camera.html */
float o_size[2] = {ortho_size_[0] * t_size[0] * 10, ortho_size_[1] * t_size[1] * 10};
gf_camera.SetHorizontalAperture(o_size[0]);
gf_camera.SetVerticalAperture(o_size[1]);
gf_camera.SetHorizontalApertureOffset(l_shift[0] * o_size[0]);
gf_camera.SetVerticalApertureOffset(l_shift[1] * o_size[1]);
break;
}
default:
break;
}
gf_camera.SetTransform(transform_);
return gf_camera;
}
} // namespace blender::io::hydra

@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include <tuple>
#include <pxr/base/gf/camera.h>
#include <pxr/base/gf/vec2f.h>
struct ARegion;
struct Object;
struct View3D;
namespace blender::io::hydra {
class CameraData {
private:
int mode_;
pxr::GfRange1f clip_range_;
float focal_length_;
pxr::GfVec2f sensor_size_;
pxr::GfMatrix4d transform_;
pxr::GfVec2f lens_shift_;
pxr::GfVec2f ortho_size_;
std::tuple<float, float, int> dof_data_;
public:
CameraData(View3D *v3d, ARegion *region);
CameraData(Object *camera_obj, pxr::GfVec2i res, pxr::GfVec4f tile);
pxr::GfCamera gf_camera();
pxr::GfCamera gf_camera(pxr::GfVec4f tile);
};
} // namespace blender::io::hydra

@ -0,0 +1,183 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include "curves.h"
#include <pxr/base/gf/vec2f.h>
#include <pxr/imaging/hd/tokens.h>
#include "BKE_customdata.h"
#include "BKE_material.h"
#include "hydra_scene_delegate.h"
namespace blender::io::hydra {
CurvesData::CurvesData(HydraSceneDelegate *scene_delegate,
Object *object,
pxr::SdfPath const &prim_id)
: ObjectData(scene_delegate, object, prim_id)
{
}
void CurvesData::init()
{
ID_LOGN(1, "");
Object *object = (Object *)id;
write_curves((Curves *)object->data);
write_transform();
write_materials();
}
void CurvesData::insert()
{
ID_LOGN(1, "");
scene_delegate_->GetRenderIndex().InsertRprim(
pxr::HdPrimTypeTokens->basisCurves, scene_delegate_, prim_id);
}
void CurvesData::remove()
{
ID_LOG(1, "");
scene_delegate_->GetRenderIndex().RemoveRprim(prim_id);
}
void CurvesData::update()
{
Object *object = (Object *)id;
pxr::HdDirtyBits bits = pxr::HdChangeTracker::Clean;
if ((id->recalc & ID_RECALC_GEOMETRY) || (((ID *)object->data)->recalc & ID_RECALC_GEOMETRY)) {
init();
bits = pxr::HdChangeTracker::AllDirty;
}
if (id->recalc & ID_RECALC_SHADING) {
write_materials();
bits |= pxr::HdChangeTracker::DirtyMaterialId | pxr::HdChangeTracker::DirtyDoubleSided;
}
if (id->recalc & ID_RECALC_TRANSFORM) {
write_transform();
bits |= pxr::HdChangeTracker::DirtyTransform;
}
if (bits == pxr::HdChangeTracker::Clean) {
return;
}
scene_delegate_->GetRenderIndex().GetChangeTracker().MarkRprimDirty(prim_id, bits);
ID_LOGN(1, "");
}
pxr::VtValue CurvesData::get_data(pxr::TfToken const &key) const
{
if (key == pxr::HdTokens->points) {
return pxr::VtValue(vertices_);
}
else if (key == pxr::HdPrimvarRoleTokens->textureCoordinate) {
return pxr::VtValue(uvs_);
}
else if (key == pxr::HdTokens->widths) {
return pxr::VtValue(widths_);
}
return pxr::VtValue();
}
pxr::SdfPath CurvesData::material_id() const
{
if (!mat_data_) {
return pxr::SdfPath();
}
return mat_data_->prim_id;
}
void CurvesData::available_materials(Set<pxr::SdfPath> &paths) const
{
if (mat_data_ && !mat_data_->prim_id.IsEmpty()) {
paths.add(mat_data_->prim_id);
}
}
pxr::HdBasisCurvesTopology CurvesData::topology() const
{
return pxr::HdBasisCurvesTopology(pxr::HdTokens->linear,
pxr::TfToken(),
pxr::HdTokens->nonperiodic,
curve_vertex_counts_,
pxr::VtIntArray());
}
pxr::HdPrimvarDescriptorVector CurvesData::primvar_descriptors(
pxr::HdInterpolation interpolation) const
{
pxr::HdPrimvarDescriptorVector primvars;
if (interpolation == pxr::HdInterpolationVertex) {
if (!vertices_.empty()) {
primvars.emplace_back(pxr::HdTokens->points, interpolation, pxr::HdPrimvarRoleTokens->point);
}
if (!widths_.empty()) {
primvars.emplace_back(pxr::HdTokens->widths, interpolation, pxr::HdPrimvarRoleTokens->none);
}
}
else if (interpolation == pxr::HdInterpolationConstant) {
if (!uvs_.empty()) {
primvars.emplace_back(pxr::HdPrimvarRoleTokens->textureCoordinate,
interpolation,
pxr::HdPrimvarRoleTokens->textureCoordinate);
}
}
return primvars;
}
void CurvesData::write_materials()
{
Object *object = (Object *)id;
Material *mat = nullptr;
/* TODO: Using only first material. Add support for multimaterial. */
if (BKE_object_material_count_eval(object) > 0) {
mat = BKE_object_material_get_eval(object, 0);
}
mat_data_ = get_or_create_material(mat);
}
void CurvesData::write_curves(Curves *curves)
{
curve_vertex_counts_.clear();
widths_.clear();
vertices_.clear();
const float *radii = (const float *)CustomData_get_layer_named(
&curves->geometry.point_data, CD_PROP_FLOAT, "radius");
const float(*positions)[3] = (const float(*)[3])CustomData_get_layer_named(
&curves->geometry.point_data, CD_PROP_FLOAT3, "position");
vertices_.reserve(curves->geometry.curve_num);
for (int i = 0; i < curves->geometry.curve_num; i++) {
int first_point_index = *(curves->geometry.curve_offsets + i);
int num_points = *(curves->geometry.curve_offsets + i + 1) - first_point_index;
curve_vertex_counts_.push_back(num_points);
/* Set radius similar to Cycles if isn't set */
for (int j = 0; j < num_points; j++) {
int ind = first_point_index + j;
widths_.push_back(radii ? radii[ind] * 2 : 0.01f);
vertices_.push_back(pxr::GfVec3f(positions[ind][0], positions[ind][1], positions[ind][2]));
}
}
write_uv_maps(curves);
}
void CurvesData::write_uv_maps(Curves *curves)
{
uvs_.clear();
const float(*uvs)[2] = (const float(*)[2])CustomData_get_layer_named(
&curves->geometry.curve_data, CD_PROP_FLOAT2, "surface_uv_coordinate");
if (uvs) {
for (int i = 0; i < curves->geometry.curve_num; i++) {
uvs_.push_back(pxr::GfVec2f(uvs[i][0], uvs[i][1]));
}
}
}
} // namespace blender::io::hydra

@ -0,0 +1,52 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include <pxr/base/vt/array.h>
#include <pxr/imaging/hd/sceneDelegate.h>
#include "DNA_curves_types.h"
#include "BLI_set.hh"
#include "BKE_duplilist.h"
#include "material.h"
#include "object.h"
namespace blender::io::hydra {
class CurvesData : public ObjectData {
private:
pxr::VtIntArray curve_vertex_counts_;
pxr::VtVec3fArray vertices_;
pxr::VtVec2fArray uvs_;
pxr::VtFloatArray widths_;
MaterialData *mat_data_ = nullptr;
public:
CurvesData(HydraSceneDelegate *scene_delegate, Object *object, pxr::SdfPath const &prim_id);
void init() override;
void insert() override;
void remove() override;
void update() override;
pxr::VtValue get_data(pxr::TfToken const &key) const override;
pxr::SdfPath material_id() const override;
void available_materials(Set<pxr::SdfPath> &paths) const override;
pxr::HdBasisCurvesTopology topology() const;
pxr::HdPrimvarDescriptorVector primvar_descriptors(pxr::HdInterpolation interpolation) const;
protected:
void write_materials() override;
private:
void write_curves(Curves *curves);
void write_uv_maps(Curves *curves);
};
} // namespace blender::io::hydra

@ -0,0 +1,543 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include "hydra_scene_delegate.h"
#include <bitset>
#include "DNA_scene_types.h"
#include "BLI_set.hh"
#include "DEG_depsgraph_query.h"
namespace blender::io::hydra {
CLG_LOGREF_DECLARE_GLOBAL(LOG_HYDRA_SCENE, "hydra.scene");
bool HydraSceneDelegate::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 &&
studiolight_rotation == other.studiolight_rotation &&
studiolight_intensity == other.studiolight_intensity;
}
return ret;
}
HydraSceneDelegate::HydraSceneDelegate(pxr::HdRenderIndex *parent_index,
pxr::SdfPath const &delegate_id)
: HdSceneDelegate(parent_index, delegate_id)
{
instancer_data_ = std::make_unique<InstancerData>(this, instancer_prim_id());
}
pxr::HdMeshTopology HydraSceneDelegate::GetMeshTopology(pxr::SdfPath const &id)
{
CLOG_INFO(LOG_HYDRA_SCENE, 3, "%s", id.GetText());
MeshData *m_data = mesh_data(id);
return m_data->topology(id);
}
pxr::HdBasisCurvesTopology HydraSceneDelegate::GetBasisCurvesTopology(pxr::SdfPath const &id)
{
CLOG_INFO(LOG_HYDRA_SCENE, 3, "%s", id.GetText());
CurvesData *c_data = curves_data(id);
return c_data->topology();
};
pxr::GfMatrix4d HydraSceneDelegate::GetTransform(pxr::SdfPath const &id)
{
CLOG_INFO(LOG_HYDRA_SCENE, 3, "%s", id.GetText());
InstancerData *i_data = instancer_data(id, true);
if (i_data) {
return i_data->transform(id);
}
ObjectData *obj_data = object_data(id);
if (obj_data) {
return obj_data->transform;
}
return pxr::GfMatrix4d();
}
pxr::VtValue HydraSceneDelegate::Get(pxr::SdfPath const &id, pxr::TfToken const &key)
{
CLOG_INFO(LOG_HYDRA_SCENE, 3, "%s, %s", id.GetText(), key.GetText());
ObjectData *obj_data = object_data(id);
if (obj_data) {
return obj_data->get_data(id, key);
}
MaterialData *mat_data = material_data(id);
if (mat_data) {
return mat_data->get_data(key);
}
InstancerData *i_data = instancer_data(id);
if (i_data) {
return i_data->get_data(key);
}
return pxr::VtValue();
}
pxr::VtValue HydraSceneDelegate::GetLightParamValue(pxr::SdfPath const &id,
pxr::TfToken const &key)
{
CLOG_INFO(LOG_HYDRA_SCENE, 3, "%s, %s", id.GetText(), key.GetText());
LightData *l_data = light_data(id);
if (l_data) {
return l_data->get_data(key);
}
return pxr::VtValue();
}
pxr::HdPrimvarDescriptorVector HydraSceneDelegate::GetPrimvarDescriptors(
pxr::SdfPath const &id, pxr::HdInterpolation interpolation)
{
CLOG_INFO(LOG_HYDRA_SCENE, 3, "%s, %d", id.GetText(), interpolation);
MeshData *m_data = mesh_data(id);
if (m_data) {
return m_data->primvar_descriptors(interpolation);
}
CurvesData *c_data = curves_data(id);
if (c_data) {
return c_data->primvar_descriptors(interpolation);
}
InstancerData *i_data = instancer_data(id);
if (i_data) {
return i_data->primvar_descriptors(interpolation);
}
return pxr::HdPrimvarDescriptorVector();
}
pxr::SdfPath HydraSceneDelegate::GetMaterialId(pxr::SdfPath const &rprim_id)
{
CLOG_INFO(LOG_HYDRA_SCENE, 3, "%s", rprim_id.GetText());
ObjectData *obj_data = object_data(rprim_id);
if (obj_data) {
return obj_data->material_id(rprim_id);
}
return pxr::SdfPath();
}
pxr::VtValue HydraSceneDelegate::GetMaterialResource(pxr::SdfPath const &id)
{
CLOG_INFO(LOG_HYDRA_SCENE, 3, "%s", id.GetText());
MaterialData *mat_data = material_data(id);
if (mat_data) {
return mat_data->get_material_resource();
}
return pxr::VtValue();
}
bool HydraSceneDelegate::GetVisible(pxr::SdfPath const &id)
{
CLOG_INFO(LOG_HYDRA_SCENE, 3, "%s", id.GetText());
if (id == world_prim_id()) {
return true;
}
InstancerData *i_data = instancer_data(id, true);
if (i_data) {
return true;
}
return object_data(id)->visible;
}
bool HydraSceneDelegate::GetDoubleSided(pxr::SdfPath const &id)
{
CLOG_INFO(LOG_HYDRA_SCENE, 3, "%s", id.GetText());
return mesh_data(id)->double_sided(id);
}
pxr::HdCullStyle HydraSceneDelegate::GetCullStyle(pxr::SdfPath const &id)
{
CLOG_INFO(LOG_HYDRA_SCENE, 3, "%s", id.GetText());
return mesh_data(id)->cull_style(id);
}
pxr::SdfPath HydraSceneDelegate::GetInstancerId(pxr::SdfPath const &prim_id)
{
CLOG_INFO(LOG_HYDRA_SCENE, 3, "%s", prim_id.GetText());
InstancerData *i_data = instancer_data(prim_id, true);
if (i_data && mesh_data(prim_id)) {
return i_data->prim_id;
}
return pxr::SdfPath();
}
pxr::SdfPathVector HydraSceneDelegate::GetInstancerPrototypes(pxr::SdfPath const &instancer_id)
{
CLOG_INFO(LOG_HYDRA_SCENE, 3, "%s", instancer_id.GetText());
InstancerData *i_data = instancer_data(instancer_id);
return i_data->prototypes();
}
pxr::VtIntArray HydraSceneDelegate::GetInstanceIndices(pxr::SdfPath const &instancer_id,
pxr::SdfPath const &prototype_id)
{
CLOG_INFO(LOG_HYDRA_SCENE, 3, "%s, %s", instancer_id.GetText(), prototype_id.GetText());
InstancerData *i_data = instancer_data(instancer_id);
return i_data->indices(prototype_id);
}
pxr::GfMatrix4d HydraSceneDelegate::GetInstancerTransform(pxr::SdfPath const &instancer_id)
{
CLOG_INFO(LOG_HYDRA_SCENE, 3, "%s", instancer_id.GetText());
InstancerData *i_data = instancer_data(instancer_id);
return i_data->transform(instancer_id);
}
pxr::HdVolumeFieldDescriptorVector HydraSceneDelegate::GetVolumeFieldDescriptors(
pxr::SdfPath const &volume_id)
{
CLOG_INFO(LOG_HYDRA_SCENE, 3, "%s", volume_id.GetText());
VolumeData *v_data = volume_data(volume_id);
return v_data->field_descriptors();
}
void HydraSceneDelegate::populate(Depsgraph *deps, View3D *v3d)
{
bool is_populated = depsgraph != nullptr;
depsgraph = deps;
bmain = DEG_get_bmain(deps);
scene = DEG_get_input_scene(depsgraph);
view3d = v3d;
if (is_populated) {
check_updates();
}
else {
set_light_shading_settings();
set_world_shading_settings();
update_collection();
update_world();
}
}
void HydraSceneDelegate::clear()
{
for (auto &obj_data : objects_.values()) {
obj_data->remove();
}
objects_.clear();
instancer_data_->remove();
for (auto &mat_data : materials_.values()) {
mat_data->remove();
}
materials_.clear();
depsgraph = nullptr;
bmain = nullptr;
scene = nullptr;
view3d = nullptr;
}
pxr::SdfPath HydraSceneDelegate::prim_id(ID *id, const char *prefix) const
{
/* Making id of object in form like <prefix>_<pointer in 16 hex digits format> */
char name[32];
snprintf(name, sizeof(name), "%s_%p", prefix, id);
return GetDelegateID().AppendElementString(name);
}
pxr::SdfPath HydraSceneDelegate::object_prim_id(Object *object) const
{
return prim_id((ID *)object, "O");
}
pxr::SdfPath HydraSceneDelegate::material_prim_id(Material *mat) const
{
return prim_id((ID *)mat, "M");
}
pxr::SdfPath HydraSceneDelegate::instancer_prim_id() const
{
return GetDelegateID().AppendElementString("Instancer");
}
pxr::SdfPath HydraSceneDelegate::world_prim_id() const
{
return GetDelegateID().AppendElementString("World");
}
ObjectData *HydraSceneDelegate::object_data(pxr::SdfPath const &id) const
{
if (id == world_prim_id()) {
return world_data_.get();
}
auto name = id.GetName();
pxr::SdfPath p_id = (STRPREFIX(name.c_str(), "SM_") || STRPREFIX(name.c_str(), "VF_")) ?
id.GetParentPath() :
id;
auto obj_data = objects_.lookup_ptr(p_id);
if (obj_data) {
return obj_data->get();
}
InstancerData *i_data = instancer_data(p_id, true);
if (i_data) {
return i_data->object_data(id);
}
return nullptr;
}
MeshData *HydraSceneDelegate::mesh_data(pxr::SdfPath const &id) const
{
return dynamic_cast<MeshData *>(object_data(id));
}
CurvesData *HydraSceneDelegate::curves_data(pxr::SdfPath const &id) const
{
return dynamic_cast<CurvesData *>(object_data(id));
}
VolumeData *HydraSceneDelegate::volume_data(pxr::SdfPath const &id) const
{
return dynamic_cast<VolumeData *>(object_data(id));
}
LightData *HydraSceneDelegate::light_data(pxr::SdfPath const &id) const
{
return dynamic_cast<LightData *>(object_data(id));
}
MaterialData *HydraSceneDelegate::material_data(pxr::SdfPath const &id) const
{
auto mat_data = materials_.lookup_ptr(id);
if (!mat_data) {
return nullptr;
}
return mat_data->get();
}
InstancerData *HydraSceneDelegate::instancer_data(pxr::SdfPath const &id, bool child_id) const
{
pxr::SdfPath p_id;
if (child_id) {
/* Getting instancer path id from child Mesh instance (consist with 3 path elements) and
* Light instance (consist with 4 path elements) */
int n = id.GetPathElementCount();
if (n == 3) {
p_id = id.GetParentPath();
}
else if (n == 4) {
p_id = id.GetParentPath().GetParentPath();
}
}
else {
p_id = id;
}
if (instancer_data_ && p_id == instancer_data_->prim_id) {
return instancer_data_.get();
}
return nullptr;
}
void HydraSceneDelegate::update_world()
{
if (!world_data_) {
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 (!shading_settings.use_scene_world || (shading_settings.use_scene_world && scene->world)) {
world_data_->update();
}
else {
world_data_->remove();
world_data_ = nullptr;
}
}
}
void HydraSceneDelegate::check_updates()
{
bool do_update_collection = false;
bool do_update_world = false;
if (set_world_shading_settings()) {
do_update_world = true;
}
if (set_light_shading_settings()) {
do_update_collection = true;
}
DEGIDIterData data = {0};
data.graph = depsgraph;
data.only_updated = true;
ITER_BEGIN (DEG_iterator_ids_begin, DEG_iterator_ids_next, DEG_iterator_ids_end, &data, ID *, id)
{
CLOG_INFO(LOG_HYDRA_SCENE,
0,
"Update: %s [%s]",
id->name,
std::bitset<32>(id->recalc).to_string().c_str());
switch (GS(id->name)) {
case ID_OB: {
do_update_collection = true;
} break;
case ID_MA: {
MaterialData *mat_data = material_data(material_prim_id((Material *)id));
if (mat_data) {
mat_data->update();
}
} break;
case ID_WO: {
if (shading_settings.use_scene_world && id->recalc & ID_RECALC_SHADING) {
do_update_world = true;
}
} break;
case ID_SCE: {
if ((id->recalc & ID_RECALC_COPY_ON_WRITE && !(id->recalc & ID_RECALC_SELECT)) ||
id->recalc & (ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_BASE_FLAGS))
{
do_update_collection = true;
}
if (id->recalc & ID_RECALC_AUDIO_VOLUME &&
((scene->world && !world_data_) || (!scene->world && world_data_)))
{
do_update_world = true;
}
} break;
default:
break;
}
}
ITER_END;
if (do_update_world) {
update_world();
}
if (do_update_collection) {
update_collection();
}
}
void HydraSceneDelegate::update_collection()
{
Set<std::string> available_objects;
DEGObjectIterSettings settings = {0};
settings.depsgraph = depsgraph;
settings.flags = DEG_OBJECT_ITER_FOR_RENDER_ENGINE_FLAGS;
DEGObjectIterData data = {0};
data.settings = &settings;
data.graph = settings.depsgraph;
data.flag = settings.flags;
instancer_data_->pre_update();
ITER_BEGIN (DEG_iterator_objects_begin,
DEG_iterator_objects_next,
DEG_iterator_objects_end,
&data,
Object *,
object)
{
if (data.dupli_object_current) {
DupliObject *dupli = data.dupli_object_current;
if (!ObjectData::is_supported(dupli->ob) ||
!ObjectData::is_visible(this, data.dupli_parent, OB_VISIBLE_INSTANCES) ||
(!shading_settings.use_scene_lights && object->type == OB_LAMP))
{
continue;
}
instancer_data_->update_instance(dupli);
continue;
}
if (!ObjectData::is_supported(object) || !ObjectData::is_visible(this, object) ||
(!shading_settings.use_scene_lights && object->type == OB_LAMP))
{
continue;
}
available_objects.add(object_prim_id(object).GetName());
pxr::SdfPath id = object_prim_id(object);
ObjectData *obj_data = object_data(id);
if (obj_data) {
obj_data->update();
}
else {
obj_data = objects_.lookup_or_add(id, ObjectData::create(this, object, id)).get();
obj_data->insert();
}
}
ITER_END;
instancer_data_->post_update();
/* Remove unused objects */
objects_.remove_if([&](auto item) {
bool ret = !available_objects.contains(item.key.GetName());
if (ret) {
item.value->remove();
}
return ret;
});
/* Remove unused materials */
Set<pxr::SdfPath> available_materials;
for (auto &val : objects_.values()) {
MeshData *m_data = dynamic_cast<MeshData *>(val.get());
if (m_data) {
m_data->available_materials(available_materials);
}
CurvesData *c_data = dynamic_cast<CurvesData *>(val.get());
if (c_data) {
c_data->available_materials(available_materials);
}
VolumeData *v_data = dynamic_cast<VolumeData *>(val.get());
if (v_data) {
v_data->available_materials(available_materials);
}
}
instancer_data_->available_materials(available_materials);
materials_.remove_if([&](auto item) {
bool ret = !available_materials.contains(item.key);
if (ret) {
item.value->remove();
}
return ret;
});
}
bool HydraSceneDelegate::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 HydraSceneDelegate::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::io::hydra

@ -0,0 +1,112 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include <pxr/base/gf/vec2f.h>
#include <pxr/imaging/hd/sceneDelegate.h>
#include "BLI_map.hh"
#include "DEG_depsgraph.h"
#include "CLG_log.h"
#include "curves.h"
#include "instancer.h"
#include "light.h"
#include "mesh.h"
#include "object.h"
#include "volume.h"
#include "volume_modifier.h"
#include "world.h"
struct Depsgraph;
struct Main;
struct Scene;
struct View3D;
namespace blender::io::hydra {
extern struct CLG_LogRef *LOG_HYDRA_SCENE;
class Engine;
class HydraSceneDelegate : public pxr::HdSceneDelegate {
friend ObjectData; /* has access to materials */
friend MaterialData; /* has access to objects and instancers */
public:
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);
};
Depsgraph *depsgraph = nullptr;
View3D *view3d = nullptr;
Main *bmain = nullptr;
Scene *scene = nullptr;
ShadingSettings shading_settings;
private:
ObjectDataMap objects_;
MaterialDataMap materials_;
std::unique_ptr<InstancerData> instancer_data_;
std::unique_ptr<WorldData> world_data_;
public:
HydraSceneDelegate(pxr::HdRenderIndex *parent_index, pxr::SdfPath const &delegate_id);
~HydraSceneDelegate() override = default;
/* Delegate methods */
pxr::HdMeshTopology GetMeshTopology(pxr::SdfPath const &id) override;
pxr::HdBasisCurvesTopology GetBasisCurvesTopology(pxr::SdfPath const &id) override;
pxr::GfMatrix4d GetTransform(pxr::SdfPath const &id) override;
pxr::VtValue Get(pxr::SdfPath const &id, pxr::TfToken const &key) override;
pxr::VtValue GetLightParamValue(pxr::SdfPath const &id, pxr::TfToken const &key) override;
pxr::HdPrimvarDescriptorVector GetPrimvarDescriptors(
pxr::SdfPath const &id, pxr::HdInterpolation interpolation) override;
pxr::SdfPath GetMaterialId(pxr::SdfPath const &rprim_id) override;
pxr::VtValue GetMaterialResource(pxr::SdfPath const &material_id) override;
bool GetVisible(pxr::SdfPath const &id) override;
bool GetDoubleSided(pxr::SdfPath const &id) override;
pxr::HdCullStyle GetCullStyle(pxr::SdfPath const &id) override;
pxr::SdfPath GetInstancerId(pxr::SdfPath const &prim_id) override;
pxr::SdfPathVector GetInstancerPrototypes(pxr::SdfPath const &instancer_id) override;
pxr::VtIntArray GetInstanceIndices(pxr::SdfPath const &instancer_id,
pxr::SdfPath const &prototype_id) override;
pxr::GfMatrix4d GetInstancerTransform(pxr::SdfPath const &instancer_id) override;
pxr::HdVolumeFieldDescriptorVector GetVolumeFieldDescriptors(
pxr::SdfPath const &volume_id) override;
void populate(Depsgraph *depsgraph, View3D *v3d);
void clear();
private:
pxr::SdfPath prim_id(ID *id, const char *prefix) const;
pxr::SdfPath object_prim_id(Object *object) const;
pxr::SdfPath material_prim_id(Material *mat) const;
pxr::SdfPath instancer_prim_id() const;
pxr::SdfPath world_prim_id() const;
ObjectData *object_data(pxr::SdfPath const &id) const;
MeshData *mesh_data(pxr::SdfPath const &id) const;
CurvesData *curves_data(pxr::SdfPath const &id) const;
VolumeData *volume_data(pxr::SdfPath const &id) const;
LightData *light_data(pxr::SdfPath const &id) const;
MaterialData *material_data(pxr::SdfPath const &id) const;
InstancerData *instancer_data(pxr::SdfPath const &id, bool child_id = false) const;
void update_world();
void check_updates();
void update_collection();
bool set_light_shading_settings();
bool set_world_shading_settings();
};
} // namespace blender::io::hydra

@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include "id.h"
#include "BKE_lib_id.h"
namespace blender::io::hydra {
IdData::IdData(HydraSceneDelegate *scene_delegate, ID *id, pxr::SdfPath const &prim_id)
: id(id), prim_id(prim_id), scene_delegate_(scene_delegate)
{
}
} // namespace blender::io::hydra

@ -0,0 +1,63 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include <pxr/base/tf/token.h>
#include <pxr/base/vt/value.h>
#include <pxr/usd/sdf/path.h>
#include "DNA_ID.h"
#include "BLI_hash.hh"
template<> struct blender::DefaultHash<pxr::SdfPath> {
uint64_t operator()(const pxr::SdfPath &value) const
{
return (uint64_t)value.GetHash();
}
};
template<> struct blender::DefaultHash<pxr::TfToken> {
uint64_t operator()(const pxr::TfToken &value) const
{
return (uint64_t)value.Hash();
}
};
namespace blender::io::hydra {
class HydraSceneDelegate;
class IdData {
public:
ID *id;
pxr::SdfPath prim_id;
protected:
HydraSceneDelegate *scene_delegate_;
public:
IdData(HydraSceneDelegate *scene_delegate, ID *id, pxr::SdfPath const &prim_id);
virtual ~IdData() = default;
virtual void init() = 0;
virtual void insert() = 0;
virtual void remove() = 0;
virtual void update() = 0;
virtual pxr::VtValue get_data(pxr::TfToken const &key) const = 0;
};
#define ID_LOG(level, msg, ...) \
CLOG_INFO(LOG_HYDRA_SCENE, level, "%s: " msg, prim_id.GetText(), ##__VA_ARGS__);
#define ID_LOGN(level, msg, ...) \
CLOG_INFO(LOG_HYDRA_SCENE, \
level, \
"%s (%s): " msg, \
prim_id.GetText(), \
id ? id->name : "", \
##__VA_ARGS__);
} // namespace blender::io::hydra

@ -0,0 +1,123 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include "image.h"
#include <pxr/imaging/hio/imageRegistry.h>
#include "BLI_fileops.h"
#include "BLI_path_util.h"
#include "BKE_appdir.h"
#include "BKE_image.h"
#include "BKE_image_format.h"
#include "BKE_image_save.h"
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
#include "hydra_scene_delegate.h"
namespace blender::io::hydra {
static std::string get_cache_file(const std::string &file_name, bool mkdir = true)
{
char dir_path[FILE_MAX];
BLI_path_join(dir_path, sizeof(dir_path), BKE_tempdir_session(), "hydra", "image_cache");
if (mkdir) {
BLI_dir_create_recursive(dir_path);
}
char file_path[FILE_MAX];
BLI_path_join(file_path, sizeof(file_path), dir_path, file_name.c_str());
return file_path;
}
static std::string cache_image_file(
Main *bmain, Scene *scene, Image *image, ImageUser *iuser, bool check_exist)
{
std::string file_path;
ImageSaveOptions opts;
if (BKE_image_save_options_init(&opts, bmain, scene, image, iuser, false, false)) {
char file_name[32];
const char *r_ext = BLI_path_extension_or_end(image->id.name);
if (!pxr::HioImageRegistry::GetInstance().IsSupportedImageFile(image->id.name)) {
BKE_image_path_ext_from_imformat(&scene->r.im_format, &r_ext);
opts.im_format = scene->r.im_format;
}
snprintf(file_name, sizeof(file_name), "img_%p%s", image, r_ext);
file_path = get_cache_file(file_name);
if (check_exist && BLI_exists(file_path.c_str())) {
return file_path;
}
opts.save_copy = true;
STRNCPY(opts.filepath, file_path.c_str());
if (BKE_image_save(nullptr, bmain, image, iuser, &opts)) {
CLOG_INFO(LOG_HYDRA_SCENE, 1, "%s -> %s", image->id.name, file_path.c_str());
}
else {
CLOG_ERROR(LOG_HYDRA_SCENE, "Can't save %s", file_path.c_str());
file_path = "";
}
}
BKE_image_save_options_free(&opts);
return file_path;
}
std::string cache_or_get_image_file(Main *bmain, Scene *scene, Image *image, ImageUser *iuser)
{
std::string file_path;
if (image->source == IMA_SRC_GENERATED) {
file_path = cache_image_file(bmain, scene, image, iuser, false);
}
else if (BKE_image_has_packedfile(image)) {
file_path = cache_image_file(bmain, scene, image, iuser, true);
}
else {
char str[FILE_MAX];
BKE_image_user_file_path_ex(bmain, iuser, image, str, false, true);
file_path = str;
if (!pxr::HioImageRegistry::GetInstance().IsSupportedImageFile(file_path)) {
file_path = cache_image_file(bmain, scene, image, iuser, true);
}
}
CLOG_INFO(LOG_HYDRA_SCENE, 1, "%s -> %s", image->id.name, file_path.c_str());
return file_path;
}
std::string cache_image_color(float color[4])
{
char name[128];
snprintf(name,
sizeof(name),
"color_%02d%02d%02d.hdr",
int(color[0] * 255),
int(color[1] * 255),
int(color[2] * 255));
std::string file_path = get_cache_file(name);
if (BLI_exists(file_path.c_str())) {
return file_path;
}
ImBuf *ibuf = IMB_allocImBuf(4, 4, 32, IB_rectfloat);
IMB_rectfill(ibuf, color);
ibuf->ftype = IMB_FTYPE_RADHDR;
if (IMB_saveiff(ibuf, file_path.c_str(), IB_rectfloat)) {
CLOG_INFO(LOG_HYDRA_SCENE, 1, "%s", file_path.c_str());
}
else {
CLOG_ERROR(LOG_HYDRA_SCENE, "Can't save %s", file_path.c_str());
file_path = "";
}
IMB_freeImBuf(ibuf);
return file_path;
}
} // namespace blender::io::hydra

@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include <string>
struct Main;
struct Scene;
struct Image;
struct ImageUser;
namespace blender::io::hydra {
std::string cache_or_get_image_file(Main *bmain, Scene *Scene, Image *image, ImageUser *iuser);
std::string cache_image_color(float color[4]);
} // namespace blender::io::hydra

@ -0,0 +1,286 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include "instancer.h"
#include <pxr/base/gf/vec2f.h>
#include <pxr/imaging/hd/light.h>
#include "DEG_depsgraph_query.h"
#include "hydra_scene_delegate.h"
namespace blender::io::hydra {
InstancerData::InstancerData(HydraSceneDelegate *scene_delegate, pxr::SdfPath const &prim_id)
: IdData(scene_delegate, nullptr, prim_id)
{
}
void InstancerData::init() {}
void InstancerData::insert() {}
void InstancerData::remove()
{
CLOG_INFO(LOG_HYDRA_SCENE, 1, "%s", prim_id.GetText());
for (auto &m_inst : mesh_instances_.values()) {
m_inst.data->remove();
}
if (!mesh_instances_.is_empty()) {
scene_delegate_->GetRenderIndex().RemoveInstancer(prim_id);
}
mesh_instances_.clear();
for (auto &l_inst : nonmesh_instances_.values()) {
l_inst.transforms.clear();
update_nonmesh_instance(l_inst);
}
nonmesh_instances_.clear();
}
void InstancerData::update() {}
pxr::VtValue InstancerData::get_data(pxr::TfToken const &key) const
{
ID_LOG(3, "%s", key.GetText());
if (key == pxr::HdInstancerTokens->instanceTransform) {
return pxr::VtValue(mesh_transforms_);
}
return pxr::VtValue();
}
pxr::GfMatrix4d InstancerData::transform(pxr::SdfPath const &id) const
{
NonmeshInstance *nm_inst = nonmesh_instance(id);
if (nm_inst) {
return nm_inst->transforms[nonmesh_prim_id_index(id)];
}
/* Mesh instance transform must be identity */
return pxr::GfMatrix4d(1.0);
}
pxr::HdPrimvarDescriptorVector InstancerData::primvar_descriptors(
pxr::HdInterpolation interpolation) const
{
pxr::HdPrimvarDescriptorVector primvars;
if (interpolation == pxr::HdInterpolationInstance) {
primvars.emplace_back(
pxr::HdInstancerTokens->instanceTransform, interpolation, pxr::HdPrimvarRoleTokens->none);
}
return primvars;
}
pxr::VtIntArray InstancerData::indices(pxr::SdfPath const &id) const
{
return mesh_instance(id)->indices;
}
ObjectData *InstancerData::object_data(pxr::SdfPath const &id) const
{
MeshInstance *m_inst = mesh_instance(id);
if (m_inst) {
return m_inst->data.get();
}
NonmeshInstance *nm_inst = nonmesh_instance(id);
if (nm_inst) {
return nm_inst->data.get();
}
return nullptr;
}
pxr::SdfPathVector InstancerData::prototypes() const
{
pxr::SdfPathVector paths;
for (auto &m_inst : mesh_instances_.values()) {
for (auto &p : m_inst.data->submesh_paths()) {
paths.push_back(p);
}
}
return paths;
}
void InstancerData::available_materials(Set<pxr::SdfPath> &paths) const
{
for (auto &m_inst : mesh_instances_.values()) {
m_inst.data->available_materials(paths);
}
for (auto &l_inst : nonmesh_instances_.values()) {
l_inst.data->available_materials(paths);
}
}
void InstancerData::update_double_sided(MaterialData *mat_data)
{
for (auto &m_inst : mesh_instances_.values()) {
m_inst.data->update_double_sided(mat_data);
}
}
void InstancerData::pre_update()
{
mesh_transforms_.clear();
for (auto &m_inst : mesh_instances_.values()) {
m_inst.indices.clear();
}
for (auto &l_inst : nonmesh_instances_.values()) {
l_inst.transforms.clear();
}
}
void InstancerData::update_instance(DupliObject *dupli)
{
Object *object = dupli->ob;
pxr::SdfPath p_id = object_prim_id(object);
if (ObjectData::is_mesh(object)) {
MeshInstance *m_inst = mesh_instance(p_id);
if (!m_inst) {
m_inst = &mesh_instances_.lookup_or_add_default(p_id);
m_inst->data = std::make_unique<MeshData>(scene_delegate_, object, p_id);
m_inst->data->init();
m_inst->data->insert();
}
else {
m_inst->data->update();
}
ID_LOG(2, "Mesh %s %d", m_inst->data->id->name, (int)mesh_transforms_.size());
m_inst->indices.push_back(mesh_transforms_.size());
mesh_transforms_.push_back(gf_matrix_from_transform(dupli->mat));
}
else {
NonmeshInstance *nm_inst = nonmesh_instance(p_id);
if (!nm_inst) {
nm_inst = &nonmesh_instances_.lookup_or_add_default(p_id);
nm_inst->data = ObjectData::create(scene_delegate_, object, p_id);
}
ID_LOG(2, "Light %s %d", nm_inst->data->id->name, (int)nm_inst->transforms.size());
nm_inst->transforms.push_back(gf_matrix_from_transform(dupli->mat));
}
}
void InstancerData::post_update()
{
/* Remove mesh intances without indices */
mesh_instances_.remove_if([&](auto item) {
bool res = item.value.indices.empty();
if (res) {
item.value.data->remove();
}
return res;
});
/* Update light intances and remove instances without transforms */
for (auto &l_inst : nonmesh_instances_.values()) {
update_nonmesh_instance(l_inst);
}
nonmesh_instances_.remove_if([&](auto item) { return item.value.transforms.empty(); });
/* Insert/remove/update instancer in RenderIndex */
pxr::HdRenderIndex &index = scene_delegate_->GetRenderIndex();
if (mesh_instances_.is_empty()) {
/* Important: removing instancer when nonmesh_instances_ are empty too */
if (index.HasInstancer(prim_id) && nonmesh_instances_.is_empty()) {
index.RemoveInstancer(prim_id);
ID_LOG(1, "Remove instancer");
}
}
else {
if (index.HasInstancer(prim_id)) {
index.GetChangeTracker().MarkInstancerDirty(prim_id, pxr::HdChangeTracker::AllDirty);
ID_LOG(1, "Update instancer");
}
else {
index.InsertInstancer(scene_delegate_, prim_id);
ID_LOG(1, "Insert instancer");
}
}
}
pxr::SdfPath InstancerData::object_prim_id(Object *object) const
{
/* Making id of object in form like <prefix>_<pointer in 16 hex digits format> */
char name[32];
snprintf(name, sizeof(name), "O_%p", object);
return prim_id.AppendElementString(name);
}
pxr::SdfPath InstancerData::nonmesh_prim_id(pxr::SdfPath const &prim_id, int index) const
{
char name[16];
snprintf(name, sizeof(name), "NM_%08d", index);
return prim_id.AppendElementString(name);
}
int InstancerData::nonmesh_prim_id_index(pxr::SdfPath const &id) const
{
int index;
sscanf(id.GetName().c_str(), "NM_%d", &index);
return index;
}
void InstancerData::update_nonmesh_instance(NonmeshInstance &nm_inst)
{
ObjectData *obj_data = nm_inst.data.get();
pxr::SdfPath prev_id = nm_inst.data->prim_id;
int i;
/* Remove old light instances */
while (nm_inst.count > nm_inst.transforms.size()) {
--nm_inst.count;
obj_data->prim_id = nonmesh_prim_id(prev_id, nm_inst.count);
obj_data->remove();
}
/* Update current light instances */
LightData *l_data = dynamic_cast<LightData *>(obj_data);
if (l_data && l_data->prim_type((Light *)((Object *)l_data->id)->data) != l_data->prim_type_) {
/* Special case: recreate instances when prim_type was changed */
for (i = 0; i < nm_inst.count; ++i) {
obj_data->prim_id = nonmesh_prim_id(prev_id, i);
obj_data->remove();
}
l_data->init();
for (i = 0; i < nm_inst.count; ++i) {
obj_data->prim_id = nonmesh_prim_id(prev_id, i);
obj_data->insert();
}
}
else {
for (i = 0; i < nm_inst.count; ++i) {
obj_data->prim_id = nonmesh_prim_id(prev_id, i);
obj_data->update();
}
}
/* Add new light instances */
while (nm_inst.count < nm_inst.transforms.size()) {
obj_data->prim_id = nonmesh_prim_id(prev_id, nm_inst.count);
obj_data->insert();
++nm_inst.count;
}
obj_data->prim_id = prev_id;
}
InstancerData::MeshInstance *InstancerData::mesh_instance(pxr::SdfPath const &id) const
{
auto m_inst = mesh_instances_.lookup_ptr(id.GetPathElementCount() == 4 ? id.GetParentPath() :
id);
if (!m_inst) {
return nullptr;
}
return const_cast<MeshInstance *>(m_inst);
}
InstancerData::NonmeshInstance *InstancerData::nonmesh_instance(pxr::SdfPath const &id) const
{
auto nm_inst = nonmesh_instances_.lookup_ptr(id.GetPathElementCount() == 4 ? id.GetParentPath() :
id);
if (!nm_inst) {
return nullptr;
}
return const_cast<NonmeshInstance *>(nm_inst);
}
} // namespace blender::io::hydra

@ -0,0 +1,66 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include "BLI_map.hh"
#include "BLI_set.hh"
#include "mesh.h"
namespace blender::io::hydra {
class InstancerData : public IdData {
struct MeshInstance {
std::unique_ptr<MeshData> data;
pxr::VtIntArray indices;
};
struct NonmeshInstance {
std::unique_ptr<ObjectData> data;
pxr::VtMatrix4dArray transforms;
int count = 0;
};
private:
Map<pxr::SdfPath, MeshInstance> mesh_instances_;
Map<pxr::SdfPath, NonmeshInstance> nonmesh_instances_;
pxr::VtMatrix4dArray mesh_transforms_;
public:
InstancerData(HydraSceneDelegate *scene_delegate, pxr::SdfPath const &prim_id);
void init() override;
void insert() override;
void remove() override;
void update() override;
pxr::VtValue get_data(pxr::TfToken const &key) const override;
pxr::GfMatrix4d transform(pxr::SdfPath const &id) const;
pxr::HdPrimvarDescriptorVector primvar_descriptors(pxr::HdInterpolation interpolation) const;
pxr::VtIntArray indices(pxr::SdfPath const &id) const;
ObjectData *object_data(pxr::SdfPath const &id) const;
pxr::SdfPathVector prototypes() const;
void available_materials(Set<pxr::SdfPath> &paths) const;
void update_double_sided(MaterialData *mat_data);
/* Following update functions are working together:
* pre_update()
* update_instance()
* update_instance()
* ...
* post_update() */
void pre_update();
void update_instance(DupliObject *dupli);
void post_update();
private:
pxr::SdfPath object_prim_id(Object *object) const;
pxr::SdfPath nonmesh_prim_id(pxr::SdfPath const &prim_id, int index) const;
int nonmesh_prim_id_index(pxr::SdfPath const &id) const;
void update_nonmesh_instance(NonmeshInstance &inst);
MeshInstance *mesh_instance(pxr::SdfPath const &id) const;
NonmeshInstance *nonmesh_instance(pxr::SdfPath const &id) const;
};
} // namespace blender::io::hydra

@ -0,0 +1,177 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include "light.h"
#include <pxr/imaging/hd/light.h>
#include <pxr/imaging/hd/tokens.h>
#include <pxr/usd/usdLux/tokens.h>
#include "DNA_light_types.h"
#include "BLI_math_rotation.h"
#include "hydra_scene_delegate.h"
namespace blender::io::hydra {
LightData::LightData(HydraSceneDelegate *scene_delegate,
Object *object,
pxr::SdfPath const &prim_id)
: ObjectData(scene_delegate, object, prim_id)
{
}
void LightData::init()
{
ID_LOGN(1, "");
Light *light = (Light *)((Object *)id)->data;
data_.clear();
switch (light->type) {
case LA_AREA: {
switch (light->area_shape) {
case LA_AREA_SQUARE:
data_[pxr::HdLightTokens->width] = light->area_size;
data_[pxr::HdLightTokens->height] = light->area_size;
break;
case LA_AREA_RECT:
data_[pxr::HdLightTokens->width] = light->area_size;
data_[pxr::HdLightTokens->height] = light->area_sizey;
break;
case LA_AREA_DISK:
data_[pxr::HdLightTokens->radius] = light->area_size / 2.0f;
break;
case LA_AREA_ELLIPSE:
/* An ellipse light deteriorates into a disk light. */
data_[pxr::HdLightTokens->radius] = (light->area_size + light->area_sizey) / 4.0f;
break;
}
break;
}
case LA_LOCAL:
case LA_SPOT: {
data_[pxr::HdLightTokens->radius] = light->radius;
if (light->radius == 0.0f) {
data_[pxr::UsdLuxTokens->treatAsPoint] = true;
}
if (light->type == LA_SPOT) {
data_[pxr::UsdLuxTokens->inputsShapingConeAngle] = RAD2DEGF(light->spotsize * 0.5f);
data_[pxr::UsdLuxTokens->inputsShapingConeSoftness] = light->spotblend;
}
break;
}
case LA_SUN: {
data_[pxr::HdLightTokens->angle] = RAD2DEGF(light->sun_angle * 0.5f);
break;
}
default: {
BLI_assert_unreachable();
break;
}
}
float intensity;
if (light->type == LA_SUN) {
/* Unclear why, but approximately matches Karma. */
intensity = light->energy / 4.0f;
}
else {
/* Convert from radiant flux to intensity. */
intensity = light->energy / M_PI;
}
data_[pxr::HdLightTokens->intensity] = intensity;
data_[pxr::HdLightTokens->exposure] = 0.0f;
data_[pxr::HdLightTokens->color] = pxr::GfVec3f(light->r, light->g, light->b);
data_[pxr::HdLightTokens->diffuse] = light->diff_fac;
data_[pxr::HdLightTokens->specular] = light->spec_fac;
data_[pxr::HdLightTokens->normalize] = true;
prim_type_ = prim_type(light);
write_transform();
}
void LightData::insert()
{
ID_LOGN(1, "");
scene_delegate_->GetRenderIndex().InsertSprim(prim_type_, scene_delegate_, prim_id);
}
void LightData::remove()
{
ID_LOG(1, "");
scene_delegate_->GetRenderIndex().RemoveSprim(prim_type_, prim_id);
}
void LightData::update()
{
Object *object = (Object *)id;
Light *light = (Light *)object->data;
pxr::HdDirtyBits bits = pxr::HdLight::Clean;
if (id->recalc & ID_RECALC_GEOMETRY || light->id.recalc & ID_RECALC_GEOMETRY) {
if (prim_type(light) != prim_type_) {
remove();
init();
insert();
return;
}
init();
bits = pxr::HdLight::AllDirty;
}
else if (id->recalc & ID_RECALC_TRANSFORM) {
write_transform();
bits = pxr::HdLight::DirtyTransform;
}
if (bits != pxr::HdChangeTracker::Clean) {
scene_delegate_->GetRenderIndex().GetChangeTracker().MarkSprimDirty(prim_id, bits);
ID_LOGN(1, "");
}
}
pxr::VtValue LightData::get_data(pxr::TfToken const &key) const
{
ID_LOGN(3, "%s", key.GetText());
auto it = data_.find(key);
if (it != data_.end()) {
return pxr::VtValue(it->second);
}
return pxr::VtValue();
}
pxr::TfToken LightData::prim_type(Light *light)
{
switch (light->type) {
case LA_AREA:
switch (light->area_shape) {
case LA_AREA_SQUARE:
case LA_AREA_RECT:
return pxr::HdPrimTypeTokens->rectLight;
case LA_AREA_DISK:
case LA_AREA_ELLIPSE:
return pxr::HdPrimTypeTokens->diskLight;
default:
return pxr::HdPrimTypeTokens->rectLight;
}
break;
case LA_LOCAL:
case LA_SPOT:
return pxr::HdPrimTypeTokens->sphereLight;
case LA_SUN:
return pxr::HdPrimTypeTokens->distantLight;
default:
BLI_assert_unreachable();
}
return pxr::TfToken();
}
} // namespace blender::io::hydra

@ -0,0 +1,39 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include <pxr/base/tf/hashmap.h>
#include <pxr/usd/sdf/assetPath.h>
#include <pxr/usd/sdf/path.h>
#include "BKE_light.h"
#include "object.h"
namespace blender::io::hydra {
class InstancerData;
class LightData : public ObjectData {
friend InstancerData;
protected:
std::map<pxr::TfToken, pxr::VtValue> data_;
pxr::TfToken prim_type_;
public:
LightData(HydraSceneDelegate *scene_delegate, Object *object, pxr::SdfPath const &prim_id);
void init() override;
void insert() override;
void remove() override;
void update() override;
pxr::VtValue get_data(pxr::TfToken const &key) const override;
protected:
pxr::TfToken prim_type(Light *light);
};
} // namespace blender::io::hydra

@ -0,0 +1,87 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include "material.h"
#include <Python.h>
#include <unicodeobject.h>
#include <pxr/imaging/hd/material.h>
#include <pxr/imaging/hd/renderDelegate.h>
#include <pxr/imaging/hd/tokens.h>
#include "MEM_guardedalloc.h"
#include "BKE_lib_id.h"
#include "BKE_material.h"
#include "RNA_access.h"
#include "RNA_prototypes.h"
#include "RNA_types.h"
#include "bpy_rna.h"
#include "hydra_scene_delegate.h"
namespace blender::io::hydra {
MaterialData::MaterialData(HydraSceneDelegate *scene_delegate,
Material *material,
pxr::SdfPath const &prim_id)
: IdData(scene_delegate, (ID *)material, prim_id)
{
}
void MaterialData::init()
{
ID_LOGN(1, "");
double_sided = (((Material *)id)->blend_flag & MA_BL_CULL_BACKFACE) == 0;
}
void MaterialData::insert()
{
ID_LOGN(1, "");
scene_delegate_->GetRenderIndex().InsertSprim(
pxr::HdPrimTypeTokens->material, scene_delegate_, prim_id);
}
void MaterialData::remove()
{
ID_LOG(1, "");
scene_delegate_->GetRenderIndex().RemoveSprim(pxr::HdPrimTypeTokens->material, prim_id);
}
void MaterialData::update()
{
ID_LOGN(1, "");
bool prev_double_sided = double_sided;
init();
scene_delegate_->GetRenderIndex().GetChangeTracker().MarkSprimDirty(prim_id,
pxr::HdMaterial::AllDirty);
if (prev_double_sided != double_sided) {
for (auto &obj_data : scene_delegate_->objects_.values()) {
MeshData *m_data = dynamic_cast<MeshData *>(obj_data.get());
if (m_data) {
m_data->update_double_sided(this);
}
}
scene_delegate_->instancer_data_->update_double_sided(this);
}
}
pxr::VtValue MaterialData::get_data(pxr::TfToken const & /* key */) const
{
return pxr::VtValue();
}
pxr::VtValue MaterialData::get_material_resource() const
{
return pxr::VtValue();
}
pxr::HdCullStyle MaterialData::cull_style() const
{
return double_sided ? pxr::HdCullStyle::HdCullStyleNothing : pxr::HdCullStyle::HdCullStyleBack;
}
} // namespace blender::io::hydra

@ -0,0 +1,38 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include <pxr/imaging/hd/enums.h>
#include <pxr/usd/sdf/assetPath.h>
#include <pxr/usd/sdf/path.h>
#include "DNA_material_types.h"
#include "BLI_map.hh"
#include "id.h"
namespace blender::io::hydra {
class MaterialData : public IdData {
public:
MaterialData(HydraSceneDelegate *scene_delegate,
Material *material,
pxr::SdfPath const &prim_id);
void init() override;
void insert() override;
void remove() override;
void update() override;
pxr::VtValue get_data(pxr::TfToken const &key) const override;
pxr::VtValue get_material_resource() const;
pxr::HdCullStyle cull_style() const;
bool double_sided = true;
};
using MaterialDataMap = Map<pxr::SdfPath, std::unique_ptr<MaterialData>>;
} // namespace blender::io::hydra

@ -0,0 +1,322 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include <pxr/base/gf/vec2f.h>
#include <pxr/base/tf/staticTokens.h>
#include <pxr/imaging/hd/tokens.h>
#include "BKE_material.h"
#include "BKE_mesh.hh"
#include "BKE_mesh_runtime.hh"
#include "hydra_scene_delegate.h"
#include "mesh.h"
PXR_NAMESPACE_OPEN_SCOPE
TF_DEFINE_PRIVATE_TOKENS(tokens_, (st));
PXR_NAMESPACE_CLOSE_SCOPE
namespace blender::io::hydra {
MeshData::MeshData(HydraSceneDelegate *scene_delegate, Object *object, pxr::SdfPath const &prim_id)
: ObjectData(scene_delegate, object, prim_id)
{
}
void MeshData::init()
{
ID_LOGN(1, "");
Object *object = (Object *)id;
Mesh *mesh = BKE_object_to_mesh(nullptr, object, false);
if (mesh) {
write_submeshes(mesh);
}
BKE_object_to_mesh_clear(object);
write_transform();
write_materials();
}
void MeshData::insert()
{
ID_LOGN(1, "");
update_prims();
}
void MeshData::remove()
{
ID_LOG(1, "");
submeshes_.clear();
update_prims();
}
void MeshData::update()
{
Object *object = (Object *)id;
if ((id->recalc & ID_RECALC_GEOMETRY) || (((ID *)object->data)->recalc & ID_RECALC_GEOMETRY)) {
init();
update_prims();
return;
}
pxr::HdDirtyBits bits = pxr::HdChangeTracker::Clean;
if (id->recalc & ID_RECALC_SHADING) {
write_materials();
bits |= pxr::HdChangeTracker::DirtyMaterialId | pxr::HdChangeTracker::DirtyDoubleSided;
}
if (id->recalc & ID_RECALC_TRANSFORM) {
write_transform();
bits |= pxr::HdChangeTracker::DirtyTransform;
}
if (bits == pxr::HdChangeTracker::Clean) {
return;
}
for (int i = 0; i < submeshes_.size(); ++i) {
scene_delegate_->GetRenderIndex().GetChangeTracker().MarkRprimDirty(submesh_prim_id(i), bits);
ID_LOGN(1, "%d", i);
}
}
pxr::VtValue MeshData::get_data(pxr::TfToken const & /* key */) const
{
return pxr::VtValue();
}
pxr::VtValue MeshData::get_data(pxr::SdfPath const &id, pxr::TfToken const &key) const
{
if (key == pxr::HdTokens->normals) {
return pxr::VtValue(submesh(id).normals);
}
if (key == pxr::tokens_->st) {
return pxr::VtValue(submesh(id).uvs);
}
if (key == pxr::HdTokens->points) {
return pxr::VtValue(submesh(id).vertices);
}
return get_data(key);
}
pxr::SdfPath MeshData::material_id(pxr::SdfPath const &id) const
{
const SubMesh &sm = submesh(id);
if (!sm.mat_data) {
return pxr::SdfPath();
}
return sm.mat_data->prim_id;
}
void MeshData::available_materials(Set<pxr::SdfPath> &paths) const
{
for (auto &sm : submeshes_) {
if (sm.mat_data && !sm.mat_data->prim_id.IsEmpty()) {
paths.add(sm.mat_data->prim_id);
}
}
}
pxr::HdMeshTopology MeshData::topology(pxr::SdfPath const &id) const
{
const SubMesh &sm = submesh(id);
return pxr::HdMeshTopology(pxr::PxOsdOpenSubdivTokens->none,
pxr::HdTokens->rightHanded,
sm.face_vertex_counts,
sm.face_vertex_indices);
}
pxr::HdPrimvarDescriptorVector MeshData::primvar_descriptors(
pxr::HdInterpolation interpolation) const
{
pxr::HdPrimvarDescriptorVector primvars;
if (interpolation == pxr::HdInterpolationVertex) {
primvars.emplace_back(pxr::HdTokens->points, interpolation, pxr::HdPrimvarRoleTokens->point);
}
else if (interpolation == pxr::HdInterpolationFaceVarying) {
if (!submeshes_[0].normals.empty()) {
primvars.emplace_back(
pxr::HdTokens->normals, interpolation, pxr::HdPrimvarRoleTokens->normal);
}
if (!submeshes_[0].uvs.empty()) {
primvars.emplace_back(
pxr::tokens_->st, interpolation, pxr::HdPrimvarRoleTokens->textureCoordinate);
}
}
return primvars;
}
pxr::HdCullStyle MeshData::cull_style(pxr::SdfPath const &id) const
{
const SubMesh &sm = submesh(id);
if (sm.mat_data) {
return sm.mat_data->cull_style();
}
return pxr::HdCullStyle::HdCullStyleNothing;
}
bool MeshData::double_sided(pxr::SdfPath const &id) const
{
const SubMesh &sm = submesh(id);
if (sm.mat_data) {
return sm.mat_data->double_sided;
}
return true;
}
void MeshData::update_double_sided(MaterialData *mat_data)
{
for (int i = 0; i < submeshes_.size(); ++i) {
if (submeshes_[i].mat_data == mat_data) {
scene_delegate_->GetRenderIndex().GetChangeTracker().MarkRprimDirty(
submesh_prim_id(i),
pxr::HdChangeTracker::DirtyDoubleSided | pxr::HdChangeTracker::DirtyCullStyle);
ID_LOGN(1, "%d", i);
}
}
}
pxr::SdfPathVector MeshData::submesh_paths() const
{
pxr::SdfPathVector ret;
for (int i = 0; i < submeshes_.size(); ++i) {
ret.push_back(submesh_prim_id(i));
}
return ret;
}
void MeshData::write_materials()
{
Object *object = (Object *)id;
for (int i = 0; i < submeshes_.size(); ++i) {
SubMesh &m = submeshes_[i];
Material *mat = BKE_object_material_get_eval(object, m.mat_index + 1);
m.mat_data = get_or_create_material(mat);
}
}
pxr::SdfPath MeshData::submesh_prim_id(int index) const
{
char name[16];
snprintf(name, sizeof(name), "SM_%04d", index);
return prim_id.AppendElementString(name);
}
const MeshData::SubMesh &MeshData::submesh(pxr::SdfPath const &id) const
{
int index;
sscanf(id.GetName().c_str(), "SM_%d", &index);
return submeshes_[index];
}
void MeshData::write_submeshes(Mesh *mesh)
{
submeshes_.clear();
/* Insert base submeshes */
int mat_count = BKE_object_material_count_eval((Object *)id);
for (int i = 0; i < std::max(mat_count, 1); ++i) {
SubMesh sm;
sm.mat_index = i;
submeshes_.push_back(sm);
}
/* Fill submeshes data */
const int *material_indices = BKE_mesh_material_indices(mesh);
blender::Span<int> looptri_faces = mesh->looptri_faces();
blender::Span<int> corner_verts = mesh->corner_verts();
blender::Span<MLoopTri> looptris = mesh->looptris();
BKE_mesh_calc_normals_split(mesh);
const float(*lnors)[3] = (float(*)[3])CustomData_get_layer(&mesh->loop_data, CD_NORMAL);
const float(*luvs)[2] = (float(*)[2])CustomData_get_layer(&mesh->loop_data, CD_PROP_FLOAT2);
for (size_t i = 0; i < looptris.size(); ++i) {
int mat_ind = material_indices ? material_indices[looptri_faces[i]] : 0;
const MLoopTri &lt = looptris[i];
SubMesh &sm = submeshes_[mat_ind];
sm.face_vertex_counts.push_back(3);
sm.face_vertex_indices.push_back(corner_verts[lt.tri[0]]);
sm.face_vertex_indices.push_back(corner_verts[lt.tri[1]]);
sm.face_vertex_indices.push_back(corner_verts[lt.tri[2]]);
if (lnors) {
sm.normals.push_back(pxr::GfVec3f(lnors[lt.tri[0]]));
sm.normals.push_back(pxr::GfVec3f(lnors[lt.tri[1]]));
sm.normals.push_back(pxr::GfVec3f(lnors[lt.tri[2]]));
}
if (luvs) {
sm.uvs.push_back(pxr::GfVec2f(luvs[lt.tri[0]]));
sm.uvs.push_back(pxr::GfVec2f(luvs[lt.tri[1]]));
sm.uvs.push_back(pxr::GfVec2f(luvs[lt.tri[2]]));
}
}
/* Remove submeshes without faces */
for (auto it = submeshes_.begin(); it != submeshes_.end();) {
if (it->face_vertex_counts.empty()) {
it = submeshes_.erase(it);
}
else {
++it;
}
}
if (submeshes_.empty()) {
return;
}
/* vertices */
blender::Span<blender::float3> verts = mesh->vert_positions();
pxr::VtVec3fArray vertices(mesh->totvert);
int i = 0;
for (blender::float3 v : verts) {
vertices[i++] = pxr::GfVec3f(v.x, v.y, v.z);
}
if (submeshes_.size() == 1) {
submeshes_[0].vertices = std::move(vertices);
}
else {
/* Optimizing submeshes: getting only used vertices, rearranged indices */
for (SubMesh &sm : submeshes_) {
Vector<int> index_map(vertices.size(), 0);
for (int &face_vertex_index : sm.face_vertex_indices) {
const int v = face_vertex_index;
if (index_map[v] == 0) {
sm.vertices.push_back(vertices[v]);
index_map[v] = sm.vertices.size();
}
face_vertex_index = index_map[v] - 1;
}
}
}
}
void MeshData::update_prims()
{
auto &render_index = scene_delegate_->GetRenderIndex();
int i;
for (i = 0; i < submeshes_.size(); ++i) {
pxr::SdfPath p = submesh_prim_id(i);
if (i < submeshes_count_) {
render_index.GetChangeTracker().MarkRprimDirty(p, pxr::HdChangeTracker::AllDirty);
ID_LOGN(1, "Update %d", i);
}
else {
render_index.InsertRprim(pxr::HdPrimTypeTokens->mesh, scene_delegate_, p);
ID_LOGN(1, "Insert %d", i);
}
}
for (; i < submeshes_count_; ++i) {
render_index.RemoveRprim(submesh_prim_id(i));
ID_LOG(1, "Remove %d", i);
}
submeshes_count_ = submeshes_.size();
}
} // namespace blender::io::hydra

@ -0,0 +1,63 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include <pxr/base/vt/array.h>
#include <pxr/imaging/hd/sceneDelegate.h>
#include "BLI_set.hh"
#include "BKE_duplilist.h"
#include "material.h"
#include "object.h"
namespace blender::io::hydra {
class MeshData : public ObjectData {
struct SubMesh {
pxr::VtVec3fArray vertices;
pxr::VtIntArray face_vertex_counts;
pxr::VtIntArray face_vertex_indices;
pxr::VtVec3fArray normals;
pxr::VtVec2fArray uvs;
int mat_index = 0;
MaterialData *mat_data = nullptr;
};
private:
std::vector<SubMesh> submeshes_;
int submeshes_count_ = 0;
public:
MeshData(HydraSceneDelegate *scene_delegate, Object *object, pxr::SdfPath const &prim_id);
void init() override;
void insert() override;
void remove() override;
void update() override;
pxr::VtValue get_data(pxr::TfToken const &key) const override;
pxr::VtValue get_data(pxr::SdfPath const &id, pxr::TfToken const &key) const override;
pxr::SdfPath material_id(pxr::SdfPath const &id) const override;
void available_materials(Set<pxr::SdfPath> &paths) const override;
pxr::HdMeshTopology topology(pxr::SdfPath const &id) const;
pxr::HdPrimvarDescriptorVector primvar_descriptors(pxr::HdInterpolation interpolation) const;
pxr::HdCullStyle cull_style(pxr::SdfPath const &id) const;
bool double_sided(pxr::SdfPath const &id) const;
void update_double_sided(MaterialData *mat_data);
pxr::SdfPathVector submesh_paths() const;
protected:
void write_materials() override;
private:
pxr::SdfPath submesh_prim_id(int index) const;
const SubMesh &submesh(pxr::SdfPath const &id) const;
void write_submeshes(Mesh *mesh);
void update_prims();
};
} // namespace blender::io::hydra

@ -0,0 +1,157 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include "DEG_depsgraph_query.h"
#include "curves.h"
#include "hydra_scene_delegate.h"
#include "light.h"
#include "mesh.h"
#include "object.h"
#include "volume.h"
namespace blender::io::hydra {
ObjectData::ObjectData(HydraSceneDelegate *scene_delegate,
Object *object,
pxr::SdfPath const &prim_id)
: IdData(scene_delegate, (ID *)object, prim_id), transform(pxr::GfMatrix4d(1.0))
{
}
std::unique_ptr<ObjectData> ObjectData::create(HydraSceneDelegate *scene_delegate,
Object *object,
pxr::SdfPath const &prim_id)
{
std::unique_ptr<ObjectData> obj_data;
switch (object->type) {
case OB_MESH:
case OB_SURF:
case OB_FONT:
case OB_CURVES_LEGACY:
case OB_MBALL:
if (VolumeModifierData::is_volume_modifier(object)) {
obj_data = std::make_unique<VolumeModifierData>(scene_delegate, object, prim_id);
break;
}
obj_data = std::make_unique<MeshData>(scene_delegate, object, prim_id);
break;
case OB_CURVES:
obj_data = std::make_unique<CurvesData>(scene_delegate, object, prim_id);
break;
case OB_LAMP:
obj_data = std::make_unique<LightData>(scene_delegate, object, prim_id);
break;
case OB_VOLUME:
obj_data = std::make_unique<VolumeData>(scene_delegate, object, prim_id);
break;
default:
BLI_assert_unreachable();
break;
}
obj_data->init();
return obj_data;
}
bool ObjectData::is_supported(Object *object)
{
switch (object->type) {
case OB_MESH:
case OB_SURF:
case OB_FONT:
case OB_CURVES:
case OB_CURVES_LEGACY:
case OB_MBALL:
case OB_LAMP:
case OB_VOLUME:
return true;
default:
break;
}
return false;
}
bool ObjectData::is_mesh(Object *object)
{
switch (object->type) {
case OB_MESH:
case OB_SURF:
case OB_FONT:
case OB_CURVES_LEGACY:
case OB_MBALL:
if (VolumeModifierData::is_volume_modifier(object)) {
return false;
}
return true;
default:
break;
}
return false;
}
bool ObjectData::is_visible(HydraSceneDelegate *scene_delegate, Object *object, int mode)
{
eEvaluationMode deg_mode = DEG_get_mode(scene_delegate->depsgraph);
bool ret = BKE_object_visibility(object, deg_mode) & mode;
if (deg_mode == DAG_EVAL_VIEWPORT) {
ret &= BKE_object_is_visible_in_viewport(scene_delegate->view3d, object);
}
/* Note: visibility for final render we are taking from depsgraph */
return ret;
}
pxr::VtValue ObjectData::get_data(pxr::SdfPath const & /* id */, pxr::TfToken const &key) const
{
return get_data(key);
}
pxr::SdfPath ObjectData::material_id() const
{
return pxr::SdfPath();
}
pxr::SdfPath ObjectData::material_id(pxr::SdfPath const & /* id */) const
{
return material_id();
}
void ObjectData::available_materials(Set<pxr::SdfPath> & /* paths */) const {}
void ObjectData::write_transform()
{
transform = gf_matrix_from_transform(((Object *)id)->object_to_world);
}
void ObjectData::write_materials() {}
MaterialData *ObjectData::get_or_create_material(Material *mat)
{
if (!mat) {
return nullptr;
}
pxr::SdfPath p_id = scene_delegate_->material_prim_id(mat);
MaterialData *mat_data = scene_delegate_->material_data(p_id);
if (!mat_data) {
scene_delegate_->materials_.add_new(
p_id, std::make_unique<MaterialData>(scene_delegate_, mat, p_id));
mat_data = scene_delegate_->material_data(p_id);
mat_data->init();
mat_data->insert();
}
return mat_data;
}
pxr::GfMatrix4d gf_matrix_from_transform(float m[4][4])
{
pxr::GfMatrix4d ret;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
ret[i][j] = m[i][j];
}
}
return ret;
}
} // namespace blender::io::hydra

@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include <pxr/base/gf/matrix4d.h>
#include <pxr/base/tf/hashmap.h>
#include "DNA_object_types.h"
#include "BLI_map.hh"
#include "BLI_set.hh"
#include "BKE_layer.h"
#include "BKE_object.h"
#include "id.h"
#include "material.h"
namespace blender::io::hydra {
class ObjectData : public IdData {
public:
pxr::GfMatrix4d transform;
bool visible = true;
public:
ObjectData(HydraSceneDelegate *scene_delegate, Object *object, pxr::SdfPath const &prim_id);
static std::unique_ptr<ObjectData> create(HydraSceneDelegate *scene_delegate,
Object *object,
pxr::SdfPath const &prim_id);
static bool is_supported(Object *object);
static bool is_mesh(Object *object);
static bool is_visible(HydraSceneDelegate *scene_delegate,
Object *object,
int mode = OB_VISIBLE_SELF);
using IdData::get_data;
virtual pxr::VtValue get_data(pxr::SdfPath const &id, pxr::TfToken const &key) const;
virtual pxr::SdfPath material_id() const;
virtual pxr::SdfPath material_id(pxr::SdfPath const &id) const;
virtual void available_materials(Set<pxr::SdfPath> &paths) const;
protected:
virtual void write_transform();
virtual void write_materials();
MaterialData *get_or_create_material(Material *mat);
};
using ObjectDataMap = Map<pxr::SdfPath, std::unique_ptr<ObjectData>>;
pxr::GfMatrix4d gf_matrix_from_transform(float m[4][4]);
} // namespace blender::io::hydra

@ -0,0 +1,162 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include <pxr/imaging/hd/bprim.h>
#include <pxr/imaging/hd/tokens.h>
#include <pxr/imaging/hd/volumeFieldSchema.h>
#include <pxr/usd/usdHydra/tokens.h>
#include <pxr/usd/usdVol/tokens.h>
#include <pxr/usdImaging/usdVolImaging/tokens.h>
#include "BKE_material.h"
#include "BKE_volume.h"
#include "BLI_index_range.hh"
#include "DNA_volume_types.h"
#include "hydra_scene_delegate.h"
#include "volume.h"
namespace blender::io::hydra {
VolumeData::VolumeData(HydraSceneDelegate *scene_delegate,
Object *object,
pxr::SdfPath const &prim_id)
: ObjectData(scene_delegate, object, prim_id)
{
}
void VolumeData::init()
{
field_descriptors_.clear();
Volume *volume = (Volume *)((Object *)this->id)->data;
if (!BKE_volume_load(volume, scene_delegate_->bmain)) {
return;
}
filepath_ = BKE_volume_grids_frame_filepath(volume);
ID_LOGN(1, "%s", filepath_.c_str());
if (volume->runtime.grids) {
const int num_grids = BKE_volume_num_grids(volume);
if (num_grids) {
for (const int i : IndexRange(num_grids)) {
const VolumeGrid *grid = BKE_volume_grid_get_for_read(volume, i);
const std::string grid_name = BKE_volume_grid_name(grid);
field_descriptors_.emplace_back(pxr::TfToken(grid_name),
pxr::UsdVolImagingTokens->openvdbAsset,
prim_id.AppendElementString("VF_" + grid_name));
}
}
}
write_transform();
write_materials();
BKE_volume_unload(volume);
}
void VolumeData::insert()
{
scene_delegate_->GetRenderIndex().InsertRprim(
pxr::HdPrimTypeTokens->volume, scene_delegate_, prim_id);
ID_LOGN(1, "");
for (auto &desc : field_descriptors_) {
scene_delegate_->GetRenderIndex().InsertBprim(
desc.fieldPrimType, scene_delegate_, desc.fieldId);
ID_LOGN(2, "Volume field %s", desc.fieldId.GetText());
}
}
void VolumeData::remove()
{
for (auto &desc : field_descriptors_) {
ID_LOG(2, "%s", desc.fieldId.GetText());
scene_delegate_->GetRenderIndex().RemoveBprim(desc.fieldPrimType, desc.fieldId);
}
ID_LOG(1, "");
scene_delegate_->GetRenderIndex().RemoveRprim(prim_id);
}
void VolumeData::update()
{
Object *object = (Object *)id;
pxr::HdDirtyBits bits = pxr::HdChangeTracker::Clean;
if ((id->recalc & ID_RECALC_GEOMETRY) || (((ID *)object->data)->recalc & ID_RECALC_GEOMETRY)) {
init();
bits = pxr::HdChangeTracker::AllDirty;
}
if (id->recalc & ID_RECALC_SHADING) {
write_materials();
bits |= pxr::HdChangeTracker::DirtyMaterialId | pxr::HdChangeTracker::DirtyDoubleSided;
}
if (id->recalc & ID_RECALC_TRANSFORM) {
write_transform();
bits |= pxr::HdChangeTracker::DirtyTransform;
}
if (bits == pxr::HdChangeTracker::Clean) {
return;
}
scene_delegate_->GetRenderIndex().GetChangeTracker().MarkRprimDirty(prim_id, bits);
ID_LOGN(1, "");
}
pxr::VtValue VolumeData::get_data(pxr::TfToken const &key) const
{
if (key == pxr::HdVolumeFieldSchemaTokens->filePath) {
return pxr::VtValue(pxr::SdfAssetPath(filepath_, filepath_));
}
if (key == pxr::HdVolumeFieldSchemaTokens->fieldIndex) {
return pxr::VtValue(0);
}
if (key == pxr::UsdHydraTokens->textureMemory) {
return pxr::VtValue(0.0f);
}
return pxr::VtValue();
}
pxr::VtValue VolumeData::get_data(pxr::SdfPath const &id, pxr::TfToken const &key) const
{
if (key == pxr::HdVolumeFieldSchemaTokens->fieldName) {
std::string name = id.GetName();
return pxr::VtValue(pxr::TfToken(name.substr(name.find("VF_") + 3)));
}
return get_data(key);
}
pxr::SdfPath VolumeData::material_id() const
{
if (!mat_data_) {
return pxr::SdfPath();
}
return mat_data_->prim_id;
}
void VolumeData::available_materials(Set<pxr::SdfPath> &paths) const
{
if (mat_data_ && !mat_data_->prim_id.IsEmpty()) {
paths.add(mat_data_->prim_id);
}
}
pxr::HdVolumeFieldDescriptorVector VolumeData::field_descriptors() const
{
return field_descriptors_;
}
void VolumeData::write_materials()
{
Object *object = (Object *)id;
Material *mat = nullptr;
/* TODO: Using only first material. Add support for multimaterial. */
if (BKE_object_material_count_eval(object) > 0) {
mat = BKE_object_material_get_eval(object, 0);
}
mat_data_ = get_or_create_material(mat);
}
} // namespace blender::io::hydra

@ -0,0 +1,37 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include <pxr/imaging/hd/sceneDelegate.h>
#include "object.h"
namespace blender::io::hydra {
class VolumeData : public ObjectData {
protected:
std::string filepath_;
pxr::HdVolumeFieldDescriptorVector field_descriptors_;
MaterialData *mat_data_ = nullptr;
public:
VolumeData(HydraSceneDelegate *scene_delegate, Object *object, pxr::SdfPath const &prim_id);
void init() override;
void insert() override;
void remove() override;
void update() override;
pxr::VtValue get_data(pxr::TfToken const &key) const override;
pxr::VtValue get_data(pxr::SdfPath const &id, pxr::TfToken const &key) const override;
pxr::SdfPath material_id() const override;
void available_materials(Set<pxr::SdfPath> &paths) const override;
pxr::HdVolumeFieldDescriptorVector field_descriptors() const;
protected:
void write_materials() override;
};
} // namespace blender::io::hydra

@ -0,0 +1,134 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include "volume_modifier.h"
#include <pxr/usdImaging/usdVolImaging/tokens.h>
#include "DNA_scene_types.h"
#include "DNA_volume_types.h"
#include "BLI_path_util.h"
#include "BKE_mesh.h"
#include "BKE_modifier.h"
#include "hydra_scene_delegate.h"
PXR_NAMESPACE_OPEN_SCOPE
TF_DEFINE_PRIVATE_TOKENS(grid_tokens_, (density)(flame)(shadow)(temperature)(velocity));
PXR_NAMESPACE_CLOSE_SCOPE
namespace blender::io::hydra {
VolumeModifierData::VolumeModifierData(HydraSceneDelegate *scene_delegate,
Object *object,
pxr::SdfPath const &prim_id)
: VolumeData(scene_delegate, object, prim_id)
{
}
bool VolumeModifierData::is_volume_modifier(Object *object)
{
if (object->type != OB_MESH) {
return false;
}
FluidModifierData *modifier = (FluidModifierData *)BKE_modifiers_findby_type(
object, eModifierType_Fluid);
return modifier && modifier->type & MOD_FLUID_TYPE_DOMAIN &&
modifier->domain->type == FLUID_DOMAIN_TYPE_GAS;
}
void VolumeModifierData::init()
{
field_descriptors_.clear();
Object *object = (Object *)this->id;
ModifierData *md = BKE_modifiers_findby_type(object, eModifierType_Fluid);
modifier_ = (FluidModifierData *)BKE_modifier_get_evaluated(
scene_delegate_->depsgraph, object, md);
if ((modifier_->domain->cache_data_format & FLUID_DOMAIN_FILE_OPENVDB) == 0) {
CLOG_WARN(LOG_HYDRA_SCENE,
"Volume %s is't exported: only OpenVDB file format supported",
prim_id.GetText());
return;
}
filepath_ = get_cached_file_path(modifier_->domain->cache_directory,
scene_delegate_->scene->r.cfra);
ID_LOG(1, "%s", filepath_.c_str());
for (auto &grid_name : pxr::grid_tokens_->allTokens) {
field_descriptors_.emplace_back(grid_name,
pxr::UsdVolImagingTokens->openvdbAsset,
prim_id.AppendElementString("VF_" + grid_name.GetString()));
}
write_transform();
write_materials();
}
void VolumeModifierData::update()
{
Object *object = (Object *)id;
if ((id->recalc & ID_RECALC_GEOMETRY) || (((ID *)object->data)->recalc & ID_RECALC_GEOMETRY)) {
remove();
init();
insert();
return;
}
pxr::HdDirtyBits bits = pxr::HdChangeTracker::Clean;
if (id->recalc & ID_RECALC_SHADING) {
write_materials();
bits |= pxr::HdChangeTracker::DirtyMaterialId | pxr::HdChangeTracker::DirtyDoubleSided;
}
if (id->recalc & ID_RECALC_TRANSFORM) {
write_transform();
bits |= pxr::HdChangeTracker::DirtyTransform;
}
if (bits == pxr::HdChangeTracker::Clean) {
return;
}
scene_delegate_->GetRenderIndex().GetChangeTracker().MarkRprimDirty(prim_id, bits);
ID_LOG(1, "");
}
void VolumeModifierData::write_transform()
{
Object *object = (Object *)this->id;
/* set base scaling */
transform = pxr::GfMatrix4d().SetScale(
pxr::GfVec3d(modifier_->domain->scale / modifier_->domain->global_size[0],
modifier_->domain->scale / modifier_->domain->global_size[1],
modifier_->domain->scale / modifier_->domain->global_size[2]));
/* positioning to center */
transform *= pxr::GfMatrix4d().SetTranslate(pxr::GfVec3d(-1, -1, -1));
/* including texspace transform */
float texspace_loc[3] = {0.0f, 0.0f, 0.0f}, texspace_scale[3] = {1.0f, 1.0f, 1.0f};
BKE_mesh_texspace_get((Mesh *)object->data, texspace_loc, texspace_scale);
transform *= pxr::GfMatrix4d(1.0f).SetScale(pxr::GfVec3d(texspace_scale)) *
pxr::GfMatrix4d(1.0f).SetTranslate(pxr::GfVec3d(texspace_loc));
/* applying object transform */
transform *= gf_matrix_from_transform(object->object_to_world);
}
std::string VolumeModifierData::get_cached_file_path(std::string directory, int frame)
{
char file_path[FILE_MAX];
char file_name[32];
snprintf(
file_name, sizeof(file_name), "%s_####%s", FLUID_NAME_DATA, FLUID_DOMAIN_EXTENSION_OPENVDB);
BLI_path_frame(file_name, sizeof(file_name), frame, 0);
BLI_path_join(file_path, sizeof(file_path), directory.c_str(), FLUID_DOMAIN_DIR_DATA, file_name);
return file_path;
}
} // namespace blender::io::hydra

@ -0,0 +1,32 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include "DNA_fluid_types.h"
#include "volume.h"
namespace blender::io::hydra {
class VolumeModifierData : public VolumeData {
public:
VolumeModifierData(HydraSceneDelegate *scene_delegate,
Object *object,
pxr::SdfPath const &prim_id);
static bool is_volume_modifier(Object *object);
void init() override;
void update() override;
protected:
void write_transform() override;
private:
std::string get_cached_file_path(std::string directory, int frame);
FluidModifierData *modifier_;
};
} // namespace blender::io::hydra

@ -0,0 +1,156 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include "world.h"
#include <pxr/base/gf/rotation.h>
#include <pxr/base/gf/vec2f.h>
#include <pxr/base/vt/array.h>
#include <pxr/imaging/hd/light.h>
#include <pxr/imaging/hd/renderDelegate.h>
#include <pxr/imaging/hd/tokens.h>
#include <pxr/usd/usdLux/tokens.h>
#include "DNA_node_types.h"
#include "DNA_scene_types.h"
#include "BLI_math_rotation.h"
#include "BLI_path_util.h"
#include "BKE_node.h"
#include "BKE_node_runtime.hh"
#include "BKE_studiolight.h"
#include "NOD_shader.h"
#include "hydra_scene_delegate.h"
#include "image.h"
/* TODO : add custom tftoken "transparency"? */
/* NOTE: opacity and blur aren't supported by USD */
namespace blender::io::hydra {
WorldData::WorldData(HydraSceneDelegate *scene_delegate, pxr::SdfPath const &prim_id)
: LightData(scene_delegate, nullptr, prim_id)
{
prim_type_ = pxr::HdPrimTypeTokens->domeLight;
}
void WorldData::init()
{
data_.clear();
data_[pxr::UsdLuxTokens->orientToStageUpAxis] = true;
float intensity = 1.0f;
float exposure = 1.0f;
pxr::GfVec3f color(1.0f, 1.0f, 1.0f);
pxr::SdfAssetPath texture_file;
if (scene_delegate_->shading_settings.use_scene_world) {
World *world = scene_delegate_->scene->world;
ID_LOG(1, "%s", world->id.name);
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;
}
bNode *input_node = link->fromnode;
if (input_node->type != SH_NODE_BACKGROUND) {
return;
}
const bNodeSocket &color_input = input_node->input_by_identifier("Color");
const bNodeSocket &strength_input = input_node->input_by_identifier("Strength");
float const *strength = strength_input.default_value_typed<float>();
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 (ELEM(color_input_node->type, SH_NODE_TEX_IMAGE, SH_NODE_TEX_ENVIRONMENT)) {
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(
scene_delegate_->bmain, scene_delegate_->scene, image, &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 {
ID_LOG(1, "studiolight: %s", 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);
/* coefficient to follow Cycles result */
intensity = scene_delegate_->shading_settings.studiolight_intensity / 2;
}
}
data_[pxr::HdLightTokens->intensity] = intensity;
data_[pxr::HdLightTokens->exposure] = exposure;
data_[pxr::HdLightTokens->color] = color;
data_[pxr::HdLightTokens->textureFile] = texture_file;
write_transform();
}
void WorldData::update()
{
ID_LOG(1, "");
init();
scene_delegate_->GetRenderIndex().GetChangeTracker().MarkSprimDirty(prim_id,
pxr::HdLight::AllDirty);
}
void WorldData::write_transform()
{
transform = pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(1.0, 0.0, 0.0), 90.0)) *
pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(0.0, 0.0, 1.0), 90.0));
if (!scene_delegate_->shading_settings.use_scene_world) {
transform *= pxr::GfMatrix4d().SetRotate(
pxr::GfRotation(pxr::GfVec3d(0.0, 0.0, -1.0),
RAD2DEGF(scene_delegate_->shading_settings.studiolight_rotation)));
}
}
} // namespace blender::io::hydra

@ -0,0 +1,32 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include <map>
#include <pxr/base/gf/matrix4d.h>
#include <pxr/base/tf/staticTokens.h>
#include <pxr/base/vt/value.h>
#include <pxr/usd/sdf/assetPath.h>
#include <pxr/usd/sdf/path.h>
#include "DNA_view3d_types.h"
#include "DNA_world_types.h"
#include "light.h"
namespace blender::io::hydra {
class WorldData : public LightData {
public:
WorldData(HydraSceneDelegate *scene_delegate, pxr::SdfPath const &prim_id);
void init() override;
void update() override;
protected:
void write_transform() override;
};
} // namespace blender::io::hydra

@ -3,6 +3,7 @@
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "usd.h"
#include "usd.hh"
#include "usd_hierarchy_iterator.h"
#include <pxr/base/plug/registry.h>
@ -198,67 +199,41 @@ static bool perform_usdz_conversion(const ExportJobData *data)
return true;
}
static void export_startjob(void *customdata,
/* Cannot be const, this function implements wm_jobs_start_callback.
* NOLINTNEXTLINE: readability-non-const-parameter. */
bool *stop,
bool *do_update,
float *progress)
static pxr::UsdStageRefPtr export_to_stage(const USDExportParams &params,
Depsgraph *depsgraph,
const char *filepath,
bool *stop,
bool *do_update,
float *progress)
{
ExportJobData *data = static_cast<ExportJobData *>(customdata);
data->export_ok = false;
data->start_time = timeit::Clock::now();
G.is_rendering = true;
if (data->wm) {
WM_set_locked_interface(data->wm, true);
}
G.is_break = false;
/* Construct the depsgraph for exporting. */
Scene *scene = DEG_get_input_scene(data->depsgraph);
if (data->params.visible_objects_only) {
DEG_graph_build_from_view_layer(data->depsgraph);
}
else {
DEG_graph_build_for_all_objects(data->depsgraph);
}
BKE_scene_graph_update_tagged(data->depsgraph, data->bmain);
*progress = 0.0f;
*do_update = true;
/* For restoring the current frame after exporting animation is done. */
const int orig_frame = scene->r.cfra;
pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(data->unarchived_filepath);
pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(filepath);
if (!usd_stage) {
/* This happens when the USD JSON files cannot be found. When that happens,
* the USD library doesn't know it has the functionality to write USDA and
* USDC files, and creating a new UsdStage fails. */
WM_reportf(RPT_ERROR,
"USD Export: unable to find suitable USD plugin to write %s",
data->unarchived_filepath);
return;
return usd_stage;
}
usd_stage->SetMetadata(pxr::UsdGeomTokens->upAxis, pxr::VtValue(pxr::UsdGeomTokens->z));
Scene *scene = DEG_get_input_scene(depsgraph);
Main *bmain = DEG_get_bmain(depsgraph);
usd_stage->SetMetadata(pxr::UsdGeomTokens->metersPerUnit, double(scene->unit.scale_length));
usd_stage->GetRootLayer()->SetDocumentation(std::string("Blender v") +
BKE_blender_version_string());
/* Set up the stage for animated data. */
if (data->params.export_animation) {
if (params.export_animation) {
usd_stage->SetTimeCodesPerSecond(FPS);
usd_stage->SetStartTimeCode(scene->r.sfra);
usd_stage->SetEndTimeCode(scene->r.efra);
}
ensure_root_prim(usd_stage, data->params);
/* For restoring the current frame after exporting animation is done. */
const int orig_frame = scene->r.cfra;
USDHierarchyIterator iter(data->bmain, data->depsgraph, usd_stage, data->params);
usd_stage->SetMetadata(pxr::UsdGeomTokens->upAxis, pxr::VtValue(pxr::UsdGeomTokens->z));
ensure_root_prim(usd_stage, params);
if (data->params.export_animation) {
USDHierarchyIterator iter(bmain, depsgraph, usd_stage, params);
if (params.export_animation) {
/* Writing the animated frames is not 100% of the work, but it's our best guess. */
float progress_per_frame = 1.0f / std::max(1, (scene->r.efra - scene->r.sfra + 1));
@ -270,13 +245,17 @@ static void export_startjob(void *customdata,
/* Update the scene for the next frame to render. */
scene->r.cfra = int(frame);
scene->r.subframe = frame - scene->r.cfra;
BKE_scene_graph_update_for_newframe(data->depsgraph);
BKE_scene_graph_update_for_newframe(depsgraph);
iter.set_export_frame(frame);
iter.iterate_and_write();
*progress += progress_per_frame;
*do_update = true;
if (progress) {
*progress += progress_per_frame;
}
if (do_update) {
*do_update = true;
}
}
}
else {
@ -296,14 +275,65 @@ static void export_startjob(void *customdata,
}
}
usd_stage->GetRootLayer()->Save();
/* Finish up by going back to the keyframe that was current before we started. */
if (scene->r.cfra != orig_frame) {
scene->r.cfra = orig_frame;
BKE_scene_graph_update_for_newframe(data->depsgraph);
BKE_scene_graph_update_for_newframe(depsgraph);
}
return usd_stage;
}
pxr::UsdStageRefPtr export_to_stage(const USDExportParams &params,
Depsgraph *depsgraph,
const char *filepath)
{
return export_to_stage(params, depsgraph, filepath, nullptr, nullptr, nullptr);
}
static void export_startjob(void *customdata,
/* Cannot be const, this function implements wm_jobs_start_callback.
* NOLINTNEXTLINE: readability-non-const-parameter. */
bool *stop,
bool *do_update,
float *progress)
{
ExportJobData *data = static_cast<ExportJobData *>(customdata);
data->export_ok = false;
data->start_time = timeit::Clock::now();
G.is_rendering = true;
if (data->wm) {
WM_set_locked_interface(data->wm, true);
}
G.is_break = false;
/* Construct the depsgraph for exporting. */
if (data->params.visible_objects_only) {
DEG_graph_build_from_view_layer(data->depsgraph);
}
else {
DEG_graph_build_for_all_objects(data->depsgraph);
}
BKE_scene_graph_update_tagged(data->depsgraph, data->bmain);
*progress = 0.0f;
*do_update = true;
pxr::UsdStageRefPtr usd_stage = export_to_stage(
data->params, data->depsgraph, data->unarchived_filepath, stop, do_update, progress);
if (!usd_stage) {
/* This happens when the USD JSON files cannot be found. When that happens,
* the USD library doesn't know it has the functionality to write USDA and
* USDC files, and creating a new UsdStage fails. */
WM_reportf(RPT_ERROR,
"USD Export: unable to find suitable USD plugin to write %s",
data->unarchived_filepath);
return;
}
usd_stage->GetRootLayer()->Save();
if (data->targets_usdz()) {
bool usd_conversion_success = perform_usdz_conversion(data);
if (!usd_conversion_success) {

@ -0,0 +1,18 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <pxr/usd/usd/stage.h>
struct Depsgraph;
struct USDExportParams;
namespace blender::io::usd {
pxr::UsdStageRefPtr export_to_stage(const USDExportParams &params,
Depsgraph *depsgraph,
const char *filepath);
};

@ -1010,6 +1010,14 @@ static void rna_def_render_engine(BlenderRNA *brna)
RNA_define_verify_sdna(true);
}
static void rna_def_hydra_render_engine(BlenderRNA *brna)
{
/* This is implemented in Python. */
StructRNA *srna = RNA_def_struct(brna, "HydraRenderEngine", "RenderEngine");
RNA_def_struct_sdna(srna, "RenderEngine");
RNA_def_struct_ui_text(srna, "Hydra Render Engine", "Base class from USD Hydra based renderers");
}
static void rna_def_render_result(BlenderRNA *brna)
{
StructRNA *srna;
@ -1238,6 +1246,7 @@ static void rna_def_render_pass(BlenderRNA *brna)
void RNA_def_render(BlenderRNA *brna)
{
rna_def_render_engine(brna);
rna_def_hydra_render_engine(brna);
rna_def_render_result(brna);
rna_def_render_view(brna);
rna_def_render_layer(brna);

@ -364,6 +364,13 @@ if(WITH_HARU)
add_definitions(-DWITH_HARU)
endif()
if(WITH_HYDRA)
list(APPEND LIB
bf_render_hydra
)
add_definitions(-DWITH_HYDRA)
endif()
blender_add_lib(bf_python "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
# RNA_prototypes.h

@ -257,6 +257,11 @@ static PyObject *CCL_initPython()
}
#endif
#ifdef WITH_HYDRA
/* defined in render_hydra module */
PyObject *BPyInit_hydra();
#endif
static _inittab bpy_internal_modules[] = {
{"mathutils", PyInit_mathutils},
#if 0
@ -286,6 +291,9 @@ static _inittab bpy_internal_modules[] = {
#endif
{"gpu", BPyInit_gpu},
{"idprop", BPyInit_idprop},
#ifdef WITH_HYDRA
{"_bpy_hydra", BPyInit_hydra},
#endif
{nullptr, nullptr},
};

@ -93,3 +93,7 @@ endif()
blender_add_lib_nolist(bf_render "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
if(WITH_HYDRA)
add_subdirectory(hydra)
endif()

@ -0,0 +1,96 @@
# SPDX-FileCopyrightText: 2011-2022 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
# This suppresses the warning "This file includes at least one deprecated or antiquated
# header which may be removed without further notice at a future date", which is caused
# by the USD library including <ext/hash_set> on Linux. This has been reported at:
# https://github.com/PixarAnimationStudios/USD/issues/1057.
if(UNIX AND NOT APPLE)
add_definitions(-D_GLIBCXX_PERMIT_BACKWARD_HASH)
endif()
if(WIN32)
add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN -DBOOST_DEBUG_PYTHON)
endif()
add_definitions(-DBOOST_ALL_NO_LIB)
# Precompiled Linux libs are made with GCC, and USD uses some extensions
# which lead to an incompatible ABI for Clang. Using those extensions with
# Clang as well works around the issue.
if(UNIX AND NOT APPLE)
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
if(EXISTS ${LIBDIR})
add_definitions(-DARCH_HAS_GNU_STL_EXTENSIONS)
endif()
endif()
endif()
# USD headers use deprecated TBB headers, silence warning.
add_definitions(-DTBB_SUPPRESS_DEPRECATED_MESSAGES=1)
if(WIN32)
# Some USD library headers trigger the "unreferenced formal parameter"
# warning alert.
# Silence them by restore warn C4100 back to w4
remove_cc_flag("/w34100")
endif()
set(INC
../../../../intern/clog
../../../../intern/guardedalloc
../../makesdna
../../makesrna
../../nodes
../../blenlib
../../depsgraph
../../blenkernel
../../imbuf
../../io/usd
../../gpu
../../gpu/intern
../../python/intern
# RNA_prototypes.h
${CMAKE_BINARY_DIR}/source/blender/makesrna
..
)
set(INC_SYS
${PYTHON_INCLUDE_DIRS}
${Epoxy_INCLUDE_DIRS}
${USD_INCLUDE_DIRS}
${BOOST_INCLUDE_DIR}
${TBB_INCLUDE_DIR}
${GFLAGS_INCLUDE_DIRS}
${EIGEN3_INCLUDE_DIRS}
)
set(LIB
${Epoxy_LIBRARIES}
${PYTHON_LIBRARIES}
${BOOST_LIBRARIES}
${USD_LIBRARIES}
${TBB_LIBRARIES}
bf_usd
)
set(SRC
engine.cc
final_engine.cc
light_tasks_delegate.cc
preview_engine.cc
python.cc
render_task_delegate.cc
viewport_engine.cc
engine.h
final_engine.h
light_tasks_delegate.h
preview_engine.h
render_task_delegate.h
viewport_engine.h
)
blender_add_lib(bf_render_hydra "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
# RNA_prototypes.h
add_dependencies(bf_render_hydra bf_rna)

@ -0,0 +1,124 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include "engine.h"
#include <pxr/base/plug/plugin.h>
#include <pxr/base/plug/registry.h>
#include <pxr/imaging/hd/rendererPluginRegistry.h>
#include <pxr/imaging/hdSt/renderDelegate.h>
#include <pxr/imaging/hgi/tokens.h>
#include <pxr/usd/usdGeom/tokens.h>
#include "BLI_path_util.h"
#include "BKE_context.h"
#include "GPU_context.h"
#include "DEG_depsgraph_query.h"
#include "RE_engine.h"
#include "CLG_log.h"
namespace blender::render::hydra {
CLG_LOGREF_DECLARE_GLOBAL(LOG_HYDRA_RENDER, "hydra.render");
Engine::Engine(RenderEngine *bl_engine, const std::string &render_delegate_name)
: render_delegate_name_(render_delegate_name), bl_engine_(bl_engine)
{
pxr::HdRendererPluginRegistry &registry = pxr::HdRendererPluginRegistry::GetInstance();
pxr::TF_PY_ALLOW_THREADS_IN_SCOPE();
if (GPU_backend_get_type() == GPU_BACKEND_VULKAN) {
BLI_setenv("HGI_ENABLE_VULKAN", "1");
}
pxr::HdDriverVector hd_drivers;
if (bl_engine->type->flag & RE_USE_GPU_CONTEXT) {
hgi_ = pxr::Hgi::CreatePlatformDefaultHgi();
hgi_driver_.name = pxr::HgiTokens->renderDriver;
hgi_driver_.driver = pxr::VtValue(hgi_.get());
hd_drivers.push_back(&hgi_driver_);
}
render_delegate_ = registry.CreateRenderDelegate(pxr::TfToken(render_delegate_name_));
if (!render_delegate_) {
throw std::runtime_error("Cannot create render delegate: " + render_delegate_name_);
}
render_index_.reset(pxr::HdRenderIndex::New(render_delegate_.Get(), hd_drivers));
free_camera_delegate_ = std::make_unique<pxr::HdxFreeCameraSceneDelegate>(
render_index_.get(), pxr::SdfPath::AbsoluteRootPath().AppendElementString("freeCamera"));
if (bl_engine->type->flag & RE_USE_GPU_CONTEXT && GPU_backend_get_type() == GPU_BACKEND_OPENGL) {
render_task_delegate_ = std::make_unique<GPURenderTaskDelegate>(
render_index_.get(), pxr::SdfPath::AbsoluteRootPath().AppendElementString("renderTask"));
}
else {
render_task_delegate_ = std::make_unique<RenderTaskDelegate>(
render_index_.get(), pxr::SdfPath::AbsoluteRootPath().AppendElementString("renderTask"));
}
render_task_delegate_->set_camera(free_camera_delegate_->GetCameraId());
if (render_delegate_name_ == "HdStormRendererPlugin") {
light_tasks_delegate_ = std::make_unique<LightTasksDelegate>(
render_index_.get(), pxr::SdfPath::AbsoluteRootPath().AppendElementString("lightTasks"));
light_tasks_delegate_->set_camera(free_camera_delegate_->GetCameraId());
}
engine_ = std::make_unique<pxr::HdEngine>();
}
void Engine::sync(Depsgraph *depsgraph, bContext *context)
{
depsgraph_ = depsgraph;
context_ = context;
scene_ = DEG_get_evaluated_scene(depsgraph);
if (!hydra_scene_delegate_) {
pxr::SdfPath scene_path = pxr::SdfPath::AbsoluteRootPath().AppendElementString("scene");
hydra_scene_delegate_ = std::make_unique<io::hydra::HydraSceneDelegate>(render_index_.get(),
scene_path);
}
hydra_scene_delegate_->populate(depsgraph, context ? CTX_wm_view3d(context) : nullptr);
}
void Engine::set_render_setting(const std::string &key, const pxr::VtValue &val)
{
render_delegate_->SetRenderSetting(pxr::TfToken(key), val);
}
float Engine::renderer_percent_done()
{
pxr::VtDictionary render_stats = render_delegate_->GetRenderStats();
auto it = render_stats.find("percentDone");
if (it == render_stats.end()) {
return 0.0f;
}
return (float)it->second.UncheckedGet<double>();
}
pxr::HdTaskSharedPtrVector Engine::tasks()
{
pxr::HdTaskSharedPtrVector res;
if (light_tasks_delegate_) {
if (scene_->r.alphamode != R_ALPHAPREMUL) {
#ifndef __APPLE__
/* TODO: Temporary disable skydome task for MacOS due to crash with error:
* Failed to created pipeline state, error depthAttachmentPixelFormat is not valid
* and shader writes to depth */
res.push_back(light_tasks_delegate_->skydome_task());
#endif
}
res.push_back(light_tasks_delegate_->simple_task());
}
res.push_back(render_task_delegate_->task());
return res;
}
} // namespace blender::render::hydra

@ -0,0 +1,69 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include <memory>
#include <string>
#include <pxr/imaging/hd/driver.h>
#include <pxr/imaging/hd/engine.h>
#include <pxr/imaging/hd/pluginRenderDelegateUniqueHandle.h>
#include <pxr/imaging/hdx/freeCameraSceneDelegate.h>
#include <pxr/imaging/hgi/hgi.h>
#include <pxr/usd/usd/stage.h>
#include <pxr/usdImaging/usdImaging/delegate.h>
#include "hydra/hydra_scene_delegate.h"
#include "hydra/settings.h"
#include "light_tasks_delegate.h"
#include "render_task_delegate.h"
struct bContext;
struct RenderEngine;
struct CLG_LogRef;
namespace blender::render::hydra {
extern struct CLG_LogRef *LOG_HYDRA_RENDER;
class Engine {
protected:
std::string render_delegate_name_;
RenderEngine *bl_engine_ = nullptr;
Depsgraph *depsgraph_ = nullptr;
bContext *context_ = nullptr;
Scene *scene_ = nullptr;
/* The order is important due to deletion order */
pxr::HgiUniquePtr hgi_;
pxr::HdDriver hgi_driver_;
pxr::HdPluginRenderDelegateUniqueHandle render_delegate_;
std::unique_ptr<pxr::HdRenderIndex> render_index_;
std::unique_ptr<io::hydra::HydraSceneDelegate> hydra_scene_delegate_;
std::unique_ptr<RenderTaskDelegate> render_task_delegate_;
std::unique_ptr<pxr::HdxFreeCameraSceneDelegate> free_camera_delegate_;
std::unique_ptr<LightTasksDelegate> light_tasks_delegate_;
std::unique_ptr<pxr::HdEngine> engine_;
public:
Engine(RenderEngine *bl_engine, const std::string &render_delegate_name);
virtual ~Engine() = default;
void sync(Depsgraph *depsgraph, bContext *context);
virtual void render() = 0;
virtual void set_render_setting(const std::string &key, const pxr::VtValue &val);
protected:
float renderer_percent_done();
pxr::HdTaskSharedPtrVector tasks();
virtual void notify_status(float progress,
const std::string &title,
const std::string &info) = 0;
};
} // namespace blender::render::hydra

@ -0,0 +1,137 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include "final_engine.h"
#include <pxr/imaging/hd/light.h>
#include <pxr/imaging/hd/renderBuffer.h>
#include "DNA_scene_types.h"
#include "BLI_timecode.h"
#include "PIL_time.h"
#include "BKE_lib_id.h"
#include "DEG_depsgraph_query.h"
#include "IMB_imbuf_types.h"
#include "RE_engine.h"
#include "hydra/camera.h"
namespace blender::render::hydra {
void FinalEngine::render()
{
const ViewLayer *view_layer = DEG_get_evaluated_view_layer(depsgraph_);
char scene_name[MAX_ID_FULL_NAME];
BKE_id_full_name_get(scene_name, &scene_->id, 0);
const RenderData &r = scene_->r;
pxr::GfVec4f border(0, 0, 1, 1);
if (r.mode & R_BORDER) {
border.Set(r.border.xmin,
r.border.ymin,
r.border.xmax - r.border.xmin,
r.border.ymax - r.border.ymin);
}
pxr::GfVec2i image_res(r.xsch * r.size / 100, r.ysch * r.size / 100);
int width = image_res[0] * border[2];
int height = image_res[1] * border[3];
pxr::GfCamera camera =
io::hydra::CameraData(scene_->camera, image_res, pxr::GfVec4f(0, 0, 1, 1)).gf_camera(border);
free_camera_delegate_->SetCamera(camera);
render_task_delegate_->set_viewport(pxr::GfVec4d(0, 0, width, height));
if (light_tasks_delegate_) {
light_tasks_delegate_->set_viewport(pxr::GfVec4d(0, 0, width, height));
}
RenderResult *rr = RE_engine_get_result(bl_engine_);
RenderLayer *rlayer = (RenderLayer *)rr->layers.first;
LISTBASE_FOREACH (RenderPass *, rpass, &rlayer->passes) {
pxr::TfToken *aov_token = aov_tokens_.lookup_ptr(rpass->name);
if (!aov_token) {
CLOG_WARN(LOG_HYDRA_RENDER, "Couldn't find AOV token for render pass: %s", rpass->name);
continue;
}
render_task_delegate_->add_aov(*aov_token);
}
if (bl_engine_->type->flag & RE_USE_GPU_CONTEXT) {
/* For GPU context engine color and depth AOVs has to be added anyway */
render_task_delegate_->add_aov(pxr::HdAovTokens->color);
render_task_delegate_->add_aov(pxr::HdAovTokens->depth);
}
render_task_delegate_->bind();
auto t = tasks();
engine_->Execute(render_index_.get(), &t);
char elapsed_time[32];
double time_begin = PIL_check_seconds_timer();
float percent_done = 0.0;
while (true) {
if (RE_engine_test_break(bl_engine_)) {
break;
}
percent_done = renderer_percent_done();
BLI_timecode_string_from_time_simple(
elapsed_time, sizeof(elapsed_time), PIL_check_seconds_timer() - time_begin);
notify_status(percent_done / 100.0,
std::string(scene_name) + ": " + view_layer->name,
std::string("Render Time: ") + elapsed_time +
" | Done: " + std::to_string(int(percent_done)) + "%");
if (render_task_delegate_->is_converged()) {
break;
}
update_render_result(width, height, view_layer->name);
}
update_render_result(width, height, view_layer->name);
render_task_delegate_->unbind();
}
void FinalEngine::set_render_setting(const std::string &key, const pxr::VtValue &val)
{
if (STRPREFIX(key.c_str(), "aovToken:")) {
aov_tokens_.add_overwrite(key.substr(key.find(":") + 1),
pxr::TfToken(val.UncheckedGet<std::string>()));
return;
}
Engine::set_render_setting(key, val);
}
void FinalEngine::notify_status(float progress, const std::string &title, const std::string &info)
{
RE_engine_update_progress(bl_engine_, progress);
RE_engine_update_stats(bl_engine_, title.c_str(), info.c_str());
}
void FinalEngine::update_render_result(int width, int height, const char *layer_name)
{
RenderResult *rr = RE_engine_begin_result(bl_engine_, 0, 0, width, height, layer_name, nullptr);
RenderLayer *rlayer = static_cast<RenderLayer *>(
BLI_findstring(&rr->layers, layer_name, offsetof(RenderLayer, name)));
if (rlayer) {
LISTBASE_FOREACH (RenderPass *, rpass, &rlayer->passes) {
pxr::TfToken *aov_token = aov_tokens_.lookup_ptr(rpass->name);
if (aov_token) {
render_task_delegate_->read_aov(*aov_token, rpass->ibuf->float_buffer.data);
}
}
}
RE_engine_end_result(bl_engine_, rr, false, false, false);
}
} // namespace blender::render::hydra

@ -0,0 +1,27 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include "engine.h"
namespace blender::render::hydra {
class FinalEngine : public Engine {
private:
Map<std::string, pxr::TfToken> aov_tokens_;
public:
using Engine::Engine;
void render() override;
void set_render_setting(const std::string &key, const pxr::VtValue &val) override;
protected:
void notify_status(float progress, const std::string &title, const std::string &info) override;
private:
void update_render_result(int width, int height, const char *layer_name);
};
} // namespace blender::render::hydra

@ -0,0 +1,73 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include "light_tasks_delegate.h"
#include "engine.h"
namespace blender::render::hydra {
LightTasksDelegate::LightTasksDelegate(pxr::HdRenderIndex *parent_index,
pxr::SdfPath const &delegate_id)
: pxr::HdSceneDelegate(parent_index, delegate_id)
{
simple_task_id_ = GetDelegateID().AppendElementString("simpleTask");
GetRenderIndex().InsertTask<pxr::HdxSimpleLightTask>(this, simple_task_id_);
skydome_task_id_ = GetDelegateID().AppendElementString("skydomeTask");
GetRenderIndex().InsertTask<pxr::HdxSkydomeTask>(this, skydome_task_id_);
CLOG_INFO(LOG_HYDRA_RENDER, 1, "%s", simple_task_id_.GetText());
CLOG_INFO(LOG_HYDRA_RENDER, 1, "%s", skydome_task_id_.GetText());
}
pxr::VtValue LightTasksDelegate::Get(pxr::SdfPath const &id, pxr::TfToken const &key)
{
CLOG_INFO(LOG_HYDRA_RENDER, 3, "%s, %s", id.GetText(), key.GetText());
if (key == pxr::HdTokens->params) {
if (id == simple_task_id_) {
return pxr::VtValue(simple_task_params_);
}
else if (id == skydome_task_id_) {
return pxr::VtValue(skydome_task_params_);
}
}
return pxr::VtValue();
}
pxr::HdTaskSharedPtr LightTasksDelegate::simple_task()
{
return GetRenderIndex().GetTask(simple_task_id_);
}
pxr::HdTaskSharedPtr LightTasksDelegate::skydome_task()
{
/* Note that this task is intended to be the first "Render Task",
* so that the AOV's are properly cleared, however it
* does not spawn a HdRenderPass. */
return GetRenderIndex().GetTask(skydome_task_id_);
}
void LightTasksDelegate::set_camera(pxr::SdfPath const &camera_id)
{
if (simple_task_params_.cameraPath == camera_id) {
return;
}
simple_task_params_.cameraPath = camera_id;
GetRenderIndex().GetChangeTracker().MarkTaskDirty(simple_task_id_,
pxr::HdChangeTracker::DirtyParams);
skydome_task_params_.camera = camera_id;
GetRenderIndex().GetChangeTracker().MarkTaskDirty(skydome_task_id_,
pxr::HdChangeTracker::DirtyParams);
}
void LightTasksDelegate::set_viewport(pxr::GfVec4d const &viewport)
{
if (skydome_task_params_.viewport == viewport) {
return;
}
skydome_task_params_.viewport = viewport;
GetRenderIndex().GetChangeTracker().MarkTaskDirty(skydome_task_id_,
pxr::HdChangeTracker::DirtyParams);
}
} // namespace blender::render::hydra

@ -0,0 +1,32 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include <pxr/imaging/hd/sceneDelegate.h>
#include <pxr/imaging/hdx/simpleLightTask.h>
#include <pxr/imaging/hdx/skydomeTask.h>
namespace blender::render::hydra {
class LightTasksDelegate : public pxr::HdSceneDelegate {
public:
LightTasksDelegate(pxr::HdRenderIndex *parentIndex, pxr::SdfPath const &delegate_id);
~LightTasksDelegate() override = default;
/* Delegate methods */
pxr::VtValue Get(pxr::SdfPath const &id, pxr::TfToken const &key) override;
pxr::HdTaskSharedPtr simple_task();
pxr::HdTaskSharedPtr skydome_task();
void set_camera(pxr::SdfPath const &camera_id);
void set_viewport(pxr::GfVec4d const &viewport);
private:
pxr::SdfPath simple_task_id_;
pxr::SdfPath skydome_task_id_;
pxr::HdxSimpleLightTaskParams simple_task_params_;
pxr::HdxRenderTaskParams skydome_task_params_;
};
} // namespace blender::render::hydra

@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include "preview_engine.h"
namespace blender::render::hydra {
void PreviewEngine::notify_status(float /* progress */,
const std::string & /* title */,
const std::string & /* info */)
{
/* Empty fucntion */
}
} // namespace blender::render::hydra

@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include "final_engine.h"
namespace blender::render::hydra {
class PreviewEngine : public FinalEngine {
public:
using FinalEngine::FinalEngine;
protected:
void notify_status(float progress, const std::string &title, const std::string &info) override;
};
} // namespace blender::render::hydra

@ -0,0 +1,213 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include "final_engine.h"
#include "preview_engine.h"
#include "viewport_engine.h"
#include <Python.h>
#include "RE_engine.h"
#include "bpy_rna.h"
#include "BKE_context.h"
#include "RE_engine.h"
#include "RNA_prototypes.h"
#include "hydra/image.h"
namespace blender::render::hydra {
template<typename T> T *pyrna_to_pointer(PyObject *pyobject, const StructRNA *rnatype)
{
const PointerRNA *ptr = pyrna_struct_as_ptr_or_null(pyobject, rnatype);
return (ptr) ? static_cast<T *>(ptr->data) : nullptr;
}
static PyObject *engine_create_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pyengine;
char *engine_type, *render_delegate_id;
if (!PyArg_ParseTuple(args, "Oss", &pyengine, &engine_type, &render_delegate_id)) {
Py_RETURN_NONE;
}
RenderEngine *bl_engine = pyrna_to_pointer<RenderEngine>(pyengine, &RNA_RenderEngine);
CLOG_INFO(LOG_HYDRA_RENDER, 1, "Engine %s", engine_type);
Engine *engine = nullptr;
try {
if (STREQ(engine_type, "VIEWPORT")) {
engine = new ViewportEngine(bl_engine, render_delegate_id);
}
else if (STREQ(engine_type, "PREVIEW")) {
engine = new PreviewEngine(bl_engine, render_delegate_id);
}
else {
engine = new FinalEngine(bl_engine, render_delegate_id);
}
}
catch (std::runtime_error &e) {
CLOG_ERROR(LOG_HYDRA_RENDER, "%s", e.what());
}
CLOG_INFO(LOG_HYDRA_RENDER, 1, "Engine %p", engine);
return PyLong_FromVoidPtr(engine);
}
static PyObject *engine_free_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pyengine;
if (!PyArg_ParseTuple(args, "O", &pyengine)) {
Py_RETURN_NONE;
}
Engine *engine = static_cast<Engine *>(PyLong_AsVoidPtr(pyengine));
CLOG_INFO(LOG_HYDRA_RENDER, 1, "Engine %p", engine);
delete engine;
Py_RETURN_NONE;
}
static PyObject *engine_update_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pyengine, *pydepsgraph, *pycontext;
if (!PyArg_ParseTuple(args, "OOO", &pyengine, &pydepsgraph, &pycontext)) {
Py_RETURN_NONE;
}
Engine *engine = static_cast<Engine *>(PyLong_AsVoidPtr(pyengine));
Depsgraph *depsgraph = pyrna_to_pointer<Depsgraph>(pydepsgraph, &RNA_Depsgraph);
bContext *context = pyrna_to_pointer<bContext>(pycontext, &RNA_Context);
CLOG_INFO(LOG_HYDRA_RENDER, 2, "Engine %p", engine);
engine->sync(depsgraph, context);
Py_RETURN_NONE;
}
static PyObject *engine_render_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pyengine;
if (!PyArg_ParseTuple(args, "O", &pyengine)) {
Py_RETURN_NONE;
}
Engine *engine = static_cast<Engine *>(PyLong_AsVoidPtr(pyengine));
CLOG_INFO(LOG_HYDRA_RENDER, 2, "Engine %p", engine);
/* Allow Blender to execute other Python scripts. */
Py_BEGIN_ALLOW_THREADS;
engine->render();
Py_END_ALLOW_THREADS;
Py_RETURN_NONE;
}
static PyObject *engine_view_draw_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pyengine, *pycontext;
if (!PyArg_ParseTuple(args, "OO", &pyengine, &pycontext)) {
Py_RETURN_NONE;
}
ViewportEngine *engine = static_cast<ViewportEngine *>(PyLong_AsVoidPtr(pyengine));
bContext *context = pyrna_to_pointer<bContext>(pycontext, &RNA_Context);
CLOG_INFO(LOG_HYDRA_RENDER, 3, "Engine %p", engine);
/* Allow Blender to execute other Python scripts. */
Py_BEGIN_ALLOW_THREADS;
engine->render(context);
Py_END_ALLOW_THREADS;
Py_RETURN_NONE;
}
static pxr::VtValue get_setting_val(PyObject *pyval)
{
pxr::VtValue val;
if (PyBool_Check(pyval)) {
val = Py_IsTrue(pyval);
}
else if (PyLong_Check(pyval)) {
val = PyLong_AsLong(pyval);
}
else if (PyFloat_Check(pyval)) {
val = PyFloat_AsDouble(pyval);
}
else if (PyUnicode_Check(pyval)) {
val = std::string(PyUnicode_AsUTF8(pyval));
}
return val;
}
static PyObject *engine_set_render_setting_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pyengine, *pyval;
char *key;
if (!PyArg_ParseTuple(args, "OsO", &pyengine, &key, &pyval)) {
Py_RETURN_NONE;
}
Engine *engine = static_cast<Engine *>(PyLong_AsVoidPtr(pyengine));
CLOG_INFO(LOG_HYDRA_RENDER, 3, "Engine %p: %s", engine, key);
engine->set_render_setting(key, get_setting_val(pyval));
Py_RETURN_NONE;
}
static PyObject *cache_or_get_image_file_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pycontext, *pyimage;
if (!PyArg_ParseTuple(args, "OO", &pycontext, &pyimage)) {
Py_RETURN_NONE;
}
bContext *context = static_cast<bContext *>(PyLong_AsVoidPtr(pycontext));
Image *image = static_cast<Image *>(PyLong_AsVoidPtr(pyimage));
std::string image_path = io::hydra::cache_or_get_image_file(
CTX_data_main(context), CTX_data_scene(context), image, nullptr);
return PyUnicode_FromString(image_path.c_str());
}
static PyMethodDef methods[] = {
{"engine_create", engine_create_func, METH_VARARGS, ""},
{"engine_free", engine_free_func, METH_VARARGS, ""},
{"engine_update", engine_update_func, METH_VARARGS, ""},
{"engine_render", engine_render_func, METH_VARARGS, ""},
{"engine_view_draw", engine_view_draw_func, METH_VARARGS, ""},
{"engine_set_render_setting", engine_set_render_setting_func, METH_VARARGS, ""},
{"cache_or_get_image_file", cache_or_get_image_file_func, METH_VARARGS, ""},
{NULL, NULL, 0, NULL},
};
static struct PyModuleDef module = {
PyModuleDef_HEAD_INIT,
"_bpy_hydra",
"Hydra render API",
-1,
methods,
NULL,
NULL,
NULL,
NULL,
};
} // namespace blender::render::hydra
PyObject *BPyInit_hydra();
PyObject *BPyInit_hydra()
{
PyObject *mod = PyModule_Create(&blender::render::hydra::module);
return mod;
}

@ -0,0 +1,342 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include "render_task_delegate.h"
#include <epoxy/gl.h>
#include "GPU_context.h"
#include <pxr/imaging/hd/renderBuffer.h>
#include <pxr/imaging/hd/renderDelegate.h>
#include <pxr/imaging/hdx/renderTask.h>
#include "MEM_guardedalloc.h"
#include "Eigen/Core"
#include "engine.h"
namespace blender::render::hydra {
RenderTaskDelegate::RenderTaskDelegate(pxr::HdRenderIndex *parent_index,
pxr::SdfPath const &delegate_id)
: pxr::HdSceneDelegate(parent_index, delegate_id)
{
task_id_ = GetDelegateID().AppendElementString("task");
GetRenderIndex().InsertTask<pxr::HdxRenderTask>(this, task_id_);
task_params_.enableLighting = true;
task_params_.alphaThreshold = 0.1f;
CLOG_INFO(LOG_HYDRA_RENDER, 1, "%s", task_id_.GetText());
}
pxr::VtValue RenderTaskDelegate::Get(pxr::SdfPath const &id, pxr::TfToken const &key)
{
CLOG_INFO(LOG_HYDRA_RENDER, 3, "%s, %s", id.GetText(), key.GetText());
if (key == pxr::HdTokens->params) {
return pxr::VtValue(task_params_);
}
if (key == pxr::HdTokens->collection) {
return pxr::VtValue(pxr::HdRprimCollection(
pxr::HdTokens->geometry, pxr::HdReprSelector(pxr::HdReprTokens->smoothHull)));
}
return pxr::VtValue();
}
pxr::TfTokenVector RenderTaskDelegate::GetTaskRenderTags(pxr::SdfPath const &id)
{
CLOG_INFO(LOG_HYDRA_RENDER, 3, "%s", id.GetText());
return {pxr::HdRenderTagTokens->geometry};
}
pxr::HdRenderBufferDescriptor RenderTaskDelegate::GetRenderBufferDescriptor(pxr::SdfPath const &id)
{
CLOG_INFO(LOG_HYDRA_RENDER, 3, "%s", id.GetText());
return buffer_descriptors_[id];
}
pxr::HdTaskSharedPtr RenderTaskDelegate::task()
{
return GetRenderIndex().GetTask(task_id_);
}
void RenderTaskDelegate::set_camera(pxr::SdfPath const &camera_id)
{
if (task_params_.camera == camera_id) {
return;
}
task_params_.camera = camera_id;
GetRenderIndex().GetChangeTracker().MarkTaskDirty(task_id_, pxr::HdChangeTracker::DirtyParams);
}
bool RenderTaskDelegate::is_converged()
{
return static_cast<pxr::HdxRenderTask *>(task().get())->IsConverged();
}
void RenderTaskDelegate::set_viewport(pxr::GfVec4d const &viewport)
{
if (task_params_.viewport == viewport) {
return;
}
auto &render_index = GetRenderIndex();
task_params_.viewport = viewport;
render_index.GetChangeTracker().MarkTaskDirty(task_id_, pxr::HdChangeTracker::DirtyParams);
int w = viewport[2] - viewport[0];
int h = viewport[3] - viewport[1];
for (auto &it : buffer_descriptors_) {
it.second.dimensions = pxr::GfVec3i(w, h, 1);
render_index.GetChangeTracker().MarkBprimDirty(it.first,
pxr::HdRenderBuffer::DirtyDescription);
}
}
void RenderTaskDelegate::add_aov(pxr::TfToken const &aov_key)
{
pxr::SdfPath buf_id = buffer_id(aov_key);
if (buffer_descriptors_.find(buf_id) != buffer_descriptors_.end()) {
return;
}
auto &render_index = GetRenderIndex();
pxr::HdAovDescriptor aov_desc = render_index.GetRenderDelegate()->GetDefaultAovDescriptor(
aov_key);
if (aov_desc.format == pxr::HdFormatInvalid) {
CLOG_ERROR(LOG_HYDRA_RENDER, "Invalid AOV: %s", aov_key.GetText());
return;
}
if (!ELEM(
pxr::HdGetComponentFormat(aov_desc.format), pxr::HdFormatFloat32, pxr::HdFormatFloat16))
{
CLOG_WARN(LOG_HYDRA_RENDER,
"Unsupported data format %s for AOV %s",
pxr::TfEnum::GetName(aov_desc.format).c_str(),
aov_key.GetText());
return;
}
int w = task_params_.viewport[2] - task_params_.viewport[0];
int h = task_params_.viewport[3] - task_params_.viewport[1];
render_index.InsertBprim(pxr::HdPrimTypeTokens->renderBuffer, this, buf_id);
buffer_descriptors_[buf_id] = pxr::HdRenderBufferDescriptor(
pxr::GfVec3i(w, h, 1), aov_desc.format, aov_desc.multiSampled);
pxr::HdRenderPassAovBinding binding;
binding.aovName = aov_key;
binding.renderBufferId = buf_id;
binding.aovSettings = aov_desc.aovSettings;
binding.clearValue = aov_desc.clearValue;
task_params_.aovBindings.push_back(binding);
render_index.GetChangeTracker().MarkTaskDirty(task_id_, pxr::HdChangeTracker::DirtyParams);
CLOG_INFO(LOG_HYDRA_RENDER, 1, "%s", aov_key.GetText());
}
void RenderTaskDelegate::read_aov(pxr::TfToken const &aov_key, void *data)
{
pxr::HdRenderBuffer *buffer = static_cast<pxr::HdRenderBuffer *>(
GetRenderIndex().GetBprim(pxr::HdPrimTypeTokens->renderBuffer, buffer_id(aov_key)));
if (!buffer) {
return;
}
pxr::HdFormat format = buffer->GetFormat();
size_t len = buffer->GetWidth() * buffer->GetHeight() * pxr::HdGetComponentCount(format);
if (pxr::HdGetComponentFormat(format) == pxr::HdFormatFloat32) {
void *buf_data = buffer->Map();
memcpy(data, buf_data, len * sizeof(float));
buffer->Unmap();
}
else if (pxr::HdGetComponentFormat(format) == pxr::HdFormatFloat16) {
Eigen::half *buf_data = (Eigen::half *)buffer->Map();
float *fdata = (float *)data;
for (size_t i = 0; i < len; ++i) {
fdata[i] = buf_data[i];
}
buffer->Unmap();
}
else {
BLI_assert_unreachable();
}
}
void RenderTaskDelegate::read_aov(pxr::TfToken const &aov_key, GPUTexture *texture)
{
pxr::HdRenderBuffer *buffer = (pxr::HdRenderBuffer *)GetRenderIndex().GetBprim(
pxr::HdPrimTypeTokens->renderBuffer, buffer_id(aov_key));
if (!buffer) {
return;
}
eGPUDataFormat format = buffer->GetFormat() == pxr::HdFormat::HdFormatFloat16Vec4 ?
GPU_DATA_HALF_FLOAT :
GPU_DATA_FLOAT;
void *buf_data = buffer->Map();
GPU_texture_update(texture, format, buf_data);
buffer->Unmap();
}
void RenderTaskDelegate::bind() {}
void RenderTaskDelegate::unbind() {}
pxr::SdfPath RenderTaskDelegate::buffer_id(pxr::TfToken const &aov_key) const
{
return GetDelegateID().AppendElementString("aov_" + aov_key.GetString());
}
GPURenderTaskDelegate::~GPURenderTaskDelegate()
{
unbind();
if (tex_color_) {
GPU_texture_free(tex_color_);
}
if (tex_depth_) {
GPU_texture_free(tex_depth_);
}
}
void GPURenderTaskDelegate::set_viewport(pxr::GfVec4d const &viewport)
{
if (task_params_.viewport == viewport) {
return;
}
auto &render_index = GetRenderIndex();
task_params_.viewport = viewport;
render_index.GetChangeTracker().MarkTaskDirty(task_id_, pxr::HdChangeTracker::DirtyParams);
if (tex_color_) {
GPU_texture_free(tex_color_);
tex_color_ = nullptr;
add_aov(pxr::HdAovTokens->color);
}
if (tex_depth_) {
GPU_texture_free(tex_depth_);
tex_depth_ = nullptr;
add_aov(pxr::HdAovTokens->depth);
}
}
void GPURenderTaskDelegate::add_aov(pxr::TfToken const &aov_key)
{
eGPUTextureFormat format;
GPUTexture **tex;
if (aov_key == pxr::HdAovTokens->color) {
format = GPU_RGBA32F;
tex = &tex_color_;
}
else if (aov_key == pxr::HdAovTokens->depth) {
format = GPU_DEPTH_COMPONENT32F;
tex = &tex_depth_;
}
else {
CLOG_ERROR(LOG_HYDRA_RENDER, "Invalid AOV: %s", aov_key.GetText());
return;
}
if (*tex) {
return;
}
*tex = GPU_texture_create_2d(("tex_render_hydra_" + aov_key.GetString()).c_str(),
task_params_.viewport[2] - task_params_.viewport[0],
task_params_.viewport[3] - task_params_.viewport[1],
1,
format,
GPU_TEXTURE_USAGE_GENERAL,
nullptr);
CLOG_INFO(LOG_HYDRA_RENDER, 1, "%s", aov_key.GetText());
}
void GPURenderTaskDelegate::read_aov(pxr::TfToken const &aov_key, void *data)
{
GPUTexture *tex = nullptr;
int c;
if (aov_key == pxr::HdAovTokens->color) {
tex = tex_color_;
c = 4;
}
else if (aov_key == pxr::HdAovTokens->depth) {
tex = tex_depth_;
c = 1;
}
if (!tex) {
return;
}
int w = GPU_texture_width(tex), h = GPU_texture_height(tex);
void *tex_data = GPU_texture_read(tex, GPU_DATA_FLOAT, 0);
memcpy(data, tex_data, sizeof(float) * w * h * c);
MEM_freeN(tex_data);
}
void GPURenderTaskDelegate::read_aov(pxr::TfToken const &aov_key, GPUTexture *texture)
{
GPUTexture *tex = nullptr;
if (aov_key == pxr::HdAovTokens->color) {
tex = tex_color_;
}
else if (aov_key == pxr::HdAovTokens->depth) {
tex = tex_depth_;
}
if (!tex) {
return;
}
void *tex_data = GPU_texture_read(tex, GPU_DATA_FLOAT, 0);
GPU_texture_update(texture, GPU_DATA_FLOAT, tex_data);
MEM_freeN(tex_data);
}
void GPURenderTaskDelegate::bind()
{
if (!framebuffer_) {
framebuffer_ = GPU_framebuffer_create("fb_render_hydra");
}
GPU_framebuffer_ensure_config(
&framebuffer_, {GPU_ATTACHMENT_TEXTURE(tex_depth_), GPU_ATTACHMENT_TEXTURE(tex_color_)});
GPU_framebuffer_bind(framebuffer_);
float clear_color[4] = {0.0f, 0.0f, 0.0f, 0.0f};
GPU_framebuffer_clear_color_depth(framebuffer_, clear_color, 1.0f);
/* Workaround missing/buggy VAOs in hgiGL and hdSt. For OpenGL compatibility
* profile this is not a problem, but for core profile it is. */
if (VAO_ == 0 && GPU_backend_get_type() == GPU_BACKEND_OPENGL) {
glGenVertexArrays(1, &VAO_);
glBindVertexArray(VAO_);
}
CLOG_INFO(LOG_HYDRA_RENDER, 3, "bind");
}
void GPURenderTaskDelegate::unbind()
{
if (VAO_) {
glDeleteVertexArrays(1, &VAO_);
VAO_ = 0;
}
if (framebuffer_) {
GPU_framebuffer_free(framebuffer_);
framebuffer_ = nullptr;
}
CLOG_INFO(LOG_HYDRA_RENDER, 3, "unbind");
}
GPUTexture *GPURenderTaskDelegate::aov_texture(pxr::TfToken const &aov_key)
{
if (aov_key == pxr::HdAovTokens->color) {
return tex_color_;
}
if (aov_key == pxr::HdAovTokens->depth) {
return tex_depth_;
}
return nullptr;
}
} // namespace blender::render::hydra

@ -0,0 +1,66 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include <pxr/imaging/hd/sceneDelegate.h>
#include <pxr/imaging/hdx/renderSetupTask.h>
#include "GPU_framebuffer.h"
#include "GPU_texture.h"
namespace blender::render::hydra {
/* Delegate to create a render task with given camera, viewport and AOVs. */
class RenderTaskDelegate : public pxr::HdSceneDelegate {
protected:
pxr::SdfPath task_id_;
pxr::HdxRenderTaskParams task_params_;
pxr::TfHashMap<pxr::SdfPath, pxr::HdRenderBufferDescriptor, pxr::SdfPath::Hash>
buffer_descriptors_;
public:
RenderTaskDelegate(pxr::HdRenderIndex *parent_index, pxr::SdfPath const &delegate_id);
~RenderTaskDelegate() override = default;
/* Delegate methods */
pxr::VtValue Get(pxr::SdfPath const &id, pxr::TfToken const &key) override;
pxr::TfTokenVector GetTaskRenderTags(pxr::SdfPath const &id) override;
pxr::HdRenderBufferDescriptor GetRenderBufferDescriptor(pxr::SdfPath const &id) override;
pxr::HdTaskSharedPtr task();
void set_camera(pxr::SdfPath const &camera_id);
bool is_converged();
virtual void set_viewport(pxr::GfVec4d const &viewport);
virtual void add_aov(pxr::TfToken const &aov_key);
virtual void read_aov(pxr::TfToken const &aov_key, void *data);
virtual void read_aov(pxr::TfToken const &aov_key, GPUTexture *texture);
virtual void bind();
virtual void unbind();
protected:
pxr::SdfPath buffer_id(pxr::TfToken const &aov_key) const;
};
class GPURenderTaskDelegate : public RenderTaskDelegate {
private:
GPUFrameBuffer *framebuffer_ = nullptr;
GPUTexture *tex_color_ = nullptr;
GPUTexture *tex_depth_ = nullptr;
unsigned int VAO_ = 0;
public:
using RenderTaskDelegate::RenderTaskDelegate;
~GPURenderTaskDelegate() override;
void set_viewport(pxr::GfVec4d const &viewport) override;
void add_aov(pxr::TfToken const &aov_key) override;
void read_aov(pxr::TfToken const &aov_key, void *data) override;
void read_aov(pxr::TfToken const &aov_key, GPUTexture *texture) override;
void bind() override;
void unbind() override;
GPUTexture *aov_texture(pxr::TfToken const &aov_key);
};
} // namespace blender::render::hydra

@ -0,0 +1,295 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include "viewport_engine.h"
#include <pxr/base/gf/camera.h>
#include <pxr/imaging/glf/drawTarget.h>
#include <pxr/usd/usdGeom/camera.h>
#include "DNA_camera_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_vec_types.h" /* this include must be before BKE_camera.h due to "rctf" type */
#include "DNA_view3d_types.h"
#include "BLI_math_matrix.h"
#include "BLI_timecode.h"
#include "PIL_time.h"
#include "BKE_camera.h"
#include "BKE_context.h"
#include "DEG_depsgraph_query.h"
#include "GPU_context.h"
#include "GPU_matrix.h"
#include "RE_engine.h"
#include "hydra/camera.h"
namespace blender::render::hydra {
struct ViewSettings {
ViewSettings(bContext *context);
int width();
int height();
pxr::GfCamera gf_camera();
io::hydra::CameraData camera_data;
int screen_width;
int screen_height;
pxr::GfVec4i border;
};
ViewSettings::ViewSettings(bContext *context)
: camera_data(CTX_wm_view3d(context), CTX_wm_region(context))
{
View3D *view3d = CTX_wm_view3d(context);
RegionView3D *region_data = static_cast<RegionView3D *>(CTX_wm_region_data(context));
ARegion *region = CTX_wm_region(context);
screen_width = region->winx;
screen_height = region->winy;
Scene *scene = CTX_data_scene(context);
/* Getting render border. */
int x1 = 0, y1 = 0;
int x2 = screen_width, y2 = screen_height;
if (region_data->persp == RV3D_CAMOB) {
if (scene->r.mode & R_BORDER) {
Object *camera_obj = scene->camera;
float camera_points[4][3];
BKE_camera_view_frame(scene, static_cast<Camera *>(camera_obj->data), camera_points);
float screen_points[4][2];
for (int i = 0; i < 4; i++) {
float world_location[] = {
camera_points[i][0], camera_points[i][1], camera_points[i][2], 1.0f};
mul_m4_v4(camera_obj->object_to_world, world_location);
mul_m4_v4(region_data->persmat, world_location);
if (world_location[3] > 0.0) {
screen_points[i][0] = screen_width * 0.5f +
screen_width * 0.5f * (world_location[0] / world_location[3]);
screen_points[i][1] = screen_height * 0.5f +
screen_height * 0.5f * (world_location[1] / world_location[3]);
}
}
/* Getting camera view region. */
float x1_f = std::min(
{screen_points[0][0], screen_points[1][0], screen_points[2][0], screen_points[3][0]});
float x2_f = std::max(
{screen_points[0][0], screen_points[1][0], screen_points[2][0], screen_points[3][0]});
float y1_f = std::min(
{screen_points[0][1], screen_points[1][1], screen_points[2][1], screen_points[3][1]});
float y2_f = std::max(
{screen_points[0][1], screen_points[1][1], screen_points[2][1], screen_points[3][1]});
/* Adjusting region to border. */
float x = x1_f, y = y1_f;
float dx = x2_f - x1_f, dy = y2_f - y1_f;
x1 = x + scene->r.border.xmin * dx;
x2 = x + scene->r.border.xmax * dx;
y1 = y + scene->r.border.ymin * dy;
y2 = y + scene->r.border.ymax * dy;
/* Adjusting to region screen resolution. */
x1 = std::max(std::min(x1, screen_width), 0);
x2 = std::max(std::min(x2, screen_width), 0);
y1 = std::max(std::min(y1, screen_height), 0);
y2 = std::max(std::min(y2, screen_height), 0);
}
}
else {
if (view3d->flag2 & V3D_RENDER_BORDER) {
int x = x1, y = y1;
int dx = x2 - x1, dy = y2 - y1;
x1 = int(x + view3d->render_border.xmin * dx);
x2 = int(x + view3d->render_border.xmax * dx);
y1 = int(y + view3d->render_border.ymin * dy);
y2 = int(y + view3d->render_border.ymax * dy);
}
}
border = pxr::GfVec4i(x1, y1, x2 - x1, y2 - y1);
}
int ViewSettings::width()
{
return border[2];
}
int ViewSettings::height()
{
return border[3];
}
pxr::GfCamera ViewSettings::gf_camera()
{
return camera_data.gf_camera(pxr::GfVec4f((float)border[0] / screen_width,
(float)border[1] / screen_height,
(float)border[2] / screen_width,
(float)border[3] / screen_height));
}
DrawTexture::DrawTexture()
{
float coords[8] = {0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0};
GPUVertFormat format = {0};
GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
GPU_vertformat_attr_add(&format, "texCoord", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
GPUVertBuf *vbo = GPU_vertbuf_create_with_format(&format);
GPU_vertbuf_data_alloc(vbo, 4);
GPU_vertbuf_attr_fill(vbo, 0, coords);
GPU_vertbuf_attr_fill(vbo, 1, coords);
batch_ = GPU_batch_create_ex(GPU_PRIM_TRI_FAN, vbo, nullptr, GPU_BATCH_OWNS_VBO);
}
DrawTexture::~DrawTexture()
{
if (texture_) {
GPU_texture_free(texture_);
}
GPU_batch_discard(batch_);
}
void DrawTexture::write_data(int width, int height, const void *data)
{
if (texture_ && width == GPU_texture_width(texture_) && height == GPU_texture_height(texture_)) {
if (data) {
GPU_texture_update(texture_, GPU_DATA_FLOAT, data);
}
return;
}
if (texture_) {
GPU_texture_free(texture_);
}
texture_ = GPU_texture_create_2d("tex_hydra_render_viewport",
width,
height,
1,
GPU_RGBA32F,
GPU_TEXTURE_USAGE_GENERAL,
(float *)data);
}
void DrawTexture::draw(GPUShader *shader, const pxr::GfVec4d &viewport, GPUTexture *tex)
{
if (!tex) {
tex = texture_;
}
int slot = GPU_shader_get_sampler_binding(shader, "image");
GPU_texture_bind(tex, slot);
GPU_shader_uniform_1i(shader, "image", slot);
GPU_matrix_push();
GPU_matrix_translate_2f(viewport[0], viewport[1]);
GPU_matrix_scale_2f(viewport[2] - viewport[0], viewport[3] - viewport[1]);
GPU_batch_set_shader(batch_, shader);
GPU_batch_draw(batch_);
GPU_matrix_pop();
}
GPUTexture *DrawTexture::texture() const
{
return texture_;
}
void ViewportEngine::render()
{
ViewSettings view_settings(context_);
if (view_settings.width() * view_settings.height() == 0) {
return;
};
pxr::GfCamera gf_camera = view_settings.gf_camera();
free_camera_delegate_->SetCamera(gf_camera);
pxr::GfVec4d viewport(view_settings.border[0],
view_settings.border[1],
view_settings.border[2],
view_settings.border[3]);
render_task_delegate_->set_viewport(viewport);
if (light_tasks_delegate_) {
light_tasks_delegate_->set_viewport(viewport);
}
render_task_delegate_->add_aov(pxr::HdAovTokens->color);
render_task_delegate_->add_aov(pxr::HdAovTokens->depth);
GPUFrameBuffer *view_framebuffer = GPU_framebuffer_active_get();
render_task_delegate_->bind();
auto t = tasks();
engine_->Execute(render_index_.get(), &t);
render_task_delegate_->unbind();
GPU_framebuffer_bind(view_framebuffer);
GPUShader *shader = GPU_shader_get_builtin_shader(GPU_SHADER_3D_IMAGE);
GPU_shader_bind(shader);
GPURenderTaskDelegate *gpu_task = dynamic_cast<GPURenderTaskDelegate *>(
render_task_delegate_.get());
if (gpu_task) {
draw_texture_.draw(shader, viewport, gpu_task->aov_texture(pxr::HdAovTokens->color));
}
else {
draw_texture_.write_data(view_settings.width(), view_settings.height(), nullptr);
render_task_delegate_->read_aov(pxr::HdAovTokens->color, draw_texture_.texture());
draw_texture_.draw(shader, viewport);
}
GPU_shader_unbind();
if (renderer_percent_done() == 0.0f) {
time_begin_ = PIL_check_seconds_timer();
}
char elapsed_time[32];
BLI_timecode_string_from_time_simple(
elapsed_time, sizeof(elapsed_time), PIL_check_seconds_timer() - time_begin_);
float percent_done = renderer_percent_done();
if (!render_task_delegate_->is_converged()) {
notify_status(percent_done / 100.0,
std ::string("Time: ") + elapsed_time +
" | Done: " + std::to_string(int(percent_done)) + "%",
"Render");
bl_engine_->flag |= RE_ENGINE_DO_DRAW;
}
else {
notify_status(percent_done / 100.0, std::string("Time: ") + elapsed_time, "Rendering Done");
}
}
void ViewportEngine::render(bContext *context)
{
context_ = context;
render();
}
void ViewportEngine::notify_status(float /*progress*/,
const std::string &info,
const std::string &status)
{
RE_engine_update_stats(bl_engine_, status.c_str(), info.c_str());
}
} // namespace blender::render::hydra

@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include <pxr/imaging/hd/renderBuffer.h>
#include "GPU_batch.h"
#include "GPU_shader.h"
#include "GPU_texture.h"
#include "engine.h"
namespace blender::render::hydra {
class DrawTexture {
private:
GPUTexture *texture_ = nullptr;
GPUBatch *batch_;
public:
DrawTexture();
~DrawTexture();
void write_data(int width, int height, const void *data);
void draw(GPUShader *shader, const pxr::GfVec4d &viewport, GPUTexture *tex = nullptr);
GPUTexture *texture() const;
private:
};
class ViewportEngine : public Engine {
private:
double time_begin_;
DrawTexture draw_texture_;
public:
using Engine::Engine;
void render() override;
void render(bContext *context);
protected:
void notify_status(float progress, const std::string &title, const std::string &info) override;
};
} // namespace blender::render::hydra

@ -853,9 +853,16 @@ static void engine_render_view_layer(Render *re,
/* Sync data to engine, within draw lock so scene data can be accessed safely. */
if (use_engine) {
const bool use_gpu_context = (engine->type->flag & RE_USE_GPU_CONTEXT);
if (use_gpu_context) {
DRW_render_context_enable(engine->re);
}
if (engine->type->update) {
engine->type->update(engine, re->main, engine->depsgraph);
}
if (use_gpu_context) {
DRW_render_context_disable(engine->re);
}
}
if (re->draw_lock) {