diff --git a/package-lock.json b/package-lock.json index 815b321..f7cccee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@types/uuid": "^9.0.0", "@typescript-eslint/eslint-plugin": "^5.46.0", "@typescript-eslint/parser": "^5.46.0", - "@webgpu/types": "^0.1.23", + "@webgpu/types": "^0.1.40", "chai": "^4.3.7", "eslint": "^8.29.0", "eslint-config-prettier": "^8.5.0", @@ -685,9 +685,9 @@ } }, "node_modules/@webgpu/types": { - "version": "0.1.23", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.23.tgz", - "integrity": "sha512-SxHrhOTH1C7EcbnVZ3DzzliQs10QJO19GoUtzVJLIqDD0VeAdtxLEoNnP5zkCDKyMFvx3ptU7jESdaOIUqAuJg==", + "version": "0.1.40", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.40.tgz", + "integrity": "sha512-/BBkHLS6/eQjyWhY2H7Dx5DHcVrS2ICj9owvSRdgtQT6KcafLZA86tPze0xAOsd4FbsYKCUBUQyNi87q7gV7kw==", "dev": true }, "node_modules/accepts": { @@ -6285,9 +6285,9 @@ } }, "@webgpu/types": { - "version": "0.1.23", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.23.tgz", - "integrity": "sha512-SxHrhOTH1C7EcbnVZ3DzzliQs10QJO19GoUtzVJLIqDD0VeAdtxLEoNnP5zkCDKyMFvx3ptU7jESdaOIUqAuJg==", + "version": "0.1.40", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.40.tgz", + "integrity": "sha512-/BBkHLS6/eQjyWhY2H7Dx5DHcVrS2ICj9owvSRdgtQT6KcafLZA86tPze0xAOsd4FbsYKCUBUQyNi87q7gV7kw==", "dev": true }, "accepts": { diff --git a/package.json b/package.json index 0511eaf..d7c0249 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@types/uuid": "^9.0.0", "@typescript-eslint/eslint-plugin": "^5.46.0", "@typescript-eslint/parser": "^5.46.0", - "@webgpu/types": "^0.1.23", + "@webgpu/types": "^0.1.40", "chai": "^4.3.7", "eslint": "^8.29.0", "eslint-config-prettier": "^8.5.0", diff --git a/src/capture/registry.ts b/src/capture/registry.ts index 8610e27..1f0c14b 100644 --- a/src/capture/registry.ts +++ b/src/capture/registry.ts @@ -95,7 +95,13 @@ export interface TraceObject { } // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface TraceAdapter extends TraceObject {} +export interface TraceAdapter extends TraceObject { } +export interface TraceAdapterInfo extends TraceObject { + vendor: string; + architecture: string; + device: string; + description: string; +} export interface TraceBindGroupEntry { binding: number; @@ -153,7 +159,8 @@ export interface TraceExplicitBindGroupLayout extends TraceObject { export interface TraceImplicitBindGroupLayout extends TraceObject { deviceSerial: number; - renderPipelineSerial: number; + renderPipelineSerial?: number; + computePipelineSerial?: number; groupIndex: number; } @@ -232,10 +239,10 @@ export interface TraceRenderPassColorAttachment { storeOp: GPUStoreOp; } -export interface TraceRenderPassTimestampWrite { +export interface TraceRenderPassTimestampWrites { querySetSerial: number; - queryIndex: number; - location: GPURenderPassTimestampLocation; + beginningOfPassWriteIndex?: number; + endOfPassWriteIndex?: number; } export interface TraceRenderPassDepthStencilAttachment { @@ -263,6 +270,8 @@ export interface TraceCommandBufferCommandBeginRenderPass { args: TraceCommandBeginRenderPassArgs; } +export interface TraceCommandBeginComputePassArgs { } + export interface TraceCommandBufferCommandDraw { name: 'draw'; args: { @@ -346,6 +355,22 @@ export interface TraceCommandBufferCommandEndPass { name: 'endPass'; } +export interface TraceCommandBufferCommandBeginComputePass { + name: 'beginComputePass'; + args: TraceCommandBeginComputePassArgs; +} + +export interface TraceCommandDispatchWorkgroupsArgs { + workgroupCountX: number; + workgroupCountY: number; + workgroupCountZ: number; +} + +export interface TraceCommandBufferCommandDispatchWorkgroups { + name: 'dispatchWorkgroups'; + args: TraceCommandDispatchWorkgroupsArgs; +} + export type TraceCommandBufferCommand = | TraceCommandBufferCommandCopyBufferToBuffer | TraceCommandBufferCommandCopyBufferToTexture @@ -362,7 +387,9 @@ export type TraceCommandBufferCommand = | TraceCommandBufferCommandSetVertexBuffer | TraceCommandBufferCommandSetScissorRect | TraceCommandBufferCommandSetViewport - | TraceCommandBufferCommandEndPass; + | TraceCommandBufferCommandEndPass + | TraceCommandBufferCommandBeginComputePass + | TraceCommandBufferCommandDispatchWorkgroups; export interface TraceDevice extends TraceObject { adapterSerial: number; @@ -403,14 +430,14 @@ export interface TraceSampler extends TraceSamplerState { export interface TraceVertexState { moduleSerial: number; - entryPoint: string; + entryPoint?: string; constants: Record; buffers: GPUVertexBufferLayout[]; } export interface TraceFragmentState { moduleSerial: number; - entryPoint: string; + entryPoint?: string; constants: Record; targets: GPUColorTargetState[]; } @@ -426,6 +453,19 @@ export interface TraceRenderPipeline extends TraceObject { fragment?: TraceFragmentState; } +export interface TraceComputeState { + moduleSerial: number; + entryPoint?: string; + constants: Record; +} + +export interface TraceComputePipeline extends TraceObject { + deviceSerial: number; + layoutSerial?: number; + layout?: string; + compute: TraceComputeState; +} + export interface TraceShaderModule extends TraceObject { deviceSerial: number; code: string; @@ -565,6 +605,8 @@ export interface Trace { samplers: Record; renderPassEncoders: Record; // TODO: Add correct type renderPipelines: Record; + computePassEncoders: Record; + computePipelines: Record; shaderModules: Record; textures: Record; textureViews: Record; @@ -591,6 +633,8 @@ export class WebGPUDebugger { queues = new ObjectRegistry(); renderPassEncoders = new ObjectRegistry(); renderPipelines = new ObjectRegistry(); + computePassEncoders = new ObjectRegistry(); + computePipelines = new ObjectRegistry(); samplers = new ObjectRegistry(); shaderModules = new ObjectRegistry(); textures = new ObjectRegistry(); @@ -605,6 +649,8 @@ export class WebGPUDebugger { queueProto: Proto; renderPassEncoderProto: Proto; renderPipelineProto: Proto; + computePassEncoderProto: Proto; + computePipelineProto: Proto; textureProto: Proto; canvasGetContext: Function; @@ -627,6 +673,8 @@ export class WebGPUDebugger { const originalProto: Record = {}; for (const name in c.prototype) { + console.log(name); + const props = Object.getOwnPropertyDescriptor(c.prototype, name); if (!props?.writable || typeof c.prototype[name] !== 'function') { continue; @@ -664,6 +712,8 @@ export class WebGPUDebugger { this.queueProto = replacePrototypeOf(GPUQueue, this.queues); this.renderPassEncoderProto = replacePrototypeOf(GPURenderPassEncoder, this.renderPassEncoders); this.renderPipelineProto = replacePrototypeOf(GPURenderPipeline, this.renderPipelines); + this.computePassEncoderProto = replacePrototypeOf(GPUComputePassEncoder, this.computePassEncoders); + this.computePipelineProto = replacePrototypeOf(GPUComputePipeline, this.computePipelines); // GPUSampler doesn't have methods except the label setter? // TODO shader module prototype this.textureProto = replacePrototypeOf(GPUTexture, this.textures); @@ -697,6 +747,7 @@ export class WebGPUDebugger { { Class: GPUQueue, proto: this.queueProto, wrappers: {} }, { Class: GPURenderPassEncoder, proto: this.renderPassEncoderProto, wrappers: {} }, { Class: GPURenderPipeline, proto: this.renderPipelineProto, wrappers: {} }, + { Class: GPUComputePipeline, proto: this.computePipelineProto, wrappers: {} }, { Class: GPUTexture, proto: this.textureProto, wrappers: {} }, ], getContextWrapper: HTMLCanvasElement.prototype.getContext, @@ -824,6 +875,8 @@ export class WebGPUDebugger { samplers: serializeAllObjects(this.samplers), renderPassEncoders: {}, renderPipelines: serializeAllObjects(this.renderPipelines), + computePassEncoders: {}, + computePipelines: serializeAllObjects(this.computePipelines), shaderModules: serializeAllObjects(this.shaderModules), textures: serializeAsyncAllObjects(this.textures, this.pendingTraceOperations), textureViews: serializeAllObjects(this.textureViews), @@ -938,6 +991,12 @@ class AdapterState extends BaseState { return device; } + async requestAdapterInfo() { + const info = await tracer.adapterProto.requestAdapterInfo.call(this.webgpuObject); + + return info; + } + destroy() { // TODO: handle this } @@ -1012,7 +1071,7 @@ class BindGroupState extends BaseState { interface ImplicitBindGroupLayoutDescriptor { implicit: boolean; - renderPipeline: RenderPipelineState; + pipeline: RenderPipelineState | ComputePipelineState; groupIndex: number; } @@ -1020,7 +1079,7 @@ class BindGroupLayoutState extends BaseState { device: DeviceState; implicit?: boolean; entries?: TraceExplicitBindGroupEntry[]; - parentRenderPipeline: any; + parentPipeline: any; pipelineGroupIndex: any; constructor(device: DeviceState, desc: GPUBindGroupLayoutDescriptor | ImplicitBindGroupLayoutDescriptor) { @@ -1034,7 +1093,7 @@ class BindGroupLayoutState extends BaseState { // different parameters. Should this be refactored? this.implicit = implicitDesc.implicit; if (this.implicit) { - this.parentRenderPipeline = implicitDesc.renderPipeline; + this.parentPipeline = implicitDesc.pipeline; this.pipelineGroupIndex = implicitDesc.groupIndex; return; } @@ -1078,7 +1137,8 @@ class BindGroupLayoutState extends BaseState { if (this.implicit) { const b: TraceImplicitBindGroupLayout = { deviceSerial: this.device.traceSerial, - renderPipelineSerial: this.parentRenderPipeline.traceSerial, + renderPipelineSerial: this.parentPipeline instanceof RenderPipelineState ? this.parentPipeline.traceSerial : undefined, + computePipelineSerial: this.parentPipeline instanceof ComputePipelineState ? this.parentPipeline.traceSerial : undefined, groupIndex: this.pipelineGroupIndex, }; return b; @@ -1253,6 +1313,12 @@ class CommandEncoderState extends BaseState { return pass; } + beginComputePass(desc: GPUComputePassDescriptor) { + const pass = tracer.commandEncoderProto.beginComputePass.call(this.webgpuObject, desc); + tracer.registerObjectIn('computePassEncoders', pass, new ComputePassEncoderState(this, desc)); + return pass; + } + copyTextureToTexture(source: GPUImageCopyTexture, destination: GPUImageCopyTexture, copySize: GPUExtent3D) { tracer.commandEncoderProto.copyTextureToTexture.call(this.webgpuObject, source, destination, copySize); this.addCommand({ @@ -1518,6 +1584,12 @@ class DeviceState extends BaseState { return pipeline; } + createComputePipeline(desc: GPUComputePipelineDescriptor) { + const pipeline = tracer.deviceProto.createComputePipeline.call(this.webgpuObject, desc); + tracer.registerObjectIn('computePipelines', pipeline, new ComputePipelineState(this, desc)); + return pipeline; + } + createSampler(desc: GPUSamplerDescriptor) { const module = tracer.deviceProto.createSampler.call(this.webgpuObject, desc); tracer.registerObjectIn('samplers', module, new SamplerState(this, desc ?? {})); @@ -1722,6 +1794,18 @@ class RenderPassEncoderState extends BaseState { constructor(encoder: CommandEncoderState, desc: GPURenderPassDescriptor) { super(desc); this.encoder = encoder; + + let timestampWrites; + if (desc.timestampWrites) { + this.encoder.reference(desc.timestampWrites.querySet); + + timestampWrites = { + querySetSerial: tracer.querySets.get(desc.timestampWrites.querySet)!.traceSerial, + beginningOfPassWriteIndex: desc.timestampWrites.beginningOfPassWriteIndex, + endOfPassWriteIndex: desc.timestampWrites.endOfPassWriteIndex, + }; + } + const serializeDesc: TraceCommandBeginRenderPassArgs = { colorAttachments: (desc.colorAttachments as GPURenderPassColorAttachment[]).map(a => { this.encoder.reference(a.view); @@ -1946,7 +2030,7 @@ class RenderPipelineState extends BaseState { layout: GPUPipelineLayout | string; vertex: { module: ShaderModuleState; - entryPoint: string; + entryPoint?: string; constants: Record; buffers: GPUVertexBufferLayout[]; }; @@ -1955,7 +2039,7 @@ class RenderPipelineState extends BaseState { multisample: GPUMultisampleState; fragment?: { module: ShaderModuleState; - entryPoint: string; + entryPoint?: string; constants: Record; targets: GPUColorTargetState[]; }; @@ -2112,7 +2196,145 @@ class RenderPipelineState extends BaseState { bgl, new BindGroupLayoutState(this.device, { implicit: true, - renderPipeline: this, + pipeline: this, + groupIndex, + }) + ); + return bgl; + } +} + +class ComputePassEncoderState extends BaseState { + encoder: CommandEncoderState; + + constructor(encoder: CommandEncoderState, desc: GPUComputePassDescriptor) { + super(desc); + this.encoder = encoder; + + const serializeDesc: TraceCommandBeginComputePassArgs = {}; + + this.encoder.addCommand({ + name: 'beginComputePass', + args: serializeDesc, + }); + } + + serialize() { + return {}; + } + + popDebugGroup() { + tracer.computePassEncoderProto.popDebugGroup.call(this.webgpuObject); + this.encoder.addCommand({ name: 'popDebugGroup' }); + } + + pushDebugGroup(groupLabel: string) { + tracer.computePassEncoderProto.pushDebugGroup.call(this.webgpuObject, groupLabel); + this.encoder.addCommand({ + name: 'pushDebugGroup', + args: { + groupLabel, + }, + }); + } + + dispatchWorkgroups(workgroupCountX: number, workgroupCountY: number, workgroupCountZ: number) { + tracer.computePassEncoderProto.dispatchWorkgroups.call(this.webgpuObject, workgroupCountX, workgroupCountY, workgroupCountZ); + + this.encoder.addCommand({ + name: 'dispatchWorkgroups', + args: { + workgroupCountX, + workgroupCountY, + workgroupCountZ + }, + }); + } + + setBindGroup(index: number, bindGroup: GPUBindGroup, dynamicOffsets?: number[]) { + if (dynamicOffsets !== undefined) { + console.assert(false, "Don't know how to handle dynamic bindgroups yet."); + } + + tracer.computePassEncoderProto.setBindGroup.call(this.webgpuObject, index, bindGroup); + this.encoder.reference(bindGroup); + this.encoder.addCommand({ + name: 'setBindGroup', + args: { + index, + bindGroupSerial: tracer.bindGroups.get(bindGroup)!.traceSerial, + dynamicOffsets: window.structuredClone(dynamicOffsets), + }, + }); + } + + setPipeline(pipeline: GPUComputePipeline) { + tracer.computePassEncoderProto.setPipeline.call(this.webgpuObject, pipeline); + this.encoder.reference(pipeline); + this.encoder.addCommand({ + name: 'setPipeline', + args: { + pipelineSerial: tracer.computePipelines.get(pipeline)!.traceSerial, + }, + }); + } + + end() { + tracer.computePassEncoderProto.end.call(this.webgpuObject); + this.encoder.addCommand({ name: 'endPass' }); + } +} + +class ComputePipelineState extends BaseState { + device: DeviceState; + layout: GPUPipelineLayout | string; + compute: { + module: ShaderModuleState; + entryPoint?: string; + constants: Record; + }; + + constructor(device: DeviceState, desc: GPUComputePipelineDescriptor) { + super(desc); + + this.device = device; + this.layout = desc.layout; + + this.compute = { + module: tracer.shaderModules.get(desc.compute.module)!, + entryPoint: desc.compute.entryPoint, + constants: { ...desc.compute.constants }, + }; + } + + serialize(): TraceComputePipeline { + const { layout } = this; + const result: TraceComputePipeline = { + deviceSerial: this.device.traceSerial, + compute: { + moduleSerial: this.compute.module.traceSerial, + entryPoint: this.compute.entryPoint, + constants: this.compute.constants, + }, + }; + + if (layout === 'auto') { + result.layout = 'auto'; + } else { + result.layoutSerial = tracer.pipelineLayouts.get(layout as GPUPipelineLayout)!.traceSerial; + } + + return result; + } + + getBindGroupLayout(groupIndex: number) { + const bgl = tracer.computePipelineProto.getBindGroupLayout.call(this.webgpuObject, groupIndex); + tracer.registerObjectIn( + 'bindGroupLayouts', + bgl, + new BindGroupLayoutState(this.device, { + implicit: true, + pipeline: this, groupIndex, }) ); @@ -2231,6 +2453,7 @@ export const kTextureFormatInfo: Record = { rgb10a2unorm: c114, rg11b10ufloat: c114, rgb9e5ufloat: c114, + rgb10a2uint: c114, stencil8: { type: 'stencil', blockByteSize: 1 }, depth16unorm: { type: 'depth', blockWidth: 1, blockHeight: 1, blockByteSize: 2 }, diff --git a/src/capture/unwrapped-webgpu.ts b/src/capture/unwrapped-webgpu.ts index 5057a9e..7896295 100644 --- a/src/capture/unwrapped-webgpu.ts +++ b/src/capture/unwrapped-webgpu.ts @@ -10,7 +10,7 @@ const mapClassToCreationFunctionNames = new Map>(); // eslint-disable-next-line @typescript-eslint/no-unused-vars let getUnwrappedDevice = (_: GPUDevice): GPUDevice | undefined => undefined; // eslint-disable-next-line @typescript-eslint/no-unused-vars -let setUnwrappedDevice = (wrapped: GPUDevice, unwrapped: GPUDevice) => {}; +let setUnwrappedDevice = (wrapped: GPUDevice, unwrapped: GPUDevice) => { }; if (typeof GPUDevice !== 'undefined') { if (!GPUDevice.prototype.createBuffer.toString().includes('native')) { @@ -29,6 +29,8 @@ if (typeof GPUDevice !== 'undefined') { GPUQueue, GPURenderPassEncoder, GPURenderPipeline, + GPUComputePipeline, + GPUComputePassEncoder, GPUTexture, ]; diff --git a/src/replay/lib.ts b/src/replay/lib.ts index 0175489..4cc6437 100644 --- a/src/replay/lib.ts +++ b/src/replay/lib.ts @@ -10,6 +10,7 @@ import { TraceBufferUpdate, TraceCommandBuffer, TraceCommandBufferCommand, + TraceComputePipeline, TraceData, TraceDevice, TraceExplicitBindGroupLayout, @@ -101,6 +102,7 @@ export interface ReplayCommandBufferCommandCopyTextureToBuffer { export interface ReplayBindGroupLayoutDescriptor extends GPUBindGroupLayoutDescriptor { renderPipelineSerial?: number; + computePipelineSerial?: number; groupIndex?: number; } @@ -118,7 +120,8 @@ export interface ReplayRenderPassTimestampWrite { querySet: GPUQuerySet; querySetState: ReplayQuerySet; queryIndex: number; - location: GPURenderPassTimestampLocation; + beginningOfPassWriteIndex: number; + endOfPassWriteIndex: number; } export interface ReplayRenderPassDepthStencilAttachment { @@ -150,6 +153,11 @@ export interface ReplayCommandBufferCommandRenderPass { renderPass: ReplayRenderPass; } +export interface ReplayCommandBufferCommandComputePass { + name: 'computePass'; + computePass: ReplayComputePass; +} + export interface ReplayCommandBufferCommandBeginRenderPass { name: 'beginRenderPass'; args: ReplayCommandBeginRenderPassArgs; @@ -213,7 +221,7 @@ export interface ReplayCommandBufferCommandSetIndexBuffer { export interface ReplayCommandBufferCommandSetPipeline { name: 'setPipeline'; args: { - pipeline: ReplayRenderPipeline; + pipeline: ReplayRenderPipeline | ReplayComputePipeline; }; } @@ -249,6 +257,25 @@ export interface ReplayCommandBufferCommandSetViewport { }; } +export interface ReplayCommandBeginComputePassArgs { +} + +export interface ReplayCommandBufferCommandBeginComputePass { + name: 'beginComputePass'; + args: ReplayCommandBeginComputePassArgs; +} + +export interface ReplayCommandDispatchWorkgroupsArgs { + workgroupCountX: number; + workgroupCountY: number; + workgroupCountZ: number; +} + +export interface ReplayCommandBufferCommandDispatchWorkgroups { + name: 'dispatchWorkgroups'; + args: ReplayCommandDispatchWorkgroupsArgs; +} + export type ReplayCommandBufferCommand = | ReplayCommandBufferCommandCopyBufferToBuffer | ReplayCommandBufferCommandCopyBufferToTexture @@ -266,7 +293,10 @@ export type ReplayCommandBufferCommand = | ReplayCommandBufferCommandSetVertexBuffer | ReplayCommandBufferCommandSetScissorRect | ReplayCommandBufferCommandSetViewport - | ReplayCommandBufferCommandEndPass; + | ReplayCommandBufferCommandEndPass + | ReplayCommandBufferCommandComputePass + | ReplayCommandBufferCommandBeginComputePass + | ReplayCommandBufferCommandDispatchWorkgroups; export interface ReplayCommandSubmit { name: 'queueSubmit'; @@ -392,6 +422,11 @@ interface ReplayStateBeginRenderPass { indexBuffer?: ReplayStateBuffer; } +interface ReplayStateBeginComputePass { + pipeline?: ReplayComputePipeline; + bindGroups: ReplayStateBindGroup[]; +} + export class Replay { commands: ReplayQueueCommand[] = []; data: Record = {}; @@ -406,6 +441,7 @@ export class Replay { pipelineLayouts: Record = {}; shaderModules: Record = {}; renderPipelines: Record = {}; + computePipelines: Record = {}; buffers: Record = {}; samplers: Record = {}; textures: Record = {}; @@ -421,6 +457,7 @@ export class Replay { pipelineLayoutsToReplayMap = new Map(); shaderModulesToReplayMap = new Map(); renderPipelinesToReplayMap = new Map(); + computePipelinesToReplayMap = new Map(); buffersToReplayMap = new Map(); samplersToReplayMap = new Map(); texturesToReplayMap = new Map(); @@ -429,7 +466,7 @@ export class Replay { bindGroupsToReplayMap = new Map(); commandBuffersToReplayMap = new Map(); - constructor() {} + constructor() { } async load(trace: Trace) { async function recreateObjectsAsync, TraceType>( @@ -521,6 +558,12 @@ export class Replay { ReplayRenderPipeline, trace.objects.renderPipelines ); + [this.computePipelines, this.computePipelinesToReplayMap] = await recreateObjectsAsync( + this, + ReplayComputePipeline, + trace.objects.computePipelines + ); + // Initialize the implicit bind group layouts now that all pipelines are created. // Luckily implicit layouts can't be used to create pipeline layouts so we don't have circular dependencies. for (const i in this.bindGroupLayouts) { @@ -970,8 +1013,10 @@ export class ReplayRenderPass extends ReplayObject { state.indexBuffer = { buffer: c.args.buffer, offset: c.args.offset, size: c.args.size }; break; case 'setPipeline': - renderPass.setPipeline(c.args.pipeline.webgpuObject); - state.pipeline = c.args.pipeline; + if (c.args.pipeline instanceof ReplayRenderPipeline) { + renderPass.setPipeline(c.args.pipeline.webgpuObject); + state.pipeline = c.args.pipeline; + } break; case 'setVertexBuffer': renderPass.setVertexBuffer(c.args.slot, c.args.buffer.webgpuObject, c.args.offset, c.args.size); @@ -1035,6 +1080,147 @@ export class ReplayRenderPass extends ReplayObject { } } +export class ReplayComputePass extends ReplayObject { + commands: ReplayCommandBufferCommand[]; + + constructor(replay: Replay) { + super(replay); + this.commands = []; + } + + consumeCommands( + beginComputePassCommand: ReplayCommandBufferCommandBeginComputePass, + commandIterator: Iterable + ) { + console.assert(this.commands.length === 0); + this.commands.push(beginComputePassCommand); + + for (const command of commandIterator) { + const c = window.structuredClone(command); + switch (c.name) { + case 'endPass': + this.commands.push(c); + return; + + case 'setBindGroup': + c.args.bindGroup = this.replay.bindGroups[c.args.bindGroupSerial]; + delete c.args.bindGroupSerial; + break; + case 'setPipeline': + c.args.pipeline = this.replay.computePipelines[c.args.pipelineSerial]; + delete c.args.pipelineSerial; + break; + case 'dispatchWorkgroups': + case 'drawIndexed': + break; + case 'pushDebugGroup': + case 'popDebugGroup': + break; + default: + console.assert(false, `Unhandled render pass command type '${c.name}'`); + } + this.commands.push(c); + } + + console.assert(false, `BeginComputePass command doesn't have an associated EndPass.`); + } + + encodeIn(encoder: GPUCommandEncoder) { + this.encodeUpTo([this.commands.length - 1], encoder); + } + + encodeUpTo(path: number[], encoder: GPUCommandEncoder) { + const commandIndex = path.shift()!; + + console.assert(this.commands.length > 0); + console.assert(this.commands[0].name === 'beginComputePass'); + console.assert(this.commands[this.commands.length - 1].name === 'endPass'); + + const computePassDesc = (this.commands[0] as ReplayCommandBufferCommandBeginComputePass).args; + + if (commandIndex === 0) { + this.replay.state = computePassDesc; + return; + } + + // init to defaults + const state: ReplayStateBeginComputePass = { + pipeline: undefined, + bindGroups: [], + }; + + const computePass = encoder.beginComputePass(computePassDesc); + let computePassEnded = false; + + for (let i = 1; i <= commandIndex; i++) { + const c = this.commands[i]; + switch (c.name) { + case 'dispatchWorkgroups': + computePass.dispatchWorkgroups(c.args.workgroupCountX, c.args.workgroupCountY, c.args.workgroupCountZ); + break; + case 'endPass': + computePass.end(); + computePassEnded = true; + break; + case 'popDebugGroup': + computePass.popDebugGroup(); + break; + case 'pushDebugGroup': + computePass.pushDebugGroup(c.args.groupLabel); + break; + case 'setBindGroup': + computePass.setBindGroup(c.args.index, c.args.bindGroup.webgpuObject, c.args.dynamicOffsets); + state.bindGroups[c.args.index] = { group: c.args.bindGroup, dynamicOffsets: c.args.dynamicOffsets }; + break; + case 'setPipeline': + console.assert(c.args.pipeline instanceof ReplayComputePipeline); + + if (c.args.pipeline instanceof ReplayComputePipeline) { + computePass.setPipeline(c.args.pipeline.webgpuObject); + state.pipeline = c.args.pipeline; + } + + break; + default: + console.assert(false, `Unhandled render pass command type '${c.name}'`); + } + } + + if (!computePassDesc) { + this.replay.state = state; + computePass.end(); + } + } + + *iterateCommands([i, j, k]: number[]) { + for (let l = 0; l < this.commands.length; l++) { + const c = this.commands[l]; + switch (c.name) { + case 'beginRenderPass': + case 'copyBufferToBuffer': + case 'copyBufferToTexture': + case 'copyTextureToTexture': + case 'copyTextureToBuffer': + case 'draw': + case 'drawIndexed': + case 'endPass': + case 'popDebugGroup': + case 'pushDebugGroup': + case 'setBindGroup': + case 'setIndexBuffer': + case 'setPipeline': + case 'setVertexBuffer': + case 'setViewport': + case 'dispatchWorkgroups': + yield { path: [i, j, k, l], command: c }; + break; + default: + console.assert(false, `Unhandled render pass command type '${c.name}'`); + } + } + } +} + export class ReplayCommandBuffer extends ReplayObject { commands: ReplayCommandBufferCommand[]; webgpuObject!: GPUCommandBuffer; // THIS DOESN'T EXIST. It's only here to make typescript happy @@ -1092,6 +1278,13 @@ export class ReplayCommandBuffer extends ReplayObject { this.commands.push({ name: 'renderPass', renderPass: rp }); continue; } + case 'beginComputePass': { + // Special case, add a pseudo-command that's a whole compute pass command. + const cp = new ReplayComputePass(this.replay); + cp.consumeCommands(c, commandIterator); + this.commands.push({ name: 'computePass', computePass: cp }); + continue; + } case 'copyBufferToBuffer': c.args.sourceState = this.replay.buffers[c.args.sourceSerial]; c.args.source = c.args.sourceState.webgpuObject; @@ -1150,6 +1343,13 @@ export class ReplayCommandBuffer extends ReplayObject { c.renderPass.encodeIn(encoder); } break; + case 'computePass': + if (i === commandIndex && !full) { + c.computePass.encodeUpTo(path, encoder); + } else { + c.computePass.encodeIn(encoder); + } + break; case 'copyBufferToBuffer': encoder.copyBufferToBuffer( c.args.source, @@ -1312,7 +1512,7 @@ export class ReplayBindGroupLayout extends ReplayObject { } initializeFromImplicitDesc() { - const pipeline = this.replay.renderPipelines[this.desc.renderPipelineSerial!]; + const pipeline = this.desc.renderPipelineSerial != undefined ? this.replay.renderPipelines[this.desc.renderPipelineSerial!] : this.replay.computePipelines[this.desc.computePipelineSerial!]; this.webgpuObject = pipeline.webgpuObject.getBindGroupLayout(this.desc.groupIndex!); } } @@ -1388,7 +1588,7 @@ export class ReplayQueue extends ReplayObject { export interface ReplayGPUProgramableStage { module: ReplayShaderModule; - entryPoint: string; + entryPoint?: string; constants?: Record; } @@ -1483,6 +1683,55 @@ export class ReplayRenderPipeline extends ReplayObject { } } +export interface ReplayGPUComputePipelineDescriptor { + layout?: ReplayPipelineLayout; + compute: ReplayShaderModule; +} + +export class ReplayComputePipeline extends ReplayObject { + device: ReplayDevice; + desc?: ReplayGPUComputePipelineDescriptor; + webgpuObject!: GPUComputePipeline; + + constructor(replay: Replay, desc: TraceComputePipeline) { + super(replay, desc); + this.device = this.replay.devices[desc.deviceSerial]; + } + + async recreate(desc: TraceComputePipeline) { + let replayLayout: ReplayPipelineLayout | undefined; + let layout: GPUPipelineLayout | 'auto'; + + if (desc.layout === 'auto') { + layout = 'auto'; + } else { + replayLayout = this.replay.pipelineLayouts[desc.layoutSerial!]; + layout = replayLayout.webgpuObject; + } + + const computeModule = this.replay.shaderModules[desc.compute.moduleSerial]; + + const replayDesc: ReplayGPUComputePipelineDescriptor = { + layout: replayLayout, + compute: computeModule + }; + + // Do this properly and with all state pls. + const localDesc: GPUComputePipelineDescriptor = { + layout, + label: desc.label, + compute: { + entryPoint: desc.compute.entryPoint, + module: computeModule.webgpuObject, + constants: desc.compute.constants + } + }; + + this.desc = replayDesc; + this.webgpuObject = await this.device.webgpuObject.createComputePipelineAsync(localDesc); + } +} + export class ReplaySampler extends ReplayObject { device: ReplayDevice; desc: TraceSampler; diff --git a/src/ui/views/objectViews/ShaderModuleVis/ShaderModuleVis.tsx b/src/ui/views/objectViews/ShaderModuleVis/ShaderModuleVis.tsx index 97a7be7..707113c 100644 --- a/src/ui/views/objectViews/ShaderModuleVis/ShaderModuleVis.tsx +++ b/src/ui/views/objectViews/ShaderModuleVis/ShaderModuleVis.tsx @@ -32,7 +32,7 @@ export default function ShaderModuleVis({ data }: { data: ReplayShaderModule }) return (
- +
{optionallyRemoveLeadingWhitespace(!raw, desc.code)}