pmndrs / react-three-fiber

🇨🇭 A React renderer for Three.js
https://docs.pmnd.rs/react-three-fiber
MIT License
27.62k stars 1.6k forks source link

Texture causing loss of context in ipadOS (iOS), 17.5.1 #3309

Closed micbay closed 3 months ago

micbay commented 4 months ago

I recently updated my ipad pro from, I think 16, maybe even 15, to the latest ios17.5.1.

Before the update my three js project was rendering just fine. After the update I now get an error:

TypeError: null is not an object (evaluating 'gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT).precision') [object Object]
LOGTHREE.WebGLRenderer: Context Lost.

I believe this is from the following function in the three.cjs build file.

    function getMaxPrecision( precision ) {
        if ( precision === 'highp' ) {
            if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.HIGH_FLOAT ).precision > 0 &&
                gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.HIGH_FLOAT ).precision > 0 ) {
                return 'highp';
            }
            precision = 'mediump';
        }
        if ( precision === 'mediump' ) {
            if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.MEDIUM_FLOAT ).precision > 0 &&
                gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT ).precision > 0 ) {
                return 'mediump';
            }
        }
        return 'lowp';
    }

My app is a lot to try and post, but the following linked example from some react-three-fiber, doc site that deals a lot with textures, as does my app, also exhibits the same problem. React Three Fiber Tutorials - Material Picker working example page of example with code

on an ipad with iOS 17.5.1(21F90) will produce a black screen when trying to render the linked example.

KindaSloth commented 3 months ago

I'm getting the same issue on my app.

Environment: Safari, iPad 7th Gen iOS v17.1

image

micbay commented 3 months ago

Here is a link to some more info and some comparable test codesandboxes, from discussion of this issue I'd posted on the three.js forum. discourse.threejs post. And a post on the apple forum: apple forum issue post

This issue appears to be related to recent iOS used on older ipads and with R3F in general. I no longer believe it's tied to use of a texture, but it is related to, or at minimum, exacerbated by using R3F, as using straight three.js seems to not have the same issue.

this remains an issue even with the most recent iOS 17.6.1 update

CodyJasonBennett commented 3 months ago

You should try lowering DPR as a first step, but continue with three.js or Apple support forums. R3F only deals with reconciliation and has nothing do to here.

micbay commented 3 months ago

I know it seems hard for folks to accept that R3F is affecting code to cause problems, but as best as I can, the occurrence of the problem has been isolated to usage of R3F. Something about how R3F operates is affecting code execution, timing, order of events, something, it is doing is exposing this issue.

Reposted here are multiple codesanboxes that prove this. Using react for all components, one component is built using vanilla three.js, and one using R3F. The R3F component causes a failure on older ipads and iOS 17.5 or 17.6. Probably a change to three.js, R3F, or iOS could fix this issue, but it is without a doubt an error that is encouraged by the use of R3F.

In the following sandboxes, on my ipad i can see the vanilla cube rotating fine and i can see the both ways app if i comment out the fiber cube component. I get the described null error if I try to view the fiber only or the both ways with fiber cube enabled. Basic-ThreeJS-Cubes-Bothways Basic-ThreeJS-Cube-Fiber Basic-ThreeJS-Cube-Vanilla

CodyJasonBennett commented 3 months ago

