Render targets working on external monitor only?

I’ve recently stumbled across a very odd bug: When I run my game on the laptop (a 2022 apple silicon macbook), all of the render targets get filled with a solid color or pixel junk instead of the scene I am attempting to render. However, when I plug the USB-C table into the external monitor the problem disappears. Any idea what could be causing this? I tried it on both Edge and Firefox and I get the same thing, so I know it’s not browser-specific.

From what I can tell, the problem appears to be in writing to, as opposed to reading from, the off-screen buffer - at least, when I modify the shader to display a checkerboard pattern instead of the texture, I see the pattern.

So the official post processing examples do not work on your mac book? three.js examples

The examples work. However, postprocessing is not what I’m doing. I’m using render targets for portals. (Yes the portal example does work, but my code doesn’t resemble the portal example very much. I had a hard time following the code in the example, so I ended up figuring out how to do it independently.)

At this point I’m kind of stumped as to how to go about debugging this. I don’t even have a hypothesis to test.

Just guessing, but —

  1. if you set the pixel ratio to 1 and enforce the same canvas resolution in both cases, e.g. 512x512, does anything change?
  2. I’ve seen some weird performance issues related to the “ProMotion” refresh rate on newer MBP, may be worth a try changing it

Good catch! Setting the device pixel ratio to 1 - or not setting it at all - causes the problem to disappear. Previously I was setting it to:

this.renderer.setPixelRatio(window.devicePixelRatio);

(The other suggestions had no effect).

OK, so my next question is - what should I do about this? Is this a bug that can be fixed? Or should I leave the pixel ratio unset as a workaround?

The problem might be in the way you create render target and use, for example, get/set viewport renderer functions on the target. In some cases the library internally multiplies the sizes provided in the function call by the pixel ratio, which might make your code read outside the memory allocated for the target buffer.

Thanks for the suggestion. I tried playing around with various combinations of setViewport and setPixelRatio, and didn’t find a workable combination - but then again, I don’t really know what I am doing here so that’s not surprising. Also, calling setPixelRatio each frame (for the portals) just devastates my frame rate (120fps to around 25fps).

My portal rendering code looks like this:

    if (this.destinationScene && this.isOnscreen) {
      renderer.setRenderTarget(this.renderTarget);
      renderer.setViewport(this.portalViewport);
      renderer.clippingPlanes[0].copy(this.clippingPlane);
      renderer.clear(true, true, true);
      renderer.render(this.destinationScene, this.portalCamera);
    }

BTW there’s a bug in this code I haven’t fixed yet, which is that I need to clear the shadow buffers for the directional lights before each portal render.

I had a problem with this code that I use to render on targets, it was producing an error on high dpi displays (mobile phone was a good test as I only have a normal HD display):

const renderRTVP = (rend, scene, camera, outputRT, vp, clear = true) => {
	const bool = !clear && rend.autoClear;
	if (bool) rend.autoClear = false;
	const vp_old = new THREE.Vector4();
	rend.getCurrentViewport(vp_old);
	rend.setRenderTarget(outputRT);
	rend.setViewport(vp);
	rend.render(scene, camera);
	rend.setViewport(vp_old);
	rend.setRenderTarget(null);
	if(bool) rend.autoClear = true;
};

after digging through the THREE code I found out that getCurrentViewport (as opposite to getViewport) returns viewport size in “true” pixels i.e. multiplied by devicePixelRatio.

I did some reading online about rather questionable benefits of using pixelratio and having realized that I have no idea where else in the library bowels this multiplication might occur, I banished this line of code from my projects:

renderer.setPixelRatio(window.devicePixelRatio);

instead, I now have an option to use pixel ratio as an option either via a GET variable or some UI and then I do this:

const dpr = USE_HIDPI ? (window.devicePixelRatio || 1) : 1;
renderer.setSize(Math.floor(canvas_w * dpr), Math.floor(canvas_h * dpr), false);

so far, so good :wink: