diff --git a/engine/viewport.py b/engine/viewport.py index 3279ce66..2923c01e 100644 --- a/engine/viewport.py +++ b/engine/viewport.py @@ -6,70 +6,95 @@ from ..utils import render as utils_render from ..utils.errorlog import LuxCoreErrorLog from ..export.config import convert_viewport_engine -import threading -import traceback -# Executed in separate thread def start_session(engine): try: engine.session.Start() engine.viewport_start_time = time() except ReferenceError: - # Could not start render session because RenderEngine struct was deleted (caused - # by the user cancelling the viewport render before this function is called) return - except (pyluxcore.LuxCoreError, RuntimeError) as error: - # Catch LuxCore specific errors or runtime errors - engine.session = None - engine.exporter = None - engine.viewport_fatal_error = f"LuxCoreError: {str(error)}" - LuxCoreErrorLog.add_error(error) - engine.update_stats("Error: ", str(error)) - traceback.print_exc() except Exception as error: - # Generic exception handler engine.session = None engine.exporter = None - engine.viewport_fatal_error = f"General Error: {str(error)}" - LuxCoreErrorLog.add_error(error) + engine.update_stats("Error: ", str(error)) + LuxCoreErrorLog.add_error(error) + + import traceback traceback.print_exc() - # Note: Due to CPython implementation details, it's not necessary to use a lock here (this modification is atomic) engine.starting_session = False - def force_session_restart(engine): - """ - For unknown reasons, the old way of handling changes to the renderconfig causes a memory leak. - As a workaround, this method stops and deletes the session to trigger a full re-export of the scene. - """ - if engine.session: - try: - engine.session.Stop() - except Exception as error: - LuxCoreErrorLog.add_error(f"Error stopping session: {error}") - traceback.print_exc() - engine.session = None - engine.exporter = None + if engine.session is not None: + engine.session.Stop() + engine.session = None + +def apply_color_management_changes(engine, scene, context): + view_settings = scene.view_settings + exposure = view_settings.exposure + gamma = view_settings.gamma + + if engine.session is not None: + config = engine.session.GetRenderConfig() + + props = pyluxcore.Properties() + props.Set(pyluxcore.Property("film.imagepipeline.0.gamma", gamma)) + props.Set(pyluxcore.Property("film.imagepipeline.0.exposure", exposure)) + + # Apply the new properties + config.Parse(props) + + # Notify LuxCore to update the frame buffer without restarting + if hasattr(engine.session, 'UpdateFilmSettings'): + engine.session.UpdateFilmSettings(props) + +def check_color_management_changes(engine, scene): + view_settings = scene.view_settings + + # Initialize these attributes if they don't exist yet + if not hasattr(engine, 'last_exposure'): + engine.last_exposure = view_settings.exposure + if not hasattr(engine, 'last_gamma'): + engine.last_gamma = view_settings.gamma + if not hasattr(engine, 'last_view_transform'): + engine.last_view_transform = getattr(view_settings, 'view_transform', None) + + # Check if the exposure, gamma, or view transform have changed + exposure_changed = view_settings.exposure != engine.last_exposure + gamma_changed = view_settings.gamma != engine.last_gamma + view_transform_changed = view_settings.view_transform != engine.last_view_transform + + # Update the stored values for the next comparison + engine.last_exposure = view_settings.exposure + engine.last_gamma = view_settings.gamma + engine.last_view_transform = view_settings.view_transform + # If any of the values changed, return True + if exposure_changed or gamma_changed or view_transform_changed: + # Handle view transform changes separately + if view_transform_changed: + if view_settings.view_transform in ['AgX', 'Filmic', 'Khronos PBR Neutral']: + # Return False if only the view transform changed, True otherwise + return not (exposure_changed or gamma_changed) + return True + + return False def view_update(engine, context, depsgraph, changes=None): start = time() - + if engine.starting_session or engine.viewport_fatal_error: - # Prevent deadlock return LuxCoreErrorLog.clear(force_ui_update=False) if engine.session is None: if not engine.viewport_starting_message_shown: - # Let one engine.view_draw() happen so it shows a message in the UI return if not engine.is_first_viewport_start: - filmsize = utils.calc_filmsize(depsgraph.scene_eval, context) + filmsize = utils.calc_filmsize(depsgraph.scene, context) was_resized = engine.last_viewport_size != filmsize engine.last_viewport_size = filmsize @@ -77,80 +102,79 @@ def view_update(engine, context, depsgraph, changes=None): engine.time_of_last_viewport_resize = time() return elif time() - engine.time_of_last_viewport_resize < engine.VIEWPORT_RESIZE_TIMEOUT: - # Don't re-export the session before the timeout is done return - + try: print("=" * 50) print("[Engine/Viewport] New session") engine.exporter = export.Exporter() engine.session = engine.exporter.create_session(depsgraph, context, engine=engine) - # Start in separate thread to avoid blocking the UI engine.starting_session = True engine.is_first_viewport_start = False - threading.Thread(target=start_session, args=(engine,), daemon=True).start() - except (pyluxcore.LuxCoreError, RuntimeError) as error: - engine.session = None - engine.exporter = None - engine.viewport_fatal_error = f"LuxCoreError: {str(error)}" - LuxCoreErrorLog.add_error(error) - traceback.print_exc() + import _thread + _thread.start_new_thread(start_session, (engine,)) except Exception as error: engine.session = None engine.exporter = None - engine.viewport_fatal_error = f"General Error: {str(error)}" + engine.viewport_fatal_error = str(error) + + engine.update_stats("Error: ", str(error)) LuxCoreErrorLog.add_error(error) + + import traceback traceback.print_exc() + return + # Check for color management changes + if check_color_management_changes(engine, depsgraph.scene): + apply_color_management_changes(engine, depsgraph.scene, context) + engine.tag_redraw() return - # Handle changes to the scene, like geometry, camera, etc. - s = time() + # Regular session update logic changes = engine.exporter.get_changes(depsgraph, context, changes) - print(f"view_update(): checking for changes took {(time() - s) * 1000:.1f} ms") if changes: if changes & export.Change.REQUIRES_VIEW_UPDATE: + # Only restart the session if the view transform didn't change by itself force_session_restart(engine) return - s = time() + # Update session with changes engine.session = engine.exporter.update(depsgraph, context, engine.session, changes) engine.viewport_start_time = time() if engine.framebuffer: engine.framebuffer.reset_denoiser() - print(f"view_update(): applying changes took {(time() - s) * 1000:.1f} ms") - - print(f"view_update() took {(time() - start) * 1000:.1f} ms") + + print("view_update() took %.1f ms" % ((time() - start) * 1000)) def view_draw(engine, context, depsgraph): - scene = depsgraph.scene_eval - + scene = depsgraph.scene + if engine.starting_session: engine.tag_redraw() return - + if engine.viewport_fatal_error: engine.update_stats("Error:", engine.viewport_fatal_error) engine.tag_redraw() return - + if engine.session is None: config = scene.luxcore.config definitions = {} luxcore_engine, _ = convert_viewport_engine(context, scene, definitions, config) message = "" - + if luxcore_engine.endswith("OCL"): - # Create dummy renderconfig to check if we have to compile OpenCL kernels luxcore_scene = pyluxcore.Scene() definitions = { "scene.camera.type": "perspective", } luxcore_scene.Parse(utils.create_props("", definitions)) - + devices = scene.luxcore.devices definitions = { "renderengine.type": "RTPATHOCL", @@ -161,11 +185,11 @@ def view_draw(engine, context, depsgraph): } config_props = utils.create_props("", definitions) renderconfig = pyluxcore.RenderConfig(config_props, luxcore_scene) - + if not renderconfig.HasCachedKernels(): gpu_backend = utils.get_addon_preferences(context).gpu_backend message = f"Compiling {gpu_backend} kernels (just once, usually takes 15-30 minutes)" - + engine.update_stats("Starting viewport render", message) engine.viewport_starting_message_shown = True engine.tag_update() @@ -180,13 +204,14 @@ def view_draw(engine, context, depsgraph): changes = engine.exporter.get_viewport_changes(depsgraph, context) if changes & export.Change.REQUIRES_VIEW_UPDATE: + engine.tag_redraw() force_session_restart(engine) return - elif changes & export.Change.CAMERA: - engine.session = engine.exporter.update(depsgraph, context, engine.session, export.Change.CAMERA) + elif changes & (export.Change.CAMERA | export.Change.MATERIAL): + # Update session with changes + engine.session = engine.exporter.update(depsgraph, context, engine.session, changes) engine.viewport_start_time = time() - # Denoising and performance logic if utils.in_material_shading_mode(context): if not engine.session.IsInPause(): engine.session.WaitNewFrame() @@ -205,6 +230,7 @@ def view_draw(engine, context, depsgraph): framebuffer.draw(engine, context, scene) return + # Check if we need to pause the viewport render rendered_time = time() - engine.viewport_start_time halt_time = scene.luxcore.viewport.halt_time status_message = "" @@ -230,13 +256,10 @@ def view_draw(engine, context, depsgraph): framebuffer.start_denoiser(engine.session) engine.tag_redraw() except Exception as error: - status_message = f"Could not start denoiser: {error}" + status_message = "Could not start denoiser: %s" % error else: engine.session.WaitNewFrame() - try: - engine.session.UpdateStats() - except RuntimeError as error: - print(f"[Engine/Viewport] Error during UpdateStats(): {error}") + engine.session.UpdateStats() framebuffer.update(engine.session, scene) framebuffer.reset_denoiser() engine.tag_redraw()