playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
425 lines (424 loc) • 13 kB
JavaScript
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
};