Hydra render engine #104712

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

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
65 changed files with 5514 additions and 61 deletions

View File

@ -365,7 +365,10 @@ option(WITH_ALEMBIC "Enable Alembic Support" ON)
option(WITH_USD "Enable Universal Scene Description (USD) Support" ON)
# MaterialX
option(WITH_MATERIALX "Enable MaterialX Support" OFF)
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
@ -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

View File

@ -57,7 +57,8 @@ set(WITH_QUADRIFLOW ON CACHE BOOL "" FORCE)
set(WITH_SDL ON CACHE BOOL "" FORCE)
set(WITH_TBB ON CACHE BOOL "" FORCE)
set(WITH_USD ON CACHE BOOL "" FORCE)
set(WITH_MATERIALX OFF 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)

View File

@ -58,7 +58,8 @@ set(WITH_QUADRIFLOW ON CACHE BOOL "" FORCE)
set(WITH_SDL ON CACHE BOOL "" FORCE)
set(WITH_TBB ON CACHE BOOL "" FORCE)
set(WITH_USD ON CACHE BOOL "" FORCE)
set(WITH_MATERIALX OFF 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)

View File

@ -965,6 +965,10 @@ if(WITH_USD)
endif()
endif()
if(WITH_MATERIALX)
include("${LIBDIR}/MaterialX/lib/cmake/MaterialX/MaterialXTargets.cmake")
endif()
if(WINDOWS_PYTHON_DEBUG)
# Include the system scripts in the blender_python_system_scripts project.
file(GLOB_RECURSE inFiles "${CMAKE_SOURCE_DIR}/scripts/*.*" )

View File

@ -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()

View File

@ -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)

View File

