|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- import {Class} from 'ts-browser-helpers'
- import {createRenderTargetKey, CreateRenderTargetOptions, IRenderTarget} from './RenderTarget'
- import {
- BaseEvent,
- ClampToEdgeWrapping,
- DepthFormat,
- DepthTexture,
- EventDispatcher,
- LinearFilter,
- LinearMipMapLinearFilter,
- NoColorSpace,
- RGBAFormat,
- Texture,
- UnsignedByteType,
- UnsignedIntType,
- Vector2,
- WebGLCubeRenderTarget,
- WebGLMultipleRenderTargets,
- WebGLRenderTarget,
- WebGLRenderTargetOptions,
- } from 'three'
-
- export abstract class RenderTargetManager<E extends BaseEvent = BaseEvent, ET extends string = string> extends EventDispatcher<E, ET> {
- abstract isWebGL2: boolean
- abstract readonly renderSize: Vector2
- abstract renderScale: number
-
- private _trackedTargets: IRenderTarget[] = []
- private _trackedTempTargets: IRenderTarget[] = []
- private _releasedTempTargets: Record<string, IRenderTarget[]> = {}
-
- readonly maxTempPerKey = 5
-
- protected constructor() {
- super()
- this._processNewTarget = this._processNewTarget.bind(this)
- this._processNewTempTarget = this._processNewTempTarget.bind(this)
- this.trackTarget = this.trackTarget.bind(this)
- this.disposeTarget = this.disposeTarget.bind(this)
- this.createTarget = this.createTarget.bind(this)
- this.createTargetCustom = this.createTargetCustom.bind(this)
-
- }
-
- trackTarget(target: IRenderTarget) {
- this._trackedTargets.push(target)
- }
-
- removeTrackedTarget(target: IRenderTarget) {
- const ind = this._trackedTargets.indexOf(target)
- if (ind >= 0)
- this._trackedTargets.splice(ind, 1)
- }
-
- createTarget<T extends IRenderTarget = IRenderTarget>({
- sizeMultiplier = undefined,
- samples = 0,
- colorSpace = NoColorSpace,
- type = UnsignedByteType,
- format = RGBAFormat,
- depthBuffer = true,
- depthTexture = false,
- depthTextureType = UnsignedIntType,
- depthTextureFormat = DepthFormat,
- size = undefined,
- textureCount = 1,
- ...op
- }: CreateRenderTargetOptions = {}, trackTarget = true): T {
- if (!this.isWebGL2) samples = 0
- if (sizeMultiplier !== undefined && size !== undefined)
- console.error('Both sizeMultiplier and size are defined. sizeMultiplier will be ignored.')
- size = size || this.renderSize.clone().multiplyScalar(this.renderScale * (sizeMultiplier = sizeMultiplier || 1))
- size.width = Math.floor(size.width)
- size.height = Math.floor(size.height)
- const depthTex = depthTexture ? new DepthTexture(size.width, size.height, depthTextureType) : undefined
- if (depthTex) depthTex.format = depthTextureFormat
- const target = this.createTargetCustom<T>(textureCount > 1 ? {
- width: size.width,
- height: size.height,
- count: textureCount,
- } : size,
- {samples, colorSpace, type, format, depthBuffer, depthTexture: depthTex},
- textureCount > 1 ? WebGLMultipleRenderTargets as any : WebGLRenderTarget)
- this._processNewTarget(target, sizeMultiplier, trackTarget)
- this._setTargetOptions(target, op)
- return target
- }
-
- /**
- * Dispose and remove tracked target. Release target in-case of temporary target.
- * To just dispose from the GPU memory and keep reference, call `target.dispose()` or `target.dispose(false)`
- * @param target
- * @param remove
- */
- disposeTarget(target: IRenderTarget, remove = true): void {
- if (!target) return
- if (target.isTemporary) return this.releaseTempTarget(target)
- if (remove) this.removeTrackedTarget(target)
- // @ts-expect-error internal, not in types
- target.dispose(false) // false is not required but still passing so that it doesnt cause infinite loop in future.
- }
-
- getTempTarget<T extends IRenderTarget = IRenderTarget>(op: CreateRenderTargetOptions = {}): T {
- const key = createRenderTargetKey(op)
- let target: T | undefined
- if (this._releasedTempTargets[key]?.length) target = this._releasedTempTargets[key].pop() as T
- if (!target) {
- target = this.createTarget<T>(op)
- this._processNewTempTarget(target, key)
- } else {
- this._setTargetOptions(target, op)
- }
- return target
- }
-
- releaseTempTarget(target: IRenderTarget): void {
- const key = target.targetKey
- if (!key || !target.isTemporary) {
- throw 'Not a temp target'
- }
- if (this._releasedTempTargets[key].length > this.maxTempPerKey) {
- this.removeTrackedTarget(target)
- target.dispose()
- } else this._releasedTempTargets[key].push(target)
- }
-
-
- createTargetCustom<T extends IRenderTarget>({
- width,
- height,
- count,
- }: {width: number, height: number, count?: number}, options: WebGLRenderTargetOptions = {}, clazz?: Class<T>): T {
- let size = [width, height]
- if (count && count > 1) size.push(count)
-
- if (clazz?.prototype === WebGLCubeRenderTarget.prototype) { // todo: check for subclass also of WebGLCubeRenderTarget
- if (width !== height) throw 'Width and height of cube render target must be equal'
- size = [width]
- }
- return this._createTargetClass((clazz as any) ?? WebGLRenderTarget, size, {
- format: RGBAFormat,
- minFilter: LinearFilter,
- magFilter: LinearFilter,
- generateMipmaps: false,
- type: UnsignedByteType,
- colorSpace: NoColorSpace,
- ...options,
- }) as T
- }
-
- protected abstract _createTargetClass(clazz: Class<WebGLRenderTarget>, size: number[], options: WebGLRenderTargetOptions): IRenderTarget
-
- dispose(clear = true) {
- this._trackedTargets.forEach(t=>t.dispose())
- Object.values(this._trackedTempTargets).forEach(t=>t.dispose())
- if (clear) {
- this._trackedTargets = []
- this._releasedTempTargets = {}
- this._trackedTempTargets = []
- }
- }
-
- /**
- * Resizes all tracked targets with a sizeMultiplier based on the current renderSize and renderScale.
- * This must be automatically called by the renderer on resize, and manually when sizeMultiplier of a target changes.
- */
- resizeTrackedTargets() {
- for (const v of this._trackedTargets) this.resizeTrackedTarget(v)
- }
- resizeTrackedTarget(target: IRenderTarget): void {
- const multiplier = target.sizeMultiplier
- if (multiplier) {
- const s = this.renderSize.clone().multiplyScalar(this.renderScale * multiplier)
- target.setSize(Math.floor(s.width), Math.floor(s.height))
- }
- }
-
- private _processNewTempTarget(target: IRenderTarget, key: string): IRenderTarget {
- target.isTemporary = true
- target.targetKey = key
- if (this._releasedTempTargets[key] === undefined) this._releasedTempTargets[key] = []
- this._trackedTempTargets.push(target)
- return target
- }
-
- private _setTargetOptions(target: IRenderTarget, op: CreateRenderTargetOptions) {
- const tex = target.texture
- for (const t of Array.isArray(tex) ? tex : [tex])
- this._setTargetTextureOptions(t, op)
- }
-
- private _setTargetTextureOptions(texture: Texture, op: CreateRenderTargetOptions) {
- texture.minFilter = op.minFilter ?? LinearFilter
- texture.magFilter = op.magFilter ?? LinearFilter
- texture.wrapS = op.wrapS ?? ClampToEdgeWrapping
- texture.wrapT = op.wrapT ?? ClampToEdgeWrapping
- texture.generateMipmaps = op.generateMipmaps ?? false
- if (texture.generateMipmaps && texture.minFilter === LinearFilter)
- texture.minFilter = LinearMipMapLinearFilter
- if (!texture.generateMipmaps && texture.minFilter === LinearMipMapLinearFilter)
- texture.minFilter = LinearFilter
- }
-
- protected _processNewTarget(target: IRenderTarget, sizeMultiplier: number | undefined, trackTarget: boolean): IRenderTarget {
- if (sizeMultiplier !== undefined) target.sizeMultiplier = sizeMultiplier
- if (trackTarget) this.trackTarget(target)
- return target
- }
-
- }
|