UNPKG

playcanvas

Version:

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

425 lines (424 loc) 13 kB
import { version } from "../../core/core.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 { StorageBuffer } from "./storage-buffer.js"; const _tempSet = /* @__PURE__ */ new Set(); class GraphicsDevice extends EventHandler { canvas; backBuffer = null; backBufferSize = new Vec2(); backBufferFormat; backBufferAntialias = false; isWebGPU = false; isWebGL2 = false; isNull = false; isHdr = false; scope; maxIndirectDrawCount = 1024; maxIndirectDispatchCount = 256; maxAnisotropy; maxCubeMapSize; maxTextureSize; maxVolumeSize; maxColorAttachments = 1; precision; samples; maxSamples = 1; supportsStencil; supportsMultiDraw = true; supportsCompute = false; supportsStorageTextureRead = false; supportsSubgroups = false; supportsSubgroupUniformity = false; supportsSubgroupId = false; supportsLinearIndexing = false; supportsPointerCompositeAccess = false; supportsPacked4x8IntegerDotProduct = false; supportsTextureAndSamplerLet = false; supportsUnrestrictedPointerParameters = false; maxSubgroupSize = 0; minSubgroupSize = 0; renderTarget = null; shaders = []; textures = /* @__PURE__ */ new Set(); texturesToUpload = /* @__PURE__ */ new Set(); targets = /* @__PURE__ */ new Set(); renderVersion = 0; renderPassIndex; insideRenderPass = false; supportsUniformBuffers = false; supportsClipDistances = false; supportsTextureFormatTier1 = false; supportsTextureFormatTier2 = false; supportsPrimitiveIndex = false; supportsShaderF16 = false; supportsHtmlTextures = false; textureFloatRenderable; textureHalfFloatRenderable; textureRG11B10Renderable = false; textureFloatFilterable = false; quadVertexBuffer; quadIndexBuffer; blendState = new BlendState(); depthState = new DepthState(); stencilEnabled = false; stencilFront = new StencilParameters(); stencilBack = new StencilParameters(); dynamicBuffers; gpuProfiler; _destroyed = false; defaultClearOptions = { color: [0, 0, 0, 1], depth: 1, stencil: 0, flags: CLEARFLAG_COLOR | CLEARFLAG_DEPTH }; clientRect = { width: 0, height: 0 }; _shadersDirty = false; capsDefines = /* @__PURE__ */ new Map(); mapsToClear = /* @__PURE__ */ new Set(); static EVENT_RESIZE = "resizecanvas"; constructor(canvas, options) { var _a, _b, _c, _d, _e, _f, _g; super(); 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 = { 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); } 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); } 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() { 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); } } 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; } loseContext() { 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(); } restoreContext() { 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); } setStencilState(stencilFront, stencilBack) { } setBlendState(blendState) { } setBlendColor(r, g, b, a) { } setDepthState(depthState) { } setCullMode(cullMode) { } setFrontFace(frontFace) { } 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); } setRenderTarget(renderTarget) { this.renderTarget = renderTarget; } setVertexBuffer(vertexBuffer) { if (vertexBuffer) { this.vertexBuffers.push(vertexBuffer); } } clearVertexBuffer() { this.vertexBuffers.length = 0; } getIndirectDrawSlot(count = 1) { return 0; } get indirectDrawBuffer() { return null; } getIndirectDispatchSlot(count = 1) { return 0; } get indirectDispatchBuffer() { return null; } getRenderTarget() { return this.renderTarget; } initRenderTarget(target) { if (target.initialized) return; target.init(); this.targets.add(target); } draw(primitive, indexBuffer, numInstances, drawCommands, first = true, last = true) { } _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; } _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); } 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); } } 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; } } get width() { return this.canvas.width; } get height() { return this.canvas.height; } set fullscreen(fullscreen) { } get fullscreen() { return false; } set maxPixelRatio(ratio) { this._maxPixelRatio = ratio; } get maxPixelRatio() { return this._maxPixelRatio; } get deviceType() { return this._deviceType; } startRenderPass(renderPass) { } endRenderPass(renderPass) { } startComputePass(name) { } endComputePass() { } frameStart() { this.renderPassIndex = 0; this.renderVersion++; } frameEnd() { this.mapsToClear.forEach((map) => map.clear()); this.mapsToClear.clear(); } computeDispatch(computes, name = "Unnamed") { } 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; } validateAttributes(shader, vb0Format, vb1Format) { } } export { GraphicsDevice };