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 000000000000..2285fedc1299 --- /dev/null +++ b/doc/python_api/examples/bpy.types.HydraRenderEngine.py @@ -0,0 +1,54 @@ +""" +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, + } + + # Settings used by the synchronization process. + def get_sync_settings(self, engine_type): + return { + 'MaterialXFilenameKey': "MaterialXFilename", + } + + # 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 + +# 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_hydra.py b/scripts/modules/bpy_hydra.py index 649351288d58..56fe6664f165 100644 --- a/scripts/modules/bpy_hydra.py +++ b/scripts/modules/bpy_hydra.py @@ -1,147 +1,9 @@ # SPDX-License-Identifier: GPL-2.0-or-later -""" -Implementation of class `HydraRenderEngine`. - -Render Blender addon with Hydra render delegate should inherit `HydraRenderEngine`. -Example: -``` -import bpy_hydra - -class CustomHydraRenderEngine(HydraRenderEngine): - bl_idname = 'CustomHydraRenderEngine' - bl_label = "Hydra: Custom" - bl_info = "Hydra Custom render delegate" - - delegate_id = 'HdCustomRendererPlugin' - - @classmethod - def register(cls): - super().register() - - bpy_hydra.register_plugins(["/path/to/plugin")]) - - def get_sync_settings(self, engine_type): - return { - 'MaterialXFilenameKey': "MaterialXFilename", - } - - def get_render_settings(self, engine_type): - return { - 'enableTinyPrimCulling': True, - 'maxLights': 8, - } -``` -""" - __all__ = ( - "HydraRenderEngine", "export_mtlx", - "register_plugins", ) -import os -import platform -from pathlib import Path - -import bpy -import _bpy_hydra - -from _bpy_hydra import register_plugins - - -class HydraRenderEngine(bpy.types.RenderEngine): - """ Render addon with Hydra render delegate should inherit this class """ - - bl_use_shading_nodes_custom = False - - delegate_id = '' - engine_ptr = None - - def __del__(self): - if not self.engine_ptr: - return - - _bpy_hydra.engine_free(self.engine_ptr) - - @classmethod - def register(cls): - root_folder = "blender.shared" if platform.system() == 'Windows' else "lib" - os.environ['PXR_MTLX_STDLIB_SEARCH_PATHS'] = os.pathsep.join([ - str(Path(bpy.app.binary_path).parent / f"{root_folder}/materialx/libraries"), - os.environ.get('PXR_MTLX_STDLIB_SEARCH_PATHS', "")]) - - @classmethod - def unregister(cls): - pass - - def get_sync_settings(self, engine_type): - """ - Provide settings for Blender scene delegate. Available settings: - `MaterialXFilenameKey` - if provided then MaterialX file will be provided directly to render delegate - without converting to HdMaterialNetwork - """ - return {} - - def get_render_settings(self, engine_type): - """ - Provide render settings for render delegate. List of settings should be available in render delegate - documentation or in `pxr.UsdImagingGL.Engine.GetRendererSettingsList()` - """ - return {} - - # final render - def _update(self, depsgraph): - """This function is preferable to override in child classes instead of update()""" - engine_type = 'PREVIEW' if self.is_preview else 'FINAL' - self.engine_ptr = _bpy_hydra.engine_create(self.as_pointer(), engine_type, self.delegate_id) - if not self.engine_ptr: - return - - for key, val in self.get_sync_settings(engine_type).items(): - _bpy_hydra.engine_set_sync_setting(self.engine_ptr, key, val) - - _bpy_hydra.engine_sync(self.engine_ptr, depsgraph.as_pointer(), bpy.context.as_pointer()) - - def update(self, data, depsgraph): - # If bl_use_gpu_context is true, this function is ignored and render() is used - if not self.bl_use_gpu_context: - self._update(depsgraph) - - def render(self, depsgraph): - if self.bl_use_gpu_context: - self._update(depsgraph) - - if not self.engine_ptr: - return - - 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) - - _bpy_hydra.engine_render(self.engine_ptr, depsgraph.as_pointer()) - - # viewport render - def view_update(self, context, depsgraph): - if not self.engine_ptr: - self.engine_ptr = _bpy_hydra.engine_create(self.as_pointer(), 'VIEWPORT', self.delegate_id) - if not self.engine_ptr: - return - - for key, val in self.get_sync_settings('VIEWPORT').items(): - _bpy_hydra.engine_set_sync_setting(self.engine_ptr, key, val) - - _bpy_hydra.engine_sync(self.engine_ptr, depsgraph.as_pointer(), context.as_pointer()) - - 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 - - _bpy_hydra.engine_view_draw(self.engine_ptr, depsgraph.as_pointer(), context.as_pointer()) - - def export_mtlx(material): """ Exports material to .mtlx file. It is called from Blender source code. """ try: diff --git a/scripts/modules/bpy_types.py b/scripts/modules/bpy_types.py index 96baeef1f11d..8ac414bc2146 100644 --- a/scripts/modules/bpy_types.py +++ b/scripts/modules/bpy_types.py @@ -935,10 +935,6 @@ class PropertyGroup(StructRNA, metaclass=RNAMetaPropGroup): __slots__ = () -class RenderEngine(StructRNA, metaclass=RNAMeta): - __slots__ = () - - class KeyingSetInfo(StructRNA, metaclass=RNAMeta): __slots__ = () @@ -1249,3 +1245,86 @@ 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_sync_settings(self, engine_type: str): + """ + Provide settings for Blender scene export. Available settings: + `MaterialXFilenameKey` - if provided then MaterialX file will be provided directly to render delegate + without converting to HdMaterialNetwork + """ + return {} + + def get_render_settings(self, engine_type: str): + """ + Provide render settings for `HdRenderDelegate`. + """ + return {} + + # Final render. + def update(self, data, depsgraph): + import _bpy_hydra + import bpy + + 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 + + for key, val in self.get_sync_settings(engine_type).items(): + _bpy_hydra.engine_set_sync_setting(self.engine_ptr, key, val) + + _bpy_hydra.engine_update(self.engine_ptr, depsgraph, bpy.context) + + 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, depsgraph) + + # 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 + + for key, val in self.get_sync_settings('VIEWPORT').items(): + _bpy_hydra.engine_set_sync_setting(self.engine_ptr, key, val) + + _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, depsgraph, context) diff --git a/scripts/site/sitecustomize.py b/scripts/site/sitecustomize.py index c0fcd419a3d3..a847192f3c4a 100644 --- a/scripts/site/sitecustomize.py +++ b/scripts/site/sitecustomize.py @@ -38,7 +38,11 @@ materialx_libs_dir = os.path.abspath(os.path.join(shared_lib_dir, "materialx", " materialx_libs_env = os.getenv("MATERIALX_SEARCH_PATH") if materialx_libs_env is None: os.environ["MATERIALX_SEARCH_PATH"] = materialx_libs_dir -elif sys.platform == "win32": - os.environ["MATERIALX_SEARCH_PATH"] = materialx_libs_dir + ";" + materialx_libs_env else: - os.environ["MATERIALX_SEARCH_PATH"] = materialx_libs_dir + ":" + materialx_libs_env + os.environ["MATERIALX_SEARCH_PATH"] = materialx_libs_env + os.pathsep + materialx_libs_dir + +materialx_libs_env = os.getenv("PXR_MTLX_STDLIB_SEARCH_PATHS") +if materialx_libs_env is None: + os.environ["PXR_MTLX_STDLIB_SEARCH_PATHS"] = materialx_libs_dir +else: + os.environ["PXR_MTLX_STDLIB_SEARCH_PATHS"] = materialx_libs_env + os.pathsep + materialx_libs_dir diff --git a/source/blender/makesrna/intern/rna_render.cc b/source/blender/makesrna/intern/rna_render.cc index 80df76fcc610..e6b3bb382f11 100644 --- a/source/blender/makesrna/intern/rna_render.cc +++ b/source/blender/makesrna/intern/rna_render.cc @@ -1007,6 +1007,14 @@ static void rna_def_render_engine(BlenderRNA *brna) RNA_define_verify_sdna(1); } +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; @@ -1235,6 +1243,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/render/hydra/CMakeLists.txt b/source/blender/render/hydra/CMakeLists.txt index d5301388f81e..9f3ec8d8a15f 100644 --- a/source/blender/render/hydra/CMakeLists.txt +++ b/source/blender/render/hydra/CMakeLists.txt @@ -46,6 +46,8 @@ set(INC ../../gpu ../../gpu/intern ../../python/intern + # RNA_prototypes.h + ${CMAKE_BINARY_DIR}/source/blender/makesrna .. ${CMAKE_BINARY_DIR}/source/blender/makesrna/intern ) @@ -124,4 +126,5 @@ set(SRC 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/python.cc b/source/blender/render/hydra/python.cc index da8b5b96dce4..e7335ebf9b31 100644 --- a/source/blender/render/hydra/python.cc +++ b/source/blender/render/hydra/python.cc @@ -3,56 +3,26 @@ #include -#include +#include "RE_engine.h" -#include -#include -#include -#include +#include "bpy_rna.h" -#include "BKE_appdir.h" -#include "BLI_fileops.h" -#include "BLI_path_util.h" +#include "BKE_context.h" + +#include "RNA_prototypes.h" + +#include "scene_delegate/image.h" #include "final_engine.h" #include "preview_engine.h" -#include "scene_delegate/image.h" #include "viewport_engine.h" namespace blender::render::hydra { -static PyObject *register_plugins_func(PyObject * /*self*/, PyObject *args) +template T *pyrna_to_pointer(PyObject *pyobject, const StructRNA *rnatype) { - PyObject *pyplugin_dirs; - if (!PyArg_ParseTuple(args, "O", &pyplugin_dirs)) { - Py_RETURN_NONE; - } - - std::vector plugin_dirs; - PyObject *pyiter, *pyitem; - - pyiter = PyObject_GetIter(pyplugin_dirs); - if (pyiter) { - while ((pyitem = PyIter_Next(pyiter))) { - plugin_dirs.push_back(PyUnicode_AsUTF8(pyitem)); - Py_DECREF(pyitem); - } - Py_DECREF(pyiter); - } - - pxr::PlugRegistry ®istry = pxr::PlugRegistry::GetInstance(); - registry.RegisterPlugins(plugin_dirs); - - /* logging */ - std::stringstream ss; - ss << "plugins=["; - for (auto &s : plugin_dirs) { - ss << s << ", "; - } - ss << "]"; - CLOG_INFO(LOG_RENDER_HYDRA, 0, "Register %s", ss.str().c_str()); - - Py_RETURN_NONE; + 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) @@ -63,7 +33,7 @@ static PyObject *engine_create_func(PyObject * /*self*/, PyObject *args) Py_RETURN_NONE; } - RenderEngine *bl_engine = (RenderEngine *)PyLong_AsVoidPtr(pyengine); + RenderEngine *bl_engine = pyrna_to_pointer(pyengine, &RNA_RenderEngine); Engine *engine = nullptr; try { @@ -111,16 +81,16 @@ static PyObject *engine_free_func(PyObject * /*self*/, PyObject *args) Py_RETURN_NONE; } -static PyObject *engine_sync_func(PyObject * /*self*/, PyObject *args) +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 = (Engine *)PyLong_AsVoidPtr(pyengine); - Depsgraph *depsgraph = (Depsgraph *)PyLong_AsVoidPtr(pydepsgraph); - bContext *context = (bContext *)PyLong_AsVoidPtr(pycontext); + 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_RENDER_HYDRA, 2, "Engine %p", engine); engine->sync(depsgraph, context); @@ -136,8 +106,8 @@ static PyObject *engine_render_func(PyObject * /*self*/, PyObject *args) Py_RETURN_NONE; } - Engine *engine = (Engine *)PyLong_AsVoidPtr(pyengine); - Depsgraph *depsgraph = (Depsgraph *)PyLong_AsVoidPtr(pydepsgraph); + Engine *engine = static_cast(PyLong_AsVoidPtr(pyengine)); + Depsgraph *depsgraph = pyrna_to_pointer(pydepsgraph, &RNA_Depsgraph); CLOG_INFO(LOG_RENDER_HYDRA, 2, "Engine %p", engine); @@ -156,9 +126,9 @@ static PyObject *engine_view_draw_func(PyObject * /*self*/, PyObject *args) Py_RETURN_NONE; } - ViewportEngine *engine = (ViewportEngine *)PyLong_AsVoidPtr(pyengine); - Depsgraph *depsgraph = (Depsgraph *)PyLong_AsVoidPtr(pydepsgraph); - bContext *context = (bContext *)PyLong_AsVoidPtr(pycontext); + ViewportEngine *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_RENDER_HYDRA, 3, "Engine %p", engine); @@ -235,11 +205,9 @@ static PyObject *cache_or_get_image_file_func(PyObject * /*self*/, PyObject *arg } static PyMethodDef methods[] = { - {"register_plugins", register_plugins_func, METH_VARARGS, ""}, - {"engine_create", engine_create_func, METH_VARARGS, ""}, {"engine_free", engine_free_func, METH_VARARGS, ""}, - {"engine_sync", engine_sync_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_sync_setting", engine_set_sync_setting_func, METH_VARARGS, ""}, diff --git a/source/blender/render/intern/engine.cc b/source/blender/render/intern/engine.cc index 758c8a93e69d..3410231dc760 100644 --- a/source/blender/render/intern/engine.cc +++ b/source/blender/render/intern/engine.cc @@ -919,9 +919,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) {