engine.activeView only returns the second of two views

Please help me understand, what can cause this issue?
Both views exist in the same and only engine. No problem in demo, but when trying to bring this into our huge project, it always returns the second view, never the first. Is there any html-attributes for the canvas causing this? I tried z-level, size, overlaps, hidden, but no success so far.
Thanks!

Hey @8thdwarf . Is this the demo you are referring to? Babylon.js - Multi views demo
When reading the documentation for activeView there only seems to be one Engine | Babylon.js Documentation

I think if you want to get all the views you can get them here: Engine | Babylon.js Documentation It is then up to you to keep track of which view is which canvas. For example if you put 2 views and the first one is the left then views[0] will be the left.

Let’s look at how one gets a view to become active though. Looks like it’s indexing through all the views? Make sure your canvas is visible and has a camera on it? Looking directly at the source code in engine.views.ts here’s what I see.

Also for anyone’s further reference for multiple views here is the documentation link,

Finally, showing a snippet of your set up structure can also maybe help us debug a little more and see what you’re doing. :slight_smile:

2 Likes

This is the structure, I use, which works fine:

Babylon.js - Multi views demo
<script src="https://preview.babylonjs.com/babylon.js"></script>

<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">

<style>
    html,
    body {
        width: 100%;
        height: 100%;
        padding: 0;
        margin: 0;
        overflow: hidden;
        font-family: 'Open Sans';
        color: white;
        background: #2A2342;
    }



    #renderCanvas0 {

    }

    #renderCanvas1 {
      margin-left: -30px;

    }
   </style>
    <div class="container">
        <canvas class="renderCanvas" id="renderCanvas0" touch-action="none"></canvas>
        <canvas class="renderCanvas" id="renderCanvas1" touch-action="none"></canvas>
    </div>
</div>

<script>
    var createScene = function () {

      // This creates a basic Babylon Scene object (non-mesh)
      var scene = new BABYLON.Scene(engine);
      scene.createDefaultEnvironment();
      scene.ambientColor = new BABYLON.Color3(1, 1, 1);

      // This creates and positions a free camera (non-mesh)
      var camera = new BABYLON.ArcRotateCamera("Camera0", 0, 0.8, 5, new BABYLON.Vector3.Zero(), scene);
      camera.setTarget(BABYLON.Vector3.Zero());
      camera.lowerRadiusLimit = 4;
      camera.upperRadiusLimit = 20;


      var camera1 = new BABYLON.ArcRotateCamera("Camera1", 0, 0.8, 10, new BABYLON.Vector3.Zero(), scene);
      camera.attachControl(document.getElementById("renderCanvas0"), true);

      var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
      light.intensity = 0.7;
      light.diffuse = new BABYLON.Color3(1, 0, 0);
      light.specular = new BABYLON.Color3(0, 1, 0);
      light.groundColor = new BABYLON.Color3(0, 1, 0);
      // Our built-in 'sphere' shape.
      var box = BABYLON.MeshBuilder.CreateBox("Box", {size: 2}, scene);
      box.position.y = 1.0;

      const mat = new BABYLON.StandardMaterial('_Material1', scene);
      mat.ambientColor = new BABYLON.Color3(1,0,0);

      box.material = mat;
  

      // Some animations
      var alpha = 0;
      scene.registerBeforeRender(() => {
          camera1.radius = 10 + Math.cos(alpha) * 0.5;
          alpha += 0.1;
      })
      return scene;
    };
    var createScene2 = function () {

      // This creates a basic Babylon Scene object (non-mesh)
      var scene = new BABYLON.Scene(engine);
      scene.createDefaultEnvironment();
      scene.ambientColor = new BABYLON.Color3(1, 1, 1);

      // This creates and positions a free camera (non-mesh)
      var camera = new BABYLON.ArcRotateCamera("Camera0", 0, 0.8, 5, new BABYLON.Vector3.Zero(), scene);
      camera.setTarget(BABYLON.Vector3.Zero());
      camera.lowerRadiusLimit = 4;
      camera.upperRadiusLimit = 20;


      var camera1 = new BABYLON.ArcRotateCamera("Camera1", 0, 0.8, 10, new BABYLON.Vector3.Zero(), scene);
      camera.attachControl(document.getElementById("renderCanvas0"), true);

      var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
      light.intensity = 0.7;
      light.diffuse = new BABYLON.Color3(1, 0, 0);
      light.specular = new BABYLON.Color3(0, 1, 0);
      light.groundColor = new BABYLON.Color3(0, 1, 0);
      // Our built-in 'sphere' shape.
      var box = BABYLON.MeshBuilder.CreateBox("Box", {size: 2}, scene);
      box.position.y = 1.0;

      const mat = new BABYLON.StandardMaterial('_Material1', scene);
      mat.ambientColor = new BABYLON.Color3(0,1,0);

      box.material = mat;


      // Some animations
      var alpha = 0;
      scene.registerBeforeRender(() => {
          camera1.radius = 10 + Math.cos(alpha) * 0.5;
          alpha += 0.1;
      })
      return scene;
    };



    // Create a working document
    var canvas = document.createElement("canvas");

    var engine = new BABYLON.Engine(canvas, true);

    // Set the default canvas to use for events
    engine.inputElement = document.getElementById("renderCanvas0");

    var scene = createScene();
  
    var scene2 = createScene2();
    

    engine.registerView(document.getElementById("renderCanvas0"), scene.cameras[0]);
    engine.registerView(document.getElementById("renderCanvas1"), scene2.cameras[1]);

    engine.runRenderLoop(function() {

      if (engine.activeView.target.id == 'renderCanvas0'){
        scene.render();
      }
      else{
        scene2.render();
      }

    });

