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

RenderManager.ts 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. import {
  2. Blending,
  3. Color,
  4. FloatType,
  5. HalfFloatType,
  6. IUniform,
  7. NoBlending,
  8. NoColorSpace,
  9. NormalBlending,
  10. NoToneMapping,
  11. PCFShadowMap,
  12. ShaderMaterial,
  13. Texture,
  14. Vector2,
  15. Vector4,
  16. WebGLRenderer,
  17. WebGLRenderTarget,
  18. WebGLRenderTargetOptions,
  19. } from 'three'
  20. import {EffectComposer2, IPassID, IPipelinePass, sortPasses} from '../postprocessing'
  21. import {IRenderTarget} from './RenderTarget'
  22. import {RenderTargetManager} from './RenderTargetManager'
  23. import {IShaderPropertiesUpdater} from '../materials'
  24. import {
  25. IRenderManager,
  26. IRenderManagerEvent,
  27. IRenderManagerEventTypes,
  28. type IRenderManagerOptions,
  29. IRenderManagerUpdateEvent,
  30. IScene,
  31. IWebGLRenderer,
  32. upgradeWebGLRenderer,
  33. } from '../core'
  34. import {base64ToArrayBuffer, canvasFlipY, Class, onChange2, serializable, serialize, ValOrArr} from 'ts-browser-helpers'
  35. import {uiButton, uiConfig, uiFolderContainer, uiMonitor, uiSlider, uiToggle} from 'uiconfig.js'
  36. import {generateUUID, textureDataToImageData} from '../three'
  37. import {BlobExt, EXRExporter2} from '../assetmanager'
  38. @serializable('RenderManager')
  39. @uiFolderContainer('Render Manager')
  40. export class RenderManager extends RenderTargetManager<IRenderManagerEvent, IRenderManagerEventTypes> implements IShaderPropertiesUpdater, IRenderManager {
  41. private readonly _isWebGL2: boolean
  42. private readonly _composer: EffectComposer2
  43. private readonly _context: WebGLRenderingContext
  44. @uiMonitor('Render Size')
  45. private readonly _renderSize = new Vector2(512, 512) // this is updated automatically.
  46. protected readonly _renderer: IWebGLRenderer<this>
  47. private _renderScale = 1.
  48. @uiConfig(undefined, {label: 'Passes'})
  49. private _passes: IPipelinePass[] = []
  50. private _pipeline: IPassID[] = []
  51. private _passesNeedsUpdate = true
  52. private _frameCount = 0
  53. private _lastTime = 0
  54. private _totalFrameCount = 0
  55. public static readonly POWER_PREFERENCE: 'high-performance' | 'low-power' | 'default' = 'high-performance'
  56. get renderer() {return this._renderer}
  57. /**
  58. * Use total frame count, if this is set to true, then frameCount won't be reset when the viewer is set to dirty.
  59. * Which will generate different random numbers for each frame during postprocessing steps. With TAA set properly, this will give a smoother result.
  60. */
  61. @uiToggle() @serialize() stableNoise = false
  62. public frameWaitTime = 0 // time to wait before next frame // used by canvas recorder //todo/
  63. protected _dirty = true
  64. /**
  65. * Set autoBuildPipeline = false to be able to set the pipeline manually.
  66. */
  67. @onChange2(RenderManager.prototype.rebuildPipeline)
  68. public autoBuildPipeline = true
  69. @uiButton('Rebuild Pipeline')
  70. rebuildPipeline(setDirty = true): void {
  71. this._passesNeedsUpdate = true
  72. if (setDirty) this._updated({change: 'rebuild'})
  73. }
  74. /**
  75. * Regenerates the render pipeline by resolving dependencies and sorting the passes.
  76. * This is called automatically when the passes are changed.
  77. */
  78. private _refreshPipeline(): IPassID[] {
  79. if (!this.autoBuildPipeline) return this._pipeline
  80. const ps = this._passes
  81. return this._pipeline = sortPasses(ps)
  82. }
  83. private _animationLoop(time: number, frame?:XRFrame) {
  84. const deltaTime = time - this._lastTime
  85. this._lastTime = time
  86. this.frameWaitTime -= deltaTime
  87. if (this.frameWaitTime > 0) return
  88. this.frameWaitTime = 0
  89. this.dispatchEvent({type: 'animationLoop', deltaTime, time, renderer: this._renderer, xrFrame: frame})
  90. }
  91. constructor({canvas, alpha = true, targetOptions}:IRenderManagerOptions) {
  92. super()
  93. this._animationLoop = this._animationLoop.bind(this)
  94. // this._xrPreAnimationLoop = this._xrPreAnimationLoop.bind(this)
  95. this._renderSize = new Vector2(canvas.clientWidth, canvas.clientHeight)
  96. this._renderer = this._initWebGLRenderer(canvas, alpha)
  97. this._context = this._renderer.getContext()
  98. this._isWebGL2 = this._renderer.capabilities.isWebGL2
  99. this.resetShadows()
  100. const composerTarget = this.createTarget<WebGLRenderTarget>(targetOptions, false)
  101. composerTarget.texture.name = 'EffectComposer.rt1'
  102. this._composer = new EffectComposer2(this._renderer, composerTarget)
  103. // if (animationLoop) this.addEventListener('animationLoop', animationLoop) // todo: from viewer
  104. }
  105. protected _initWebGLRenderer(canvas: HTMLCanvasElement, alpha: boolean): IWebGLRenderer<this> {
  106. const renderer = new WebGLRenderer({
  107. canvas,
  108. antialias: false,
  109. alpha,
  110. premultipliedAlpha: false, // todo: see this, maybe use this with rgbm mode.
  111. preserveDrawingBuffer: true,
  112. powerPreference: RenderManager.POWER_PREFERENCE,
  113. })
  114. renderer.useLegacyLights = false
  115. renderer.setAnimationLoop(this._animationLoop)
  116. renderer.onContextLost = (event: WebGLContextEvent) => {
  117. this.dispatchEvent({type: 'contextLost', event})
  118. }
  119. renderer.onContextRestore = () => {
  120. // console.log('restored')
  121. this.dispatchEvent({type: 'contextRestored'})
  122. }
  123. renderer.setSize(this._renderSize.width, this._renderSize.height, false)
  124. renderer.setPixelRatio(this._renderScale)
  125. renderer.toneMapping = NoToneMapping
  126. renderer.toneMappingExposure = 1
  127. renderer.outputColorSpace = NoColorSpace // or SRGBColorSpace
  128. renderer.shadowMap.enabled = true
  129. renderer.shadowMap.type = PCFShadowMap // use? THREE.PCFShadowMap. dont use VSM if need ground: https://github.com/mrdoob/three.js/issues/17473
  130. // renderer.shadowMap.type = BasicShadowMap // use? THREE.PCFShadowMap. dont use VSM if need ground: https://github.com/mrdoob/three.js/issues/17473
  131. renderer.shadowMap.autoUpdate = false
  132. return upgradeWebGLRenderer.call(renderer, this)
  133. }
  134. setSize(width?: number, height?: number, force = false) {
  135. if (!force &&
  136. (width ? Math.abs(width - this._renderSize.width) : 0) +
  137. (height ? Math.abs(height - this._renderSize.height) : 0) < 0.1
  138. ) return
  139. if (width) this._renderSize.width = width
  140. if (height) this._renderSize.height = height
  141. if (!this.webglRenderer.xr.enabled) {
  142. this._renderer.setSize(this._renderSize.width, this._renderSize.height, false)
  143. this._renderer.setPixelRatio(this._renderScale)
  144. }
  145. this._composer.setPixelRatio(this._renderScale, false)
  146. this._composer.setSize(this._renderSize.width, this._renderSize.height)
  147. this.resizeTrackedTargets()
  148. // console.log('setSize', {...this._renderSize}, this._trackedTargets.length)
  149. this.dispatchEvent({type: 'resize'})
  150. this._updated({change: 'size', data: this._renderSize.toArray()})
  151. this.reset()
  152. }
  153. // render(scene: RenderScene): void {
  154. // const camera = scene.activeCamera
  155. // const activeScene = scene.activeScene
  156. // if(!camera) return
  157. // this._renderer.render(scene.threeScene, camera)
  158. // // todo gizmos
  159. // }
  160. render(scene: IScene): void {
  161. if (this._passesNeedsUpdate) {
  162. this._refreshPipeline()
  163. this.refreshPasses()
  164. }
  165. for (const pass of this._passes) {
  166. if (pass.enabled) pass.beforeRender?.(scene, scene.mainCamera, this)
  167. }
  168. this._composer.render()
  169. this._frameCount += 1
  170. this._totalFrameCount += 1
  171. this._dirty = false
  172. }
  173. get needsRender(): boolean {
  174. this._dirty = this._dirty || this._passes.findIndex(value => value.dirty) >= 0 // todo: check for enabled passes only.
  175. return this._dirty
  176. }
  177. setDirty(reset = false): void {
  178. this._dirty = true
  179. if (reset) this.reset()
  180. // do NOT call _updated from here.
  181. }
  182. reset(): void {
  183. this._frameCount = 0
  184. this._dirty = true
  185. // do NOT call _updated from here.
  186. }
  187. resetShadows(): void {
  188. this._renderer.shadowMap.needsUpdate = true
  189. }
  190. refreshPasses(): void {
  191. if (!this._passesNeedsUpdate) return
  192. this._passesNeedsUpdate = false
  193. const p = []
  194. for (const passId of this._pipeline) {
  195. const a = this._passes.find(value => value.passId === passId)
  196. if (!a) {
  197. console.warn('Unable to find pass: ', passId)
  198. continue
  199. }
  200. p.push(a)
  201. }
  202. [...this._composer.passes].forEach(p1=>this._composer.removePass(p1))
  203. p.forEach(p1=>this._composer.addPass(p1))
  204. this._updated({change: 'passRefresh'})
  205. }
  206. dispose(): void {
  207. super.dispose()
  208. this._renderer.dispose()
  209. }
  210. updateShaderProperties(material: {defines: Record<string, string|number|undefined>, uniforms: {[name: string]: IUniform}}): this {
  211. // if (material.uniforms.currentFrameCount) material.uniforms.currentFrameCount.value = this.frameCount
  212. if (!this.stableNoise) {
  213. if (material.uniforms.frameCount) material.uniforms.frameCount.value = this._totalFrameCount
  214. else console.warn('RenderManager: no uniform: frameCount')
  215. } else {
  216. if (material.uniforms.frameCount) material.uniforms.frameCount.value = this.frameCount
  217. else console.warn('RenderManager: no uniform: frameCount')
  218. }
  219. return this
  220. }
  221. // region Passes
  222. registerPass(pass: IPipelinePass, replaceId = true): void {
  223. if (replaceId) {
  224. for (const pass1 of [...this._passes]) {
  225. if (pass.passId === pass1.passId) this.unregisterPass(pass1)
  226. }
  227. }
  228. this._passes.push(pass)
  229. pass.onRegister?.(this)
  230. this.rebuildPipeline(false)
  231. this._updated({change: 'registerPass', pass})
  232. }
  233. unregisterPass(pass: IPipelinePass): void {
  234. const i = this._passes.indexOf(pass)
  235. if (i >= 0) {
  236. pass.onUnregister?.(this)
  237. this._passes.splice(i, 1)
  238. this.rebuildPipeline(false)
  239. this._updated({change: 'unregisterPass', pass})
  240. }
  241. }
  242. // endregion
  243. // region Getters and Setters
  244. get frameCount(): number {
  245. return this._frameCount
  246. }
  247. get totalFrameCount(): number {
  248. return this._totalFrameCount
  249. }
  250. resetTotalFrameCount(): void {
  251. this._totalFrameCount = 0
  252. }
  253. set pipeline(value: IPassID[]) {
  254. this._pipeline = value
  255. if (this.autoBuildPipeline) {
  256. console.warn('RenderManager: pipeline changed, but autoBuildPipeline is true. This will not have any effect.')
  257. }
  258. this.rebuildPipeline()
  259. }
  260. get pipeline(): IPassID[] {
  261. return this._pipeline
  262. }
  263. get composer(): EffectComposer2 {
  264. return this._composer
  265. }
  266. get passes(): IPipelinePass[] {
  267. return this._passes
  268. }
  269. get isWebGL2(): boolean {
  270. return this._isWebGL2
  271. }
  272. get composerTarget(): IRenderTarget {
  273. return this._composer.renderTarget1
  274. }
  275. get composerTarget2(): IRenderTarget {
  276. return this._composer.renderTarget2
  277. }
  278. get renderSize(): Vector2 {
  279. return this._renderSize
  280. }
  281. @uiSlider('Render Scale', [0.1, 8], 0.05)
  282. get renderScale(): number {
  283. return this._renderScale
  284. }
  285. set renderScale(value: number) {
  286. if (value !== this._renderScale) {
  287. this._renderScale = value
  288. this.setSize(undefined, undefined, true)
  289. }
  290. }
  291. get context(): WebGLRenderingContext {
  292. return this._context
  293. }
  294. get webglRenderer(): WebGLRenderer {
  295. return this._renderer
  296. }
  297. @serialize()
  298. get useLegacyLights(): boolean {
  299. return this._renderer.useLegacyLights
  300. }
  301. set useLegacyLights(v: boolean) {
  302. this._renderer.useLegacyLights = v
  303. this._updated({change: 'useLegacyLights', data: v})
  304. this.resetShadows()
  305. }
  306. get clock() {
  307. return this._composer.clock
  308. }
  309. // endregion
  310. // region Utils
  311. /**
  312. *
  313. * @param destination - destination target, or screen if undefined or null
  314. * @param source - source Texture
  315. * @param viewport - viewport and scissor
  316. * @param material - override material
  317. * @param clear - clear before blit
  318. * @param respectColorSpace - does color space conversion when reading and writing to the target
  319. * @param blending - Note - Set to NormalBlending if transparent is set to false
  320. * @param transparent
  321. */
  322. blit(destination: IRenderTarget|undefined|null, {source, viewport, material, clear = true, respectColorSpace = false, blending = NoBlending, transparent = true}: {source?: Texture, viewport?: Vector4, material?: ShaderMaterial, clear?: boolean, respectColorSpace?: boolean, blending?: Blending, transparent?: boolean} = {}): void {
  323. const copyPass = !respectColorSpace ? this._composer.copyPass : this._composer.copyPass2
  324. const {renderToScreen, material: oldMaterial, uniforms: oldUniforms, clear: oldClear} = copyPass
  325. if (material) {
  326. copyPass.material = material
  327. }
  328. const oldTransparent = copyPass.material.transparent
  329. const oldViewport = !destination ? this._renderer.getViewport(new Vector4()) : destination.viewport.clone()
  330. const oldScissor = !destination ? this._renderer.getScissor(new Vector4()) : destination.scissor.clone()
  331. const oldScissorTest = !destination ? this._renderer.getScissorTest() : destination.scissorTest
  332. const oldAutoClear = this._renderer.autoClear
  333. const oldTarget = this._renderer.getRenderTarget()
  334. const oldBlending = copyPass.material.blending
  335. if (viewport) {
  336. if (!destination) {
  337. this._renderer.setViewport(viewport)
  338. this._renderer.setScissor(viewport)
  339. this._renderer.setScissorTest(true)
  340. } else {
  341. destination.viewport.copy(viewport)
  342. destination.scissor.copy(viewport)
  343. destination.scissorTest = true
  344. }
  345. }
  346. this._renderer.autoClear = false
  347. copyPass.material.blending = !transparent ? NormalBlending : blending
  348. copyPass.uniforms = copyPass.material.uniforms
  349. copyPass.renderToScreen = false
  350. copyPass.clear = clear
  351. copyPass.material.transparent = transparent
  352. copyPass.material.needsUpdate = true
  353. this._renderer.renderWithModes({
  354. sceneRender: true,
  355. opaqueRender: true,
  356. shadowMapRender: false,
  357. backgroundRender: false,
  358. transparentRender: true,
  359. transmissionRender: false,
  360. }, ()=>{
  361. copyPass.render(this._renderer, <WebGLRenderTarget>destination || null, {texture: source} as any, 0, false)
  362. })
  363. copyPass.renderToScreen = renderToScreen
  364. copyPass.clear = oldClear
  365. copyPass.material = oldMaterial
  366. copyPass.uniforms = oldUniforms
  367. copyPass.material.blending = oldBlending
  368. copyPass.material.transparent = oldTransparent
  369. this._renderer.autoClear = oldAutoClear
  370. if (viewport) {
  371. if (!destination) {
  372. this._renderer.setViewport(oldViewport)
  373. this._renderer.setScissor(oldScissor)
  374. this._renderer.setScissorTest(oldScissorTest)
  375. } else {
  376. destination.viewport.copy(oldViewport)
  377. destination.scissor.copy(oldScissor)
  378. destination.scissorTest = oldScissorTest
  379. }
  380. }
  381. this._renderer.setRenderTarget(oldTarget) // todo: active cubeface etc
  382. }
  383. clearColor({r, g, b, a, target, depth = true, stencil = true, viewport}:
  384. {r?: number, g?: number, b?: number, a?: number, target?: IRenderTarget, depth?: boolean, stencil?: boolean, viewport?: Vector4}): void {
  385. const color = this._renderer.getClearColor(new Color())
  386. const alpha = this._renderer.getClearAlpha()
  387. this._renderer.setClearColor(new Color(r ?? color.r, g ?? color.g, b ?? color.b))
  388. this._renderer.setClearAlpha(a ?? alpha)
  389. const lastTarget = this._renderer.getRenderTarget()
  390. const activeCubeFace = this._renderer.getActiveCubeFace()
  391. const activeMipLevel = this._renderer.getActiveMipmapLevel()
  392. const oldViewport = !target ? this._renderer.getViewport(new Vector4()) : target.viewport.clone()
  393. const oldScissor = !target ? this._renderer.getScissor(new Vector4()) : target.scissor.clone()
  394. const oldScissorTest = !target ? this._renderer.getScissorTest() : target.scissorTest
  395. if (viewport) {
  396. if (!target) {
  397. this._renderer.setViewport(viewport)
  398. this._renderer.setScissor(viewport)
  399. this._renderer.setScissorTest(true)
  400. } else {
  401. target.viewport.copy(viewport)
  402. target.scissor.copy(viewport)
  403. target.scissorTest = true
  404. }
  405. }
  406. this._renderer.setRenderTarget((target as WebGLRenderTarget) ?? null)
  407. this._renderer.clear(true, depth, stencil)
  408. if (viewport) {
  409. if (!target) {
  410. this._renderer.setViewport(oldViewport)
  411. this._renderer.setScissor(oldScissor)
  412. this._renderer.setScissorTest(oldScissorTest)
  413. } else {
  414. target.viewport.copy(oldViewport)
  415. target.scissor.copy(oldScissor)
  416. target.scissorTest = oldScissorTest
  417. }
  418. }
  419. this._renderer.setRenderTarget(lastTarget, activeCubeFace, activeMipLevel)
  420. this._renderer.setClearColor(color)
  421. this._renderer.setClearAlpha(alpha)
  422. }
  423. /**
  424. * Converts a render target to a png/jpeg data url string.
  425. * Note: this will clamp the values to [0, 1] and converts to srgb for float and half-float render targets.
  426. * @param target
  427. * @param mimeType
  428. * @param quality
  429. */
  430. renderTargetToDataUrl(target: WebGLRenderTarget, mimeType = 'image/png', quality = 90): string {
  431. const canvas = document.createElement('canvas')
  432. canvas.width = target.width
  433. canvas.height = target.height
  434. const ctx = canvas.getContext('2d')
  435. if (!ctx) throw new Error('Unable to get 2d context')
  436. const imageData = ctx.createImageData(target.width, target.height, {colorSpace: ['display-p3', 'srgb'].includes(target.texture.colorSpace) ? <PredefinedColorSpace>target.texture.colorSpace : undefined})
  437. if (target.texture.type === HalfFloatType || target.texture.type === FloatType) {
  438. const buffer = this.renderTargetToBuffer(target)
  439. textureDataToImageData({data: buffer, width: target.width, height: target.height}, target.texture.colorSpace, imageData) // this handles converting to srgb
  440. } else {
  441. // todo: handle rgbm to srgb conversion?
  442. this._renderer.readRenderTargetPixels(target, 0, 0, target.width, target.height, imageData.data)
  443. }
  444. ctx.putImageData(imageData, 0, 0)
  445. const string = (target.texture.flipY ? canvas : canvasFlipY(canvas)).toDataURL(mimeType, quality) // intentionally inverted ternary
  446. canvas.remove()
  447. return string
  448. }
  449. renderTargetToBuffer(target: WebGLRenderTarget): Uint8Array|Uint16Array|Float32Array {
  450. const buffer =
  451. target.texture.type === HalfFloatType ?
  452. new Uint16Array(target.width * target.height * 4) :
  453. target.texture.type === FloatType ?
  454. new Float32Array(target.width * target.height * 4) :
  455. new Uint8Array(target.width * target.height * 4)
  456. this._renderer.readRenderTargetPixels(target, 0, 0, target.width, target.height, buffer)
  457. return buffer
  458. }
  459. exportRenderTarget(target: WebGLRenderTarget, mimeType = 'auto'): BlobExt {
  460. const hdrFormats = ['image/x-exr']
  461. let hdr = target.texture.type === HalfFloatType || target.texture.type === FloatType
  462. if (mimeType === 'auto') {
  463. mimeType = hdr ? 'image/x-exr' : 'image/png'
  464. }
  465. if (!hdrFormats.includes(mimeType)) hdr = false
  466. let buffer: ArrayBufferLike
  467. if (!hdr) {
  468. const url = this.renderTargetToDataUrl(target, mimeType === 'auto' ? undefined : mimeType)
  469. buffer = base64ToArrayBuffer(url.split(',')[1])
  470. mimeType = url.split(';')[0].split(':')[1]
  471. } else {
  472. if (mimeType !== 'image/x-exr') {
  473. console.warn('RenderManager: mimeType ', mimeType, ' is not supported for HDR. Using EXR instead')
  474. mimeType = 'image/x-exr'
  475. }
  476. const exporter = new EXRExporter2()
  477. buffer = exporter.parse(this._renderer, target)
  478. }
  479. const b = new Blob([buffer], {type: mimeType}) as BlobExt
  480. b.ext = mimeType === 'image/x-exr' ? 'exr' : mimeType.split('/')[1]
  481. return b
  482. }
  483. // endregion
  484. // region Events Dispatch
  485. private _updated(data?: Partial<IRenderManagerUpdateEvent>) {
  486. this.dispatchEvent({...data, type: 'update'})
  487. }
  488. // endregion
  489. protected _createTargetClass(clazz: Class<WebGLRenderTarget>, size: number[], options: WebGLRenderTargetOptions): IRenderTarget {
  490. const processNewTarget = this._processNewTarget
  491. return new class RenderTarget extends clazz implements IRenderTarget {
  492. isTemporary?: boolean
  493. sizeMultiplier?: number
  494. uuid: string
  495. readonly assetType = 'renderTarget'
  496. name = 'RenderTarget'
  497. // @ts-expect-error because WebGLRenderTarget does not have texture as array
  498. texture: ValOrArr<Texture&{_target: IRenderTarget}>
  499. constructor(public readonly renderManager: IRenderManager, ...ps: any[]) {
  500. super(...ps)
  501. this.uuid = generateUUID()
  502. const ops = ps[ps.length - 1] as WebGLRenderTargetOptions
  503. if (Array.isArray(this.texture)) {
  504. this.texture.forEach(t => {
  505. if (ops.colorSpace !== undefined) t.colorSpace = ops.colorSpace
  506. t._target = this
  507. t.toJSON = () => {
  508. console.warn('Multiple render target texture.toJSON not supported yet.')
  509. return {}
  510. }
  511. })
  512. } else {
  513. this.texture._target = this
  514. this.texture.toJSON = () => ({ // todo use readRenderTargetPixels as data url or data buffer.
  515. isRenderTargetTexture: true,
  516. }) // so that it doesn't get serialized
  517. }
  518. }
  519. setSize(w: number, h: number, depth?: number) {
  520. super.setSize(Math.floor(w), Math.floor(h), depth)
  521. // console.log('setSize', w, h, depth)
  522. return this
  523. }
  524. clone(trackTarget = true): any {
  525. if (this.isTemporary) throw 'Cloning temporary render targets not supported'
  526. if (Array.isArray(this.texture)) throw 'Cloning multiple render targets not supported'
  527. // Note: todo: webgl render target.clone messes up the texture, by not copying isRenderTargetTexture prop and maybe some other stuff. So its better to just create a new one
  528. const cloned = super.clone() as IRenderTarget
  529. const tex = cloned.texture
  530. if (Array.isArray(tex)) tex.forEach(t => t.isRenderTargetTexture = true)
  531. else tex.isRenderTargetTexture = true
  532. return processNewTarget(cloned, this.sizeMultiplier || 1, trackTarget)
  533. }
  534. }(this, ...size, options)
  535. }
  536. /**
  537. * @deprecated use renderScale instead
  538. */
  539. get displayCanvasScaling() {
  540. console.error('displayCanvasScaling is deprecated, use renderScale instead')
  541. return this.renderScale
  542. }
  543. /**
  544. * @deprecated use renderScale instead
  545. */
  546. set displayCanvasScaling(value) {
  547. console.error('displayCanvasScaling is deprecated, use renderScale instead')
  548. this.renderScale = value
  549. }
  550. }