' ) ) {
+
+ const strong = document.createElement( 'strong' );
+ strong.textContent = part.slice( 8, - 9 );
+ fragment.appendChild( strong );
+
+ } else if ( /^("|'|`)/.test( part ) ) {
const codeSpan = document.createElement( 'span' );
codeSpan.className = 'log-code';
diff --git a/examples/screenshots/webgpu_materials_debug_rebuild.jpg b/examples/screenshots/webgpu_materials_debug_rebuild.jpg
new file mode 100644
index 00000000000000..82674e6b371682
Binary files /dev/null and b/examples/screenshots/webgpu_materials_debug_rebuild.jpg differ
diff --git a/examples/webgpu_materials_debug_rebuild.html b/examples/webgpu_materials_debug_rebuild.html
new file mode 100644
index 00000000000000..115bae9ad6847a
--- /dev/null
+++ b/examples/webgpu_materials_debug_rebuild.html
@@ -0,0 +1,331 @@
+
+
+
+ three.js webgpu - node material rebuild debug
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Open the Inspector Console tab to see material rebuild reasons.
+ The Materials trace option is enabled when this example starts.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderers/common/InspectorBase.js b/src/renderers/common/InspectorBase.js
index b1e61902c7df5a..e6ee159239d417 100644
--- a/src/renderers/common/InspectorBase.js
+++ b/src/renderers/common/InspectorBase.js
@@ -57,6 +57,20 @@ class InspectorBase extends EventDispatcher {
}
+ /**
+ * Called before a missing node builder state is built.
+ *
+ * @param {Object} info - Information about the node build.
+ */
+ beginNodeBuild( /*info*/ ) { }
+
+ /**
+ * Called after a missing node builder state has been built.
+ *
+ * @param {Object} info - Information about the node build.
+ */
+ finishNodeBuild( /*info*/ ) { }
+
/**
* Returns the renderer associated with this inspector.
*
diff --git a/src/renderers/common/nodes/NodeManager.js b/src/renderers/common/nodes/NodeManager.js
index 2caf90c8efea77..0b691e7d8def0d 100644
--- a/src/renderers/common/nodes/NodeManager.js
+++ b/src/renderers/common/nodes/NodeManager.js
@@ -14,6 +14,34 @@ import { error } from '../../../utils.js';
const _chainKeys = [];
const _cacheKeyValues = [];
+function getNodeBuildInfo( renderer, renderObject, useAsync, cacheKey ) {
+
+ const object = renderObject.object || null;
+ const material = renderObject.material || null;
+ const geometry = renderObject.geometry || object && object.geometry || null;
+
+ return {
+ renderer,
+ renderObject,
+ object,
+ material,
+ geometry,
+ lightsNode: renderObject.lightsNode || null,
+ scene: renderObject.scene || null,
+ camera: renderObject.camera || null,
+ useAsync,
+ cacheKey: cacheKey || null,
+ materialVersion: material ? material.version : null,
+ materialUuid: material ? material.uuid : null,
+ geometryUuid: geometry ? geometry.uuid : null,
+ objectUuid: object ? object.uuid : null,
+ reason: 'missing-node-builder-state',
+ durationMs: 0,
+ result: null
+ };
+
+}
+
/**
* This renderer module manages node-related objects and is the
* primary interface between the renderer and the node system.
@@ -197,6 +225,19 @@ class NodeManager extends DataMap {
if ( nodeBuilderState === undefined ) {
+ const renderer = this.renderer;
+ const inspector = renderer.inspector;
+ const nodeBuildInfo = inspector !== null ? getNodeBuildInfo( renderer, renderObject, useAsync, cacheKey ) : null;
+ let startTime = 0;
+
+ if ( inspector !== null ) {
+
+ startTime = performance.now();
+
+ inspector.beginNodeBuild( nodeBuildInfo );
+
+ }
+
const buildNodeBuilder = async () => {
let nodeBuilder = this._createNodeBuilder( renderObject, renderObject.material );
@@ -244,6 +285,15 @@ class NodeManager extends DataMap {
nodeBuilderState.usedTimes ++;
renderObjectData.nodeBuilderState = nodeBuilderState;
+ if ( inspector !== null ) {
+
+ nodeBuildInfo.durationMs = performance.now() - startTime;
+ nodeBuildInfo.result = nodeBuilderState;
+
+ inspector.finishNodeBuild( nodeBuildInfo );
+
+ }
+
return nodeBuilderState;
} );
@@ -280,6 +330,15 @@ class NodeManager extends DataMap {
nodeBuilderCache.set( cacheKey, nodeBuilderState );
+ if ( inspector !== null ) {
+
+ nodeBuildInfo.durationMs = performance.now() - startTime;
+ nodeBuildInfo.result = nodeBuilderState;
+
+ inspector.finishNodeBuild( nodeBuildInfo );
+
+ }
+
}
}
diff --git a/test/e2e/image.js b/test/e2e/image.js
index cecdc5598e827d..916d9b0e9e1424 100644
--- a/test/e2e/image.js
+++ b/test/e2e/image.js
@@ -169,10 +169,18 @@ class Image {
buffer = await fs.readFile( input );
- } else {
+ } else if ( Buffer.isBuffer( input ) ) {
buffer = input;
+ } else if ( ArrayBuffer.isView( input ) ) {
+
+ buffer = Buffer.from( input.buffer, input.byteOffset, input.byteLength );
+
+ } else {
+
+ buffer = Buffer.from( input );
+
}
// Check if PNG (starts with PNG signature)
diff --git a/test/e2e/puppeteer.js b/test/e2e/puppeteer.js
index 6445cf1a66c1d4..2191b7927d2655 100644
--- a/test/e2e/puppeteer.js
+++ b/test/e2e/puppeteer.js
@@ -27,6 +27,7 @@ const exceptionList = [
'webgl_worker_offscreencanvas',
'webgpu_backdrop_water',
'webgpu_lightprobe_cubecamera',
+ 'webgpu_materials_debug_rebuild',
'webgpu_portal',
'webgpu_postprocessing_ao',
'webgpu_postprocessing_dof',