</script>

So, I have a multi-scene, multi-canvas, single-engine situation.
I just wonder, how could I provoke this little example to also only return one view instead of both?

Perhaps I can clarify the issue:
I register two views, exactly like in the demo code provided, but engine.activeView always returns the same view (in our big project). What can cause this behavior?

For the no longer appearing view, canvas.getContext(“2d”) is null. How can this happen?

After registering the first canvas, the context is null !
The canvas itself is valid.

  const canvas = document.getElementById('renderCanvas') as HTMLCanvasElement;
  engine.registerView(canvas,SceneManager.getInstance().current.cameras[0]);

  const a = Graphics.getInstance().engine.views[0].target;
  const context = a.getContext("2d"); // null!

could you create a quick repro in the playground ?

This sounds pretty weird to me :frowning:

1 Like

No, as it works perfectly with the html-code, I provided. This cannot be done in playground with two html-canvasses, I guess. I cannot reproduce it outside our huge project.

I now just need to understand, how the context can be null after registering the view? What must be wrong with the canvas in the first place?

If it is part of a larger project. May I suggest trying to isolate your code. Perhaps there is a dependency or extra piece I am not seeing.

Also what does

const a = Graphics.getInstance().engine.views[1].target;
const context = a.getContext("2d"); 

get? Null as well?

No, the second canvas and context is fine.
The difference is, that one is generated by code after the snippet I posted.

The four line of code above really cause the issue. Getting the canvas, calling registerView and the context is null.

What can be wrong with a canvas element causing this issue?

One of the only reason would be to reuse a canvas with a webgl context on it you need to be sure to not share them

1 Like

It’s the only visible canvas with a webgl context (Babylon) at that point. No sharing.

This is how the canvas is bound with the engine’s creation:

contextAttributes = contextAttributes || {
  alpha: true,
  depth: true,
  desynchronized: false,
  stencil: true, 
  antialias: true,
  premultipliedAlpha: true,
  preserveDrawingBuffer: true,
  powerPreference: 'high-performance',
  audioEngine: false,
};

this.engine = new Engine(canvas, contextAttributes.antialias, contextAttributes);

I wonder if maybe createScene2 is creating only 1 camera, in which case scene2.cameras[0] should be passed to registerView instead of scene2.cameras[1]? I’m just guessing though since I haven’t see the code for the createScene functions. :slightly_smiling_face:

EDIT: On the other hand, if you have 2 active cameras in scene 2, then this line from the documentation shows a potential issue:

A view cannot be rendered if it has a defined camera and the underlying scene is using multiple active cameras

1 Like

In my test case, which works fine, I added two cameras in each scene to make sure, this doesn’t change anything.

If the canvas that you’re trying to get a 2d context for is the same canvas that was passed to the Engine constructor, that would result in null being returned by getContext since the Engine constructor would have already created a webgl context on the canvas. I’m just guessing thou because IDK why else it would return null…

I wonder if you could repo in a js fiddle or code pen?

Calling getContext with different arguments on the same canvas will always return null on the second call. You said youre manually creating another canvas later in the code? I think you should provide an example of that

1 Like