@ -38,7 +38,11 @@ materialx_libs_dir = os.path.abspath(os.path.join(shared_lib_dir, "materialx", "
materialx_libs_env = os.getenv("MATERIALX_SEARCH_PATH")
if materialx_libs_env is None:
os.environ["MATERIALX_SEARCH_PATH"] = materialx_libs_dir
elif sys.platform == "win32":
os.environ["MATERIALX_SEARCH_PATH"] = materialx_libs_dir + ";" + materialx_libs_env
else:
os.environ["MATERIALX_SEARCH_PATH"] = materialx_libs_dir + ":" + materialx_libs_env
os.environ["MATERIALX_SEARCH_PATH"] = materialx_libs_env + os.pathsep + materialx_libs_dir
materialx_libs_env = os.getenv("PXR_MTLX_STDLIB_SEARCH_PATHS")
if materialx_libs_env is None:
os.environ["PXR_MTLX_STDLIB_SEARCH_PATHS"] = materialx_libs_dir
else:
os.environ["PXR_MTLX_STDLIB_SEARCH_PATHS"] = materialx_libs_env + os.pathsep + materialx_libs_dir

View File

@ -1109,6 +1109,25 @@ class RENDER_PT_simplify_greasepencil(RenderButtonsPanel, Panel, GreasePencilSim
bl_options = {'DEFAULT_CLOSED'}
class RENDER_PT_hydra_debug(RenderButtonsPanel, Panel):
bl_label = "Hydra Debug"
bl_options = {'DEFAULT_CLOSED'}
bl_order = 200
COMPAT_ENGINES = {'HYDRA_STORM'}
@classmethod
def poll(cls, context):
prefs = context.preferences
return (context.engine in cls.COMPAT_ENGINES) and prefs.view.show_developer_ui
def draw(self, context):
layout = self.layout
layout.use_property_split = True
hydra = context.scene.hydra
layout.prop(hydra, "export_method")
classes = (
RENDER_PT_context,
RENDER_PT_eevee_sampling,
@ -1151,6 +1170,7 @@ classes = (
RENDER_PT_opengl_color,
RENDER_PT_opengl_options,
RENDER_PT_opengl_film,
RENDER_PT_hydra_debug,
RENDER_PT_color_management,
RENDER_PT_color_management_curves,
RENDER_PT_simplify,

View File

@ -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,40 @@ 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/usd_scene_delegate.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/usd_scene_delegate.h
hydra/volume.h
hydra/volume_modifier.h
hydra/world.h
)
endif()
set(LIB
bf_blenkernel
PRIVATE bf::blenlib
@ -153,6 +204,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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,74 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#include "usd_scene_delegate.h"
#include "BLI_fileops.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BKE_appdir.h"
#include "DEG_depsgraph_query.h"
#include "usd.h"
#include "usd.hh"
namespace blender::io::hydra {
USDSceneDelegate::USDSceneDelegate(pxr::HdRenderIndex *render_index,
pxr::SdfPath const &delegate_id)
: render_index_(render_index), delegate_id_(delegate_id)
{
/* Temporary directory to write any additional files to, like image or VDB files. */
char unique_name[FILE_MAXFILE];
BLI_snprintf(unique_name, sizeof(unique_name), "%p", this);
char dir_path[FILE_MAX];
BLI_path_join(
dir_path, sizeof(dir_path), BKE_tempdir_session(), "usd_scene_delegate", unique_name);
BLI_dir_create_recursive(dir_path);
char file_path[FILE_MAX];
BLI_path_join(file_path, sizeof(file_path), dir_path, "scene.usdc");
temp_dir_ = dir_path;
temp_file_ = file_path;
}
USDSceneDelegate::~USDSceneDelegate()
{
BLI_delete(temp_dir_.c_str(), true, true);
}
void USDSceneDelegate::populate(Depsgraph *depsgraph)
{
USDExportParams params = {};
params.export_hair = true;
params.export_uvmaps = true;
params.export_normals = true;
params.export_materials = true;
params.selected_objects_only = false;
params.visible_objects_only = true;
params.use_instancing = true;
params.evaluation_mode = DEG_get_mode(depsgraph);
params.generate_preview_surface = true;
params.export_textures = true;
params.overwrite_textures = true;
params.relative_paths = true;
/* Create clean directory for export. */
BLI_delete(temp_dir_.c_str(), true, true);
BLI_dir_create_recursive(temp_dir_.c_str());
/* Free previous delegate and stage first to save memory. */
delegate_.reset();
stage_.Reset();
/* Convert depsgraph to stage + aditional file in temp directory. */
stage_ = io::usd::export_to_stage(params, depsgraph, temp_file_.c_str());
delegate_ = std::make_unique<pxr::UsdImagingDelegate>(render_index_, delegate_id_);
delegate_->Populate(stage_->GetPseudoRoot());
}
} // namespace blender::io::hydra

View File

@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2023 Blender Foundation */
#pragma once
#include <string>
#include <pxr/imaging/hd/renderIndex.h>
#include <pxr/usdImaging/usdImaging/delegate.h>
struct Depsgraph;
namespace blender::io::hydra {
/* Populate Hydra render index using USD file export, for testing. */
class USDSceneDelegate {
private:
pxr::HdRenderIndex *render_index_;
pxr::SdfPath const delegate_id_;
pxr::UsdStageRefPtr stage_;
std::unique_ptr<pxr::UsdImagingDelegate> delegate_;
std::string temp_dir_;
std::string temp_file_;
public:
USDSceneDelegate(pxr::HdRenderIndex *render_index, pxr::SdfPath const &delegate_id);
~USDSceneDelegate();
void populate(Depsgraph *depsgraph);
};
} // namespace blender::io::hydra

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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);
};

View File

@ -245,6 +245,11 @@
SCE_EEVEE_SSR_HALF_RESOLUTION | SCE_EEVEE_SHADOW_SOFT, \
}
#define _DNA_DEFAULT_SceneHydra \
{ \
.export_method = SCE_HYDRA_EXPORT_HYDRA, \
}
#define _DNA_DEFAULT_Scene \
{ \
.cursor = _DNA_DEFAULT_View3DCursor, \
@ -258,6 +263,8 @@
.safe_areas = _DNA_DEFAULT_DisplaySafeAreas, \
\
.eevee = _DNA_DEFAULT_SceneEEVEE, \
\
.hydra = _DNA_DEFAULT_SceneHydra, \
}
/** \} */

View File

@ -1908,6 +1908,11 @@ typedef struct SceneGpencil {
char _pad[4];
} SceneGpencil;
typedef struct SceneHydra {
int export_method;
int _pad0;
} SceneHydra;
/** \} */
/* -------------------------------------------------------------------- */
@ -2054,6 +2059,7 @@ typedef struct Scene {
struct SceneDisplay display;
struct SceneEEVEE eevee;
struct SceneGpencil grease_pencil_settings;
struct SceneHydra hydra;
} Scene;
/** \} */
@ -2870,6 +2876,13 @@ enum {
SCE_DISPLAY_AA_SAMPLES_32 = 32,
};
/** #SceneHydra->export_method */
enum {
SCE_HYDRA_EXPORT_HYDRA = 0,
SCE_HYDRA_EXPORT_USD = 1,
};
/** \} */
#ifdef __cplusplus

View File

@ -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);

View File

@ -1197,6 +1197,11 @@ static char *rna_SceneGpencil_path(const PointerRNA * /*ptr*/)
return BLI_strdup("grease_pencil_settings");
}
static char *rna_SceneHydra_path(const PointerRNA * /*ptr*/)
{
return BLI_strdup("hydra");
}
static int rna_RenderSettings_stereoViews_skip(CollectionPropertyIterator *iter, void * /*data*/)
{
ListBaseIterator *internal = &iter->internal.listbase;
@ -8082,6 +8087,36 @@ static void rna_def_scene_gpencil(BlenderRNA *brna)
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);
}
static void rna_def_scene_hydra(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
static const EnumPropertyItem hydra_export_method_items[] = {
{SCE_HYDRA_EXPORT_HYDRA,
"HYDRA",
0,
"Hydra",
"Fast interactive editing through native Hydra integration"},
{SCE_HYDRA_EXPORT_USD,
"USD",
0,
"USD",
"Export scene through USD file, for accurate comparison with USD file export"},
{0, nullptr, 0, nullptr, nullptr},
};
srna = RNA_def_struct(brna, "SceneHydra", nullptr);
RNA_def_struct_path_func(srna, "rna_SceneHydra_path");
RNA_def_struct_ui_text(srna, "Scene Hydra", "Scene Hydra render engine settings");
prop = RNA_def_property(srna, "export_method", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, hydra_export_method_items);
RNA_def_property_ui_text(
prop, "Export Method", "How to export the Blender scene to the Hydra render engine");
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);
}
void RNA_def_scene(BlenderRNA *brna)
{
StructRNA *srna;
@ -8573,6 +8608,11 @@ void RNA_def_scene(BlenderRNA *brna)
RNA_def_property_struct_type(prop, "SceneGpencil");
RNA_def_property_ui_text(prop, "Grease Pencil", "Grease Pencil settings for the scene");
/* Hydra */
prop = RNA_def_property(srna, "hydra", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "SceneHydra");
RNA_def_property_ui_text(prop, "Hydra", "Hydra settings for the scene");
/* Nestled Data. */
/* *** Non-Animated *** */
RNA_define_animate_sdna(false);
@ -8592,6 +8632,7 @@ void RNA_def_scene(BlenderRNA *brna)
rna_def_scene_display(brna);
rna_def_raytrace_eevee(brna);
rna_def_scene_eevee(brna);
rna_def_scene_hydra(brna);
rna_def_view_layer_aov(brna);
rna_def_view_layer_lightgroup(brna);
rna_def_view_layer_eevee(brna);

View File

@ -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

View File

@ -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},
};

