@forgeax/engine-rhi-debug 0.0.0-dev.8d955ade1c79

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.
Files changed (237) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +178 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/bootstrap-integration.test.d.ts +2 -0
  5. package/dist/__tests__/bootstrap-integration.test.d.ts.map +1 -0
  6. package/dist/__tests__/consumer-inventory.unit.test.d.ts +2 -0
  7. package/dist/__tests__/consumer-inventory.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/copy-src-usage-validity.dawn.test.d.ts +15 -0
  9. package/dist/__tests__/copy-src-usage-validity.dawn.test.d.ts.map +1 -0
  10. package/dist/__tests__/coverage-invariant.test-d.d.ts +2 -0
  11. package/dist/__tests__/coverage-invariant.test-d.d.ts.map +1 -0
  12. package/dist/__tests__/coverage-invariant.unit.test.d.ts +2 -0
  13. package/dist/__tests__/coverage-invariant.unit.test.d.ts.map +1 -0
  14. package/dist/__tests__/e2e.browser.test.d.ts +2 -0
  15. package/dist/__tests__/e2e.browser.test.d.ts.map +1 -0
  16. package/dist/__tests__/error-code-narrowing.test-d.d.ts +2 -0
  17. package/dist/__tests__/error-code-narrowing.test-d.d.ts.map +1 -0
  18. package/dist/__tests__/errors-v7.test-d.d.ts +2 -0
  19. package/dist/__tests__/errors-v7.test-d.d.ts.map +1 -0
  20. package/dist/__tests__/errors-v7.unit.test.d.ts +2 -0
  21. package/dist/__tests__/errors-v7.unit.test.d.ts.map +1 -0
  22. package/dist/__tests__/errors.unit.test.d.ts +2 -0
  23. package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
  24. package/dist/__tests__/event-semantics-coverage.unit.test.d.ts +2 -0
  25. package/dist/__tests__/event-semantics-coverage.unit.test.d.ts.map +1 -0
  26. package/dist/__tests__/format-info.unit.test.d.ts +2 -0
  27. package/dist/__tests__/format-info.unit.test.d.ts.map +1 -0
  28. package/dist/__tests__/frame-model-parity.test-d.d.ts +2 -0
  29. package/dist/__tests__/frame-model-parity.test-d.d.ts.map +1 -0
  30. package/dist/__tests__/frame-model-parity.unit.test.d.ts +2 -0
  31. package/dist/__tests__/frame-model-parity.unit.test.d.ts.map +1 -0
  32. package/dist/__tests__/guard-gates.test.d.ts +2 -0
  33. package/dist/__tests__/guard-gates.test.d.ts.map +1 -0
  34. package/dist/__tests__/public-surface.integration.test.d.ts +2 -0
  35. package/dist/__tests__/public-surface.integration.test.d.ts.map +1 -0
  36. package/dist/__tests__/readback-fault-cleanup.unit.test.d.ts +2 -0
  37. package/dist/__tests__/readback-fault-cleanup.unit.test.d.ts.map +1 -0
  38. package/dist/__tests__/readback-format-matrix.dawn.test.d.ts +2 -0
  39. package/dist/__tests__/readback-format-matrix.dawn.test.d.ts.map +1 -0
  40. package/dist/__tests__/readback-format-matrix.unit.test.d.ts +2 -0
  41. package/dist/__tests__/readback-format-matrix.unit.test.d.ts.map +1 -0
  42. package/dist/__tests__/readback-matrix-fixture.d.ts +32 -0
  43. package/dist/__tests__/readback-matrix-fixture.d.ts.map +1 -0
  44. package/dist/__tests__/recorder-bootstrap.unit.test.d.ts +2 -0
  45. package/dist/__tests__/recorder-bootstrap.unit.test.d.ts.map +1 -0
  46. package/dist/__tests__/recorder-budget.unit.test.d.ts +2 -0
  47. package/dist/__tests__/recorder-budget.unit.test.d.ts.map +1 -0
  48. package/dist/__tests__/recorder-fail-fast.unit.test.d.ts +2 -0
  49. package/dist/__tests__/recorder-fail-fast.unit.test.d.ts.map +1 -0
  50. package/dist/__tests__/recorder-lifecycle.unit.test.d.ts +2 -0
  51. package/dist/__tests__/recorder-lifecycle.unit.test.d.ts.map +1 -0
  52. package/dist/__tests__/recorder-push-event.unit.test.d.ts +2 -0
  53. package/dist/__tests__/recorder-push-event.unit.test.d.ts.map +1 -0
  54. package/dist/__tests__/recorder-session.integration.test.d.ts +2 -0
  55. package/dist/__tests__/recorder-session.integration.test.d.ts.map +1 -0
  56. package/dist/__tests__/recorder-session.unit.test.d.ts +2 -0
  57. package/dist/__tests__/recorder-session.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/recorder-steady-frame.dawn.test.d.ts +2 -0
  59. package/dist/__tests__/recorder-steady-frame.dawn.test.d.ts.map +1 -0
  60. package/dist/__tests__/replay-fail-fast.unit.test.d.ts +2 -0
  61. package/dist/__tests__/replay-fail-fast.unit.test.d.ts.map +1 -0
  62. package/dist/__tests__/replay-session.dawn.test.d.ts +2 -0
  63. package/dist/__tests__/replay-session.dawn.test.d.ts.map +1 -0
  64. package/dist/__tests__/replay-session.test-d.d.ts +2 -0
  65. package/dist/__tests__/replay-session.test-d.d.ts.map +1 -0
  66. package/dist/__tests__/replay-session.unit.test.d.ts +2 -0
  67. package/dist/__tests__/replay-session.unit.test.d.ts.map +1 -0
  68. package/dist/__tests__/resolve-texture-descriptor.unit.test.d.ts +2 -0
  69. package/dist/__tests__/resolve-texture-descriptor.unit.test.d.ts.map +1 -0
  70. package/dist/__tests__/resource-lifecycle.unit.test.d.ts +2 -0
  71. package/dist/__tests__/resource-lifecycle.unit.test.d.ts.map +1 -0
  72. package/dist/__tests__/rhi-debug-fresh-replay.dawn.test.d.ts +2 -0
  73. package/dist/__tests__/rhi-debug-fresh-replay.dawn.test.d.ts.map +1 -0
  74. package/dist/__tests__/tape-index.unit.test.d.ts +2 -0
  75. package/dist/__tests__/tape-index.unit.test.d.ts.map +1 -0
  76. package/dist/__tests__/tape-v7-invalid.unit.test.d.ts +2 -0
  77. package/dist/__tests__/tape-v7-invalid.unit.test.d.ts.map +1 -0
  78. package/dist/__tests__/tape-v7-roundtrip.unit.test.d.ts +2 -0
  79. package/dist/__tests__/tape-v7-roundtrip.unit.test.d.ts.map +1 -0
  80. package/dist/__tests__/tape-v7-version.unit.test.d.ts +2 -0
  81. package/dist/__tests__/tape-v7-version.unit.test.d.ts.map +1 -0
  82. package/dist/__tests__/tree-shake.unit.test.d.ts +2 -0
  83. package/dist/__tests__/tree-shake.unit.test.d.ts.map +1 -0
  84. package/dist/__tests__/types.test.d.ts +2 -0
  85. package/dist/__tests__/types.test.d.ts.map +1 -0
  86. package/dist/browser.d.ts +45 -0
  87. package/dist/browser.d.ts.map +1 -0
  88. package/dist/browser.mjs +83 -0
  89. package/dist/browser.mjs.map +1 -0
  90. package/dist/errors.d.ts +85 -0
  91. package/dist/errors.d.ts.map +1 -0
  92. package/dist/frame-model.d.ts +158 -0
  93. package/dist/frame-model.d.ts.map +1 -0
  94. package/dist/index.d.ts +17 -0
  95. package/dist/index.d.ts.map +1 -0
  96. package/dist/index.mjs +10415 -0
  97. package/dist/index.mjs.map +1 -0
  98. package/dist/protocol/codec.d.ts +6 -0
  99. package/dist/protocol/codec.d.ts.map +1 -0
  100. package/dist/protocol/event-semantics.d.ts +15 -0
  101. package/dist/protocol/event-semantics.d.ts.map +1 -0
  102. package/dist/protocol/tape-index.d.ts +30 -0
  103. package/dist/protocol/tape-index.d.ts.map +1 -0
  104. package/dist/protocol/types.d.ts +44 -0
  105. package/dist/protocol/types.d.ts.map +1 -0
  106. package/dist/protocol/validation.d.ts +7 -0
  107. package/dist/protocol/validation.d.ts.map +1 -0
  108. package/dist/readback.d.ts +156 -0
  109. package/dist/readback.d.ts.map +1 -0
  110. package/dist/recorder/assemble.d.ts +11 -0
  111. package/dist/recorder/assemble.d.ts.map +1 -0
  112. package/dist/recorder/closure.d.ts +39 -0
  113. package/dist/recorder/closure.d.ts.map +1 -0
  114. package/dist/recorder/core.d.ts +296 -0
  115. package/dist/recorder/core.d.ts.map +1 -0
  116. package/dist/recorder/device.d.ts +4 -0
  117. package/dist/recorder/device.d.ts.map +1 -0
  118. package/dist/recorder/encoder.d.ts +5 -0
  119. package/dist/recorder/encoder.d.ts.map +1 -0
  120. package/dist/recorder/lifecycle.d.ts +20 -0
  121. package/dist/recorder/lifecycle.d.ts.map +1 -0
  122. package/dist/recorder/pass.d.ts +6 -0
  123. package/dist/recorder/pass.d.ts.map +1 -0
  124. package/dist/recorder/proxy.d.ts +19 -0
  125. package/dist/recorder/proxy.d.ts.map +1 -0
  126. package/dist/recorder/queue.d.ts +4 -0
  127. package/dist/recorder/queue.d.ts.map +1 -0
  128. package/dist/recorder/resource-registry.d.ts +15 -0
  129. package/dist/recorder/resource-registry.d.ts.map +1 -0
  130. package/dist/recorder/session.d.ts +25 -0
  131. package/dist/recorder/session.d.ts.map +1 -0
  132. package/dist/recorder/shader.d.ts +3 -0
  133. package/dist/recorder/shader.d.ts.map +1 -0
  134. package/dist/recorder/snapshot.d.ts +10 -0
  135. package/dist/recorder/snapshot.d.ts.map +1 -0
  136. package/dist/recorder/wrap.d.ts +4 -0
  137. package/dist/recorder/wrap.d.ts.map +1 -0
  138. package/dist/recorder.d.ts +7 -0
  139. package/dist/recorder.d.ts.map +1 -0
  140. package/dist/replay/device-request.d.ts +8 -0
  141. package/dist/replay/device-request.d.ts.map +1 -0
  142. package/dist/replay/execute-support.d.ts +33 -0
  143. package/dist/replay/execute-support.d.ts.map +1 -0
  144. package/dist/replay/execute.d.ts +18 -0
  145. package/dist/replay/execute.d.ts.map +1 -0
  146. package/dist/replay/readback.d.ts +32 -0
  147. package/dist/replay/readback.d.ts.map +1 -0
  148. package/dist/replay/resources.d.ts +57 -0
  149. package/dist/replay/resources.d.ts.map +1 -0
  150. package/dist/replay/session.d.ts +49 -0
  151. package/dist/replay/session.d.ts.map +1 -0
  152. package/dist/replay/texture-format.d.ts +21 -0
  153. package/dist/replay/texture-format.d.ts.map +1 -0
  154. package/dist/texel-decode.d.ts +35 -0
  155. package/dist/texel-decode.d.ts.map +1 -0
  156. package/dist/texel-layout.d.ts +76 -0
  157. package/dist/texel-layout.d.ts.map +1 -0
  158. package/dist/types.d.ts +722 -0
  159. package/dist/types.d.ts.map +1 -0
  160. package/package.json +72 -0
  161. package/src/__tests__/bootstrap-integration.test.ts +268 -0
  162. package/src/__tests__/consumer-inventory.unit.test.ts +154 -0
  163. package/src/__tests__/copy-src-usage-validity.dawn.test.ts +117 -0
  164. package/src/__tests__/coverage-invariant.test-d.ts +115 -0
  165. package/src/__tests__/coverage-invariant.unit.test.ts +45 -0
  166. package/src/__tests__/e2e.browser.test.ts +239 -0
  167. package/src/__tests__/error-code-narrowing.test-d.ts +32 -0
  168. package/src/__tests__/errors-v7.test-d.ts +19 -0
  169. package/src/__tests__/errors-v7.unit.test.ts +15 -0
  170. package/src/__tests__/errors.unit.test.ts +83 -0
  171. package/src/__tests__/event-semantics-coverage.unit.test.ts +22 -0
  172. package/src/__tests__/format-info.unit.test.ts +85 -0
  173. package/src/__tests__/frame-model-parity.test-d.ts +20 -0
  174. package/src/__tests__/frame-model-parity.unit.test.ts +285 -0
  175. package/src/__tests__/guard-gates.test.ts +317 -0
  176. package/src/__tests__/public-surface.integration.test.ts +38 -0
  177. package/src/__tests__/readback-fault-cleanup.unit.test.ts +66 -0
  178. package/src/__tests__/readback-format-matrix.dawn.test.ts +378 -0
  179. package/src/__tests__/readback-format-matrix.unit.test.ts +120 -0
  180. package/src/__tests__/readback-matrix-fixture.ts +20 -0
  181. package/src/__tests__/recorder-bootstrap.unit.test.ts +302 -0
  182. package/src/__tests__/recorder-budget.unit.test.ts +42 -0
  183. package/src/__tests__/recorder-fail-fast.unit.test.ts +434 -0
  184. package/src/__tests__/recorder-lifecycle.unit.test.ts +42 -0
  185. package/src/__tests__/recorder-push-event.unit.test.ts +573 -0
  186. package/src/__tests__/recorder-session.integration.test.ts +32 -0
  187. package/src/__tests__/recorder-session.unit.test.ts +63 -0
  188. package/src/__tests__/recorder-steady-frame.dawn.test.ts +52 -0
  189. package/src/__tests__/replay-fail-fast.unit.test.ts +155 -0
  190. package/src/__tests__/replay-session.dawn.test.ts +220 -0
  191. package/src/__tests__/replay-session.test-d.ts +27 -0
  192. package/src/__tests__/replay-session.unit.test.ts +171 -0
  193. package/src/__tests__/resolve-texture-descriptor.unit.test.ts +144 -0
  194. package/src/__tests__/resource-lifecycle.unit.test.ts +114 -0
  195. package/src/__tests__/rhi-debug-fresh-replay.dawn.test.ts +178 -0
  196. package/src/__tests__/tape-index.unit.test.ts +56 -0
  197. package/src/__tests__/tape-v7-invalid.unit.test.ts +45 -0
  198. package/src/__tests__/tape-v7-roundtrip.unit.test.ts +66 -0
  199. package/src/__tests__/tape-v7-version.unit.test.ts +13 -0
  200. package/src/__tests__/tree-shake.unit.test.ts +151 -0
  201. package/src/__tests__/types.test.ts +257 -0
  202. package/src/browser.ts +146 -0
  203. package/src/errors.ts +173 -0
  204. package/src/frame-model.ts +753 -0
  205. package/src/index.ts +92 -0
  206. package/src/pako.d.ts +8 -0
  207. package/src/protocol/codec.ts +220 -0
  208. package/src/protocol/event-semantics.ts +180 -0
  209. package/src/protocol/tape-index.ts +120 -0
  210. package/src/protocol/types.ts +60 -0
  211. package/src/protocol/validation.ts +173 -0
  212. package/src/readback.ts +804 -0
  213. package/src/recorder/assemble.ts +192 -0
  214. package/src/recorder/closure.ts +408 -0
  215. package/src/recorder/core.ts +587 -0
  216. package/src/recorder/device.ts +474 -0
  217. package/src/recorder/encoder.ts +251 -0
  218. package/src/recorder/lifecycle.ts +847 -0
  219. package/src/recorder/pass.ts +241 -0
  220. package/src/recorder/proxy.ts +59 -0
  221. package/src/recorder/queue.ts +109 -0
  222. package/src/recorder/resource-registry.ts +53 -0
  223. package/src/recorder/session.ts +152 -0
  224. package/src/recorder/shader.ts +47 -0
  225. package/src/recorder/snapshot.ts +38 -0
  226. package/src/recorder/wrap.ts +157 -0
  227. package/src/recorder.ts +15 -0
  228. package/src/replay/device-request.ts +38 -0
  229. package/src/replay/execute-support.ts +243 -0
  230. package/src/replay/execute.ts +870 -0
  231. package/src/replay/readback.ts +671 -0
  232. package/src/replay/resources.ts +154 -0
  233. package/src/replay/session.ts +509 -0
  234. package/src/replay/texture-format.ts +99 -0
  235. package/src/texel-decode.ts +289 -0
  236. package/src/texel-layout.ts +324 -0
  237. package/src/types.ts +888 -0
