Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: WIP fix for memory leak #273

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions .storybook/stories/MemoryLeak.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { Box, Stats, useTexture } from '@react-three/drei'
import type { Meta, StoryObj } from '@storybook/react'
import React, { useEffect, useRef, useState } from 'react'
import * as THREE from 'three'
import { BackSide } from 'three'

import { useFrame, useThree } from '@react-three/fiber'
import { EffectComposer, LensFlare } from '../../src'
import { Setup } from '../Setup'

const meta = {
title: 'MemoryLeak',
component: LensFlare,
decorators: [
(Story) => (
<Setup cameraPosition={new THREE.Vector3(8, 1, 10)} cameraFov={50}>
<Stats showPanel={2} />
{Story()}
</Setup>
),
],
} satisfies Meta<typeof LensFlare>

export default meta
type Story = StoryObj<typeof meta>

export const WithPostprocessing: Story = {
render: (args) => (
<>
<color attach="background" args={['#303035']} />

<CameraSwitcher />

<directionalLight intensity={3} position={[-25, 60, -60]} />

<Box />

<SkyBox />

<EffectComposer multisampling={0}>
<LensFlare {...args} />
</EffectComposer>
</>
),
args: {},
}

export const WithoutPostprocessing: Story = {
render: (args) => (
<>
<color attach="background" args={['#303035']} />

<CameraSwitcher />

<directionalLight intensity={3} position={[-25, 60, -60]} />

<Box />

<SkyBox />
</>
),
args: {},
}

function CameraSwitcher() {
const { camera, set } = useThree()
const camRef = useRef(new THREE.OrthographicCamera())
const camDef = useRef(camera)

const keySPressedCount = useKeyPressedCount('c')

const switchCamera = () => {
const newcam = camera === camDef.current ? camRef.current : camDef.current
set(() => ({ camera: newcam }))

// log memory usage
// if ('memory' in performance) {
// console.log(((performance as any).memory.usedJSHeapSize / 1024 / 1024).toFixed(0))
// }
}

useFrame(() => {
if (keySPressedCount % 2 === 1) {
switchCamera()
}
})

return null
}

function useKeyPressedCount(key: string) {
const [count, setCount] = useState(0)

useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.key === key) {
setCount((prev) => prev + 1)
}
}

window.addEventListener('keydown', handler)

return () => window.removeEventListener('keydown', handler)
}, [])

return count
}