View File

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

View File

@ -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
BogdanNagirniak marked this conversation as resolved Outdated

Native Blender code should use CLOG instead of GLOG.

Native Blender code should use CLOG instead of GLOG.
# 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)

View File

@ -0,0 +1,144 @@
/* 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"
BogdanNagirniak marked this conversation as resolved Outdated

LOG_EN -> LOG_RENDER_HYDRA
rhd.en -> render.hydra

No need to have an obscure name for this.

LOG_EN -> LOG_RENDER_HYDRA rhd.en -> render.hydra No need to have an obscure name for this.
#include "GPU_context.h"
#include "DEG_depsgraph_query.h"
#include "RE_engine.h"
#include "CLG_log.h"
namespace blender::render::hydra {
BogdanNagirniak marked this conversation as resolved Outdated

This will need an addition for Vulkan support. It seems there is no way to pass a parameter, but this should work:

if (GPU_backend_get_type() == GPU_BACKEND_VULKAN) {
  setenv HGI_ENABLE_VULKAN=1
}

This should be relatively thread safe, as Blender can not switch between OpenGL and Vulkan at runtime, so it should always get set to the same thing.

This will need an addition for Vulkan support. It seems there is no way to pass a parameter, but this should work: ``` if (GPU_backend_get_type() == GPU_BACKEND_VULKAN) { setenv HGI_ENABLE_VULKAN=1 } ``` This should be relatively thread safe, as Blender can not switch between OpenGL and Vulkan at runtime, so it should always get set to the same thing.
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();
BogdanNagirniak marked this conversation as resolved Outdated

Should this name get some unique prefix so it's unlikely to clash with other elements? Or is this at a level in the namespace where this can't happen?

Should this name get some unique prefix so it's unlikely to clash with other elements? Or is this at a level in the namespace where this can't happen?

Yes, this is at level in the namespace where this can't happen.

Yes, this is at level in the namespace where this can't happen.
if (GPU_backend_get_type() == GPU_BACKEND_VULKAN) {
BLI_setenv("HGI_ENABLE_VULKAN", "1");
}
BogdanNagirniak marked this conversation as resolved Outdated

This seems OpenGL specific, can this be made to work for Metal and Vulkan too, or is it already working somehow?

This seems OpenGL specific, can this be made to work for Metal and Vulkan too, or is it already working somehow?

This is display name of Storm render delegate (default hydra built in renderer)

This is display name of Storm render delegate (default hydra built in renderer)

Seems it can be "Metal" too:
https://github.com/PixarAnimationStudios/USD/blob/release/pxr/usdImaging/usdImagingGL/engine.cpp#L93

Maybe comparing with the display name is not the best solution, and GetCurrentRendererId is better?

Seems it can be "Metal" too: https://github.com/PixarAnimationStudios/USD/blob/release/pxr/usdImaging/usdImagingGL/engine.cpp#L93 Maybe comparing with the display name is not the best solution, and `GetCurrentRendererId` is better?
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"));
BogdanNagirniak marked this conversation as resolved Outdated

Setting to null here seems unnecessary? If it's to guarantee a particular order of destruction, the order of these members in the class should already specify that.

Setting to null here seems unnecessary? If it's to guarantee a particular order of destruction, the order of these members in the class should already specify that.

We did that because we need order of destruction. We'll change it to order of members in the class

We did that because we need order of destruction. We'll change it to order of members in the class
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 (scene_->hydra.export_method == SCE_HYDRA_EXPORT_HYDRA) {
/* Fast path. */
usd_scene_delegate_.reset();
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);
}
else {
/* Slow USD export for reference. */
if (hydra_scene_delegate_) {
/* Freeing the Hydra scene delegate crashes as something internal to USD
* still holds a pointer to it, only clear it instead. */
hydra_scene_delegate_->clear();
}
if (!usd_scene_delegate_) {
pxr::SdfPath scene_path = pxr::SdfPath::AbsoluteRootPath().AppendElementString("usd_scene");
usd_scene_delegate_ = std::make_unique<io::hydra::USDSceneDelegate>(render_index_.get(),
scene_path);
}
usd_scene_delegate_->populate(depsgraph);
}
}
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