As the maintainer of R3F and three's WebGLRenderer, I am probably the most qualified to tell you that R3F does not impose memory limits or alter GPU memory, and you should heed my comment as a consequence if you expect any resolve. The error TypeError: null is not an object (evaluating 'gl.getShaderPrecisionFormat(gl.VERTEX_SHADER tells me that the browser is revoking the context and any resource creation, so this is an OS level error from the GPU device being lost or OOM generally as a safety measure. It is possible there is a memory leak in R3F we haven't seen despite scaling to massive games and software, but such is definitely not intentional and should be considered a bug.

CodyJasonBennett commented 3 months ago

Looks like the component had a memory leak where it would create a new texture every render without memoization. Here is the same thing with the 2D canvas memoized, and a CanvasTexture bound in JSX so R3F will dispose on unmount and memoize through JSX.

https://codesandbox.io/p/sandbox/basic-threejs-cube-fiber-forked-5rn4rs

micbay commented 3 months ago

I would agree this appears to be ultimately rooted in iOS, and I'm certainly not intending to present any rejection of expertise. I appreciate all help in this matter.

I still have the same issue with this forked memoized version as well. In fact the issue exhibits itself on any of the R3F examples from the R3F example page. Occasionally a render will work, I'm guessing based on the timing of renders and cache. But inevitably refreshing for clean loads results in an error and failure to render.

Were you detecting a memory leak, or predicting one based on code? I'm relatively new with three.js and react so I don't know the best practices for when to use memoization.

Regardless for this particular problem, if there is a memory leak, it doesn't appear to be the issue, if the forked sandbox you made is leak free, as the error still presents itself. ipad2017-iOS17 6 1-R3F-error

Have you been able to re-create the error on a device, did memoization resolve it for you?

CodyJasonBennett commented 3 months ago

I glanced over the code and saw it held an unstable resource to a texture.

Seeing this is not endemic to minor user error (you're creating a very small texture (256p = 64 KB), this looks to not be a memory leak at all and a device/OS issue since you can't even create a WebGL program and aren't allocating GPU memory as a result.

I would take this upstream to three or especially Apple, note the OS and device, and try updating it if possible.

micbay commented 3 months ago

Unfortunately I don't expect much attention from Apple, though I've tried, and will continue to push, making them aware of the issue. Apple iOS has already had at least 1 update without fixing this issue.

I was hoping, since R3F is such an un-obtrusive construct that it might be straight forward to see why the vanilla three.js and R3F implementations behave differently, and what could be done to make an R3F implementation, as robust against this iOS issue, as the vanilla three.js seems to be. Perhaps it's as simple as adding a null check somewhere. Though i realize this would be in three.js code, not R3F, I hoped whatever the impact that R3F is having, that vanilla doesn't, could isolate where maintainers should look.

My only notice to three.js is the forum post I linked above, if there is another place I should use to run this up the flag pole for three.js maintainers, that you know of please point me that way.

CodyJasonBennett commented 3 months ago

The only thing different in your vanilla demo is it doesn't set dpr like R3F does renderer.setPixelRatio(Math.min(2, Math.max(1, window.devicePixelRatio))) which increases the framebuffer resolution. I'd see if that creates the problem. Everything else is related to color management or pure reconciliation, so they can be ignored.

hotelcostes commented 3 months ago

Same problem. both my app and R3F example page. device: iPad Pro 10.5 os: ipadOS 17.6.1 any browser(safari, chrome, edge)

Other Apple OS(m1 mac, iPhone 15 pro) are OK.

CodyJasonBennett commented 3 months ago

See https://github.com/pmndrs/react-three-fiber/issues/3309#issuecomment-2276420872 and try decreasing DPR to lower the memory impact of the canvas framebuffer and/or antialias which may supersample on some hardware. I'm afraid we can't do anything more to help as R3F does not impact runtime behavior whatsoever, and I don't think three.js is the right place either if it's something other than memory limits you're hitting (again see my prior comments). If that is not enough, please take this to Apple who I'd strongly urge you to reach to first.

For reference, this is the configuration R3F produces. I'd whittle away at it until it matches your expectations with vanilla and apply that change to your app with R3F.

const renderer = new THREE.WebGLRenderer({
  powerPreference: 'high-performance',
  antialias: true,
  alpha: true,
})
renderer.setPixelRatio(Math.min(2, Math.max(1, devicePixelRatio)))
renderer.setSize(innerWidth, innerHeight)

const camera = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 1000)
camera.position.z = 5

//
texture.colorSpace = THREE.SRGBColorSpace
mjurczyk commented 3 months ago

Spent a moment debugging it on discord, since iPad Pros are a huge chunk of the market for CAD / drawing apps with R3F - keeping the device support list inline with vanilla three.js can be important.

By default <Canvas /> uses powerPreference: "high-performance" and antialias: true which causes iPad Pro 10.5 and iPad 8th gen to instantly lose context due to lack of memory, use the following as a work-around:

<Canvas gl={{ powerPreference: 'default', antialias: false }} />

Since dpr is set to native iPads resolution by default, there should be pretty much no visual difference. Verified on iPad Pro 10.5 with 17.5.1:

React-three-fiber vs Three 3

@hotelcostes @micbay could you verify it fixes the issue? I'll add it to a related thread on discourse of vanilla three.js then.

CodyJasonBennett commented 3 months ago

That would be antialias: false. It's quite strange powerPreference has any effect here due to the lack of discrete hardware on Mx hardware. I'd still take this up with Apple so they don't regress their iPad line which can be quite different than iOS for iPhone. We've seen regressions in support for WebGL on iOS which used to be exclusive to iPad (e.g. float textures).

hotelcostes commented 3 months ago
<Canvas gl={{ powerPreference: 'default', antialiasing: false }} />

@hotelcostes @micbay could you verify it fixes the issue? I'll add it to a related thread on discourse of vanilla three.js then.

<Canvas gl={{ powerPreference: 'default', antialias: false }} />

@mjurczyk antialiasing: false probably a typo. Works fine. thx.

micbay commented 3 months ago
<Canvas gl={{ powerPreference: 'default', antialias: false }} />

Hi, @mjurczyk , yes, changing the powerPreference to 'default', and antialias to 'false' corrects the problem in my app, on my older iPad. Thanks so much for digging in deeper, isolating the root cause, and sharing a solution, it's much appreciated.