function SkyBox() {
const texture = useTexture('digital_painting_golden_hour_sunset.jpg')

return (
<mesh userData={{ lensflare: 'no-occlusion' }} scale={[-1, 1, 1]} castShadow={false} receiveShadow={false}>
<sphereGeometry args={[50, 32, 32]} />
<meshBasicMaterial toneMapped={false} map={texture} side={BackSide} />
</mesh>
)
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"buffer": "^6.0.3",
"maath": "^0.6.0",
"n8ao": "^1.6.6",
"postprocessing": "^6.32.1",
"postprocessing": "^6.35.2",
"three-stdlib": "^2.23.4"
},
"devDependencies": {
Expand Down
130 changes: 70 additions & 60 deletions src/EffectComposer.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import type { TextureDataType } from 'three'
import { HalfFloatType, NoToneMapping } from 'three'
import React, {
forwardRef,
useMemo,
useEffect,
useLayoutEffect,
createContext,
useRef,
useImperativeHandle,
} from 'react'
import React, { forwardRef, useMemo, useEffect, createContext, useRef, useImperativeHandle } from 'react'
import { useThree, useFrame, useInstanceHandle } from '@react-three/fiber'
import {
EffectComposer as EffectComposerImpl,
Expand All @@ -32,7 +24,7 @@ export const EffectComposerContext = createContext<{
resolutionScale?: number
}>(null!)

export type EffectComposerProps = {
export type EffectComposerProps = {
enabled?: boolean
children: JSX.Element | JSX.Element[]
depthBuffer?: boolean
Expand Down Expand Up @@ -74,66 +66,45 @@ export const EffectComposer = React.memo(
const scene = _scene || defaultScene
const camera = _camera || defaultCamera

const [composer, normalPass, downSamplingPass] = useMemo(() => {
const composer = useRef<EffectComposerImpl | undefined>()
const normalPass = useRef<NormalPass | undefined>()
const downSamplingPass = useRef<DepthDownsamplingPass | undefined>()

const group = useRef(null)
const instance = useInstanceHandle(group)

useEffect(() => {
const webGL2Available = isWebGL2Available()

// Initialize composer
const effectComposer = new EffectComposerImpl(gl, {
composer.current = new EffectComposerImpl(gl, {
depthBuffer,
stencilBuffer,
multisampling: multisampling > 0 && webGL2Available ? multisampling : 0,
frameBufferType,
})

// Add render pass
effectComposer.addPass(new RenderPass(scene, camera))
composer.current.addPass(new RenderPass(scene, camera))

// Create normal pass
let downSamplingPass = null
let normalPass = null
if (enableNormalPass) {
normalPass = new NormalPass(scene, camera)
normalPass.enabled = false
effectComposer.addPass(normalPass)
normalPass.current = new NormalPass(scene, camera)
normalPass.current.enabled = false
composer.current.addPass(normalPass.current)
if (resolutionScale !== undefined && webGL2Available) {
downSamplingPass = new DepthDownsamplingPass({ normalBuffer: normalPass.texture, resolutionScale })
downSamplingPass.enabled = false
effectComposer.addPass(downSamplingPass)
downSamplingPass.current = new DepthDownsamplingPass({
normalBuffer: normalPass.current.texture,
resolutionScale,
})
downSamplingPass.current.enabled = false
composer.current.addPass(downSamplingPass.current)
}
}

return [effectComposer, normalPass, downSamplingPass]
}, [
camera,
gl,
depthBuffer,
stencilBuffer,
multisampling,
frameBufferType,
scene,
enableNormalPass,
resolutionScale,
])

useEffect(() => composer?.setSize(size.width, size.height), [composer, size])
useFrame(
(_, delta) => {
if (enabled) {
const currentAutoClear = gl.autoClear
gl.autoClear = autoClear
if (stencilBuffer && !autoClear) gl.clearStencil()
composer.render(delta)
gl.autoClear = currentAutoClear
}
},
enabled ? renderPriority : 0
)

const group = useRef(null)
const instance = useInstanceHandle(group)
useLayoutEffect(() => {
const passes: Pass[] = []

if (group.current && instance.current && composer) {
if (group.current && instance.current && composer.current) {
const children = instance.current.objects as unknown[]

for (let i = 0; i < children.length; i++) {
Expand All @@ -158,18 +129,50 @@ export const EffectComposer = React.memo(
}
}

for (const pass of passes) composer?.addPass(pass)
for (const pass of passes) composer.current?.addPass(pass)

if (normalPass) normalPass.enabled = true
if (downSamplingPass) downSamplingPass.enabled = true
if (normalPass.current) normalPass.current.enabled = true
if (downSamplingPass.current) downSamplingPass.current.enabled = true
}

return () => {
for (const pass of passes) composer?.removePass(pass)
if (normalPass) normalPass.enabled = false
if (downSamplingPass) downSamplingPass.enabled = false
for (const pass of passes) composer.current?.removePass(pass)
if (normalPass.current) normalPass.current.enabled = false
if (downSamplingPass.current) downSamplingPass.current.enabled = false
normalPass.current?.dispose()
downSamplingPass.current?.dispose()
composer.current?.dispose()

composer.current = undefined
normalPass.current = undefined
downSamplingPass.current = undefined
}
}, [composer, children, camera, normalPass, downSamplingPass, instance])
}, [
camera,
gl,
depthBuffer,
stencilBuffer,
multisampling,
frameBufferType,
scene,
enableNormalPass,
resolutionScale,
instance,
])

useEffect(() => composer.current?.setSize(size.width, size.height), [size])
useFrame(
(_, delta) => {
if (enabled) {
const currentAutoClear = gl.autoClear
gl.autoClear = autoClear
if (stencilBuffer && !autoClear) gl.clearStencil()
composer.current?.render(delta)
gl.autoClear = currentAutoClear
}
},
enabled ? renderPriority : 0
)

// Disable tone mapping because threejs disallows tonemapping on render targets
useEffect(() => {
Expand All @@ -182,7 +185,14 @@ export const EffectComposer = React.memo(

// Memoize state, otherwise it would trigger all consumers on every render
const state = useMemo(
() => ({ composer, normalPass, downSamplingPass, resolutionScale, camera, scene }),
() => ({
composer: composer.current!,
normalPass: normalPass.current!,
downSamplingPass: downSamplingPass.current!,
resolutionScale,
camera,
scene,
}),
[composer, normalPass, downSamplingPass, resolutionScale, camera, scene]
)

Expand Down
8 changes: 4 additions & 4 deletions src/effects/LensFlare.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@ const LensFlareShader = {
float rShp(vec2 p, int N){float f;float a=atan(p.x,p.y)+.2;float b=6.28319/float(N);f=smoothstep(.5,.51, cos(floor(.5+a/b)*b-a)*length(p.xy)* 2.0 -ghostScale);return f;}
vec3 drC(vec2 p, float zsi, float dCy, vec3 clr, vec3 clr2, float ams2, vec2 esom){float l = length(p + esom*(ams2*2.))+zsi/2.;float l2 = length(p + esom*(ams2*4.))+zsi/3.;float c = max(0.01-pow(length(p + esom*ams2), zsi*ghostScale), 0.0)*10.;float c1 = max(0.001-pow(l-0.3, 1./40.)+sin(l*20.), 0.0)*3.;float c2 = max(0.09/pow(length(p-esom*ams2/.5)*1., .95), 0.0)/20.;float s = max(0.02-pow(rShp(p*5. + esom*ams2*5. + dCy, 6) , 1.), 0.0)*1.5;clr = cos(vec3(0.44, .24, .2)*8. + ams2*4.)*0.5+.5;vec3 f = c*clr;f += c1*clr;f += c2*clr;f += s*clr;return f-0.01;}
vec4 geLC(float x){return vec4(vec3(mix(mix(mix(mix(mix(mix(mix(mix(mix(mix(mix(mix(mix(mix(mix(vec3(0., 0., 0.),vec3(0., 0., 0.), smoothstep(0.0, 0.063, x)),vec3(0., 0., 0.), smoothstep(0.063, 0.125, x)),vec3(0.0, 0., 0.), smoothstep(0.125, 0.188, x)),vec3(0.188, 0.131, 0.116), smoothstep(0.188, 0.227, x)),vec3(0.31, 0.204, 0.537), smoothstep(0.227, 0.251, x)),vec3(0.192, 0.106, 0.286), smoothstep(0.251, 0.314, x)),vec3(0.102, 0.008, 0.341), smoothstep(0.314, 0.392, x)),vec3(0.086, 0.0, 0.141), smoothstep(0.392, 0.502, x)),vec3(1.0, 0.31, 0.0), smoothstep(0.502, 0.604, x)),vec3(.1, 0.1, 0.1), smoothstep(0.604, 0.643, x)),vec3(1.0, 0.929, 0.0), smoothstep(0.643, 0.761, x)),vec3(1.0, 0.086, 0.424), smoothstep(0.761, 0.847, x)),vec3(1.0, 0.49, 0.0), smoothstep(0.847, 0.89, x)),vec3(0.945, 0.275, 0.475), smoothstep(0.89, 0.941, x)),vec3(0.251, 0.275, 0.796), smoothstep(0.941, 1.0, x))),1.0);}
float diTN(vec2 p){vec2 f = fract(p);f = (f * f) * (3.0 - (2.0 * f));float n = dot(floor(p), vec2(1.0, 157.0));vec4 a = fract(sin(vec4(n + 0.0, n + 1.0, n + 157.0, n + 158.0)) * 43758.5453123);return mix(mix(a.x, a.y, f.x), mix(a.z, a.w, f.x), f.y);}
float fbm(vec2 p){const mat2 m = mat2(0.80, -0.60, 0.60, 0.80);float f = 0.0;f += 0.5000*diTN(p); p = m*p*2.02;f += 0.2500*diTN(p); p = m*p*2.03;f += 0.1250*diTN(p); p = m*p*2.01;f += 0.0625*diTN(p);return f/0.9375;}
float diTN(vec2 p){vec2 f = fract(p);f = (f * f) * (3.0 - (2.0 * f));float n = dot(floor(p), vec2(1.0, 157.0));vec4 a = fract(sin(vec4(n + 0.0, n + 1.0, n + 157.0, n + 158.0)) * 43758.5453123);return mix(mix(a.x, a.y, f.x), mix(a.z, a.w, f.x), f.y);}
float fbm(vec2 p){const mat2 m = mat2(0.80, -0.60, 0.60, 0.80);float f = 0.0;f += 0.5000*diTN(p); p = m*p*2.02;f += 0.2500*diTN(p); p = m*p*2.03;f += 0.1250*diTN(p); p = m*p*2.01;f += 0.0625*diTN(p);return f/0.9375;}
vec4 geLS(vec2 p){vec2 pp = (p - vec2(0.5)) * 2.0;float a = atan(pp.y, pp.x);vec4 cp = vec4(sin(a * 1.0), length(pp), sin(a * 13.0), sin(a * 53.0));float d = sin(clamp(pow(length(vec2(0.5) - p) * 0.5 + haloScale /2., 5.0), 0.0, 1.0) * 3.14159);vec3 c = vec3(d) * vec3(fbm(cp.xy * 16.0) * fbm(cp.zw * 9.0) * max(max(max(max(0.5, sin(a * 1.0)), sin(a * 3.0) * 0.8), sin(a * 7.0) * 0.8), sin(a * 9.0) * 10.6));c *= vec3(mix(2.0, (sin(length(pp.xy) * 256.0) * 0.5) + 0.5, sin((clamp((length(pp.xy) - 0.875) / 0.1, 0.0, 1.0) + 0.0) * 2.0 * 3.14159) * 1.5) + 0.5) * 0.3275;return vec4(vec3(c * 1.0), d);}
vec4 geLD(vec2 p){p.xy += vec2(fbm(p.yx * 3.0), fbm(p.yx * 2.0)) * 0.0825;vec3 o = vec3(mix(0.125, 0.25, max(max(smoothstep(0.1, 0.0, length(p - vec2(0.25))),smoothstep(0.4, 0.0, length(p - vec2(0.75)))),smoothstep(0.8, 0.0, length(p - vec2(0.875, 0.125))))));o += vec3(max(fbm(p * 1.0) - 0.5, 0.0)) * 0.5;o += vec3(max(fbm(p * 2.0) - 0.5, 0.0)) * 0.5;o += vec3(max(fbm(p * 4.0) - 0.5, 0.0)) * 0.25;o += vec3(max(fbm(p * 8.0) - 0.75, 0.0)) * 1.0;o += vec3(max(fbm(p * 16.0) - 0.75, 0.0)) * 0.75;o += vec3(max(fbm(p * 64.0) - 0.75, 0.0)) * 0.5;return vec4(clamp(o, vec3(0.15), vec3(1.0)), 1.0);}
vec4 txL(sampler2D tex, vec2 xtC){if(((xtC.x < 0.) || (xtC.y < 0.)) || ((xtC.x > 1.) || (xtC.y > 1.))){return vec4(0.0);}else{return texture(tex, xtC); }}
vec4 txD(sampler2D tex, vec2 xtC, vec2 dir, vec3 ditn) {return vec4(txL(tex, (xtC + (dir * ditn.r))).r,txL(tex, (xtC + (dir * ditn.g))).g,txL(tex, (xtC + (dir * ditn.b))).b,1.0);}
vec4 strB(){vec2 aspXtc = vec2(1.0) - (((vxtC - vec2(0.5)) * vec2(1.0)) + vec2(0.5)); vec2 xtC = vec2(1.0) - vxtC; vec2 ghvc = (vec2(0.5) - xtC) * 0.3 - lensPosition; vec2 ghNm = normalize(ghvc * vec2(1.0)) * vec2(1.0);vec2 haloVec = normalize(ghvc) * 0.6;vec2 hlNm = ghNm * 0.6;vec2 texelSize = vec2(1.0) / vec2(iResolution.xy);vec3 ditn = vec3(-(texelSize.x * 1.5), 0.2, texelSize.x * 1.5);vec4 c = vec4(0.0);for (int i = 0; i < 8; i++) {vec2 offset = xtC + (ghvc * float(i));c += txD(lensDirtTexture, offset, ghNm, ditn) * pow(max(0.0, 1.0 - (length(vec2(0.5) - offset) / length(vec2(0.5)))), 10.0);}vec2 uyTrz = xtC + hlNm; return (c * geLC((length(vec2(0.5) - aspXtc) / length(vec2(haloScale))))) +(txD(lensDirtTexture, uyTrz, ghNm, ditn) * pow(max(0.0, 1.0 - (length(vec2(0.5) - uyTrz) / length(vec2(0.5)))), 10.0));}
vec4 strB(){vec2 aspXtc = vec2(1.0) - (((vxtC - vec2(0.5)) * vec2(1.0)) + vec2(0.5)); vec2 xtC = vec2(1.0) - vxtC; vec2 ghvc = (vec2(0.5) - xtC) * 0.3 - lensPosition; vec2 ghNm = normalize(ghvc * vec2(1.0)) * vec2(1.0);vec2 haloVec = normalize(ghvc) * 0.6;vec2 hlNm = ghNm * 0.6;vec2 texelSize = vec2(1.0) / vec2(iResolution.xy);vec3 ditn = vec3(-(texelSize.x * 1.5), 0.2, texelSize.x * 1.5);vec4 c = vec4(0.0);for (int i = 0; i < 8; i++) {vec2 offset = xtC + (ghvc * float(i));c += txD(lensDirtTexture, offset, ghNm, ditn) * pow(max(0.0, 1.0 - (length(vec2(0.5) - offset) / length(vec2(0.5)))), 10.0);}vec2 uyTrz = xtC + hlNm; return (c * geLC((length(vec2(0.5) - aspXtc) / length(vec2(haloScale))))) +(txD(lensDirtTexture, uyTrz, ghNm, ditn) * pow(max(0.0, 1.0 - (length(vec2(0.5) - uyTrz) / length(vec2(0.5)))), 10.0));}
void mainImage(vec4 v,vec2 r,out vec4 i){vec2 g=r-.5;g.y*=iResolution.y/iResolution.x;vec2 l=lensPosition*.5;l.y*=iResolution.y/iResolution.x;vec3 f=mLs(g,l)*20.*colorGain/256.;if(aditionalStreaks){vec3 o=vec3(.9,.2,.1),p=vec3(.3,.1,.9);for(float n=0.;n<10.;n++)f+=drC(g,pow(rnd(n*2e3)*2.8,.1)+1.41,0.,o+n,p+n,rnd(n*20.)*3.+.2-.5,lensPosition);}if(secondaryGhosts){vec3 n=vec3(0);n+=rHx(g,-lensPosition*.25,ghostScale*1.4,vec3(.25,.35,0));n+=rHx(g,lensPosition*.25,ghostScale*.5,vec3(1,.5,.5));n+=rHx(g,lensPosition*.1,ghostScale*1.6,vec3(1));n+=rHx(g,lensPosition*1.8,ghostScale*2.,vec3(0,.5,.75));n+=rHx(g,lensPosition*1.25,ghostScale*.8,vec3(1,1,.5));n+=rHx(g,-lensPosition*1.25,ghostScale*5.,vec3(.5,.5,.25));n+=fpow(1.-abs(distance(lensPosition*.8,g)-.7),.985)*colorGain/2100.;f+=n;}if(starBurst){vxtC=g+.5;vec4 n=geLD(g);float o=1.-clamp(0.5,0.,.5)*2.;n+=mix(n,pow(n*2.,vec4(2))*.5,o);float s=(g.x+g.y)*(1./6.);vec2 d=mat2(cos(s),-sin(s),sin(s),cos(s))*vxtC;n+=geLS(d)*2.;f+=clamp(n.xyz*strB().xyz,.01,1.);}i=enabled?vec4(mix(f,vec3(0),opacity)+v.xyz,v.w):vec4(v);}
`,
}
Expand Down Expand Up @@ -183,6 +183,6 @@ export const LensFlare = forwardRef<LensFlareEffect, LensFlareProps>(
}
}, [effect, viewport])

return <primitive ref={ref} object={effect} dispose={null} />
return <primitive ref={ref} object={effect} />
}
)
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8394,10 +8394,10 @@ postcss@^8.4.23:
picocolors "^1.0.0"
source-map-js "^1.0.2"

postprocessing@^6.32.1:
version "6.32.1"
resolved "https://registry.yarnpkg.com/postprocessing/-/postprocessing-6.32.1.tgz#a91fa4101246620e12113cded7028d9e4b504845"
integrity sha512-GiUv5vN/QCWnPJ3DdYPYn/4V1amps94T/0jFPSUL40KfaLCkfE9yPudzTtJJQjs168QNpwkmnvYF9RcP3HiAWA==
postprocessing@^6.35.2:
version "6.35.2"
resolved "https://registry.yarnpkg.com/postprocessing/-/postprocessing-6.35.2.tgz#7a7b42f7d3cc21cd2fded2505af645bf62276716"
integrity sha512-yGmidrVzA1dSEmExYGgWOGcRvyOVahvurNo9iuzOonRCY6f1hnJe6/HMVSnKV9ppjLtCTqzZOI9iz8CACkmijw==

potpack@^1.0.1:
version "1.0.2"
Expand Down
Loading