UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

1,340 lines (1,219 loc) 58.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _WebGPUConstants = require("./utils/WebGPUConstants.js"); var _WGSLNodeBuilder = _interopRequireDefault(require("./nodes/WGSLNodeBuilder.js")); var _Backend = _interopRequireDefault(require("../common/Backend.js")); var _WebGPUUtils = _interopRequireDefault(require("./utils/WebGPUUtils.js")); var _WebGPUAttributeUtils = _interopRequireDefault(require("./utils/WebGPUAttributeUtils.js")); var _WebGPUBindingUtils = _interopRequireDefault(require("./utils/WebGPUBindingUtils.js")); var _WebGPUPipelineUtils = _interopRequireDefault(require("./utils/WebGPUPipelineUtils.js")); var _WebGPUTextureUtils = _interopRequireDefault(require("./utils/WebGPUTextureUtils.js")); var _constants = require("../../constants.js"); var _WebGPUTimestampQueryPool = _interopRequireDefault(require("./utils/WebGPUTimestampQueryPool.js")); var _utils = require("../../utils.js"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /*// debugger tools import 'https://greggman.github.io/webgpu-avoid-redundant-state-setting/webgpu-check-redundant-state-setting.js'; //*/ /** * A backend implementation targeting WebGPU. * * @private * @augments Backend */ class WebGPUBackend extends _Backend.default { /** * WebGPUBackend options. * * @typedef {Object} WebGPUBackend~Options * @property {boolean} [logarithmicDepthBuffer=false] - Whether logarithmic depth buffer is enabled or not. * @property {boolean} [alpha=true] - Whether the default framebuffer (which represents the final contents of the canvas) should be transparent or opaque. * @property {boolean} [depth=true] - Whether the default framebuffer should have a depth buffer or not. * @property {boolean} [stencil=false] - Whether the default framebuffer should have a stencil buffer or not. * @property {boolean} [antialias=false] - Whether MSAA as the default anti-aliasing should be enabled or not. * @property {number} [samples=0] - When `antialias` is `true`, `4` samples are used by default. Set this parameter to any other integer value than 0 to overwrite the default. * @property {boolean} [forceWebGL=false] - If set to `true`, the renderer uses a WebGL 2 backend no matter if WebGPU is supported or not. * @property {boolean} [trackTimestamp=false] - Whether to track timestamps with a Timestamp Query API or not. * @property {string} [powerPreference=undefined] - The power preference. * @property {Object} [requiredLimits=undefined] - Specifies the limits that are required by the device request. The request will fail if the adapter cannot provide these limits. * @property {GPUDevice} [device=undefined] - If there is an existing GPU device on app level, it can be passed to the renderer as a parameter. * @property {number} [outputType=undefined] - Texture type for output to canvas. By default, device's preferred format is used; other formats may incur overhead. */ /** * Constructs a new WebGPU backend. * * @param {WebGPUBackend~Options} [parameters] - The configuration parameter. */ constructor(parameters = {}) { super(parameters); /** * This flag can be used for type testing. * * @type {boolean} * @readonly * @default true */ this.isWebGPUBackend = true; // some parameters require default values other than "undefined" this.parameters.alpha = parameters.alpha === undefined ? true : parameters.alpha; this.parameters.requiredLimits = parameters.requiredLimits === undefined ? {} : parameters.requiredLimits; /** * A reference to the device. * * @type {?GPUDevice} * @default null */ this.device = null; /** * A reference to the context. * * @type {?GPUCanvasContext} * @default null */ this.context = null; /** * A reference to the color attachment of the default framebuffer. * * @type {?GPUTexture} * @default null */ this.colorBuffer = null; /** * A reference to the default render pass descriptor. * * @type {?Object} * @default null */ this.defaultRenderPassdescriptor = null; /** * A reference to a backend module holding common utility functions. * * @type {WebGPUUtils} */ this.utils = new _WebGPUUtils.default(this); /** * A reference to a backend module holding shader attribute-related * utility functions. * * @type {WebGPUAttributeUtils} */ this.attributeUtils = new _WebGPUAttributeUtils.default(this); /** * A reference to a backend module holding shader binding-related * utility functions. * * @type {WebGPUBindingUtils} */ this.bindingUtils = new _WebGPUBindingUtils.default(this); /** * A reference to a backend module holding shader pipeline-related * utility functions. * * @type {WebGPUPipelineUtils} */ this.pipelineUtils = new _WebGPUPipelineUtils.default(this); /** * A reference to a backend module holding shader texture-related * utility functions. * * @type {WebGPUTextureUtils} */ this.textureUtils = new _WebGPUTextureUtils.default(this); /** * A map that manages the resolve buffers for occlusion queries. * * @type {Map<number,GPUBuffer>} */ this.occludedResolveCache = new Map(); } /** * Initializes the backend so it is ready for usage. * * @async * @param {Renderer} renderer - The renderer. * @return {Promise} A Promise that resolves when the backend has been initialized. */ async init(renderer) { await super.init(renderer); // const parameters = this.parameters; // create the device if it is not passed with parameters let device; if (parameters.device === undefined) { const adapterOptions = { powerPreference: parameters.powerPreference }; const adapter = typeof navigator !== 'undefined' ? await navigator.gpu.requestAdapter(adapterOptions) : null; if (adapter === null) { throw new Error('WebGPUBackend: Unable to create WebGPU adapter.'); } // feature support const features = Object.values(_WebGPUConstants.GPUFeatureName); const supportedFeatures = []; for (const name of features) { if (adapter.features.has(name)) { supportedFeatures.push(name); } } const deviceDescriptor = { requiredFeatures: supportedFeatures, requiredLimits: parameters.requiredLimits }; device = await adapter.requestDevice(deviceDescriptor); } else { device = parameters.device; } device.lost.then(info => { const deviceLossInfo = { api: 'WebGPU', message: info.message || 'Unknown reason', reason: info.reason || null, originalEvent: info }; renderer.onDeviceLost(deviceLossInfo); }); const context = parameters.context !== undefined ? parameters.context : renderer.domElement.getContext('webgpu'); this.device = device; this.context = context; const alphaMode = parameters.alpha ? 'premultiplied' : 'opaque'; this.trackTimestamp = this.trackTimestamp && this.hasFeature(_WebGPUConstants.GPUFeatureName.TimestampQuery); this.context.configure({ device: this.device, format: this.utils.getPreferredCanvasFormat(), usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, alphaMode: alphaMode }); this.updateSize(); } /** * The coordinate system of the backend. * * @type {number} * @readonly */ get coordinateSystem() { return _constants.WebGPUCoordinateSystem; } /** * This method performs a readback operation by moving buffer data from * a storage buffer attribute from the GPU to the CPU. * * @async * @param {StorageBufferAttribute} attribute - The storage buffer attribute. * @return {Promise<ArrayBuffer>} A promise that resolves with the buffer data when the data are ready. */ async getArrayBufferAsync(attribute) { return await this.attributeUtils.getArrayBufferAsync(attribute); } /** * Returns the backend's rendering context. * * @return {GPUCanvasContext} The rendering context. */ getContext() { return this.context; } /** * Returns the default render pass descriptor. * * In WebGPU, the default framebuffer must be configured * like custom framebuffers so the backend needs a render * pass descriptor even when rendering directly to screen. * * @private * @return {Object} The render pass descriptor. */ _getDefaultRenderPassDescriptor() { let descriptor = this.defaultRenderPassdescriptor; if (descriptor === null) { const renderer = this.renderer; descriptor = { colorAttachments: [{ view: null }] }; if (this.renderer.depth === true || this.renderer.stencil === true) { descriptor.depthStencilAttachment = { view: this.textureUtils.getDepthBuffer(renderer.depth, renderer.stencil).createView() }; } const colorAttachment = descriptor.colorAttachments[0]; if (this.renderer.samples > 0) { colorAttachment.view = this.colorBuffer.createView(); } else { colorAttachment.resolveTarget = undefined; } this.defaultRenderPassdescriptor = descriptor; } const colorAttachment = descriptor.colorAttachments[0]; if (this.renderer.samples > 0) { colorAttachment.resolveTarget = this.context.getCurrentTexture().createView(); } else { colorAttachment.view = this.context.getCurrentTexture().createView(); } return descriptor; } /** * Returns the render pass descriptor for the given render context. * * @private * @param {RenderContext} renderContext - The render context. * @param {Object} colorAttachmentsConfig - Configuration object for the color attachments. * @return {Object} The render pass descriptor. */ _getRenderPassDescriptor(renderContext, colorAttachmentsConfig = {}) { const renderTarget = renderContext.renderTarget; const renderTargetData = this.get(renderTarget); let descriptors = renderTargetData.descriptors; if (descriptors === undefined || renderTargetData.width !== renderTarget.width || renderTargetData.height !== renderTarget.height || renderTargetData.dimensions !== renderTarget.dimensions || renderTargetData.activeMipmapLevel !== renderContext.activeMipmapLevel || renderTargetData.activeCubeFace !== renderContext.activeCubeFace || renderTargetData.samples !== renderTarget.samples) { descriptors = {}; renderTargetData.descriptors = descriptors; // dispose const onDispose = () => { renderTarget.removeEventListener('dispose', onDispose); this.delete(renderTarget); }; if (renderTarget.hasEventListener('dispose', onDispose) === false) { renderTarget.addEventListener('dispose', onDispose); } } const cacheKey = renderContext.getCacheKey(); let descriptorBase = descriptors[cacheKey]; if (descriptorBase === undefined) { const textures = renderContext.textures; const textureViews = []; let sliceIndex; for (let i = 0; i < textures.length; i++) { const textureData = this.get(textures[i]); const viewDescriptor = { label: `colorAttachment_${i}`, baseMipLevel: renderContext.activeMipmapLevel, mipLevelCount: 1, baseArrayLayer: renderContext.activeCubeFace, arrayLayerCount: 1, dimension: _WebGPUConstants.GPUTextureViewDimension.TwoD }; if (renderTarget.isRenderTarget3D) { sliceIndex = renderContext.activeCubeFace; viewDescriptor.baseArrayLayer = 0; viewDescriptor.dimension = _WebGPUConstants.GPUTextureViewDimension.ThreeD; viewDescriptor.depthOrArrayLayers = textures[i].image.depth; } else if (renderTarget.isRenderTargetArray) { viewDescriptor.dimension = _WebGPUConstants.GPUTextureViewDimension.TwoDArray; viewDescriptor.depthOrArrayLayers = textures[i].image.depth; } const textureView = textureData.texture.createView(viewDescriptor); let view, resolveTarget; if (textureData.msaaTexture !== undefined) { view = textureData.msaaTexture.createView(); resolveTarget = textureView; } else { view = textureView; resolveTarget = undefined; } textureViews.push({ view, resolveTarget, depthSlice: sliceIndex }); } descriptorBase = { textureViews }; if (renderContext.depth) { const depthTextureData = this.get(renderContext.depthTexture); descriptorBase.depthStencilView = depthTextureData.texture.createView(); } descriptors[cacheKey] = descriptorBase; renderTargetData.width = renderTarget.width; renderTargetData.height = renderTarget.height; renderTargetData.samples = renderTarget.samples; renderTargetData.activeMipmapLevel = renderContext.activeMipmapLevel; renderTargetData.activeCubeFace = renderContext.activeCubeFace; renderTargetData.dimensions = renderTarget.dimensions; } const descriptor = { colorAttachments: [] }; // Apply dynamic properties to cached views for (let i = 0; i < descriptorBase.textureViews.length; i++) { const viewInfo = descriptorBase.textureViews[i]; let clearValue = { r: 0, g: 0, b: 0, a: 1 }; if (i === 0 && colorAttachmentsConfig.clearValue) { clearValue = colorAttachmentsConfig.clearValue; } descriptor.colorAttachments.push({ view: viewInfo.view, depthSlice: viewInfo.depthSlice, resolveTarget: viewInfo.resolveTarget, loadOp: colorAttachmentsConfig.loadOp || _WebGPUConstants.GPULoadOp.Load, storeOp: colorAttachmentsConfig.storeOp || _WebGPUConstants.GPUStoreOp.Store, clearValue: clearValue }); } if (descriptorBase.depthStencilView) { descriptor.depthStencilAttachment = { view: descriptorBase.depthStencilView }; } return descriptor; } /** * This method is executed at the beginning of a render call and prepares * the WebGPU state for upcoming render calls * * @param {RenderContext} renderContext - The render context. */ beginRender(renderContext) { const renderContextData = this.get(renderContext); const device = this.device; const occlusionQueryCount = renderContext.occlusionQueryCount; let occlusionQuerySet; if (occlusionQueryCount > 0) { if (renderContextData.currentOcclusionQuerySet) renderContextData.currentOcclusionQuerySet.destroy(); if (renderContextData.currentOcclusionQueryBuffer) renderContextData.currentOcclusionQueryBuffer.destroy(); // Get a reference to the array of objects with queries. The renderContextData property // can be changed by another render pass before the buffer.mapAsyc() completes. renderContextData.currentOcclusionQuerySet = renderContextData.occlusionQuerySet; renderContextData.currentOcclusionQueryBuffer = renderContextData.occlusionQueryBuffer; renderContextData.currentOcclusionQueryObjects = renderContextData.occlusionQueryObjects; // occlusionQuerySet = device.createQuerySet({ type: 'occlusion', count: occlusionQueryCount, label: `occlusionQuerySet_${renderContext.id}` }); renderContextData.occlusionQuerySet = occlusionQuerySet; renderContextData.occlusionQueryIndex = 0; renderContextData.occlusionQueryObjects = new Array(occlusionQueryCount); renderContextData.lastOcclusionObject = null; } let descriptor; if (renderContext.textures === null) { descriptor = this._getDefaultRenderPassDescriptor(); } else { descriptor = this._getRenderPassDescriptor(renderContext, { loadOp: _WebGPUConstants.GPULoadOp.Load }); } this.initTimestampQuery(renderContext, descriptor); descriptor.occlusionQuerySet = occlusionQuerySet; const depthStencilAttachment = descriptor.depthStencilAttachment; if (renderContext.textures !== null) { const colorAttachments = descriptor.colorAttachments; for (let i = 0; i < colorAttachments.length; i++) { const colorAttachment = colorAttachments[i]; if (renderContext.clearColor) { colorAttachment.clearValue = i === 0 ? renderContext.clearColorValue : { r: 0, g: 0, b: 0, a: 1 }; colorAttachment.loadOp = _WebGPUConstants.GPULoadOp.Clear; colorAttachment.storeOp = _WebGPUConstants.GPUStoreOp.Store; } else { colorAttachment.loadOp = _WebGPUConstants.GPULoadOp.Load; colorAttachment.storeOp = _WebGPUConstants.GPUStoreOp.Store; } } } else { const colorAttachment = descriptor.colorAttachments[0]; if (renderContext.clearColor) { colorAttachment.clearValue = renderContext.clearColorValue; colorAttachment.loadOp = _WebGPUConstants.GPULoadOp.Clear; colorAttachment.storeOp = _WebGPUConstants.GPUStoreOp.Store; } else { colorAttachment.loadOp = _WebGPUConstants.GPULoadOp.Load; colorAttachment.storeOp = _WebGPUConstants.GPUStoreOp.Store; } } // if (renderContext.depth) { if (renderContext.clearDepth) { depthStencilAttachment.depthClearValue = renderContext.clearDepthValue; depthStencilAttachment.depthLoadOp = _WebGPUConstants.GPULoadOp.Clear; depthStencilAttachment.depthStoreOp = _WebGPUConstants.GPUStoreOp.Store; } else { depthStencilAttachment.depthLoadOp = _WebGPUConstants.GPULoadOp.Load; depthStencilAttachment.depthStoreOp = _WebGPUConstants.GPUStoreOp.Store; } } if (renderContext.stencil) { if (renderContext.clearStencil) { depthStencilAttachment.stencilClearValue = renderContext.clearStencilValue; depthStencilAttachment.stencilLoadOp = _WebGPUConstants.GPULoadOp.Clear; depthStencilAttachment.stencilStoreOp = _WebGPUConstants.GPUStoreOp.Store; } else { depthStencilAttachment.stencilLoadOp = _WebGPUConstants.GPULoadOp.Load; depthStencilAttachment.stencilStoreOp = _WebGPUConstants.GPUStoreOp.Store; } } // const encoder = device.createCommandEncoder({ label: 'renderContext_' + renderContext.id }); const currentPass = encoder.beginRenderPass(descriptor); // renderContextData.descriptor = descriptor; renderContextData.encoder = encoder; renderContextData.currentPass = currentPass; renderContextData.currentSets = { attributes: {}, bindingGroups: [], pipeline: null, index: null }; renderContextData.renderBundles = []; // if (renderContext.viewport) { this.updateViewport(renderContext); } if (renderContext.scissor) { const { x, y, width, height } = renderContext.scissorValue; currentPass.setScissorRect(x, y, width, height); } } /** * This method is executed at the end of a render call and finalizes work * after draw calls. * * @param {RenderContext} renderContext - The render context. */ finishRender(renderContext) { const renderContextData = this.get(renderContext); const occlusionQueryCount = renderContext.occlusionQueryCount; if (renderContextData.renderBundles.length > 0) { renderContextData.currentPass.executeBundles(renderContextData.renderBundles); } if (occlusionQueryCount > renderContextData.occlusionQueryIndex) { renderContextData.currentPass.endOcclusionQuery(); } renderContextData.currentPass.end(); if (occlusionQueryCount > 0) { const bufferSize = occlusionQueryCount * 8; // 8 byte entries for query results // let queryResolveBuffer = this.occludedResolveCache.get(bufferSize); if (queryResolveBuffer === undefined) { queryResolveBuffer = this.device.createBuffer({ size: bufferSize, usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC }); this.occludedResolveCache.set(bufferSize, queryResolveBuffer); } // const readBuffer = this.device.createBuffer({ size: bufferSize, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ }); // two buffers required here - WebGPU doesn't allow usage of QUERY_RESOLVE & MAP_READ to be combined renderContextData.encoder.resolveQuerySet(renderContextData.occlusionQuerySet, 0, occlusionQueryCount, queryResolveBuffer, 0); renderContextData.encoder.copyBufferToBuffer(queryResolveBuffer, 0, readBuffer, 0, bufferSize); renderContextData.occlusionQueryBuffer = readBuffer; // this.resolveOccludedAsync(renderContext); } this.device.queue.submit([renderContextData.encoder.finish()]); // if (renderContext.textures !== null) { const textures = renderContext.textures; for (let i = 0; i < textures.length; i++) { const texture = textures[i]; if (texture.generateMipmaps === true) { this.textureUtils.generateMipmaps(texture); } } } } /** * Returns `true` if the given 3D object is fully occluded by other * 3D objects in the scene. * * @param {RenderContext} renderContext - The render context. * @param {Object3D} object - The 3D object to test. * @return {boolean} Whether the 3D object is fully occluded or not. */ isOccluded(renderContext, object) { const renderContextData = this.get(renderContext); return renderContextData.occluded && renderContextData.occluded.has(object); } /** * This method processes the result of occlusion queries and writes it * into render context data. * * @async * @param {RenderContext} renderContext - The render context. * @return {Promise} A Promise that resolves when the occlusion query results have been processed. */ async resolveOccludedAsync(renderContext) { const renderContextData = this.get(renderContext); // handle occlusion query results const { currentOcclusionQueryBuffer, currentOcclusionQueryObjects } = renderContextData; if (currentOcclusionQueryBuffer && currentOcclusionQueryObjects) { const occluded = new WeakSet(); renderContextData.currentOcclusionQueryObjects = null; renderContextData.currentOcclusionQueryBuffer = null; await currentOcclusionQueryBuffer.mapAsync(GPUMapMode.READ); const buffer = currentOcclusionQueryBuffer.getMappedRange(); const results = new BigUint64Array(buffer); for (let i = 0; i < currentOcclusionQueryObjects.length; i++) { if (results[i] === BigInt(0)) { occluded.add(currentOcclusionQueryObjects[i]); } } currentOcclusionQueryBuffer.destroy(); renderContextData.occluded = occluded; } } /** * Updates the viewport with the values from the given render context. * * @param {RenderContext} renderContext - The render context. */ updateViewport(renderContext) { const { currentPass } = this.get(renderContext); const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue; currentPass.setViewport(x, y, width, height, minDepth, maxDepth); } /** * Returns the clear color and alpha into a single * color object. * * @return {Color4} The clear color. */ getClearColor() { const clearColor = super.getClearColor(); // only premultiply alpha when alphaMode is "premultiplied" if (this.renderer.alpha === true) { clearColor.r *= clearColor.a; clearColor.g *= clearColor.a; clearColor.b *= clearColor.a; } return clearColor; } /** * Performs a clear operation. * * @param {boolean} color - Whether the color buffer should be cleared or not. * @param {boolean} depth - Whether the depth buffer should be cleared or not. * @param {boolean} stencil - Whether the stencil buffer should be cleared or not. * @param {?RenderContext} [renderTargetContext=null] - The render context of the current set render target. */ clear(color, depth, stencil, renderTargetContext = null) { const device = this.device; const renderer = this.renderer; let colorAttachments = []; let depthStencilAttachment; let clearValue; let supportsDepth; let supportsStencil; if (color) { const clearColor = this.getClearColor(); clearValue = { r: clearColor.r, g: clearColor.g, b: clearColor.b, a: clearColor.a }; } if (renderTargetContext === null) { supportsDepth = renderer.depth; supportsStencil = renderer.stencil; const descriptor = this._getDefaultRenderPassDescriptor(); if (color) { colorAttachments = descriptor.colorAttachments; const colorAttachment = colorAttachments[0]; colorAttachment.clearValue = clearValue; colorAttachment.loadOp = _WebGPUConstants.GPULoadOp.Clear; colorAttachment.storeOp = _WebGPUConstants.GPUStoreOp.Store; } if (supportsDepth || supportsStencil) { depthStencilAttachment = descriptor.depthStencilAttachment; } } else { supportsDepth = renderTargetContext.depth; supportsStencil = renderTargetContext.stencil; const clearConfig = { loadOp: color ? _WebGPUConstants.GPULoadOp.Clear : _WebGPUConstants.GPULoadOp.Load, clearValue: color ? clearValue : undefined }; if (supportsDepth) { clearConfig.depthLoadOp = depth ? _WebGPUConstants.GPULoadOp.Clear : _WebGPUConstants.GPULoadOp.Load; clearConfig.depthClearValue = depth ? renderer.getClearDepth() : undefined; clearConfig.depthStoreOp = _WebGPUConstants.GPUStoreOp.Store; } if (supportsStencil) { clearConfig.stencilLoadOp = stencil ? _WebGPUConstants.GPULoadOp.Clear : _WebGPUConstants.GPULoadOp.Load; clearConfig.stencilClearValue = stencil ? renderer.getClearStencil() : undefined; clearConfig.stencilStoreOp = _WebGPUConstants.GPUStoreOp.Store; } const descriptor = this._getRenderPassDescriptor(renderTargetContext, clearConfig); colorAttachments = descriptor.colorAttachments; depthStencilAttachment = descriptor.depthStencilAttachment; } if (supportsDepth && depthStencilAttachment && depthStencilAttachment.depthLoadOp === undefined) { if (depth) { depthStencilAttachment.depthLoadOp = _WebGPUConstants.GPULoadOp.Clear; depthStencilAttachment.depthClearValue = renderer.getClearDepth(); depthStencilAttachment.depthStoreOp = _WebGPUConstants.GPUStoreOp.Store; } else { depthStencilAttachment.depthLoadOp = _WebGPUConstants.GPULoadOp.Load; depthStencilAttachment.depthStoreOp = _WebGPUConstants.GPUStoreOp.Store; } } // if (supportsStencil && depthStencilAttachment && depthStencilAttachment.stencilLoadOp === undefined) { if (stencil) { depthStencilAttachment.stencilLoadOp = _WebGPUConstants.GPULoadOp.Clear; depthStencilAttachment.stencilClearValue = renderer.getClearStencil(); depthStencilAttachment.stencilStoreOp = _WebGPUConstants.GPUStoreOp.Store; } else { depthStencilAttachment.stencilLoadOp = _WebGPUConstants.GPULoadOp.Load; depthStencilAttachment.stencilStoreOp = _WebGPUConstants.GPUStoreOp.Store; } } // const encoder = device.createCommandEncoder({ label: 'clear' }); const currentPass = encoder.beginRenderPass({ colorAttachments, depthStencilAttachment }); currentPass.end(); device.queue.submit([encoder.finish()]); } // compute /** * This method is executed at the beginning of a compute call and * prepares the state for upcoming compute tasks. * * @param {Node|Array<Node>} computeGroup - The compute node(s). */ beginCompute(computeGroup) { const groupGPU = this.get(computeGroup); const descriptor = { label: 'computeGroup_' + computeGroup.id }; this.initTimestampQuery(computeGroup, descriptor); groupGPU.cmdEncoderGPU = this.device.createCommandEncoder({ label: 'computeGroup_' + computeGroup.id }); groupGPU.passEncoderGPU = groupGPU.cmdEncoderGPU.beginComputePass(descriptor); } /** * Executes a compute command for the given compute node. * * @param {Node|Array<Node>} computeGroup - The group of compute nodes of a compute call. Can be a single compute node. * @param {Node} computeNode - The compute node. * @param {Array<BindGroup>} bindings - The bindings. * @param {ComputePipeline} pipeline - The compute pipeline. */ compute(computeGroup, computeNode, bindings, pipeline) { const { passEncoderGPU } = this.get(computeGroup); // pipeline const pipelineGPU = this.get(pipeline).pipeline; passEncoderGPU.setPipeline(pipelineGPU); // bind groups for (let i = 0, l = bindings.length; i < l; i++) { const bindGroup = bindings[i]; const bindingsData = this.get(bindGroup); passEncoderGPU.setBindGroup(i, bindingsData.group); } const maxComputeWorkgroupsPerDimension = this.device.limits.maxComputeWorkgroupsPerDimension; const computeNodeData = this.get(computeNode); if (computeNodeData.dispatchSize === undefined) computeNodeData.dispatchSize = { x: 0, y: 1, z: 1 }; const { dispatchSize } = computeNodeData; if (computeNode.dispatchCount > maxComputeWorkgroupsPerDimension) { dispatchSize.x = Math.min(computeNode.dispatchCount, maxComputeWorkgroupsPerDimension); dispatchSize.y = Math.ceil(computeNode.dispatchCount / maxComputeWorkgroupsPerDimension); } else { dispatchSize.x = computeNode.dispatchCount; } passEncoderGPU.dispatchWorkgroups(dispatchSize.x, dispatchSize.y, dispatchSize.z); } /** * This method is executed at the end of a compute call and * finalizes work after compute tasks. * * @param {Node|Array<Node>} computeGroup - The compute node(s). */ finishCompute(computeGroup) { const groupData = this.get(computeGroup); groupData.passEncoderGPU.end(); this.device.queue.submit([groupData.cmdEncoderGPU.finish()]); } /** * Can be used to synchronize CPU operations with GPU tasks. So when this method is called, * the CPU waits for the GPU to complete its operation (e.g. a compute task). * * @async * @return {Promise} A Promise that resolves when synchronization has been finished. */ async waitForGPU() { await this.device.queue.onSubmittedWorkDone(); } // render object /** * Executes a draw command for the given render object. * * @param {RenderObject} renderObject - The render object to draw. * @param {Info} info - Holds a series of statistical information about the GPU memory and the rendering process. */ draw(renderObject, info) { const { object, material, context, pipeline } = renderObject; const bindings = renderObject.getBindings(); const renderContextData = this.get(context); const pipelineGPU = this.get(pipeline).pipeline; const currentSets = renderContextData.currentSets; const passEncoderGPU = renderContextData.currentPass; const drawParams = renderObject.getDrawParameters(); if (drawParams === null) return; // pipeline if (currentSets.pipeline !== pipelineGPU) { passEncoderGPU.setPipeline(pipelineGPU); currentSets.pipeline = pipelineGPU; } // bind groups const currentBindingGroups = currentSets.bindingGroups; for (let i = 0, l = bindings.length; i < l; i++) { const bindGroup = bindings[i]; const bindingsData = this.get(bindGroup); if (currentBindingGroups[bindGroup.index] !== bindGroup.id) { passEncoderGPU.setBindGroup(bindGroup.index, bindingsData.group); currentBindingGroups[bindGroup.index] = bindGroup.id; } } // attributes const index = renderObject.getIndex(); const hasIndex = index !== null; // index if (hasIndex === true) { if (currentSets.index !== index) { const buffer = this.get(index).buffer; const indexFormat = index.array instanceof Uint16Array ? _WebGPUConstants.GPUIndexFormat.Uint16 : _WebGPUConstants.GPUIndexFormat.Uint32; passEncoderGPU.setIndexBuffer(buffer, indexFormat); currentSets.index = index; } } // vertex buffers const vertexBuffers = renderObject.getVertexBuffers(); for (let i = 0, l = vertexBuffers.length; i < l; i++) { const vertexBuffer = vertexBuffers[i]; if (currentSets.attributes[i] !== vertexBuffer) { const buffer = this.get(vertexBuffer).buffer; passEncoderGPU.setVertexBuffer(i, buffer); currentSets.attributes[i] = vertexBuffer; } } // occlusion queries - handle multiple consecutive draw calls for an object if (renderContextData.occlusionQuerySet !== undefined) { const lastObject = renderContextData.lastOcclusionObject; if (lastObject !== object) { if (lastObject !== null && lastObject.occlusionTest === true) { passEncoderGPU.endOcclusionQuery(); renderContextData.occlusionQueryIndex++; } if (object.occlusionTest === true) { passEncoderGPU.beginOcclusionQuery(renderContextData.occlusionQueryIndex); renderContextData.occlusionQueryObjects[renderContextData.occlusionQueryIndex] = object; } renderContextData.lastOcclusionObject = object; } } // stencil if (context.stencil === true && material.stencilWrite === true && renderContextData.currentStencilRef !== material.stencilRef) { passEncoderGPU.setStencilReference(material.stencilRef); renderContextData.currentStencilRef = material.stencilRef; } // draw const draw = () => { if (object.isBatchedMesh === true) { const starts = object._multiDrawStarts; const counts = object._multiDrawCounts; const drawCount = object._multiDrawCount; const drawInstances = object._multiDrawInstances; if (drawInstances !== null) { // @deprecated, r174 (0, _utils.warnOnce)('THREE.WebGPUBackend: renderMultiDrawInstances has been deprecated and will be removed in r184. Append to renderMultiDraw arguments and use indirection.'); } for (let i = 0; i < drawCount; i++) { const count = drawInstances ? drawInstances[i] : 1; const firstInstance = count > 1 ? 0 : i; if (hasIndex === true) { passEncoderGPU.drawIndexed(counts[i], count, starts[i] / index.array.BYTES_PER_ELEMENT, 0, firstInstance); } else { passEncoderGPU.draw(counts[i], count, starts[i], firstInstance); } info.update(object, counts[i], count); } } else if (hasIndex === true) { const { vertexCount: indexCount, instanceCount, firstVertex: firstIndex } = drawParams; const indirect = renderObject.getIndirect(); if (indirect !== null) { const buffer = this.get(indirect).buffer; passEncoderGPU.drawIndexedIndirect(buffer, 0); } else { passEncoderGPU.drawIndexed(indexCount, instanceCount, firstIndex, 0, 0); } info.update(object, indexCount, instanceCount); } else { const { vertexCount, instanceCount, firstVertex } = drawParams; const indirect = renderObject.getIndirect(); if (indirect !== null) { const buffer = this.get(indirect).buffer; passEncoderGPU.drawIndirect(buffer, 0); } else { passEncoderGPU.draw(vertexCount, instanceCount, firstVertex, 0); } info.update(object, vertexCount, instanceCount); } }; if (renderObject.camera.isArrayCamera && renderObject.camera.cameras.length > 0) { const cameraData = this.get(renderObject.camera); const cameras = renderObject.camera.cameras; const cameraIndex = renderObject.getBindingGroup('cameraIndex'); if (cameraData.indexesGPU === undefined || cameraData.indexesGPU.length !== cameras.length) { const bindingsData = this.get(cameraIndex); const indexesGPU = []; const data = new Uint32Array([0, 0, 0, 0]); for (let i = 0, len = cameras.length; i < len; i++) { data[0] = i; const bindGroupIndex = this.bindingUtils.createBindGroupIndex(data, bindingsData.layout); indexesGPU.push(bindGroupIndex); } cameraData.indexesGPU = indexesGPU; // TODO: Create a global library for this } const pixelRatio = this.renderer.getPixelRatio(); for (let i = 0, len = cameras.length; i < len; i++) { const subCamera = cameras[i]; if (object.layers.test(subCamera.layers)) { const vp = subCamera.viewport; passEncoderGPU.setViewport(Math.floor(vp.x * pixelRatio), Math.floor(vp.y * pixelRatio), Math.floor(vp.width * pixelRatio), Math.floor(vp.height * pixelRatio), context.viewportValue.minDepth, context.viewportValue.maxDepth); passEncoderGPU.setBindGroup(cameraIndex.index, cameraData.indexesGPU[i]); draw(); } } } else { draw(); } } // cache key /** * Returns `true` if the render pipeline requires an update. * * @param {RenderObject} renderObject - The render object. * @return {boolean} Whether the render pipeline requires an update or not. */ needsRenderUpdate(renderObject) { const data = this.get(renderObject); const { object, material } = renderObject; const utils = this.utils; const sampleCount = utils.getSampleCountRenderContext(renderObject.context); const colorSpace = utils.getCurrentColorSpace(renderObject.context); const colorFormat = utils.getCurrentColorFormat(renderObject.context); const depthStencilFormat = utils.getCurrentDepthStencilFormat(renderObject.context); const primitiveTopology = utils.getPrimitiveTopology(object, material); let needsUpdate = false; if (data.material !== material || data.materialVersion !== material.version || data.transparent !== material.transparent || data.blending !== material.blending || data.premultipliedAlpha !== material.premultipliedAlpha || data.blendSrc !== material.blendSrc || data.blendDst !== material.blendDst || data.blendEquation !== material.blendEquation || data.blendSrcAlpha !== material.blendSrcAlpha || data.blendDstAlpha !== material.blendDstAlpha || data.blendEquationAlpha !== material.blendEquationAlpha || data.colorWrite !== material.colorWrite || data.depthWrite !== material.depthWrite || data.depthTest !== material.depthTest || data.depthFunc !== material.depthFunc || data.stencilWrite !== material.stencilWrite || data.stencilFunc !== material.stencilFunc || data.stencilFail !== material.stencilFail || data.stencilZFail !== material.stencilZFail || data.stencilZPass !== material.stencilZPass || data.stencilFuncMask !== material.stencilFuncMask || data.stencilWriteMask !== material.stencilWriteMask || data.side !== material.side || data.alphaToCoverage !== material.alphaToCoverage || data.sampleCount !== sampleCount || data.colorSpace !== colorSpace || data.colorFormat !== colorFormat || data.depthStencilFormat !== depthStencilFormat || data.primitiveTopology !== primitiveTopology || data.clippingContextCacheKey !== renderObject.clippingContextCacheKey) { data.material = material; data.materialVersion = material.version; data.transparent = material.transparent; data.blending = material.blending; data.premultipliedAlpha = material.premultipliedAlpha; data.blendSrc = material.blendSrc; data.blendDst = material.blendDst; data.blendEquation = material.blendEquation; data.blendSrcAlpha = material.blendSrcAlpha; data.blendDstAlpha = material.blendDstAlpha; data.blendEquationAlpha = material.blendEquationAlpha; data.colorWrite = material.colorWrite; data.depthWrite = material.depthWrite; data.depthTest = material.depthTest; data.depthFunc = material.depthFunc; data.stencilWrite = material.stencilWrite; data.stencilFunc = material.stencilFunc; data.stencilFail = material.stencilFail; data.stencilZFail = material.stencilZFail; data.stencilZPass = material.stencilZPass; data.stencilFuncMask = material.stencilFuncMask; data.stencilWriteMask = material.stencilWriteMask; data.side = material.side; data.alphaToCoverage = material.alphaToCoverage; data.sampleCount = sampleCount; data.colorSpace = colorSpace; data.colorFormat = colorFormat; data.depthStencilFormat = depthStencilFormat; data.primitiveTopology = primitiveTopology; data.clippingContextCacheKey = renderObject.clippingContextCacheKey; needsUpdate = true; } return needsUpdate; } /** * Returns a cache key that is used to identify render pipelines. * * @param {RenderObject} renderObject - The render object. * @return {string} The cache key. */ getRenderCacheKey(renderObject) { const { object, material } = renderObject; const utils = this.utils; const renderContext = renderObject.context; return [material.transparent, material.blending, material.premultipliedAlpha, material.blendSrc, material.blendDst, material.blendEquation, material.blendSrcAlpha, material.blendDstAlpha, material.blendEquationAlpha, material.colorWrite, material.depthWrite, material.depthTest, material.depthFunc, material.stencilWrite, material.stencilFunc, material.stencilFail, material.stencilZFail, material.stencilZPass, material.stencilFuncMask, material.stencilWriteMask, material.side, utils.getSampleCountRenderContext(renderContext), utils.getCurrentColorSpace(renderContext), utils.getCurrentColorFormat(renderContext), utils.getCurrentDepthStencilFormat(renderContext), utils.getPrimitiveTopology(object, material), renderObject.getGeometryCacheKey(), renderObject.clippingContextCacheKey].join(); } // textures /** * Creates a GPU sampler for the given texture. * * @param {Texture} texture - The texture to create the sampler for. */ createSampler(texture) { this.textureUtils.createSampler(texture); } /** * Destroys the GPU sampler for the given texture. * * @param {Texture} texture - The texture to destroy the sampler for. */ destroySampler(texture) { this.textureUtils.destroySampler(texture); } /** * Creates a default texture for the given texture that can be used * as a placeholder until the actual texture is ready for usage. * * @param {Texture} texture - The texture to create a default texture for. */ createDefaultTexture(texture) { this.textureUtils.createDefaultTexture(texture); } /** * Defines a texture on the GPU for the given texture object. * * @param {Texture} texture - The texture. * @param {Object} [options={}] - Optional configuration parameter. */ createTexture(texture, options) { this.textureUtils.createTexture(texture, options); } /** * Uploads the updated texture data to the GPU. * * @param {Texture} texture - The texture. * @param {Object} [options={}] - Optional configuration parameter. */ updateTexture(texture, options) { this.textureUtils.updateTexture(texture, options); } /** * Generates mipmaps for the given texture. * * @param {Texture} texture - The texture. */ generateMipmaps(texture) { this.textureUtils.generateMipmaps(texture); } /** * Destroys the GPU data for the given texture object. * * @param {Texture} texture - The texture. */ destroyTexture(texture) { this.textureUtils.destroyTexture(texture); } /** * Returns texture data as a typed array. * * @async * @param {Texture} texture - The texture to copy. * @param {number} x - The x coordinate of the copy origin. * @param {number} y - The y coordinate of the copy origin. * @param {number} width - The width of the copy. * @param {number} height - The height of the copy. * @param {number} faceIndex - The face index. * @return {Promise<TypedArray>} A Promise that resolves with a typed array when the copy operation has finished. */ async copyTextureToBuffer(texture, x, y, width, height, faceIndex) { return this.textureUtils.copyTextureToBuffer(texture, x, y, width, height, faceIndex); } /** * Inits a time stamp query for the given render context. * * @param {RenderContext} renderContext - The render context. * @param {Object} descriptor - The query descriptor. */ initTimestampQuery(renderContext, descriptor) { if (!this.trackTimestamp) return; const type = renderContext.isComputeNode ? 'compute' : 'render'; if (!this.timestampQueryPool[type]) { // TODO: Variable maxQueries? this.timestampQueryPool[type] = new _WebGPUTimestampQueryPool.default(this.device, type, 2048); } const timestampQueryPool = this.timestampQueryPool[type]; const baseOffset = timestampQueryPool.allocateQueriesForContext(renderContext); descriptor.timestampWrites = { querySet: timestampQueryPool.querySet, beginningOfPassWriteIndex: baseOffset, endOfPassWriteIndex: baseOffset + 1 }; } // node builder /** * Returns a node builder for the given render object. * * @param {RenderObject} object - The render object. * @param {Renderer} renderer - The renderer. * @return {WGSLNodeBuilder} The node builder. */ createNodeBuilder(object, renderer) { return new _WGSLNodeBuilder.default(object, renderer); } // program /** * Creates a shader program from the given programmable stage. * * @param {ProgrammableStage} program - The programmable stage. */ createProgram(program) { const programGPU = this.get(program); programGPU.module = { module: this.device.createShaderModule({ code: program.code, label: program.stage + (program.name !== '' ? `_${program.name}` : '') }), entryPoint: 'main' }; } /** * Destroys the shader program of the given programmable stage. * * @param {ProgrammableStage} program - The programmable stage. */ destroyProgram(program) { this.delete(program); } // pipelines /** * Creates a render pipeline for the given render object. * * @param {RenderObject} renderObject - The render object. * @param {Array<Promise>} promises - An array of compilation promises which are used in `compileAsync()`. */ createRenderPipeline(renderObject, promises) { this.pipelineUtils.createRenderPipeline(renderObject, promises); } /** * Creates a compute pipeline for the given compute node. * * @param {ComputePipeline} computePipeline - The compute pipeline. * @param {Array<BindGroup>} bindings - The bindings. */ createComputePipeline(computePipeline, bindings) { this.pipelineUtils.createComputePipeline(computePipeline, bindings); } /** * Prepares the state for encoding render bundles. * * @param {RenderContext} renderContext - The render context. */ beginBundle(renderContext) { const renderContextData = this.get(renderContext); renderContextData._currentPass = renderContextData.currentPass; renderContextData._currentSets = renderContextData.currentSets; renderContextData.currentSets = { attributes: {}, bindingGroups: [], pipeline: null, index: null }; renderContextData.currentPass = this.pipelineUtils.createBundleEncoder(renderContext); } /** * After processing render bundles this method finalizes related work. * * @param {RenderContext} renderContext - The render context. * @param {RenderBundle} bundle - The render bundle. */ finishBundle(renderContext, bundle) { const renderContextData = this.get(renderContext); const bundleEncoder = renderContextData.currentPass; const bundleGPU = bundleEncoder.finish(); this.get(bundle).bundleGPU = bundleGPU; // restore render pass state renderContextData.currentSets = renderContextData._currentSets; renderContextData.currentPass = renderContextData._currentPass; } /** * Adds a render bundle to the render context data. * * @param {RenderContext} renderContext - The render context. * @param {RenderBundle} bundle - The render bundle to add. */ addBundle(renderContext, bundle) { const renderContextData = this.get(renderContext); renderContextData.renderBundles.push(this.get(bundle).bundleGPU); } // bindings /** * Creates bindings from the given bind group definition. * * @param {BindGroup} bindGroup - The bind group. * @param {Array<BindGroup>} bindings - Array of bind groups. * @param {number} cacheIndex - The cache index. * @param {number} version - The version. */ createBindings(bindGroup, bindings, cacheIndex, version) { this.bindingUtils.createBindings(bindGroup, bindings, cacheIndex, version);