@zephyr3d/scene 0.9.13 → 0.9.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/animation/animationset.js +334 -63
- package/dist/animation/animationset.js.map +1 -1
- package/dist/animation/morphtargetgrouptrack.js +66 -0
- package/dist/animation/morphtargetgrouptrack.js.map +1 -0
- package/dist/asset/model.js +9 -0
- package/dist/asset/model.js.map +1 -1
- package/dist/index.d.ts +266 -4
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/posteffect/sao.js +1 -1
- package/dist/posteffect/sao.js.map +1 -1
- package/dist/render/rendergraph/device_pool_allocator.js +50 -43
- package/dist/render/rendergraph/device_pool_allocator.js.map +1 -1
- package/dist/render/rendergraph/executor.js +270 -1
- package/dist/render/rendergraph/executor.js.map +1 -1
- package/dist/render/rendergraph/forward_plus_builder.js +19 -1
- package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
- package/dist/render/rendergraph/types.js.map +1 -1
- package/dist/scene/scene.js +1 -0
- package/dist/scene/scene.js.map +1 -1
- package/dist/utility/serialization/manager.js +2 -1
- package/dist/utility/serialization/manager.js.map +1 -1
- package/dist/utility/serialization/scene/animation.js +70 -1
- package/dist/utility/serialization/scene/animation.js.map +1 -1
- package/dist/utility/serialization/scene/camera.js.map +1 -1
- package/package.json +2 -2
- package/dist/animation/joint_dynamics/convex_collider.js +0 -320
- package/dist/animation/joint_dynamics/convex_collider.js.map +0 -1
|
@@ -1,29 +1,36 @@
|
|
|
1
1
|
import { getDevice } from '../../app/api.js';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Bridges the render graph's {@link RGTextureAllocator} interface to the
|
|
5
|
-
* engine's device resource pool (`device.pool`).
|
|
6
|
-
*
|
|
7
|
-
* Transient textures are fetched from the pool on `allocate()` and
|
|
8
|
-
* returned to the pool on `release()`, enabling automatic reuse
|
|
9
|
-
* across frames without manual lifecycle management.
|
|
10
|
-
*
|
|
11
|
-
* Usage:
|
|
12
|
-
* ```ts
|
|
13
|
-
* const allocator = new DevicePoolAllocator();
|
|
14
|
-
* const executor = new RenderGraphExecutor(allocator, width, height);
|
|
15
|
-
* ```
|
|
16
|
-
*
|
|
17
|
-
* @public
|
|
3
|
+
/**
|
|
4
|
+
* Bridges the render graph's {@link RGTextureAllocator} interface to the
|
|
5
|
+
* engine's device resource pool (`device.pool`).
|
|
6
|
+
*
|
|
7
|
+
* Transient textures are fetched from the pool on `allocate()` and
|
|
8
|
+
* returned to the pool on `release()`, enabling automatic reuse
|
|
9
|
+
* across frames without manual lifecycle management.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* ```ts
|
|
13
|
+
* const allocator = new DevicePoolAllocator();
|
|
14
|
+
* const executor = new RenderGraphExecutor(allocator, width, height);
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
18
|
*/ class DevicePoolAllocator {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
*
|
|
22
|
-
* @param
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
_device;
|
|
20
|
+
/**
|
|
21
|
+
* Creates a new instance of the DevicePoolAllocator.
|
|
22
|
+
* @param device - Optional device instance to use for resource allocation. If not provided, the global device will be used.
|
|
23
|
+
*/ constructor(device){
|
|
24
|
+
this._device = device ?? null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Allocate a transient texture from the device pool.
|
|
28
|
+
*
|
|
29
|
+
* @param desc - Texture descriptor from the render graph pass.
|
|
30
|
+
* @param size - Resolved pixel dimensions.
|
|
31
|
+
* @returns A pooled Texture2D instance.
|
|
25
32
|
*/ allocate(desc, size) {
|
|
26
|
-
const device = getDevice();
|
|
33
|
+
const device = this._device ?? getDevice();
|
|
27
34
|
const mipmapping = (desc.mipLevels ?? 1) > 1;
|
|
28
35
|
const texture = device.pool.fetchTemporalTexture2D(false, desc.format, size.width, size.height, mipmapping);
|
|
29
36
|
if (desc.mipLevels && texture.mipLevelCount < desc.mipLevels) {
|
|
@@ -32,41 +39,41 @@ import { getDevice } from '../../app/api.js';
|
|
|
32
39
|
}
|
|
33
40
|
return texture;
|
|
34
41
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Release a transient texture back to the device pool.
|
|
37
|
-
*
|
|
38
|
-
* @param texture - The texture to release.
|
|
42
|
+
/**
|
|
43
|
+
* Release a transient texture back to the device pool.
|
|
44
|
+
*
|
|
45
|
+
* @param texture - The texture to release.
|
|
39
46
|
*/ release(texture) {
|
|
40
|
-
const device = getDevice();
|
|
47
|
+
const device = this._device ?? getDevice();
|
|
41
48
|
device.pool.releaseTexture(texture);
|
|
42
49
|
}
|
|
43
|
-
/**
|
|
44
|
-
* Retain a pooled texture so it can be owned outside the graph lifetime.
|
|
45
|
-
*
|
|
46
|
-
* @param texture - The texture to retain.
|
|
50
|
+
/**
|
|
51
|
+
* Retain a pooled texture so it can be owned outside the graph lifetime.
|
|
52
|
+
*
|
|
53
|
+
* @param texture - The texture to retain.
|
|
47
54
|
*/ retain(texture) {
|
|
48
|
-
const device = getDevice();
|
|
55
|
+
const device = this._device ?? getDevice();
|
|
49
56
|
device.pool.retainTexture(texture);
|
|
50
57
|
}
|
|
51
|
-
/**
|
|
52
|
-
* Allocate a temporary framebuffer from the device pool.
|
|
53
|
-
*
|
|
54
|
-
* @param desc - Framebuffer descriptor from the render graph pass.
|
|
55
|
-
* @returns A pooled FrameBuffer instance.
|
|
58
|
+
/**
|
|
59
|
+
* Allocate a temporary framebuffer from the device pool.
|
|
60
|
+
*
|
|
61
|
+
* @param desc - Framebuffer descriptor from the render graph pass.
|
|
62
|
+
* @returns A pooled FrameBuffer instance.
|
|
56
63
|
*/ allocateFramebuffer(desc) {
|
|
57
|
-
const device = getDevice();
|
|
64
|
+
const device = this._device ?? getDevice();
|
|
58
65
|
const colors = Array.isArray(desc.colorAttachments) ? desc.colorAttachments : desc.colorAttachments ? [
|
|
59
66
|
desc.colorAttachments
|
|
60
67
|
] : [];
|
|
61
68
|
const depthAttachment = desc.depthAttachment ?? null;
|
|
62
69
|
return device.pool.fetchTemporalFramebuffer(false, desc.width ?? 0, desc.height ?? 0, colors, depthAttachment, desc.mipmapping ?? false, desc.sampleCount ?? 1, desc.ignoreDepthStencil ?? true, desc.attachmentMipLevel ?? 0, desc.attachmentCubeface ?? 0, desc.attachmentLayer ?? 0);
|
|
63
70
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Release a temporary framebuffer back to the device pool.
|
|
66
|
-
*
|
|
67
|
-
* @param framebuffer - The framebuffer to release.
|
|
71
|
+
/**
|
|
72
|
+
* Release a temporary framebuffer back to the device pool.
|
|
73
|
+
*
|
|
74
|
+
* @param framebuffer - The framebuffer to release.
|
|
68
75
|
*/ releaseFramebuffer(framebuffer) {
|
|
69
|
-
const device = getDevice();
|
|
76
|
+
const device = this._device ?? getDevice();
|
|
70
77
|
device.pool.releaseFrameBuffer(framebuffer);
|
|
71
78
|
}
|
|
72
79
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device_pool_allocator.js","sources":["../../../src/render/rendergraph/device_pool_allocator.ts"],"sourcesContent":["import type { FrameBuffer, Texture2D, TextureFormat } from '@zephyr3d/device';\
|
|
1
|
+
{"version":3,"file":"device_pool_allocator.js","sources":["../../../src/render/rendergraph/device_pool_allocator.ts"],"sourcesContent":["import type { FrameBuffer, Texture2D, TextureFormat, AbstractDevice } from '@zephyr3d/device';\nimport type { RGFramebufferDesc, RGTextureAllocator, RGTextureDesc, RGResolvedSize } from './types';\nimport { getDevice } from '../../app/api';\nimport type { Nullable } from '@zephyr3d/base';\n\n/**\n * Bridges the render graph's {@link RGTextureAllocator} interface to the\n * engine's device resource pool (`device.pool`).\n *\n * Transient textures are fetched from the pool on `allocate()` and\n * returned to the pool on `release()`, enabling automatic reuse\n * across frames without manual lifecycle management.\n *\n * Usage:\n * ```ts\n * const allocator = new DevicePoolAllocator();\n * const executor = new RenderGraphExecutor(allocator, width, height);\n * ```\n *\n * @public\n */\nexport class DevicePoolAllocator implements RGTextureAllocator<Texture2D, FrameBuffer> {\n private _device: Nullable<AbstractDevice>;\n /**\n * Creates a new instance of the DevicePoolAllocator.\n * @param device - Optional device instance to use for resource allocation. If not provided, the global device will be used.\n */\n constructor(device?: AbstractDevice) {\n this._device = device ?? null;\n }\n /**\n * Allocate a transient texture from the device pool.\n *\n * @param desc - Texture descriptor from the render graph pass.\n * @param size - Resolved pixel dimensions.\n * @returns A pooled Texture2D instance.\n */\n allocate(desc: RGTextureDesc, size: RGResolvedSize): Texture2D {\n const device = this._device ?? getDevice();\n const mipmapping = (desc.mipLevels ?? 1) > 1;\n const texture = device.pool.fetchTemporalTexture2D(\n false,\n desc.format,\n size.width,\n size.height,\n mipmapping\n );\n if (desc.mipLevels && texture.mipLevelCount < desc.mipLevels) {\n device.pool.releaseTexture(texture);\n throw new Error(\n `DevicePoolAllocator: texture \"${desc.label ?? '<unnamed>'}\" requested ${desc.mipLevels} ` +\n `mip levels, but only ${texture.mipLevelCount} were allocated.`\n );\n }\n return texture;\n }\n\n /**\n * Release a transient texture back to the device pool.\n *\n * @param texture - The texture to release.\n */\n release(texture: Texture2D): void {\n const device = this._device ?? getDevice();\n device.pool.releaseTexture(texture);\n }\n\n /**\n * Retain a pooled texture so it can be owned outside the graph lifetime.\n *\n * @param texture - The texture to retain.\n */\n retain(texture: Texture2D): void {\n const device = this._device ?? getDevice();\n device.pool.retainTexture(texture);\n }\n\n /**\n * Allocate a temporary framebuffer from the device pool.\n *\n * @param desc - Framebuffer descriptor from the render graph pass.\n * @returns A pooled FrameBuffer instance.\n */\n allocateFramebuffer(desc: RGFramebufferDesc): FrameBuffer {\n const device = this._device ?? getDevice();\n const colors = Array.isArray(desc.colorAttachments)\n ? (desc.colorAttachments as Array<Texture2D | TextureFormat>)\n : desc.colorAttachments\n ? [desc.colorAttachments as Texture2D | TextureFormat]\n : [];\n const depthAttachment = (desc.depthAttachment ?? null) as Texture2D | TextureFormat | null;\n return device.pool.fetchTemporalFramebuffer(\n false,\n desc.width ?? 0,\n desc.height ?? 0,\n colors,\n depthAttachment,\n desc.mipmapping ?? false,\n desc.sampleCount ?? 1,\n desc.ignoreDepthStencil ?? true,\n desc.attachmentMipLevel ?? 0,\n desc.attachmentCubeface ?? 0,\n desc.attachmentLayer ?? 0\n );\n }\n\n /**\n * Release a temporary framebuffer back to the device pool.\n *\n * @param framebuffer - The framebuffer to release.\n */\n releaseFramebuffer(framebuffer: FrameBuffer): void {\n const device = this._device ?? getDevice();\n device.pool.releaseFrameBuffer(framebuffer);\n }\n}\n"],"names":["DevicePoolAllocator","_device","device","allocate","desc","size","getDevice","mipmapping","mipLevels","texture","pool","fetchTemporalTexture2D","format","width","height","mipLevelCount","releaseTexture","Error","label","release","retain","retainTexture","allocateFramebuffer","colors","Array","isArray","colorAttachments","depthAttachment","fetchTemporalFramebuffer","sampleCount","ignoreDepthStencil","attachmentMipLevel","attachmentCubeface","attachmentLayer","releaseFramebuffer","framebuffer","releaseFrameBuffer"],"mappings":";;AAKA;;;;;;;;;;;;;;;AAeC,IACM,MAAMA,mBAAAA,CAAAA;IACHC,OAAkC;AAC1C;;;MAIA,WAAA,CAAYC,MAAuB,CAAE;QACnC,IAAI,CAACD,OAAO,GAAGC,MAAU,IAAA,IAAA;AAC3B;AACA;;;;;;AAMC,MACDC,QAASC,CAAAA,IAAmB,EAAEC,IAAoB,EAAa;AAC7D,QAAA,MAAMH,MAAS,GAAA,IAAI,CAACD,OAAO,IAAIK,SAAAA,EAAAA;AAC/B,QAAA,MAAMC,aAAa,CAACH,KAAKI,SAAS,IAAI,CAAA,IAAK,CAAA;AAC3C,QAAA,MAAMC,OAAUP,GAAAA,MAAAA,CAAOQ,IAAI,CAACC,sBAAsB,CAChD,KAAA,EACAP,IAAKQ,CAAAA,MAAM,EACXP,IAAKQ,CAAAA,KAAK,EACVR,IAAAA,CAAKS,MAAM,EACXP,UAAAA,CAAAA;QAEF,IAAIH,IAAAA,CAAKI,SAAS,IAAIC,OAAAA,CAAQM,aAAa,GAAGX,IAAAA,CAAKI,SAAS,EAAE;YAC5DN,MAAOQ,CAAAA,IAAI,CAACM,cAAc,CAACP,OAAAA,CAAAA;YAC3B,MAAM,IAAIQ,KACR,CAAA,CAAC,8BAA8B,EAAEb,KAAKc,KAAK,IAAI,WAAY,CAAA,YAAY,EAAEd,IAAAA,CAAKI,SAAS,CAAC,CAAC,CAAC,GACxF,CAAC,qBAAqB,EAAEC,OAAQM,CAAAA,aAAa,CAAC,gBAAgB,CAAC,CAAA;AAErE;QACA,OAAON,OAAAA;AACT;AAEA;;;;MAKAU,OAAAA,CAAQV,OAAkB,EAAQ;AAChC,QAAA,MAAMP,MAAS,GAAA,IAAI,CAACD,OAAO,IAAIK,SAAAA,EAAAA;QAC/BJ,MAAOQ,CAAAA,IAAI,CAACM,cAAc,CAACP,OAAAA,CAAAA;AAC7B;AAEA;;;;MAKAW,MAAAA,CAAOX,OAAkB,EAAQ;AAC/B,QAAA,MAAMP,MAAS,GAAA,IAAI,CAACD,OAAO,IAAIK,SAAAA,EAAAA;QAC/BJ,MAAOQ,CAAAA,IAAI,CAACW,aAAa,CAACZ,OAAAA,CAAAA;AAC5B;AAEA;;;;;MAMAa,mBAAAA,CAAoBlB,IAAuB,EAAe;AACxD,QAAA,MAAMF,MAAS,GAAA,IAAI,CAACD,OAAO,IAAIK,SAAAA,EAAAA;AAC/B,QAAA,MAAMiB,MAASC,GAAAA,KAAAA,CAAMC,OAAO,CAACrB,IAAKsB,CAAAA,gBAAgB,CAC7CtB,GAAAA,IAAAA,CAAKsB,gBAAgB,GACtBtB,IAAKsB,CAAAA,gBAAgB,GACnB;AAACtB,YAAAA,IAAAA,CAAKsB;AAA8C,SAAA,GACpD,EAAE;QACR,MAAMC,eAAAA,GAAmBvB,IAAKuB,CAAAA,eAAe,IAAI,IAAA;AACjD,QAAA,OAAOzB,OAAOQ,IAAI,CAACkB,wBAAwB,CACzC,OACAxB,IAAKS,CAAAA,KAAK,IAAI,CAAA,EACdT,KAAKU,MAAM,IAAI,GACfS,MACAI,EAAAA,eAAAA,EACAvB,KAAKG,UAAU,IAAI,KACnBH,EAAAA,IAAAA,CAAKyB,WAAW,IAAI,CAAA,EACpBzB,IAAK0B,CAAAA,kBAAkB,IAAI,IAC3B1B,EAAAA,IAAAA,CAAK2B,kBAAkB,IAAI,GAC3B3B,IAAK4B,CAAAA,kBAAkB,IAAI,CAC3B5B,EAAAA,IAAAA,CAAK6B,eAAe,IAAI,CAAA,CAAA;AAE5B;AAEA;;;;MAKAC,kBAAAA,CAAmBC,WAAwB,EAAQ;AACjD,QAAA,MAAMjC,MAAS,GAAA,IAAI,CAACD,OAAO,IAAIK,SAAAA,EAAAA;QAC/BJ,MAAOQ,CAAAA,IAAI,CAAC0B,kBAAkB,CAACD,WAAAA,CAAAA;AACjC;AACF;;;;"}
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { RGHandle } from './types.js';
|
|
2
|
+
import { getDevice } from '../../app/api.js';
|
|
2
3
|
|
|
4
|
+
const DEFAULT_PROFILING_OPTIONS = {
|
|
5
|
+
enabled: false,
|
|
6
|
+
graph: true,
|
|
7
|
+
pass: true,
|
|
8
|
+
subpass: true,
|
|
9
|
+
includePendingUploads: true,
|
|
10
|
+
allowCrossFrame: false,
|
|
11
|
+
maxPendingFrames: 3,
|
|
12
|
+
label: 'RenderGraph'
|
|
13
|
+
};
|
|
3
14
|
/**
|
|
4
15
|
* Executes a compiled render graph with automatic resource lifecycle management.
|
|
5
16
|
*
|
|
@@ -16,6 +27,11 @@ import { RGHandle } from './types.js';
|
|
|
16
27
|
* @typeParam TTexture - The concrete texture type (e.g. `Texture2D`).
|
|
17
28
|
* @public
|
|
18
29
|
*/ class RenderGraphExecutor {
|
|
30
|
+
static _defaultProfilingOptions = false;
|
|
31
|
+
static _latestProfileResult = null;
|
|
32
|
+
static _latestPendingProfileFrame = null;
|
|
33
|
+
static _latestResolvedProfileSerial = 0;
|
|
34
|
+
static _nextProfileSerial = 0;
|
|
19
35
|
/** @internal */ _allocator;
|
|
20
36
|
/** @internal */ _backbufferWidth;
|
|
21
37
|
/** @internal */ _backbufferHeight;
|
|
@@ -25,10 +41,45 @@ import { RGHandle } from './types.js';
|
|
|
25
41
|
/** @internal */ _importedTextureAliases = new Map();
|
|
26
42
|
/** @internal */ _resolvedImportedTextures = new Map();
|
|
27
43
|
/** @internal */ _cleanupCallbacks = [];
|
|
28
|
-
|
|
44
|
+
/** @internal */ _profilingOptions;
|
|
45
|
+
/** @internal */ _pendingProfileFrames = [];
|
|
46
|
+
/** @internal */ _latestProfileResult = null;
|
|
47
|
+
/** @internal */ _latestResolvedProfileSerial = 0;
|
|
48
|
+
constructor(allocator, backbufferWidth, backbufferHeight, options){
|
|
29
49
|
this._allocator = allocator;
|
|
30
50
|
this._backbufferWidth = backbufferWidth;
|
|
31
51
|
this._backbufferHeight = backbufferHeight;
|
|
52
|
+
this._profilingOptions = this._normalizeProfilingOptions(options?.profiling ?? RenderGraphExecutor._defaultProfilingOptions, options?.device);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Set the default profiling options used by newly constructed render graph executors.
|
|
56
|
+
*/ static setDefaultProfilingOptions(options) {
|
|
57
|
+
RenderGraphExecutor._defaultProfilingOptions = options;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get the latest resolved profile result from any render graph executor.
|
|
61
|
+
*/ static getLatestProfileResult() {
|
|
62
|
+
return RenderGraphExecutor._latestProfileResult;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolve the latest pending render graph profile result from any executor.
|
|
66
|
+
*/ static resolveProfileResult() {
|
|
67
|
+
return RenderGraphExecutor._latestPendingProfileFrame?.resolvePromise ?? Promise.resolve(RenderGraphExecutor._latestProfileResult);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Enable, disable, or update timestamp profiling for this executor.
|
|
71
|
+
*/ setProfilingOptions(options) {
|
|
72
|
+
this._profilingOptions = this._normalizeProfilingOptions(options, this._profilingOptions.device);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get the latest resolved profile result produced by this executor.
|
|
76
|
+
*/ getLatestProfileResult() {
|
|
77
|
+
return this._latestProfileResult;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Resolve the latest pending profile result produced by this executor.
|
|
81
|
+
*/ resolveProfileResult() {
|
|
82
|
+
return this._pendingProfileFrames[this._pendingProfileFrames.length - 1]?.resolvePromise ?? Promise.resolve(this._latestProfileResult);
|
|
32
83
|
}
|
|
33
84
|
/**
|
|
34
85
|
* Update the backbuffer dimensions used for 'backbuffer-relative' sizing.
|
|
@@ -56,6 +107,7 @@ import { RGHandle } from './types.js';
|
|
|
56
107
|
*/ execute(compiled) {
|
|
57
108
|
this._cleanupCallbacks.length = 0;
|
|
58
109
|
this._resolveImportedTextureAliases(compiled);
|
|
110
|
+
const profileFrame = this._beginProfileFrame();
|
|
59
111
|
// Build per-pass allocation and release schedules
|
|
60
112
|
const allocateAt = new Map(); // passIndex -> transient texture resourceIds to allocate
|
|
61
113
|
const releaseAt = new Map(); // passIndex -> transient texture resourceIds to release
|
|
@@ -111,15 +163,19 @@ import { RGHandle } from './types.js';
|
|
|
111
163
|
// Execute the pass with exception safety for resource cleanup.
|
|
112
164
|
// Release errors must not hide the original pass execution error.
|
|
113
165
|
let passError = null;
|
|
166
|
+
const passProfile = this._beginPassProfileScope(profileFrame, pass);
|
|
114
167
|
try {
|
|
115
168
|
if (pass.subpasses.length > 0) {
|
|
116
169
|
const accessScope = this._createAccessScope(pass);
|
|
117
170
|
const ctx = this._createContext(accessScope);
|
|
118
171
|
for (const subpass of pass.subpasses){
|
|
172
|
+
const subpassProfile = this._beginSubpassProfileScope(profileFrame, passProfile, pass, subpass.name);
|
|
119
173
|
try {
|
|
120
174
|
subpass.executeFn(ctx, pass.data);
|
|
121
175
|
} catch (e) {
|
|
122
176
|
throw this._wrapSubpassError(pass.name, subpass.name, e);
|
|
177
|
+
} finally{
|
|
178
|
+
this._endProfileScope(profileFrame, subpassProfile);
|
|
123
179
|
}
|
|
124
180
|
}
|
|
125
181
|
} else if (pass.executeFn) {
|
|
@@ -129,6 +185,8 @@ import { RGHandle } from './types.js';
|
|
|
129
185
|
}
|
|
130
186
|
} catch (e) {
|
|
131
187
|
passError = e;
|
|
188
|
+
} finally{
|
|
189
|
+
this._endProfileScope(profileFrame, passProfile);
|
|
132
190
|
}
|
|
133
191
|
let releaseError = null;
|
|
134
192
|
const framebuffersToRelease = releaseFramebufferAt.get(i);
|
|
@@ -171,6 +229,7 @@ import { RGHandle } from './types.js';
|
|
|
171
229
|
} catch (e) {
|
|
172
230
|
executionError = e;
|
|
173
231
|
} finally{
|
|
232
|
+
this._finishProfileFrame(profileFrame);
|
|
174
233
|
let cleanupError = null;
|
|
175
234
|
try {
|
|
176
235
|
this._runCleanupCallbacks();
|
|
@@ -212,6 +271,216 @@ import { RGHandle } from './types.js';
|
|
|
212
271
|
this._resolvedImportedTextures.clear();
|
|
213
272
|
}
|
|
214
273
|
// ─── Private ────────────────────────────────────────────────────────
|
|
274
|
+
/** @internal */ _normalizeProfilingOptions(options, fallbackDevice) {
|
|
275
|
+
const source = typeof options === 'object' ? options : {};
|
|
276
|
+
const enabled = typeof options === 'boolean' ? options : source.enabled ?? true;
|
|
277
|
+
return {
|
|
278
|
+
enabled,
|
|
279
|
+
graph: source.graph ?? DEFAULT_PROFILING_OPTIONS.graph,
|
|
280
|
+
pass: source.pass ?? DEFAULT_PROFILING_OPTIONS.pass,
|
|
281
|
+
subpass: source.subpass ?? DEFAULT_PROFILING_OPTIONS.subpass,
|
|
282
|
+
includePendingUploads: source.includePendingUploads ?? DEFAULT_PROFILING_OPTIONS.includePendingUploads,
|
|
283
|
+
allowCrossFrame: source.allowCrossFrame ?? DEFAULT_PROFILING_OPTIONS.allowCrossFrame,
|
|
284
|
+
maxPendingFrames: Math.max(1, source.maxPendingFrames ?? DEFAULT_PROFILING_OPTIONS.maxPendingFrames),
|
|
285
|
+
label: source.label ?? DEFAULT_PROFILING_OPTIONS.label,
|
|
286
|
+
device: source.device ?? fallbackDevice
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
/** @internal */ _getProfilingDevice() {
|
|
290
|
+
if (this._profilingOptions.device) {
|
|
291
|
+
return this._profilingOptions.device;
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
return getDevice();
|
|
295
|
+
} catch {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/** @internal */ _beginProfileFrame() {
|
|
300
|
+
if (!this._profilingOptions.enabled) {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
const device = this._getProfilingDevice();
|
|
304
|
+
const supported = !!device?.getDeviceCaps().miscCaps.supportTimestampQuery;
|
|
305
|
+
const rootResult = {
|
|
306
|
+
name: this._profilingOptions.label,
|
|
307
|
+
type: 'graph',
|
|
308
|
+
queryId: 0,
|
|
309
|
+
durationMs: 0,
|
|
310
|
+
status: supported ? 'resolved' : 'unsupported',
|
|
311
|
+
children: [],
|
|
312
|
+
message: supported ? undefined : 'GPU timestamp queries are not supported'
|
|
313
|
+
};
|
|
314
|
+
const result = {
|
|
315
|
+
frameId: device?.frameInfo.frameCounter ?? -1,
|
|
316
|
+
status: rootResult.status,
|
|
317
|
+
graph: rootResult,
|
|
318
|
+
passes: rootResult.children
|
|
319
|
+
};
|
|
320
|
+
const root = {
|
|
321
|
+
result: rootResult,
|
|
322
|
+
queryId: 0,
|
|
323
|
+
ended: false
|
|
324
|
+
};
|
|
325
|
+
const frame = {
|
|
326
|
+
serial: ++RenderGraphExecutor._nextProfileSerial,
|
|
327
|
+
device,
|
|
328
|
+
supported,
|
|
329
|
+
result,
|
|
330
|
+
root,
|
|
331
|
+
scopes: [],
|
|
332
|
+
resolvePromise: Promise.resolve(result)
|
|
333
|
+
};
|
|
334
|
+
if (this._profilingOptions.graph) {
|
|
335
|
+
this._beginTimestampScope(frame, root, this._profilingOptions.label);
|
|
336
|
+
}
|
|
337
|
+
return frame;
|
|
338
|
+
}
|
|
339
|
+
/** @internal */ _beginPassProfileScope(frame, pass) {
|
|
340
|
+
if (!frame || !this._profilingOptions.pass && !this._profilingOptions.subpass) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
return this._beginProfileScope(frame, frame.root, 'pass', pass.name, this._profilingOptions.pass);
|
|
344
|
+
}
|
|
345
|
+
/** @internal */ _beginSubpassProfileScope(frame, passScope, pass, subpassName) {
|
|
346
|
+
if (!frame || !this._profilingOptions.subpass) {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
const parent = passScope ?? frame.root;
|
|
350
|
+
return this._beginProfileScope(frame, parent, 'subpass', subpassName, true, `${pass.name}/${subpassName}`);
|
|
351
|
+
}
|
|
352
|
+
/** @internal */ _beginProfileScope(frame, parent, type, name, queryEnabled, queryLabel) {
|
|
353
|
+
const status = frame.supported && !queryEnabled ? 'resolved' : frame.supported ? 'pending' : 'unsupported';
|
|
354
|
+
const result = {
|
|
355
|
+
name,
|
|
356
|
+
type,
|
|
357
|
+
queryId: 0,
|
|
358
|
+
durationMs: 0,
|
|
359
|
+
status,
|
|
360
|
+
children: [],
|
|
361
|
+
message: frame.supported ? undefined : 'GPU timestamp queries are not supported'
|
|
362
|
+
};
|
|
363
|
+
parent.result.children.push(result);
|
|
364
|
+
const scope = {
|
|
365
|
+
result,
|
|
366
|
+
queryId: 0,
|
|
367
|
+
ended: false
|
|
368
|
+
};
|
|
369
|
+
if (queryEnabled) {
|
|
370
|
+
this._beginTimestampScope(frame, scope, queryLabel ?? name);
|
|
371
|
+
}
|
|
372
|
+
return scope;
|
|
373
|
+
}
|
|
374
|
+
/** @internal */ _beginTimestampScope(frame, scope, label) {
|
|
375
|
+
if (!frame.supported || !frame.device) {
|
|
376
|
+
scope.result.status = 'unsupported';
|
|
377
|
+
scope.result.message = 'GPU timestamp queries are not supported';
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const queryId = frame.device.beginTimestampQuery(label, {
|
|
381
|
+
includePendingUploads: this._profilingOptions.includePendingUploads,
|
|
382
|
+
allowCrossFrame: this._profilingOptions.allowCrossFrame
|
|
383
|
+
});
|
|
384
|
+
scope.queryId = queryId;
|
|
385
|
+
scope.result.queryId = queryId;
|
|
386
|
+
if (queryId > 0) {
|
|
387
|
+
scope.result.status = 'pending';
|
|
388
|
+
frame.scopes.push(scope);
|
|
389
|
+
} else {
|
|
390
|
+
scope.result.status = 'unsupported';
|
|
391
|
+
scope.result.message = 'GPU timestamp query was not started';
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/** @internal */ _endProfileScope(frame, scope) {
|
|
395
|
+
if (!frame || !frame.device || !scope || scope.ended) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
scope.ended = true;
|
|
399
|
+
if (scope.queryId > 0) {
|
|
400
|
+
frame.device.endTimestampQuery(scope.queryId);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/** @internal */ _finishProfileFrame(frame) {
|
|
404
|
+
if (!frame) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
this._endProfileScope(frame, frame.root);
|
|
408
|
+
if (frame.scopes.length === 0 || !frame.device) {
|
|
409
|
+
frame.result.status = this._aggregateProfileStatus(frame.result.graph);
|
|
410
|
+
this._publishProfileResult(frame);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
frame.resolvePromise = Promise.all(frame.scopes.map((scope)=>frame.device.resolveTimestampQuery(scope.queryId))).then((results)=>{
|
|
414
|
+
for(let i = 0; i < results.length; i++){
|
|
415
|
+
this._applyTimestampResult(frame.scopes[i], results[i]);
|
|
416
|
+
}
|
|
417
|
+
frame.result.status = this._aggregateProfileStatus(frame.result.graph);
|
|
418
|
+
this._publishProfileResult(frame);
|
|
419
|
+
return frame.result;
|
|
420
|
+
}).catch((err)=>{
|
|
421
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
422
|
+
for (const scope of frame.scopes){
|
|
423
|
+
scope.result.status = 'failed';
|
|
424
|
+
scope.result.message = message;
|
|
425
|
+
}
|
|
426
|
+
frame.result.status = 'failed';
|
|
427
|
+
this._publishProfileResult(frame);
|
|
428
|
+
return frame.result;
|
|
429
|
+
});
|
|
430
|
+
this._trackPendingProfileFrame(frame);
|
|
431
|
+
}
|
|
432
|
+
/** @internal */ _trackPendingProfileFrame(frame) {
|
|
433
|
+
this._pendingProfileFrames.push(frame);
|
|
434
|
+
while(this._pendingProfileFrames.length > this._profilingOptions.maxPendingFrames){
|
|
435
|
+
this._pendingProfileFrames.shift();
|
|
436
|
+
}
|
|
437
|
+
RenderGraphExecutor._latestPendingProfileFrame = frame;
|
|
438
|
+
}
|
|
439
|
+
/** @internal */ _publishProfileResult(frame) {
|
|
440
|
+
this._pendingProfileFrames = this._pendingProfileFrames.filter((pending)=>pending !== frame);
|
|
441
|
+
if (frame.serial >= this._latestResolvedProfileSerial) {
|
|
442
|
+
this._latestResolvedProfileSerial = frame.serial;
|
|
443
|
+
this._latestProfileResult = frame.result;
|
|
444
|
+
}
|
|
445
|
+
if (frame.serial >= RenderGraphExecutor._latestResolvedProfileSerial) {
|
|
446
|
+
RenderGraphExecutor._latestResolvedProfileSerial = frame.serial;
|
|
447
|
+
RenderGraphExecutor._latestProfileResult = frame.result;
|
|
448
|
+
}
|
|
449
|
+
if (RenderGraphExecutor._latestPendingProfileFrame === frame) {
|
|
450
|
+
RenderGraphExecutor._latestPendingProfileFrame = null;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/** @internal */ _applyTimestampResult(scope, result) {
|
|
454
|
+
scope.result.queryId = result.id;
|
|
455
|
+
scope.result.durationMs = result.durationMs;
|
|
456
|
+
scope.result.status = result.status;
|
|
457
|
+
scope.result.message = result.message;
|
|
458
|
+
}
|
|
459
|
+
/** @internal */ _aggregateProfileStatus(scope) {
|
|
460
|
+
const statuses = [];
|
|
461
|
+
const collect = (node)=>{
|
|
462
|
+
statuses.push(node.status);
|
|
463
|
+
for (const child of node.children){
|
|
464
|
+
collect(child);
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
collect(scope);
|
|
468
|
+
for (const status of [
|
|
469
|
+
'failed',
|
|
470
|
+
'invalid',
|
|
471
|
+
'exhausted',
|
|
472
|
+
'pending',
|
|
473
|
+
'auto-closed'
|
|
474
|
+
]){
|
|
475
|
+
if (statuses.includes(status)) {
|
|
476
|
+
return status;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
if (statuses.includes('unsupported')) {
|
|
480
|
+
return 'unsupported';
|
|
481
|
+
}
|
|
482
|
+
return 'resolved';
|
|
483
|
+
}
|
|
215
484
|
/** @internal */ _resolveSize(desc) {
|
|
216
485
|
const mode = desc.sizeMode ?? 'backbuffer-relative';
|
|
217
486
|
if (mode === 'absolute') {
|