What they do
OrthographicCamera and PerspectiveCamera share one code path — when given children they render the scene into an FBO from the camera's point of view:
src/core/Cameras/PerspectiveCamera/PerspectiveCamera.tsx:77-85 (and the orthographic twin at :79-87):
useFrame((state) => {
...
state.gl.setRenderTarget(fbo)
...
state.gl.render(state.scene, cameraRef.current)
...
state.gl.setRenderTarget(null)
})
Filed as one issue because the two files are near-identical here; fix one, apply to both.
What to check
state.gl is deprecated in r3f v10 and warns on every access — these should be state.renderer. That is not cosmetic here: it is two accesses per camera per frame.
setRenderTarget(null) unconditionally restores to the default framebuffer rather than to whatever was previously bound. Under View, RenderTexture or a post-processing pipeline that is already wrong on WebGL; on WebGPU it may fail differently. Compare with RenderTexture, which does save and restore properly.
- Whether the FBO created here is a
WebGLRenderTarget or a RenderTarget under the WebGPU build — it comes from Fbo, which claims to handle both.
Shared context (identical across all seven)
These components are classified agnostic — they live in core/, so they are assumed to work on both renderers. That assumption held for 104 of the 106 agnostic components when swept: 73 never touch the renderer at all, and 31 touch only members both renderers have. These seven are different: they drive their own render calls, and that is where semantics diverge even when the API surface matches. See #2818.
setRenderTarget(renderTarget, activeCubeFace = 0, activeMipmapLevel = 0) has an identical signature on both (Renderer.js:2578 vs WebGLRenderer.js:2890), so that part is fine.
render() is where they differ — three/src/renderers/common/Renderer.js:1362:
render( scene, camera ) {
if ( this._initialized === false ) {
throw new Error( 'THREE.Renderer: .render() called before the backend is initialized. Use "await renderer.init();" before rendering.' );
}
this._renderScene( scene, camera );
}
WebGL has no such gate. A render() that happens before the backend is ready throws on WebGPU and silently works on WebGL.
This is the class of bug that produced #2809: Preload calls gl.compile(), which on WebGPU is a getter aliasing compileAsync — same name, returns an unawaited promise, so preloading never happens. Grep cannot find these; they need reading.
Acceptance
Read the component against the WebGPU Renderer implementation and either confirm it is correct or fix it and say what was wrong. A story exercising the render path on WebGPU is the evidence — note Setup sets frameloop: 'never' under test, so useFrame does not run there and a green story proves nothing about this.
What they do
OrthographicCameraandPerspectiveCamerashare one code path — when given children they render the scene into an FBO from the camera's point of view:src/core/Cameras/PerspectiveCamera/PerspectiveCamera.tsx:77-85(and the orthographic twin at:79-87):Filed as one issue because the two files are near-identical here; fix one, apply to both.
What to check
state.glis deprecated in r3f v10 and warns on every access — these should bestate.renderer. That is not cosmetic here: it is two accesses per camera per frame.setRenderTarget(null)unconditionally restores to the default framebuffer rather than to whatever was previously bound. UnderView,RenderTextureor a post-processing pipeline that is already wrong on WebGL; on WebGPU it may fail differently. Compare withRenderTexture, which does save and restore properly.WebGLRenderTargetor aRenderTargetunder the WebGPU build — it comes fromFbo, which claims to handle both.Shared context (identical across all seven)
These components are classified
agnostic— they live incore/, so they are assumed to work on both renderers. That assumption held for 104 of the 106 agnostic components when swept: 73 never touch the renderer at all, and 31 touch only members both renderers have. These seven are different: they drive their own render calls, and that is where semantics diverge even when the API surface matches. See #2818.setRenderTarget(renderTarget, activeCubeFace = 0, activeMipmapLevel = 0)has an identical signature on both (Renderer.js:2578vsWebGLRenderer.js:2890), so that part is fine.render()is where they differ —three/src/renderers/common/Renderer.js:1362:WebGL has no such gate. A
render()that happens before the backend is ready throws on WebGPU and silently works on WebGL.This is the class of bug that produced #2809:
Preloadcallsgl.compile(), which on WebGPU is a getter aliasingcompileAsync— same name, returns an unawaited promise, so preloading never happens. Grep cannot find these; they need reading.Acceptance
Read the component against the WebGPU
Rendererimplementation and either confirm it is correct or fix it and say what was wrong. A story exercising the render path on WebGPU is the evidence — noteSetupsetsframeloop: 'never'under test, souseFramedoes not run there and a green story proves nothing about this.