threepipe
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RenderTargetManager.ts 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import {Class} from 'ts-browser-helpers'
  2. import {createRenderTargetKey, CreateRenderTargetOptions, IRenderTarget} from './RenderTarget'
  3. import {
  4. BaseEvent,
  5. ClampToEdgeWrapping,
  6. DepthFormat,
  7. DepthTexture,
  8. EventDispatcher,
  9. LinearFilter,
  10. LinearMipMapLinearFilter,
  11. NoColorSpace,
  12. RGBAFormat,
  13. Texture,
  14. UnsignedByteType,
  15. UnsignedIntType,
  16. Vector2,
  17. WebGLCubeRenderTarget,
  18. WebGLMultipleRenderTargets,
  19. WebGLRenderTarget,
  20. WebGLRenderTargetOptions,
  21. } from 'three'
  22. export abstract class RenderTargetManager<E extends BaseEvent = BaseEvent, ET extends string = string> extends EventDispatcher<E, ET> {
  23. abstract isWebGL2: boolean
  24. abstract readonly renderSize: Vector2
  25. abstract renderScale: number
  26. private _trackedTargets: IRenderTarget[] = []
  27. private _trackedTempTargets: IRenderTarget[] = []
  28. private _releasedTempTargets: Record<string, IRenderTarget[]> = {}
  29. readonly maxTempPerKey = 5
  30. protected constructor() {
  31. super()
  32. this._processNewTarget = this._processNewTarget.bind(this)
  33. this._processNewTempTarget = this._processNewTempTarget.bind(this)
  34. this.trackTarget = this.trackTarget.bind(this)
  35. this.disposeTarget = this.disposeTarget.bind(this)
  36. this.createTarget = this.createTarget.bind(this)
  37. this.createTargetCustom = this.createTargetCustom.bind(this)
  38. }
  39. trackTarget(target: IRenderTarget) {
  40. this._trackedTargets.push(target)
  41. }
  42. removeTrackedTarget(target: IRenderTarget) {
  43. const ind = this._trackedTargets.indexOf(target)
  44. if (ind >= 0)
  45. this._trackedTargets.splice(ind, 1)
  46. }
  47. createTarget<T extends IRenderTarget = IRenderTarget>({
  48. sizeMultiplier = undefined,
  49. samples = 0,
  50. colorSpace = NoColorSpace,
  51. type = UnsignedByteType,
  52. format = RGBAFormat,
  53. depthBuffer = true,
  54. depthTexture = false,
  55. depthTextureType = UnsignedIntType,
  56. depthTextureFormat = DepthFormat,
  57. size = undefined,
  58. textureCount = 1,
  59. ...op
  60. }: CreateRenderTargetOptions = {}, trackTarget = true): T {
  61. if (!this.isWebGL2) samples = 0
  62. if (sizeMultiplier !== undefined && size !== undefined)
  63. console.error('Both sizeMultiplier and size are defined. sizeMultiplier will be ignored.')
  64. size = size || this.renderSize.clone().multiplyScalar(this.renderScale * (sizeMultiplier = sizeMultiplier || 1))
  65. size.width = Math.floor(size.width)
  66. size.height = Math.floor(size.height)
  67. const depthTex = depthTexture ? new DepthTexture(size.width, size.height, depthTextureType) : undefined
  68. if (depthTex) depthTex.format = depthTextureFormat
  69. const target = this.createTargetCustom<T>(textureCount > 1 ? {
  70. width: size.width,
  71. height: size.height,
  72. count: textureCount,
  73. } : size,
  74. {samples, colorSpace, type, format, depthBuffer, depthTexture: depthTex},
  75. textureCount > 1 ? WebGLMultipleRenderTargets as any : WebGLRenderTarget)
  76. this._processNewTarget(target, sizeMultiplier, trackTarget)
  77. this._setTargetOptions(target, op)
  78. return target
  79. }
  80. /**
  81. * Dispose and remove tracked target. Release target in-case of temporary target.
  82. * To just dispose from the GPU memory and keep reference, call `target.dispose()` or `target.dispose(false)`
  83. * @param target
  84. * @param remove
  85. */
  86. disposeTarget(target: IRenderTarget, remove = true): void {
  87. if (!target) return
  88. if (target.isTemporary) return this.releaseTempTarget(target)
  89. if (remove) this.removeTrackedTarget(target)
  90. // @ts-expect-error internal, not in types
  91. target.dispose(false) // false is not required but still passing so that it doesnt cause infinite loop in future.
  92. }
  93. getTempTarget<T extends IRenderTarget = IRenderTarget>(op: CreateRenderTargetOptions = {}): T {
  94. const key = createRenderTargetKey(op)
  95. let target: T | undefined
  96. if (this._releasedTempTargets[key]?.length) target = this._releasedTempTargets[key].pop() as T
  97. if (!target) {
  98. target = this.createTarget<T>(op)
  99. this._processNewTempTarget(target, key)
  100. } else {
  101. this._setTargetOptions(target, op)
  102. }
  103. return target
  104. }
  105. releaseTempTarget(target: IRenderTarget): void {
  106. const key = target.targetKey
  107. if (!key || !target.isTemporary) {
  108. throw 'Not a temp target'
  109. }
  110. if (this._releasedTempTargets[key].length > this.maxTempPerKey) {
  111. this.removeTrackedTarget(target)
  112. target.dispose()
  113. } else this._releasedTempTargets[key].push(target)
  114. }
  115. createTargetCustom<T extends IRenderTarget>({
  116. width,
  117. height,
  118. count,
  119. }: {width: number, height: number, count?: number}, options: WebGLRenderTargetOptions = {}, clazz?: Class<T>): T {
  120. let size = [width, height]
  121. if (count && count > 1) size.push(count)
  122. if (clazz?.prototype === WebGLCubeRenderTarget.prototype) { // todo: check for subclass also of WebGLCubeRenderTarget
  123. if (width !== height) throw 'Width and height of cube render target must be equal'
  124. size = [width]
  125. }
  126. return this._createTargetClass((clazz as any) ?? WebGLRenderTarget, size, {
  127. format: RGBAFormat,
  128. minFilter: LinearFilter,
  129. magFilter: LinearFilter,
  130. generateMipmaps: false,
  131. type: UnsignedByteType,
  132. colorSpace: NoColorSpace,
  133. ...options,
  134. }) as T
  135. }
  136. protected abstract _createTargetClass(clazz: Class<WebGLRenderTarget>, size: number[], options: WebGLRenderTargetOptions): IRenderTarget
  137. dispose(clear = true) {
  138. this._trackedTargets.forEach(t=>t.dispose())
  139. Object.values(this._trackedTempTargets).forEach(t=>t.dispose())
  140. if (clear) {
  141. this._trackedTargets = []
  142. this._releasedTempTargets = {}
  143. this._trackedTempTargets = []
  144. }
  145. }
  146. /**
  147. * Resizes all tracked targets with a sizeMultiplier based on the current renderSize and renderScale.
  148. * This must be automatically called by the renderer on resize, and manually when sizeMultiplier of a target changes.
  149. */
  150. resizeTrackedTargets() {
  151. for (const v of this._trackedTargets) this.resizeTrackedTarget(v)
  152. }
  153. resizeTrackedTarget(target: IRenderTarget): void {
  154. const multiplier = target.sizeMultiplier
  155. if (multiplier) {
  156. const s = this.renderSize.clone().multiplyScalar(this.renderScale * multiplier)
  157. target.setSize(Math.floor(s.width), Math.floor(s.height))
  158. }
  159. }
  160. private _processNewTempTarget(target: IRenderTarget, key: string): IRenderTarget {
  161. target.isTemporary = true
  162. target.targetKey = key
  163. if (this._releasedTempTargets[key] === undefined) this._releasedTempTargets[key] = []
  164. this._trackedTempTargets.push(target)
  165. return target
  166. }
  167. private _setTargetOptions(target: IRenderTarget, op: CreateRenderTargetOptions) {
  168. const tex = target.texture
  169. for (const t of Array.isArray(tex) ? tex : [tex])
  170. this._setTargetTextureOptions(t, op)
  171. }
  172. private _setTargetTextureOptions(texture: Texture, op: CreateRenderTargetOptions) {
  173. texture.minFilter = op.minFilter ?? LinearFilter
  174. texture.magFilter = op.magFilter ?? LinearFilter
  175. texture.wrapS = op.wrapS ?? ClampToEdgeWrapping
  176. texture.wrapT = op.wrapT ?? ClampToEdgeWrapping
  177. texture.generateMipmaps = op.generateMipmaps ?? false
  178. if (texture.generateMipmaps && texture.minFilter === LinearFilter)
  179. texture.minFilter = LinearMipMapLinearFilter
  180. if (!texture.generateMipmaps && texture.minFilter === LinearMipMapLinearFilter)
  181. texture.minFilter = LinearFilter
  182. }
  183. protected _processNewTarget(target: IRenderTarget, sizeMultiplier: number | undefined, trackTarget: boolean): IRenderTarget {
  184. if (sizeMultiplier !== undefined) target.sizeMultiplier = sizeMultiplier
  185. if (trackTarget) this.trackTarget(target)
  186. return target
  187. }
  188. }