diff --git a/__init__.py b/__init__.py index ce0cd99c..6397f461 100644 --- a/__init__.py +++ b/__init__.py @@ -30,8 +30,8 @@ bl_info = { "name": "RenderMan For Blender", "author": "Pixar", - "version": (25, 0, 0), - "blender": (2, 83, 0), + "version": (25, 1, 0), + "blender": (2, 93, 0), "location": "Render Properties > Render Engine > RenderMan", "description": "RenderMan 25 integration", "doc_url": "https://rmanwiki.pixar.com/display/RFB", diff --git a/changelog.txt b/changelog.txt index 605f26b8..fd58dff1 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,16 @@ +v25.1, June 1, 2023 + +Changes: +* Blender 3.1 to 3.5 are now officially supported. +* Support for 2.83 has been dropped. The minimum version supported is 2.93. + +Bug Fixes: +* Fixed an issue with UV maps not working correctly with geometry nodes instances +* Fixed a bug that prevented PxrVariable from working in a shading network +* Fixed bugs that prevented the use of color to float, or float to color connections from working +* Fixed a bug where the PxrStylizedControl pattern node was not getting correctly added when using +the Stylized Looks UI. + v25.0 April 17, 2023 New Features: diff --git a/display_driver/d_blender.cpp b/display_driver/d_blender.cpp index 77b9afba..3a8e3097 100644 --- a/display_driver/d_blender.cpp +++ b/display_driver/d_blender.cpp @@ -25,6 +25,7 @@ #include #include "libndspy/Dspy.h" + #ifdef OSX #include #else @@ -169,6 +170,16 @@ void CopyXpuBuffer(BlenderImage* blenderImage) float* linebuffer = new float[blenderImage->width * blenderImage->entrysize]; + std::vector shouldNormalizeBySampleCount(blenderImage->noutputs); + for (size_t roi = 0; roi < blenderImage->noutputs; ++roi) + { + const display::RenderOutput& ro = blenderImage->renderOutputs[roi]; + + // Cache whether we should normalize data to avoid calling ShouldNormalizeBySampleCount for + // every single pixel + shouldNormalizeBySampleCount[roi] = ro.ShouldNormalizeBySampleCount(); + } + /* For each pixel ... */ size_t pixel = 0; for (size_t y = 0; y < blenderImage->height; ++y) @@ -176,10 +187,11 @@ void CopyXpuBuffer(BlenderImage* blenderImage) size_t outchannel = 0; for (size_t x = 0; x < blenderImage->width; ++x) { - /* Compute reciprical, which we'll use to divide each pixel intensity by */ - float rcp = 1.f / weights[pixel]; + /* Compute reciprocal, which we'll use to divide each pixel intensity by */ + const float weight = (weights[pixel] != 0.0f) ? weights[pixel] : 1.0f; + const float rcp = 1.0f / weight; - /* Itterate through our render outputs */ + /* Iterate through our render outputs */ for (size_t roi = 0; roi < blenderImage->noutputs; ++roi) { const display::RenderOutput& ro = blenderImage->renderOutputs[roi]; @@ -189,19 +201,12 @@ void CopyXpuBuffer(BlenderImage* blenderImage) /* For each channel in the current render output */ for (size_t c = 0; c < ro.nelems; ++c) { - float res = 0.0; - // NB - we don't want to average integer values. Instead, - // we expect the renderer to have applied a rule such as - // overwrite, max or min to preserve precision and avoid - // nonesense values at pixels were multiple integer ids - // are written by differing primitives - if (blenderImage->type == display::RenderOutput::DataType::kDataTypeUInt) + float res = floatData[pixel]; + if (shouldNormalizeBySampleCount[roi]) { - res = floatData[pixel]; - } - else - { - res = floatData[pixel] * rcp; + // Only normalize suitable data, such as channels of float data format and + // RenderOutputs that don't use the min, max, zmin, zmax accumulation rules. + res *= rcp; } linebuffer[outchannel] = res; @@ -287,20 +292,21 @@ int GetNumberOfChannels(size_t pos) // Return the float buffer for this display PRMANEXPORT -float* GetFloatFramebuffer(size_t pos) +void GetFloatFramebuffer(size_t pos, size_t pybuffersize, float* pybuffer) { if (s_blenderImages.empty() || pos >= s_blenderImages.size()) - return nullptr; + return; BlenderImage* blenderImage = s_blenderImages[pos]; if (blenderImage == nullptr) - return nullptr; + return; if (DenoiseBuffer(blenderImage)) { - return (float*) blenderImage->denoiseFrameBuffer; + memcpy(pybuffer, blenderImage->denoiseFrameBuffer, sizeof(float) * pybuffersize); + return; } - return (float*) blenderImage->framebuffer; + memcpy(pybuffer, blenderImage->framebuffer, sizeof(float) * pybuffersize); } // Return the active region that RenderMan is currently working on @@ -382,7 +388,6 @@ void DrawBufferToBlender(int viewWidth, int viewHeight) 1.0, 1.0, 0.0, 1.0}; GLuint vertex_array; - std::array vertex_buffer; GLuint texcoord_location; GLuint position_location; @@ -482,6 +487,10 @@ DspyImageOpen( PtDspyDevFormat* format, PtFlagStuff* flagstuff) { + PIXAR_ARGUSED(drivername); + PIXAR_ARGUSED(filename); + PIXAR_ARGUSED(flagstuff); + char* cformat = (char*)"float32"; DspyFindStringInParamList("format", &cformat, paramCount, parameters); @@ -697,6 +706,12 @@ DspyImageActiveRegion( int ymin, int ymax_plus_one) { + PIXAR_ARGUSED(pvImage); + PIXAR_ARGUSED(xmin); + PIXAR_ARGUSED(xmax_plus_one); + PIXAR_ARGUSED(ymin); + PIXAR_ARGUSED(ymax_plus_one); + return PkDspyErrorNone; } @@ -726,6 +741,9 @@ BlenderDspyMetadata( PtDspyImageHandle pvImage, char* metadata) { + PIXAR_ARGUSED(pvImage); + PIXAR_ARGUSED(metadata); + return PkDspyErrorNone; } @@ -749,11 +767,16 @@ DisplayBlender::~DisplayBlender() } bool DisplayBlender::Rebind(const uint32_t width, const uint32_t height, - const char* srfaddrhandle, const void* srfaddr, - const size_t srfsizebytes, const size_t samplecountoffset, + const char* srfaddrhandle, + const void* srfaddr, + const size_t srfsizebytes, + const size_t samplecountoffset, const size_t* offsets, const display::RenderOutput* outputs, const size_t noutputs) { + PIXAR_ARGUSED(srfaddrhandle); + PIXAR_ARGUSED(srfsizebytes); + size_t nchans = 0; size_t pixelsizebytes = 0; m_image->renderOutputs.clear(); @@ -847,15 +870,28 @@ void DisplayBlender::Close() tag_redraw_func = NULL; } -void DisplayBlender::Notify(const uint32_t iteration, const uint32_t totaliterations, - const NotifyFlags flags, const pxrcore::ParamList& /*metadata*/) +void DisplayBlender::Notify(const uint32_t iteration, + const uint32_t totaliterations, + const NotifyFlags flags, + const pxrcore::ParamList& metadata) { + PIXAR_ARGUSED(iteration); + PIXAR_ARGUSED(totaliterations); + PIXAR_ARGUSED(metadata); + if (flags != k_notifyIteration && flags != k_notifyRender) { return; } CopyXpuBuffer(m_image); m_image->bufferUpdated = true; + if (tag_redraw_func) + { + if (!tag_redraw_func()) + { + tag_redraw_func = NULL; + } + } } static void closeBlenderImages() diff --git a/rfb_utils/generate_property_utils.py b/rfb_utils/generate_property_utils.py index 8339a580..95f02182 100644 --- a/rfb_utils/generate_property_utils.py +++ b/rfb_utils/generate_property_utils.py @@ -265,6 +265,7 @@ def generate_property(node, sp, update_function=None, set_function=None, get_fun 'inherit_true_value', 'presets', 'readOnly', + 'hideInput', 'struct_name', 'always_write']: if hasattr(sp, nm): diff --git a/rfb_utils/property_utils.py b/rfb_utils/property_utils.py index 530a1f8d..bff84421 100644 --- a/rfb_utils/property_utils.py +++ b/rfb_utils/property_utils.py @@ -73,6 +73,7 @@ def __init__(self, node, prop_name, prop_meta): self.renderman_array_type = prop_meta.get('renderman_array_type', '') self.type = prop_meta.get('type', '') self.page = prop_meta.get('page', '') + self.hide_input = prop_meta.get('hideInput', False) inputs = getattr(node, 'inputs', dict()) self.has_input = (prop_name in inputs) @@ -104,6 +105,8 @@ def is_exportable(self): # if widget is marked null, don't export parameter and rely on default # unless it has a vstructmember return False + if self.hide_input: + return False if self.param_type == 'page': return False diff --git a/rfb_utils/rfb_node_desc_utils/rfb_node_desc_param.py b/rfb_utils/rfb_node_desc_utils/rfb_node_desc_param.py index 81e8d52c..4732f95c 100644 --- a/rfb_utils/rfb_node_desc_utils/rfb_node_desc_param.py +++ b/rfb_utils/rfb_node_desc_utils/rfb_node_desc_param.py @@ -13,7 +13,7 @@ 'inherit_true_value', 'update_function_name', 'update_function', 'set_function_name', 'set_function', 'get_function_name', 'get_function', - 'readOnly', 'always_write', 'ipr_editable'] + 'readOnly', 'always_write', 'ipr_editable', 'hideInput'] def blender_finalize(obj): """Post-process some parameters for Blender. diff --git a/rfb_utils/shadergraph_utils.py b/rfb_utils/shadergraph_utils.py index 0c19b99b..63c05668 100644 --- a/rfb_utils/shadergraph_utils.py +++ b/rfb_utils/shadergraph_utils.py @@ -15,12 +15,15 @@ def __init__(self, sg_node, group_node=None, is_cycles_node=False): class RmanConvertNode: - def __init__(self, node_type, from_node, from_socket, to_node, to_socket): + def __init__(self, node_type, from_node, from_socket): self.node_type = node_type self.from_node = from_node self.from_socket = from_socket - self.to_node = to_node - self.to_socket = to_socket + + def __eq__(self, other): + if type(other) != RmanConvertNode: + return False + return (self.node_type == other.node_type and self.from_node == other.from_node and self.from_socket == other.from_socket) def is_renderman_nodetree(material): return find_node(material, 'RendermanOutputNode') @@ -418,11 +421,11 @@ def gather_nodes(node): # if this is a float->float3 type or float3->float connections, insert # either PxrToFloat3 or PxrToFloat conversion nodes if is_socket_float_type(link.from_socket) and is_socket_float3_type(socket): - convert_node = RmanConvertNode('PxrToFloat3', link.from_node, link.from_socket, link.to_node, link.to_socket) + convert_node = RmanConvertNode('PxrToFloat3', link.from_node, link.from_socket) if convert_node not in nodes: nodes.append(convert_node) elif is_socket_float3_type(link.from_socket) and is_socket_float_type(socket): - convert_node = RmanConvertNode('PxrToFloat', link.from_node, link.from_socket, link.to_node, link.to_socket) + convert_node = RmanConvertNode('PxrToFloat', link.from_node, link.from_socket) if convert_node not in nodes: nodes.append(convert_node) diff --git a/rman_config/config/rman_dspychan_definitions.json b/rman_config/config/rman_dspychan_definitions.json index b870e2c0..4f3bbdc9 100644 --- a/rman_config/config/rman_dspychan_definitions.json +++ b/rman_config/config/rman_dspychan_definitions.json @@ -663,9 +663,9 @@ "channelSource": "lpe:nothruput;noinfinitecheck;noclamp;unoccluded;overwrite;CU4L", "group": "User Lobes" }, - "NPRallLines": { + "NPRlineOut": { "channelType": "color", - "channelSource": "NPRallLines", + "channelSource": "NPRlineOut", "group": "NPR" }, "NPRcurvature": { @@ -693,9 +693,9 @@ "channelSource": "NPRlineCamdist", "group": "NPR" }, - "NPRlineMask": { + "NPRmask": { "channelType": "color", - "channelSource": "NPRlineMask", + "channelSource": "NPRmask", "group": "NPR" }, "NPRlineAlbedo": { @@ -708,9 +708,9 @@ "channelSource": "NPRlineWidth", "group": "NPR" }, - "NPRallLinesAlpha": { + "NPRlineOutAlpha": { "channelType": "color", - "channelSource": "NPRallLinesAlpha", + "channelSource": "NPRlineOutAlpha", "group": "NPR" }, "NPRtextureCoords": { @@ -733,9 +733,14 @@ "channelSource": "NPRalbedo", "group": "NPR" }, - "NPRtoonDiffRamp": { + "NPRhatchOut": { "channelType": "color", - "channelSource": "NPRtoonDiffRamp", + "channelSource": "NPRhatchOut", + "group": "NPR" + }, + "NPRtoonOut": { + "channelType": "color", + "channelSource": "NPRtoonOut", "group": "NPR" }, "NPRdistort": { @@ -810,16 +815,17 @@ "Nn", "sampleCount", "directSpecular", - "NPRtoonDiffRamp", - "NPRallLines", - "NPRallLinesAlpha", + "NPRtoonOut", + "NPRhatchOut", + "NPRlineOut", + "NPRlineOutAlpha", "NPRoutline", "NPRlineNZ", "NPRsections", "NPRlineCamdist", "NPRlineAlbedo", "NPRlineWidth", - "NPRlineMask", + "NPRmask", "NPRcurvature", "NPRalbedo", "NPRtextureCoords", diff --git a/rman_config/config/rman_properties_scene.json b/rman_config/config/rman_properties_scene.json index f7c5a75b..e9453c40 100644 --- a/rman_config/config/rman_properties_scene.json +++ b/rman_config/config/rman_properties_scene.json @@ -1048,7 +1048,8 @@ "riopt": "limits:texturememory", "label": "Texture", "type": "int", - "default": 4194304, + "default": 2097152, + "always_write": true, "min": 0, "widget": false, "help": "Texture cache size in kB" @@ -1060,7 +1061,8 @@ "riopt": "limits:geocachememory", "label": "Geometry", "type": "int", - "default": 4194304, + "default": 2097152, + "always_write": true, "min": 0, "widget": false, "help": "Geometry cache size in kB." @@ -1072,7 +1074,8 @@ "riopt": "limits:opacitycachememory", "label": "Opacity", "type": "int", - "default": 2097152, + "default": 1048576, + "always_write": true, "min": 0, "widget": false, "help": "Opacity cache in kB" @@ -1085,6 +1088,7 @@ "label": "Ptex", "type": "int", "default": 32768, + "always_write": true, "min": "0", "widget": false, "help": "PTex cache size in kB" @@ -1544,7 +1548,7 @@ "help": "Use the previous denoiser to denoise passes." }, { - "panel": "RENDER_PT_renderman_spooling_export_options", + "panel": "", "page": "Denoising", "name": "ai_denoiser_asymmetry", "label": "Asymmetry", diff --git a/rman_constants.py b/rman_constants.py index 4c854516..740be869 100644 --- a/rman_constants.py +++ b/rman_constants.py @@ -15,8 +15,12 @@ BLENDER_VERSION = bpy.app.version +# Starting with Blender 3.5, the bgl module is considered deprecated +# addons are to use the gpu module +USE_GPU_MODULE = ( BLENDER_VERSION_MAJOR == 3 and BLENDER_VERSION_MINOR >= 5) + BLENDER_SUPPORTED_VERSION_MAJOR = 2 -BLENDER_SUPPORTED_VERSION_MINOR = 83 +BLENDER_SUPPORTED_VERSION_MINOR = 93 BLENDER_SUPPORTED_VERSION_PATCH = 0 BLENDER_SUPPORTED_VERSION = (BLENDER_SUPPORTED_VERSION_MAJOR, BLENDER_SUPPORTED_VERSION_MINOR, BLENDER_SUPPORTED_VERSION_PATCH) @@ -26,7 +30,7 @@ BLENDER_PYTHON_VERSION = '%s.%s' % (pyver.major, pyver.minor) RMAN_SUPPORTED_VERSION_MAJOR = 25 -RMAN_SUPPORTED_VERSION_MINOR = 0 +RMAN_SUPPORTED_VERSION_MINOR = 1 RMAN_SUPPORTED_VERSION_BETA = '' RMAN_SUPPORTED_VERSION = (RMAN_SUPPORTED_VERSION_MAJOR, RMAN_SUPPORTED_VERSION_MINOR, RMAN_SUPPORTED_VERSION_BETA) RMAN_SUPPORTED_VERSION_STRING = '%d.%d%s' % (RMAN_SUPPORTED_VERSION_MAJOR, RMAN_SUPPORTED_VERSION_MINOR, RMAN_SUPPORTED_VERSION_BETA) @@ -84,7 +88,8 @@ 'dimensions' : 'rman_dimensions', 'inputs': 'rman_inputs', 'outputs': 'rman_outputs', - 'resolution': 'rman_resolution' + 'resolution': 'rman_resolution', + 'name': 'rman_name' } CYCLES_NODE_MAP = { diff --git a/rman_engine.py b/rman_engine.py index 1fdee647..60d10244 100644 --- a/rman_engine.py +++ b/rman_engine.py @@ -1,11 +1,18 @@ import bpy -import bgl -import blf from .rfb_utils.prefs_utils import get_pref from .rfb_utils import string_utils from .rfb_utils import register_utils from .rfb_logger import rfb_log +from .rman_constants import USE_GPU_MODULE + +if USE_GPU_MODULE: + bgl = None + blf = None + import gpu +else: + import bgl + import blf class PRManRender(bpy.types.RenderEngine): bl_idname = 'PRMAN_RENDER' @@ -199,6 +206,8 @@ def render(self, depsgraph): self._increment_version_tokens(external_render=False) def draw_viewport_message(self, context, msg): + if USE_GPU_MODULE: + return w = context.region.width pos_x = w / 2 - 100 @@ -225,14 +234,20 @@ def _draw_pixels(self, context, depsgraph): h = context.region.height # Bind shader that converts from scene linear to display space, - bgl.glEnable(bgl.GL_BLEND) - bgl.glBlendFunc(bgl.GL_ONE, bgl.GL_ONE_MINUS_SRC_ALPHA) - self.bind_display_space_shader(scene) + if USE_GPU_MODULE: + gpu.state.blend_set("ADDITIVE_PREMULT") + self.bind_display_space_shader(scene) + self.rman_render.draw_pixels(w, h) + self.unbind_display_space_shader() + gpu.state.blend_set("NONE") - self.rman_render.draw_pixels(w, h) - - self.unbind_display_space_shader() - bgl.glDisable(bgl.GL_BLEND) + else: + bgl.glEnable(bgl.GL_BLEND) + bgl.glBlendFunc(bgl.GL_ONE, bgl.GL_ONE_MINUS_SRC_ALPHA) + self.bind_display_space_shader(scene) + self.rman_render.draw_pixels(w, h) + self.unbind_display_space_shader() + bgl.glDisable(bgl.GL_BLEND) classes = [ PRManRender, diff --git a/rman_operators/rman_operators_stylized.py b/rman_operators/rman_operators_stylized.py index 4e832d85..1fdb81f4 100644 --- a/rman_operators/rman_operators_stylized.py +++ b/rman_operators/rman_operators_stylized.py @@ -117,8 +117,9 @@ def attach_pattern(self, context, ob): return for nm in RMAN_UTILITY_PATTERN_NAMES: - if hasattr(node, nm): - prop_name = nm + if not hasattr(node, nm): + continue + prop_name = nm if shadergraph_utils.has_stylized_pattern_node(ob, node=node): continue diff --git a/rman_render.py b/rman_render.py index 488e1ec9..e970fd5c 100644 --- a/rman_render.py +++ b/rman_render.py @@ -4,7 +4,7 @@ import ice import bpy import sys -from .rman_constants import RFB_VIEWPORT_MAX_BUCKETS, RMAN_RENDERMAN_BLUE +from .rman_constants import RFB_VIEWPORT_MAX_BUCKETS, RMAN_RENDERMAN_BLUE, USE_GPU_MODULE from .rman_scene import RmanScene from .rman_scene_sync import RmanSceneSync from. import rman_spool @@ -20,6 +20,7 @@ # for viewport buckets import gpu from gpu_extras.batch import batch_for_shader +from gpu_extras.presets import draw_texture_2d # utils from .rfb_utils.envconfig_utils import envconfig @@ -269,6 +270,10 @@ def preload_dsos(rman_render): plugins = [ 'lib/libxpu.so', 'lib/plugins/impl_openvdb.so', + 'lib/plugins/d_blender.so', + 'lib/plugins/PxrSurface.so', + 'lib/plugins/PxrDisneyBsdf.so', + 'lib/libstats.so', ] tree = envconfig().rmantree @@ -1314,8 +1319,22 @@ def draw_pixels(self, width, height): dspy_plugin = self.get_blender_dspy_plugin() - # (the driver will handle pixel scaling to the given viewport size) - dspy_plugin.DrawBufferToBlender(ctypes.c_int(width), ctypes.c_int(height)) + if USE_GPU_MODULE: + res_mult = self.rman_scene.viewport_render_res_mult + width = int(self.viewport_res_x * res_mult) + height = int(self.viewport_res_y * res_mult) + buffer = self._get_buffer(width, height) + if buffer is None: + rfb_log().debug("Buffer is None") + return + pixels = gpu.types.Buffer('FLOAT', width * height * 4, buffer) + + texture = gpu.types.GPUTexture((width, height), format='RGBA32F', data=pixels) + draw_texture_2d(texture, (0, 0), self.viewport_res_x, self.viewport_res_y) + else: + # (the driver will handle pixel scaling to the given viewport size) + dspy_plugin.DrawBufferToBlender(ctypes.c_int(width), ctypes.c_int(height)) + if self.do_draw_buckets(): # draw bucket indicator image_num = 0 @@ -1389,12 +1408,17 @@ def _get_buffer(self, width, height, image_num=0, num_channels=-1, raw_buffer=Fa rfb_log().debug("Could not get buffer. Incorrect number of channels: %d" % num_channels) return None - ArrayType = ctypes.c_float * (width * height * num_channels) + # code reference: https://asiffer.github.io/posts/numpy/ + RMAN_NUMPY_POINTER = numpy.ctypeslib.ndpointer(dtype=numpy.float32, + ndim=1, + flags="C") f = dspy_plugin.GetFloatFramebuffer - f.restype = ctypes.POINTER(ArrayType) + f.argtypes = [ctypes.c_size_t, ctypes.c_size_t, RMAN_NUMPY_POINTER] try: - buffer = numpy.array(f(ctypes.c_size_t(image_num)).contents, dtype=numpy.float32) + array_size = width * height * num_channels + buffer = numpy.zeros(array_size, dtype=numpy.float32) + f(ctypes.c_size_t(image_num), buffer.size, buffer) if raw_buffer: if not as_flat: diff --git a/rman_translators/rman_mesh_translator.py b/rman_translators/rman_mesh_translator.py index ea3c6042..0a06275c 100644 --- a/rman_translators/rman_mesh_translator.py +++ b/rman_translators/rman_mesh_translator.py @@ -33,29 +33,54 @@ def _is_multi_material_(ob, mesh): return False # requires facevertex interpolation -def _get_mesh_uv_(mesh, name=""): +def _get_mesh_uv_(mesh, name="", ob=None): uvs = [] + data = "uv" if not name: uv_loop_layer = mesh.uv_layers.active + if ob and uv_loop_layer is None: + if not hasattr(ob.original.data, 'uv_layers'): + return None + # when dealing geometry nodes, uv_layers are actually + # on the attributes property. + # Look up from original object what the active + # uv layer was + active = ob.original.data.uv_layers.active + if active: + uv_loop_layer = mesh.attributes.get(active.name) + data = "vector" else: - # assuming uv loop layers and uv textures share identical indices - #idx = mesh.uv_textures.keys().index(name) - #uv_loop_layer = mesh.uv_layers[idx] uv_loop_layer = mesh.uv_layers.get(name, None) + if uv_loop_layer is None: + uv_loop_layer = mesh.attributes.get(name, None) + data = "vector" if uv_loop_layer is None: return None uv_count = len(uv_loop_layer.data) fastuvs = np.zeros(uv_count * 2) - uv_loop_layer.data.foreach_get("uv", fastuvs) + uv_loop_layer.data.foreach_get(data, fastuvs) uvs = fastuvs.tolist() return uvs -def _get_mesh_vcol_(mesh, name=""): - vcol_layer = mesh.vertex_colors[name] if name != "" \ - else mesh.vertex_colors.active +def _get_mesh_vcol_(mesh, name="", ob=None): + if not name: + vcol_layer = mesh.vertex_colors.active + if ob and not vcol_layer: + if not hasattr(ob.original.data, 'vertex_colors'): + return None + # same issue with uv's + # vertex colors for geometry nodes are on the attributes + # property + active = ob.original.data.vertex_colors.active + if active: + vcol_layer = mesh.attributes.get(active.name, None) + else: + vcol_layer = mesh.vertex_colors[name] + if not vcol_layer: + vcol_layer = mesh.attributes.get(name, None) if vcol_layer is None: return None @@ -188,7 +213,7 @@ def _get_primvars_(ob, rman_sg_mesh, geo, rixparams): facevarying_detail = rman_sg_mesh.nverts if rm.export_default_uv: - uvs = _get_mesh_uv_(geo) + uvs = _get_mesh_uv_(geo, ob=ob) if uvs and len(uvs) > 0: detail = "facevarying" if (facevarying_detail*2) == len(uvs) else "vertex" rixparams.SetFloatArrayDetail("st", uvs, 2, detail) @@ -196,7 +221,7 @@ def _get_primvars_(ob, rman_sg_mesh, geo, rixparams): export_tangents(ob, geo, rixparams) if rm.export_default_vcol: - vcols = _get_mesh_vcol_(geo) + vcols = _get_mesh_vcol_(geo, ob=ob) if vcols and len(vcols) > 0: detail = "facevarying" if facevarying_detail == len(vcols) else "vertex" rixparams.SetColorDetail("Cs", vcols, detail) diff --git a/rman_ui/rman_ui_light_handlers/__init__.py b/rman_ui/rman_ui_light_handlers/__init__.py index 2a1e1943..f817dc61 100644 --- a/rman_ui/rman_ui_light_handlers/__init__.py +++ b/rman_ui/rman_ui_light_handlers/__init__.py @@ -1,12 +1,10 @@ -import bpy -import gpu -import bgl + import os from gpu_extras.batch import batch_for_shader from ...rfb_utils import string_utils from ...rfb_utils import prefs_utils from ...rfb_logger import rfb_log -from ...rman_constants import RMAN_AREA_LIGHT_TYPES +from ...rman_constants import RMAN_AREA_LIGHT_TYPES, USE_GPU_MODULE from .barn_light_filter_draw_helper import BarnLightFilterDrawHelper from .frustrum_draw_helper import FrustumDrawHelper from mathutils import Vector, Matrix @@ -14,6 +12,14 @@ import mathutils import math import ice +import bpy +import gpu + +if USE_GPU_MODULE: + bgl = None + from gpu_extras.batch import batch_for_shader +else: + import bgl _DRAW_HANDLER_ = None _FRUSTUM_DRAW_HELPER_ = None @@ -450,57 +456,112 @@ __MTX_X_90__ = Matrix.Rotation(math.radians(90.0), 4, 'X') __MTX_Y_90__ = Matrix.Rotation(math.radians(90.0), 4, 'Y') -_VERTEX_SHADER_UV_ = ''' - uniform mat4 modelMatrix; - uniform mat4 viewProjectionMatrix; - - in vec3 position; - in vec2 uv; - - out vec2 uvInterp; - - void main() - { - uvInterp = uv; - gl_Position = viewProjectionMatrix * modelMatrix * vec4(position, 1.0); - } -''' - -_VERTEX_SHADER_ = ''' - uniform mat4 modelMatrix; - uniform mat4 viewProjectionMatrix; - in vec3 position; - - void main() - { - gl_Position = viewProjectionMatrix * modelMatrix * vec4(position, 1.0f); - } -''' - -_FRAGMENT_SHADER_TEX_ = ''' - uniform sampler2D image; - - in vec2 uvInterp; - out vec4 FragColor; - - void main() - { - FragColor = texture(image, uvInterp); - } -''' - -_FRAGMENT_SHADER_COL_ = ''' - uniform vec4 lightColor; - - in vec2 uvInterp; - out vec4 FragColor; - - void main() - { - FragColor = lightColor; - } -''' +if USE_GPU_MODULE: + # Code reference: https://projects.blender.org/blender/blender/src/branch/main/doc/python_api/examples/gpu.7.py + + vert_out = gpu.types.GPUStageInterfaceInfo("image_interface") + vert_out.smooth('VEC2', "uvInterp") + + _SHADER_IMAGE_INFO_ = gpu.types.GPUShaderCreateInfo() + + _SHADER_IMAGE_INFO_.push_constant('MAT4', "viewProjectionMatrix") + _SHADER_IMAGE_INFO_.sampler(0, 'FLOAT_2D', "image") + _SHADER_IMAGE_INFO_.vertex_in(0, 'VEC3', "position") + _SHADER_IMAGE_INFO_.vertex_in(1, 'VEC2', "uv") + _SHADER_IMAGE_INFO_.vertex_out(vert_out) + _SHADER_IMAGE_INFO_.fragment_out(0, 'VEC4', "FragColor") + + _SHADER_IMAGE_INFO_.vertex_source( + ''' + void main() + { + gl_Position = viewProjectionMatrix * vec4(position, 1.0f); + uvInterp = uv; + } + ''') + _SHADER_IMAGE_INFO_.fragment_source( + ''' + void main() + { + FragColor = texture(image, uvInterp); + } + + ''') + + _SHADER_COLOR_INFO_ = gpu.types.GPUShaderCreateInfo() + _SHADER_COLOR_INFO_.vertex_in(0, 'VEC3', "position") + _SHADER_COLOR_INFO_.push_constant('MAT4', "viewProjectionMatrix") + _SHADER_COLOR_INFO_.push_constant('VEC4', 'lightColor') + _SHADER_COLOR_INFO_.fragment_out(0, 'VEC4', 'FragColor') + + _SHADER_COLOR_INFO_.vertex_source( + ''' + void main() + { + gl_Position = viewProjectionMatrix * vec4(position, 1.0f); + } + ''') + _SHADER_COLOR_INFO_.fragment_source( + ''' + void main() + { + FragColor = lightColor; + } + + ''') +else: + _VERTEX_SHADER_UV_ = ''' + uniform mat4 modelMatrix; + uniform mat4 viewProjectionMatrix; + + in vec3 position; + in vec2 uv; + + out vec2 uvInterp; + + void main() + { + uvInterp = uv; + gl_Position = viewProjectionMatrix * modelMatrix * vec4(position, 1.0); + } + ''' + + _VERTEX_SHADER_ = ''' + uniform mat4 modelMatrix; + uniform mat4 viewProjectionMatrix; + + in vec3 position; + + void main() + { + gl_Position = viewProjectionMatrix * modelMatrix * vec4(position, 1.0f); + } + ''' + + _FRAGMENT_SHADER_TEX_ = ''' + uniform sampler2D image; + + in vec2 uvInterp; + out vec4 FragColor; + + void main() + { + FragColor = texture(image, uvInterp); + } + ''' + + _FRAGMENT_SHADER_COL_ = ''' + uniform vec4 lightColor; + + in vec2 uvInterp; + out vec4 FragColor; + + void main() + { + FragColor = lightColor; + } + ''' _SHADER_ = None if not bpy.app.background: @@ -589,8 +650,11 @@ def load_gl_texture(tex): ice._registry.Mark() iceimg = ice.Load(real_path) - # quantize to 8 bits - iceimg = iceimg.TypeConvert(ice.constants.FRACTIONAL) + if USE_GPU_MODULE: + iceimg = iceimg.TypeConvert(ice.constants.FLOAT) + else: + # quantize to 8 bits + iceimg = iceimg.TypeConvert(ice.constants.FRACTIONAL) x1, x2, y1, y2 = iceimg.DataBox() width = (x2 - x1) + 1 @@ -613,36 +677,49 @@ def load_gl_texture(tex): width = (x2 - x1) + 1 height = (y2 - y1) + 1 - buffer = iceimg.AsByteArray() numChannels = iceimg.Ply() + if USE_GPU_MODULE: + iFormat = 'RGBA32F' + if numChannels != 4: + # if this is not a 4-channel image, we create a card with an alpha + # and composite the image over the card + bg = ice.Card(ice.constants.FLOAT, [0,0,0,1]) + iceimg = bg.Over(iceimg) + + # Generate texture + buffer = iceimg.AsByteArray() + pixels = gpu.types.Buffer('FLOAT', len(buffer), buffer) + texture = gpu.types.GPUTexture((width, height), format=iFormat, data=pixels) + _PRMAN_TEX_CACHE_[tex] = texture + else: + buffer = iceimg.AsByteArray() + pixels = bgl.Buffer(bgl.GL_BYTE, len(buffer), buffer) + texture = bgl.Buffer(bgl.GL_INT, 1) + _PRMAN_TEX_CACHE_[tex] = texture - pixels = bgl.Buffer(bgl.GL_BYTE, len(buffer), buffer) - texture = bgl.Buffer(bgl.GL_INT, 1) - _PRMAN_TEX_CACHE_[tex] = texture - - iFormat = bgl.GL_RGBA - texFormat = bgl.GL_RGBA - if numChannels == 1: - iFormat = bgl.GL_RGB - texFormat = bgl.GL_LUMINANCE - elif numChannels == 2: - iFormat = bgl.GL_RGB - texFormat = bgl.GL_LUMINANCE_ALPHA - elif numChannels == 3: - iFormat = bgl.GL_RGB - texFormat = bgl.GL_RGB - - elif numChannels == 4: iFormat = bgl.GL_RGBA texFormat = bgl.GL_RGBA - - bgl.glGenTextures(1, texture) - bgl.glActiveTexture(bgl.GL_TEXTURE0) - bgl.glBindTexture(bgl.GL_TEXTURE_2D, texture[0]) - bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, iFormat, width, height, 0, texFormat, bgl.GL_UNSIGNED_BYTE, pixels) - bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_LINEAR) - bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_LINEAR) - bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0) + if numChannels == 1: + iFormat = bgl.GL_RGB + texFormat = bgl.GL_LUMINANCE + elif numChannels == 2: + iFormat = bgl.GL_RGB + texFormat = bgl.GL_LUMINANCE_ALPHA + elif numChannels == 3: + iFormat = bgl.GL_RGB + texFormat = bgl.GL_RGB + + elif numChannels == 4: + iFormat = bgl.GL_RGBA + texFormat = bgl.GL_RGBA + + bgl.glGenTextures(1, texture) + bgl.glActiveTexture(bgl.GL_TEXTURE0) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, texture[0]) + bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, iFormat, width, height, 0, texFormat, bgl.GL_UNSIGNED_BYTE, pixels) + bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_LINEAR) + bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_LINEAR) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0) ice._registry.RemoveToMark() del iceimg @@ -743,33 +820,52 @@ def draw_solid(ob, pts, mtx, uvs=list(), indices=None, tex='', col=None): if not prefs_utils.get_pref('rman_viewport_draw_lights_textured'): return - + real_path = string_utils.expand_string(tex) if os.path.exists(real_path): - shader = gpu.types.GPUShader(_VERTEX_SHADER_UV_, _FRAGMENT_SHADER_TEX_) + if USE_GPU_MODULE: + shader = gpu.shader.create_from_info(_SHADER_IMAGE_INFO_) + else: + shader = gpu.types.GPUShader(_VERTEX_SHADER_UV_, _FRAGMENT_SHADER_TEX_) if indices: batch = batch_for_shader(shader, 'TRIS', {"position": pts, "uv": uvs}, indices=indices) - else: - batch = batch_for_shader(shader, 'TRI_FAN', {"position": pts, "uv": uvs}) + elif uvs: + batch = batch_for_shader(shader, 'TRI_FAN', {"position": pts, "uv": uvs}) texture = _PRMAN_TEX_CACHE_.get(tex, None) if not texture: texture = load_gl_texture(tex) - bgl.glActiveTexture(bgl.GL_TEXTURE0) - bgl.glBindTexture(bgl.GL_TEXTURE_2D, texture[0]) + if USE_GPU_MODULE: + shader.bind() + matrix = bpy.context.region_data.perspective_matrix + shader.uniform_float("viewProjectionMatrix", matrix @ mtx) + shader.uniform_sampler("image", texture) + gpu.state.blend_set("ALPHA") + gpu.state.depth_test_set("LESS") + batch.draw(shader) + gpu.state.depth_test_set("NONE") + gpu.state.blend_set("NONE") + else: - shader.bind() - matrix = bpy.context.region_data.perspective_matrix - shader.uniform_float("modelMatrix", mtx) - shader.uniform_float("viewProjectionMatrix", matrix) - shader.uniform_float("image", texture[0]) - bgl.glEnable(bgl.GL_DEPTH_TEST) - batch.draw(shader) - bgl.glDisable(bgl.GL_DEPTH_TEST) + bgl.glActiveTexture(bgl.GL_TEXTURE0) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, texture[0]) + + shader.bind() + matrix = bpy.context.region_data.perspective_matrix + shader.uniform_float("modelMatrix", mtx) + shader.uniform_float("viewProjectionMatrix", matrix) + shader.uniform_float("image", texture[0]) + bgl.glEnable(bgl.GL_DEPTH_TEST) + batch.draw(shader) + bgl.glDisable(bgl.GL_DEPTH_TEST) elif col: - shader = gpu.types.GPUShader(_VERTEX_SHADER_, _FRAGMENT_SHADER_COL_) + if USE_GPU_MODULE: + shader = gpu.shader.create_from_info(_SHADER_COLOR_INFO_) + else: + shader = gpu.types.GPUShader(_VERTEX_SHADER_, _FRAGMENT_SHADER_COL_) + if indices: batch = batch_for_shader(shader, 'TRIS', {"position": pts}, indices=indices) else: @@ -777,24 +873,40 @@ def draw_solid(ob, pts, mtx, uvs=list(), indices=None, tex='', col=None): lightColor = (col[0], col[1], col[2], 1.0) shader.bind() - matrix = bpy.context.region_data.perspective_matrix - shader.uniform_float("modelMatrix", mtx) - shader.uniform_float("viewProjectionMatrix", matrix) shader.uniform_float("lightColor", lightColor) - bgl.glEnable(bgl.GL_DEPTH_TEST) - batch.draw(shader) - bgl.glDisable(bgl.GL_DEPTH_TEST) + if USE_GPU_MODULE: + matrix = bpy.context.region_data.perspective_matrix + shader.uniform_float("viewProjectionMatrix", matrix @ mtx) + gpu.state.depth_test_set("LESS") + gpu.state.blend_set("ALPHA") + batch.draw(shader) + gpu.state.blend_set("NONE") + gpu.state.depth_test_set("NONE") + else: + matrix = bpy.context.region_data.perspective_matrix + shader.uniform_float("modelMatrix", mtx) + shader.uniform_float("viewProjectionMatrix", matrix) + bgl.glEnable(bgl.GL_DEPTH_TEST) + batch.draw(shader) + bgl.glDisable(bgl.GL_DEPTH_TEST) def draw_line_shape(ob, shader, pts, indices): do_draw = ((ob in bpy.context.selected_objects) or (prefs_utils.get_pref('rman_viewport_lights_draw_wireframe'))) if do_draw: batch = batch_for_shader(shader, 'LINES', {"pos": pts}, indices=indices) - bgl.glEnable(bgl.GL_DEPTH_TEST) - bgl.glEnable(bgl.GL_BLEND) - batch.draw(shader) - bgl.glDisable(bgl.GL_DEPTH_TEST) - bgl.glDisable(bgl.GL_BLEND) - + if USE_GPU_MODULE: + gpu.state.depth_test_set("LESS") + gpu.state.blend_set("ALPHA") + batch.draw(shader) + gpu.state.depth_test_set("NONE") + gpu.state.blend_set("NONE") + else: + bgl.glEnable(bgl.GL_DEPTH_TEST) + bgl.glEnable(bgl.GL_BLEND) + batch.draw(shader) + bgl.glDisable(bgl.GL_DEPTH_TEST) + bgl.glDisable(bgl.GL_BLEND) + def draw_rect_light(ob): global _FRUSTUM_DRAW_HELPER_ _SHADER_.bind() @@ -1562,7 +1674,10 @@ def draw(): break if not still_exists: - bgl.glDeleteTextures(1, v) + if USE_GPU_MODULE: + del v + else: + bgl.glDeleteTextures(1, v) remove_textures.append(k) for k in remove_textures: @@ -1575,7 +1690,10 @@ def clear_gl_tex_cache(bl_scene=None): if _PRMAN_TEX_CACHE_: rfb_log().debug("Clearing _PRMAN_TEX_CACHE_.") for k, v in _PRMAN_TEX_CACHE_.items(): - bgl.glDeleteTextures(1, v) + if USE_GPU_MODULE: + del v + else: + bgl.glDeleteTextures(1, v) _PRMAN_TEX_CACHE_.clear() def register():