UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

1,292 lines (1,291 loc) 45.7 kB
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 };