View File

@ -0,0 +1,71 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation */
#pragma once
#include <memory>
#include <string>
BogdanNagirniak marked this conversation as resolved Outdated

Is a direct include of Python.h needed? I don't see Python API calls.

Is a direct include of Python.h needed? I don't see Python API calls.
#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 "hydra/usd_scene_delegate.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<io::hydra::USDSceneDelegate> usd_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

View File

@ -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 {
BogdanNagirniak marked this conversation as resolved Outdated

Seems unnecessary since there is already Py_BEGIN_ALLOW_THREADS in engine_render_func.

Seems unnecessary since there is already `Py_BEGIN_ALLOW_THREADS` in `engine_render_func`.
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;
BogdanNagirniak marked this conversation as resolved Outdated

Use DEG_get_evaluated_scene and DEG_get_evaluated_view_layer instead.

Rendering should use the scene with all animation and drivers evaluated.

Also add const if possible, though it may not be if some functions don't work with it.

Use `DEG_get_evaluated_scene` and `DEG_get_evaluated_view_layer` instead. Rendering should use the scene with all animation and drivers evaluated. Also add `const` if possible, though it may not be if some functions don't work with it.
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);
BogdanNagirniak marked this conversation as resolved Outdated

Add const

Add `const`
}
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));
BogdanNagirniak marked this conversation as resolved Outdated