@@ -0,0 +1,753 @@
1
+ // @forgeax/engine-rhi-debug/src/frame-model -- v7 structural tape projection.
2
+
3
+ /// <reference types="@webgpu/types" />
4
+
5
+ import {
6
+ EVENT_SEMANTICS,
7
+ type EventCategory,
8
+ isWorkEvent,
9
+ resourceKindForEvent,
10
+ } from './protocol/event-semantics';
11
+ import type { TapeWorkEntry } from './protocol/tape-index';
12
+ import { buildTapeIndex } from './protocol/tape-index';
13
+ import type { Tape as V7Tape } from './protocol/types';
14
+ import { computeTextureLayout } from './texel-layout';
15
+ import type { HandleId, RhiCallEvent } from './types';
16
+
17
+ type JsonPrimitive = string | number | boolean | null;
18
+ export type JsonValue =
19
+ | JsonPrimitive
20
+ | readonly JsonValue[]
21
+ | { readonly [key: string]: JsonValue };
22
+
23
+ export interface CommandEntry {
24
+ readonly eventIndex: number;
25
+ readonly passIndex: number;
26
+ readonly kind: string;
27
+ readonly category: EventCategory;
28
+ readonly isWork: boolean;
29
+ readonly params: JsonValue;
30
+ readonly group: readonly string[];
31
+ readonly marker?: string;
32
+ }
33
+
34
+ export interface FramePass {
35
+ readonly passIndex: number;
36
+ readonly kind: 'render' | 'compute';
37
+ readonly beginEventIndex: number;
38
+ readonly endEventIndex: number | null;
39
+ readonly workIndices: readonly number[];
40
+ readonly commandIndices: readonly number[];
41
+ colorAttachmentViewHandleIds: readonly string[];
42
+ depthStencilViewHandleId: string | null;
43
+ }
44
+
45
+ export interface ResourceConsumer {
46
+ readonly eventIndex: number;
47
+ readonly workIndex: number | null;
48
+ readonly access: 'read' | 'write';
49
+ }
50
+
51
+ export interface ResourceEntry {
52
+ readonly resourceId: string;
53
+ readonly kind: string;
54
+ readonly origin: 'bootstrap' | 'frame';
55
+ readonly createEventIndex: number | null;
56
+ readonly destroyEventIndex: number | null;
57
+ readonly descriptor: JsonValue | null;
58
+ consumers: readonly ResourceConsumer[];
59
+ readonly lifecycle: {
60
+ readonly state: 'live' | 'destroyed' | 'unavailable';
61
+ readonly byteEstimate: ResourceByteEstimate | null;
62
+ };
63
+ }
64
+
65
+ export interface WorkBinding {
66
+ readonly groupIndex: number;
67
+ readonly binding: number;
68
+ readonly bindGroupId: string;
69
+ readonly resourceId: string | null;
70
+ readonly resourceKind: string | null;
71
+ readonly bufferOffset: number | null;
72
+ readonly bufferSize: number | null;
73
+ }
74
+
75
+ export interface WorkPipeline {
76
+ readonly status: 'available' | 'unavailable';
77
+ readonly pipelineHandleId?: string;
78
+ readonly kind?: 'render' | 'compute';
79
+ readonly descriptor?: JsonValue;
80
+ readonly shaders: readonly {
81
+ readonly stage: 'vertex' | 'fragment' | 'compute';
82
+ readonly moduleHandleId: string;
83
+ readonly entryPoint: string | null;
84
+ readonly source: string | null;
85
+ }[];
86
+ readonly reason?: 'pipeline-not-bound' | 'descriptor-unavailable';
87
+ }
88
+
89
+ export interface WorkEntry extends TapeWorkEntry {
90
+ readonly commandIndex: number;
91
+ readonly drawCall: JsonValue;
92
+ readonly pipeline: WorkPipeline;
93
+ readonly bindings: readonly WorkBinding[];
94
+ readonly vertexBuffers: readonly {
95
+ readonly slot: number;
96
+ readonly bufferHandleId: string;
97
+ readonly offset: number;
98
+ readonly size: number | null;
99
+ }[];
100
+ readonly indexBuffer: {
101
+ readonly bufferHandleId: string;
102
+ readonly format: string;
103
+ readonly offset: number;
104
+ readonly size: number | null;
105
+ } | null;
106
+ readonly attachments: {
107
+ readonly colorViewHandleIds: readonly string[];
108
+ readonly depthStencilViewHandleId: string | null;
109
+ } | null;
110
+ }
111
+
112
+ export interface FrameModel {
113
+ readonly commands: readonly CommandEntry[];
114
+ readonly passes: readonly FramePass[];
115
+ readonly resources: readonly ResourceEntry[];
116
+ readonly resourceLifecycle: ResourceLifecycleSummary;
117
+ readonly works: readonly WorkEntry[];
118
+ }
119
+
120
+ function jsonValue(value: unknown): JsonValue {
121
+ if (
122
+ value === null ||
123
+ typeof value === 'string' ||
124
+ typeof value === 'number' ||
125
+ typeof value === 'boolean'
126
+ )
127
+ return value;
128
+ if (value === undefined) return null;
129
+ if (Array.isArray(value)) return value.map(jsonValue);
130
+ if (ArrayBuffer.isView(value))
131
+ return Array.from(value as unknown as ArrayLike<unknown>, jsonValue);
132
+ if (typeof value === 'object') {
133
+ const output: Record<string, JsonValue> = {};
134
+ for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
135
+ if (child !== undefined) output[key] = jsonValue(child);
136
+ }
137
+ return output;
138
+ }
139
+ return String(value);
140
+ }
141
+
142
+ function eventDescriptor(event: RhiCallEvent): JsonValue {
143
+ return jsonValue(event);
144
+ }
145
+
146
+ function workPipeline(
147
+ pipelineId: string | undefined,
148
+ pipelineEvents: ReadonlyMap<string, RhiCallEvent>,
149
+ shaderEvents: ReadonlyMap<string, RhiCallEvent>,
150
+ ): WorkPipeline {
151
+ if (pipelineId === undefined)
152
+ return { status: 'unavailable', shaders: [], reason: 'pipeline-not-bound' };
153
+ const event = pipelineEvents.get(pipelineId);
154
+ if (event === undefined)
155
+ return { status: 'unavailable', shaders: [], reason: 'descriptor-unavailable' };
156
+ const shaders: WorkPipeline['shaders'][number][] = [];
157
+ const shaderRefs =
158
+ event.kind === 'createRenderPipeline'
159
+ ? [
160
+ ['vertex', event.vertexShaderModuleHandleId, event.desc.vertex?.entryPoint],
161
+ ['fragment', event.fragmentShaderModuleHandleId, event.desc.fragment?.entryPoint],
162
+ ]
163
+ : event.kind === 'createComputePipeline'
164
+ ? [['compute', event.computeShaderModuleHandleId, event.desc.compute.entryPoint]]
165
+ : [];
166
+ for (const [stage, moduleId, entryPoint] of shaderRefs) {
167
+ if (typeof moduleId !== 'string') continue;
168
+ const shader = shaderEvents.get(moduleId);
169
+ shaders.push({
170
+ stage: stage as 'vertex' | 'fragment' | 'compute',
171
+ moduleHandleId: moduleId,
172
+ entryPoint: typeof entryPoint === 'string' ? entryPoint : null,
173
+ source: shader?.kind === 'createShaderModule' ? shader.wgslCode : null,
174
+ });
175
+ }
176
+ return {
177
+ status: 'available',
178
+ pipelineHandleId: pipelineId,
179
+ kind: event.kind === 'createRenderPipeline' ? 'render' : 'compute',
180
+ descriptor: eventDescriptor(event),
181
+ shaders,
182
+ };
183
+ }
184
+
185
+ export function buildFrameModel(tape: V7Tape): FrameModel {
186
+ const index = buildTapeIndex(tape);
187
+ const events = tape.events;
188
+ const passCommandIndices = index.passes.map(() => [] as number[]);
189
+ const commands: CommandEntry[] = [];
190
+ const groupPath: string[] = [];
191
+ const currentPassByEvent = index.passIndexByEvent;
192
+ const passAttachmentByIndex = new Map<number, FramePass>();
193
+
194
+ for (const pass of index.passes) {
195
+ passAttachmentByIndex.set(pass.passIndex, {
196
+ passIndex: pass.passIndex,
197
+ kind: pass.kind,
198
+ beginEventIndex: pass.beginEventIndex,
199
+ endEventIndex: pass.endEventIndex ?? null,
200
+ workIndices: pass.workIndices,
201
+ commandIndices: [],
202
+ colorAttachmentViewHandleIds: [],
203
+ depthStencilViewHandleId: null,
204
+ });
205
+ const begin = events[pass.beginEventIndex];
206
+ if (begin?.kind === 'beginRenderPass') {
207
+ const attachment = passAttachmentByIndex.get(pass.passIndex);
208
+ if (attachment !== undefined) {
209
+ attachment.colorAttachmentViewHandleIds = begin.colorAttachmentViewHandleIds.filter(
210
+ (id): id is string => typeof id === 'string',
211
+ );
212
+ attachment.depthStencilViewHandleId = begin.depthStencilViewHandleId ?? null;
213
+ }
214
+ }
215
+ }
216
+
217
+ for (const [eventIndex, event] of events.entries()) {
218
+ const passIndex = currentPassByEvent[eventIndex] ?? -1;
219
+ const isPush = event.kind === 'pushDebugGroup' || event.kind === 'passPushDebugGroup';
220
+ const isPop = event.kind === 'popDebugGroup' || event.kind === 'passPopDebugGroup';
221
+ const group = isPush ? [...groupPath, event.groupLabel] : [...groupPath];
222
+ const marker =
223
+ event.kind === 'insertDebugMarker' || event.kind === 'passInsertDebugMarker'
224
+ ? event.markerLabel
225
+ : undefined;
226
+ const commandIndex = commands.length;
227
+ commands.push({
228
+ eventIndex,
229
+ passIndex,
230
+ kind: event.kind,
231
+ category: EVENT_SEMANTICS[event.kind].category,
232
+ isWork: isWorkEvent(event.kind),
233
+ params: eventDescriptor(event),
234
+ group,
235
+ ...(marker === undefined ? {} : { marker }),
236
+ });
237
+ if (passIndex >= 0) passCommandIndices[passIndex]?.push(commandIndex);
238
+ if (isPush) groupPath.push(event.groupLabel);
239
+ if (isPop) groupPath.pop();
240
+ }
241
+
242
+ const pipelineEvents = new Map<string, RhiCallEvent>();
243
+ const shaderEvents = new Map<string, RhiCallEvent>();
244
+ const bindGroups = new Map<string, Extract<RhiCallEvent, { kind: 'createBindGroup' }>>();
245
+ const resourceRecords = new Map<string, ResourceEntry>();
246
+ const workByEvent = new Map<number, number>();
247
+ for (const work of index.works) workByEvent.set(work.eventIndex, work.workIndex);
248
+
249
+ for (const bootstrap of tape.bootstrap) {
250
+ // Strict v7 decoding validates each bootstrap create record against the same
251
+ // RhiCallEvent union before FrameModel construction.
252
+ const create = bootstrap.create as unknown as RhiCallEvent;
253
+ if (create.kind === 'createRenderPipeline' || create.kind === 'createComputePipeline')
254
+ pipelineEvents.set(create.handleId, create);
255
+ if (create.kind === 'createShaderModule') shaderEvents.set(create.handleId, create);
256
+ if (create.kind === 'createBindGroup') bindGroups.set(create.handleId, create);
257
+ resourceRecords.set(bootstrap.handleId, {
258
+ resourceId: bootstrap.handleId,
259
+ kind: bootstrap.kind,
260
+ origin: 'bootstrap',
261
+ createEventIndex: null,
262
+ destroyEventIndex: null,
263
+ descriptor: jsonValue(bootstrap.create),
264
+ consumers: [],
265
+ lifecycle: { state: 'unavailable', byteEstimate: null },
266
+ });
267
+ }
268
+ for (const [eventIndex, event] of events.entries()) {
269
+ if (event.kind === 'createRenderPipeline' || event.kind === 'createComputePipeline')
270
+ pipelineEvents.set(event.handleId, event);
271
+ if (event.kind === 'createShaderModule') shaderEvents.set(event.handleId, event);
272
+ if (event.kind === 'createBindGroup') bindGroups.set(event.handleId, event);
273
+ const kind = resourceKindForEvent(event.kind);
274
+ const handleId =
275
+ kind === undefined
276
+ ? undefined
277
+ : event.kind === 'createTextureView'
278
+ ? event.resultHandleId
279
+ : event.kind === 'createCommandEncoder'
280
+ ? event.cmdHandleId
281
+ : 'handleId' in event
282
+ ? event.handleId
283
+ : undefined;
284
+ if (kind !== undefined && handleId !== undefined) {
285
+ resourceRecords.set(handleId, {
286
+ resourceId: handleId,
287
+ kind,
288
+ origin: 'frame',
289
+ createEventIndex: eventIndex,
290
+ destroyEventIndex: null,
291
+ descriptor: eventDescriptor(event),
292
+ consumers: [],
293
+ lifecycle: { state: 'unavailable', byteEstimate: null },
294
+ });
295
+ }
296
+ }
297
+
298
+ const lifecycle = buildResourceLifecycle(events);
299
+ for (const record of lifecycle.resources) {
300
+ const resource = resourceRecords.get(record.handleId);
301
+ if (resource === undefined) continue;
302
+ resourceRecords.set(record.handleId, {
303
+ ...resource,
304
+ destroyEventIndex: record.destroyedEventIndex ?? null,
305
+ lifecycle: { state: record.state, byteEstimate: record.byteEstimate },
306
+ });
307
+ }
308
+ for (const [eventIndex, event] of events.entries()) {
309
+ const workIndex = workByEvent.get(eventIndex) ?? null;
310
+ for (const resourceId of EVENT_SEMANTICS[event.kind].read(event)) {
311
+ const resource = resourceRecords.get(resourceId);
312
+ if (resource !== undefined)
313
+ resource.consumers = [...resource.consumers, { eventIndex, workIndex, access: 'read' }];
314
+ }
315
+ for (const resourceId of EVENT_SEMANTICS[event.kind].written(event)) {
316
+ const resource = resourceRecords.get(resourceId);
317
+ if (resource !== undefined)
318
+ resource.consumers = [...resource.consumers, { eventIndex, workIndex, access: 'write' }];
319
+ }
320
+ }
321
+
322
+ const works: WorkEntry[] = [];
323
+ const currentPipeline = new Map<string, string>();
324
+ const currentVertexBuffers = new Map<
325
+ string,
326
+ Map<number, { bufferHandleId: string; offset: number; size: number | null }>
327
+ >();
328
+ const currentIndexBuffers = new Map<string, WorkEntry['indexBuffer']>();
329
+ const currentBindGroups = new Map<string, Map<number, string>>();
330
+ const workByEventEntry = new Map(index.works.map((work) => [work.eventIndex, work]));
331
+ for (const [eventIndex, event] of events.entries()) {
332
+ if (event === undefined) continue;
333
+ const passHandleId = 'passHandleId' in event ? event.passHandleId : '';
334
+ if (event.kind === 'setPipeline' || event.kind === 'setComputePipeline')
335
+ currentPipeline.set(passHandleId, event.pipelineHandleId);
336
+ if (event.kind === 'setVertexBuffer') {
337
+ const buffers = currentVertexBuffers.get(passHandleId) ?? new Map();
338
+ buffers.set(event.slot, {
339
+ bufferHandleId: event.bufferHandleId,
340
+ offset: event.offset ?? 0,
341
+ size: event.size ?? null,
342
+ });
343
+ currentVertexBuffers.set(passHandleId, buffers);
344
+ }
345
+ if (event.kind === 'setIndexBuffer')
346
+ currentIndexBuffers.set(passHandleId, {
347
+ bufferHandleId: event.bufferHandleId,
348
+ format: event.format,
349
+ offset: event.offset ?? 0,
350
+ size: event.size ?? null,
351
+ });
352
+ if (event.kind === 'setBindGroup') {
353
+ const groups = currentBindGroups.get(passHandleId) ?? new Map();
354
+ groups.set(event.index, event.bindGroupHandleId);
355
+ currentBindGroups.set(passHandleId, groups);
356
+ }
357
+ if (!isWorkEvent(event.kind)) continue;
358
+ const work = workByEventEntry.get(eventIndex);
359
+ if (work === undefined) continue;
360
+ const pipelineId = currentPipeline.get(passHandleId);
361
+ const groups = currentBindGroups.get(passHandleId) ?? new Map();
362
+ const bindings: WorkBinding[] = [];
363
+ for (const [groupIndex, bindGroupId] of groups) {
364
+ const bindGroup = bindGroups.get(bindGroupId);
365
+ for (const [entryIndex, resourceId] of bindGroup?.resourceHandleIds.entries() ?? []) {
366
+ const entry = bindGroup?.entries[entryIndex];
367
+ bindings.push({
368
+ groupIndex,
369
+ binding: entry?.binding ?? entryIndex,
370
+ bindGroupId,
371
+ resourceId: resourceId ?? null,
372
+ resourceKind: entry?.resourceKind ?? null,
373
+ bufferOffset: entry?.bufferOffset ?? null,
374
+ bufferSize: entry?.bufferSize ?? null,
375
+ });
376
+ }
377
+ }
378
+ const attachment = passAttachmentByIndex.get(work.passIndex);
379
+ works.push({
380
+ ...work,
381
+ commandIndex: commands.findIndex((command) => command.eventIndex === work.eventIndex),
382
+ drawCall: eventDescriptor(event),
383
+ pipeline: workPipeline(pipelineId, pipelineEvents, shaderEvents),
384
+ bindings,
385
+ vertexBuffers: [...(currentVertexBuffers.get(passHandleId)?.entries() ?? [])].map(
386
+ ([slot, buffer]) => ({ slot, ...buffer }),
387
+ ),
388
+ indexBuffer: currentIndexBuffers.get(passHandleId) ?? null,
389
+ attachments:
390
+ attachment === undefined
391
+ ? null
392
+ : {
393
+ colorViewHandleIds: attachment.colorAttachmentViewHandleIds,
394
+ depthStencilViewHandleId: attachment.depthStencilViewHandleId,
395
+ },
396
+ });
397
+ }
398
+
399
+ const passes = index.passes.map((pass) => ({
400
+ ...pass,
401
+ endEventIndex: pass.endEventIndex ?? null,
402
+ commandIndices: passCommandIndices[pass.passIndex] ?? [],
403
+ colorAttachmentViewHandleIds:
404
+ passAttachmentByIndex.get(pass.passIndex)?.colorAttachmentViewHandleIds ?? [],
405
+ depthStencilViewHandleId:
406
+ passAttachmentByIndex.get(pass.passIndex)?.depthStencilViewHandleId ?? null,
407
+ }));
408
+ return {
409
+ commands,
410
+ passes,
411
+ resources: [...resourceRecords.values()],
412
+ resourceLifecycle: lifecycle,
413
+ works,
414
+ };
415
+ }
416
+
417
+ /** A byte estimate that distinguishes known descriptor size from unavailable GPU facts. */
418
+ export type ResourceByteEstimate =
419
+ | {
420
+ readonly status: 'known';
421
+ readonly bytes: number;
422
+ readonly basis: 'buffer-descriptor' | 'texture-tight-layout';
423
+ }
424
+ | {
425
+ readonly status: 'unavailable';
426
+ readonly reason:
427
+ | 'non-memory-resource'
428
+ | 'unsupported-texture-dimension'
429
+ | 'unsupported-texture-format'
430
+ | 'invalid-texture-size';
431
+ };
432
+
433
+ export type ResourceKind =
434
+ | 'buffer'
435
+ | 'texture'
436
+ | 'texture-view'
437
+ | 'sampler'
438
+ | 'bind-group-layout'
439
+ | 'bind-group'
440
+ | 'pipeline-layout'
441
+ | 'render-pipeline'
442
+ | 'compute-pipeline'
443
+ | 'shader-module';
444
+
445
+ export type ResourceOrigin = 'engine' | 'swapchain';
446
+
447
+ export interface ResourceLifecycleEntry {
448
+ readonly handleId: HandleId;
449
+ readonly kind: ResourceKind;
450
+ readonly origin: ResourceOrigin;
451
+ readonly state: 'live' | 'destroyed';
452
+ readonly createdEventIndex: number;
453
+ readonly destroyedEventIndex?: number;
454
+ readonly byteEstimate: ResourceByteEstimate;
455
+ }
456
+
457
+ export interface ResourceLifecycleSummary {
458
+ /** This is the resource closure represented by the tape, not the whole device heap. */
459
+ readonly scope: 'captured-tape-resource-closure';
460
+ readonly counts: {
461
+ readonly created: number;
462
+ readonly destroyed: number;
463
+ readonly live: number;
464
+ readonly destroyEvents: number;
465
+ readonly unknownDestroyEvents: number;
466
+ };
467
+ readonly bytes: {
468
+ readonly knownCreated: number;
469
+ readonly knownDestroyed: number;
470
+ readonly knownLive: number;
471
+ readonly unavailableCreated: number;
472
+ readonly unavailableDestroyed: number;
473
+ readonly unavailableLive: number;
474
+ };
475
+ readonly originBreakdown: Readonly<
476
+ Record<
477
+ ResourceOrigin,
478
+ {
479
+ readonly created: number;
480
+ readonly destroyed: number;
481
+ readonly live: number;
482
+ readonly knownCreated: number;
483
+ readonly knownDestroyed: number;
484
+ readonly knownLive: number;
485
+ readonly unavailableCreated: number;
486
+ readonly unavailableDestroyed: number;
487
+ readonly unavailableLive: number;
488
+ }
489
+ >
490
+ >;
491
+ readonly availability: {
492
+ readonly destroy: 'observed-buffer-texture';
493
+ readonly retire: 'unavailable';
494
+ readonly driverAllocation: 'unavailable';
495
+ };
496
+ readonly resources: readonly ResourceLifecycleEntry[];
497
+ }
498
+
499
+ function asPositiveInteger(value: unknown): number | undefined {
500
+ return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : undefined;
501
+ }
502
+
503
+ function normalizeTextureSize(
504
+ size: unknown,
505
+ ):
506
+ | { readonly width: number; readonly height: number; readonly depthOrArrayLayers: number }
507
+ | undefined {
508
+ if (typeof size === 'number') {
509
+ const width = asPositiveInteger(size);
510
+ return width === undefined ? undefined : { width, height: 1, depthOrArrayLayers: 1 };
511
+ }
512
+ if (Array.isArray(size)) {
513
+ const width = asPositiveInteger(size[0]);
514
+ const height = asPositiveInteger(size[1] ?? 1);
515
+ const depthOrArrayLayers = asPositiveInteger(size[2] ?? 1);
516
+ return width === undefined || height === undefined || depthOrArrayLayers === undefined
517
+ ? undefined
518
+ : { width, height, depthOrArrayLayers };
519
+ }
520
+ if (size !== null && typeof size === 'object') {
521
+ const raw = size as {
522
+ readonly width?: unknown;
523
+ readonly height?: unknown;
524
+ readonly depthOrArrayLayers?: unknown;
525
+ };
526
+ const width = asPositiveInteger(raw.width);
527
+ const height = asPositiveInteger(raw.height ?? 1);
528
+ const depthOrArrayLayers = asPositiveInteger(raw.depthOrArrayLayers ?? 1);
529
+ return width === undefined || height === undefined || depthOrArrayLayers === undefined
530
+ ? undefined
531
+ : { width, height, depthOrArrayLayers };
532
+ }
533
+ return undefined;
534
+ }
535
+
536
+ function estimateBytes(event: RhiCallEvent): ResourceByteEstimate {
537
+ if (event.kind === 'createBuffer') {
538
+ return { status: 'known', bytes: event.desc.size, basis: 'buffer-descriptor' };
539
+ }
540
+ if (event.kind !== 'createTexture') {
541
+ return { status: 'unavailable', reason: 'non-memory-resource' };
542
+ }
543
+
544
+ const dimension = event.desc.dimension ?? '2d';
545
+ if (dimension === '3d') {
546
+ return { status: 'unavailable', reason: 'unsupported-texture-dimension' };
547
+ }
548
+ const size = normalizeTextureSize(event.desc.size);
549
+ if (size === undefined) return { status: 'unavailable', reason: 'invalid-texture-size' };
550
+ const layers = dimension === '1d' ? 1 : size.depthOrArrayLayers;
551
+ const layout = computeTextureLayout(
552
+ event.desc.format,
553
+ size.width,
554
+ size.height,
555
+ layers,
556
+ event.desc.mipLevelCount ?? 1,
557
+ );
558
+ if (layout === undefined) return { status: 'unavailable', reason: 'unsupported-texture-format' };
559
+ return {
560
+ status: 'known',
561
+ bytes: layout.totalBytes * (event.desc.sampleCount ?? 1),
562
+ basis: 'texture-tight-layout',
563
+ };
564
+ }
565
+
566
+ function resourceIdentity(
567
+ event: RhiCallEvent,
568
+ ): { readonly kind: ResourceKind; readonly handleId: HandleId } | undefined {
569
+ switch (event.kind) {
570
+ case 'createBuffer':
571
+ return { kind: 'buffer', handleId: event.handleId };
572
+ case 'createTexture':
573
+ return { kind: 'texture', handleId: event.handleId };
574
+ case 'createTextureView':
575
+ return { kind: 'texture-view', handleId: event.resultHandleId };
576
+ case 'createSampler':
577
+ return { kind: 'sampler', handleId: event.handleId };
578
+ case 'createBindGroupLayout':
579
+ return { kind: 'bind-group-layout', handleId: event.handleId };
580
+ case 'createBindGroup':
581
+ return { kind: 'bind-group', handleId: event.handleId };
582
+ case 'createPipelineLayout':
583
+ return { kind: 'pipeline-layout', handleId: event.handleId };
584
+ case 'createRenderPipeline':
585
+ return { kind: 'render-pipeline', handleId: event.handleId };
586
+ case 'createComputePipeline':
587
+ return { kind: 'compute-pipeline', handleId: event.handleId };
588
+ case 'createShaderModule':
589
+ return { kind: 'shader-module', handleId: event.handleId };
590
+ default:
591
+ return undefined;
592
+ }
593
+ }
594
+
595
+ function addBytes(
596
+ bytes: { known: number; unavailable: number },
597
+ estimate: ResourceByteEstimate,
598
+ ): void {
599
+ if (estimate.status === 'known') bytes.known += estimate.bytes;
600
+ else bytes.unavailable++;
601
+ }
602
+
603
+ /** Build a pure lifecycle ledger from the ordered tape events. */
604
+ export function buildResourceLifecycle(events: readonly RhiCallEvent[]): ResourceLifecycleSummary {
605
+ const records = new Map<
606
+ HandleId,
607
+ {
608
+ readonly kind: ResourceKind;
609
+ readonly origin: ResourceOrigin;
610
+ readonly createdEventIndex: number;
611
+ readonly byteEstimate: ResourceByteEstimate;
612
+ destroyedEventIndex?: number;
613
+ }
614
+ >();
615
+ const origins = new Map<HandleId, ResourceOrigin>();
616
+ let destroyEvents = 0;
617
+ let unknownDestroyEvents = 0;
618
+
619
+ for (let eventIndex = 0; eventIndex < events.length; eventIndex++) {
620
+ const event = events[eventIndex];
621
+ if (event === undefined) continue;
622
+ const identity = resourceIdentity(event);
623
+ if (identity !== undefined) {
624
+ const origin: ResourceOrigin =
625
+ event.kind === 'createTexture' && event.origin === 'swapchain'
626
+ ? 'swapchain'
627
+ : event.kind === 'createTextureView'
628
+ ? (origins.get(event.sourceHandleId) ?? 'engine')
629
+ : 'engine';
630
+ origins.set(identity.handleId, origin);
631
+ records.set(identity.handleId, {
632
+ kind: identity.kind,
633
+ origin,
634
+ createdEventIndex: eventIndex,
635
+ byteEstimate: estimateBytes(event),
636
+ });
637
+ continue;
638
+ }
639
+ if (event.kind !== 'destroyBuffer' && event.kind !== 'destroyTexture') continue;
640
+ destroyEvents++;
641
+ const record = records.get(event.handleId);
642
+ const expectedKind = event.kind === 'destroyBuffer' ? 'buffer' : 'texture';
643
+ if (
644
+ record === undefined ||
645
+ record.kind !== expectedKind ||
646
+ record.destroyedEventIndex !== undefined
647
+ ) {
648
+ unknownDestroyEvents++;
649
+ continue;
650
+ }
651
+ record.destroyedEventIndex = eventIndex;
652
+ }
653
+
654
+ const resources: ResourceLifecycleEntry[] = [];
655
+ const createdBytes = { known: 0, unavailable: 0 };
656
+ const destroyedBytes = { known: 0, unavailable: 0 };
657
+ const liveBytes = { known: 0, unavailable: 0 };
658
+ const originBreakdown: Record<
659
+ ResourceOrigin,
660
+ {
661
+ created: number;
662
+ destroyed: number;
663
+ live: number;
664
+ knownCreated: number;
665
+ knownDestroyed: number;
666
+ knownLive: number;
667
+ unavailableCreated: number;
668
+ unavailableDestroyed: number;
669
+ unavailableLive: number;
670
+ }
671
+ > = {
672
+ engine: {
673
+ created: 0,
674
+ destroyed: 0,
675
+ live: 0,
676
+ knownCreated: 0,
677
+ knownDestroyed: 0,
678
+ knownLive: 0,
679
+ unavailableCreated: 0,
680
+ unavailableDestroyed: 0,
681
+ unavailableLive: 0,
682
+ },
683
+ swapchain: {
684
+ created: 0,
685
+ destroyed: 0,
686
+ live: 0,
687
+ knownCreated: 0,
688
+ knownDestroyed: 0,
689
+ knownLive: 0,
690
+ unavailableCreated: 0,
691
+ unavailableDestroyed: 0,
692
+ unavailableLive: 0,
693
+ },
694
+ };
695
+ let destroyed = 0;
696
+
697
+ for (const [handleId, record] of records) {
698
+ const state = record.destroyedEventIndex === undefined ? 'live' : 'destroyed';
699
+ if (state === 'destroyed') destroyed++;
700
+ addBytes(createdBytes, record.byteEstimate);
701
+ addBytes(state === 'live' ? liveBytes : destroyedBytes, record.byteEstimate);
702
+ const byOrigin = originBreakdown[record.origin];
703
+ byOrigin.created++;
704
+ if (state === 'destroyed') byOrigin.destroyed++;
705
+ else byOrigin.live++;
706
+ if (record.byteEstimate.status === 'known') {
707
+ byOrigin.knownCreated += record.byteEstimate.bytes;
708
+ if (state === 'destroyed') byOrigin.knownDestroyed += record.byteEstimate.bytes;
709
+ else byOrigin.knownLive += record.byteEstimate.bytes;
710
+ } else {
711
+ byOrigin.unavailableCreated++;
712
+ if (state === 'destroyed') byOrigin.unavailableDestroyed++;
713
+ else byOrigin.unavailableLive++;
714
+ }
715
+ resources.push({
716
+ handleId,
717
+ kind: record.kind,
718
+ origin: record.origin,
719
+ state,
720
+ createdEventIndex: record.createdEventIndex,
721
+ ...(record.destroyedEventIndex === undefined
722
+ ? {}
723
+ : { destroyedEventIndex: record.destroyedEventIndex }),
724
+ byteEstimate: record.byteEstimate,
725
+ });
726
+ }
727
+
728
+ return {
729
+ scope: 'captured-tape-resource-closure',
730
+ counts: {
731
+ created: resources.length,
732
+ destroyed,
733
+ live: resources.length - destroyed,
734
+ destroyEvents,
735
+ unknownDestroyEvents,
736
+ },
737
+ bytes: {
738
+ knownCreated: createdBytes.known,
739
+ knownDestroyed: destroyedBytes.known,
740
+ knownLive: liveBytes.known,
741
+ unavailableCreated: createdBytes.unavailable,
742
+ unavailableDestroyed: destroyedBytes.unavailable,
743
+ unavailableLive: liveBytes.unavailable,
744
+ },
745
+ originBreakdown,
746
+ availability: {
747
+ destroy: 'observed-buffer-texture',
748
+ retire: 'unavailable',
749
+ driverAllocation: 'unavailable',
750
+ },
751
+ resources,
752
+ };
753
+ }