diff --git a/examples/jsm/inspector/tabs/Timeline.js b/examples/jsm/inspector/tabs/Timeline.js
index 603ab4b45b8673..f0d9a02b224f0f 100644
--- a/examples/jsm/inspector/tabs/Timeline.js
+++ b/examples/jsm/inspector/tabs/Timeline.js
@@ -102,11 +102,22 @@ class Timeline extends Tab {
} );
+ this.exportButton = document.createElement( 'button' );
+ this.exportButton.className = 'console-copy-button';
+ this.exportButton.title = 'Export';
+ this.exportButton.innerHTML = '';
+ this.exportButton.style.padding = '0 10px';
+ this.exportButton.style.lineHeight = '24px';
+ this.exportButton.style.display = 'flex';
+ this.exportButton.style.alignItems = 'center';
+ this.exportButton.addEventListener( 'click', () => this.exportData() );
+
const buttonsGroup = document.createElement( 'div' );
buttonsGroup.className = 'console-buttons-group';
buttonsGroup.appendChild( this.viewModeButton );
buttonsGroup.appendChild( this.recordButton );
buttonsGroup.appendChild( this.recordRefreshButton );
+ buttonsGroup.appendChild( this.exportButton );
buttonsGroup.appendChild( clearButton );
header.style.display = 'flex';
@@ -597,8 +608,12 @@ class Timeline extends Tab {
}
- // Only record method name as requested, skipping detail arguments
- this.currentFrame.calls.push( { method: methodLabel } );
+ const call = { method: methodLabel };
+ const details = this.getCallDetail( prop, args );
+
+ if ( details ) call.details = details;
+
+ this.currentFrame.calls.push( call );
return originalFunc.apply( backend, args );
@@ -647,6 +662,108 @@ class Timeline extends Tab {
}
+ exportData() {
+
+ if ( this.frames.length === 0 ) return;
+
+ const data = JSON.stringify( this.frames, null, '\t' );
+ const blob = new Blob( [ data ], { type: 'application/json' } );
+ const url = URL.createObjectURL( blob );
+ const a = document.createElement( 'a' );
+ a.href = url;
+ a.download = 'threejs-timeline.json';
+ a.click();
+ URL.revokeObjectURL( url );
+
+ }
+
+ getCallDetail( method, args ) {
+
+ switch ( method ) {
+
+ case 'draw': {
+
+ const renderObject = args[ 0 ];
+ if ( ! renderObject ) return null;
+
+ const details = {};
+ if ( renderObject.object ) {
+
+ details.object = renderObject.object.name || renderObject.object.type;
+
+ if ( renderObject.object.count > 1 ) {
+
+ details.instance = renderObject.object.count;
+
+ }
+
+ }
+
+ if ( renderObject.material ) details.material = renderObject.material.name || renderObject.material.type;
+ if ( renderObject.geometry ) details.geometry = renderObject.geometry.name || undefined;
+
+ if ( renderObject.camera ) details.camera = renderObject.camera.name || renderObject.camera.type;
+
+ return details;
+
+ }
+
+ case 'updateBinding': {
+
+ const binding = args[ 0 ];
+ return binding?.name ? { binding: binding.name } : null;
+
+ }
+
+ case 'createProgram': {
+
+ const program = args[ 0 ];
+ if ( ! program ) return null;
+ return { stage: program.stage, name: program.name || undefined };
+
+ }
+
+ case 'createBindings': {
+
+ const bindGroup = args[ 0 ];
+ return bindGroup?.name ? { group: bindGroup.name } : null;
+
+ }
+
+ case 'createTexture':
+ case 'destroyTexture': {
+
+ const texture = args[ 0 ];
+ return texture?.name ? { texture: texture.name } : null;
+
+ }
+
+ }
+
+ return null;
+
+ }
+
+ formatDetails( details ) {
+
+ const parts = [];
+
+ for ( const key in details ) {
+
+ if ( details[ key ] !== undefined ) {
+
+ parts.push( `${key}: ${details[ key ]}` );
+
+ }
+
+ }
+
+ if ( parts.length === 0 ) return '';
+
+ return `{ ${parts.join( ', ' )} }`;
+
+ }
+
renderSlider() {
if ( this.frames.length === 0 ) {
@@ -768,14 +885,15 @@ class Timeline extends Tab {
const call = frame.calls[ i ];
const isStructural = call.method.startsWith( 'begin' ) || call.method.startsWith( 'finish' );
+ const formatedDetails = call.details ? this.formatDetails( call.details ) : '';
- if ( currentGroup && currentGroup.method === call.method && ! isStructural ) {
+ if ( currentGroup && currentGroup.method === call.method && currentGroup.formatedDetails === formatedDetails && ! isStructural ) {
currentGroup.count ++;
} else {
- currentGroup = { method: call.method, count: 1 };
+ currentGroup = { method: call.method, count: 1, formatedDetails };
groupedCalls.push( currentGroup );
}
@@ -836,7 +954,7 @@ class Timeline extends Tab {
// Title
const title = document.createElement( 'span' );
- title.textContent = call.method + ( call.count > 1 ? ` ( ${call.count} )` : '' );
+ title.innerHTML = call.method + ( call.formatedDetails ? call.formatedDetails : '' ) + ( call.count > 1 ? ` ( ${call.count} )` : '' );
block.appendChild( title );
block.addEventListener( 'click', ( e ) => {
@@ -862,14 +980,14 @@ class Timeline extends Tab {
} else if ( call.method.startsWith( 'finish' ) ) {
- block.textContent = call.method + ( call.count > 1 ? ` ( ${call.count} )` : '' );
+ block.innerHTML = call.method + ( call.formatedDetails ? call.formatedDetails : '' ) + ( call.count > 1 ? ` ( ${call.count} )` : '' );
currentIndent = Math.max( 0, currentIndent - 1 );
elementStack.pop();
} else {
- block.textContent = call.method + ( call.count > 1 ? ` ( ${call.count} )` : '' );
+ block.innerHTML = call.method + ( call.formatedDetails ? call.formatedDetails : '' ) + ( call.count > 1 ? ` ( ${call.count} )` : '' );
}
diff --git a/src/nodes/display/PassNode.js b/src/nodes/display/PassNode.js
index 9b9eeac8c9ad2a..f6add436d0386a 100644
--- a/src/nodes/display/PassNode.js
+++ b/src/nodes/display/PassNode.js
@@ -5,7 +5,7 @@ import { context } from '../tsl/TSLBase.js';
import { uniform } from '../core/UniformNode.js';
import { viewZToOrthographicDepth, perspectiveDepthToViewZ } from './ViewportDepthNode.js';
-import { HalfFloatType/*, FloatType*/ } from '../../constants.js';
+import { HalfFloatType, FloatType } from '../../constants.js';
import { Vector2 } from '../../math/Vector2.js';
import { Vector4 } from '../../math/Vector4.js';
import { DepthTexture } from '../../textures/DepthTexture.js';
@@ -757,6 +757,12 @@ class PassNode extends TempNode {
this.renderTarget.texture.type = renderer.getOutputBufferType();
+ if ( renderer.reversedDepthBuffer === true ) {
+
+ this.renderTarget.depthTexture.type = FloatType;
+
+ }
+
return this.scope === PassNode.COLOR ? this.getTextureNode() : this.getLinearDepthNode();
}
diff --git a/src/renderers/webgpu/utils/WebGPUTextureUtils.js b/src/renderers/webgpu/utils/WebGPUTextureUtils.js
index b52aedc9983704..f12d5cf897b099 100644
--- a/src/renderers/webgpu/utils/WebGPUTextureUtils.js
+++ b/src/renderers/webgpu/utils/WebGPUTextureUtils.js
@@ -463,12 +463,12 @@ class WebGPUTextureUtils {
if ( stencil ) {
format = DepthStencilFormat;
- type = UnsignedInt248Type;
+ type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type;
} else if ( depth ) {
format = DepthFormat;
- type = UnsignedIntType;
+ type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType;
}
diff --git a/src/renderers/webgpu/utils/WebGPUUtils.js b/src/renderers/webgpu/utils/WebGPUUtils.js
index 92cac6975ea372..9baee7b3f3d239 100644
--- a/src/renderers/webgpu/utils/WebGPUUtils.js
+++ b/src/renderers/webgpu/utils/WebGPUUtils.js
@@ -42,11 +42,27 @@ class WebGPUUtils {
} else if ( renderContext.stencil ) {
- format = GPUTextureFormat.Depth24PlusStencil8;
+ if ( this.backend.renderer.reversedDepthBuffer === true ) {
+
+ format = GPUTextureFormat.Depth32FloatStencil8;
+
+ } else {
+
+ format = GPUTextureFormat.Depth24PlusStencil8;
+
+ }
} else {
- format = GPUTextureFormat.Depth24Plus;
+ if ( this.backend.renderer.reversedDepthBuffer === true ) {
+
+ format = GPUTextureFormat.Depth32Float;
+
+ } else {
+
+ format = GPUTextureFormat.Depth24Plus;
+
+ }
}