From 852d77159e8b48213443b41d62d7df434c74afa6 Mon Sep 17 00:00:00 2001 From: Terry Jia Date: Wed, 11 Mar 2026 22:12:20 -0700 Subject: [PATCH] fix: prevent WebGLRenderer leak in app mode 3D preview (#9766) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Reuse the Load3d instance when switching between 3D results in app mode instead of creating a new WebGLRenderer each time. Add onUnmounted cleanup to Preview3d to release WebGL resources when the component is removed. ## Screenshots (if applicable) before https://github.com/user-attachments/assets/c9818d10-941f-4994-9b48-2710c88454e7 after https://github.com/user-attachments/assets/36361763-6800-4bc8-8089-14d64b7fcd16 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9766-fix-prevent-WebGLRenderer-leak-in-app-mode-3D-preview-3216d73d365081e19305d7255b71bc49) by [Unito](https://www.unito.io) --- src/composables/useLoad3dViewer.ts | 25 ++++++++++++++++++- .../extensions/linearMode/Preview3d.vue | 6 ++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/composables/useLoad3dViewer.ts b/src/composables/useLoad3dViewer.ts index 94e4ebc24c..37b7e14a61 100644 --- a/src/composables/useLoad3dViewer.ts +++ b/src/composables/useLoad3dViewer.ts @@ -357,7 +357,8 @@ export const useLoad3dViewer = (node?: LGraphNode) => { } /** - * Initialize viewer in standalone mode (for asset preview) + * Initialize viewer in standalone mode (for asset preview). + * Creates the Load3d instance once; subsequent calls reuse it. */ const initializeStandaloneViewer = async ( containerRef: HTMLElement, @@ -366,6 +367,11 @@ export const useLoad3dViewer = (node?: LGraphNode) => { if (!containerRef) return try { + if (load3d) { + await loadStandaloneModel(modelUrl) + return + } + isStandaloneMode.value = true load3d = new Load3d(containerRef, { @@ -392,6 +398,23 @@ export const useLoad3dViewer = (node?: LGraphNode) => { setupAnimationEvents() } catch (error) { console.error('Error initializing standalone 3D viewer:', error) + useToastStore().addAlert(t('toastMessages.failedToLoadModel')) + } + } + + /** + * Load a new model into an existing standalone viewer, + * reusing the same WebGLRenderer. + */ + const loadStandaloneModel = async (modelUrl: string) => { + if (!load3d) return + + try { + await load3d.loadModel(modelUrl) + isSplatModel.value = load3d.isSplatModel() + isPlyModel.value = load3d.isPlyModel() + } catch (error) { + console.error('Error loading model in standalone viewer:', error) useToastStore().addAlert('Failed to load 3D model') } } diff --git a/src/renderer/extensions/linearMode/Preview3d.vue b/src/renderer/extensions/linearMode/Preview3d.vue index 2c4f02d861..988ae042c6 100644 --- a/src/renderer/extensions/linearMode/Preview3d.vue +++ b/src/renderer/extensions/linearMode/Preview3d.vue @@ -1,5 +1,5 @@