Skip to content

Commit

Permalink
fix: WIP fix for memory leak
Browse files Browse the repository at this point in the history
  • Loading branch information
davcri committed Mar 28, 2024
1 parent 29fda1b commit 8757d24
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 69 deletions.
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

0 comments on commit 8757d24

Please sign in to comment.