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

RenderTargetManager.ts 7.5KB

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