diff --git a/examples/example.css b/examples/example.css
index 47957c0f29e209..e7e2093563cb9e 100644
--- a/examples/example.css
+++ b/examples/example.css
@@ -15,7 +15,7 @@ a {
position: fixed;
top: 15px;
left: 15px;
- z-index: 1001;
+ z-index: 100;
display: grid;
grid-template-columns: 50px auto;
@@ -25,6 +25,11 @@ a {
color: #e0e0e0;
text-shadow: 1px 1px 5px rgba(0, 0, 0, .7);
font: 400 14px 'Inter', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ pointer-events: none;
+}
+
+#info > a {
+ pointer-events: auto;
}
#info > a.logo-link {
diff --git a/examples/jsm/inspector/tabs/Memory.js b/examples/jsm/inspector/tabs/Memory.js
index d9337671d47b3e..53c29510968e1d 100644
--- a/examples/jsm/inspector/tabs/Memory.js
+++ b/examples/jsm/inspector/tabs/Memory.js
@@ -80,7 +80,6 @@ class Memory extends Tab {
updateGraph( inspector ) {
const renderer = inspector.getRenderer();
- if ( ! renderer ) return;
const memory = renderer.info.memory;
@@ -95,7 +94,6 @@ class Memory extends Tab {
updateText( inspector ) {
const renderer = inspector.getRenderer();
- if ( ! renderer ) return;
const memory = renderer.info.memory;
diff --git a/examples/jsm/inspector/tabs/Viewer.js b/examples/jsm/inspector/tabs/Viewer.js
index 7028213146b11a..e72ee8ad7d9462 100644
--- a/examples/jsm/inspector/tabs/Viewer.js
+++ b/examples/jsm/inspector/tabs/Viewer.js
@@ -4,11 +4,15 @@ import { Item } from '../ui/Item.js';
import { splitPath, splitCamelCase } from '../ui/utils.js';
import { getItem, setItem } from '../Inspector.js';
-import { RendererUtils, NoToneMapping, LinearSRGBColorSpace, QuadMesh, NodeMaterial, CanvasTarget, Vector2 } from 'three/webgpu';
+import { RendererUtils, NoToneMapping, LinearSRGBColorSpace, QuadMesh, NodeMaterial, CanvasTarget, Vector2, Color } from 'three/webgpu';
import { renderOutput, vec2, vec3, vec4, Fn, screenUV, step, OnMaterialUpdate, uniform, float } from 'three/tsl';
const _size = /*@__PURE__*/ new Vector2();
+const splitIcon = '';
+const fullscreenIcon = '';
+const backIcon = '';
+
const aspectRatioUV = /*@__PURE__*/ Fn( ( [ uv, textureNode, canvasAspect ] ) => {
const textureAspect = uniform( 0 );
@@ -55,7 +59,7 @@ class Viewer extends Tab {
const backBtn = document.createElement( 'button' );
backBtn.className = 'viewer-back-btn';
backBtn.innerHTML = '';
- backBtn.title = 'Back to list';
+ backBtn.title = 'Back';
backBtn.style.display = 'none';
toolbar.appendChild( backBtn );
@@ -73,11 +77,13 @@ class Viewer extends Tab {
select.appendChild( defaultOption );
toolbar.appendChild( select );
- this.content.appendChild( toolbar );
const nodeList = new List( 'Viewer', 'Name' );
nodeList.setGridStyle( '150px minmax(200px, 2fr)' );
nodeList.domElement.style.minWidth = '400px';
+ this.nodeList = nodeList;
+
+ this.content.appendChild( toolbar );
const scrollWrapper = document.createElement( 'div' );
scrollWrapper.className = 'list-scroll-wrapper';
@@ -103,7 +109,6 @@ class Viewer extends Tab {
this.folderLibrary = new Map();
this.canvasNodes = new Map();
this.currentDataList = [];
- this.nodeList = nodeList;
this.nodes = nodes;
this.scrollWrapper = scrollWrapper;
this.fullViewerContainer = fullViewerContainer;
@@ -112,8 +117,24 @@ class Viewer extends Tab {
this.activeFullNodeId = null;
this.pendingRestoreView = true;
+ this.splitActive = false;
+ this.splitCanvasData = null;
+ this.splitOverlay = null;
+ this.splitLine = null;
+ this.splitQuad = null;
+ this.splitMaterial = null;
+ this.splitUniforms = null;
+ this.splitCanvas = null;
+ this.splitCanvasTarget = null;
+
backBtn.addEventListener( 'click', () => {
+ if ( this.splitActive ) {
+
+ this.stopSplitMode();
+
+ }
+
select.value = 'list';
this.showListView();
@@ -162,8 +183,6 @@ class Viewer extends Tab {
const renderer = this.inspector.getRenderer();
- if ( ! renderer || ! renderer.domElement ) return;
-
if ( e.isForwarded ) return;
// Block native event from reaching other document-level listeners (OrbitControls)
@@ -220,6 +239,12 @@ class Viewer extends Tab {
this.activeSourceCanvas = null;
this.activePointerIds.clear();
+ if ( this.splitActive ) {
+
+ this.stopSplitMode();
+
+ }
+
}
addNodeItem( canvasData ) {
@@ -249,6 +274,13 @@ class Viewer extends Tab {
viewBtn.onclick = ( e ) => {
e.stopPropagation();
+
+ if ( this.splitActive ) {
+
+ this.stopSplitMode();
+
+ }
+
this.select.value = canvasData.id;
this.showNodeView( canvasData.id );
this.saveLastView();
@@ -259,45 +291,60 @@ class Viewer extends Tab {
const fullscreenBtn = document.createElement( 'button' );
fullscreenBtn.className = 'node-canvas-fullscreen-btn';
fullscreenBtn.title = 'Fullscreen view';
- fullscreenBtn.innerHTML = '';
+ fullscreenBtn.innerHTML = fullscreenIcon;
fullscreenBtn.onclick = ( e ) => {
e.stopPropagation();
- this.select.value = canvasData.id;
- this.showNodeView( canvasData.id );
- if ( this.profiler && ! this.profiler.panel.classList.contains( 'maximized' ) ) {
+ if ( this.splitActive && this.splitCanvasData === canvasData && this.splitFullscreen ) {
- this.profiler.toggleMaximize();
- this.maximizedByFullscreenButton = true;
+ this.stopSplitMode();
+
+ } else {
+
+ this.splitFullscreen = true;
+ this.startSplitMode( canvasData );
- if ( ! this._maximizeListenerAdded && this.profiler.maximizeBtn ) {
+ }
- this.profiler.maximizeBtn.addEventListener( 'click', () => {
+ };
- this.maximizedByFullscreenButton = false;
+ // Split screen button
+ const splitBtn = document.createElement( 'button' );
+ splitBtn.className = 'node-canvas-split-btn';
+ splitBtn.title = 'Interactive split screen';
+ splitBtn.innerHTML = splitIcon;
- } );
- this._maximizeListenerAdded = true;
+ splitBtn.onclick = ( e ) => {
- }
+ e.stopPropagation();
- }
+ if ( this.splitActive && this.splitCanvasData === canvasData && ! this.splitFullscreen ) {
- this.saveLastView();
+ this.stopSplitMode();
+
+ } else {
+
+ this.splitFullscreen = false;
+ this.startSplitMode( canvasData );
+
+ }
};
wrapper.appendChild( domElement );
wrapper.appendChild( viewBtn );
wrapper.appendChild( fullscreenBtn );
+ wrapper.appendChild( splitBtn );
this.setupEventForwarding( domElement );
// Store elements in canvasData for access
canvasData.domElement = domElement;
canvasData.wrapperElement = wrapper;
+ canvasData.splitBtn = splitBtn;
+ canvasData.fullscreenBtn = fullscreenBtn;
item = new Item( wrapper, name );
item.itemRow.children[ 1 ].style[ 'justify-content' ] = 'flex-start';
@@ -441,6 +488,45 @@ class Viewer extends Tab {
}
+ init( inspector ) {
+
+ super.init( inspector );
+
+ const updateLayoutFromPosition = () => {
+
+ const position = inspector.profiler.position;
+ const isHorizontal = position === 'top' || position === 'bottom';
+ this.setViewMode( isHorizontal ? 'grid' : 'list' );
+
+ };
+
+ inspector.profiler.addEventListener( 'resize', updateLayoutFromPosition );
+
+ // Run initially once
+ updateLayoutFromPosition();
+
+ }
+
+ setViewMode( mode ) {
+
+ if ( this.nodeList ) {
+
+ if ( mode === 'grid' ) {
+
+ this.nodeList.domElement.style.minWidth = '0';
+
+ } else {
+
+ this.nodeList.domElement.style.minWidth = '400px';
+
+ }
+
+ this.nodeList.setViewMode( mode );
+
+ }
+
+ }
+
showListView() {
if ( this.activeFullNodeId ) {
@@ -514,6 +600,273 @@ class Viewer extends Tab {
}
+ get isActive() {
+
+ if ( this.splitActive ) return true;
+
+ return super.isActive;
+
+ }
+
+ set isActive( value ) {
+
+ super.isActive = value;
+
+ }
+
+ startSplitMode( canvasData ) {
+
+ this.splitActive = true;
+ this.splitCanvasData = canvasData;
+
+ const renderer = this.inspector.getRenderer();
+ const mainCanvas = renderer.domElement;
+ const rect = mainCanvas.getBoundingClientRect();
+
+ // Position target canvas on top of main canvas
+ if ( ! this.splitCanvas ) {
+
+ this.splitCanvas = document.createElement( 'canvas' );
+ this.splitCanvas.style.position = 'fixed';
+ this.splitCanvas.style.pointerEvents = 'none';
+ this.splitCanvas.style.zIndex = '998';
+
+ this.splitCanvasTarget = new CanvasTarget( this.splitCanvas );
+ this.splitCanvasTarget.setPixelRatio( window.devicePixelRatio );
+
+ }
+
+ this.splitCanvas.style.left = `${ rect.left }px`;
+ this.splitCanvas.style.top = `${ rect.top }px`;
+ this.splitCanvas.style.width = `${ rect.width }px`;
+ this.splitCanvas.style.height = `${ rect.height }px`;
+
+ this.splitCanvasTarget.setSize( rect.width, rect.height );
+ renderer.backend.delete( this.splitCanvasTarget );
+
+ document.body.appendChild( this.splitCanvas );
+
+ // Overlay divider line (only in split/non-fullscreen mode)
+ if ( ! this.splitFullscreen ) {
+
+ if ( ! this.splitOverlay ) {
+
+ const overlay = document.createElement( 'div' );
+ overlay.className = 'split-screen-overlay';
+
+ const line = document.createElement( 'div' );
+ line.className = 'split-screen-line';
+
+ overlay.appendChild( line );
+
+ let isDragging = false;
+
+ const onPointerDown = ( e ) => {
+
+ isDragging = true;
+ e.preventDefault();
+
+ };
+
+ const onPointerMove = ( e ) => {
+
+ if ( ! isDragging ) return;
+
+ const minPadding = 10; // Keep the line at least 15px away from the edges for easy grabbing
+ const x = Math.max( minPadding, Math.min( window.innerWidth - minPadding, e.clientX ) );
+ const pct = x / window.innerWidth;
+ this.splitX = pct;
+ line.style.left = `${ pct * 100 }%`;
+
+ if ( this.splitUniforms && this.splitUniforms.splitX ) {
+
+ this.splitUniforms.splitX.value = pct;
+
+ }
+
+ };
+
+ const onPointerUp = () => {
+
+ isDragging = false;
+
+ };
+
+ line.addEventListener( 'pointerdown', onPointerDown );
+ window.addEventListener( 'pointermove', onPointerMove );
+ window.addEventListener( 'pointerup', onPointerUp );
+
+ this.splitOverlay = overlay;
+ this.splitLine = line;
+
+ }
+
+ this.splitLine.style.left = '50%';
+
+ this.profiler.domElement.appendChild( this.splitOverlay );
+
+ } else {
+
+ // If we transitioned from split to fullscreen, remove the overlay slider
+ if ( this.splitOverlay && this.splitOverlay.parentElement ) {
+
+ this.splitOverlay.parentElement.removeChild( this.splitOverlay );
+
+ }
+
+ }
+
+ if ( ! this.splitUniforms ) {
+
+ this.splitUniforms = {
+ splitX: uniform( 0.5 ),
+ viewportWidth: uniform( window.innerWidth )
+ };
+
+ }
+
+ this.splitUniforms.splitX.value = 0.5;
+ this.splitUniforms.viewportWidth.value = renderer.domElement.width;
+
+ // Recreate or setup material for split screen comparison
+ const node = canvasData.node;
+ const target = node.context( { getUV: () => screenUV } );
+ const targetColor = renderOutput( vec4( vec3( target ), 1 ), NoToneMapping, renderer.outputColorSpace );
+ const targetColorCtx = targetColor.context( { inspector: true } );
+
+ let finalColor;
+
+ if ( this.splitFullscreen ) {
+
+ finalColor = targetColorCtx;
+
+ } else {
+
+ const thickness = float( 1 ).div( this.splitUniforms.viewportWidth );
+ const isLine = screenUV.x.sub( this.splitUniforms.splitX ).abs().lessThan( thickness );
+ finalColor = Fn( () => {
+
+ screenUV.x.lessThan( this.splitUniforms.splitX ).discard();
+
+ return isLine.select( vec4( 0.29, 0.29, 0.35, 1.0 ), targetColorCtx );
+
+ } )();
+
+ }
+
+ if ( this.splitMaterial ) {
+
+ this.splitMaterial.dispose();
+
+ }
+
+ this.splitMaterial = new NodeMaterial();
+ this.splitMaterial.outputNode = finalColor;
+ this.splitMaterial.depthTest = false;
+ this.splitMaterial.depthWrite = false;
+
+ if ( this.splitQuad ) {
+
+ this.splitQuad.material = this.splitMaterial;
+
+ } else {
+
+ this.splitQuad = new QuadMesh( this.splitMaterial );
+
+ }
+
+ this.updateSplitButtonsState();
+
+ }
+
+ stopSplitMode() {
+
+ this.splitActive = false;
+ this.splitCanvasData = null;
+ this.splitFullscreen = false;
+
+ if ( this.splitOverlay && this.splitOverlay.parentElement ) {
+
+ this.splitOverlay.parentElement.removeChild( this.splitOverlay );
+
+ }
+
+ if ( this.splitCanvas && this.splitCanvas.parentElement ) {
+
+ this.splitCanvas.parentElement.removeChild( this.splitCanvas );
+
+ }
+
+ this.updateSplitButtonsState();
+
+ }
+
+ updateSplitButtonsState() {
+
+ if ( this.splitActive ) {
+
+ this.backBtn.style.display = '';
+
+ } else {
+
+ if ( this.select.value === 'list' ) {
+
+ this.backBtn.style.display = 'none';
+
+ } else {
+
+ this.backBtn.style.display = '';
+
+ }
+
+ }
+
+ for ( const canvasData of this.canvasNodes.values() ) {
+
+ if ( ! canvasData.splitBtn || ! canvasData.fullscreenBtn ) continue;
+
+ const isSelected = this.splitActive && this.splitCanvasData === canvasData;
+
+ if ( isSelected ) {
+
+ if ( this.splitFullscreen ) {
+
+ canvasData.fullscreenBtn.classList.add( 'active' );
+ canvasData.fullscreenBtn.innerHTML = backIcon;
+ canvasData.fullscreenBtn.title = 'Exit Fullscreen';
+
+ canvasData.splitBtn.classList.remove( 'active' );
+ canvasData.splitBtn.innerHTML = splitIcon;
+ canvasData.splitBtn.title = 'Interactive split screen';
+
+ } else {
+
+ canvasData.splitBtn.classList.add( 'active' );
+ canvasData.splitBtn.innerHTML = backIcon;
+ canvasData.splitBtn.title = 'Exit Split Screen';
+
+ canvasData.fullscreenBtn.classList.remove( 'active' );
+ canvasData.fullscreenBtn.innerHTML = fullscreenIcon;
+ canvasData.fullscreenBtn.title = 'Fullscreen view';
+
+ }
+
+ } else {
+
+ canvasData.splitBtn.classList.remove( 'active' );
+ canvasData.splitBtn.innerHTML = splitIcon;
+ canvasData.splitBtn.title = 'Interactive split screen';
+
+ canvasData.fullscreenBtn.classList.remove( 'active' );
+ canvasData.fullscreenBtn.innerHTML = fullscreenIcon;
+ canvasData.fullscreenBtn.title = 'Fullscreen view';
+
+ }
+
+ }
+
+ }
+
getCanvasDataByNode( renderer, node ) {
let canvasData = this.canvasNodes.get( node );
@@ -575,6 +928,48 @@ class Viewer extends Tab {
update( inspector ) {
const renderer = inspector.getRenderer();
+
+ if ( this.splitActive ) {
+
+ // Resize canvas target to match the main canvas if window resized
+ const mainCanvas = renderer.domElement;
+ const rect = mainCanvas.getBoundingClientRect();
+
+ if ( this.splitCanvasTarget.domElement.width !== rect.width || this.splitCanvasTarget.domElement.height !== rect.height ) {
+
+ this.splitCanvas.style.width = `${ rect.width }px`;
+ this.splitCanvas.style.height = `${ rect.height }px`;
+ this.splitCanvas.style.left = `${ rect.left }px`;
+ this.splitCanvas.style.top = `${ rect.top }px`;
+
+ this.splitCanvasTarget.setSize( rect.width, rect.height );
+
+ renderer.backend.delete( this.splitCanvasTarget );
+
+ }
+
+ // Render the split quad to the splitCanvasTarget
+ const previousCanvasTarget = renderer.getCanvasTarget();
+ const previousClearColor = renderer.getClearColor( new Color() );
+ const previousClearAlpha = renderer.getClearAlpha();
+
+ const state = RendererUtils.resetRendererState( renderer );
+
+ renderer.toneMapping = NoToneMapping;
+ renderer.outputColorSpace = LinearSRGBColorSpace;
+
+ renderer.setCanvasTarget( this.splitCanvasTarget );
+ renderer.setClearColor( 0x000000, 0 );
+
+ this.splitQuad.render( renderer );
+
+ renderer.setCanvasTarget( previousCanvasTarget );
+ renderer.setClearColor( previousClearColor, previousClearAlpha );
+
+ RendererUtils.restoreRendererState( renderer, state );
+
+ }
+
const nodes = inspector.getNodes();
if ( nodes.length > 0 ) {
diff --git a/examples/jsm/inspector/ui/List.js b/examples/jsm/inspector/ui/List.js
index 53d574a9690a8f..62f8e43ee809b6 100644
--- a/examples/jsm/inspector/ui/List.js
+++ b/examples/jsm/inspector/ui/List.js
@@ -39,6 +39,20 @@ export class List {
}
+ setViewMode( mode ) {
+
+ if ( mode === 'grid' ) {
+
+ this.domElement.classList.add( 'grid-mode' );
+
+ } else {
+
+ this.domElement.classList.remove( 'grid-mode' );
+
+ }
+
+ }
+
add( item ) {
if ( item.parent !== null ) {
diff --git a/examples/jsm/inspector/ui/Profiler.js b/examples/jsm/inspector/ui/Profiler.js
index 138ae798d710ca..4e5c7f70295466 100644
--- a/examples/jsm/inspector/ui/Profiler.js
+++ b/examples/jsm/inspector/ui/Profiler.js
@@ -2004,6 +2004,9 @@ export class Profiler extends EventDispatcher {
// Update panel size after loading layout
this.updatePanelSize();
+ // Update widget position (toggle and mini panel alignment)
+ this.updateWidgetPosition();
+
// Ensure initial open state applies to mini panel as well
if ( this.panel.classList.contains( 'visible' ) ) {
diff --git a/examples/jsm/inspector/ui/Style.js b/examples/jsm/inspector/ui/Style.js
index c4d888de0427a3..ceeb16b324b299 100644
--- a/examples/jsm/inspector/ui/Style.js
+++ b/examples/jsm/inspector/ui/Style.js
@@ -53,7 +53,7 @@ export class Style {
border-radius: 12px 6px 6px 12px;
color: var(--text-primary);
cursor: pointer;
- z-index: 1001;
+ z-index: 1002;
transition: all 0.2s ease-in-out;
/*font-size: 14px;*/
font-size: 15px;
@@ -596,6 +596,7 @@ export class Style {
.profiler-panel.maximized {
height: 100%;
+ z-index: 10000 !important;
}
/* Position-specific styles */
@@ -1197,6 +1198,7 @@ export class Style {
.list-children-container.closed {
max-height: 0;
+ display: none !important;
}
.item-toggler {
@@ -2047,6 +2049,140 @@ export class Style {
touch-action: none;
}
+ .node-canvas-wrapper .node-canvas-split-btn {
+ position: absolute;
+ top: 5px;
+ left: 5px;
+ background: rgba(30, 30, 36, 0.85);
+ border: 1px solid var(--profiler-border);
+ color: var(--text-primary);
+ border-radius: 4px;
+ padding: 4px;
+ cursor: pointer;
+ opacity: 1;
+ transition: background-color 0.2s, border-color 0.2s, color 0.2s;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 10;
+ }
+
+ .node-canvas-wrapper .node-canvas-split-btn:hover {
+ background-color: var(--color-accent);
+ border-color: var(--color-accent);
+ color: white;
+ }
+
+ .node-canvas-wrapper .node-canvas-split-btn.active,
+ .node-canvas-wrapper .node-canvas-fullscreen-btn.active {
+ background-color: var(--color-accent) !important;
+ border-color: var(--color-accent) !important;
+ color: white !important;
+ }
+
+ .split-screen-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none !important;
+ z-index: 999;
+ touch-action: none;
+ }
+
+ .split-screen-line {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 4px;
+ margin-left: -2px;
+ left: 50%;
+ background: var(--profiler-border);
+ cursor: ew-resize;
+ pointer-events: auto !important;
+ z-index: 10;
+ touch-action: none;
+ }
+
+ .split-screen-line::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: -10px;
+ width: 24px;
+ background: transparent;
+ cursor: ew-resize;
+ }
+
+ /* Grid Mode styles for List component */
+ .list-scroll-wrapper:has(> .list-container.grid-mode) {
+ width: 100% !important;
+ }
+
+ .list-container.grid-mode {
+ min-width: 0 !important;
+ width: 100% !important;
+ box-sizing: border-box;
+ }
+
+ .list-container.grid-mode .list-header {
+ display: none !important;
+ }
+
+ .list-container.grid-mode .list-children-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 15px;
+ padding-left: 0 !important;
+ margin-top: 10px;
+ margin-bottom: 15px;
+ width: 100%;
+ box-sizing: border-box;
+ }
+
+ .list-container.grid-mode .list-children-container > .list-item-wrapper {
+ display: inline-block;
+ width: 160px;
+ margin: 0;
+ }
+
+ .list-container.grid-mode .list-children-container > .list-item-wrapper > .list-item-row {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-start;
+ /*background-color: var(--profiler-header);
+ border: 1px solid var(--profiler-border);*/
+ border-radius: 6px;
+ padding: 8px;
+ gap: 8px;
+ width: 100%;
+ box-sizing: border-box;
+ grid-template-columns: none !important;
+ }
+
+ .list-container.grid-mode .list-children-container > .list-item-wrapper > .list-item-row > .list-item-cell:first-child {
+ width: 140px;
+ height: 140px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
+ }
+
+ .list-container.grid-mode .list-children-container > .list-item-wrapper > .list-item-row > .list-item-cell:not(:first-child) {
+ width: 100%;
+ text-align: center !important;
+ font-size: 11px;
+ font-weight: 500;
+ color: var(--text-primary);
+ white-space: normal;
+ word-break: break-all;
+ justify-content: center !important;
+ }
+
}
`;
diff --git a/examples/webgpu_postprocessing_ao.html b/examples/webgpu_postprocessing_ao.html
index fef87ec3a203fd..a8aead8d8cdea9 100644
--- a/examples/webgpu_postprocessing_ao.html
+++ b/examples/webgpu_postprocessing_ao.html
@@ -59,7 +59,7 @@
const params = {
aoType: 'GTAO',
samples: 16,
- radius: 0.5,
+ radius: 0.4,
resolutionScale: 0.5,
scale: 0.8, // GTAO
thickness: 1, // GTAO
diff --git a/src/core/Object3D.js b/src/core/Object3D.js
index 556ee3f4ac5848..42c9888d22a97c 100644
--- a/src/core/Object3D.js
+++ b/src/core/Object3D.js
@@ -1653,6 +1653,27 @@ class Object3D extends EventDispatcher {
}
+ /**
+ * Frees the GPU-related resources allocated by this instance. Call this
+ * method whenever this instance is no longer used in your app.
+ *
+ * Geometries, materials and textures are potentially shared with other
+ * 3D objects and must be disposed of separately.
+ *
+ * @fires Object3D#dispose
+ */
+ dispose() {
+
+ /**
+ * Fires when the 3D object has been disposed of.
+ *
+ * @event Object3D#dispose
+ * @type {Object}
+ */
+ this.dispatchEvent( { type: 'dispose' } );
+
+ }
+
}
/**
diff --git a/src/helpers/ArrowHelper.js b/src/helpers/ArrowHelper.js
index e99afff8db4d5b..406b2388bc0cd2 100644
--- a/src/helpers/ArrowHelper.js
+++ b/src/helpers/ArrowHelper.js
@@ -159,6 +159,8 @@ class ArrowHelper extends Object3D {
*/
dispose() {
+ super.dispose();
+
this.line.geometry.dispose();
this.line.material.dispose();
this.cone.geometry.dispose();
diff --git a/src/helpers/AxesHelper.js b/src/helpers/AxesHelper.js
index 305eae5e5b21ef..0287a0f8e8e4ec 100644
--- a/src/helpers/AxesHelper.js
+++ b/src/helpers/AxesHelper.js
@@ -85,6 +85,8 @@ class AxesHelper extends LineSegments {
*/
dispose() {
+ super.dispose();
+
this.geometry.dispose();
this.material.dispose();
diff --git a/src/helpers/Box3Helper.js b/src/helpers/Box3Helper.js
index af0ceee8449349..7d70d9f2d3c8eb 100644
--- a/src/helpers/Box3Helper.js
+++ b/src/helpers/Box3Helper.js
@@ -73,6 +73,8 @@ class Box3Helper extends LineSegments {
*/
dispose() {
+ super.dispose();
+
this.geometry.dispose();
this.material.dispose();
diff --git a/src/helpers/BoxHelper.js b/src/helpers/BoxHelper.js
index ce41a78db7c772..46080feb6dd6bb 100644
--- a/src/helpers/BoxHelper.js
+++ b/src/helpers/BoxHelper.js
@@ -138,6 +138,8 @@ class BoxHelper extends LineSegments {
*/
dispose() {
+ super.dispose();
+
this.geometry.dispose();
this.material.dispose();
diff --git a/src/helpers/CameraHelper.js b/src/helpers/CameraHelper.js
index 2ff070c265f6d6..ac31e077c2fc54 100644
--- a/src/helpers/CameraHelper.js
+++ b/src/helpers/CameraHelper.js
@@ -314,6 +314,8 @@ class CameraHelper extends LineSegments {
*/
dispose() {
+ super.dispose();
+
this.geometry.dispose();
this.material.dispose();
diff --git a/src/helpers/DirectionalLightHelper.js b/src/helpers/DirectionalLightHelper.js
index b8f5e71ee80918..93d0ad2fb24d0b 100644
--- a/src/helpers/DirectionalLightHelper.js
+++ b/src/helpers/DirectionalLightHelper.js
@@ -103,6 +103,8 @@ class DirectionalLightHelper extends Object3D {
*/
dispose() {
+ super.dispose();
+
this.lightPlane.geometry.dispose();
this.lightPlane.material.dispose();
this.targetLine.geometry.dispose();
diff --git a/src/helpers/GridHelper.js b/src/helpers/GridHelper.js
index 78c4c02da5a3c1..b26576b4556758 100644
--- a/src/helpers/GridHelper.js
+++ b/src/helpers/GridHelper.js
@@ -71,6 +71,8 @@ class GridHelper extends LineSegments {
*/
dispose() {
+ super.dispose();
+
this.geometry.dispose();
this.material.dispose();
diff --git a/src/helpers/HemisphereLightHelper.js b/src/helpers/HemisphereLightHelper.js
index b64cbd004637d0..119a8ea1cc088e 100644
--- a/src/helpers/HemisphereLightHelper.js
+++ b/src/helpers/HemisphereLightHelper.js
@@ -82,6 +82,8 @@ class HemisphereLightHelper extends Object3D {
*/
dispose() {
+ super.dispose();
+
this.children[ 0 ].geometry.dispose();
this.children[ 0 ].material.dispose();
diff --git a/src/helpers/PlaneHelper.js b/src/helpers/PlaneHelper.js
index f1f36324c0c938..4aadd716fea2c9 100644
--- a/src/helpers/PlaneHelper.js
+++ b/src/helpers/PlaneHelper.js
@@ -84,6 +84,8 @@ class PlaneHelper extends Line {
*/
dispose() {
+ super.dispose();
+
this.geometry.dispose();
this.material.dispose();
this.children[ 0 ].geometry.dispose();
diff --git a/src/helpers/PointLightHelper.js b/src/helpers/PointLightHelper.js
index 43793ab1260823..37c0f3235f6555 100644
--- a/src/helpers/PointLightHelper.js
+++ b/src/helpers/PointLightHelper.js
@@ -65,6 +65,8 @@ class PointLightHelper extends Mesh {
*/
dispose() {
+ super.dispose();
+
this.geometry.dispose();
this.material.dispose();
diff --git a/src/helpers/PolarGridHelper.js b/src/helpers/PolarGridHelper.js
index 90994e03ebc476..7dd54eda976611 100644
--- a/src/helpers/PolarGridHelper.js
+++ b/src/helpers/PolarGridHelper.js
@@ -115,6 +115,8 @@ class PolarGridHelper extends LineSegments {
*/
dispose() {
+ super.dispose();
+
this.geometry.dispose();
this.material.dispose();
diff --git a/src/helpers/SkeletonHelper.js b/src/helpers/SkeletonHelper.js
index 4ea9cbf191b818..05cc2ca0d003b3 100644
--- a/src/helpers/SkeletonHelper.js
+++ b/src/helpers/SkeletonHelper.js
@@ -162,6 +162,8 @@ class SkeletonHelper extends LineSegments {
*/
dispose() {
+ super.dispose();
+
this.geometry.dispose();
this.material.dispose();
diff --git a/src/helpers/SpotLightHelper.js b/src/helpers/SpotLightHelper.js
index fef093d40d602e..fa2be33037feed 100644
--- a/src/helpers/SpotLightHelper.js
+++ b/src/helpers/SpotLightHelper.js
@@ -95,6 +95,8 @@ class SpotLightHelper extends Object3D {
*/
dispose() {
+ super.dispose();
+
this.cone.geometry.dispose();
this.cone.material.dispose();
diff --git a/src/lights/Light.js b/src/lights/Light.js
index 4d81b98fe10c5a..60f8757d2a01fc 100644
--- a/src/lights/Light.js
+++ b/src/lights/Light.js
@@ -48,16 +48,6 @@ class Light extends Object3D {
}
- /**
- * Frees the GPU-related resources allocated by this instance. Call this
- * method whenever this instance is no longer used in your app.
- */
- dispose() {
-
- this.dispatchEvent( { type: 'dispose' } );
-
- }
-
copy( source, recursive ) {
super.copy( source, recursive );
diff --git a/src/objects/BatchedMesh.js b/src/objects/BatchedMesh.js
index 7ab4e4756a46fb..9dc076b34e4480 100644
--- a/src/objects/BatchedMesh.js
+++ b/src/objects/BatchedMesh.js
@@ -1498,6 +1498,8 @@ class BatchedMesh extends Mesh {
*/
dispose() {
+ super.dispose();
+
// Assuming the geometry is not shared with other meshes
this.geometry.dispose();
diff --git a/src/objects/InstancedMesh.js b/src/objects/InstancedMesh.js
index 7c29cc68873fe3..d55dc5d1311200 100644
--- a/src/objects/InstancedMesh.js
+++ b/src/objects/InstancedMesh.js
@@ -395,7 +395,7 @@ class InstancedMesh extends Mesh {
*/
dispose() {
- this.dispatchEvent( { type: 'dispose' } );
+ super.dispose();
if ( this.morphTexture !== null ) {
diff --git a/src/renderers/common/RenderObject.js b/src/renderers/common/RenderObject.js
index c900fa4c9654f8..8c1091da73db2f 100644
--- a/src/renderers/common/RenderObject.js
+++ b/src/renderers/common/RenderObject.js
@@ -343,6 +343,19 @@ class RenderObject {
};
+ /**
+ * An event listener which is executed when `dispose()` is called on
+ * the 3D object of this render object.
+ *
+ * @method
+ */
+ this.onObjectDispose = () => {
+
+ this.dispose();
+
+ };
+
+ this.object.addEventListener( 'dispose', this.onObjectDispose );
this.material.addEventListener( 'dispose', this.onMaterialDispose );
this.geometry.addEventListener( 'dispose', this.onGeometryDispose );
@@ -957,6 +970,7 @@ class RenderObject {
*/
dispose() {
+ this.object.removeEventListener( 'dispose', this.onObjectDispose );
this.material.removeEventListener( 'dispose', this.onMaterialDispose );
this.geometry.removeEventListener( 'dispose', this.onGeometryDispose );
diff --git a/src/renderers/webgl-fallback/utils/WebGLState.js b/src/renderers/webgl-fallback/utils/WebGLState.js
index eea31cd9c07479..beae95be961b5b 100644
--- a/src/renderers/webgl-fallback/utils/WebGLState.js
+++ b/src/renderers/webgl-fallback/utils/WebGLState.js
@@ -1,7 +1,7 @@
import {
CullFaceNone, CullFaceBack, CullFaceFront, DoubleSide, BackSide,
NormalBlending, NoBlending, CustomBlending, AddEquation,
- AdditiveBlending, SubtractiveBlending, MultiplyBlending, SubtractEquation, ReverseSubtractEquation,
+ AdditiveBlending, SubtractiveBlending, MultiplyBlending, SubtractEquation, ReverseSubtractEquation, MinEquation, MaxEquation,
ZeroFactor, OneFactor, SrcColorFactor, SrcAlphaFactor, SrcAlphaSaturateFactor, DstColorFactor, DstAlphaFactor,
OneMinusSrcColorFactor, OneMinusSrcAlphaFactor, OneMinusDstColorFactor, OneMinusDstAlphaFactor,
NeverDepth, AlwaysDepth, LessDepth, LessEqualDepth, EqualDepth, GreaterEqualDepth, GreaterDepth, NotEqualDepth,
@@ -108,7 +108,9 @@ class WebGLState {
equationToGL = {
[ AddEquation ]: gl.FUNC_ADD,
[ SubtractEquation ]: gl.FUNC_SUBTRACT,
- [ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT
+ [ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT,
+ [ MinEquation ]: gl.MIN,
+ [ MaxEquation ]: gl.MAX
};
factorToGL = {