- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
- 378
- 379
- 380
- 381
- 382
- 383
- 384
- 385
- 386
- 387
- 388
- 389
- 390
- 391
- 392
- 393
- 394
- 395
- 396
- 397
- 398
- 399
- 400
- 401
- 402
- 403
- 404
- 405
- 406
- 407
- 408
- 409
- 410
- 411
- 412
- 413
- 414
- 415
- 416
- 417
- 418
- 419
- 420
- 421
- 422
- 423
- 424
- 425
- 426
- 427
- 428
- 429
- 430
- 431
- 432
- 433
- 434
- 435
- 436
- 437
- 438
- 439
- 440
- 441
- 442
- 443
- 444
- 445
- 446
- 447
- 448
- 449
- 450
- 451
- 452
- 453
- 454
- 455
- 456
- 457
- 458
- 459
- 460
- 461
- 462
- 463
- 464
- 465
- 466
- 467
- 468
- 469
- 470
- 471
- 472
- 473
- 474
- 475
- 476
- 477
- 478
- 479
- 480
- 481
- 482
- 483
- 484
- 485
- 486
- 487
- 488
- 489
- 490
- 491
- 492
- 493
- 494
- 495
- 496
- 497
- 498
- 499
- 500
- 501
- 502
- 503
- 504
- 505
- 506
- 507
- 508
- 509
- 510
/**
* Created by ivanp on 29.01.2017.
*/
import { Container } from '@pixi/display';
import { Group } from './Group';
import { RenderTexture, Renderer } from '@pixi/core';
import { Rectangle } from '@pixi/math';
import { settings } from '@pixi/settings';
import type { DisplayObject, IDestroyOptions } from '@pixi/display';
import type { Stage } from './Stage';
import type { ILayeredRenderer } from './RendererMixin';
/**
* This manages the render-texture a {@link Layer} renders into.
*
* This is used internally by {@link Layer#render}.
*/
export class LayerTextureCache
{
constructor(public layer: Layer) {}
private renderTexture: RenderTexture = null;
private doubleBuffer: Array<RenderTexture> = null;
private currentBufferIndex = 0;
_tempRenderTarget: RenderTexture = null;
_tempRenderTargetSource = new Rectangle();
_tempRenderTargetDestination = new Rectangle();
private init(renderer?: Renderer): void
{
const width = renderer ? renderer.screen.width : 100;
const height = renderer ? renderer.screen.height : 100;
const resolution = renderer ? renderer.resolution : settings.RESOLUTION;
this.renderTexture = RenderTexture.create({ width, height, resolution });
if (this.layer.group.useDoubleBuffer)
{
this.doubleBuffer = [
RenderTexture.create({ width, height, resolution }),
RenderTexture.create({ width, height, resolution })
];
}
}
/** See {@link Layer#getRenderTexture}. */
getRenderTexture(): RenderTexture
{
if (!this.renderTexture)
{
this.init();
}
return this.renderTexture;
}
/** Prepares the layer's render-texture and set it as the render-target. */
pushTexture(renderer: Renderer): void
{
// TODO: take not screen, but offset screen, in case there's matrix transform
const screen = renderer.screen;
if (!this.renderTexture)
{
this.init(renderer);
}
const rt = this.renderTexture;
const group = this.layer.group;
const db = this.doubleBuffer;
if (rt.width !== screen.width
|| rt.height !== screen.height
|| rt.baseTexture.resolution !== renderer.resolution)
{
rt.baseTexture.resolution = renderer.resolution;
rt.resize(screen.width, screen.height);
if (db)
{
db[0].baseTexture.resolution = renderer.resolution;
db[0].resize(screen.width, screen.height);
db[1].baseTexture.resolution = renderer.resolution;
db[1].resize(screen.width, screen.height);
}
}
if (db)
{
db[0].framebuffer.multisample = rt.framebuffer.multisample;
db[1].framebuffer.multisample = rt.framebuffer.multisample;
}
this._tempRenderTarget = renderer.renderTexture.current;
this._tempRenderTargetSource.copyFrom(renderer.renderTexture.sourceFrame);
this._tempRenderTargetDestination.copyFrom(renderer.renderTexture.destinationFrame);
renderer.batch.flush();
if (group.useDoubleBuffer)
{
// double-buffer logic
let buffer = db[this.currentBufferIndex];
if (!(buffer.baseTexture as any)._glTextures[renderer.CONTEXT_UID])
{
renderer.renderTexture.bind(buffer, undefined, undefined);
renderer.texture.bind(buffer);
if (group.clearColor)
{
renderer.renderTexture.clear(group.clearColor as any);
}
}
renderer.texture.unbind(rt.baseTexture);
(rt.baseTexture as any)._glTextures = (buffer.baseTexture as any)._glTextures;
(rt.baseTexture as any).framebuffer = (buffer.baseTexture as any).framebuffer;
buffer = db[1 - this.currentBufferIndex];
renderer.renderTexture.bind(buffer, undefined, undefined);
}
else
{
// simple logic
renderer.renderTexture.bind(rt, undefined, undefined);
}
if (group.clearColor)
{
renderer.renderTexture.clear(group.clearColor as any);
}
// fix for filters
const filterStack = renderer.filter.defaultFilterStack;
if (filterStack.length > 1)
{
filterStack[filterStack.length - 1].renderTexture = renderer.renderTexture.current;
}
}
/** Flushes the renderer and restores the old render-target. */
popTexture(renderer: Renderer): void
{
renderer.batch.flush();
renderer.framebuffer.blit();
// switch filters back
const filterStack = renderer.filter.defaultFilterStack;
if (filterStack.length > 1)
{
filterStack[filterStack.length - 1].renderTexture = this._tempRenderTarget;
}
renderer.renderTexture.bind(this._tempRenderTarget,
this._tempRenderTargetSource, this._tempRenderTargetDestination);
this._tempRenderTarget = null;
const rt = this.renderTexture;
const group = this.layer.group;
const db = this.doubleBuffer;
if (group.useDoubleBuffer)
{
renderer.texture.unbind(rt.baseTexture);
this.currentBufferIndex = 1 - this.currentBufferIndex;
const buffer = db[this.currentBufferIndex];
(rt.baseTexture as any)._glTextures = (buffer.baseTexture as any)._glTextures;
(rt.baseTexture as any).framebuffer = (buffer.baseTexture as any).framebuffer;
}
}
/** Destroy the texture-cache. Set {@link Layer.textureCache} to {@code null} after destroying it! */
destroy(): void
{
if (this.renderTexture)
{
this.renderTexture.destroy();
if (this.doubleBuffer)
{
this.doubleBuffer[0].destroy(true);
this.doubleBuffer[1].destroy(true);
}
}
}
}
/**
* A {@link Layer layer} can be used to render {@link PIXI.DisplayObject}s in a different part of the scene graph together.
*
* A layer can be used to structure a scene graph in a data-oriented manner and separate the z-ordering hierarchy in
* a different tree. Each layer is associated with a {@link Group} that provides the context for sorting objects
* in the same layer.
*
* All layers must be placed underneath a {@link Stage} - generally, you should assign a {@link Stage} as your
* scene's root.
*/
export class Layer extends Container
{
/** Flags that this container is a layer! */
public readonly isLayer = true;
/** The group of {@link DisplayObject}s that are rendered within this layer */
public group: Group = null;
/** The texture manager used when rendering into a {@link Layer#useRenderTexture layer render-texture}. */
public textureCache: LayerTextureCache;
_activeChildren: Array<DisplayObject> = [];
_tempChildren: Array<DisplayObject> = null;
_activeStageParent: Stage = null;
_sortedChildren: Array<DisplayObject> = [];
_tempLayerParent: Layer = null;
insertChildrenBeforeActive = true;
insertChildrenAfterActive = true;
/**
* @param group - The group of {@link DisplayObject}s to be rendered by this layer.
*/
constructor(group: Group = null)
{
super();
if (group)
{
this.group = group;
this.zIndex = group.zIndex;
}
else
{
this.group = new Group(0, false);
}
this._tempChildren = this.children;
}
/**
* Flags whether this layer should render into a render-texture.
*
* This is useful if you want to use the layer as a texture elsewhere - for example, in sprites or to apply
* filters. The layer's render-texture is resized to the size of the renderer's screen.
*/
get useRenderTexture(): boolean
{
return this.group.useRenderTexture;
}
set useRenderTexture(value: boolean)
{
this.group.useRenderTexture = value;
}
/**
* This will enable double buffering for this layer.
*
* This layer will keep two render-textures to render into - choosing one each frame on a flip-flop
* basis. This is useful when you
*
* **Caveat**: You must enable {@link Layer#useRenderTexture} to prevent framebuffer errors in rendering.
*/
get useDoubleBuffer(): boolean
{
return this.group.useDoubleBuffer;
}
set useDoubleBuffer(value: boolean)
{
this.group.useDoubleBuffer = value;
}
/**
* The background color to clear the layer.
*
* This should be used when {@link Layer#useRenderTexture} is enabled.
*/
get clearColor(): ArrayLike<number>
{
return this.group.clearColor;
}
set clearColor(value: ArrayLike<number>)
{
this.group.clearColor = value;
}
get sortPriority(): number
{
return this.group.sortPriority;
}
set sortPriority(value: number)
{
this.group.sortPriority = value;
}
/**
* The rendering {@link Layer#useRenderTexture into a render-texture} is enabled, this will return
* the render-texture used by this layer.
*/
getRenderTexture(): RenderTexture
{
if (!this.textureCache)
{
this.textureCache = new LayerTextureCache(this);
}
return this.textureCache.getRenderTexture();
}
/**
* you can override this method for this particular layer, if you want
*/
public doSort(): void
{
this.group.doSort(this, this._sortedChildren);
}
/** @override */
public destroy(options?: IDestroyOptions): void
{
if (this.textureCache)
{
this.textureCache.destroy();
this.textureCache = null;
}
super.destroy(options);
}
/** @override */
public render(renderer: Renderer): void
{
if (!this.prerender(renderer as any))
{
return;
}
if (this.group.useRenderTexture)
{
if (!this.textureCache)
{
this.textureCache = new LayerTextureCache(this);
}
this.textureCache.pushTexture(renderer);
}
this.containerRenderWebGL(renderer);
this.postrender(renderer as any);
if (this.group.useRenderTexture)
{
this.textureCache.popTexture(renderer);
}
}
/**
* renderCanvas named this way because of some TS mixin problem
* @param renderer
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public layerRenderCanvas(renderer: any): void
{
if (this.prerender(renderer))
{
this.containerRenderCanvas(renderer);
this.postrender(renderer);
}
}
/**
* This should be called when the layer is found while traversing the scene for updating object-layer association.
*
* This is an **internal** method.
*
* @see Stage#updateStage
*/
_onBeginLayerSubtreeTraversal(stage: Stage): void
{
// This will transfer all "_activeChildren" of "this.group" into "this._activeChildren". This is done
// because a DisplayObject in that group may be placed before the layer in the scene tree.
const active = this._activeChildren;
this._activeStageParent = stage;
this.group._resolveLayer(stage, this);
const groupChildren = this.group._activeChildren;
active.length = 0;
for (let i = 0; i < groupChildren.length; i++)
{
groupChildren[i]._activeParentLayer = this;
active.push(groupChildren[i]);
}
groupChildren.length = 0;
}
/**
* This should be called when the full subtree of the layer has been traversed while updating the stage's scene.
*
* This is an **internal** method.
*
* @see Stage#updateStage
*/
_onEndLayerSubtreeTraversal(): void
{
const children = this.children;
const active = this._activeChildren;
const sorted = this._sortedChildren;
for (let i = 0; i < active.length; i++)
{
this.emit('display', active[i]);
}
sorted.length = 0;
if (this.insertChildrenBeforeActive)
{
for (let i = 0; i < children.length; i++)
{
sorted.push(children[i]);
}
}
for (let i = 0; i < active.length; i++)
{
sorted.push(active[i]);
}
if (!this.insertChildrenBeforeActive
&& this.insertChildrenAfterActive)
{
for (let i = 0; i < children.length; i++)
{
sorted.push(children[i]);
}
}
if (this.group.enableSort)
{
this.doSort();
}
}
/**
* Prepares the renderer for this layer.
*
* It will assign {@link PIXI.Renderer#_activeLayer} to {@code this}, and set the active layer before
* this to {@link Layer#_activeParentLayer _activeParentLayer}. It will also temporarily sort the
* children by z-order.
*
* @return `true`, if the layer needs to be rendered; `false`, when the layer is invisible or has
* zero alpha.
*/
protected prerender(renderer: ILayeredRenderer): boolean
{
// eslint-disable-next-line eqeqeq
if (this._activeParentLayer && this._activeParentLayer != renderer._activeLayer)
{
return false;
}
if (!this.visible)
{
this.displayOrder = 0;
return false;
}
this.displayOrder = renderer.incDisplayOrder();
// if the object is not visible or the alpha is 0 then no need to render this element
if (this.worldAlpha <= 0 || !this.renderable)
{
return false;
}
// we are making a hack with swapping children, it can go wrong easily
// this is special "recover" if that allows stage to recover just after failed frame
if (this.children !== this._sortedChildren
&& this._tempChildren !== this.children)
{
this._tempChildren = this.children;
}
// just a temporary feature - getBounds() for filters will work with that
// TODO: make a better hack for getBounds()
(this as any)._boundsID++;
(this as any).children = this._sortedChildren;
this._tempLayerParent = renderer._activeLayer;
renderer._activeLayer = this;
return true;
}
/**
* Cleans up the renderer after this layer is rendered.
*
* It restores {@link Renderer#_activeLayer} to the parent layer and restores the canonical
* order of children.
*/
protected postrender(renderer: ILayeredRenderer): void
{
(this as any).children = this._tempChildren;
renderer._activeLayer = this._tempLayerParent;
this._tempLayerParent = null;
}
}
(Layer.prototype as any).renderCanvas = Layer.prototype.layerRenderCanvas;