Can this call get_resolution() to deduplicate code?

Can this call `get_resolution()` to deduplicate code?
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)
{
BogdanNagirniak marked this conversation as resolved Outdated

blender.shared is a Windows specific path, on other operating systems it is lib. But I don't think that path should be hardcoded here, it should be retrieved from somewhere.

`blender.shared` is a Windows specific path, on other operating systems it is `lib`. But I don't think that path should be hardcoded here, it should be retrieved from somewhere.

It could be done in several ways here:

  1. #ifdef
  2. extend BKE_appdir.h with something like BKE_appdir_lib_dir
  3. something else?

What would you prefer?

It could be done in several ways here: 1. #ifdef 2. extend `BKE_appdir.h` with something like `BKE_appdir_lib_dir` 3. something else? What would you prefer?

We have existing code for this in ensure_usd_plugin_path_registered, but actually it's not longer used now. Because the usd is located next to the USD library, it automatically loads plugins from that location.

So I think this line can just be removed now?

We have existing code for this in `ensure_usd_plugin_path_registered`, but actually it's not longer used now. Because the `usd` is located next to the USD library, it automatically loads plugins from that location. So I think this line can just be removed now?
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")) {
BogdanNagirniak marked this conversation as resolved Outdated

Similar comments about modifying environment variables as PATH. Is this the only way to set MaterialX search paths?

Similar comments about modifying environment variables as `PATH`. Is this the only way to set MaterialX search paths?
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);
BogdanNagirniak marked this conversation as resolved Outdated

Modifying PATH seems strange to me, is that really what USD expects?

This also doesn't restore the environment variable to its old values, and just keeps adding to it.

It's also not thread safe in general to modify environment variables unless we are certain it only happens in the main thread and other threads are not affected, which doesn't seems like we can guarantee for PATH.

Modifying `PATH` seems strange to me, is that really what USD expects? This also doesn't restore the environment variable to its old values, and just keeps adding to it. It's also not thread safe in general to modify environment variables unless we are certain it only happens in the main thread and other threads are not affected, which doesn't seems like we can guarantee for `PATH`.
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;
}

View File

@ -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

View File

@ -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

View File

View File

@ -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"
BogdanNagirniak marked this conversation as resolved Outdated

Add empty line between includes with different prefix. Also that way you don't have to use clang-format off.

Add empty line between includes with different prefix. Also that way you don't have to use `clang-format off`.
#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

View File

@ -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

View File

@ -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) {

View File

@ -1623,16 +1623,32 @@ if((DEFINED LIBDIR) AND TARGETDIR_LIB)
${USD_LIBRARY_DIR}/usd
DESTINATION ${TARGETDIR_LIB}
)
install(DIRECTORY
${LIBDIR}/usd/plugin/usd/hdStorm
${LIBDIR}/usd/plugin/usd/usdShaders
${LIBDIR}/usd/plugin/usd/hioOiio
DESTINATION "./blender.shared/usd"
)
elseif(USD_PYTHON_SUPPORT)
install(DIRECTORY
${USD_LIBRARY_DIR}/usd
DESTINATION ${TARGETDIR_LIB}
)
install(DIRECTORY
${LIBDIR}/usd/plugin/usd/hdStorm
${LIBDIR}/usd/plugin/usd/usdShaders
DESTINATION ${TARGETDIR_LIB}/usd
)
else()
install(DIRECTORY
${USD_LIBRARY_DIR}/usd
DESTINATION "${TARGETDIR_VER}/datafiles"
)
install(DIRECTORY
${LIBDIR}/usd/plugin/usd/hdStorm
${LIBDIR}/usd/plugin/usd/usdShaders
DESTINATION "${TARGETDIR_VER}/datafiles/usd"
)
endif()
endif()
if(WIN32)

