@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,870 @@
1
+ import type {
2
+ BindGroupDescriptor,
3
+ Buffer,
4
+ CommandBuffer,
5
+ PipelineLayout,
6
+ RenderPassColorAttachment,
7
+ RenderPassDepthStencilAttachment,
8
+ RhiBindingResource,
9
+ RhiCommandEncoder,
10
+ RhiDevice,
11
+ RhiError,
12
+ RhiQueue,
13
+ Texture,
14
+ } from '@forgeax/engine-rhi';
15
+ import { ok, type Result } from '@forgeax/engine-types';
16
+ import type { RhiDebugError } from '../errors';
17
+ import type { RhiCallEvent, Tape } from '../protocol/types';
18
+ import {
19
+ blob,
20
+ clearBuffer,
21
+ commandEncoderCall,
22
+ endPass,
23
+ eventFailure,
24
+ eventRecord,
25
+ finish,
26
+ missingResource,
27
+ passCall,
28
+ pipelineError,
29
+ requireReplayResource,
30
+ requireResource,
31
+ seedInitialData,
32
+ unsupportedEvent,
33
+ } from './execute-support';
34
+ import type { ReplayResource, ResourceTable } from './resources';
35
+ import { isDepthTextureFormat } from './texture-format';
36
+
37
+ export { eventFailure } from './execute-support';
38
+
39
+ export interface ReplayExecutionContext {
40
+ readonly device: RhiDevice;
41
+ readonly queue: RhiQueue;
42
+ readonly tape: Tape;
43
+ readonly table: ResourceTable;
44
+ readonly createShaderModule: (
45
+ device: RhiDevice,
46
+ desc: { readonly code: string; readonly label?: string | undefined },
47
+ ) => Promise<Result<import('@forgeax/engine-rhi').ShaderModule, RhiError>>;
48
+ }
49
+
50
+ export async function executeEvent(
51
+ context: ReplayExecutionContext,
52
+ event: RhiCallEvent,
53
+ eventIndex: number,
54
+ ): Promise<Result<void, RhiDebugError>> {
55
+ try {
56
+ switch (event.kind) {
57
+ case 'frameMark':
58
+ return ok(undefined);
59
+ case 'createBuffer':
60
+ return createResource(context, eventIndex, event, context.device.createBuffer(event.desc), {
61
+ kind: 'buffer',
62
+ });
63
+ case 'createTexture':
64
+ return createResource(
65
+ context,
66
+ eventIndex,
67
+ event,
68
+ context.device.createTexture(replayTextureDescriptor(event.desc) as never),
69
+ {
70
+ kind: 'texture',
71
+ },
72
+ );
73
+ case 'destroyBuffer':
74
+ return destroyResource(context, eventIndex, event, 'buffer');
75
+ case 'destroyTexture':
76
+ return destroyResource(context, eventIndex, event, 'texture');
77
+ case 'createTextureView': {
78
+ const texture = requireResource<Texture>(
79
+ context,
80
+ event.sourceHandleId,
81
+ 'texture',
82
+ eventIndex,
83
+ event.kind,
84
+ );
85
+ if (!texture.ok) return texture;
86
+ return createResource(
87
+ context,
88
+ eventIndex,
89
+ event,
90
+ context.device.createTextureView(texture.value, event.desc),
91
+ { kind: 'texture-view' },
92
+ event.resultHandleId,
93
+ );
94
+ }
95
+ case 'createSampler':
96
+ return createResource(
97
+ context,
98
+ eventIndex,
99
+ event,
100
+ context.device.createSampler(event.desc),
101
+ {
102
+ kind: 'sampler',
103
+ },
104
+ );
105
+ case 'createBindGroupLayout':
106
+ return createResource(
107
+ context,
108
+ eventIndex,
109
+ event,
110
+ context.device.createBindGroupLayout(event.desc as never),
111
+ { kind: 'binding', role: 'bind-group-layout' },
112
+ );
113
+ case 'createBindGroup':
114
+ return createBindGroup(context, event, eventIndex);
115
+ case 'createPipelineLayout':
116
+ return createPipelineLayout(context, event, eventIndex);
117
+ case 'createRenderPipeline':
118
+ return createRenderPipeline(context, event, eventIndex);
119
+ case 'createComputePipeline':
120
+ return createComputePipeline(context, event, eventIndex);
121
+ case 'createShaderModule':
122
+ return createShaderModule(context, event, eventIndex);
123
+ case 'createCommandEncoder':
124
+ return createResource(
125
+ context,
126
+ eventIndex,
127
+ event,
128
+ context.device.createCommandEncoder(event.desc),
129
+ { kind: 'encoder', role: 'command' },
130
+ event.cmdHandleId,
131
+ );
132
+ case 'writeBuffer':
133
+ return writeBuffer(context, event, eventIndex);
134
+ case 'writeTexture':
135
+ return writeTexture(context, event, eventIndex);
136
+ case 'copyExternalImageToTexture':
137
+ return unsupportedEvent(eventIndex, event, 'external image sources are not self-contained');
138
+ case 'submit':
139
+ return submit(context, event, eventIndex);
140
+ case 'beginRenderPass':
141
+ return beginRenderPass(context, event, eventIndex);
142
+ case 'beginComputePass':
143
+ return beginComputePass(context, event, eventIndex);
144
+ case 'copyBufferToBuffer':
145
+ return copyBufferToBuffer(context, event, eventIndex);
146
+ case 'copyBufferToTexture':
147
+ return copyBufferToTexture(context, event, eventIndex);
148
+ case 'copyTextureToBuffer':
149
+ return copyTextureToBuffer(context, event, eventIndex);
150
+ case 'copyTextureToTexture':
151
+ return copyTextureToTexture(context, event, eventIndex);
152
+ case 'clearBuffer':
153
+ return clearBuffer(context, event, eventIndex);
154
+ case 'pushDebugGroup':
155
+ return commandEncoderCall(context, event, eventIndex, (encoder) =>
156
+ encoder.pushDebugGroup(event.groupLabel),
157
+ );
158
+ case 'popDebugGroup':
159
+ return commandEncoderCall(context, event, eventIndex, (encoder) => encoder.popDebugGroup());
160
+ case 'insertDebugMarker':
161
+ return commandEncoderCall(context, event, eventIndex, (encoder) =>
162
+ encoder.insertDebugMarker(event.markerLabel),
163
+ );
164
+ case 'finish':
165
+ return finish(context, event, eventIndex);
166
+ case 'setPipeline':
167
+ return passCall(context, event, eventIndex, 'render-pass', (pass) => {
168
+ const pipeline = requireReplayResource(
169
+ context,
170
+ event.pipelineHandleId,
171
+ 'pipeline',
172
+ eventIndex,
173
+ event.kind,
174
+ );
175
+ if (!pipeline.ok || pipeline.value.role !== 'render')
176
+ return pipelineError(pipeline, event, eventIndex);
177
+ pass.setPipeline(pipeline.value.value as never);
178
+ return ok(undefined);
179
+ });
180
+ case 'setVertexBuffer':
181
+ return passCall(context, event, eventIndex, 'render-pass', (pass) => {
182
+ const buffer = requireResource<Buffer>(
183
+ context,
184
+ event.bufferHandleId,
185
+ 'buffer',
186
+ eventIndex,
187
+ event.kind,
188
+ );
189
+ if (!buffer.ok) return buffer;
190
+ pass.setVertexBuffer(event.slot, buffer.value, event.offset, event.size);
191
+ return ok(undefined);
192
+ });
193
+ case 'setIndexBuffer':
194
+ return passCall(context, event, eventIndex, 'render-pass', (pass) => {
195
+ const buffer = requireResource<Buffer>(
196
+ context,
197
+ event.bufferHandleId,
198
+ 'buffer',
199
+ eventIndex,
200
+ event.kind,
201
+ );
202
+ if (!buffer.ok) return buffer;
203
+ pass.setIndexBuffer(buffer.value, event.format, event.offset, event.size);
204
+ return ok(undefined);
205
+ });
206
+ case 'setBindGroup':
207
+ return passCall(context, event, eventIndex, 'pass', (pass) => {
208
+ const bindGroup = requireReplayResource(
209
+ context,
210
+ event.bindGroupHandleId,
211
+ 'binding',
212
+ eventIndex,
213
+ event.kind,
214
+ );
215
+ if (!bindGroup.ok || bindGroup.value.role !== 'bind-group')
216
+ return pipelineError(bindGroup, event, eventIndex);
217
+ pass.setBindGroup(event.index, bindGroup.value.value as never, event.dynamicOffsets);
218
+ return ok(undefined);
219
+ });
220
+ case 'draw':
221
+ return passCall(context, event, eventIndex, 'render-pass', (pass) => {
222
+ pass.draw(event.vertexCount, event.instanceCount, event.firstVertex, event.firstInstance);
223
+ return ok(undefined);
224
+ });
225
+ case 'drawIndexed':
226
+ return passCall(context, event, eventIndex, 'render-pass', (pass) => {
227
+ pass.drawIndexed(
228
+ event.indexCount,
229
+ event.instanceCount,
230
+ event.firstIndex,
231
+ event.baseVertex,
232
+ event.firstInstance,
233
+ );
234
+ return ok(undefined);
235
+ });
236
+ case 'setViewport':
237
+ return passCall(context, event, eventIndex, 'render-pass', (pass) => {
238
+ pass.setViewport(event.x, event.y, event.w, event.h, event.minDepth, event.maxDepth);
239
+ return ok(undefined);
240
+ });
241
+ case 'setScissorRect':
242
+ return passCall(context, event, eventIndex, 'render-pass', (pass) => {
243
+ pass.setScissorRect(event.x, event.y, event.w, event.h);
244
+ return ok(undefined);
245
+ });
246
+ case 'setStencilReference':
247
+ return passCall(context, event, eventIndex, 'render-pass', (pass) => {
248
+ pass.setStencilReference(event.reference);
249
+ return ok(undefined);
250
+ });
251
+ case 'endRenderPass':
252
+ return endPass(context, event, eventIndex, 'render-pass');
253
+ case 'setBlendConstant':
254
+ return passCall(context, event, eventIndex, 'render-pass', (pass) => {
255
+ pass.setBlendConstant(event.color);
256
+ return ok(undefined);
257
+ });
258
+ case 'drawIndirect':
259
+ return passCall(context, event, eventIndex, 'render-pass', (pass) => {
260
+ const buffer = requireResource<Buffer>(
261
+ context,
262
+ event.indirectBufferHandleId,
263
+ 'buffer',
264
+ eventIndex,
265
+ event.kind,
266
+ );
267
+ if (!buffer.ok) return buffer;
268
+ pass.drawIndirect(buffer.value, event.indirectOffset);
269
+ return ok(undefined);
270
+ });
271
+ case 'drawIndexedIndirect':
272
+ return passCall(context, event, eventIndex, 'render-pass', (pass) => {
273
+ const buffer = requireResource<Buffer>(
274
+ context,
275
+ event.indirectBufferHandleId,
276
+ 'buffer',
277
+ eventIndex,
278
+ event.kind,
279
+ );
280
+ if (!buffer.ok) return buffer;
281
+ pass.drawIndexedIndirect(buffer.value, event.indirectOffset);
282
+ return ok(undefined);
283
+ });
284
+ case 'passPushDebugGroup':
285
+ return passCall(context, event, eventIndex, 'pass', (pass) => {
286
+ pass.pushDebugGroup(event.groupLabel);
287
+ return ok(undefined);
288
+ });
289
+ case 'passPopDebugGroup':
290
+ return passCall(context, event, eventIndex, 'pass', (pass) => {
291
+ pass.popDebugGroup();
292
+ return ok(undefined);
293
+ });
294
+ case 'passInsertDebugMarker':
295
+ return passCall(context, event, eventIndex, 'pass', (pass) => {
296
+ pass.insertDebugMarker(event.markerLabel);
297
+ return ok(undefined);
298
+ });
299
+ case 'setComputePipeline':
300
+ return passCall(context, event, eventIndex, 'compute-pass', (pass) => {
301
+ const pipeline = requireReplayResource(
302
+ context,
303
+ event.pipelineHandleId,
304
+ 'pipeline',
305
+ eventIndex,
306
+ event.kind,
307
+ );
308
+ if (!pipeline.ok || pipeline.value.role !== 'compute')
309
+ return pipelineError(pipeline, event, eventIndex);
310
+ pass.setPipeline(pipeline.value.value as never);
311
+ return ok(undefined);
312
+ });
313
+ case 'dispatchWorkgroups':
314
+ return passCall(context, event, eventIndex, 'compute-pass', (pass) => {
315
+ pass.dispatchWorkgroups(event.x, event.y, event.z);
316
+ return ok(undefined);
317
+ });
318
+ case 'dispatchWorkgroupsIndirect':
319
+ return passCall(context, event, eventIndex, 'compute-pass', (pass) => {
320
+ const buffer = requireResource<Buffer>(
321
+ context,
322
+ event.indirectBufferHandleId,
323
+ 'buffer',
324
+ eventIndex,
325
+ event.kind,
326
+ );
327
+ if (!buffer.ok) return buffer;
328
+ pass.dispatchWorkgroupsIndirect(buffer.value, event.indirectOffset);
329
+ return ok(undefined);
330
+ });
331
+ case 'endComputePass':
332
+ return endPass(context, event, eventIndex, 'compute-pass');
333
+ case 'initialData':
334
+ return seedInitialData(context, event, eventIndex);
335
+ default:
336
+ return unsupportedEvent(eventIndex, event, 'event kind has no replay executor');
337
+ }
338
+ } catch (cause) {
339
+ return eventFailure(eventIndex, event, 'encode', cause);
340
+ }
341
+ }
342
+
343
+ function replayTextureDescriptor(
344
+ descriptor: Extract<RhiCallEvent, { kind: 'createTexture' }>['desc'],
345
+ ): Extract<RhiCallEvent, { kind: 'createTexture' }>['desc'] {
346
+ if (!isDepthTextureFormat(descriptor.format)) return descriptor;
347
+ return {
348
+ ...descriptor,
349
+ // depth24plus* readback uses a package-owned depth-to-color blit and thus
350
+ // needs the source texture to be bindable on a fresh replay device.
351
+ usage: (descriptor.usage ?? 0) | 0x04,
352
+ };
353
+ }
354
+
355
+ function createResource<T extends ReplayResource>(
356
+ context: ReplayExecutionContext,
357
+ eventIndex: number,
358
+ event: RhiCallEvent,
359
+ result: Result<T['value'], RhiError>,
360
+ shape: Pick<ReplayResource, 'kind'> & { readonly role?: string },
361
+ resourceId?: string,
362
+ ): Result<void, RhiDebugError> {
363
+ if (!result.ok) return eventFailure(eventIndex, event, 'create', result.error);
364
+ const id = resourceId ?? ('handleId' in event ? event.handleId : undefined);
365
+ if (id === undefined)
366
+ return eventFailure(eventIndex, event, 'lookup', 'created resource has no handle id');
367
+ const stored = context.table.set(id, { ...shape, value: result.value } as T, eventRecord(event));
368
+ return stored.ok ? ok(undefined) : stored;
369
+ }
370
+
371
+ function destroyResource(
372
+ context: ReplayExecutionContext,
373
+ eventIndex: number,
374
+ event: Extract<RhiCallEvent, { kind: 'destroyBuffer' | 'destroyTexture' }>,
375
+ kind: 'buffer' | 'texture',
376
+ ): Result<void, RhiDebugError> {
377
+ const entry = context.table.get(event.handleId);
378
+ if (entry === undefined || entry.resource.kind !== kind)
379
+ return missingResource(eventIndex, event, event.handleId, kind);
380
+ const result =
381
+ kind === 'buffer'
382
+ ? context.device.destroyBuffer(entry.resource.value as Buffer)
383
+ : context.device.destroyTexture(entry.resource.value as Texture);
384
+ if (!result.ok) return eventFailure(eventIndex, event, 'create', result.error);
385
+ context.table.delete(event.handleId);
386
+ return ok(undefined);
387
+ }
388
+
389
+ async function createShaderModule(
390
+ context: ReplayExecutionContext,
391
+ event: Extract<RhiCallEvent, { kind: 'createShaderModule' }>,
392
+ eventIndex: number,
393
+ ): Promise<Result<void, RhiDebugError>> {
394
+ const result = await context.createShaderModule(context.device, { code: event.wgslCode });
395
+ return createResource(context, eventIndex, event, result, { kind: 'shader-module' });
396
+ }
397
+
398
+ function createBindGroup(
399
+ context: ReplayExecutionContext,
400
+ event: Extract<RhiCallEvent, { kind: 'createBindGroup' }>,
401
+ eventIndex: number,
402
+ ): Result<void, RhiDebugError> {
403
+ const layout = requireReplayResource(
404
+ context,
405
+ event.layoutHandleId,
406
+ 'binding',
407
+ eventIndex,
408
+ event.kind,
409
+ );
410
+ if (!layout.ok || layout.value.role !== 'bind-group-layout')
411
+ return pipelineError(layout, event, eventIndex);
412
+ const resources: RhiBindingResource[] = [];
413
+ for (const [index, entry] of event.entries.entries()) {
414
+ const resourceId = event.resourceHandleIds[index];
415
+ if (resourceId === undefined)
416
+ return eventFailure(eventIndex, event, 'lookup', 'bind group resource handle is missing');
417
+ const resolved = context.table.get(resourceId);
418
+ if (resolved === undefined)
419
+ return missingResource(eventIndex, event, resourceId, entry.resourceKind);
420
+ if (entry.resourceKind === 'buffer' && resolved.resource.kind === 'buffer') {
421
+ resources.push({
422
+ kind: 'buffer',
423
+ value: {
424
+ buffer: resolved.resource.value,
425
+ ...(entry.bufferOffset === undefined ? {} : { offset: entry.bufferOffset }),
426
+ ...(entry.bufferSize === undefined ? {} : { size: entry.bufferSize }),
427
+ },
428
+ });
429
+ } else if (entry.resourceKind === 'sampler' && resolved.resource.kind === 'sampler') {
430
+ resources.push({ kind: 'sampler', value: resolved.resource.value });
431
+ } else if (entry.resourceKind === 'textureView' && resolved.resource.kind === 'texture-view') {
432
+ resources.push({ kind: 'textureView', value: resolved.resource.value });
433
+ } else {
434
+ return missingResource(eventIndex, event, resourceId, entry.resourceKind);
435
+ }
436
+ }
437
+ const descriptor: BindGroupDescriptor = {
438
+ layout: layout.value.value as never,
439
+ entries: event.entries.map((entry, index) => ({
440
+ binding: entry.binding,
441
+ resource: resources[index] as RhiBindingResource,
442
+ })),
443
+ };
444
+ return createResource(context, eventIndex, event, context.device.createBindGroup(descriptor), {
445
+ kind: 'binding',
446
+ role: 'bind-group',
447
+ });
448
+ }
449
+
450
+ function createPipelineLayout(
451
+ context: ReplayExecutionContext,
452
+ event: Extract<RhiCallEvent, { kind: 'createPipelineLayout' }>,
453
+ eventIndex: number,
454
+ ): Result<void, RhiDebugError> {
455
+ const layouts: import('@forgeax/engine-rhi').BindGroupLayout[] = [];
456
+ for (const id of event.bglHandleIds) {
457
+ const layout = requireReplayResource(context, id, 'binding', eventIndex, event.kind);
458
+ if (!layout.ok || layout.value.role !== 'bind-group-layout')
459
+ return pipelineError(layout, event, eventIndex);
460
+ layouts.push(layout.value.value as import('@forgeax/engine-rhi').BindGroupLayout);
461
+ }
462
+ return createResource(
463
+ context,
464
+ eventIndex,
465
+ event,
466
+ context.device.createPipelineLayout({ bindGroupLayouts: layouts }),
467
+ { kind: 'binding', role: 'pipeline-layout' },
468
+ );
469
+ }
470
+
471
+ function createRenderPipeline(
472
+ context: ReplayExecutionContext,
473
+ event: Extract<RhiCallEvent, { kind: 'createRenderPipeline' }>,
474
+ eventIndex: number,
475
+ ): Result<void, RhiDebugError> {
476
+ const layout = requireReplayResource(
477
+ context,
478
+ event.layoutHandleId,
479
+ 'binding',
480
+ eventIndex,
481
+ event.kind,
482
+ );
483
+ if (!layout.ok || layout.value.role !== 'pipeline-layout')
484
+ return pipelineError(layout, event, eventIndex);
485
+ const vertex = event.desc.vertex;
486
+ const fragment = event.desc.fragment;
487
+ const vertexShader =
488
+ vertex === undefined ? undefined : shaderValue(context, event.vertexShaderModuleHandleId);
489
+ const fragmentShader =
490
+ fragment === undefined ? undefined : shaderValue(context, event.fragmentShaderModuleHandleId);
491
+ const descriptor = {
492
+ ...event.desc,
493
+ layout: layout.value.value as PipelineLayout,
494
+ ...(vertex === undefined ? {} : { vertex: { ...vertex, module: vertexShader } }),
495
+ ...(fragment === undefined
496
+ ? {}
497
+ : {
498
+ fragment: {
499
+ ...fragment,
500
+ module: fragmentShader,
501
+ },
502
+ }),
503
+ };
504
+ if (
505
+ (vertex !== undefined && vertexShader === undefined) ||
506
+ (fragment !== undefined && fragmentShader === undefined)
507
+ ) {
508
+ return eventFailure(
509
+ eventIndex,
510
+ event,
511
+ 'lookup',
512
+ 'render pipeline shader module handle is missing',
513
+ );
514
+ }
515
+ return createResource(
516
+ context,
517
+ eventIndex,
518
+ event,
519
+ context.device.createRenderPipeline(descriptor as never),
520
+ {
521
+ kind: 'pipeline',
522
+ role: 'render',
523
+ },
524
+ );
525
+ }
526
+
527
+ function createComputePipeline(
528
+ context: ReplayExecutionContext,
529
+ event: Extract<RhiCallEvent, { kind: 'createComputePipeline' }>,
530
+ eventIndex: number,
531
+ ): Result<void, RhiDebugError> {
532
+ const layout = requireReplayResource(
533
+ context,
534
+ event.layoutHandleId,
535
+ 'binding',
536
+ eventIndex,
537
+ event.kind,
538
+ );
539
+ if (!layout.ok || layout.value.role !== 'pipeline-layout')
540
+ return pipelineError(layout, event, eventIndex);
541
+ const shader = shaderValue(context, event.computeShaderModuleHandleId);
542
+ if (shader === undefined)
543
+ return eventFailure(
544
+ eventIndex,
545
+ event,
546
+ 'lookup',
547
+ 'compute pipeline shader module handle is missing',
548
+ );
549
+ return createResource(
550
+ context,
551
+ eventIndex,
552
+ event,
553
+ context.device.createComputePipeline({
554
+ ...event.desc,
555
+ layout: layout.value.value as PipelineLayout,
556
+ compute: { ...event.desc.compute, module: shader },
557
+ } as never),
558
+ { kind: 'pipeline', role: 'compute' },
559
+ );
560
+ }
561
+
562
+ function shaderValue(
563
+ context: ReplayExecutionContext,
564
+ id: string | undefined,
565
+ ): import('@forgeax/engine-rhi').ShaderModule | undefined {
566
+ if (id === undefined) return undefined;
567
+ const entry = context.table.get(id);
568
+ if (entry?.resource.kind !== 'shader-module') return undefined;
569
+ return entry.resource.value;
570
+ }
571
+
572
+ function writeBuffer(
573
+ context: ReplayExecutionContext,
574
+ event: Extract<RhiCallEvent, { kind: 'writeBuffer' }>,
575
+ eventIndex: number,
576
+ ): Result<void, RhiDebugError> {
577
+ const buffer = requireResource<Buffer>(context, event.handleId, 'buffer', eventIndex, event.kind);
578
+ if (!buffer.ok) return buffer;
579
+ const bytes = blob(context.tape, event.dataHash, event, eventIndex);
580
+ if (!bytes.ok) return bytes;
581
+ const result = context.queue.writeBuffer(
582
+ buffer.value,
583
+ event.bufferOffset,
584
+ bytes.value,
585
+ 0,
586
+ event.size,
587
+ );
588
+ return result.ok ? ok(undefined) : eventFailure(eventIndex, event, 'write', result.error);
589
+ }
590
+
591
+ function writeTexture(
592
+ context: ReplayExecutionContext,
593
+ event: Extract<RhiCallEvent, { kind: 'writeTexture' }>,
594
+ eventIndex: number,
595
+ ): Result<void, RhiDebugError> {
596
+ const texture = requireResource<Texture>(
597
+ context,
598
+ event.destination.textureHandleId,
599
+ 'texture',
600
+ eventIndex,
601
+ event.kind,
602
+ );
603
+ if (!texture.ok) return texture;
604
+ const bytes = blob(context.tape, event.dataHash, event, eventIndex);
605
+ if (!bytes.ok) return bytes;
606
+ const result = context.queue.writeTexture(
607
+ {
608
+ texture: texture.value,
609
+ mipLevel: event.destination.mipLevel ?? 0,
610
+ origin: event.destination.origin,
611
+ aspect: event.destination.aspect,
612
+ } as never,
613
+ bytes.value,
614
+ {
615
+ offset: event.dataLayout.offset ?? 0,
616
+ ...(event.dataLayout.bytesPerRow === undefined
617
+ ? {}
618
+ : { bytesPerRow: event.dataLayout.bytesPerRow }),
619
+ ...(event.dataLayout.rowsPerImage === undefined
620
+ ? {}
621
+ : { rowsPerImage: event.dataLayout.rowsPerImage }),
622
+ },
623
+ event.size,
624
+ );
625
+ return result.ok ? ok(undefined) : eventFailure(eventIndex, event, 'write', result.error);
626
+ }
627
+
628
+ function submit(
629
+ context: ReplayExecutionContext,
630
+ event: Extract<RhiCallEvent, { kind: 'submit' }>,
631
+ eventIndex: number,
632
+ ): Result<void, RhiDebugError> {
633
+ const commandBuffers: CommandBuffer[] = [];
634
+ for (const id of event.cmdHandleIds) {
635
+ const entry = context.table.get(id);
636
+ if (entry?.resource.kind !== 'encoder' || entry.resource.role !== 'command-buffer') {
637
+ return missingResource(eventIndex, event, id, 'encoder');
638
+ }
639
+ commandBuffers.push(entry.resource.value as CommandBuffer);
640
+ }
641
+ const result = context.queue.submit(commandBuffers);
642
+ return result.ok ? ok(undefined) : eventFailure(eventIndex, event, 'submit', result.error);
643
+ }
644
+
645
+ function beginRenderPass(
646
+ context: ReplayExecutionContext,
647
+ event: Extract<RhiCallEvent, { kind: 'beginRenderPass' }>,
648
+ eventIndex: number,
649
+ ): Result<void, RhiDebugError> {
650
+ const encoder = requireResource<RhiCommandEncoder>(
651
+ context,
652
+ event.cmdHandleId,
653
+ 'encoder',
654
+ eventIndex,
655
+ event.kind,
656
+ 'command',
657
+ );
658
+ if (!encoder.ok) return encoder;
659
+ const colors = Array.from(event.desc.colorAttachments).map((attachment, index) => {
660
+ if (attachment === null || attachment === undefined) return attachment;
661
+ const viewId = event.colorAttachmentViewHandleIds[index];
662
+ const view = viewId === undefined ? undefined : context.table.get(viewId)?.resource;
663
+ if (view?.kind !== 'texture-view') throw new Error(`missing color attachment view ${viewId}`);
664
+ const resolveId = event.colorAttachmentResolveTargetHandleIds?.[index];
665
+ const resolve = resolveId === undefined ? undefined : context.table.get(resolveId)?.resource;
666
+ return {
667
+ ...attachment,
668
+ view: view.value,
669
+ ...(resolve?.kind === 'texture-view' ? { resolveTarget: resolve.value } : {}),
670
+ } as RenderPassColorAttachment;
671
+ });
672
+ const depth =
673
+ event.depthStencilViewHandleId === undefined
674
+ ? undefined
675
+ : context.table.get(event.depthStencilViewHandleId)?.resource;
676
+ const descriptor = {
677
+ ...event.desc,
678
+ colorAttachments: colors,
679
+ ...(depth?.kind === 'texture-view'
680
+ ? {
681
+ depthStencilAttachment: {
682
+ ...event.desc.depthStencilAttachment,
683
+ view: depth.value,
684
+ } as RenderPassDepthStencilAttachment,
685
+ }
686
+ : {}),
687
+ };
688
+ const pass = encoder.value.beginRenderPass(descriptor as never);
689
+ return context.table.set(
690
+ event.passHandleId,
691
+ { kind: 'encoder', role: 'render-pass', value: pass },
692
+ eventRecord(event),
693
+ );
694
+ }
695
+
696
+ function beginComputePass(
697
+ context: ReplayExecutionContext,
698
+ event: Extract<RhiCallEvent, { kind: 'beginComputePass' }>,
699
+ eventIndex: number,
700
+ ): Result<void, RhiDebugError> {
701
+ const encoder = requireResource<RhiCommandEncoder>(
702
+ context,
703
+ event.cmdHandleId,
704
+ 'encoder',
705
+ eventIndex,
706
+ event.kind,
707
+ 'command',
708
+ );
709
+ if (!encoder.ok) return encoder;
710
+ const pass = encoder.value.beginComputePass(JSON.parse(JSON.stringify(event.desc)));
711
+ return context.table.set(
712
+ event.passHandleId,
713
+ { kind: 'encoder', role: 'compute-pass', value: pass },
714
+ eventRecord(event),
715
+ );
716
+ }
717
+
718
+ function copyBufferToBuffer(
719
+ context: ReplayExecutionContext,
720
+ event: Extract<RhiCallEvent, { kind: 'copyBufferToBuffer' }>,
721
+ eventIndex: number,
722
+ ): Result<void, RhiDebugError> {
723
+ const encoder = requireResource<RhiCommandEncoder>(
724
+ context,
725
+ event.cmdHandleId,
726
+ 'encoder',
727
+ eventIndex,
728
+ event.kind,
729
+ 'command',
730
+ );
731
+ const source = requireResource<Buffer>(
732
+ context,
733
+ event.sourceHandleId,
734
+ 'buffer',
735
+ eventIndex,
736
+ event.kind,
737
+ );
738
+ const destination = requireResource<Buffer>(
739
+ context,
740
+ event.destinationHandleId,
741
+ 'buffer',
742
+ eventIndex,
743
+ event.kind,
744
+ );
745
+ if (!encoder.ok) return encoder;
746
+ if (!source.ok) return source;
747
+ if (!destination.ok) return destination;
748
+ encoder.value.copyBufferToBuffer(
749
+ source.value,
750
+ event.sourceOffset,
751
+ destination.value,
752
+ event.destinationOffset,
753
+ event.size,
754
+ );
755
+ return ok(undefined);
756
+ }
757
+
758
+ function copyBufferToTexture(
759
+ context: ReplayExecutionContext,
760
+ event: Extract<RhiCallEvent, { kind: 'copyBufferToTexture' }>,
761
+ eventIndex: number,
762
+ ): Result<void, RhiDebugError> {
763
+ const encoder = requireResource<RhiCommandEncoder>(
764
+ context,
765
+ event.cmdHandleId,
766
+ 'encoder',
767
+ eventIndex,
768
+ event.kind,
769
+ 'command',
770
+ );
771
+ const buffer = requireResource<Buffer>(
772
+ context,
773
+ event.source.bufferHandleId,
774
+ 'buffer',
775
+ eventIndex,
776
+ event.kind,
777
+ );
778
+ const texture = requireResource<Texture>(
779
+ context,
780
+ event.destination.textureHandleId,
781
+ 'texture',
782
+ eventIndex,
783
+ event.kind,
784
+ );
785
+ if (!encoder.ok) return encoder;
786
+ if (!buffer.ok) return buffer;
787
+ if (!texture.ok) return texture;
788
+ encoder.value.copyBufferToTexture(
789
+ { ...event.source, buffer: buffer.value } as never,
790
+ { ...event.destination, texture: texture.value } as never,
791
+ event.copySize,
792
+ );
793
+ return ok(undefined);
794
+ }
795
+
796
+ function copyTextureToBuffer(
797
+ context: ReplayExecutionContext,
798
+ event: Extract<RhiCallEvent, { kind: 'copyTextureToBuffer' }>,
799
+ eventIndex: number,
800
+ ): Result<void, RhiDebugError> {
801
+ const encoder = requireResource<RhiCommandEncoder>(
802
+ context,
803
+ event.cmdHandleId,
804
+ 'encoder',
805
+ eventIndex,
806
+ event.kind,
807
+ 'command',
808
+ );
809
+ const texture = requireResource<Texture>(
810
+ context,
811
+ event.source.textureHandleId,
812
+ 'texture',
813
+ eventIndex,
814
+ event.kind,
815
+ );
816
+ const buffer = requireResource<Buffer>(
817
+ context,
818
+ event.destination.bufferHandleId,
819
+ 'buffer',
820
+ eventIndex,
821
+ event.kind,
822
+ );
823
+ if (!encoder.ok) return encoder;
824
+ if (!texture.ok) return texture;
825
+ if (!buffer.ok) return buffer;
826
+ encoder.value.copyTextureToBuffer(
827
+ { ...event.source, texture: texture.value } as never,
828
+ { ...event.destination, buffer: buffer.value } as never,
829
+ event.copySize,
830
+ );
831
+ return ok(undefined);
832
+ }
833
+
834
+ function copyTextureToTexture(
835
+ context: ReplayExecutionContext,
836
+ event: Extract<RhiCallEvent, { kind: 'copyTextureToTexture' }>,
837
+ eventIndex: number,
838
+ ): Result<void, RhiDebugError> {
839
+ const encoder = requireResource<RhiCommandEncoder>(
840
+ context,
841
+ event.cmdHandleId,
842
+ 'encoder',
843
+ eventIndex,
844
+ event.kind,
845
+ 'command',
846
+ );
847
+ const source = requireResource<Texture>(
848
+ context,
849
+ event.source.textureHandleId,
850
+ 'texture',
851
+ eventIndex,
852
+ event.kind,
853
+ );
854
+ const destination = requireResource<Texture>(
855
+ context,
856
+ event.destination.textureHandleId,
857
+ 'texture',
858
+ eventIndex,
859
+ event.kind,
860
+ );
861
+ if (!encoder.ok) return encoder;
862
+ if (!source.ok) return source;
863
+ if (!destination.ok) return destination;
864
+ encoder.value.copyTextureToTexture(
865
+ { ...event.source, texture: source.value } as never,
866
+ { ...event.destination, texture: destination.value } as never,
867
+ event.copySize,
868
+ );
869
+ return ok(undefined);
870
+ }