playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
1,292 lines (1,291 loc) • 45.7 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import { version } from "../../core/core.js";
import { Debug } from "../../core/debug.js";
import { EventHandler } from "../../core/event-handler.js";
import { platform } from "../../core/platform.js";
import { now } from "../../core/time.js";
import { Vec2 } from "../../core/math/vec2.js";
import { Tracing } from "../../core/tracing.js";
import { Color } from "../../core/math/color.js";
import { TRACEID_BUFFERS, TRACEID_TEXTURES } from "../../core/constants.js";
import {
BUFFER_STATIC,
CULLFACE_BACK,
CULLFACE_NONE,
CLEARFLAG_COLOR,
CLEARFLAG_DEPTH,
INDEXFORMAT_UINT16,
PRIMITIVE_POINTS,
PRIMITIVE_TRIFAN,
SEMANTIC_POSITION,
TYPE_FLOAT32,
PIXELFORMAT_111110F,
PIXELFORMAT_RGBA16F,
PIXELFORMAT_RGBA32F,
DISPLAYFORMAT_LDR,
semanticToLocation,
FRONTFACE_CCW
} from "./constants.js";
import { BlendState } from "./blend-state.js";
import { DepthState } from "./depth-state.js";
import { IndexBuffer } from "./index-buffer.js";
import { ScopeSpace } from "./scope-space.js";
import { VertexBuffer } from "./vertex-buffer.js";
import { VertexFormat } from "./vertex-format.js";
import { StencilParameters } from "./stencil-parameters.js";
import { DebugGraphics } from "./debug-graphics.js";
import { StorageBuffer } from "./storage-buffer.js";
const _tempSet = /* @__PURE__ */ new Set();
const _GraphicsDevice = class _GraphicsDevice extends EventHandler {
constructor(canvas, options) {
var _a, _b, _c, _d, _e, _f, _g;
super();
/**
* Fired when the canvas is resized. The handler is passed the new width and height as number
* parameters.
*
* @event
* @example
* graphicsDevice.on('resizecanvas', (width, height) => {
* console.log(`The canvas was resized to ${width}x${height}`);
* });
*/
/**
* The canvas DOM element that provides the underlying WebGL context used by the graphics device.
*
* @type {HTMLCanvasElement}
* @readonly
*/
__publicField(this, "canvas");
/**
* The render target representing the main back-buffer.
*
* @type {RenderTarget|null}
* @ignore
*/
__publicField(this, "backBuffer", null);
/**
* The dimensions of the back buffer.
*
* @ignore
*/
__publicField(this, "backBufferSize", new Vec2());
/**
* The pixel format of the back buffer. Typically PIXELFORMAT_RGBA8, PIXELFORMAT_BGRA8 or
* PIXELFORMAT_RGB8.
*
* @ignore
*/
__publicField(this, "backBufferFormat");
/**
* True if the back buffer should use anti-aliasing.
*/
__publicField(this, "backBufferAntialias", false);
/**
* True if the deviceType is WebGPU
*
* @type {boolean}
* @readonly
*/
__publicField(this, "isWebGPU", false);
/**
* True if the deviceType is WebGL2
*
* @type {boolean}
* @readonly
*/
__publicField(this, "isWebGL2", false);
/**
* True if the deviceType is Null
*
* @type {boolean}
* @readonly
*/
__publicField(this, "isNull", false);
/**
* True if the back-buffer is using HDR format, which means that the browser will display the
* rendered images in high dynamic range mode. This is true if the options.displayFormat is set
* to {@link DISPLAYFORMAT_HDR} when creating the graphics device using
* {@link createGraphicsDevice}, and HDR is supported by the device.
*/
__publicField(this, "isHdr", false);
/**
* The scope namespace for shader attributes and variables.
*
* @type {ScopeSpace}
* @readonly
*/
__publicField(this, "scope");
/**
* The maximum number of indirect draw calls that can be used within a single frame. Used on
* WebGPU only. This needs to be adjusted based on the maximum number of draw calls that can
* be used within a single frame. Defaults to 1024.
*/
__publicField(this, "maxIndirectDrawCount", 1024);
/**
* The maximum number of indirect compute dispatches that can be used within a single frame.
* Used on WebGPU only. Defaults to 256.
*/
__publicField(this, "maxIndirectDispatchCount", 256);
/**
* The maximum supported texture anisotropy setting.
*
* @type {number}
* @readonly
*/
__publicField(this, "maxAnisotropy");
/**
* The maximum supported dimension of a cube map.
*
* @type {number}
* @readonly
*/
__publicField(this, "maxCubeMapSize");
/**
* The maximum supported dimension of a texture.
*
* @type {number}
* @readonly
*/
__publicField(this, "maxTextureSize");
/**
* The maximum supported dimension of a 3D texture (any axis).
*
* @type {number}
* @readonly
*/
__publicField(this, "maxVolumeSize");
/**
* The maximum supported number of color buffers attached to a render target.
*
* @type {number}
* @readonly
*/
__publicField(this, "maxColorAttachments", 1);
/**
* The highest shader precision supported by this graphics device. Can be 'hiphp', 'mediump' or
* 'lowp'.
*
* @type {string}
* @readonly
*/
__publicField(this, "precision");
/**
* The number of hardware anti-aliasing samples used by the frame buffer.
*
* @readonly
* @type {number}
*/
__publicField(this, "samples");
/**
* The maximum supported number of hardware anti-aliasing samples.
*
* @readonly
* @type {number}
*/
__publicField(this, "maxSamples", 1);
/**
* True if the main framebuffer contains stencil attachment.
*
* @ignore
* @type {boolean}
*/
__publicField(this, "supportsStencil");
/**
* True if the device supports multi-draw. This is always supported on WebGPU, and support on
* WebGL2 is optional, but pretty common.
*/
__publicField(this, "supportsMultiDraw", true);
/**
* True if the device supports compute shaders.
*
* @readonly
* @type {boolean}
*/
__publicField(this, "supportsCompute", false);
/**
* True if the device can read from StorageTexture in the compute shader. By default, the
* storage texture can be only used with the write operation.
* When a shader uses this feature, add a `requires` directive to signal non-portability at the
* top of the WGSL shader code. The shader define `CAPS_STORAGE_TEXTURE_READ` is set when this
* capability is available.
* ```wgsl
* requires readonly_and_readwrite_storage_textures;
* ```
*
* @readonly
* @type {boolean}
*/
__publicField(this, "supportsStorageTextureRead", false);
/**
* True if the device supports subgroup operations in shaders (WebGPU only). When supported,
* compute and fragment shaders can use WGSL subgroup builtins such as `subgroupBroadcast`,
* `subgroupAll`, `subgroupAny`, `subgroupAdd`, `subgroupShuffle`, etc. The `enable subgroups;`
* directive is automatically injected into WGSL shaders when this feature is available.
*
* @type {boolean}
* @readonly
*/
__publicField(this, "supportsSubgroups", false);
/**
* True if the device supports the WGSL subgroup_uniformity extension, which allows
* subgroup functionality to be considered uniform in more cases during shader compilation.
* This is automatically enabled via the `enable subgroups;` directive when
* {@link supportsSubgroups} is true.
*
* @readonly
* @type {boolean}
*/
__publicField(this, "supportsSubgroupUniformity", false);
/**
* True if the device supports the WGSL subgroup_id extension, which provides access to
* `subgroup_id` and `num_subgroups` built-in values in workgroups. The `requires subgroup_id;`
* directive is automatically injected into WGSL shaders when this feature is available.
*
* @type {boolean}
* @readonly
*/
__publicField(this, "supportsSubgroupId", false);
/**
* True if the device supports the WGSL `linear_indexing` extension, which provides the
* `global_invocation_index` and `workgroup_index` built-in values in compute shaders. The
* `requires linear_indexing;` directive is then automatically injected for compute shader
* modules, and the shader define `CAPS_LINEAR_INDEXING` is set for conditional
* compilation.
*
* @type {boolean}
* @readonly
*/
__publicField(this, "supportsLinearIndexing", false);
/**
* True if the device supports the WGSL `pointer_composite_access` language feature, which
* provides syntactic sugar for dereferencing pointers to composite types: `p.field` and
* `p[i]` may be written instead of `(*p).field` and `(*p)[i]`. The
* `requires pointer_composite_access;` directive is automatically injected into WGSL
* shaders when this feature is available, and the shader define
* `CAPS_POINTER_COMPOSITE_ACCESS` is set for conditional compilation.
*
* @type {boolean}
* @readonly
*/
__publicField(this, "supportsPointerCompositeAccess", false);
/**
* True if the device supports the WGSL `packed_4x8_integer_dot_product` language feature,
* which exposes the DP4a-family built-in functions for 8-bit packed integer dot products:
* `dot4U8Packed`, `dot4I8Packed`, and the `pack4x{I,U}8`, `pack4x{I,U}8Clamp`,
* `unpack4x{I,U}8` helpers. Useful for accelerating quantized inference and similar
* integer-heavy compute workloads. The `requires packed_4x8_integer_dot_product;`
* directive is automatically injected into WGSL shaders when this feature is available,
* and the shader define `CAPS_PACKED_4X8_INTEGER_DOT_PRODUCT` is set for conditional
* compilation.
*
* @type {boolean}
* @readonly
*/
__publicField(this, "supportsPacked4x8IntegerDotProduct", false);
/**
* True if the device supports the WGSL `texture_and_sampler_let` language feature, which
* allows assigning texture and sampler variables to `let` bindings within a WGSL shader
* (preparation for bindless-style indirection patterns). The
* `requires texture_and_sampler_let;` directive is automatically injected into WGSL
* shaders when this feature is available, and the shader define
* `CAPS_TEXTURE_AND_SAMPLER_LET` is set for conditional compilation.
*
* @type {boolean}
* @readonly
*/
__publicField(this, "supportsTextureAndSamplerLet", false);
/**
* True if the device supports the WGSL `unrestricted_pointer_parameters` language feature,
* which allows passing pointers in the `storage`, `uniform`, and `workgroup` address spaces
* as function arguments. The `requires unrestricted_pointer_parameters;` directive is
* automatically injected into WGSL shaders when this feature is available, and the shader
* define `CAPS_UNRESTRICTED_POINTER_PARAMETERS` is set for conditional compilation.
*
* @type {boolean}
* @readonly
*/
__publicField(this, "supportsUnrestrictedPointerParameters", false);
/**
* Maximum subgroup (warp/wavefront) size reported for the device. Zero means either
* subgroups are not supported ({@link supportsSubgroups} is false), or the WebGPU
* implementation did not expose the value.
*
* @type {number}
* @ignore
*/
__publicField(this, "maxSubgroupSize", 0);
/**
* Minimum subgroup (warp/wavefront) size reported for the device. Zero means either
* subgroups are not supported ({@link supportsSubgroups} is false), or the WebGPU
* implementation did not expose the value.
*
* @type {number}
* @ignore
*/
__publicField(this, "minSubgroupSize", 0);
/**
* Currently active render target.
*
* @type {RenderTarget|null}
* @ignore
*/
__publicField(this, "renderTarget", null);
/**
* Array of objects that need to be re-initialized after a context restore event
*
* @type {Shader[]}
* @ignore
*/
__publicField(this, "shaders", []);
/**
* A set of currently created textures.
*
* @type {Set<Texture>}
* @ignore
*/
__publicField(this, "textures", /* @__PURE__ */ new Set());
/**
* A set of textures that need to be uploaded to the GPU.
*
* @type {Set<Texture>}
* @ignore
*/
__publicField(this, "texturesToUpload", /* @__PURE__ */ new Set());
/**
* A set of currently created render targets.
*
* @type {Set<RenderTarget>}
* @ignore
*/
__publicField(this, "targets", /* @__PURE__ */ new Set());
/**
* A version number that is incremented every frame. This is used to detect if some object were
* invalidated.
*
* @ignore
*/
__publicField(this, "renderVersion", 0);
/**
* Index of the currently active render pass.
*
* @type {number}
* @ignore
*/
__publicField(this, "renderPassIndex");
/** @type {boolean} */
__publicField(this, "insideRenderPass", false);
/**
* True if the device supports uniform buffers.
*
* @ignore
*/
__publicField(this, "supportsUniformBuffers", false);
/**
* True if the device supports clip distances (WebGPU only). Clip distances allow you to restrict
* primitives' clip volume with user-defined half-spaces in the output of vertex stage.
*/
__publicField(this, "supportsClipDistances", false);
/**
* True if the device supports WebGPU texture format tier 1 capabilities. When enabled, a wider
* set of normalized texture formats can be used as render targets and storage textures.
*
* @type {boolean}
* @readonly
*/
__publicField(this, "supportsTextureFormatTier1", false);
/**
* True if the device supports WebGPU texture format tier 2 capabilities. This extends tier 1
* and enables read-write storage access for selected texture formats.
*
* @type {boolean}
* @readonly
*/
__publicField(this, "supportsTextureFormatTier2", false);
/**
* True if the device supports primitive index in fragment shaders (WebGPU only). When
* supported, fragment shaders can access the `pcPrimitiveIndex` built-in variable which
* uniquely identifies the current primitive being processed.
*
* @type {boolean}
* @readonly
*/
__publicField(this, "supportsPrimitiveIndex", false);
/**
* True if the device supports 16-bit floating-point types in shaders (WebGPU only). When
* supported, shaders can use native WGSL types: `f16`, `vec2h`, `vec3h`, `vec4h`, `mat2x2h`,
* `mat3x3h`, `mat4x4h`. For convenience, PlayCanvas also provides type aliases (`half`,
* `half2`, `half3`, `half4`, `half2x2`, `half3x3`, `half4x4`) that resolve to f16 types when
* supported, or fall back to f32 types when not supported.
*
* @type {boolean}
* @readonly
*/
__publicField(this, "supportsShaderF16", false);
/**
* True if HTML elements (e.g. `<div>`) can be used as texture sources via the HTML-in-Canvas
* API. When supported, an HTML element appended to a canvas with the `layoutsubtree` attribute
* can be passed to {@link Texture#setSource} and rendered as a live texture in the 3D scene.
*
* @type {boolean}
* @readonly
*/
__publicField(this, "supportsHtmlTextures", false);
/**
* True if 32-bit floating-point textures can be used as a frame buffer.
*
* @type {boolean}
* @readonly
*/
__publicField(this, "textureFloatRenderable");
/**
* True if 16-bit floating-point textures can be used as a frame buffer.
*
* @type {boolean}
* @readonly
*/
__publicField(this, "textureHalfFloatRenderable");
/**
* True if small-float textures with format {@link PIXELFORMAT_111110F} can be used as a frame
* buffer. This is always true on WebGL2, but optional on WebGPU device.
*
* @type {boolean}
* @readonly
*/
__publicField(this, "textureRG11B10Renderable", false);
/**
* True if filtering can be applied when sampling float textures.
*
* @type {boolean}
* @readonly
*/
__publicField(this, "textureFloatFilterable", false);
/**
* A vertex buffer representing a quad.
*
* @type {VertexBuffer}
* @ignore
*/
__publicField(this, "quadVertexBuffer");
/**
* An index buffer for drawing a quad as an indexed triangle list.
* Contains 6 indices: [0, 1, 2, 2, 1, 3] forming two triangles.
*
* @type {IndexBuffer}
* @ignore
*/
__publicField(this, "quadIndexBuffer");
/**
* An object representing current blend state
*
* @ignore
*/
__publicField(this, "blendState", new BlendState());
/**
* The current depth state.
*
* @ignore
*/
__publicField(this, "depthState", new DepthState());
/**
* True if stencil is enabled and stencilFront and stencilBack are used
*
* @ignore
*/
__publicField(this, "stencilEnabled", false);
/**
* The current front stencil parameters.
*
* @ignore
*/
__publicField(this, "stencilFront", new StencilParameters());
/**
* The current back stencil parameters.
*
* @ignore
*/
__publicField(this, "stencilBack", new StencilParameters());
/**
* The dynamic buffer manager.
*
* @type {DynamicBuffers}
* @ignore
*/
__publicField(this, "dynamicBuffers");
/**
* The GPU profiler.
*
* @type {GpuProfiler}
*/
__publicField(this, "gpuProfiler");
/** @ignore */
__publicField(this, "_destroyed", false);
__publicField(this, "defaultClearOptions", {
color: [0, 0, 0, 1],
depth: 1,
stencil: 0,
flags: CLEARFLAG_COLOR | CLEARFLAG_DEPTH
});
/**
* The current client rect.
*
* @type {{ width: number, height: number }}
* @ignore
*/
__publicField(this, "clientRect", {
width: 0,
height: 0
});
/**
* A very heavy handed way to force all shaders to be rebuilt. Avoid using as much as possible.
*
* @ignore
*/
__publicField(this, "_shadersDirty", false);
/**
* A list of shader defines based on the capabilities of the device.
*
* @type {Map<string, string>}
* @ignore
*/
__publicField(this, "capsDefines", /* @__PURE__ */ new Map());
/**
* A set of maps to clear at the end of the frame.
*
* @type {Set<Map>}
* @ignore
*/
__publicField(this, "mapsToClear", /* @__PURE__ */ new Set());
this.canvas = canvas;
if ("setAttribute" in canvas) {
canvas.setAttribute("data-engine", `PlayCanvas ${version}`);
}
this.initOptions = { ...options };
(_a = this.initOptions).alpha ?? (_a.alpha = true);
(_b = this.initOptions).depth ?? (_b.depth = true);
(_c = this.initOptions).stencil ?? (_c.stencil = true);
(_d = this.initOptions).antialias ?? (_d.antialias = true);
(_e = this.initOptions).powerPreference ?? (_e.powerPreference = "high-performance");
(_f = this.initOptions).displayFormat ?? (_f.displayFormat = DISPLAYFORMAT_LDR);
(_g = this.initOptions).xrCompatible ?? (_g.xrCompatible = platform.browser && !!navigator.xr);
this._maxPixelRatio = platform.browser ? Math.min(1, window.devicePixelRatio) : 1;
this.buffers = /* @__PURE__ */ new Set();
this._vram = {
texShadow: 0,
texAsset: 0,
texLightmap: 0,
tex: 0,
vb: 0,
ib: 0,
ub: 0,
sb: 0
};
this._shaderStats = {
vsCompiled: 0,
fsCompiled: 0,
linked: 0,
materialShaders: 0,
compileTime: 0
};
this.initializeContextCaches();
this._drawCallsPerFrame = 0;
this._shaderSwitchesPerFrame = 0;
this._primsPerFrame = [];
for (let i = PRIMITIVE_POINTS; i <= PRIMITIVE_TRIFAN; i++) {
this._primsPerFrame[i] = 0;
}
this._renderTargetCreationTime = 0;
this.scope = new ScopeSpace("Device");
this.textureBias = this.scope.resolve("textureBias");
this.textureBias.setValue(0);
}
/**
* Function that executes after the device has been created.
*/
postInit() {
const vertexFormat = new VertexFormat(this, [
{ semantic: SEMANTIC_POSITION, components: 2, type: TYPE_FLOAT32 }
]);
const positions = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
this.quadVertexBuffer = new VertexBuffer(this, vertexFormat, 4, {
data: positions
});
const indices = new Uint16Array([0, 1, 2, 2, 1, 3]);
this.quadIndexBuffer = new IndexBuffer(this, INDEXFORMAT_UINT16, 6, BUFFER_STATIC, indices.buffer);
}
/**
* Initialize the map of device capabilities, which are supplied to shaders as defines.
*
* @ignore
*/
initCapsDefines() {
const { capsDefines } = this;
capsDefines.clear();
if (this.textureFloatFilterable) capsDefines.set("CAPS_TEXTURE_FLOAT_FILTERABLE", "");
if (this.textureFloatRenderable) capsDefines.set("CAPS_TEXTURE_FLOAT_RENDERABLE", "");
if (this.supportsMultiDraw) capsDefines.set("CAPS_MULTI_DRAW", "");
if (this.supportsPrimitiveIndex) capsDefines.set("CAPS_PRIMITIVE_INDEX", "");
if (this.supportsShaderF16) capsDefines.set("CAPS_SHADER_F16", "");
if (this.supportsSubgroups) capsDefines.set("CAPS_SUBGROUPS", "");
if (this.supportsSubgroupId) capsDefines.set("CAPS_SUBGROUP_ID", "");
if (this.supportsLinearIndexing) capsDefines.set("CAPS_LINEAR_INDEXING", "");
if (this.supportsUnrestrictedPointerParameters) capsDefines.set("CAPS_UNRESTRICTED_POINTER_PARAMETERS", "");
if (this.supportsPointerCompositeAccess) capsDefines.set("CAPS_POINTER_COMPOSITE_ACCESS", "");
if (this.supportsPacked4x8IntegerDotProduct) capsDefines.set("CAPS_PACKED_4X8_INTEGER_DOT_PRODUCT", "");
if (this.supportsTextureAndSamplerLet) capsDefines.set("CAPS_TEXTURE_AND_SAMPLER_LET", "");
if (this.supportsStorageTextureRead) capsDefines.set("CAPS_STORAGE_TEXTURE_READ", "");
if (platform.desktop) capsDefines.set("PLATFORM_DESKTOP", "");
if (platform.mobile) capsDefines.set("PLATFORM_MOBILE", "");
if (platform.android) capsDefines.set("PLATFORM_ANDROID", "");
if (platform.ios) capsDefines.set("PLATFORM_IOS", "");
}
/**
* Destroy the graphics device.
*/
destroy() {
this.fire("destroy");
this.quadVertexBuffer?.destroy();
this.quadVertexBuffer = null;
this.quadIndexBuffer?.destroy();
this.quadIndexBuffer = null;
this.dynamicBuffers?.destroy();
this.dynamicBuffers = null;
this.gpuProfiler?.destroy();
this.gpuProfiler = null;
this._destroyed = true;
}
onDestroyShader(shader) {
this.fire("destroy:shader", shader);
const idx = this.shaders.indexOf(shader);
if (idx !== -1) {
this.shaders.splice(idx, 1);
}
}
/**
* Called when a texture is destroyed to remove it from internal tracking structures.
*
* @param {Texture} texture - The texture being destroyed.
* @ignore
*/
onTextureDestroyed(texture) {
this.textures.delete(texture);
this.texturesToUpload.delete(texture);
this.scope.removeValue(texture);
}
// executes after the extended classes have executed their destroy function
postDestroy() {
this.scope = null;
this.canvas = null;
}
/**
* Called when the device context was lost. It releases all context related resources.
*
* @ignore
*/
loseContext() {
Debug.log("pc.GraphicsDevice: Graphics context lost.");
this.contextLost = true;
this.backBufferSize.set(-1, -1);
for (const texture of this.textures) {
texture.loseContext();
}
for (const buffer of this.buffers) {
buffer.loseContext();
}
for (const target of this.targets) {
target.loseContext();
}
this.gpuProfiler?.loseContext();
}
/**
* Called when the device context is restored. It reinitializes all context related resources.
*
* @ignore
*/
restoreContext() {
Debug.log("pc.GraphicsDevice: Graphics context restored.");
this.contextLost = false;
this.initializeRenderState();
this.initializeContextCaches();
for (const buffer of this.buffers) {
buffer.restoreContext();
}
this.gpuProfiler?.restoreContext?.();
}
// don't stringify GraphicsDevice to JSON by JSON.stringify
toJSON(key) {
return void 0;
}
initializeContextCaches() {
this.vertexBuffers = [];
this.shader = null;
this.shaderValid = void 0;
this.shaderAsyncCompile = false;
this.renderTarget = null;
}
initializeRenderState() {
this.blendState = new BlendState();
this.depthState = new DepthState();
this.cullMode = CULLFACE_BACK;
this.frontFace = FRONTFACE_CCW;
this.vx = this.vy = this.vw = this.vh = 0;
this.sx = this.sy = this.sw = this.sh = 0;
this.blendColor = new Color(0, 0, 0, 0);
}
/**
* Sets the specified stencil state. If both stencilFront and stencilBack are null, stencil
* operation is disabled.
*
* @param {StencilParameters} [stencilFront] - The front stencil parameters. Defaults to
* {@link StencilParameters.DEFAULT} if not specified.
* @param {StencilParameters} [stencilBack] - The back stencil parameters. Defaults to
* {@link StencilParameters.DEFAULT} if not specified.
*/
setStencilState(stencilFront, stencilBack) {
Debug.assert(false);
}
/**
* Sets the specified blend state.
*
* @param {BlendState} blendState - New blend state.
*/
setBlendState(blendState) {
Debug.assert(false);
}
/**
* Sets the constant blend color and alpha values used with {@link BLENDMODE_CONSTANT} and
* {@link BLENDMODE_ONE_MINUS_CONSTANT} factors specified in {@link BlendState}. Defaults to
* [0, 0, 0, 0].
*
* @param {number} r - The value for red.
* @param {number} g - The value for green.
* @param {number} b - The value for blue.
* @param {number} a - The value for alpha.
*/
setBlendColor(r, g, b, a) {
Debug.assert(false);
}
/**
* Sets the specified depth state.
*
* @param {DepthState} depthState - New depth state.
*/
setDepthState(depthState) {
Debug.assert(false);
}
/**
* Controls how triangles are culled based on their face direction. The default cull mode is
* {@link CULLFACE_BACK}.
*
* @param {number} cullMode - The cull mode to set. Can be:
*
* - {@link CULLFACE_NONE}
* - {@link CULLFACE_BACK}
* - {@link CULLFACE_FRONT}
*/
setCullMode(cullMode) {
Debug.assert(false);
}
/**
* Controls whether polygons are front- or back-facing by setting a winding
* orientation. The default frontFace is {@link FRONTFACE_CCW}.
*
* @param {number} frontFace - The front face to set. Can be:
*
* - {@link FRONTFACE_CW}
* - {@link FRONTFACE_CCW}
*/
setFrontFace(frontFace) {
Debug.assert(false);
}
/**
* Sets all draw-related render states in a single call. All parameters have sensible defaults
* for utility rendering (full-screen quads, particles, etc.), so calling `setDrawStates()` with
* no arguments resets to a safe baseline.
*
* @param {BlendState} [blendState] - Blend state. Defaults to {@link BlendState.NOBLEND}.
* @param {DepthState} [depthState] - Depth state. Defaults to {@link DepthState.NODEPTH}.
* @param {number} [cullMode] - Cull mode. Defaults to {@link CULLFACE_NONE}.
* @param {number} [frontFace] - Front face winding. Defaults to {@link FRONTFACE_CCW}.
* @param {StencilParameters} [stencilFront] - Front stencil parameters.
* @param {StencilParameters} [stencilBack] - Back stencil parameters.
*/
setDrawStates(blendState = BlendState.NOBLEND, depthState = DepthState.NODEPTH, cullMode = CULLFACE_NONE, frontFace = FRONTFACE_CCW, stencilFront, stencilBack) {
this.setBlendState(blendState);
this.setDepthState(depthState);
this.setCullMode(cullMode);
this.setFrontFace(frontFace);
this.setStencilState(stencilFront, stencilBack);
}
/**
* Sets the specified render target on the device. If null is passed as a parameter, the back
* buffer becomes the current target for all rendering operations.
*
* @param {RenderTarget|null} renderTarget - The render target to activate.
* @example
* // Set a render target to receive all rendering output
* device.setRenderTarget(renderTarget);
*
* // Set the back buffer to receive all rendering output
* device.setRenderTarget(null);
*/
setRenderTarget(renderTarget) {
this.renderTarget = renderTarget;
}
/**
* Sets the current vertex buffer on the graphics device. For subsequent draw calls, the
* specified vertex buffer(s) will be used to provide vertex data for any primitives.
*
* @param {VertexBuffer} vertexBuffer - The vertex buffer to assign to the device.
* @ignore
*/
setVertexBuffer(vertexBuffer) {
if (vertexBuffer) {
this.vertexBuffers.push(vertexBuffer);
}
}
/**
* Clears the vertex buffer set on the graphics device. This is called automatically by the
* renderer.
*
* @ignore
*/
clearVertexBuffer() {
this.vertexBuffers.length = 0;
}
/**
* Retrieves the first available slot in the {@link indirectDrawBuffer} used for indirect
* rendering, which can be utilized by a {@link Compute} shader to generate indirect draw
* parameters and by {@link MeshInstance#setIndirect} to configure indirect draw calls.
*
* When reserving multiple consecutive slots, specify the optional `count` parameter.
*
* @param {number} [count] - Number of consecutive slots to reserve. Defaults to 1.
* @returns {number} - The first reserved slot index used for indirect rendering.
*/
getIndirectDrawSlot(count = 1) {
return 0;
}
/**
* Returns the buffer used to store arguments for indirect draw calls. The size of the buffer is
* controlled by the {@link maxIndirectDrawCount} property. This buffer can be passed to a
* {@link Compute} shader along with a slot obtained by calling {@link getIndirectDrawSlot}, in
* order to prepare indirect draw parameters. Also see {@link MeshInstance#setIndirect}.
*
* Only available on WebGPU, returns null on other platforms.
*
* @type {StorageBuffer|null}
*/
get indirectDrawBuffer() {
return null;
}
/**
* Retrieves the first available slot in the {@link indirectDispatchBuffer} used for indirect
* compute dispatch, which can be utilized by a {@link Compute} shader to generate indirect
* dispatch parameters for another compute shader.
*
* When reserving multiple consecutive slots, specify the optional `count` parameter.
*
* @param {number} [count] - Number of consecutive slots to reserve. Defaults to 1.
* @returns {number} - The first reserved slot index used for indirect dispatch.
*/
getIndirectDispatchSlot(count = 1) {
return 0;
}
/**
* Returns the buffer used to store arguments for indirect compute dispatch calls. The size of
* the buffer is controlled by the {@link maxIndirectDispatchCount} property. This buffer can
* be passed to a {@link Compute} shader along with a slot obtained by calling
* {@link getIndirectDispatchSlot}, in order to prepare indirect dispatch parameters.
*
* Only available on WebGPU, returns null on other platforms.
*
* @type {StorageBuffer|null}
*/
get indirectDispatchBuffer() {
return null;
}
/**
* Queries the currently set render target on the device.
*
* @returns {RenderTarget} The current render target.
* @example
* // Get the current render target
* const renderTarget = device.getRenderTarget();
*/
getRenderTarget() {
return this.renderTarget;
}
/**
* Initialize render target before it can be used.
*
* @param {RenderTarget} target - The render target to be initialized.
* @ignore
*/
initRenderTarget(target) {
if (target.initialized) return;
const startTime = now();
this.fire("fbo:create", {
timestamp: startTime,
target: this
});
target.init();
this.targets.add(target);
this._renderTargetCreationTime += now() - startTime;
}
/**
* Submits a graphical primitive to the hardware for immediate rendering.
*
* @param {object} primitive - Primitive object describing how to submit current vertex/index
* buffers.
* @param {number} primitive.type - The type of primitive to render. Can be:
*
* - {@link PRIMITIVE_POINTS}
* - {@link PRIMITIVE_LINES}
* - {@link PRIMITIVE_LINELOOP}
* - {@link PRIMITIVE_LINESTRIP}
* - {@link PRIMITIVE_TRIANGLES}
* - {@link PRIMITIVE_TRISTRIP}
* - {@link PRIMITIVE_TRIFAN}
*
* @param {number} primitive.base - The offset of the first index or vertex to dispatch in the
* draw call.
* @param {number} primitive.count - The number of indices or vertices to dispatch in the draw
* call.
* @param {boolean} [primitive.indexed] - True to interpret the primitive as indexed, thereby
* using the currently set index buffer and false otherwise.
* @param {IndexBuffer} [indexBuffer] - The index buffer to use for the draw call.
* @param {number} [numInstances] - The number of instances to render when using instancing.
* Defaults to 1.
* @param {DrawCommands} [drawCommands] - The draw commands to use for the draw call.
* @param {boolean} [first] - True if this is the first draw call in a sequence of draw calls.
* When set to true, vertex and index buffers related state is set up. Defaults to true.
* @param {boolean} [last] - True if this is the last draw call in a sequence of draw calls.
* When set to true, vertex and index buffers related state is cleared. Defaults to true.
* @example
* // Render a single, unindexed triangle
* device.draw({
* type: pc.PRIMITIVE_TRIANGLES,
* base: 0,
* count: 3,
* indexed: false
* });
*
* @ignore
*/
draw(primitive, indexBuffer, numInstances, drawCommands, first = true, last = true) {
Debug.assert(false);
}
/**
* Reports whether a texture source is a canvas, image, video, ImageBitmap, or HTML element.
*
* @param {*} texture - Texture source data.
* @returns {boolean} True if the texture is a canvas, image, video, ImageBitmap, or HTML
* element and false otherwise.
* @ignore
*/
_isBrowserInterface(texture) {
return this._isImageBrowserInterface(texture) || this._isImageCanvasInterface(texture) || this._isImageVideoInterface(texture) || this._isHTMLElementInterface(texture);
}
_isImageBrowserInterface(texture) {
return typeof ImageBitmap !== "undefined" && texture instanceof ImageBitmap || typeof HTMLImageElement !== "undefined" && texture instanceof HTMLImageElement;
}
_isImageCanvasInterface(texture) {
return typeof HTMLCanvasElement !== "undefined" && texture instanceof HTMLCanvasElement;
}
_isImageVideoInterface(texture) {
return typeof HTMLVideoElement !== "undefined" && texture instanceof HTMLVideoElement;
}
/**
* Reports whether a texture source is a generic HTML element (not image, canvas, or video).
* Used for the HTML-in-Canvas proposal (texElementImage2D).
*
* @param {*} texture - Texture source data.
* @returns {boolean} True if the texture is an HTMLElement that is not an image, canvas, or
* video.
* @ignore
*/
_isHTMLElementInterface(texture) {
return typeof HTMLElement !== "undefined" && texture instanceof HTMLElement && !(typeof HTMLImageElement !== "undefined" && texture instanceof HTMLImageElement) && !(typeof HTMLCanvasElement !== "undefined" && texture instanceof HTMLCanvasElement) && !(typeof HTMLVideoElement !== "undefined" && texture instanceof HTMLVideoElement);
}
/**
* Sets the width and height of the canvas, then fires the `resizecanvas` event. Note that the
* specified width and height values will be multiplied by the value of {@link maxPixelRatio}
* to give the final resultant width and height for the canvas.
*
* @param {number} width - The new width of the canvas.
* @param {number} height - The new height of the canvas.
* @ignore
*/
resizeCanvas(width, height) {
const pixelRatio = Math.min(this._maxPixelRatio, platform.browser ? window.devicePixelRatio : 1);
const w = Math.floor(width * pixelRatio);
const h = Math.floor(height * pixelRatio);
if (w !== this.canvas.width || h !== this.canvas.height) {
this.setResolution(w, h);
}
}
/**
* Sets the width and height of the canvas, then fires the `resizecanvas` event. Note that the
* value of {@link maxPixelRatio} is ignored.
*
* @param {number} width - The new width of the canvas.
* @param {number} height - The new height of the canvas.
* @ignore
*/
setResolution(width, height) {
this.canvas.width = width;
this.canvas.height = height;
this.fire(_GraphicsDevice.EVENT_RESIZE, width, height);
}
update() {
this.updateClientRect();
}
updateClientRect() {
if (platform.worker) {
this.clientRect.width = this.canvas.width;
this.clientRect.height = this.canvas.height;
} else {
const rect = this.canvas.getBoundingClientRect();
this.clientRect.width = rect.width;
this.clientRect.height = rect.height;
}
}
/**
* Width of the back buffer in pixels.
*
* @type {number}
*/
get width() {
return this.canvas.width;
}
/**
* Height of the back buffer in pixels.
*
* @type {number}
*/
get height() {
return this.canvas.height;
}
/**
* Sets whether the device is currently in fullscreen mode.
*
* @type {boolean}
*/
set fullscreen(fullscreen) {
Debug.error("GraphicsDevice.fullscreen is not implemented on current device.");
}
/**
* Gets whether the device is currently in fullscreen mode.
*
* @type {boolean}
*/
get fullscreen() {
Debug.error("GraphicsDevice.fullscreen is not implemented on current device.");
return false;
}
/**
* Sets the maximum pixel ratio.
*
* @type {number}
*/
set maxPixelRatio(ratio) {
this._maxPixelRatio = ratio;
}
/**
* Gets the maximum pixel ratio.
*
* @type {number}
*/
get maxPixelRatio() {
return this._maxPixelRatio;
}
/**
* Gets the type of the device. Can be:
*
* - {@link DEVICETYPE_WEBGL2}
* - {@link DEVICETYPE_WEBGPU}
*
* @type {DEVICETYPE_WEBGL2|DEVICETYPE_WEBGPU}
*/
get deviceType() {
return this._deviceType;
}
startRenderPass(renderPass) {
}
endRenderPass(renderPass) {
}
startComputePass(name) {
}
endComputePass() {
}
/**
* Function which executes at the start of the frame. This should not be called manually, as
* it is handled by the AppBase instance.
*
* @ignore
*/
frameStart() {
this.renderPassIndex = 0;
this.renderVersion++;
Debug.call(() => {
if (Tracing.get(TRACEID_TEXTURES)) {
const textures = [...this.textures];
textures.sort((a, b) => b.gpuSize - a.gpuSize);
Debug.log(`Textures: ${textures.length}`);
let textureTotal = 0;
textures.forEach((texture, index) => {
const textureSize = texture.gpuSize;
textureTotal += textureSize;
Debug.log(`${index}. Id: ${texture.id} ${texture.name} ${texture.width}x${texture.height} VRAM: ${(textureSize / 1024 / 1024).toFixed(2)} MB`);
});
Debug.log(`Total: ${(textureTotal / 1024 / 1024).toFixed(2)}MB`);
}
if (Tracing.get(TRACEID_BUFFERS)) {
const entries = [...this.buffers].map((buffer) => {
let kind;
let size;
if (buffer instanceof VertexBuffer) {
kind = "VB";
size = buffer.storage?.byteLength ?? buffer.numBytes ?? 0;
} else if (buffer instanceof IndexBuffer) {
kind = "IB";
size = buffer.storage?.byteLength ?? buffer.numBytes ?? 0;
} else if (buffer instanceof StorageBuffer) {
kind = "SB";
size = buffer.byteSize ?? 0;
} else {
kind = buffer.constructor?.name ?? "?";
size = buffer.storage?.byteLength ?? buffer.numBytes ?? buffer.byteSize ?? 0;
}
return { buffer, kind, size };
});
entries.sort((a, b) => b.size - a.size);
Debug.log(`Buffers: ${entries.length}`);
let total = 0;
let totalVB = 0;
let totalIB = 0;
let totalSB = 0;
entries.forEach((entry, index) => {
const { buffer, kind, size } = entry;
total += size;
if (kind === "VB") {
totalVB += size;
} else if (kind === "IB") {
totalIB += size;
} else if (kind === "SB") {
totalSB += size;
}
const namePart = buffer.name ? ` ${buffer.name}` : "";
Debug.log(`${index}. ${kind} Id: ${buffer.id}${namePart} VRAM: ${(size / 1024 / 1024).toFixed(2)} MB`);
});
const mb = (n) => (n / 1024 / 1024).toFixed(2);
Debug.log(`Total VB: ${totalVB} bytes (${mb(totalVB)} MB)`);
Debug.log(`Total IB: ${totalIB} bytes (${mb(totalIB)} MB)`);
Debug.log(`Total SB: ${totalSB} bytes (${mb(totalSB)} MB)`);
Debug.log(`Total: ${total} bytes (${mb(total)} MB)`);
}
});
}
/**
* Function which executes at the end of the frame. This should not be called manually, as it is
* handled by the AppBase instance.
*
* @ignore
*/
frameEnd() {
this.mapsToClear.forEach((map) => map.clear());
this.mapsToClear.clear();
}
/**
* Dispatch multiple compute shaders inside a single compute shader pass.
*
* @param {Array<Compute>} computes - An array of compute shaders to dispatch.
* @param {string} [name] - The name of the dispatch, used for debugging and reporting only.
*/
computeDispatch(computes, name = "Unnamed") {
}
/**
* Get a renderable HDR pixel format supported by the graphics device.
*
* Note:
*
* - When the `filterable` parameter is set to false, this function returns one of the supported
* formats on the majority of devices apart from some very old iOS and Android devices (99%).
* - When the `filterable` parameter is set to true, the function returns a format on a
* considerably lower number of devices (70%).
*
* @param {number[]} [formats] - An array of pixel formats to check for support. Can contain:
*
* - {@link PIXELFORMAT_111110F}
* - {@link PIXELFORMAT_RGBA16F}
* - {@link PIXELFORMAT_RGBA32F}
*
* @param {boolean} [filterable] - If true, the format also needs to be filterable. Defaults to
* true.
* @param {number} [samples] - The number of samples to check for. Some formats are not
* compatible with multi-sampling, for example {@link PIXELFORMAT_RGBA32F} on WebGPU platform.
* Defaults to 1.
* @returns {number|undefined} The first supported renderable HDR format or undefined if none is
* supported.
*/
getRenderableHdrFormat(formats = [PIXELFORMAT_111110F, PIXELFORMAT_RGBA16F, PIXELFORMAT_RGBA32F], filterable = true, samples = 1) {
for (let i = 0; i < formats.length; i++) {
const format = formats[i];
switch (format) {
case PIXELFORMAT_111110F: {
if (this.textureRG11B10Renderable) {
return format;
}
break;
}
case PIXELFORMAT_RGBA16F:
if (this.textureHalfFloatRenderable) {
return format;
}
break;
case PIXELFORMAT_RGBA32F:
if (this.isWebGPU && samples > 1) {
continue;
}
if (this.textureFloatRenderable && (!filterable || this.textureFloatFilterable)) {
return format;
}
break;
}
}
return void 0;
}
/**
* Validate that all attributes required by the shader are present in the currently assigned
* vertex buffers.
*
* @param {Shader} shader - The shader to validate.
* @param {VertexFormat} vb0Format - The format of the first vertex buffer.
* @param {VertexFormat} vb1Format - The format of the second vertex buffer.
* @protected
*/
validateAttributes(shader, vb0Format, vb1Format) {
Debug.call(() => {
_tempSet.clear();
vb0Format?.elements.forEach((element) => _tempSet.add(semanticToLocation[element.name]));
vb1Format?.elements.forEach((element) => _tempSet.add(semanticToLocation[element.name]));
for (const [location, name] of shader.attributes) {
if (!_tempSet.has(location)) {
Debug.errorOnce(`Vertex attribute [${name}] at location ${location} required by the shader is not present in the currently assigned vertex buffers, while rendering [${DebugGraphics.toString()}]`, {
shader,
vb0Format,
vb1Format
});
}
}
});
}
};
__publicField(_GraphicsDevice, "EVENT_RESIZE", "resizecanvas");
let GraphicsDevice = _GraphicsDevice;
export {
GraphicsDevice
};