Skip to content

GraphicsDevice properties refactor #4013

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/deprecated/deprecated.js
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,10 @@ Object.keys(deprecatedChunks).forEach((chunkName) => {
});
});

VertexFormat.prototype.update = function () {
Debug.deprecated('pc.VertexFormat.update is deprecated, and VertexFormat cannot be changed after it has been created.');
};

Object.defineProperties(Texture.prototype, {
rgbm: {
get: function () {
Expand Down
292 changes: 292 additions & 0 deletions src/graphics/graphics-device.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
import { EventHandler } from '../core/event-handler.js';
import { platform } from '../core/platform.js';

import { ScopeSpace } from './scope-space.js';
import { programlib } from './program-lib/program-lib.js';
import { ProgramLibrary } from './program-library.js';

import {
PRIMITIVE_POINTS, PRIMITIVE_TRIFAN
} from './constants.js';
import { Debug } from '../core/debug.js';

const EVENT_RESIZE = 'resizecanvas';

/** @typedef {import('./render-target.js').RenderTarget} RenderTarget */

/**
* The graphics device manages the underlying graphics context. It is responsible for submitting
Expand All @@ -9,10 +23,288 @@ import { EventHandler } from '../core/event-handler.js';
* @augments EventHandler
*/
class GraphicsDevice extends EventHandler {
/**
* The canvas DOM element that provides the underlying WebGL context used by the graphics device.
*
* @type {HTMLCanvasElement}
*/
canvas;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering how this code gets executed (and also converted to es5).

This line is declaring a member and could also be initialising it, like canvas = null;. But the constructor does this.canvas = canvas;. So does the code on this line run after the constructor's call to super();, but before the rest of the constructor code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct, when you step over this in the debugger, those things execute first (and will be set to undefined), before constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is what it looks like in ES5
Screenshot 2022-02-11 at 09 48 45

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting.

So could you do canvas = canvas; here, instead of doing it in the constructor?

In a class as large as this it probably makes little difference, but in general we don't want to be initialising members twice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, you don't have constructor parameters available at that point in ES6 world. Only in ES5 it's transpiled to doing it all inside constructor.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I guess in general we want to declare (and optionally initialise) variables here or in the constructor, but not both?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I doubt that would make any perf difference .. but perhaps it'd be useful to test it in a class such as Vec3?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So yep, I probably wouldn't add that to Vec3. I considered it but so many are created, I decided to avoid potentially slowing the code down. But for classes that are rarely created, I think it's stylistically very nice (and actually maps directly to TypeScript).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't like the idea instance members are initialised twice in our constructors, it's gross.

However reading here, they say private versions of these (which we hope to use in future) must be declared this way:

Note: Private fields can only be declared up-front in a field declaration.

So it seems we ultimately won't have a choice and hopefully compiler will optimise away the redundant set.


/**
* The scope namespace for shader attributes and variables.
*
* @type {ScopeSpace}
*/
scope;

/**
* The maximum supported texture anisotropy setting.
*
* @type {number}
*/
maxAnisotropy;

/**
* The maximum supported dimension of a cube map.
*
* @type {number}
*/
maxCubeMapSize;

/**
* The maximum supported dimension of a texture.
*
* @type {number}
*/
maxTextureSize;

/**
* The maximum supported dimension of a 3D texture (any axis).
*
* @type {number}
*/
maxVolumeSize;

/**
* The highest shader precision supported by this graphics device. Can be 'hiphp', 'mediump' or
* 'lowp'.
*
* @type {string}
*/
precision;

/**
* True if hardware instancing is supported.
*
* @type {boolean}
*/
supportsInstancing;

/**
* True if 32-bit floating-point textures can be used as a frame buffer.
*
* @type {boolean}
*/
textureFloatRenderable;

/**
* True if 16-bit floating-point textures can be used as a frame buffer.
*
* @type {boolean}
*/
textureHalfFloatRenderable;

constructor(canvas) {
super();

this.canvas = canvas;

// local width/height without pixelRatio applied
this._width = 0;
this._height = 0;

this._maxPixelRatio = 1;

// Array of WebGL objects that need to be re-initialized after a context restore event
this.shaders = [];
this.buffers = [];
this.textures = [];
this.targets = [];

this._vram = {
// #if _PROFILER
texShadow: 0,
texAsset: 0,
texLightmap: 0,
// #endif
tex: 0,
vb: 0,
ib: 0
};

this._shaderStats = {
vsCompiled: 0,
fsCompiled: 0,
linked: 0,
materialShaders: 0,
compileTime: 0
};

this.initializeContextCaches();

// Profiler stats
this._drawCallsPerFrame = 0;
this._shaderSwitchesPerFrame = 0;

this._primsPerFrame = [];
for (let i = PRIMITIVE_POINTS; i <= PRIMITIVE_TRIFAN; i++) {
this._primsPerFrame[i] = 0;
}
this._renderTargetCreationTime = 0;

// Create the ScopeNamespace for shader attributes and variables
this.scope = new ScopeSpace("Device");

this.programLib = new ProgramLibrary(this);
for (const generator in programlib)
this.programLib.register(generator, programlib[generator]);
}

// don't stringify GraphicsDevice to JSON by JSON.stringify
toJSON(key) {
return undefined;
}

initializeContextCaches() {
this.indexBuffer = null;
this.vertexBuffers = [];
this.shader = null;
this.renderTarget = null;
}

/**
* Retrieves the program library assigned to the specified graphics device.
*
* @returns {ProgramLibrary} The program library assigned to the device.
* @ignore
*/
getProgramLibrary() {
return this.programLib;
}

/**
* Assigns a program library to the specified device. By default, a graphics device is created
* with a program library that manages all of the programs that are used to render any
* graphical primitives. However, this function allows the user to replace the existing program
* library with a new one.
*
* @param {ProgramLibrary} programLib - The program library to assign to the device.
* @ignore
*/
setProgramLibrary(programLib) {
this.programLib = programLib;
}

/**
* 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} 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;
}

/**
* Queries the currently set render target on the device.
*
* @returns {RenderTarget} The current render target.
* @example
* // Get the current render target
* var renderTarget = device.getRenderTarget();
*/
getRenderTarget() {
return this.renderTarget;
}

/**
* 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 GraphicsDevice#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) {
this._width = width;
this._height = height;

const ratio = Math.min(this._maxPixelRatio, platform.browser ? window.devicePixelRatio : 1);
width = Math.floor(width * ratio);
height = Math.floor(height * ratio);

if (this.canvas.width !== width || this.canvas.height !== height) {
this.canvas.width = width;
this.canvas.height = height;
this.fire(EVENT_RESIZE, width, height);
}
}

/**
* Sets the width and height of the canvas, then fires the `resizecanvas` event. Note that the
* value of {@link GraphicsDevice#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._width = width;
this._height = height;
this.canvas.width = width;
this.canvas.height = height;
this.fire(EVENT_RESIZE, width, height);
}

updateClientRect() {
this.clientRect = this.canvas.getBoundingClientRect();
}

/**
* Width of the back buffer in pixels.
*
* @type {number}
*/
get width() {
Debug.assert("GraphicsDevice.width is not implemented on current device.");
return this.canvas.width;
}

/**
* Height of the back buffer in pixels.
*
* @type {number}
*/
get height() {
Debug.assert("GraphicsDevice.height is not implemented on current device.");
return this.canvas.height;
}

/**
* Fullscreen mode.
*
* @type {boolean}
*/
set fullscreen(fullscreen) {
Debug.assert("GraphicsDevice.fullscreen is not implemented on current device.");
}

get fullscreen() {
Debug.assert("GraphicsDevice.fullscreen is not implemented on current device.");
return false;
}

/**
* Maximum pixel ratio.
*
* @type {number}
*/
set maxPixelRatio(ratio) {
this._maxPixelRatio = ratio;
this.resizeCanvas(this._width, this._height);
}

get maxPixelRatio() {
return this._maxPixelRatio;
}
}

export { GraphicsDevice };
2 changes: 1 addition & 1 deletion src/graphics/vertex-buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class VertexBuffer {

this.id = id++;

this.impl = graphicsDevice.createVertexBufferImpl(this);
this.impl = graphicsDevice.createVertexBufferImpl(this, format);

// marks vertex buffer as instancing data
this.instancing = false;
Expand Down
Loading