Guide on effectively clearing the context and canvas in three.js

We are currently developing an application that is also compatible with use on iPads, utilizing three.js version r100.

Within our application, we have a "main" component and various "popups", each equipped with its own canvas, scene, and renderer. The "main" component always remains visible and includes its own scene, etc.

In order to prevent memory-related issues, we ensure that all objects are created when a popup is opened and properly cleaned up when the popup is closed. However, we have noticed that on iPads, even after a popup is closed, the web information still displays the canvasses of those closed popups.

After opening and closing multiple popups, an error message appears about an excess of contexts ("There are too many active WebGL contexts on this page, the oldest context will be lost."). The primary context that is lost is the one for the "main" scene, followed by an attempt to lose a context for a "popup". A secondary error message is then triggered: "WebGL: INVALID_OPERATION: loseContext: context already lost." This outcome is expected since we used forceContextLoss() when closing the popup.

Upon closing a popup, the following procedures are carried out:

  • Dispose of all elements (materials, etc.) within the scene
  • Dispose of the OrbitControl
  • Dispose of the renderer
  • Execute forceContextLoss() on the renderer
  • Remove the canvas from the DOM

It is suspected that the canvas is preventing the contexts from being completely removed, but there may be something overlooked. Therefore, how can we effectively eliminate the contexts of the closed popups?

Thank you, Willem

Answer №1

If you're looking to handle multiple canvases in a more efficient way, you can consider:

(a) using a single context along with the scissor test to replicate multiple canvases (recommended)

For more information on techniques like this, you can visit this resource

or

(b) employing a virtual webgl context that mimics multiple contexts on top of a single context.

This setup allows you to have only 1 context, with others being virtual.

It's important to note that trying to force the browser to free a context is not always successful. Even triggering a context loss event does not guarantee the removal of the WebGLRenderingContext object, as it is designed to persist. When a context is lost and then restored, the same context object is used without being removed.

Therefore, there's no assurance that the browser won't simply delete the oldest context once a certain limit is reached. Typically, when new contexts are created, old ones tend to lose theirs. The decision on which context to free (whether based on least recent usage, age, resource consumption, or references) is usually left to the browser. It's not an easy task for the browser to determine which contexts to free up.

Below is a simple test involving the creation and deletion of contexts. In this test, the oldest context is lost when the 17th context is created on Chrome desktop:

'use strict';

/* global THREE */

function makeScene(canvas, color = 0x44aa88, timeout = 0) {
  
  const renderer = new THREE.WebGLRenderer({canvas: canvas});

  const fov = 75;
  const aspect = 2;  // the canvas default
  const near = 0.1;
  const far = 5;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.z = 2;

  const scene = new THREE.Scene();

  {
    const color = 0xFFFFFF;
    const intensity = 1;
    const light = new THREE.DirectionalLight(color, intensity);
    light.position.set(-1, 2, 4);
    scene.add(light);
  }

  const boxWidth = 1;
  const boxHeight = 1;
  const boxDepth = 1;
  const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);

  const material = new THREE.MeshPhongMaterial({color});

  const cube = new THREE.Mesh(geometry, material);
  scene.add(cube);

  let requestId;
  function render(time) {
    time *= 0.001;  // convert time to seconds

    cube.rotation.x = time;
    cube.rotation.y = time;

    renderer.render(scene, camera);

    requestId = requestAnimationFrame(render);
  }
  requestId = requestAnimationFrame(render);
  
  if (timeout) {
    setTimeout(() => {
      cancelAnimationFrame(requestId);
      canvas.parentElement.removeChild(canvas);
      // manually free all three objects that hold GPU resources
      geometry.dispose();
      material.dispose();
      renderer.dispose();
    }, timeout);
  }
}

makeScene(document.querySelector('#c'));

let count = 0;
setInterval(() => {
  console.log(++count);
  const canvas = document.createElement("canvas");
  document.body.appendChild(canvas);
  makeScene(canvas, Math.random() * 0xFFFFFF | 0, 500);
}, 1000);
<canvas id="c"></canvas>
  
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r98/three.min.js"></script>

If you wish to explore a similar test utilizing virtual-webgl:

'use strict';

/* global THREE */

function makeScene(canvas, color = 0x44aa88, timeout = 0) {
  
  const renderer = new THREE.WebGLRenderer({canvas: canvas});

  const fov = 75;
  const aspect = 2;  // the canvas default
  const near = 0.1;
  const far = 5;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.z = 2;

  const scene = new THREE.Scene();

  {
    const color = 0xFFFFFF;
    const intensity = 1;
    const light = new THREE.DirectionalLight(color, intensity);
    light.position.set(-1, 2, 4);
    scene.add(light);
  }

  const boxWidth = 1;
  const boxHeight = 1;
  const boxDepth = 1;
  const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);

  const material = new THREE.MeshPhongMaterial({color});

  const cube = new THREE.Mesh(geometry, material);
  scene.add(cube);

  let requestId;
  function render(time) {
    time *= 0.001;  // convert time to seconds

    cube.rotation.x = time;
    cube.rotation.y = time;

    renderer.render(scene, camera);

    requestId = requestAnimationFrame(render);
  }
  requestId = requestAnimationFrame(render);
  
  if (timeout) {
    setTimeout(() => {
      cancelAnimationFrame(requestId);
      canvas.parentElement.removeChild(canvas);
      geometry.dispose();
      material.dispose();
      const gl = renderer.context;
      renderer.dispose();
      gl.dispose(); // added by virtual-webgl
    }, timeout);
  }
}

makeScene(document.querySelector('#c'));

