threepipe
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

RenderTargetManager.ts 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  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. disposeTarget(target: IRenderTarget): void {
  81. if (!target) return
  82. if (target.isTemporary) return this.releaseTempTarget(target)
  83. this.removeTrackedTarget(target)
  84. target.dispose()
  85. }
  86. getTempTarget<T extends IRenderTarget = IRenderTarget>(op: CreateRenderTargetOptions = {}): T {
  87. const key = createRenderTargetKey(op)
  88. let target: T | undefined
  89. if (this._releasedTempTargets[key]?.length) target = this._releasedTempTargets[key].pop() as T
  90. if (!target) {
  91. target = this.createTarget<T>(op)
  92. this._processNewTempTarget(target, key)
  93. } else {
  94. this._setTargetOptions(target, op)
  95. }
  96. return target
  97. }
  98. releaseTempTarget(target: IRenderTarget): void {
  99. const key = target.targetKey
  100. if (!key || !target.isTemporary) {
  101. throw 'Not a temp target'
  102. }
  103. if (this._releasedTempTargets[key].length > this.maxTempPerKey) {
  104. this.removeTrackedTarget(target)
  105. target.dispose()
  106. } else this._releasedTempTargets[key].push(target)
  107. }
  108. createTargetCustom<T extends IRenderTarget>({
  109. width,
  110. height,
  111. count,
  112. }: {width: number, height: number, count?: number}, options: WebGLRenderTargetOptions = {}, clazz?: Class<T>): T {
  113. let size = [width, height]
  114. if (count && count > 1) size.push(count)
  115. if (clazz?.prototype === WebGLCubeRenderTarget.prototype) { // todo: check for subclass also of WebGLCubeRenderTarget
  116. if (width !== height) throw 'Width and height of cube render target must be equal'
  117. size = [width]
  118. }
  119. return this._createTargetClass((clazz as any) ?? WebGLRenderTarget, size, {
  120. format: RGBAFormat,
  121. minFilter: LinearFilter,
  122. magFilter: LinearFilter,
  123. generateMipmaps: false,
  124. type: UnsignedByteType,
  125. colorSpace: NoColorSpace,
  126. ...options,
  127. }) as T
  128. }
  129. protected abstract _createTargetClass(clazz: Class<WebGLRenderTarget>, size: number[], options: WebGLRenderTargetOptions): IRenderTarget
  130. dispose() {
  131. this._trackedTargets.forEach(t=>t.dispose())
  132. Object.values(this._trackedTempTargets).forEach(t=>t.dispose())
  133. this._trackedTargets = []
  134. this._releasedTempTargets = {}
  135. this._trackedTempTargets = []
  136. }
  137. /**
  138. * Resizes all tracked targets with a sizeMultiplier based on the current renderSize and renderScale.
  139. * This must be automatically called by the renderer on resize, and manually when sizeMultiplier of a target changes.
  140. */
  141. resizeTrackedTargets() {
  142. for (const v of this._trackedTargets) this.resizeTrackedTarget(v)
  143. }
  144. resizeTrackedTarget(target: IRenderTarget): void {
  145. const multiplier = target.sizeMultiplier
  146. if (multiplier) {
  147. const s = this.renderSize.clone().multiplyScalar(this.renderScale * multiplier)
  148. target.setSize(Math.floor(s.width), Math.floor(s.height))
  149. }
  150. }
  151. private _processNewTempTarget(target: IRenderTarget, key: string): IRenderTarget {
  152. target.isTemporary = true
  153. target.targetKey = key
  154. if (this._releasedTempTargets[key] === undefined) this._releasedTempTargets[key] = []
  155. this._trackedTempTargets.push(target)
  156. return target
  157. }
  158. private _setTargetOptions(target: IRenderTarget, op: CreateRenderTargetOptions) {
  159. const tex = target.texture
  160. for (const t of Array.isArray(tex) ? tex : [tex])
  161. this._setTargetTextureOptions(t, op)
  162. }
  163. private _setTargetTextureOptions(texture: Texture, op: CreateRenderTargetOptions) {
  164. texture.minFilter = op.minFilter ?? LinearFilter
  165. texture.magFilter = op.magFilter ?? LinearFilter
  166. texture.wrapS = op.wrapS ?? ClampToEdgeWrapping
  167. texture.wrapT = op.wrapT ?? ClampToEdgeWrapping
  168. texture.generateMipmaps = op.generateMipmaps ?? false
  169. if (texture.generateMipmaps && texture.minFilter === LinearFilter)
  170. texture.minFilter = LinearMipMapLinearFilter
  171. if (!texture.generateMipmaps && texture.minFilter === LinearMipMapLinearFilter)
  172. texture.minFilter = LinearFilter
  173. }
  174. protected _processNewTarget(target: IRenderTarget, sizeMultiplier: number | undefined, trackTarget: boolean): IRenderTarget {
  175. if (sizeMultiplier !== undefined) target.sizeMultiplier = sizeMultiplier
  176. if (trackTarget) this.trackTarget(target)
  177. return target
  178. }
  179. }