Skip to content

Commit

Permalink
Allow to provide a canvas to be used for the game rendering instead o…
Browse files Browse the repository at this point in the history
…f creating a new one (#7199)

Only show in developer changelog
  • Loading branch information
danvervlad authored Nov 29, 2024
1 parent d110e83 commit 35fd7e9
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 14 deletions.
46 changes: 34 additions & 12 deletions GDJS/Runtime/pixi-renderers/runtimegame-pixi-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,29 @@ namespace gdjs {
}

/**
* Create a standard canvas inside canvasArea.
* Create the canvas on which the game will be rendered, inside the specified DOM element, and
* setup the rendering of the game.
* If you want to use your own canvas, use `initializeForCanvas` instead.
*
* @param parentElement The parent element to which the canvas will be added.
*/
createStandardCanvas(parentElement: HTMLElement) {
this._throwIfDisposed();

let gameCanvas: HTMLCanvasElement;
const gameCanvas = document.createElement('canvas');
parentElement.appendChild(gameCanvas);

this.initializeForCanvas(gameCanvas);
}

/**
* Setup the rendering of the game to use a canvas that was already created.
* @param gameCanvas The canvas to use.
*/
initializeForCanvas(gameCanvas: HTMLCanvasElement): void {
this._throwIfDisposed();

if (typeof THREE !== 'undefined') {
gameCanvas = document.createElement('canvas');
this._threeRenderer = new THREE.WebGLRenderer({
canvas: gameCanvas,
antialias:
Expand Down Expand Up @@ -99,20 +113,17 @@ namespace gdjs {
backgroundAlpha: 0,
// TODO (3D): add a setting for pixel ratio (`resolution: window.devicePixelRatio`)
});

gameCanvas = this._threeRenderer.domElement;
} else {
// Create the renderer and setup the rendering area.
// "preserveDrawingBuffer: true" is needed to avoid flickering
// and background issues on some mobile phones (see #585 #572 #566 #463).
this._pixiRenderer = PIXI.autoDetectRenderer({
width: this._game.getGameResolutionWidth(),
height: this._game.getGameResolutionHeight(),
view: gameCanvas,
preserveDrawingBuffer: true,
antialias: false,
}) as PIXI.Renderer;

gameCanvas = this._pixiRenderer.view as HTMLCanvasElement;
}

// Deactivating accessibility support in PixiJS renderer, as we want to be in control of this.
Expand All @@ -121,7 +132,6 @@ namespace gdjs {
delete this._pixiRenderer.plugins.accessibility;

// Add the renderer view element to the DOM
parentElement.appendChild(gameCanvas);
this._gameCanvas = gameCanvas;

gameCanvas.style.position = 'absolute';
Expand Down Expand Up @@ -160,7 +170,7 @@ namespace gdjs {
// but it seems not to affect us as the `domElementsContainer` has `pointerEvents` set to `none`.
domElementsContainer.style['-webkit-user-select'] = 'none';

parentElement.appendChild(domElementsContainer);
gameCanvas.parentNode?.appendChild(domElementsContainer);
this._domElementsContainer = domElementsContainer;

this._resizeCanvas();
Expand Down Expand Up @@ -938,14 +948,26 @@ namespace gdjs {
}

/**
* Dispose PixiRenderer, ThreeRenderer and remove canvas from DOM.
* Dispose the renderers (PixiJS and/or Three.js) as well as DOM elements
* used for the game (the canvas, if specified, and the additional DOM container
* created on top of it to allow display HTML elements, for example for text inputs).
*
* @param removeCanvas If true, the canvas will be removed from the DOM.
*/
dispose() {
this._pixiRenderer?.destroy(true);
dispose(removeCanvas?: boolean) {
this._pixiRenderer?.destroy();
this._threeRenderer?.dispose();
this._pixiRenderer = null;
this._threeRenderer = null;

if (removeCanvas && this._gameCanvas) {
this._gameCanvas.parentNode?.removeChild(this._gameCanvas);
}

this._gameCanvas = null;
this._domElementsContainer?.parentNode?.removeChild(
this._domElementsContainer
);
this._domElementsContainer = null;
this._wasDisposed = true;
}
Expand Down
5 changes: 3 additions & 2 deletions GDJS/Runtime/runtimegame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -976,11 +976,12 @@ namespace gdjs {
/**
* Stop game loop, unload all scenes, dispose renderer and resources.
* After calling this method, the RuntimeGame should not be used anymore.
* @param removeCanvas If true, the canvas will be removed from the DOM.
*/
dispose(): void {
dispose(removeCanvas?: boolean): void {
this._renderer.stopGameLoop();
this._sceneStack.dispose();
this._renderer.dispose();
this._renderer.dispose(removeCanvas);
this._resourcesLoader.dispose();

this._wasDisposed = true;
Expand Down
59 changes: 59 additions & 0 deletions GDJS/tests/tests/game-canvas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
describe('gdjs.RuntimeGameRenderer canvas tests', () => {
let runtimeGame;
let renderer;
let gameContainer;

beforeEach(() => {
runtimeGame = gdjs.getPixiRuntimeGame();
renderer = runtimeGame.getRenderer();
gameContainer = document.createElement('div');
});

it('should correctly create standard canvas and domElementsContainer', () => {
renderer.createStandardCanvas(gameContainer);

const actualGameCanvas = renderer.getCanvas();
const actualDomElementsContainer = renderer.getDomElementContainer();

expect(actualGameCanvas).to.not.be(null);
expect(actualDomElementsContainer).to.not.be(null);
expect(actualGameCanvas.parentElement).to.be(gameContainer);
expect(actualDomElementsContainer.parentElement).to.be(gameContainer);
});

it('should correctly initialize external canvas and create domElementsContainer', () => {
const gameCanvas = document.createElement('canvas');
gameContainer.appendChild(gameCanvas);
renderer.initializeForCanvas(gameCanvas);

const actualGameCanvas = renderer.getCanvas();
const actualDomElementsContainer = renderer.getDomElementContainer();

expect(actualGameCanvas).to.not.be(null);
expect(actualDomElementsContainer).to.not.be(null);
expect(actualGameCanvas).to.be(gameCanvas);
expect(actualDomElementsContainer.parentElement).to.be(gameContainer);
});

it('should remove canvas and domElementsContainer on dispose', () => {
renderer.createStandardCanvas(gameContainer);

const actualGameCanvas = renderer.getCanvas();
const actualDomElementsContainer = renderer.getDomElementContainer();

expect(actualGameCanvas).to.not.be(null);
expect(actualDomElementsContainer).to.not.be(null);
expect(actualGameCanvas.parentElement).to.be(gameContainer);
expect(actualDomElementsContainer.parentElement).to.be(gameContainer);

runtimeGame.dispose(true);

const actualGameCanvasAfterDispose = renderer.getCanvas();
const actualDomElementsContainerAfterDispose = renderer.getDomElementContainer();

expect(actualGameCanvasAfterDispose).to.be(null);
expect(actualDomElementsContainerAfterDispose).to.be(null);

expect(gameContainer.childNodes.length).to.be(0);
});
});

0 comments on commit 35fd7e9

Please sign in to comment.