playcanvas
Version:
PlayCanvas WebGL game engine
900 lines (897 loc) • 41.5 kB
JavaScript
import { TRACEID_RENDER_QUEUE } from '../../../core/constants.js';
import { DebugHelper, Debug } from '../../../core/debug.js';
import { BUFFERUSAGE_READ, BUFFERUSAGE_COPY_DST, DEVICETYPE_WEBGPU, UNUSED_UNIFORM_NAME, semanticToLocation, PIXELFORMAT_SRGBA8, PIXELFORMAT_RGBA8, PIXELFORMAT_SBGRA8, PIXELFORMAT_BGRA8, DISPLAYFORMAT_LDR_SRGB, DISPLAYFORMAT_HDR, PIXELFORMAT_RGBA16F } from '../constants.js';
import { BindGroupFormat } from '../bind-group-format.js';
import { BindGroup } from '../bind-group.js';
import { DebugGraphics } from '../debug-graphics.js';
import { GraphicsDevice } from '../graphics-device.js';
import { RenderTarget } from '../render-target.js';
import { StencilParameters } from '../stencil-parameters.js';
import { WebgpuBindGroup } from './webgpu-bind-group.js';
import { WebgpuBindGroupFormat } from './webgpu-bind-group-format.js';
import { WebgpuIndexBuffer } from './webgpu-index-buffer.js';
import { WebgpuRenderPipeline } from './webgpu-render-pipeline.js';
import { WebgpuComputePipeline } from './webgpu-compute-pipeline.js';
import { WebgpuRenderTarget } from './webgpu-render-target.js';
import { WebgpuShader } from './webgpu-shader.js';
import { WebgpuTexture } from './webgpu-texture.js';
import { WebgpuUniformBuffer } from './webgpu-uniform-buffer.js';
import { WebgpuVertexBuffer } from './webgpu-vertex-buffer.js';
import { WebgpuClearRenderer } from './webgpu-clear-renderer.js';
import { WebgpuMipmapRenderer } from './webgpu-mipmap-renderer.js';
import { WebgpuDebug } from './webgpu-debug.js';
import { WebgpuDynamicBuffers } from './webgpu-dynamic-buffers.js';
import { WebgpuGpuProfiler } from './webgpu-gpu-profiler.js';
import { WebgpuResolver } from './webgpu-resolver.js';
import { WebgpuCompute } from './webgpu-compute.js';
import { WebgpuBuffer } from './webgpu-buffer.js';
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _async_to_generator(fn) {
return function() {
var self = this, args = arguments;
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
/**
* @import { BindGroup } from '../bind-group.js'
* @import { RenderPass } from '../render-pass.js'
* @import { WebgpuBuffer } from './webgpu-buffer.js'
*/ var _uniqueLocations = new Map();
class WebgpuGraphicsDevice extends GraphicsDevice {
/**
* Destroy the graphics device.
*/ destroy() {
this.clearRenderer.destroy();
this.clearRenderer = null;
this.mipmapRenderer.destroy();
this.mipmapRenderer = null;
this.resolver.destroy();
this.resolver = null;
super.destroy();
}
initDeviceCaps() {
var _this_wgpu;
var limits = (_this_wgpu = this.wgpu) == null ? void 0 : _this_wgpu.limits;
this.limits = limits;
this.precision = 'highp';
this.maxPrecision = 'highp';
this.maxSamples = 4;
this.maxTextures = 16;
this.maxTextureSize = limits.maxTextureDimension2D;
this.maxCubeMapSize = limits.maxTextureDimension2D;
this.maxVolumeSize = limits.maxTextureDimension3D;
this.maxColorAttachments = limits.maxColorAttachments;
this.maxPixelRatio = 1;
this.maxAnisotropy = 16;
this.fragmentUniformsCount = limits.maxUniformBufferBindingSize / 16;
this.vertexUniformsCount = limits.maxUniformBufferBindingSize / 16;
this.supportsUniformBuffers = true;
this.supportsAreaLights = true;
this.supportsGpuParticles = true;
this.supportsCompute = true;
this.textureFloatRenderable = true;
this.textureHalfFloatRenderable = true;
this.supportsImageBitmap = true;
// WebGPU currently only supports 1 and 4 samples
this.samples = this.backBufferAntialias ? 4 : 1;
// WGSL features
var wgslFeatures = navigator.gpu.wgslLanguageFeatures;
this.supportsStorageTextureRead = wgslFeatures == null ? void 0 : wgslFeatures.has('readonly_and_readwrite_storage_textures');
this.initCapsDefines();
}
initWebGpu(glslangUrl, twgslUrl) {
var _this = this;
return _async_to_generator(function*() {
if (!window.navigator.gpu) {
throw new Error('Unable to retrieve GPU. Ensure you are using a browser that supports WebGPU rendering.');
}
// temporary message to confirm Webgpu is being used
Debug.log('WebgpuGraphicsDevice initialization ..');
// build a full URL from a relative or absolute path
var buildUrl = (srcPath)=>{
return new URL(srcPath, window.location.href).toString();
};
var results = yield Promise.all([
import(/* @vite-ignore */ /* webpackIgnore: true */ "" + buildUrl(twgslUrl)).then((module)=>twgsl(twgslUrl.replace('.js', '.wasm'))),
import(/* @vite-ignore */ /* webpackIgnore: true */ "" + buildUrl(glslangUrl)).then((module)=>module.default())
]);
_this.twgsl = results[0];
_this.glslang = results[1];
// create the device
return _this.createDevice();
})();
}
createDevice() {
var _this = this;
return _async_to_generator(function*() {
var _this_gpuAdapter, // handle lost device
_this_wgpu_lost;
/** @type {GPURequestAdapterOptions} */ var adapterOptions = {
powerPreference: _this.initOptions.powerPreference !== 'default' ? _this.initOptions.powerPreference : undefined
};
/**
* @type {GPUAdapter}
* @private
*/ _this.gpuAdapter = yield window.navigator.gpu.requestAdapter(adapterOptions);
// request optional features
var requiredFeatures = [];
var requireFeature = (feature)=>{
var supported = _this.gpuAdapter.features.has(feature);
if (supported) {
requiredFeatures.push(feature);
}
return supported;
};
_this.textureFloatFilterable = requireFeature('float32-filterable');
_this.textureFloatBlendable = requireFeature('float32-blendable');
_this.extCompressedTextureS3TC = requireFeature('texture-compression-bc');
_this.extCompressedTextureETC = requireFeature('texture-compression-etc2');
_this.extCompressedTextureASTC = requireFeature('texture-compression-astc');
_this.supportsTimestampQuery = requireFeature('timestamp-query');
_this.supportsDepthClip = requireFeature('depth-clip-control');
_this.supportsDepth32Stencil = requireFeature('depth32float-stencil8');
_this.supportsIndirectFirstInstance = requireFeature('indirect-first-instance');
_this.supportsShaderF16 = requireFeature('shader-f16');
_this.supportsStorageRGBA8 = requireFeature('bgra8unorm-storage');
_this.textureRG11B10Renderable = requireFeature('rg11b10ufloat-renderable');
_this.supportsClipDistances = requireFeature('clip-distances');
Debug.log("WEBGPU features: " + requiredFeatures.join(', '));
// copy all adapter limits to the requiredLimits object - to created a device with the best feature sets available
var adapterLimits = (_this_gpuAdapter = _this.gpuAdapter) == null ? void 0 : _this_gpuAdapter.limits;
var requiredLimits = {};
if (adapterLimits) {
for(var limitName in adapterLimits){
// skip these as they fail on Windows Chrome and are not part of spec currently
if (limitName === 'minSubgroupSize' || limitName === 'maxSubgroupSize') {
continue;
}
requiredLimits[limitName] = adapterLimits[limitName];
}
}
/** @type {GPUDeviceDescriptor} */ var deviceDescr = {
requiredFeatures,
requiredLimits,
defaultQueue: {
label: 'Default Queue'
}
};
DebugHelper.setLabel(deviceDescr, 'PlayCanvasWebGPUDevice');
/**
* @type {GPUDevice}
* @private
*/ _this.wgpu = yield _this.gpuAdapter.requestDevice(deviceDescr);
(_this_wgpu_lost = _this.wgpu.lost) == null ? void 0 : _this_wgpu_lost.then(_this.handleDeviceLost.bind(_this));
_this.initDeviceCaps();
_this.gpuContext = _this.canvas.getContext('webgpu');
// tonemapping, used when the backbuffer is HDR
var canvasToneMapping = 'standard';
// pixel format of the framebuffer that is the most efficient one on the system
var preferredCanvasFormat = navigator.gpu.getPreferredCanvasFormat();
// display format the user asked for
var displayFormat = _this.initOptions.displayFormat;
// combine requested display format with the preferred format
_this.backBufferFormat = preferredCanvasFormat === 'rgba8unorm' ? displayFormat === DISPLAYFORMAT_LDR_SRGB ? PIXELFORMAT_SRGBA8 : PIXELFORMAT_RGBA8 : displayFormat === DISPLAYFORMAT_LDR_SRGB ? PIXELFORMAT_SBGRA8 : PIXELFORMAT_BGRA8; // (S)BGRA
// view format for the backbuffer. Backbuffer is always allocated without srgb conversion, and
// the view we create specifies srgb is needed to handle the conversion.
_this.backBufferViewFormat = displayFormat === DISPLAYFORMAT_LDR_SRGB ? "" + preferredCanvasFormat + "-srgb" : preferredCanvasFormat;
// optional HDR display format
if (displayFormat === DISPLAYFORMAT_HDR && _this.textureFloatFilterable) {
// if supported by the system
var hdrMediaQuery = window.matchMedia('(dynamic-range: high)');
if (hdrMediaQuery == null ? void 0 : hdrMediaQuery.matches) {
// configure the backbuffer to be 16 bit float
_this.backBufferFormat = PIXELFORMAT_RGBA16F;
_this.backBufferViewFormat = 'rgba16float';
preferredCanvasFormat = 'rgba16float';
_this.isHdr = true;
// use extended tonemapping for HDR to avoid clipping
canvasToneMapping = 'extended';
}
}
/**
* Configuration of the main colorframebuffer we obtain using getCurrentTexture
*
* @type {GPUCanvasConfiguration}
* @private
*/ _this.canvasConfig = {
device: _this.wgpu,
colorSpace: 'srgb',
alphaMode: _this.initOptions.alpha ? 'premultiplied' : 'opaque',
// use preferred format for optimal performance on mobile
format: preferredCanvasFormat,
toneMapping: {
mode: canvasToneMapping
},
// RENDER_ATTACHMENT is required, COPY_SRC allows scene grab to copy out from it
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
// formats that views created from textures returned by getCurrentTexture may use
// (this allows us to view the preferred format as srgb)
viewFormats: displayFormat === DISPLAYFORMAT_LDR_SRGB ? [
_this.backBufferViewFormat
] : []
};
_this.gpuContext.configure(_this.canvasConfig);
_this.createBackbuffer();
_this.clearRenderer = new WebgpuClearRenderer(_this);
_this.mipmapRenderer = new WebgpuMipmapRenderer(_this);
_this.resolver = new WebgpuResolver(_this);
_this.postInit();
return _this;
})();
}
handleDeviceLost(info) {
var _this = this, _superprop_get_loseContext = ()=>super.loseContext, _superprop_get_restoreContext = ()=>super.restoreContext;
return _async_to_generator(function*() {
// reason is 'destroyed' if we intentionally destroy the device
if (info.reason !== 'destroyed') {
Debug.warn("WebGPU device was lost: " + info.message + ", this needs to be handled");
_superprop_get_loseContext().call(_this); // 'super' works correctly here
yield _this.createDevice(); // Ensure this method is defined in your class
_superprop_get_restoreContext().call(_this); // 'super' works correctly here
}
})();
}
postInit() {
super.postInit();
this.initializeRenderState();
this.setupPassEncoderDefaults();
this.gpuProfiler = new WebgpuGpuProfiler(this);
// init dynamic buffer using 100kB allocation
this.dynamicBuffers = new WebgpuDynamicBuffers(this, 100 * 1024, this.limits.minUniformBufferOffsetAlignment);
// empty bind group
this.emptyBindGroup = new BindGroup(this, new BindGroupFormat(this, []));
this.emptyBindGroup.update();
}
createBackbuffer() {
this.supportsStencil = this.initOptions.stencil;
this.backBuffer = new RenderTarget({
name: 'WebgpuFramebuffer',
graphicsDevice: this,
depth: this.initOptions.depth,
stencil: this.supportsStencil,
samples: this.samples
});
this.backBuffer.impl.isBackbuffer = true;
}
frameStart() {
super.frameStart();
this.gpuProfiler.frameStart();
// submit any commands collected before the frame rendering
this.submit();
WebgpuDebug.memory(this);
WebgpuDebug.validate(this);
// current frame color output buffer
var outColorBuffer = this.gpuContext.getCurrentTexture();
DebugHelper.setLabel(outColorBuffer, "" + this.backBuffer.name);
// reallocate framebuffer if dimensions change, to match the output texture
if (this.backBufferSize.x !== outColorBuffer.width || this.backBufferSize.y !== outColorBuffer.height) {
this.backBufferSize.set(outColorBuffer.width, outColorBuffer.height);
this.backBuffer.destroy();
this.backBuffer = null;
this.createBackbuffer();
}
var rt = this.backBuffer;
var wrt = rt.impl;
// assign the format, allowing following init call to use it to allocate matching multisampled buffer
wrt.setColorAttachment(0, undefined, this.backBufferViewFormat);
this.initRenderTarget(rt);
// assign current frame's render texture
wrt.assignColorTexture(this, outColorBuffer);
WebgpuDebug.end(this, 'frameStart');
WebgpuDebug.end(this, 'frameStart');
}
frameEnd() {
super.frameEnd();
this.gpuProfiler.frameEnd();
// submit scheduled command buffers
this.submit();
if (!this.contextLost) {
this.gpuProfiler.request();
}
}
createBufferImpl(usageFlags) {
return new WebgpuBuffer(usageFlags);
}
createUniformBufferImpl(uniformBuffer) {
return new WebgpuUniformBuffer(uniformBuffer);
}
createVertexBufferImpl(vertexBuffer, format, options) {
return new WebgpuVertexBuffer(vertexBuffer, format, options);
}
createIndexBufferImpl(indexBuffer, options) {
return new WebgpuIndexBuffer(indexBuffer, options);
}
createShaderImpl(shader) {
return new WebgpuShader(shader);
}
createTextureImpl(texture) {
return new WebgpuTexture(texture);
}
createRenderTargetImpl(renderTarget) {
return new WebgpuRenderTarget(renderTarget);
}
createBindGroupFormatImpl(bindGroupFormat) {
return new WebgpuBindGroupFormat(bindGroupFormat);
}
createBindGroupImpl(bindGroup) {
return new WebgpuBindGroup();
}
createComputeImpl(compute) {
return new WebgpuCompute(compute);
}
/**
* @param {number} index - Index of the bind group slot
* @param {BindGroup} bindGroup - Bind group to attach
* @param {number[]} [offsets] - Byte offsets for all uniform buffers in the bind group.
*/ setBindGroup(index, bindGroup, offsets) {
// TODO: this condition should be removed, it's here to handle fake grab pass, which should be refactored instead
if (this.passEncoder) {
// set it on the device
this.passEncoder.setBindGroup(index, bindGroup.impl.bindGroup, offsets != null ? offsets : bindGroup.uniformBufferOffsets);
// store the active formats, used by the pipeline creation
this.bindGroupFormats[index] = bindGroup.format.impl;
}
}
submitVertexBuffer(vertexBuffer, slot) {
var format = vertexBuffer.format;
var { interleaved, elements } = format;
var elementCount = elements.length;
var vbBuffer = vertexBuffer.impl.buffer;
if (interleaved) {
// for interleaved buffers, we use a single vertex buffer, and attributes are specified using the layout
this.passEncoder.setVertexBuffer(slot, vbBuffer);
return 1;
}
// non-interleaved - vertex buffer per attribute
for(var i = 0; i < elementCount; i++){
this.passEncoder.setVertexBuffer(slot + i, vbBuffer, elements[i].offset);
}
return elementCount;
}
validateVBLocations(vb0, vb1) {
// in case of multiple VBs, validate all elements use unique locations
var validateVB = (vb)=>{
var { elements } = vb.format;
for(var i = 0; i < elements.length; i++){
var name = elements[i].name;
var location = semanticToLocation[name];
if (_uniqueLocations.has(location)) {
Debug.errorOnce("Vertex buffer element location " + location + " used by [" + name + "] is already used by element [" + _uniqueLocations.get(location) + "], while rendering [" + DebugGraphics.toString() + "]");
}
_uniqueLocations.set(location, name);
}
};
validateVB(vb0);
validateVB(vb1);
_uniqueLocations.clear();
}
draw(primitive, numInstances, keepBuffers) {
if (numInstances === void 0) numInstances = 1;
if (this.shader.ready && !this.shader.failed) {
WebgpuDebug.validate(this);
var passEncoder = this.passEncoder;
Debug.assert(passEncoder);
// vertex buffers
var vb0 = this.vertexBuffers[0];
var vb1 = this.vertexBuffers[1];
if (vb0) {
var vbSlot = this.submitVertexBuffer(vb0, 0);
if (vb1) {
Debug.call(()=>this.validateVBLocations(vb0, vb1));
this.submitVertexBuffer(vb1, vbSlot);
}
}
Debug.call(()=>this.validateAttributes(this.shader, vb0 == null ? void 0 : vb0.format, vb1 == null ? void 0 : vb1.format));
// render pipeline
var pipeline = this.renderPipeline.get(primitive, vb0 == null ? void 0 : vb0.format, vb1 == null ? void 0 : vb1.format, this.shader, this.renderTarget, this.bindGroupFormats, this.blendState, this.depthState, this.cullMode, this.stencilEnabled, this.stencilFront, this.stencilBack);
Debug.assert(pipeline);
if (this.pipeline !== pipeline) {
this.pipeline = pipeline;
passEncoder.setPipeline(pipeline);
}
// draw
var ib = this.indexBuffer;
if (ib) {
passEncoder.setIndexBuffer(ib.impl.buffer, ib.impl.format);
passEncoder.drawIndexed(primitive.count, numInstances, primitive.base, 0, 0);
} else {
passEncoder.draw(primitive.count, numInstances, primitive.base, 0);
}
WebgpuDebug.end(this, 'Drawing', {
vb0,
vb1,
ib,
primitive,
numInstances,
pipeline
});
}
this.vertexBuffers.length = 0;
this.indexBuffer = null;
}
setShader(shader, asyncCompile) {
if (shader !== this.shader) {
this.shader = shader;
// TODO: we should probably track other stats instead, like pipeline switches
this._shaderSwitchesPerFrame++;
}
}
setBlendState(blendState) {
this.blendState.copy(blendState);
}
setDepthState(depthState) {
this.depthState.copy(depthState);
}
setStencilState(stencilFront, stencilBack) {
if (stencilFront || stencilBack) {
this.stencilEnabled = true;
this.stencilFront.copy(stencilFront != null ? stencilFront : StencilParameters.DEFAULT);
this.stencilBack.copy(stencilBack != null ? stencilBack : StencilParameters.DEFAULT);
// ref value - based on stencil front
var ref = this.stencilFront.ref;
if (this.stencilRef !== ref) {
this.stencilRef = ref;
this.passEncoder.setStencilReference(ref);
}
} else {
this.stencilEnabled = false;
}
}
setBlendColor(r, g, b, a) {
var c = this.blendColor;
if (r !== c.r || g !== c.g || b !== c.b || a !== c.a) {
c.set(r, g, b, a);
this.passEncoder.setBlendConstant(c);
}
}
setCullMode(cullMode) {
this.cullMode = cullMode;
}
setAlphaToCoverage(state) {}
initializeContextCaches() {
super.initializeContextCaches();
}
/**
* Set up default values for the render pass encoder.
*/ setupPassEncoderDefaults() {
this.pipeline = null;
this.stencilRef = 0;
this.blendColor.set(0, 0, 0, 0);
}
_uploadDirtyTextures() {
this.textures.forEach((texture)=>{
if (texture._needsUpload || texture._needsMipmaps) {
texture.upload();
}
});
}
setupTimeStampWrites(passDesc, name) {
if (this.gpuProfiler._enabled) {
if (this.gpuProfiler.timestampQueriesSet) {
var slot = this.gpuProfiler.getSlot(name);
passDesc = passDesc != null ? passDesc : {};
passDesc.timestampWrites = {
querySet: this.gpuProfiler.timestampQueriesSet.querySet,
beginningOfPassWriteIndex: slot * 2,
endOfPassWriteIndex: slot * 2 + 1
};
}
}
return passDesc;
}
/**
* Start a render pass.
*
* @param {RenderPass} renderPass - The render pass to start.
* @ignore
*/ startRenderPass(renderPass) {
// upload textures that need it, to avoid them being uploaded / their mips generated during the pass
// TODO: this needs a better solution
this._uploadDirtyTextures();
WebgpuDebug.internal(this);
WebgpuDebug.validate(this);
var rt = renderPass.renderTarget || this.backBuffer;
this.renderTarget = rt;
Debug.assert(rt);
/** @type {WebgpuRenderTarget} */ var wrt = rt.impl;
// framebuffer is initialized at the start of the frame
if (rt !== this.backBuffer) {
this.initRenderTarget(rt);
}
// set up clear / store / load settings
wrt.setupForRenderPass(renderPass, rt);
var renderPassDesc = wrt.renderPassDescriptor;
// timestamp
this.setupTimeStampWrites(renderPassDesc, renderPass.name);
// start the pass
var commandEncoder = this.getCommandEncoder();
this.passEncoder = commandEncoder.beginRenderPass(renderPassDesc);
this.passEncoder.label = renderPass.name + "-PassEncoder RT:" + rt.name;
// push marker to the passEncoder
DebugGraphics.pushGpuMarker(this, "Pass:" + renderPass.name + " RT:" + rt.name);
this.setupPassEncoderDefaults();
// the pass always clears full target
// TODO: avoid this setting the actual viewport/scissor on webgpu as those are automatically reset to full
// render target. We just need to update internal state, for the get functionality to return it.
var { width, height } = rt;
this.setViewport(0, 0, width, height);
this.setScissor(0, 0, width, height);
Debug.assert(!this.insideRenderPass, 'RenderPass cannot be started while inside another render pass.');
this.insideRenderPass = true;
}
/**
* End a render pass.
*
* @param {RenderPass} renderPass - The render pass to end.
* @ignore
*/ endRenderPass(renderPass) {
// pop the marker from the passEncoder
DebugGraphics.popGpuMarker(this);
// end the render pass
this.passEncoder.end();
this.passEncoder = null;
this.insideRenderPass = false;
// each render pass can use different number of bind groups
this.bindGroupFormats.length = 0;
// resolve depth if needed after the pass has finished
var target = this.renderTarget;
if (target) {
// resolve depth buffer (stencil resolve is not yet implemented)
if (target.depthBuffer && renderPass.depthStencilOps.resolveDepth) {
if (renderPass.samples > 1 && target.autoResolve) {
var depthAttachment = target.impl.depthAttachment;
var destTexture = target.depthBuffer.impl.gpuTexture;
if (depthAttachment && destTexture) {
this.resolver.resolveDepth(this.commandEncoder, depthAttachment.multisampledDepthBuffer, destTexture);
}
}
}
}
// generate mipmaps using the same command buffer encoder
for(var i = 0; i < renderPass.colorArrayOps.length; i++){
var colorOps = renderPass.colorArrayOps[i];
if (colorOps.genMipmaps) {
this.mipmapRenderer.generate(renderPass.renderTarget._colorBuffers[i].impl);
}
}
WebgpuDebug.end(this, 'RenderPass', {
renderPass
});
WebgpuDebug.end(this, 'RenderPass', {
renderPass
});
}
startComputePass(name) {
WebgpuDebug.internal(this);
WebgpuDebug.validate(this);
// clear cached encoder state
this.pipeline = null;
// timestamp
var computePassDesc = this.setupTimeStampWrites(undefined, name);
// start the pass
var commandEncoder = this.getCommandEncoder();
this.passEncoder = commandEncoder.beginComputePass(computePassDesc);
DebugHelper.setLabel(this.passEncoder, "ComputePass-" + name);
Debug.assert(!this.insideRenderPass, 'ComputePass cannot be started while inside another pass.');
this.insideRenderPass = true;
}
endComputePass() {
// end the compute pass
this.passEncoder.end();
this.passEncoder = null;
this.insideRenderPass = false;
// each render pass can use different number of bind groups
this.bindGroupFormats.length = 0;
WebgpuDebug.end(this, 'ComputePass');
WebgpuDebug.end(this, 'ComputePass');
}
computeDispatch(computes, name) {
if (name === void 0) name = 'Unnamed';
this.startComputePass(name);
// update uniform buffers and bind groups
for(var i = 0; i < computes.length; i++){
var compute = computes[i];
compute.applyParameters();
compute.impl.updateBindGroup();
}
// dispatch
for(var i1 = 0; i1 < computes.length; i1++){
var compute1 = computes[i1];
compute1.impl.dispatch(compute1.countX, compute1.countY, compute1.countZ);
}
this.endComputePass();
}
getCommandEncoder() {
// use existing or create new encoder
var commandEncoder = this.commandEncoder;
if (!commandEncoder) {
commandEncoder = this.wgpu.createCommandEncoder();
DebugHelper.setLabel(commandEncoder, 'CommandEncoder-Shared');
this.commandEncoder = commandEncoder;
}
return commandEncoder;
}
endCommandEncoder() {
var { commandEncoder } = this;
if (commandEncoder) {
var cb = commandEncoder.finish();
DebugHelper.setLabel(cb, 'CommandBuffer-Shared');
this.addCommandBuffer(cb);
this.commandEncoder = null;
}
}
addCommandBuffer(commandBuffer, front) {
if (front === void 0) front = false;
if (front) {
this.commandBuffers.unshift(commandBuffer);
} else {
this.commandBuffers.push(commandBuffer);
}
}
submit() {
// end the current encoder
this.endCommandEncoder();
if (this.commandBuffers.length > 0) {
// copy dynamic buffers data to the GPU (this schedules the copy CB to run before all other CBs)
this.dynamicBuffers.submit();
// trace all scheduled command buffers
Debug.call(()=>{
if (this.commandBuffers.length > 0) {
Debug.trace(TRACEID_RENDER_QUEUE, "SUBMIT (" + this.commandBuffers.length + ")");
for(var i = 0; i < this.commandBuffers.length; i++){
Debug.trace(TRACEID_RENDER_QUEUE, " CB: " + this.commandBuffers[i].label);
}
}
});
this.wgpu.queue.submit(this.commandBuffers);
this.commandBuffers.length = 0;
// notify dynamic buffers
this.dynamicBuffers.onCommandBuffersSubmitted();
}
}
clear(options) {
if (options.flags) {
this.clearRenderer.clear(this, this.renderTarget, options, this.defaultClearOptions);
}
}
setViewport(x, y, w, h) {
// TODO: only execute when it changes. Also, the viewport of encoder matches the rendering attachments,
// so we can skip this if fullscreen
// TODO: this condition should be removed, it's here to handle fake grab pass, which should be refactored instead
if (this.passEncoder) {
if (!this.renderTarget.flipY) {
y = this.renderTarget.height - y - h;
}
this.vx = x;
this.vy = y;
this.vw = w;
this.vh = h;
this.passEncoder.setViewport(x, y, w, h, 0, 1);
}
}
setScissor(x, y, w, h) {
// TODO: only execute when it changes. Also, the viewport of encoder matches the rendering attachments,
// so we can skip this if fullscreen
// TODO: this condition should be removed, it's here to handle fake grab pass, which should be refactored instead
if (this.passEncoder) {
if (!this.renderTarget.flipY) {
y = this.renderTarget.height - y - h;
}
this.sx = x;
this.sy = y;
this.sw = w;
this.sh = h;
this.passEncoder.setScissorRect(x, y, w, h);
}
}
/**
* Clear the content of a storage buffer to 0.
*
* @param {WebgpuBuffer} storageBuffer - The storage buffer.
* @param {number} [offset] - The offset of data to clear. Defaults to 0.
* @param {number} [size] - The size of data to clear. Defaults to the full size of the buffer.
* @ignore
*/ clearStorageBuffer(storageBuffer, offset, size) {
if (offset === void 0) offset = 0;
if (size === void 0) size = storageBuffer.byteSize;
var commandEncoder = this.getCommandEncoder();
commandEncoder.clearBuffer(storageBuffer.buffer, offset, size);
}
/**
* Read a content of a storage buffer.
*
* @param {WebgpuBuffer} storageBuffer - The storage buffer.
* @param {number} [offset] - The byte offset of data to read. Defaults to 0.
* @param {number} [size] - The byte size of data to read. Defaults to the full size of the
* buffer minus the offset.
* @param {ArrayBufferView} [data] - Typed array to populate with the data read from the storage
* buffer. When typed array is supplied, enough space needs to be reserved, otherwise only
* partial data is copied. If not specified, the data is returned in an Uint8Array. Defaults to
* null.
* @param {boolean} [immediate] - If true, the read operation will be executed as soon as
* possible. This has a performance impact, so it should be used only when necessary. Defaults
* to false.
* @returns {Promise<ArrayBufferView>} A promise that resolves with the data read from the storage
* buffer.
* @ignore
*/ readStorageBuffer(storageBuffer, offset, size, data, immediate) {
if (offset === void 0) offset = 0;
if (size === void 0) size = storageBuffer.byteSize - offset;
if (data === void 0) data = null;
if (immediate === void 0) immediate = false;
// create a temporary staging buffer
var stagingBuffer = this.createBufferImpl(BUFFERUSAGE_READ | BUFFERUSAGE_COPY_DST);
stagingBuffer.allocate(this, size);
var destBuffer = stagingBuffer.buffer;
// copy the GPU buffer to the staging buffer
var commandEncoder = this.getCommandEncoder();
commandEncoder.copyBufferToBuffer(storageBuffer.buffer, offset, destBuffer, 0, size);
return this.readBuffer(stagingBuffer, size, data, immediate);
}
readBuffer(stagingBuffer, size, data, immediate) {
if (data === void 0) data = null;
if (immediate === void 0) immediate = false;
var destBuffer = stagingBuffer.buffer;
// return a promise that resolves with the data
return new Promise((resolve, reject)=>{
var read = ()=>{
destBuffer == null ? void 0 : destBuffer.mapAsync(GPUMapMode.READ).then(()=>{
// copy data to a buffer
data != null ? data : data = new Uint8Array(size);
var copySrc = destBuffer.getMappedRange(0, size);
// use the same type as the target
var srcType = data.constructor;
data.set(new srcType(copySrc));
// release staging buffer
destBuffer.unmap();
stagingBuffer.destroy(this);
resolve(data);
});
};
if (immediate) {
// submit the command buffer immediately
this.submit();
read();
} else {
// map the buffer during the next event handling cycle, when the command buffer is submitted
setTimeout(()=>{
read();
});
}
});
}
/**
* Issues a write operation of the provided data into a storage buffer.
*
* @param {WebgpuBuffer} storageBuffer - The storage buffer.
* @param {number} bufferOffset - The offset in bytes to start writing to the storage buffer.
* @param {ArrayBufferView} data - The data to write to the storage buffer.
* @param {number} dataOffset - Offset in data to begin writing from. Given in elements if data
* is a TypedArray and bytes otherwise.
* @param {number} size - Size of content to write from data to buffer. Given in elements if
* data is a TypedArray and bytes otherwise.
*/ writeStorageBuffer(storageBuffer, bufferOffset, data, dataOffset, size) {
if (bufferOffset === void 0) bufferOffset = 0;
if (dataOffset === void 0) dataOffset = 0;
Debug.assert(storageBuffer.buffer);
Debug.assert(data);
this.wgpu.queue.writeBuffer(storageBuffer.buffer, bufferOffset, data, dataOffset, size);
}
/**
* Copies source render target into destination render target. Mostly used by post-effects.
*
* @param {RenderTarget} [source] - The source render target. Defaults to frame buffer.
* @param {RenderTarget} [dest] - The destination render target. Defaults to frame buffer.
* @param {boolean} [color] - If true, will copy the color buffer. Defaults to false.
* @param {boolean} [depth] - If true, will copy the depth buffer. Defaults to false.
* @returns {boolean} True if the copy was successful, false otherwise.
*/ copyRenderTarget(source, dest, color, depth) {
/** @type {GPUExtent3D} */ var copySize = {
width: source ? source.width : dest.width,
height: source ? source.height : dest.height,
depthOrArrayLayers: 1
};
var commandEncoder = this.getCommandEncoder();
DebugGraphics.pushGpuMarker(this, 'COPY-RT');
if (color) {
// read from supplied render target, or from the framebuffer
/** @type {GPUImageCopyTexture} */ var copySrc = {
texture: source ? source.colorBuffer.impl.gpuTexture : this.backBuffer.impl.assignedColorTexture,
mipLevel: 0
};
// write to supplied render target, or to the framebuffer
/** @type {GPUImageCopyTexture} */ var copyDst = {
texture: dest ? dest.colorBuffer.impl.gpuTexture : this.backBuffer.impl.assignedColorTexture,
mipLevel: 0
};
Debug.assert(copySrc.texture !== null && copyDst.texture !== null);
commandEncoder.copyTextureToTexture(copySrc, copyDst, copySize);
}
if (depth) {
// read from supplied render target, or from the framebuffer
var sourceRT = source ? source : this.renderTarget;
var sourceTexture = sourceRT.impl.depthAttachment.depthTexture;
if (source.samples > 1) {
// resolve the depth to a color buffer of destination render target
var destTexture = dest.colorBuffer.impl.gpuTexture;
this.resolver.resolveDepth(commandEncoder, sourceTexture, destTexture);
} else {
// write to supplied render target, or to the framebuffer
var destTexture1 = dest ? dest.depthBuffer.impl.gpuTexture : this.renderTarget.impl.depthAttachment.depthTexture;
/** @type {GPUImageCopyTexture} */ var copySrc1 = {
texture: sourceTexture,
mipLevel: 0
};
/** @type {GPUImageCopyTexture} */ var copyDst1 = {
texture: destTexture1,
mipLevel: 0
};
Debug.assert(copySrc1.texture !== null && copyDst1.texture !== null);
commandEncoder.copyTextureToTexture(copySrc1, copyDst1, copySize);
}
}
DebugGraphics.popGpuMarker(this);
return true;
}
pushMarker(name) {
var _this_passEncoder;
(_this_passEncoder = this.passEncoder) == null ? void 0 : _this_passEncoder.pushDebugGroup(name);
}
popMarker() {
var _this_passEncoder;
(_this_passEncoder = this.passEncoder) == null ? void 0 : _this_passEncoder.popDebugGroup();
}
constructor(canvas, options = {}){
super(canvas, options), /**
* Object responsible for caching and creation of render pipelines.
*/ this.renderPipeline = new WebgpuRenderPipeline(this), /**
* Object responsible for caching and creation of compute pipelines.
*/ this.computePipeline = new WebgpuComputePipeline(this), /**
* An array of bind group formats, based on currently assigned bind groups
*
* @type {WebgpuBindGroupFormat[]}
*/ this.bindGroupFormats = [], /**
* Current command buffer encoder.
*
* @type {GPUCommandEncoder|null}
* @private
*/ this.commandEncoder = null, /**
* Command buffers scheduled for execution on the GPU.
*
* @type {GPUCommandBuffer[]}
* @private
*/ this.commandBuffers = [];
options = this.initOptions;
var _options_alpha;
// alpha defaults to true
options.alpha = (_options_alpha = options.alpha) != null ? _options_alpha : true;
var _options_antialias;
this.backBufferAntialias = (_options_antialias = options.antialias) != null ? _options_antialias : false;
this.isWebGPU = true;
this._deviceType = DEVICETYPE_WEBGPU;
this.scope.resolve(UNUSED_UNIFORM_NAME).setValue(0);
}
}
export { WebgpuGraphicsDevice };