let count = 0;
setInterval(() => {
  console.log(++count);
  const canvas = document.createElement("canvas");
  document.body.appendChild(canvas);
  makeScene(canvas, Math.random() * 0xFFFFFF | 0, 500);
}, 1000);
<canvas id="c"></canvas>
<script src="https://greggman.github.io/virtual-webgl/src/virtual-webgl.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r98/three.min.js"></script>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

PHP: A guide on validating textboxes with jQuery AJAX

How can I use jQuery AJAX to validate a textbox on focusout? The validation process includes: Sending the value to a PHP page for server-side validation Displaying an alert message based on the validation result I have only impl ...

The code snippet $(this).nextAll("#...").eq(0).text("***") isn't functioning as expected

I am experiencing an issue with the following line of code: $(this).nextAll("#status").eq(0).text("Deleted"). I am trying to insert the text "Deleted" in a <span> tag, but it does not seem to be working... Here is my code: admin.php PHP: $sql = "SE ...

Implementing pagination in Webgrid using AJAX post method

I've developed this JavaScript code: function PartialViewLoad() { $.ajaxSetup({ cache: false }); $.ajax({ url: "/ControllerAlpha/MethodBeta", type: "GET", dataType: "html", data: { s ...

JavaScript method overloading involves defining multiple functions with the same name

In my JavaScript code, I have implemented method overloading using the following approach: function somefunction() { //1st function } function somefunction(a) { //2nd function } function somefunction(a,b) { //3rd function } somefunction(); // ...

Issue with Ionic and Angular: Struggling to empty input field

Within my Ionic app, there is a form that contains an input field and a button. Upon clicking the button, an action should occur within the controller to clear the input field. Unfortunately, this functionality is not working as expected. Despite its simpl ...

Building Your Initial HTTP Server using Node.js

Hey everyone, I'm relatively new to node.js but have made some progress. Following the steps in this tutorial, I was able to create my first "example" server. However, there are a few things that I don't quite understand. Could someone please exp ...

Injecting cascading style sheets across different domains using Javascript

Put simply: Is it possible for an external Javascript code to inject another script that is hosted on a completely different domain into the page's Document Object Model (DOM)? For example: Imagine the website foo.com which has an html script tag w ...

Replicate the functionality of a mouse scrolling event

I have incorporated Jack Moore's Wheelzoom jQuery plugin to zoom and drag an SVG image on my website. However, I also want to include manual zoom in and out buttons for the users. I am considering two options to achieve this - triggering a mouse whe ...

Having trouble parsing JSON elements separately

I am currently working on generating data to be utilized in a chart.js plot by utilizing C# Controller and javascript. The Controller method I have returns a JSONResult to a javascript function. public JsonResult GetPlansPerDoc(){ //Code to retrieve d ...

Elevate your tooltips with Bootstrap 5: enabling hoverable tooltips and clickable links

At times, you may want to include clickable links in tooltips. I recently encountered this issue while using Bootstrap 5 and had trouble finding a solution. After some trial and error, I finally figured it out and wanted to share my findings. The standard ...

Are there alternative methods for showcasing a self-written webpage on a mobile browser without utilizing localhost or purchasing a webserver?

Embarking on my thesis writing journey, I've decided to utilize Blender and Three.js for the project. Given that it will include gps functionality, I want to ensure it can be easily accessed on mobile browsers. Are there alternative methods to achieve ...

The dot operator cannot be used to access Json objects

Utilizing a4j jsFunction to transmit data to the server and receive JSON in return <a4j:jsFunction name="submitData" action="#{imageRetriveBean.saveData}" data="#{responseNodesPathsBean}" oncomplete="processData(event.data)"> <a4j:param name= ...

Using a data loader with react-router

I am currently working on a react app where I have implemented routes using the new data loaders from react-router-dom import { RouterProvider, createBrowserRouter, createRoutesFromElements, Route } from 'react-router-dom'; import Home fr ...

Achieving uniform width in material ui: Tips for maintaining consistency

I am encountering an issue with the width of my Goal components and can't figure out what is causing it. https://i.stack.imgur.com/jPlyf.png Despite setting the width of the Paper selector to 100%, each Goal component's width remains inconsiste ...

Using Javascript to pass the value of a selected checkbox

I am having an issue with passing a row value to a different function when a user clicks on a checkbox in the last column of a table. The code I have written doesn't seem to be firing as expected. Can anyone help me figure out what might be missing in ...

Tips for transferring parameters between AJAX requests

Struggling to pass parameters between two different ajax calls, I've noticed that the params only exist within the ajax scope and not outside of it. Is there another way to achieve this without calling from one ajax success section to another ajax? He ...

Utilize jQuery to toggle classes on multiple elements in web development

I've been struggling to streamline this code I created for the website's navigation. As a novice in Javascript and jQuery, I would appreciate any help or advice. Thank you! Since the page doesn't reload, I have implemented the following met ...

How can the horizontal scroll bar width be adjusted? Is it possible to create a custom

Within my div, I have implemented multiple cards that scroll horizontally using the CSS property: overflow-x: scroll; This setup results in a horizontal scrollbar appearing under the div, which serves its purpose. However, I would prefer a customized scr ...

JavaScript code utilizing Selenium to retrieve text from a Tinymce text editor

When using https://ocr.sanskritdictionary.com/ to upload an image, the copyable text appears in a Tinymce editor. I am looking for suggestions on how to copy the resulting text using Selenium JavaScript code. I have attempted to extract it using the html ...

Creating collections in a Hashtable style using JavaScript

Creating a collection in JavaScript can be done in the following way: Start by initializing an empty collection with var c = {}; Next, you can add items to it. After addition, it will look like: { 'buttonSubmit': function() { /* do some work * ...