@awayjs/renderer
Version:
Renderer for AwayJS
359 lines (278 loc) • 11.1 kB
text/typescript
import {
AssetEvent,
IAssetClass,
Matrix3D,
PerspectiveProjection,
} from '@awayjs/core';
import {
AttributesBuffer,
Image2D,
ContextGLTriangleFace
} from '@awayjs/stage';
import {
ContainerNode,
ContainerNodeEvent,
INode,
PickGroup
} from '@awayjs/view';
import { IMaterial } from './base/IMaterial';
import { ITexture } from './base/ITexture';
import { Style } from './base/Style';
import { _IRender_MaterialClass } from './base/_IRender_MaterialClass';
import { _Render_RenderableBase } from './base/_Render_RenderableBase';
import { _Render_RendererMaterial } from './base/_Render_RendererMaterial';
import { _Stage_ElementsBase } from './base/_Stage_ElementsBase';
import { TriangleElements, _Stage_TriangleElements } from './elements/TriangleElements';
import { MaterialEvent } from './events/MaterialEvent';
import { RenderableEvent } from './events/RenderableEvent';
import { RendererBase } from './RendererBase';
import { RenderGroup } from './RenderGroup';
import { ImageTexture2D } from './textures/ImageTexture2D';
import { Settings as StageSettings } from '@awayjs/stage';
import { RenderEntity } from './base/RenderEntity';
import { DefaultRenderer } from './DefaultRenderer';
export class CacheRenderer extends RendererBase implements IMaterial {
public static assetType: string = '[renderer CacheRenderer]';
private _texture: ImageTexture2D;
private _textures: ITexture[] = [];
private _onTextureInvalidate: (event: AssetEvent) => void;
private _onInvalidateParentNode: (event: ContainerNodeEvent) => void;
private _onInvalidateColorTransform: (event: ContainerNodeEvent) => void;
public animateUVs: boolean = false;
public bothSides: boolean = false;
public curves: boolean = false;
public imageRect: boolean = false;
public useColorTransform: boolean = true;
public alphaBlending: boolean = true;
public alphaThreshold: number = 0;
public get assetType(): string {
return CacheRenderer.assetType;
}
/**
* The 2d texture to use as a bitmap cache.
*/
public get texture(): ImageTexture2D {
//TODO check invalidation of _texture
return this._texture;
}
public set texture(value: ImageTexture2D) {
if (this._texture == value)
return;
if (this._texture)
this.removeTexture(this._texture);
this._texture = value;
if (this._texture)
this.addTexture(this._texture);
this.invalidatePasses();
}
constructor() {
super();
this._onTextureInvalidate = (event: AssetEvent) => this.invalidate();
this._onInvalidateParentNode = (event: ContainerNodeEvent) => this.onInvalidate(null);
this._onInvalidateColorTransform = (event: ContainerNodeEvent) => this.onInvalidate(null);
this._traverserGroup = RenderGroup.getInstance(CacheRenderer);
this._maskGroup = RenderGroup.getInstance(DefaultRenderer);
}
public init(node: INode, pool: RenderGroup): void {
super.init(node, pool);
if (this._parentNode) {
this._parentNode.addEventListener(ContainerNodeEvent.INVALIDATE_MATRIX3D, this._onInvalidateParentNode);
this._parentNode.addEventListener(ContainerNodeEvent.INVALIDATE_COLOR_TRANSFORM, this._onInvalidateColorTransform);
}
// for check filters/blends changes
this.node.container.addEventListener(RenderableEvent.INVALIDATE_STYLE, this._onInvalidateParentNode);
this.node.container.addEventListener(ContainerNodeEvent.INVALIDATE_COLOR_TRANSFORM, this._onInvalidateColorTransform);
this.texture = new ImageTexture2D();
}
public render(
enableDepthAndStencil: boolean = true,
surfaceSelector: number = 0,
mipmapSelector: number = 0,
maskConfig: number = 0
): void {
const container = this.node.container;
const stage = this.stage;
const useNonNativeBlend = this.useNonNativeBlend;
const targetImage = <Image2D> (useNonNativeBlend ? this.parentRenderer.style.image : this._style.image);
if (!targetImage) {
return super.render(enableDepthAndStencil, surfaceSelector, mipmapSelector, maskConfig);
}
// WE MUST store older render target config,
// because node can have cached child, that can be filtered
this.stage.pushRenderTargetConfig();
let sourceImage: Image2D;
// we not require use TMP texture when not have MSAA
if (stage.context.glVersion === 2 &&
StageSettings.ENABLE_MULTISAMPLE_TEXTURE
) {
sourceImage = stage.filterManager.popTemp(
targetImage.width,
targetImage.height,
true
);
}
//for DefaultRenderer when child has a blendmode applied
if (useNonNativeBlend) {
const proj = this.parentRenderer.view.projection;
this.parentRenderer.view.projection = new PerspectiveProjection();
this.parentRenderer.view.target = targetImage;
//this.parentRenderer.disableClear = true;
this.parentRenderer._initRender(targetImage);
this.parentRenderer.executeRender();
this.parentRenderer.resetHead();
//this.parentRenderer.disableClear = false;
this.parentRenderer.view.target = null;
this.parentRenderer.view.projection = proj;
this._disableClear = true;
}
// we should render with colorTransform to self, enable it
this.node.colorTransformDisabled = false;
this._initRender(sourceImage || targetImage);
super.render(enableDepthAndStencil, surfaceSelector, mipmapSelector, maskConfig);
// restore colorTransform state as in transform state
this.node.colorTransformDisabled = this.node.transformDisabled;
if (targetImage.width * targetImage.height === 0) {
throw new Error('Cannot have image with size 0 * 0');
}
//@ts-ignore
const filters = container.filters;
if (filters && filters.length > 0) {
filters.forEach((e) => e && (e.imageScale = this._boundsScale));
stage.filterManager.applyFilters(
sourceImage || targetImage,
targetImage, // because we use source as filter target - we not require copy
targetImage.rect,
targetImage.rect,
filters
);
} else if (sourceImage) {
// this is fast, it should only call blitFramebuffer,
// same as in regular MSAA
stage.filterManager.copyPixels(
sourceImage,
targetImage,
targetImage.rect,
targetImage.rect.topLeft,
useNonNativeBlend,
useNonNativeBlend ? <string> container.blendMode : ''
);
}
if (sourceImage) {
stage.filterManager.pushTemp(sourceImage);
}
// pop render target after any filters,
// required for deep filters (when node with filter has filtered child)
this.stage.popRenderTarget();
}
// apply blend modes and swap texture if needed
public preActivateRenderPass() {
}
public getNumTextures(): number {
return this._textures.length;
}
public getTextureAt(index: number): ITexture {
return this._textures[index];
}
public addTexture(texture: ITexture): void {
this._textures.push(texture);
texture.addEventListener(AssetEvent.INVALIDATE, this._onTextureInvalidate);
this.invalidate();
}
public removeTexture(texture: ITexture): void {
this._textures.splice(this._textures.indexOf(texture), 1);
texture.removeEventListener(AssetEvent.INVALIDATE, this._onTextureInvalidate);
this.invalidate();
}
/**
* Marks the shader programs for all passes as invalid, so they will be recompiled before the next use.
*
* @private
*/
public invalidatePasses(): void {
this.dispatchEvent(new MaterialEvent(MaterialEvent.INVALIDATE_PASSES, this));
}
/**
*
*/
public enterNode(node: ContainerNode): boolean {
const enter: boolean = super.enterNode(node);
if (enter && node.boundsVisible)
this.applyEntity(node.getBoundsPrimitive(PickGroup.getInstance()));
return enter;
}
public onInvalidate(event: AssetEvent): void {
super.onInvalidate(event);
this.dispatchEvent(new RenderableEvent(RenderableEvent.INVALIDATE_ELEMENTS, this));
this.dispatchEvent(new RenderableEvent(RenderableEvent.INVALIDATE_STYLE, this));
this.invalidate();
this.invalidatePasses();
}
public onClear(event: AssetEvent): void {
this.removeTexture(this._texture);
this._texture.clear();
this._texture = null;
this._style.image.clear();
this._style.image = null;
if (this._parentNode) {
this._parentNode.removeEventListener(ContainerNodeEvent.INVALIDATE_MATRIX3D, this._onInvalidateParentNode);
this._parentNode.removeEventListener(ContainerNodeEvent.INVALIDATE_COLOR_TRANSFORM, this._onInvalidateColorTransform);
}
this.node.container.removeEventListener(RenderableEvent.INVALIDATE_STYLE, this._onInvalidateParentNode);
this.node.container.removeEventListener(ContainerNodeEvent.INVALIDATE_COLOR_TRANSFORM, this._onInvalidateColorTransform);
super.onClear(event);
}
public static registerMaterial(renderMaterialClass: _IRender_MaterialClass, materialClass: IAssetClass): void {
RenderGroup.getInstance(CacheRenderer).registerMaterial(renderMaterialClass, materialClass);
}
}
export class _Render_Renderer extends _Render_RenderableBase {
public static assetType: string = '[render Renderer]';
private _elements: TriangleElements;
protected _getStageElements(): _Stage_ElementsBase {
const asset = <CacheRenderer> this._asset;
const paddedBounds = asset.getPaddedBounds();
//const bounds = asset.getBounds();
const offsetX = 0;//paddedBounds.x - bounds.x;
const offsetY = 0;//paddedBounds.y - bounds.y;
const matrix3D: Matrix3D = Matrix3D.CALCULATION_MATRIX;
matrix3D.copyFrom(this.renderSceneTransform);
matrix3D.appendScale(asset._boundsScale, asset._boundsScale, asset._boundsScale);
matrix3D.appendTranslation(offsetX * 0.5, offsetY * 0.5, 0);
matrix3D.invert();
const vectors: number[] = [
paddedBounds.left, paddedBounds.top, 0,
paddedBounds.right, paddedBounds.bottom, 0,
paddedBounds.right, paddedBounds.top, 0,
paddedBounds.left, paddedBounds.top, 0,
paddedBounds.left, paddedBounds.bottom, 0,
paddedBounds.right, paddedBounds.bottom, 0,
];
matrix3D.transformVectors(vectors, vectors);
let elements: TriangleElements = this._elements;
if (!elements) {
elements = this._elements = new TriangleElements(new AttributesBuffer(5, 6));
elements.setPositions(vectors);
elements.setUVs([0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1]);
} else {
elements.setPositions(vectors);
}
return elements.getAbstraction<_Stage_TriangleElements>(this._stage);
}
public executeRender(
enableDepthAndStencil: boolean = true,
surfaceSelector: number = 0, mipmapSelector: number = 0, maskConfig: number = 0): void {
// disable cull, because for render to texture it is bugged
this._stage.context.setCulling(ContextGLTriangleFace.NONE);
super.executeRender(enableDepthAndStencil, surfaceSelector, mipmapSelector, maskConfig);
}
protected _getRenderMaterial(): _Render_RendererMaterial {
return this._asset.getAbstraction<_Render_RendererMaterial>(
this.entity.renderer.getRenderElements(this.stageElements.elements));
}
protected _getStyle(): Style {
return (<CacheRenderer> this._asset).style;
}
}
DefaultRenderer.registerMaterial(_Render_RendererMaterial, CacheRenderer);
CacheRenderer.registerMaterial(_Render_RendererMaterial, CacheRenderer);
RenderEntity.registerRenderable(_Render_Renderer, CacheRenderer);