diff --git a/CMakeLists.txt b/CMakeLists.txt index fe93ad41886..8644d653123 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -367,6 +367,9 @@ option(WITH_USD "Enable Universal Scene Description (USD) Suppor # MaterialX option(WITH_MATERIALX "Enable MaterialX Support" ON) +# Hydra render engine +option(WITH_HYDRA "Enable Hydra render engine" ON) + # 3D format support # Disable opencollada when we don't have precompiled libs option(WITH_OPENCOLLADA "Enable OpenCollada Support (http://www.opencollada.org)" ON) @@ -932,6 +935,9 @@ set_and_warn_dependency(WITH_IMAGE_OPENEXR WITH_OPENVDB OFF) set_and_warn_dependency(WITH_IMAGE_OPENEXR WITH_ALEMBIC OFF) set_and_warn_dependency(WITH_IMAGE_OPENEXR WITH_CYCLES_OSL OFF) +# Hydra requires USD. +set_and_warn_dependency(WITH_USD WITH_HYDRA OFF) + # auto enable openimageio for cycles if(WITH_CYCLES) # auto enable llvm for cycles_osl diff --git a/build_files/cmake/config/blender_full.cmake b/build_files/cmake/config/blender_full.cmake index b17d8fd3ed1..ecac558ff2d 100644 --- a/build_files/cmake/config/blender_full.cmake +++ b/build_files/cmake/config/blender_full.cmake @@ -58,6 +58,7 @@ set(WITH_SDL ON CACHE BOOL "" FORCE) set(WITH_TBB ON CACHE BOOL "" FORCE) set(WITH_USD ON CACHE BOOL "" FORCE) set(WITH_MATERIALX ON CACHE BOOL "" FORCE) +set(WITH_HYDRA ON CACHE BOOL "" FORCE) set(WITH_MEM_JEMALLOC ON CACHE BOOL "" FORCE) diff --git a/build_files/cmake/config/blender_release.cmake b/build_files/cmake/config/blender_release.cmake index 1f743979798..d127344bdd6 100644 --- a/build_files/cmake/config/blender_release.cmake +++ b/build_files/cmake/config/blender_release.cmake @@ -59,6 +59,7 @@ set(WITH_SDL ON CACHE BOOL "" FORCE) set(WITH_TBB ON CACHE BOOL "" FORCE) set(WITH_USD ON CACHE BOOL "" FORCE) set(WITH_MATERIALX ON CACHE BOOL "" FORCE) +set(WITH_HYDRA ON CACHE BOOL "" FORCE) set(WITH_MEM_JEMALLOC ON CACHE BOOL "" FORCE) diff --git a/doc/python_api/examples/bpy.types.HydraRenderEngine.py b/doc/python_api/examples/bpy.types.HydraRenderEngine.py new file mode 100644 index 00000000000..380302cf98a --- /dev/null +++ b/doc/python_api/examples/bpy.types.HydraRenderEngine.py @@ -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() diff --git a/scripts/modules/bpy_types.py b/scripts/modules/bpy_types.py index 3efd23cb0d7..e15a76719d8 100644 --- a/scripts/modules/bpy_types.py +++ b/scripts/modules/bpy_types.py @@ -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) diff --git a/source/blender/io/usd/CMakeLists.txt b/source/blender/io/usd/CMakeLists.txt index d028379cff5..8d5edbc2df2 100644 --- a/source/blender/io/usd/CMakeLists.txt +++ b/source/blender/io/usd/CMakeLists.txt @@ -14,6 +14,17 @@ if(WIN32) endif() add_definitions(-DBOOST_ALL_NO_LIB) +# Precompiled Linux libs are made with GCC, and USD uses some extensions +# which lead to an incompatible ABI for Clang. Using those extensions with +# Clang as well works around the issue. +if(UNIX AND NOT APPLE) + if(CMAKE_C_COMPILER_ID MATCHES "Clang") + if(EXISTS ${LIBDIR}) + add_definitions(-DARCH_HAS_GNU_STL_EXTENSIONS) + endif() + endif() +endif() + # USD headers use deprecated TBB headers, silence warning. add_definitions(-DTBB_SUPPRESS_DEPRECATED_MESSAGES=1) @@ -54,8 +65,13 @@ set(INC ../../editors/include ../../imbuf ../../makesrna + ../../nodes + ../../python/intern ../../windowmanager ../../../../intern/utfconv + ../../../../intern/clog + # RNA_prototypes.h + ${CMAKE_BINARY_DIR}/source/blender/makesrna ) set(INC_SYS @@ -94,6 +110,7 @@ set(SRC intern/usd_reader_volume.cc intern/usd_reader_xform.cc + usd.hh usd.h intern/usd_asset_utils.h @@ -124,6 +141,38 @@ set(SRC intern/usd_reader_xform.h ) +if(WITH_HYDRA) + list(APPEND SRC + hydra/camera.cc + hydra/curves.cc + hydra/hydra_scene_delegate.cc + hydra/id.cc + hydra/image.cc + hydra/instancer.cc + hydra/light.cc + hydra/material.cc + hydra/mesh.cc + hydra/object.cc + hydra/volume.cc + hydra/volume_modifier.cc + hydra/world.cc + + hydra/camera.h + hydra/curves.h + hydra/hydra_scene_delegate.h + hydra/id.h + hydra/image.h + hydra/instancer.h + hydra/light.h + hydra/material.h + hydra/mesh.h + hydra/object.h + hydra/volume.h + hydra/volume_modifier.h + hydra/world.h + ) +endif() + set(LIB bf_blenkernel PRIVATE bf::blenlib @@ -153,6 +202,9 @@ endif() blender_add_lib(bf_usd "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") +# RNA_prototypes.h +add_dependencies(bf_usd bf_rna) + if(COMMAND target_precompile_headers) target_precompile_headers(bf_usd PRIVATE intern/usd_precomp.h) endif() diff --git a/source/blender/io/usd/hydra/camera.cc b/source/blender/io/usd/hydra/camera.cc new file mode 100644 index 00000000000..f89e41ebe8d --- /dev/null +++ b/source/blender/io/usd/hydra/camera.cc @@ -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 diff --git a/source/blender/io/usd/hydra/camera.h b/source/blender/io/usd/hydra/camera.h new file mode 100644 index 00000000000..b7ad33fbd56 --- /dev/null +++ b/source/blender/io/usd/hydra/camera.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#pragma once + +#include + +#include +#include + +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 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 diff --git a/source/blender/io/usd/hydra/curves.cc b/source/blender/io/usd/hydra/curves.cc new file mode 100644 index 00000000000..caff0bf197d --- /dev/null +++ b/source/blender/io/usd/hydra/curves.cc @@ -0,0 +1,183 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#include "curves.h" + +#include +#include + +#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 &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 diff --git a/source/blender/io/usd/hydra/curves.h b/source/blender/io/usd/hydra/curves.h new file mode 100644 index 00000000000..36317afbea7 --- /dev/null +++ b/source/blender/io/usd/hydra/curves.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#pragma once + +#include +#include + +#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 &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 diff --git a/source/blender/io/usd/hydra/hydra_scene_delegate.cc b/source/blender/io/usd/hydra/hydra_scene_delegate.cc new file mode 100644 index 00000000000..763600d9179 --- /dev/null +++ b/source/blender/io/usd/hydra/hydra_scene_delegate.cc @@ -0,0 +1,543 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#include "hydra_scene_delegate.h" + +#include + +#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(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 _ */ + 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(object_data(id)); +} + +CurvesData *HydraSceneDelegate::curves_data(pxr::SdfPath const &id) const +{ + return dynamic_cast(object_data(id)); +} + +VolumeData *HydraSceneDelegate::volume_data(pxr::SdfPath const &id) const +{ + return dynamic_cast(object_data(id)); +} + +LightData *HydraSceneDelegate::light_data(pxr::SdfPath const &id) const +{ + return dynamic_cast(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(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 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 available_materials; + for (auto &val : objects_.values()) { + MeshData *m_data = dynamic_cast(val.get()); + if (m_data) { + m_data->available_materials(available_materials); + } + CurvesData *c_data = dynamic_cast(val.get()); + if (c_data) { + c_data->available_materials(available_materials); + } + VolumeData *v_data = dynamic_cast(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 diff --git a/source/blender/io/usd/hydra/hydra_scene_delegate.h b/source/blender/io/usd/hydra/hydra_scene_delegate.h new file mode 100644 index 00000000000..7402a4805a1 --- /dev/null +++ b/source/blender/io/usd/hydra/hydra_scene_delegate.h @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#pragma once + +#include +#include + +#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 instancer_data_; + std::unique_ptr 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 diff --git a/source/blender/io/usd/hydra/id.cc b/source/blender/io/usd/hydra/id.cc new file mode 100644 index 00000000000..fd7114b001c --- /dev/null +++ b/source/blender/io/usd/hydra/id.cc @@ -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 diff --git a/source/blender/io/usd/hydra/id.h b/source/blender/io/usd/hydra/id.h new file mode 100644 index 00000000000..74a334d24ec --- /dev/null +++ b/source/blender/io/usd/hydra/id.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#pragma once + +#include +#include +#include + +#include "DNA_ID.h" + +#include "BLI_hash.hh" + +template<> struct blender::DefaultHash { + uint64_t operator()(const pxr::SdfPath &value) const + { + return (uint64_t)value.GetHash(); + } +}; + +template<> struct blender::DefaultHash { + 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 diff --git a/source/blender/io/usd/hydra/image.cc b/source/blender/io/usd/hydra/image.cc new file mode 100644 index 00000000000..a56a2b14412 --- /dev/null +++ b/source/blender/io/usd/hydra/image.cc @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#include "image.h" + +#include + +#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 diff --git a/source/blender/io/usd/hydra/image.h b/source/blender/io/usd/hydra/image.h new file mode 100644 index 00000000000..05ae39f8f7d --- /dev/null +++ b/source/blender/io/usd/hydra/image.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#pragma once + +#include + +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 diff --git a/source/blender/io/usd/hydra/instancer.cc b/source/blender/io/usd/hydra/instancer.cc new file mode 100644 index 00000000000..eb84564326f --- /dev/null +++ b/source/blender/io/usd/hydra/instancer.cc @@ -0,0 +1,286 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#include "instancer.h" + +#include +#include + +#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 &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(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 _ */ + 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(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(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(nm_inst); +} + +} // namespace blender::io::hydra diff --git a/source/blender/io/usd/hydra/instancer.h b/source/blender/io/usd/hydra/instancer.h new file mode 100644 index 00000000000..34f8079723c --- /dev/null +++ b/source/blender/io/usd/hydra/instancer.h @@ -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 data; + pxr::VtIntArray indices; + }; + + struct NonmeshInstance { + std::unique_ptr data; + pxr::VtMatrix4dArray transforms; + int count = 0; + }; + + private: + Map mesh_instances_; + Map 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 &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 diff --git a/source/blender/io/usd/hydra/light.cc b/source/blender/io/usd/hydra/light.cc new file mode 100644 index 00000000000..2e5101fe844 --- /dev/null +++ b/source/blender/io/usd/hydra/light.cc @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#include "light.h" + +#include +#include +#include + +#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 diff --git a/source/blender/io/usd/hydra/light.h b/source/blender/io/usd/hydra/light.h new file mode 100644 index 00000000000..a5fe919777a --- /dev/null +++ b/source/blender/io/usd/hydra/light.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#pragma once + +#include +#include +#include + +#include "BKE_light.h" + +#include "object.h" + +namespace blender::io::hydra { + +class InstancerData; + +class LightData : public ObjectData { + friend InstancerData; + + protected: + std::map 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 diff --git a/source/blender/io/usd/hydra/material.cc b/source/blender/io/usd/hydra/material.cc new file mode 100644 index 00000000000..f62315ff346 --- /dev/null +++ b/source/blender/io/usd/hydra/material.cc @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#include "material.h" + +#include +#include + +#include +#include +#include + +#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(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 diff --git a/source/blender/io/usd/hydra/material.h b/source/blender/io/usd/hydra/material.h new file mode 100644 index 00000000000..22533439411 --- /dev/null +++ b/source/blender/io/usd/hydra/material.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#pragma once + +#include +#include +#include + +#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>; + +} // namespace blender::io::hydra diff --git a/source/blender/io/usd/hydra/mesh.cc b/source/blender/io/usd/hydra/mesh.cc new file mode 100644 index 00000000000..60e688eacc2 --- /dev/null +++ b/source/blender/io/usd/hydra/mesh.cc @@ -0,0 +1,322 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#include +#include +#include + +#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 &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 looptri_faces = mesh->looptri_faces(); + blender::Span corner_verts = mesh->corner_verts(); + blender::Span 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 < = 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 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 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 diff --git a/source/blender/io/usd/hydra/mesh.h b/source/blender/io/usd/hydra/mesh.h new file mode 100644 index 00000000000..48398af0dd6 --- /dev/null +++ b/source/blender/io/usd/hydra/mesh.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#pragma once + +#include +#include + +#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 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 &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 diff --git a/source/blender/io/usd/hydra/object.cc b/source/blender/io/usd/hydra/object.cc new file mode 100644 index 00000000000..cc0bd8f92ce --- /dev/null +++ b/source/blender/io/usd/hydra/object.cc @@ -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::create(HydraSceneDelegate *scene_delegate, + Object *object, + pxr::SdfPath const &prim_id) +{ + std::unique_ptr 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(scene_delegate, object, prim_id); + break; + } + obj_data = std::make_unique(scene_delegate, object, prim_id); + break; + case OB_CURVES: + obj_data = std::make_unique(scene_delegate, object, prim_id); + break; + case OB_LAMP: + obj_data = std::make_unique(scene_delegate, object, prim_id); + break; + case OB_VOLUME: + obj_data = std::make_unique(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 & /* 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(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 diff --git a/source/blender/io/usd/hydra/object.h b/source/blender/io/usd/hydra/object.h new file mode 100644 index 00000000000..2536fa7a9df --- /dev/null +++ b/source/blender/io/usd/hydra/object.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#pragma once + +#include +#include + +#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 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 &paths) const; + + protected: + virtual void write_transform(); + virtual void write_materials(); + MaterialData *get_or_create_material(Material *mat); +}; + +using ObjectDataMap = Map>; + +pxr::GfMatrix4d gf_matrix_from_transform(float m[4][4]); + +} // namespace blender::io::hydra diff --git a/source/blender/io/usd/hydra/volume.cc b/source/blender/io/usd/hydra/volume.cc new file mode 100644 index 00000000000..801ae732b60 --- /dev/null +++ b/source/blender/io/usd/hydra/volume.cc @@ -0,0 +1,162 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#include +#include +#include +#include +#include +#include + +#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 &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 diff --git a/source/blender/io/usd/hydra/volume.h b/source/blender/io/usd/hydra/volume.h new file mode 100644 index 00000000000..1c86553da15 --- /dev/null +++ b/source/blender/io/usd/hydra/volume.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#pragma once + +#include + +#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 &paths) const override; + + pxr::HdVolumeFieldDescriptorVector field_descriptors() const; + + protected: + void write_materials() override; +}; + +} // namespace blender::io::hydra diff --git a/source/blender/io/usd/hydra/volume_modifier.cc b/source/blender/io/usd/hydra/volume_modifier.cc new file mode 100644 index 00000000000..81612e6fd8c --- /dev/null +++ b/source/blender/io/usd/hydra/volume_modifier.cc @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#include "volume_modifier.h" + +#include + +#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 diff --git a/source/blender/io/usd/hydra/volume_modifier.h b/source/blender/io/usd/hydra/volume_modifier.h new file mode 100644 index 00000000000..8d33df33b8e --- /dev/null +++ b/source/blender/io/usd/hydra/volume_modifier.h @@ -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 diff --git a/source/blender/io/usd/hydra/world.cc b/source/blender/io/usd/hydra/world.cc new file mode 100644 index 00000000000..cf6f34d6ab0 --- /dev/null +++ b/source/blender/io/usd/hydra/world.cc @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#include "world.h" + +#include +#include +#include +#include +#include +#include +#include + +#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 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 const *input_color = color_input.default_value_typed(); + 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(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 diff --git a/source/blender/io/usd/hydra/world.h b/source/blender/io/usd/hydra/world.h new file mode 100644 index 00000000000..f8ad274372f --- /dev/null +++ b/source/blender/io/usd/hydra/world.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#pragma once + +#include + +#include +#include +#include +#include +#include + +#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 diff --git a/source/blender/io/usd/intern/usd_capi_export.cc b/source/blender/io/usd/intern/usd_capi_export.cc index 8f16085cd5b..c8cc8b2c9a1 100644 --- a/source/blender/io/usd/intern/usd_capi_export.cc +++ b/source/blender/io/usd/intern/usd_capi_export.cc @@ -3,6 +3,7 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "usd.h" +#include "usd.hh" #include "usd_hierarchy_iterator.h" #include @@ -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 ¶ms, + Depsgraph *depsgraph, + const char *filepath, + bool *stop, + bool *do_update, + float *progress) { - ExportJobData *data = static_cast(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 ¶ms, + 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(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) { diff --git a/source/blender/io/usd/usd.hh b/source/blender/io/usd/usd.hh new file mode 100644 index 00000000000..44c9dd3d1ad --- /dev/null +++ b/source/blender/io/usd/usd.hh @@ -0,0 +1,18 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include + +struct Depsgraph; +struct USDExportParams; + +namespace blender::io::usd { + +pxr::UsdStageRefPtr export_to_stage(const USDExportParams ¶ms, + Depsgraph *depsgraph, + const char *filepath); + +}; diff --git a/source/blender/makesrna/intern/rna_render.cc b/source/blender/makesrna/intern/rna_render.cc index 0f345c8e63c..40d4d8521c4 100644 --- a/source/blender/makesrna/intern/rna_render.cc +++ b/source/blender/makesrna/intern/rna_render.cc @@ -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); diff --git a/source/blender/python/intern/CMakeLists.txt b/source/blender/python/intern/CMakeLists.txt index 5f865430d0b..632c6bf4136 100644 --- a/source/blender/python/intern/CMakeLists.txt +++ b/source/blender/python/intern/CMakeLists.txt @@ -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 diff --git a/source/blender/python/intern/bpy_interface.cc b/source/blender/python/intern/bpy_interface.cc index 0d0ad30a328..bbbb26f54be 100644 --- a/source/blender/python/intern/bpy_interface.cc +++ b/source/blender/python/intern/bpy_interface.cc @@ -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}, }; diff --git a/source/blender/render/CMakeLists.txt b/source/blender/render/CMakeLists.txt index e3763b0a8e8..03a39d304d4 100644 --- a/source/blender/render/CMakeLists.txt +++ b/source/blender/render/CMakeLists.txt @@ -93,3 +93,7 @@ endif() blender_add_lib_nolist(bf_render "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") + +if(WITH_HYDRA) + add_subdirectory(hydra) +endif() diff --git a/source/blender/render/hydra/CMakeLists.txt b/source/blender/render/hydra/CMakeLists.txt new file mode 100644 index 00000000000..31946e53fd2 --- /dev/null +++ b/source/blender/render/hydra/CMakeLists.txt @@ -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 on Linux. This has been reported at: +# https://github.com/PixarAnimationStudios/USD/issues/1057. +if(UNIX AND NOT APPLE) + add_definitions(-D_GLIBCXX_PERMIT_BACKWARD_HASH) +endif() +if(WIN32) + add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN -DBOOST_DEBUG_PYTHON) +endif() +add_definitions(-DBOOST_ALL_NO_LIB) + +# Precompiled Linux libs are made with GCC, and USD uses some extensions +# which lead to an incompatible ABI for Clang. Using those extensions with +# Clang as well works around the issue. +if(UNIX AND NOT APPLE) + if(CMAKE_C_COMPILER_ID MATCHES "Clang") + if(EXISTS ${LIBDIR}) + add_definitions(-DARCH_HAS_GNU_STL_EXTENSIONS) + endif() + endif() +endif() + +# USD headers use deprecated TBB headers, silence warning. +add_definitions(-DTBB_SUPPRESS_DEPRECATED_MESSAGES=1) + +if(WIN32) + # Some USD library headers trigger the "unreferenced formal parameter" + # warning alert. + # Silence them by restore warn C4100 back to w4 + remove_cc_flag("/w34100") +endif() + +set(INC + ../../../../intern/clog + ../../../../intern/guardedalloc + ../../makesdna + ../../makesrna + ../../nodes + ../../blenlib + ../../depsgraph + ../../blenkernel + ../../imbuf + ../../io/usd + ../../gpu + ../../gpu/intern + ../../python/intern + # RNA_prototypes.h + ${CMAKE_BINARY_DIR}/source/blender/makesrna + .. +) + +set(INC_SYS + ${PYTHON_INCLUDE_DIRS} + ${Epoxy_INCLUDE_DIRS} + ${USD_INCLUDE_DIRS} + ${BOOST_INCLUDE_DIR} + ${TBB_INCLUDE_DIR} + ${GFLAGS_INCLUDE_DIRS} + ${EIGEN3_INCLUDE_DIRS} +) + +set(LIB + ${Epoxy_LIBRARIES} + ${PYTHON_LIBRARIES} + ${BOOST_LIBRARIES} + ${USD_LIBRARIES} + ${TBB_LIBRARIES} + bf_usd +) + +set(SRC + engine.cc + final_engine.cc + light_tasks_delegate.cc + preview_engine.cc + python.cc + render_task_delegate.cc + viewport_engine.cc + + engine.h + final_engine.h + light_tasks_delegate.h + preview_engine.h + render_task_delegate.h + viewport_engine.h +) + +blender_add_lib(bf_render_hydra "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") + +# RNA_prototypes.h +add_dependencies(bf_render_hydra bf_rna) diff --git a/source/blender/render/hydra/engine.cc b/source/blender/render/hydra/engine.cc new file mode 100644 index 00000000000..2b06c4e2a65 --- /dev/null +++ b/source/blender/render/hydra/engine.cc @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#include "engine.h" + +#include +#include +#include +#include +#include +#include + +#include "BLI_path_util.h" + +#include "BKE_context.h" + +#include "GPU_context.h" + +#include "DEG_depsgraph_query.h" + +#include "RE_engine.h" + +#include "CLG_log.h" + +namespace blender::render::hydra { + +CLG_LOGREF_DECLARE_GLOBAL(LOG_HYDRA_RENDER, "hydra.render"); + +Engine::Engine(RenderEngine *bl_engine, const std::string &render_delegate_name) + : render_delegate_name_(render_delegate_name), bl_engine_(bl_engine) +{ + pxr::HdRendererPluginRegistry ®istry = pxr::HdRendererPluginRegistry::GetInstance(); + + pxr::TF_PY_ALLOW_THREADS_IN_SCOPE(); + + if (GPU_backend_get_type() == GPU_BACKEND_VULKAN) { + BLI_setenv("HGI_ENABLE_VULKAN", "1"); + } + + pxr::HdDriverVector hd_drivers; + if (bl_engine->type->flag & RE_USE_GPU_CONTEXT) { + hgi_ = pxr::Hgi::CreatePlatformDefaultHgi(); + hgi_driver_.name = pxr::HgiTokens->renderDriver; + hgi_driver_.driver = pxr::VtValue(hgi_.get()); + + hd_drivers.push_back(&hgi_driver_); + } + render_delegate_ = registry.CreateRenderDelegate(pxr::TfToken(render_delegate_name_)); + + if (!render_delegate_) { + throw std::runtime_error("Cannot create render delegate: " + render_delegate_name_); + } + + render_index_.reset(pxr::HdRenderIndex::New(render_delegate_.Get(), hd_drivers)); + free_camera_delegate_ = std::make_unique( + render_index_.get(), pxr::SdfPath::AbsoluteRootPath().AppendElementString("freeCamera")); + + if (bl_engine->type->flag & RE_USE_GPU_CONTEXT && GPU_backend_get_type() == GPU_BACKEND_OPENGL) { + render_task_delegate_ = std::make_unique( + render_index_.get(), pxr::SdfPath::AbsoluteRootPath().AppendElementString("renderTask")); + } + else { + render_task_delegate_ = std::make_unique( + 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( + render_index_.get(), pxr::SdfPath::AbsoluteRootPath().AppendElementString("lightTasks")); + light_tasks_delegate_->set_camera(free_camera_delegate_->GetCameraId()); + } + + engine_ = std::make_unique(); +} + +void Engine::sync(Depsgraph *depsgraph, bContext *context) +{ + depsgraph_ = depsgraph; + context_ = context; + scene_ = DEG_get_evaluated_scene(depsgraph); + + if (!hydra_scene_delegate_) { + pxr::SdfPath scene_path = pxr::SdfPath::AbsoluteRootPath().AppendElementString("scene"); + hydra_scene_delegate_ = std::make_unique(render_index_.get(), + scene_path); + } + hydra_scene_delegate_->populate(depsgraph, context ? CTX_wm_view3d(context) : nullptr); +} + +void Engine::set_render_setting(const std::string &key, const pxr::VtValue &val) +{ + render_delegate_->SetRenderSetting(pxr::TfToken(key), val); +} + +float Engine::renderer_percent_done() +{ + pxr::VtDictionary render_stats = render_delegate_->GetRenderStats(); + auto it = render_stats.find("percentDone"); + if (it == render_stats.end()) { + return 0.0f; + } + return (float)it->second.UncheckedGet(); +} + +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 diff --git a/source/blender/render/hydra/engine.h b/source/blender/render/hydra/engine.h new file mode 100644 index 00000000000..cf8548ff433 --- /dev/null +++ b/source/blender/render/hydra/engine.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "hydra/hydra_scene_delegate.h" +#include "hydra/settings.h" + +#include "light_tasks_delegate.h" +#include "render_task_delegate.h" + +struct bContext; +struct RenderEngine; +struct CLG_LogRef; + +namespace blender::render::hydra { + +extern struct CLG_LogRef *LOG_HYDRA_RENDER; + +class Engine { + protected: + std::string render_delegate_name_; + RenderEngine *bl_engine_ = nullptr; + Depsgraph *depsgraph_ = nullptr; + bContext *context_ = nullptr; + Scene *scene_ = nullptr; + + /* The order is important due to deletion order */ + pxr::HgiUniquePtr hgi_; + pxr::HdDriver hgi_driver_; + pxr::HdPluginRenderDelegateUniqueHandle render_delegate_; + std::unique_ptr render_index_; + + std::unique_ptr hydra_scene_delegate_; + + std::unique_ptr render_task_delegate_; + std::unique_ptr free_camera_delegate_; + std::unique_ptr light_tasks_delegate_; + std::unique_ptr 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 diff --git a/source/blender/render/hydra/final_engine.cc b/source/blender/render/hydra/final_engine.cc new file mode 100644 index 00000000000..e12b24608c1 --- /dev/null +++ b/source/blender/render/hydra/final_engine.cc @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#include "final_engine.h" + +#include +#include + +#include "DNA_scene_types.h" + +#include "BLI_timecode.h" +#include "PIL_time.h" + +#include "BKE_lib_id.h" + +#include "DEG_depsgraph_query.h" + +#include "IMB_imbuf_types.h" + +#include "RE_engine.h" + +#include "hydra/camera.h" + +namespace blender::render::hydra { + +void FinalEngine::render() +{ + const ViewLayer *view_layer = DEG_get_evaluated_view_layer(depsgraph_); + + char scene_name[MAX_ID_FULL_NAME]; + BKE_id_full_name_get(scene_name, &scene_->id, 0); + + const RenderData &r = scene_->r; + pxr::GfVec4f border(0, 0, 1, 1); + if (r.mode & R_BORDER) { + border.Set(r.border.xmin, + r.border.ymin, + r.border.xmax - r.border.xmin, + r.border.ymax - r.border.ymin); + } + pxr::GfVec2i image_res(r.xsch * r.size / 100, r.ysch * r.size / 100); + int width = image_res[0] * border[2]; + int height = image_res[1] * border[3]; + pxr::GfCamera camera = + io::hydra::CameraData(scene_->camera, image_res, pxr::GfVec4f(0, 0, 1, 1)).gf_camera(border); + + free_camera_delegate_->SetCamera(camera); + render_task_delegate_->set_viewport(pxr::GfVec4d(0, 0, width, height)); + if (light_tasks_delegate_) { + light_tasks_delegate_->set_viewport(pxr::GfVec4d(0, 0, width, height)); + } + + RenderResult *rr = RE_engine_get_result(bl_engine_); + RenderLayer *rlayer = (RenderLayer *)rr->layers.first; + LISTBASE_FOREACH (RenderPass *, rpass, &rlayer->passes) { + pxr::TfToken *aov_token = aov_tokens_.lookup_ptr(rpass->name); + if (!aov_token) { + CLOG_WARN(LOG_HYDRA_RENDER, "Couldn't find AOV token for render pass: %s", rpass->name); + continue; + } + render_task_delegate_->add_aov(*aov_token); + } + if (bl_engine_->type->flag & RE_USE_GPU_CONTEXT) { + /* For GPU context engine color and depth AOVs has to be added anyway */ + render_task_delegate_->add_aov(pxr::HdAovTokens->color); + render_task_delegate_->add_aov(pxr::HdAovTokens->depth); + } + + render_task_delegate_->bind(); + + auto t = tasks(); + engine_->Execute(render_index_.get(), &t); + + char elapsed_time[32]; + double time_begin = PIL_check_seconds_timer(); + float percent_done = 0.0; + + while (true) { + if (RE_engine_test_break(bl_engine_)) { + break; + } + + percent_done = renderer_percent_done(); + BLI_timecode_string_from_time_simple( + elapsed_time, sizeof(elapsed_time), PIL_check_seconds_timer() - time_begin); + notify_status(percent_done / 100.0, + std::string(scene_name) + ": " + view_layer->name, + std::string("Render Time: ") + elapsed_time + + " | Done: " + std::to_string(int(percent_done)) + "%"); + + if (render_task_delegate_->is_converged()) { + break; + } + + update_render_result(width, height, view_layer->name); + } + + update_render_result(width, height, view_layer->name); + render_task_delegate_->unbind(); +} + +void FinalEngine::set_render_setting(const std::string &key, const pxr::VtValue &val) +{ + if (STRPREFIX(key.c_str(), "aovToken:")) { + aov_tokens_.add_overwrite(key.substr(key.find(":") + 1), + pxr::TfToken(val.UncheckedGet())); + 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( + 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 diff --git a/source/blender/render/hydra/final_engine.h b/source/blender/render/hydra/final_engine.h new file mode 100644 index 00000000000..b61044aa4e5 --- /dev/null +++ b/source/blender/render/hydra/final_engine.h @@ -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 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 diff --git a/source/blender/render/hydra/light_tasks_delegate.cc b/source/blender/render/hydra/light_tasks_delegate.cc new file mode 100644 index 00000000000..0f4ef129572 --- /dev/null +++ b/source/blender/render/hydra/light_tasks_delegate.cc @@ -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(this, simple_task_id_); + skydome_task_id_ = GetDelegateID().AppendElementString("skydomeTask"); + GetRenderIndex().InsertTask(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 diff --git a/source/blender/render/hydra/light_tasks_delegate.h b/source/blender/render/hydra/light_tasks_delegate.h new file mode 100644 index 00000000000..d1131392d1c --- /dev/null +++ b/source/blender/render/hydra/light_tasks_delegate.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#pragma once + +#include +#include +#include + +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 diff --git a/source/blender/render/hydra/preview_engine.cc b/source/blender/render/hydra/preview_engine.cc new file mode 100644 index 00000000000..ca835a73c83 --- /dev/null +++ b/source/blender/render/hydra/preview_engine.cc @@ -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 diff --git a/source/blender/render/hydra/preview_engine.h b/source/blender/render/hydra/preview_engine.h new file mode 100644 index 00000000000..776b8ecd016 --- /dev/null +++ b/source/blender/render/hydra/preview_engine.h @@ -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 diff --git a/source/blender/render/hydra/python.cc b/source/blender/render/hydra/python.cc new file mode 100644 index 00000000000..a415d9f958f --- /dev/null +++ b/source/blender/render/hydra/python.cc @@ -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 + +#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 T *pyrna_to_pointer(PyObject *pyobject, const StructRNA *rnatype) +{ + const PointerRNA *ptr = pyrna_struct_as_ptr_or_null(pyobject, rnatype); + return (ptr) ? static_cast(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(pyengine, &RNA_RenderEngine); + + CLOG_INFO(LOG_HYDRA_RENDER, 1, "Engine %s", engine_type); + Engine *engine = nullptr; + try { + if (STREQ(engine_type, "VIEWPORT")) { + engine = new ViewportEngine(bl_engine, render_delegate_id); + } + else if (STREQ(engine_type, "PREVIEW")) { + engine = new PreviewEngine(bl_engine, render_delegate_id); + } + else { + engine = new FinalEngine(bl_engine, render_delegate_id); + } + } + catch (std::runtime_error &e) { + CLOG_ERROR(LOG_HYDRA_RENDER, "%s", e.what()); + } + + CLOG_INFO(LOG_HYDRA_RENDER, 1, "Engine %p", engine); + return PyLong_FromVoidPtr(engine); +} + +static PyObject *engine_free_func(PyObject * /*self*/, PyObject *args) +{ + PyObject *pyengine; + if (!PyArg_ParseTuple(args, "O", &pyengine)) { + Py_RETURN_NONE; + } + + Engine *engine = static_cast(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(PyLong_AsVoidPtr(pyengine)); + Depsgraph *depsgraph = pyrna_to_pointer(pydepsgraph, &RNA_Depsgraph); + bContext *context = pyrna_to_pointer(pycontext, &RNA_Context); + + CLOG_INFO(LOG_HYDRA_RENDER, 2, "Engine %p", engine); + engine->sync(depsgraph, context); + + Py_RETURN_NONE; +} + +static PyObject *engine_render_func(PyObject * /*self*/, PyObject *args) +{ + PyObject *pyengine; + if (!PyArg_ParseTuple(args, "O", &pyengine)) { + Py_RETURN_NONE; + } + + Engine *engine = static_cast(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(PyLong_AsVoidPtr(pyengine)); + bContext *context = pyrna_to_pointer(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(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(PyLong_AsVoidPtr(pycontext)); + Image *image = static_cast(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; +} diff --git a/source/blender/render/hydra/render_task_delegate.cc b/source/blender/render/hydra/render_task_delegate.cc new file mode 100644 index 00000000000..72acf99a1bd --- /dev/null +++ b/source/blender/render/hydra/render_task_delegate.cc @@ -0,0 +1,342 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#include "render_task_delegate.h" + +#include + +#include "GPU_context.h" + +#include +#include +#include + +#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(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(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( + 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 diff --git a/source/blender/render/hydra/render_task_delegate.h b/source/blender/render/hydra/render_task_delegate.h new file mode 100644 index 00000000000..36e7b56b0f4 --- /dev/null +++ b/source/blender/render/hydra/render_task_delegate.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#pragma once + +#include +#include + +#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 + 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 diff --git a/source/blender/render/hydra/settings.h b/source/blender/render/hydra/settings.h new file mode 100644 index 00000000000..e69de29bb2d diff --git a/source/blender/render/hydra/viewport_engine.cc b/source/blender/render/hydra/viewport_engine.cc new file mode 100644 index 00000000000..be78e6ab437 --- /dev/null +++ b/source/blender/render/hydra/viewport_engine.cc @@ -0,0 +1,295 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#include "viewport_engine.h" + +#include +#include +#include + +#include "DNA_camera_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" +#include "DNA_vec_types.h" /* this include must be before BKE_camera.h due to "rctf" type */ +#include "DNA_view3d_types.h" + +#include "BLI_math_matrix.h" +#include "BLI_timecode.h" +#include "PIL_time.h" + +#include "BKE_camera.h" +#include "BKE_context.h" + +#include "DEG_depsgraph_query.h" + +#include "GPU_context.h" +#include "GPU_matrix.h" + +#include "RE_engine.h" + +#include "hydra/camera.h" + +namespace blender::render::hydra { + +struct ViewSettings { + ViewSettings(bContext *context); + + int width(); + int height(); + + pxr::GfCamera gf_camera(); + + io::hydra::CameraData camera_data; + + int screen_width; + int screen_height; + pxr::GfVec4i border; +}; + +ViewSettings::ViewSettings(bContext *context) + : camera_data(CTX_wm_view3d(context), CTX_wm_region(context)) +{ + View3D *view3d = CTX_wm_view3d(context); + RegionView3D *region_data = static_cast(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_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( + 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 diff --git a/source/blender/render/hydra/viewport_engine.h b/source/blender/render/hydra/viewport_engine.h new file mode 100644 index 00000000000..32d4fa73908 --- /dev/null +++ b/source/blender/render/hydra/viewport_engine.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2011-2022 Blender Foundation */ + +#pragma once + +#include + +#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 diff --git a/source/blender/render/intern/engine.cc b/source/blender/render/intern/engine.cc index be5a56666cf..899493052d6 100644 --- a/source/blender/render/intern/engine.cc +++ b/source/blender/render/intern/engine.cc @@ -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) {