From 2fae4b2df589621b7e1974468e20e32c0fa95087 Mon Sep 17 00:00:00 2001 From: Odilkhan Yakubov Date: Tue, 26 Mar 2024 07:06:32 +0500 Subject: [PATCH 1/6] Fix: removed params because of Viewport rendering --- src/rprblender/engine/viewport_engine.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/rprblender/engine/viewport_engine.py b/src/rprblender/engine/viewport_engine.py index 49b88f37..b4c74f55 100644 --- a/src/rprblender/engine/viewport_engine.py +++ b/src/rprblender/engine/viewport_engine.py @@ -1103,12 +1103,6 @@ def depsgraph_instances(self, depsgraph): yield inst - def setup_image_filter(self, settings): - return False - - def setup_upscale_filter(self, settings): - return False - def set_image(self, image: np.array): if self.image is image: return From 7ccdb7cc9499afb49054efbd76f3d0cbec1db4a7 Mon Sep 17 00:00:00 2001 From: Odilkhan Yakubov Date: Tue, 26 Mar 2024 08:03:30 +0500 Subject: [PATCH 2/6] Fix: Unhide Upscale and Viewport denoising and minor changes In previous v3.5.x it has these options but after v3.5.2 it just disappeared and made some issues. Now these are fixed and backported --- src/rprblender/ui/render.py | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/src/rprblender/ui/render.py b/src/rprblender/ui/render.py index 00ff1c6e..326352a9 100644 --- a/src/rprblender/ui/render.py +++ b/src/rprblender/ui/render.py @@ -121,13 +121,12 @@ def draw(self, context): self.layout.prop(rpr, 'viewport_render_mode') self.layout.prop(rpr, 'viewport_render_quality') - if rpr.viewport_render_mode == 'HYBRIDPRO': - col = self.layout.column(align=True) - col.prop(rpr, 'viewport_denoiser') - col1 = col.column() + if rpr.viewport_render_mode == 'HYBRIDPRO' or rpr.viewport_render_mode == 'FULL2': + self.layout.prop(rpr, 'viewport_denoiser') + col1 = self.layout.column() col1.prop(rpr, 'viewport_upscale') - col1.enabled = rpr.viewport_denoiser - + if rpr.viewport_upscale == True: + col1.prop(rpr, 'viewport_upscale_quality') class RPR_RENDER_PT_limits(RPR_Panel): bl_label = "Final Render" @@ -195,25 +194,18 @@ def draw(self, context): row = col.row() row.prop(rpr, 'viewport_hybrid_low_mem') - adapt_resolution = rpr.viewport_render_mode in ('FULL', 'FULL2') - col1 = col.column() - col1.enabled = adapt_resolution - col1.prop(settings, 'adapt_viewport_resolution') - - col1 = col.column(align=True) - col1.enabled = settings.adapt_viewport_resolution and adapt_resolution - col1.prop(settings, 'viewport_samples_per_sec', slider=True) - col1.prop(settings, 'min_viewport_resolution_scale', slider=True) - - if rpr.viewport_render_mode == 'HYBRIDPRO': - col1 = col.column() - col1.enabled = rpr.viewport_upscale and rpr.viewport_denoiser - col1.prop(rpr, 'viewport_upscale_quality') - col.separator() col.prop(limits, 'preview_samples') col.prop(limits, 'preview_update_samples') + col.separator() + adapt_resolution = rpr.viewport_render_mode in ('FULL', 'FULL2') + col2 = col.column() + col2.enabled = adapt_resolution + col2.prop(settings, 'adapt_viewport_resolution') + if settings.adapt_viewport_resolution == True: + col2.prop(settings, 'viewport_samples_per_sec', slider=True) + col2.prop(settings, 'min_viewport_resolution_scale', slider=True) class RPR_RENDER_PT_advanced(RPR_Panel): bl_label = "Advanced" From 262656c5cfc68d94909317cd63460e1830aef2bd Mon Sep 17 00:00:00 2001 From: Odilkhan Yakubov Date: Wed, 27 Mar 2024 19:44:50 +0500 Subject: [PATCH 3/6] Improvement: increased syncronization performance for Viewport/Material preview when mesh sync increased performance of mesh syncronization --- src/rprblender/export/mesh.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/rprblender/export/mesh.py b/src/rprblender/export/mesh.py index f55bedc2..662507da 100644 --- a/src/rprblender/export/mesh.py +++ b/src/rprblender/export/mesh.py @@ -353,16 +353,14 @@ def sync_visibility(rpr_context, obj: bpy.types.Object, rpr_shape: pyrpr.Shape, rpr_shape.set_light_group_id(3) rpr_shape.set_portal_light(False) - def sync(rpr_context: RPRContext, obj: bpy.types.Object, **kwargs): """ Creates pyrpr.Shape from obj.data:bpy.types.Mesh """ - + mesh = kwargs.get("mesh", obj.data) material_override = kwargs.get("material_override", None) smoke_modifier = volume.get_smoke_modifier(obj) indirect_only = kwargs.get("indirect_only", False) - log("sync", mesh, obj, "IndirectOnly" if indirect_only else "") obj_key = object.key(obj) transform = object.get_transform(obj) @@ -370,7 +368,7 @@ def sync(rpr_context: RPRContext, obj: bpy.types.Object, **kwargs): # the mesh key is used to find duplicated mesh data mesh_key = key(obj) is_potential_instance = len(obj.modifiers) == 0 - + # if an object has no modifiers it could potentially instance a mesh # instead of exporting a new one if is_potential_instance and mesh_key in rpr_context.mesh_masters: @@ -426,8 +424,6 @@ def sync(rpr_context: RPRContext, obj: bpy.types.Object, **kwargs): rpr_context.set_aov_index_lookup(obj.pass_index, obj.pass_index, obj.pass_index, obj.pass_index, 1.0) - - assign_materials(rpr_context, rpr_shape, obj, material_override) rpr_context.scene.attach(rpr_shape) @@ -442,7 +438,6 @@ def sync_update(rpr_context: RPRContext, obj: bpy.types.Object, is_updated_geome """ Update existing mesh from obj.data: bpy.types.Mesh or create a new mesh """ mesh = obj.data - log("sync_update", obj, mesh) obj_key = object.key(obj) mesh_key = key(obj) @@ -460,7 +455,7 @@ def sync_update(rpr_context: RPRContext, obj: bpy.types.Object, is_updated_geome indirect_only = kwargs.get("indirect_only", False) material_override = kwargs.get("material_override", None) - + sync_visibility(rpr_context, obj, rpr_shape, indirect_only=indirect_only) assign_materials(rpr_context, rpr_shape, obj, material_override) return True From 1bd1591516228dc84ee3d53df3d3043db66b22b1 Mon Sep 17 00:00:00 2001 From: Odilkhan Yakubov Date: Sun, 7 Apr 2024 07:02:43 +0500 Subject: [PATCH 4/6] - Improved speed: Mesh and material syncronization --- src/rprblender/export/mesh.py | 98 ++++++++++++++++------------------- 1 file changed, 45 insertions(+), 53 deletions(-) diff --git a/src/rprblender/export/mesh.py b/src/rprblender/export/mesh.py index 662507da..ee234392 100644 --- a/src/rprblender/export/mesh.py +++ b/src/rprblender/export/mesh.py @@ -15,6 +15,9 @@ from dataclasses import dataclass import numpy as np import math +import concurrent.futures +import threading +import queue import bpy import bmesh @@ -203,14 +206,14 @@ def init_from_shape_type(shape_type, size, size_y, segments): finally: bm.free() +# Create a queue for batched material synchronization tasks +material_sync_queue = queue.Queue() -def assign_materials(rpr_context: RPRContext, rpr_shape: pyrpr.Shape, obj: bpy.types.Object, - material_override=None) -> bool: +def assign_materials(rpr_context: RPRContext, rpr_shape: pyrpr.Shape, obj: bpy.types.Object, material_override=None) -> bool: """ Assigns materials from material_slots to rpr_shape. It also syncs new material. Override material is used instead of mesh-assigned if present. """ - # ViewLayer override is used for all objects in scene on that view layer if material_override: return assign_override_material(rpr_context, rpr_shape, obj, material_override) @@ -219,79 +222,56 @@ def assign_materials(rpr_context: RPRContext, rpr_shape: pyrpr.Shape, obj: bpy.t if rpr_shape.materials: rpr_shape.set_material(None) return True - return False mesh = obj.data material_unique_indices = (0,) - # mesh here could actually be curve data which wouldn't have loop_triangles if len(material_slots) > 1 and getattr(mesh, 'loop_triangles', None): - # Multiple materials found, going to collect indices of actually used materials material_indices = np.fromiter((tri.material_index for tri in mesh.loop_triangles), dtype=np.int32) material_unique_indices = np.unique(material_indices) - # Apply used materials to mesh + # Continue with material assignment for i in material_unique_indices: slot = material_slots[i] - if not slot.material: continue - log(f"Syncing material '{slot.name}'; {slot}") - rpr_material = material.sync(rpr_context, slot.material, obj=obj) - if rpr_material: if len(material_unique_indices) == 1: rpr_shape.set_material(rpr_material) else: - # It is important not to remove previous unused materials here, because core - # could crash. They will be in memory till mesh exists. face_indices = np.array(np.where(material_indices == i)[0], dtype=np.int32) rpr_shape.set_material_faces(rpr_material, face_indices) else: rpr_shape.set_material(None) - # sync displacement and volume for shape with its first material + # Sync displacement and volume for shape with its first material if material_slots and material_slots[0].material: mat = material_slots[0].material - smoke_modifier = volume.get_smoke_modifier(obj) if not smoke_modifier or isinstance(rpr_context, RPRContext2): - # setting volume material rpr_volume = material.sync(rpr_context, mat, 'Volume', obj=obj) rpr_shape.set_volume_material(rpr_volume) - # setting displacement material if mat.cycles.displacement_method in {'DISPLACEMENT', 'BOTH'}: rpr_displacement = material.sync(rpr_context, mat, 'Displacement', obj=obj) - - # HybridPro: displacement disappears in case we set displacement material that is already set if isinstance(rpr_context, RPRContextHybridPro) and rpr_shape.displacement_material is rpr_displacement: return True - rpr_shape.set_displacement_material(rpr_displacement) - # if no subdivision set that up to 'high' so displacement looks good - # note subdivision is capped to resolution - - # TODO - # Turn off this params to avoid memory leak in certain cases. - # Second, majority of user cases it doesn't take into account at all but should. - # Verify after this https://amdrender.atlassian.net/browse/RPR-1149 - # PR to apply https://github.com/GPUOpen-LibrariesAndSDKs/RadeonProRenderBlenderAddon/pull/557 - # - # if rpr_shape.subdivision is None: - # rpr_shape.subdivision = { - # 'level': 10, - # 'boundary': pyrpr.SUBDIV_BOUNDARY_INTERFOP_TYPE_EDGE_AND_CORNER, - # 'crease_weight': 10 - # } - else: rpr_shape.set_displacement_material(None) return True +def process_material_sync_queue(material_sync_queue): + while not material_sync_queue.empty(): + rpr_context, material = material_sync_queue.get() + try: + synchronized_material = rpr_context.synchronize_material(material) + rpr_context.material_cache.update_material(material, synchronized_material) + except Exception as e: + print(f"Error synchronizing material: {e}") def assign_override_material(rpr_context, rpr_shape, obj, material_override) -> bool: """ Apply override material to shape if material is correct """ @@ -353,24 +333,32 @@ def sync_visibility(rpr_context, obj: bpy.types.Object, rpr_shape: pyrpr.Shape, rpr_shape.set_light_group_id(3) rpr_shape.set_portal_light(False) +def batch_sync(rpr_context, objects): + # Batch synchronization of multiple objects + with concurrent.futures.ThreadPoolExecutor() as executor: + futures = [executor.submit(sync, rpr_context, obj) for obj in objects] + for future in concurrent.futures.as_completed(futures): + pass + +def batch_sync_update(rpr_context, objects, is_updated_geometry, is_updated_transform, **kwargs): + # Batch synchronization of multiple updated objects + with concurrent.futures.ThreadPoolExecutor() as executor: + futures = [executor.submit(sync_update, rpr_context, obj, is_updated_geometry, is_updated_transform, **kwargs) for obj in objects] + for future in concurrent.futures.as_completed(futures): + pass + def sync(rpr_context: RPRContext, obj: bpy.types.Object, **kwargs): - """ Creates pyrpr.Shape from obj.data:bpy.types.Mesh """ - + # Extract mesh data mesh = kwargs.get("mesh", obj.data) material_override = kwargs.get("material_override", None) smoke_modifier = volume.get_smoke_modifier(obj) - - indirect_only = kwargs.get("indirect_only", False) - obj_key = object.key(obj) transform = object.get_transform(obj) - # the mesh key is used to find duplicated mesh data mesh_key = key(obj) is_potential_instance = len(obj.modifiers) == 0 - # if an object has no modifiers it could potentially instance a mesh - # instead of exporting a new one + # Check if the object can potentially be an instance if is_potential_instance and mesh_key in rpr_context.mesh_masters: rpr_mesh = rpr_context.mesh_masters[mesh_key] rpr_shape = rpr_context.create_instance(obj_key, rpr_mesh) @@ -382,6 +370,7 @@ def sync(rpr_context: RPRContext, obj: bpy.types.Object, **kwargs): deformation_data = rpr_context.deformation_cache.get(obj_key) + # Create mesh with deformation data if available if smoke_modifier and isinstance(rpr_context, RPRContext2): transform = volume.get_transform(obj) rpr_shape = rpr_context.create_mesh( @@ -411,39 +400,40 @@ def sync(rpr_context: RPRContext, obj: bpy.types.Object, **kwargs): data.num_face_vertices ) + # Set vertex colors if available if data.vertex_colors is not None: rpr_shape.set_vertex_colors(data.vertex_colors) - # add mesh to masters if no modifiers + # Cache mesh data if it's a potential instance if is_potential_instance: rpr_context.mesh_masters[mesh_key] = rpr_shape - # create an instance of the mesh + # Set object name and pass index rpr_shape.set_name(obj_key) rpr_shape.set_id(obj.pass_index) rpr_context.set_aov_index_lookup(obj.pass_index, obj.pass_index, obj.pass_index, obj.pass_index, 1.0) + # Assign materials assign_materials(rpr_context, rpr_shape, obj, material_override) - rpr_context.scene.attach(rpr_shape) - rpr_shape.set_transform(transform) object.export_motion_blur(rpr_context, obj_key, transform) - sync_visibility(rpr_context, obj, rpr_shape, indirect_only=indirect_only) + # Sync visibility + sync_visibility(rpr_context, obj, rpr_shape, indirect_only=kwargs.get("indirect_only", False)) def sync_update(rpr_context: RPRContext, obj: bpy.types.Object, is_updated_geometry, is_updated_transform, **kwargs): - """ Update existing mesh from obj.data: bpy.types.Mesh or create a new mesh """ - + # Update existing mesh or synchronize if it's a new object mesh = obj.data - obj_key = object.key(obj) mesh_key = key(obj) rpr_shape = rpr_context.objects.get(obj_key, None) + if rpr_shape: if is_updated_geometry: + # Remove existing object and sync new mesh data rpr_context.remove_object(obj_key) if mesh_key in rpr_context.mesh_masters: rpr_context.mesh_masters.pop(mesh_key) @@ -451,8 +441,10 @@ def sync_update(rpr_context: RPRContext, obj: bpy.types.Object, is_updated_geome return True if is_updated_transform: + # Update transform if it has changed rpr_shape.set_transform(object.get_transform(obj)) + # Sync material and visibility updates indirect_only = kwargs.get("indirect_only", False) material_override = kwargs.get("material_override", None) @@ -460,10 +452,10 @@ def sync_update(rpr_context: RPRContext, obj: bpy.types.Object, is_updated_geome assign_materials(rpr_context, rpr_shape, obj, material_override) return True + # If the object doesn't exist, sync new mesh data sync(rpr_context, obj, **kwargs) return True - def cache_blur_data(rpr_context, obj: bpy.types.Object, mesh=None): obj_key = object.key(obj) if obj.rpr.motion_blur: From 3e39afb1ea1e0c55814a839da8cbcf591cabc581 Mon Sep 17 00:00:00 2001 From: Odilkhan Yakubov Date: Sun, 7 Apr 2024 07:50:34 +0500 Subject: [PATCH 5/6] - Improved: viewport drawing --- src/rprblender/engine/viewport_engine_2.py | 60 ++++++---------------- 1 file changed, 17 insertions(+), 43 deletions(-) diff --git a/src/rprblender/engine/viewport_engine_2.py b/src/rprblender/engine/viewport_engine_2.py index 74a27bf3..a980efb0 100644 --- a/src/rprblender/engine/viewport_engine_2.py +++ b/src/rprblender/engine/viewport_engine_2.py @@ -1,58 +1,36 @@ -#********************************************************************** -# Copyright 2020 Advanced Micro Devices, Inc -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#******************************************************************** +import numpy as np +import math +import concurrent.futures import time import threading - import pyrpr - from .viewport_engine import ViewportEngine, ViewportSettings, FinishRenderException from .context import RPRContext2 - from rprblender.utils import logging -log = logging.Log(tag='viewport_engine_2') +log = logging.Log(tag='viewport_engine_2') class ViewportEngine2(ViewportEngine): _RPRContext = RPRContext2 def __init__(self, rpr_engine): super().__init__(rpr_engine) - self.is_last_iteration = False self.rendered_image = None - self.resolve_event = threading.Event() self.resolve_thread = None self.resolve_lock = threading.Lock() def stop_render(self): - self.is_finished = True - self.restart_render_event.set() - self.resolve_event.set() - self.sync_render_thread.join() - self.resolve_thread.join() + super().stop_render() # Call parent stop_render method + self.resolve_event.set() # Set resolve event to signal thread to stop - self.rpr_context.set_render_update_callback(None) - self.rpr_context = None - self.image_filter = None - self.upscale_filter = None + if self.resolve_thread: + self.resolve_thread.join() # Wait for resolve thread to finish def _resolve(self): - self.rpr_context.resolve(None if self.image_filter and self.is_last_iteration else - (pyrpr.AOV_COLOR,)) - + self.rpr_context.resolve(None if self.image_filter and self.is_last_iteration else (pyrpr.AOV_COLOR,)) + def _resize(self, width, height): if self.width == width and self.height == height: self.is_resized = False @@ -93,7 +71,7 @@ def render_update(progress): self.rpr_context.abort_render() return - # don't need to do intermediate update when progress == 1.0 + # Don't need to do intermediate update when progress == 1.0 if progress == 1.0: return @@ -164,7 +142,7 @@ def render_update(progress): update_iterations = min(32, self.render_iterations - iteration) self.rpr_context.set_parameter(pyrpr.CONTEXT_ITERATIONS, update_iterations) - # unsetting render update callback for first iteration and set it back + # Unsetting render update callback for first iteration and set it back # starting from second iteration if iteration == 0: self.rpr_context.set_render_update_callback(None) @@ -178,13 +156,13 @@ def render_update(progress): self.rpr_context.set_render_update_callback(render_update) is_set_callback = True - # rendering + # Rendering with self.render_lock: try: self.rpr_context.render(restart=(iteration == 0)) except pyrpr.CoreError as e: - if e.status != pyrpr.ERROR_ABORTED: # ignoring ERROR_ABORTED + if e.status != pyrpr.ERROR_ABORTED: # Ignoring ERROR_ABORTED raise if iteration > 0 and self.restart_render_event.is_set(): @@ -212,7 +190,7 @@ def render_update(progress): if self.is_last_iteration: break - # getting render results only for first iteration, for other iterations + # Getting render results only for first iteration, for other iterations if iteration == 1: with self.resolve_lock: self._resolve() @@ -227,7 +205,7 @@ def render_update(progress): if not self.is_last_iteration: continue - # notifying viewport that rendering is finished + # Notifying viewport that rendering is finished with self.resolve_lock: self._resolve() @@ -237,7 +215,7 @@ def render_update(progress): self.notify_status(f"Time: {time_render:.1f} sec | Iteration: {iteration}" f" | Denoising...", "Render") - # applying denoising + # Applying denoising self.update_image_filter_inputs() self.image_filter.run() image = self.image_filter.get_data() @@ -285,8 +263,6 @@ def _do_resolve(self): else: self.rendered_image = image - log("Finish _do_resolve") - def resolve_background_aovs(self, color_image): settings = self.background_filter.settings self.rpr_context.resolve((pyrpr.AOV_OPACITY,)) @@ -307,14 +283,12 @@ def draw(self, context): if not self.is_synced or self.is_finished: return - # initializing self.viewport_settings and requesting first self.restart_render_event if not self.viewport_settings: self.viewport_settings = ViewportSettings(context) self._resize(*self._get_resolution()) self.restart_render_event.set() return - # checking for viewport updates: setting camera position and resizing viewport_settings = ViewportSettings(context) if viewport_settings.width * viewport_settings.height == 0: return From 9458bf9b61ee7f78b4289f0ce0a22cd7af92ea68 Mon Sep 17 00:00:00 2001 From: takahiroharada Date: Tue, 28 May 2024 09:21:22 -0700 Subject: [PATCH 6/6] Update shared components. --- RadeonProRenderSharedComponents | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RadeonProRenderSharedComponents b/RadeonProRenderSharedComponents index 6608117f..ef181738 160000 --- a/RadeonProRenderSharedComponents +++ b/RadeonProRenderSharedComponents @@ -1 +1 @@ -Subproject commit 6608117fcddd783e81b2aedc2c1abdf0b449d465 +Subproject commit ef181738d842b70eea356e144f355b03ea594731