View File

@ -665,6 +665,33 @@ if(WITH_CYCLES OR WITH_OPENGL_RENDER_TESTS)
-outdir "${TEST_OUT_DIR}/workbench"
)
endforeach()
if(WITH_HYDRA)
# Hydra Storm
foreach(render_test ${render_tests})
add_python_test(
storm_hydra_${render_test}_test
${CMAKE_CURRENT_LIST_DIR}/storm_render_tests.py
-blender "${TEST_BLENDER_EXE}"
-testdir "${TEST_SRC_DIR}/render/${render_test}"
-idiff "${OPENIMAGEIO_IDIFF}"
-outdir "${TEST_OUT_DIR}/storm_hydra"
-export_method "HYDRA"
)
endforeach()
foreach(render_test ${render_tests})
add_python_test(
storm_usd_${render_test}_test
${CMAKE_CURRENT_LIST_DIR}/storm_render_tests.py
-blender "${TEST_BLENDER_EXE}"
-testdir "${TEST_SRC_DIR}/render/${render_test}"
-idiff "${OPENIMAGEIO_IDIFF}"
-outdir "${TEST_OUT_DIR}/storm_usd"
-export_method "USD"
)
endforeach()
endif()
endif()
endif()
endif()

View File

@ -0,0 +1,101 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2015-2023 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
import argparse
import os
import platform
import shlex
import shutil
import subprocess
import sys
from pathlib import Path
def setup():
import bpy
import addon_utils
addon_utils.enable("hydra_storm")
for scene in bpy.data.scenes:
scene.render.engine = 'HYDRA_STORM'
scene.hydra.export_method = os.environ['BLENDER_HYDRA_EXPORT_METHOD']
# When run from inside Blender, render and exit.
try:
import bpy
inside_blender = True
except ImportError:
inside_blender = False
if inside_blender:
try:
setup()
except Exception as e:
print(e)
sys.exit(1)
def get_arguments(filepath, output_filepath):
return [
"--background",
"-noaudio",
"--factory-startup",
"--enable-autoexec",
"--debug-memory",
"--debug-exit-on-error",
filepath,
"-P",
os.path.realpath(__file__),
"-o", output_filepath,
"-F", "PNG",
"-f", "1"]
def create_argparse():
parser = argparse.ArgumentParser()
parser.add_argument("-blender", nargs="+")
parser.add_argument("-testdir", nargs=1)
parser.add_argument("-outdir", nargs=1)
parser.add_argument("-idiff", nargs=1)
parser.add_argument("-export_method", nargs=1)
return parser
def main():
parser = create_argparse()
args = parser.parse_args()
blender = args.blender[0]
test_dir = args.testdir[0]
idiff = args.idiff[0]
output_dir = args.outdir[0]
export_method = args.export_method[0]
from modules import render_report
if export_method == 'HYDRA':
report = render_report.Report("Storm Hydra", output_dir, idiff)
report.set_reference_dir("storm_hydra_renders")
report.set_compare_engine('cycles', 'CPU')
else:
report = render_report.Report("Storm USD", output_dir, idiff)
report.set_reference_dir("storm_usd_renders")
report.set_compare_engine('hydra_storm')
report.set_pixelated(True)
test_dir_name = Path(test_dir).name
os.environ['BLENDER_HYDRA_EXPORT_METHOD'] = export_method
ok = report.run(test_dir, blender, get_arguments, batch=True)
sys.exit(not ok)
if not inside_blender and __name__ == "__main__":
main()