EffectComposer performance

In my app I use React with vanilla ThreeJS (yes, no react-three-fiber, please don’t advise it). There I have a function that creates EffectComposer:

const bufferSize = new Vector2();
export const createComposer = (renderer: WebGLRenderer) => {
  let renderTarget = undefined;
  const size = renderer.getDrawingBufferSize(bufferSize);
  renderTarget = new WebGLRenderTarget(size.width, size.height, {
    samples: 8,
  return {composer: new EffectComposer(renderer, renderTarget), target: renderTarget};

I use the created composer in another component and handle resize there if needed (for that reason I return target here).

Everything works, but what bothers me is if I create composer on every re-render it yields better frame rate than if I memorize it:

const { composer, target } = useMemo(() => createComposer(renderer), []); // Slow!
const { composer } = createComposer(renderer); // Fast. Also no need to handle resize since it is recreated.

When composer is re-created on every re-render I call dispose() for it.
So, my question is why it behaves like this? How can I optimize the memory allocation and performance at the same time?

What does re-render mean? I suppose you don’t have an animation loop and render on demand, right?

It would be strange if the disposal and re-creation would yield a better performance. That should definitely be investigated. Are you able to demonstrate the issue with a live example?

By re-render I mean re-render of the react component. Also, I do have animation loop. If to simplify, I have the following:

export type BaseSceneObjects = {
  scene: THREE.Scene;
  renderer: WebGLRenderer;
  camera: PerspectiveCamera;
  renderingObject: Object3D;
  renderingObjects: Object3D[];
  viewerTexture?: Texture;

export function Viewer(baseScene: BaseSceneObjects) {
    const { composer, target } =
    // useMemo(() => createComposer(baseScene.renderer), []); // Executed only once when comonent is created. Slow!
    createComposer(baseScene.renderer); // Executed whenever React re-renders the component. Fast :/

    updateSize(camera, renderer, composer, target, width, height);

    const postPasses = useMemo(() => initPostprocessingPasses(scene, camera), []); // Calls function to create post-processing passes once and memorizes the value.

      (composer: EffectComposer, postPasses: PostProcessingPasses) =>
        initPostprocessing(composer, postPasses),
    )(composer, postPasses);

    const stats = useMemo(() => new FpsStats(), []);
    renderer.setAnimationLoop(() => {
      if (!isMobile) {
        handleFps(stats, postPasses);

    useEffect(() => {
     // Some code here
      return () => {
    }, []);

    return (<div>....</div>)

const initPostprocessingPasses = (
  scene: Scene,
  camera: PerspectiveCamera
): PostProcessingPasses => {
  return {
    taaPass: new TAARenderPass(scene, camera),
    outputPass: new OutputPass(),
    renderPass: new RenderPass(scene, camera),

const initPostprocessing = (
  composer: EffectComposer,
  postprocessingPasses: PostProcessingPasses
) => {
  //... Some code here

const updateSize = (
  camera: PerspectiveCamera,
  renderer: WebGLRenderer,
  composer: EffectComposer,
  target: WebGLRenderTarget,
  width: number,
  height: number
) => {
  camera.aspect = width / height;
  renderer.setSize(width, height);
  target.setSize(width, height);
  composer.setSize(width, height);

I will try to demonstrate it in the fiddle, please let me some time.

1 Like

Hi @Mugen87 ,

Please find the promised code here: GitHub - SSopin/ComposerTest: Test code for ThreeJS composer paired with React.

Most likely I am doing something wrong, but it is suspicious.

Hi @Mugen87,

Could you please suggest if you had a chance to check the code? I am curious to hear your verdict :slight_smile:

You’re probably best off creating a codesandbox from the repo as a live demonstration of the issue, it would be much easier to access and debug than having to download and install your entire project…

1 Like

Thanks @Lawrence3DPK . Here it is the link. Please let me know if it works, I am new to codesandbox :slight_smile:

There seems to be an issue with the reproduction test case. The default code runs and outputs the average FPS on the screen. But when I switch to the other setting, meaning:

  //const composerAndTarget = createComposer(renderer); // Fast
  useMemo(() => createComposer(renderer), []); // Slow

I get a runtime error:

Cannot find name ‘composerAndTarget’.

Hi @Mugen87 ,

Sorry for the confusion. Another mode should look following:

const composerAndTarget = useMemo(() => createComposer(renderer), []); // Slow

I updated the code, please check again once you can. Thanks.

Hi @Mugen87,

Could you please suggest if the code I provided works now? We just shouldn’t have commented this part: const composerAndTarget = , but I duplicated it there, so now you can comment the full line.

Strange, I can’t update the codesandbox anymore. It requires a sign in now which wasn’t necessary last time. Did something change in the sandbox configuration?

Not really, I didn’t update the configs. Just one line of code.
However, it seems you need to be logged in and I need to share it with you personally using the your username or email to be able to edit, otherwise it is read-only. Is there any other way I can share it?

Since I don’t want to log in, I have just downloaded your code and tried to debug it locally. I can reproduce the drop in performance but I’m unable to find a reason for this in the three.js code base. There must be a react related root cause. However, I’m not familiar enough with react to further investigate the issue.

If you believe the issue comes from the three.js side, it would be best to share a reproduction test case with plain three.js (meaning without react or any other dependencies).

1 Like

Got it. Thanks a lot! I will try to investigate the issue and let you know if I find something related to three.js there.