@awayjs/renderer
Version:
Renderer for AwayJS
1,282 lines (1,043 loc) • 41.8 kB
text/typescript
import {
Matrix,
Matrix3D,
Vector3D,
ColorTransform,
ArgumentError,
IAssetClass,
IAbstractionClass,
IAsset,
UUID,
IAbstraction,
AbstractionSet,
} from '@awayjs/core';
import {
AGALMiniAssembler,
ContextGLProfile,
ContextGLBlendFactor,
ContextGLBlendEquation,
ContextGLCompareMode,
ContextGLTriangleFace,
ProgramData,
Stage,
BlendMode,
ShaderRegisterCache,
ShaderRegisterData,
ShaderRegisterElement,
Part,
} from '@awayjs/stage';
import { _Render_RenderableBase } from './_Render_RenderableBase';
import { _Render_ElementsBase } from './_Render_ElementsBase';
import { AnimationRegisterData } from './AnimationRegisterData';
import { _Render_MaterialBase } from './_Render_MaterialBase';
import { _Stage_ElementsBase } from './_Stage_ElementsBase';
import { IAnimationSet } from './IAnimationSet';
import { View } from '@awayjs/view';
import { IPass } from './IPass';
import { IShaderBase } from './IShaderBase';
import { IRenderContainer } from './IRenderContainer';
// SRC, DST, ?SRC_ALPHA, ?DST_ALPHA
type IBlendFactorType =
[ContextGLBlendFactor, ContextGLBlendFactor] |
[ContextGLBlendFactor, ContextGLBlendFactor, ContextGLBlendFactor, ContextGLBlendFactor];
type IBlendEquationType = [ContextGLBlendEquation] | [ContextGLBlendEquation, ContextGLBlendEquation];
/**
* ShaderBase keeps track of the number of dependencies for "named registers" used across a pass.
* Named registers are that are not necessarily limited to a single method. They are created by the compiler and
* passed on to methods. The compiler uses the results to reserve usages through RegisterPool, which can be removed
* each time a method has been compiled into the shader.
*
* @see RegisterPool.addUsage
*/
export class ShaderBase implements IShaderBase {
private static _store: Record<string, IAbstraction[]> = {};
private static _abstractionClassPool: Object = new Object();
private _renderElements: _Render_ElementsBase;
private _renderMaterial: _Render_MaterialBase;
private _view: View;
private _pass: IPass;
public _stage: Stage;
private _programData: ProgramData;
private _blendFactor: IBlendFactorType = [
ContextGLBlendFactor.ONE,
ContextGLBlendFactor.ZERO
];
private _blendEquation: IBlendEquationType = [
ContextGLBlendEquation.ADD,
ContextGLBlendEquation.ADD
];
/*
private _blendFactorSource: ContextGLBlendFactor = ContextGLBlendFactor.ONE;
private _blendFactorDest: ContextGLBlendFactor = ContextGLBlendFactor.ZERO;
*/
private _invalidProgram: boolean = true;
private _animationVertexCode: string = '';
private _animationFragmentCode: string = '';
private _numUsedVertexConstants: number;
private _numUsedFragmentConstants: number;
private _numUsedStreams: number;
private _numUsedTextures: number;
private _usesAnimation: boolean;
protected _sharedRegisters: ShaderRegisterData;
protected _registerCache: ShaderRegisterCache;
protected _vertexCode: string = '';
protected _fragmentCode: string = '';
protected _postAnimationFragmentCode: string = '';
public get view(): View {
return this._view;
}
public get stage(): Stage {
return this._stage;
}
public get pass(): IPass {
return this._pass;
}
public get renderMaterial(): _Render_MaterialBase {
return this._renderMaterial;
}
public get programData(): ProgramData {
if (this._invalidProgram)
this._updateProgram();
return this._programData;
}
public usesBlending: boolean = false;
public useImageRect: boolean = false;
public usesCurves: boolean = false;
/**
*
*/
public activeElements: _Stage_ElementsBase;
/**
* The depth compare mode used to render the renderables using this material.
*
* @see away.stagegl.ContextGLCompareMode
*/
public depthCompareMode: ContextGLCompareMode = ContextGLCompareMode.LESS_EQUAL;
/**
* Indicate whether the shader should write to the depth buffer or not. Ignored when blending is enabled.
*/
public writeDepth: boolean = true;
public profile: ContextGLProfile;
public get usesAnimation(): boolean {
return this._usesAnimation;
}
public set usesAnimation(value: boolean) {
if (this._usesAnimation == value)
return;
this._usesAnimation = value;
this.invalidateProgram();
}
private _defaultCulling: ContextGLTriangleFace = ContextGLTriangleFace.BACK;
public _pInverseSceneMatrix: Float32Array = new Float32Array(16);
public animationRegisterData: AnimationRegisterData;
public get numUsedVertexConstants(): number {
if (this._invalidProgram)
this._updateProgram();
return this._numUsedVertexConstants;
}
public get numUsedFragmentConstants(): number {
if (this._invalidProgram)
this._updateProgram();
return this._numUsedFragmentConstants;
}
/**
* The amount of used vertex streams in the vertex code.
* Used by the animation code generation to know from which index on streams are available.
*/
public get numUsedStreams(): number {
if (this._invalidProgram)
this._updateProgram();
return this._numUsedStreams;
}
/**
*
*/
public get numUsedTextures(): number {
if (this._invalidProgram)
this._updateProgram();
return this._numUsedTextures;
}
public numLights: number;
public usesPremultipliedAlpha: boolean;
public useBothSides: boolean;
public usesUVTransform: boolean;
public usesColorTransform: boolean;
public alphaThreshold: number;
//set ambient values to default
public ambientR: number = 0xFF;
public ambientG: number = 0xFF;
public ambientB: number = 0xFF;
/**
*
*/
public usesCommonData: boolean;
/**
* Indicates whether the pass requires any fragment animation code.
*/
public usesFragmentAnimation: boolean;
/**
* The amount of dependencies on the projected position.
*/
public projectionDependencies: number;
/**
* The amount of dependencies on the normal vector.
*/
public normalDependencies: number;
/**
* The amount of dependencies on the vertex color.
*/
public colorDependencies: number;
/**
* The amount of dependencies on the view direction.
*/
public viewDirDependencies: number;
/**
* The amount of dependencies on the primary UV coordinates.
*/
public uvDependencies: number;
/**
* The amount of dependencies on the secondary UV coordinates.
*/
public secondaryUVDependencies: number;
/**
* The amount of dependencies on the global position. This can be 0 while hasGlobalPosDependencies is true when
* the global position is used as a temporary value (fe to calculate the view direction)
*/
public globalPosDependencies: number;
/**
* The amount of tangent vector dependencies (fragment shader).
*/
public tangentDependencies: number;
/**
*
*/
public outputsColors: boolean;
/**
* Indicates whether or not normals are output.
*/
public outputsNormals: boolean;
/**
* Indicates whether or not normal calculations are output in tangent space.
*/
public outputsTangentNormals: boolean;
/**
* Indicates whether or not normal calculations are
* expected in tangent space. This is only the case if no world-space
* dependencies exist and normals are being output.
*/
public usesTangentSpace: boolean;
/**
* Indicates whether there are any dependencies on the world-space position vector.
*/
public usesGlobalPosFragment: boolean = false;
/**
* Indicates whether there are any dependencies on the local position vector.
*/
public usesPositionFragment: boolean = false;
public vertexConstantData: Float32Array;
public fragmentConstantData: Float32Array;
public viewMatrix: Matrix3D;
public sceneMatrix: Matrix3D;
public sceneNormalMatrix: Matrix3D;
/**
* The index for the common data register.
*/
public commonsDataIndex: number;
/**
* The index for the curve vertex attribute stream.
*/
public curvesIndex: number;
/**
* The index for the UV vertex attribute stream.
*/
public uvIndex: number;
/**
* The index for the secondary UV vertex attribute stream.
*/
public secondaryUVIndex: number;
/**
* The index for the vertex normal attribute stream.
*/
public normalIndex: number;
/**
* The index for the color attribute stream.
*/
public colorBufferIndex: number;
/**
* The index for the vertex tangent attribute stream.
*/
public tangentIndex: number;
/**
*
*/
public skyboxScaleIndex: number;
/**
*
*/
public scenePositionIndex: number;
/**
* The index of the vertex constant containing the view matrix.
*/
public viewMatrixIndex: number;
/**
* The index of the vertex constant containing the scene matrix.
*/
public sceneMatrixIndex: number;
/**
* The index of the vertex constant containing the uniform scene matrix (the inverse transpose).
*/
public sceneNormalMatrixIndex: number;
/**
* The index of the vertex constant containing the camera position.
*/
public cameraPositionIndex: number;
/**
* The index for the UV transformation matrix vertex constant.
*/
public uvMatrixIndex: number;
/**
* The index for the color transform fragment constant.
*/
public colorTransformIndex: number;
/**
*
*/
public jointIndexIndex: number;
/**
*
*/
public jointWeightIndex: number;
/**
*
*/
public imageIndices: Array<number> = new Array<number>();
public readonly id: number;
public readonly abstractions: AbstractionSet;
/**
* Creates a new MethodCompilerVO object.
*/
constructor(renderElements: _Render_ElementsBase, renderMaterial: _Render_MaterialBase, pass: IPass, stage: Stage) {
this.id = UUID.Next();
this.abstractions = new AbstractionSet(this);
this._renderElements = renderElements;
this._renderMaterial = renderMaterial;
this._pass = pass;
this._stage = stage;
this._view = renderElements.renderer.view;
this.profile = this._stage.profile;
}
public requestAbstraction(asset: IAsset): IAbstraction {
const store = ShaderBase._store[asset.assetType];
return store.length ? store.pop() : new ShaderBase._abstractionClassPool[asset.assetType];
}
public storeAbstraction(abstraction: IAbstraction, assetType: string): void {
ShaderBase._store[assetType].push(abstraction);
}
/**
*
* @param imageObjectClass
*/
public static registerAbstraction(abstractionClass: IAbstractionClass, assetClass: IAssetClass): void {
ShaderBase._abstractionClassPool[assetClass.assetType] = abstractionClass;
ShaderBase._store[assetClass.assetType] = [];
}
public _includeDependencies(): void {
this._renderMaterial.renderElements._includeDependencies(this);
this._pass._includeDependencies(this);
//this.usesCommonData = this.usesCurves || this.usesCommonData;
}
/**
* Clears dependency counts for all registers. Called when recompiling a pass.
*/
public reset(): void {
this.projectionDependencies = 0;
this.normalDependencies = 0;
this.colorDependencies = 0;
this.viewDirDependencies = 0;
this.uvDependencies = 0;
this.secondaryUVDependencies = 0;
this.globalPosDependencies = 0;
this.tangentDependencies = 0;
this.usesCommonData = false;
this.usesGlobalPosFragment = false;
this.usesPositionFragment = false;
this.usesFragmentAnimation = false;
this.usesTangentSpace = false;
this.outputsNormals = false;
this.outputsTangentNormals = false;
}
/**
* The blend mode to use when drawing this renderable. The following blend modes are supported:
* <ul>
* <li>BlendMode.NORMAL: No blending, unless the material inherently needs it</li>
* <li>BlendMode.LAYER: Force blending.
* This will draw the object the same as NORMAL, but without writing depth writes.</li>
* <li>BlendMode.MULTIPLY</li>
* <li>BlendMode.ADD</li>
* <li>BlendMode.ALPHA</li>
* </ul>
*/
public setBlendMode(value: string): void {
this.usesBlending = true;
this.usesPremultipliedAlpha = true;
// reset blend equation onto ADD, ADD
this._blendEquation = [0,0]; //ADD
switch (value) {
case BlendMode.NORMAL: {
this._blendFactor = [
ContextGLBlendFactor.ONE,
ContextGLBlendFactor.ZERO
];
this.usesBlending = false;
this.usesPremultipliedAlpha = false;
break;
}
case BlendMode.LAYER: {
this._blendFactor = [
ContextGLBlendFactor.ONE,
ContextGLBlendFactor.ONE_MINUS_SOURCE_ALPHA
];
break;
}
case BlendMode.MULTIPLY: {
this._blendFactor = [
ContextGLBlendFactor.DESTINATION_COLOR,
ContextGLBlendFactor.ONE_MINUS_SOURCE_ALPHA,
ContextGLBlendFactor.ONE,
ContextGLBlendFactor.ONE_MINUS_SOURCE_ALPHA
];
break;
}
case BlendMode.ADD: {
this._blendFactor = [
ContextGLBlendFactor.ONE,
ContextGLBlendFactor.ONE
];
break;
}
case BlendMode.SCREEN: {
this._blendFactor = [
ContextGLBlendFactor.ONE,
ContextGLBlendFactor.ONE_MINUS_SOURCE_COLOR,
ContextGLBlendFactor.ONE,
ContextGLBlendFactor.ONE_MINUS_SOURCE_ALPHA
];
break;
}
case BlendMode.SUBTRACT: {
this._blendFactor = [
ContextGLBlendFactor.ONE,
ContextGLBlendFactor.ONE,
ContextGLBlendFactor.ONE,
ContextGLBlendFactor.ONE
];
this._blendEquation = [
ContextGLBlendEquation.SUBTRACT,
ContextGLBlendEquation.ADD
];
break;
}
case BlendMode.ALPHA: {
this._blendFactor = [ContextGLBlendFactor.ZERO, ContextGLBlendFactor.SOURCE_ALPHA];
this.usesPremultipliedAlpha = false;
break;
}
case BlendMode.ERASE: {
this._blendFactor = [ContextGLBlendFactor.ZERO, ContextGLBlendFactor.ONE_MINUS_SOURCE_ALPHA];
this.usesPremultipliedAlpha = false;
break;
}
default: {
throw new ArgumentError(`Unsupported blend mode: ${value}`);
}
}
}
/**
* @inheritDoc
*/
public _activate(): void {
if (!this.programData.program) {
this.programData.program = this._stage.context.createProgram();
const vertexPart: Part = (new AGALMiniAssembler()
.assemble('part vertex 1\n' + this.programData.vertexString + 'endpart')).vertex;
const fragmentPart: Part = (new AGALMiniAssembler()
.assemble('part fragment 1\n' + this.programData.fragmentString + 'endpart')).fragment;
//@ts-ignore
this.programData.program.upload(vertexPart, fragmentPart);
}
//set program data
this._stage.context.setProgram(this.programData.program);
this._stage.context.setCulling(
this.useBothSides ? ContextGLTriangleFace.NONE : this._defaultCulling,
this._view.projection.coordinateSystem);
if (!this.usesTangentSpace && this.cameraPositionIndex >= 0) {
const pos: Vector3D = this._view.projection.transform.matrix3D.position;
this.vertexConstantData[this.cameraPositionIndex] = pos.x;
this.vertexConstantData[this.cameraPositionIndex + 1] = pos.y;
this.vertexConstantData[this.cameraPositionIndex + 2] = pos.z;
}
this._stage.context.setDepthTest((this.writeDepth && !this.usesBlending), this.depthCompareMode);
//@ts-ignore
this._stage.context.setBlendEquation(
this._blendEquation[0],
this._blendEquation[1]
);
//@ts-ignore
this._stage.context.setBlendFactors(
this._blendFactor[0], this._blendFactor[1],
// MUST be undef for non-separated mode
this._blendFactor[2], this._blendFactor[3]
);
this.activeElements = null;
}
/**
* @inheritDoc
*/
public _deactivate(): void {
//For the love of god don't remove this if you want your multi-material shadows to not flicker like shit
this._stage.context.setDepthTest(true, ContextGLCompareMode.LESS_EQUAL);
this.activeElements = null;
}
/**
*
*
* @param renderable
* @param stage
* @param camera
*/
public _setRenderState(renderState: _Render_RenderableBase): void {
const node = renderState.entity.node;
if ((<IRenderContainer> node.container).animator)
(<IRenderContainer> node.container).animator.setRenderState(this, renderState);
let rawData: Float32Array;
if (this.usesUVTransform) {
const uvMatrix: Matrix = renderState.uvMatrix;
if (uvMatrix) {
//transpose
rawData = uvMatrix.rawData;
this.vertexConstantData[this.uvMatrixIndex] = rawData[0];
this.vertexConstantData[this.uvMatrixIndex + 1] = rawData[2];
this.vertexConstantData[this.uvMatrixIndex + 3] = rawData[4];
this.vertexConstantData[this.uvMatrixIndex + 4] = rawData[1];
this.vertexConstantData[this.uvMatrixIndex + 5] = rawData[3];
this.vertexConstantData[this.uvMatrixIndex + 7] = rawData[5];
} else {
this.vertexConstantData[this.uvMatrixIndex] = 1;
this.vertexConstantData[this.uvMatrixIndex + 1] = 0;
this.vertexConstantData[this.uvMatrixIndex + 3] = 0;
this.vertexConstantData[this.uvMatrixIndex + 4] = 0;
this.vertexConstantData[this.uvMatrixIndex + 5] = 1;
this.vertexConstantData[this.uvMatrixIndex + 7] = 0;
}
}
if (this.usesColorTransform) {
const colorTransform: ColorTransform = renderState.entity.colorTransform;
if (colorTransform) {
//TODO: AWDParser to write normalised color offsets
rawData = colorTransform._rawData;
this.fragmentConstantData[this.colorTransformIndex] = rawData[0];
this.fragmentConstantData[this.colorTransformIndex + 1] = rawData[1];
this.fragmentConstantData[this.colorTransformIndex + 2] = rawData[2];
this.fragmentConstantData[this.colorTransformIndex + 3] = rawData[3];
this.fragmentConstantData[this.colorTransformIndex + 4] = rawData[4] / 255;
this.fragmentConstantData[this.colorTransformIndex + 5] = rawData[5] / 255;
this.fragmentConstantData[this.colorTransformIndex + 6] = rawData[6] / 255;
this.fragmentConstantData[this.colorTransformIndex + 7] = rawData[7] / 255;
} else {
this.fragmentConstantData[this.colorTransformIndex] = 1;
this.fragmentConstantData[this.colorTransformIndex + 1] = 1;
this.fragmentConstantData[this.colorTransformIndex + 2] = 1;
this.fragmentConstantData[this.colorTransformIndex + 3] = 1;
this.fragmentConstantData[this.colorTransformIndex + 4] = 0;
this.fragmentConstantData[this.colorTransformIndex + 5] = 0;
this.fragmentConstantData[this.colorTransformIndex + 6] = 0;
this.fragmentConstantData[this.colorTransformIndex + 7] = 0;
}
}
if (this.sceneNormalMatrixIndex >= 0) {
this.sceneNormalMatrix.copyFrom(node.getInverseMatrix3D());
}
if (this.usesTangentSpace && this.cameraPositionIndex >= 0) {
node.getInverseMatrix3D().copyRawDataTo(this._pInverseSceneMatrix);
const pos: Vector3D = this._view.projection.transform.matrix3D.position;
const x: number = pos.x;
const y: number = pos.y;
const z: number = pos.z;
this.vertexConstantData[this.cameraPositionIndex] =
this._pInverseSceneMatrix[0] * x +
this._pInverseSceneMatrix[4] * y +
this._pInverseSceneMatrix[8] * z +
this._pInverseSceneMatrix[12];
this.vertexConstantData[this.cameraPositionIndex + 1] =
this._pInverseSceneMatrix[1] * x +
this._pInverseSceneMatrix[5] * y +
this._pInverseSceneMatrix[9] * z +
this._pInverseSceneMatrix[13];
this.vertexConstantData[this.cameraPositionIndex + 2] =
this._pInverseSceneMatrix[2] * x +
this._pInverseSceneMatrix[6] * y +
this._pInverseSceneMatrix[10] * z +
this._pInverseSceneMatrix[14];
}
}
public invalidateProgram(): void {
this._invalidProgram = true;
}
public dispose(): void {
this._programData.dispose();
this._programData = null;
this._registerCache.dispose();
this._registerCache = null;
this._sharedRegisters = null;
}
private _updateProgram(): void {
this._invalidProgram = false;
this._sharedRegisters = new ShaderRegisterData();
this._registerCache = new ShaderRegisterCache(this.profile);
this.reset();
this._includeDependencies();
this._initRegisterIndices();
this._compileDependencies();
//compile custom vertex & fragment codes from pass
this._vertexCode += this._pass._getVertexCode(this._registerCache, this._sharedRegisters);
this._fragmentCode += this._pass._getFragmentCode(this._registerCache, this._sharedRegisters);
this._postAnimationFragmentCode +=
this._pass._getPostAnimationFragmentCode(this._registerCache, this._sharedRegisters);
//check if alpha needs to be pre-multipled
if (this.usesPremultipliedAlpha) {
const target = this._sharedRegisters.shadedTarget;
this._postAnimationFragmentCode += `mul ${target}.xyz, ${target}, ${target}.w\n`;
}
//assign the final output color to the output register
this._postAnimationFragmentCode +=
`mov ${this._registerCache.fragmentOutputRegister}, ${this._sharedRegisters.shadedTarget}\n`;
this._registerCache.removeFragmentTempUsage(this._sharedRegisters.shadedTarget);
this._compileAnimationCode();
//initialise the required shader constants
this._initConstantData();
const programData: ProgramData = this._stage.getProgramData(
this._animationVertexCode + this._vertexCode,
this._fragmentCode +
this._animationFragmentCode +
this._postAnimationFragmentCode);
//check program data hasn't changed, keep count of program usages
if (this._programData != programData) {
if (this._programData)
this._programData.dispose();
this._programData = programData;
programData.usages++;
}
}
/**
* Reset all the indices to "unused".
*/
protected _initRegisterIndices(): void {
this.commonsDataIndex = -1;
this.cameraPositionIndex = -1;
this.curvesIndex = -1;
this.uvIndex = -1;
this.uvMatrixIndex = -1;
this.colorTransformIndex = -1;
this.secondaryUVIndex = -1;
this.normalIndex = -1;
this.colorBufferIndex = -1;
this.tangentIndex = -1;
this.sceneMatrixIndex = -1;
this.sceneNormalMatrixIndex = -1;
this.jointIndexIndex = -1;
this.jointWeightIndex = -1;
this.imageIndices.length = 0;
this._sharedRegisters.animatedPosition = this._registerCache.getFreeVertexVectorTemp();
this._registerCache.addVertexTempUsages(this._sharedRegisters.animatedPosition, 1);
this._sharedRegisters.animatableAttributes.push(this._registerCache.getFreeVertexAttribute());
this._sharedRegisters.animationTargetRegisters.push(this._sharedRegisters.animatedPosition);
this._vertexCode = '';
this._fragmentCode = '';
this._postAnimationFragmentCode = '';
//create commonly shared constant registers
if (this.usesCommonData || this.normalDependencies > 0) {
this._sharedRegisters.commons = this._registerCache.getFreeFragmentConstant();
this.commonsDataIndex = this._sharedRegisters.commons.index * 4;
}
//Creates the registers to contain the tangent data.
//Needs to be created FIRST and in this order (for when using tangent space)
if (this.tangentDependencies > 0 || this.outputsNormals) {
this._sharedRegisters.tangentInput = this._registerCache.getFreeVertexAttribute();
this.tangentIndex = this._sharedRegisters.tangentInput.index;
this._sharedRegisters.animatedTangent = this._registerCache.getFreeVertexVectorTemp();
this._registerCache.addVertexTempUsages(this._sharedRegisters.animatedTangent, 1);
if (this.usesTangentSpace) {
this._sharedRegisters.bitangent = this._registerCache.getFreeVertexVectorTemp();
this._registerCache.addVertexTempUsages(this._sharedRegisters.bitangent, 1);
}
this._sharedRegisters.animatableAttributes.push(this._sharedRegisters.tangentInput);
this._sharedRegisters.animationTargetRegisters.push(this._sharedRegisters.animatedTangent);
}
if (this.normalDependencies > 0) {
this._sharedRegisters.normalInput = this._registerCache.getFreeVertexAttribute();
this.normalIndex = this._sharedRegisters.normalInput.index;
this._sharedRegisters.animatedNormal = this._registerCache.getFreeVertexVectorTemp();
this._registerCache.addVertexTempUsages(this._sharedRegisters.animatedNormal, 1);
this._sharedRegisters.animatableAttributes.push(this._sharedRegisters.normalInput);
this._sharedRegisters.animationTargetRegisters.push(this._sharedRegisters.animatedNormal);
}
if (this.uvDependencies > 0) {
this._sharedRegisters.uvInput = this._registerCache.getFreeVertexAttribute();
this.uvIndex = this._sharedRegisters.uvInput.index;
if (!this.usesUVTransform) {
this._sharedRegisters.animatedUV = this._registerCache.getFreeVertexVectorTemp();
this._registerCache.addVertexTempUsages(this._sharedRegisters.animatedUV, 1);
}
}
this._sharedRegisters.shadedTarget = this._registerCache.getFreeFragmentVectorTemp();
this._registerCache.addFragmentTempUsages(this._sharedRegisters.shadedTarget, 1);
}
/**
* Compile the code for the methods.
*/
protected _compileDependencies(): void {
if (this.colorDependencies > 0) {
this._compileColorCode();
}
//compile the world-space position if required
if (this.globalPosDependencies > 0)
this._compileGlobalPositionCode();
//compile the local-space position if required
if (this.usesPositionFragment)
this._compilePositionCode();
if (this.usesCurves)
this._compileCurvesCode();
if (this.usesColorTransform)
this._compileColorTransformCode();
if (this.secondaryUVDependencies > 0)
this._compileSecondaryUVCode();
if (this.normalDependencies > 0)
this._compileNormalCode();
if (this.viewDirDependencies > 0)
this._compileViewDirCode();
//collect code from elements
this._vertexCode += this._renderElements._getVertexCode(this, this._registerCache, this._sharedRegisters);
this._fragmentCode += this._renderElements._getFragmentCode(this, this._registerCache, this._sharedRegisters);
}
/**
* Initializes the unchanging constant data for this shader object.
*/
protected _initConstantData(): void {
const rc = this._registerCache;
//Updates the amount of used register indices.
const usedVC = this._numUsedVertexConstants = rc.numUsedVertexConstants;
const usedFC = this._numUsedFragmentConstants = rc.numUsedFragmentConstants;
this._numUsedStreams = rc.numUsedStreams;
this._numUsedTextures = rc.numUsedTextures;
if (!this.vertexConstantData || this.vertexConstantData.length !== usedVC * 4)
this.vertexConstantData = new Float32Array(usedVC * 4);
if (!this.fragmentConstantData || this.fragmentConstantData.length !== usedFC * 4)
this.fragmentConstantData = new Float32Array(this._registerCache.numUsedFragmentConstants * 4);
//Initialies viewMatrix
if (this.viewMatrixIndex >= 0) {
const data = new Float32Array(this.vertexConstantData.buffer, this.viewMatrixIndex * 4, 16);
if (!this.viewMatrix) {
this.viewMatrix = new Matrix3D(data);
} else {
this.viewMatrix._rawData = data;
}
} else if (this.viewMatrix) {
this.viewMatrix = null;
}
//Initialies sceneMatrix
if (this.sceneMatrixIndex >= 0) {
const data = new Float32Array(this.vertexConstantData.buffer, this.sceneMatrixIndex * 4, 16);
if (!this.sceneMatrix) {
this.sceneMatrix = new Matrix3D(data);
} else {
this.sceneMatrix._rawData = data;
}
} else if (this.sceneMatrix) {
this.sceneMatrix = null;
}
//Initializes commonly required constant values.
if (this.commonsDataIndex >= 0) {
this.fragmentConstantData[this.commonsDataIndex] = .5;
this.fragmentConstantData[this.commonsDataIndex + 1] = 0;
this.fragmentConstantData[this.commonsDataIndex + 2] = 1 / 255;
this.fragmentConstantData[this.commonsDataIndex + 3] = 1;
}
//Initializes the default UV transformation matrix.
if (this.uvMatrixIndex >= 0) {
this.vertexConstantData[this.uvMatrixIndex] = 1;
this.vertexConstantData[this.uvMatrixIndex + 1] = 0;
this.vertexConstantData[this.uvMatrixIndex + 2] = 0;
this.vertexConstantData[this.uvMatrixIndex + 3] = 0;
this.vertexConstantData[this.uvMatrixIndex + 4] = 0;
this.vertexConstantData[this.uvMatrixIndex + 5] = 1;
this.vertexConstantData[this.uvMatrixIndex + 6] = 0;
this.vertexConstantData[this.uvMatrixIndex + 7] = 0;
}
//Initializes the default colorTransform.
if (this.colorTransformIndex >= 0) {
this.fragmentConstantData[this.colorTransformIndex] = 1;
this.fragmentConstantData[this.colorTransformIndex + 1] = 1;
this.fragmentConstantData[this.colorTransformIndex + 2] = 1;
this.fragmentConstantData[this.colorTransformIndex + 3] = 1;
this.fragmentConstantData[this.colorTransformIndex + 4] = 0;
this.fragmentConstantData[this.colorTransformIndex + 5] = 0;
this.fragmentConstantData[this.colorTransformIndex + 6] = 0;
this.fragmentConstantData[this.colorTransformIndex + 7] = 0;
}
if (this.sceneNormalMatrixIndex >= 0) {
const data = new Float32Array(this.vertexConstantData.buffer, this.sceneNormalMatrixIndex * 4, 16);
if (!this.sceneNormalMatrix) {
this.sceneNormalMatrix = new Matrix3D(data);
} else {
this.sceneNormalMatrix._rawData = data;
}
} else if (this.sceneNormalMatrix) {
this.sceneNormalMatrix = null;
}
if (this.cameraPositionIndex >= 0)
this.vertexConstantData[this.cameraPositionIndex + 3] = 1;
// init constant data in pass
this._pass._initConstantData();
//init constant data in animation
if (this._usesAnimation)
this._renderMaterial.animationSet.doneAGALCode(this);
}
private _compileColorCode(): void {
this._sharedRegisters.colorInput = this._registerCache.getFreeVertexAttribute();
this.colorBufferIndex = this._sharedRegisters.colorInput.index;
this._sharedRegisters.colorVarying = this._registerCache.getFreeVarying();
this._vertexCode += `mov ${this._sharedRegisters.colorVarying}, ${this._sharedRegisters.colorInput}\n`;
}
private _compileGlobalPositionCode(): void {
const temp = this._sharedRegisters.globalPositionVertex = this._registerCache.getFreeVertexVectorTemp();
this._registerCache.addVertexTempUsages(temp, this.globalPosDependencies);
const sceneMatrixReg: ShaderRegisterElement = this._registerCache.getFreeVertexConstant();
this._registerCache.getFreeVertexConstant();
this._registerCache.getFreeVertexConstant();
this._registerCache.getFreeVertexConstant();
this.sceneMatrixIndex = sceneMatrixReg.index * 4;
const r = this._sharedRegisters;
this._vertexCode += `m44 ${r.globalPositionVertex}, ${r.animatedPosition}, ${sceneMatrixReg}\n`;
if (this.usesGlobalPosFragment) {
r.globalPositionVarying = this._registerCache.getFreeVarying();
this._vertexCode += `mov ${r.globalPositionVarying}, ${r.globalPositionVertex}\n`;
}
}
private _compilePositionCode() {
const r = this._sharedRegisters;
r.positionVarying = this._registerCache.getFreeVarying();
this._vertexCode += `mov ${r.positionVarying}, ${r.animatedPosition}\n`;
}
private _compileCurvesCode(): void {
const r = this._sharedRegisters;
r.curvesInput = this._registerCache.getFreeVertexAttribute();
this.curvesIndex = r.curvesInput.index;
r.curvesVarying = this._registerCache.getFreeVarying();
this._vertexCode += 'mov ' + r.curvesVarying + ', ' + r.curvesInput + '\n';
const temp = this._registerCache.getFreeFragmentSingleTemp();
this._fragmentCode += 'mul ' + temp + ', ' + r.curvesVarying + '.y, ' + r.curvesVarying + '.y\n' +
'sub ' + temp + ', ' + temp + ', ' + r.curvesVarying + '.z\n' +
'mul ' + temp + ', ' + temp + ', ' + r.curvesVarying + '.x\n' +
'kil ' + temp + '\n';
}
/**
* Calculate the transformed colours
*/
private _compileColorTransformCode(): void {
// rm, gm, bm, am - multiplier
// ro, go, bo, ao - offset
const ct1 = this._registerCache.getFreeFragmentConstant();
const ct2 = this._registerCache.getFreeFragmentConstant();
const target = this._sharedRegisters.shadedTarget;
this.colorTransformIndex = ct1.index * 4;
this._postAnimationFragmentCode += 'mul ' + target + ', ' + target + ', ' + ct1 + '\n';
this._postAnimationFragmentCode += 'add ' + target + ', ' + target + ', ' + ct2 + '\n';
}
/**
* Provide the secondary UV coordinates.
*/
private _compileSecondaryUVCode(): void {
const uvAttributeReg: ShaderRegisterElement = this._registerCache.getFreeVertexAttribute();
const r = this._sharedRegisters;
this.secondaryUVIndex = uvAttributeReg.index;
r.secondaryUVVarying = this._registerCache.getFreeVarying();
this._vertexCode += 'mov ' + r.secondaryUVVarying + ', ' + uvAttributeReg + '\n';
}
/**
* Calculate the view direction.
*/
private _compileViewDirCode(): void {
const camPosReg = this._registerCache.getFreeVertexConstant();
const r = this._sharedRegisters;
r.viewDirVarying = this._registerCache.getFreeVarying();
r.viewDirFragment = this._registerCache.getFreeFragmentVectorTemp();
this._registerCache.addFragmentTempUsages(r.viewDirFragment, this.viewDirDependencies);
this.cameraPositionIndex = camPosReg.index * 4;
if (this.usesTangentSpace) {
const temp = this._registerCache.getFreeVertexVectorTemp();
this._vertexCode += 'sub ' + temp + ', ' + camPosReg + ', ' + r.animatedPosition + '\n' +
'm33 ' + r.viewDirVarying + '.xyz, ' + temp + ', ' + r.animatedTangent + '\n' +
'mov ' + r.viewDirVarying + '.w, ' + r.animatedPosition + '.w\n';
} else {
this._vertexCode += 'sub ' + r.viewDirVarying + ', ' + camPosReg + ', ' + r.globalPositionVertex + '\n';
this._registerCache.removeVertexTempUsage(this._sharedRegisters.globalPositionVertex);
}
//TODO is this required in all cases? (re: distancemappass)
this._fragmentCode += 'nrm ' + r.viewDirFragment + '.xyz, ' + r.viewDirVarying + '\n' +
'mov ' + r.viewDirFragment + '.w, ' + r.viewDirVarying + '.w\n';
}
/**
* Calculate the normal.
*/
private _compileNormalCode(): void {
const r = this._sharedRegisters;
r.normalFragment = this._registerCache.getFreeFragmentVectorTemp();
this._registerCache.addFragmentTempUsages(r.normalFragment, this.normalDependencies);
//simple normal aquisition if no tangent space is being used
if (this.outputsNormals && !this.outputsTangentNormals) {
this._vertexCode += this._pass._getNormalVertexCode(this._registerCache, r);
this._fragmentCode += this._pass._getNormalFragmentCode(this._registerCache, r);
return;
}
let normalMatrix: Array<ShaderRegisterElement>;
if (!this.outputsNormals || !this.usesTangentSpace) {
normalMatrix = new Array<ShaderRegisterElement>(3);
normalMatrix[0] = this._registerCache.getFreeVertexConstant();
normalMatrix[1] = this._registerCache.getFreeVertexConstant();
normalMatrix[2] = this._registerCache.getFreeVertexConstant();
this._registerCache.getFreeVertexConstant();
this.sceneNormalMatrixIndex = normalMatrix[0].index * 4;
r.normalVarying = this._registerCache.getFreeVarying();
}
if (this.outputsNormals) {
if (this.usesTangentSpace) {
// normalize normal + tangent vector and generate (approximated) bitangent
// used in m33 operation for view
this._vertexCode += 'nrm ' + r.animatedNormal + '.xyz, ' + r.animatedNormal + '\n' +
'nrm ' + r.animatedTangent + '.xyz, ' + r.animatedTangent + '\n' +
'crs ' + r.bitangent + '.xyz, ' + r.animatedNormal + ', ' + r.animatedTangent + '\n';
this._fragmentCode += this._pass._getNormalFragmentCode(this._registerCache, r);
} else {
//Compiles the vertex shader code for tangent-space normal maps.
r.tangentVarying = this._registerCache.getFreeVarying();
r.bitangentVarying = this._registerCache.getFreeVarying();
const temp: ShaderRegisterElement = this._registerCache.getFreeVertexVectorTemp();
this._vertexCode += 'm33 ' + temp + '.xyz, ' + r.animatedNormal + ', ' + normalMatrix[0] + '\n' +
'nrm ' + r.animatedNormal + '.xyz, ' + temp + '\n' +
'm33 ' + temp + '.xyz, ' + r.animatedTangent + ', ' + normalMatrix[0] + '\n' +
'nrm ' + r.animatedTangent + '.xyz, ' + temp + '\n' +
'mov ' + r.tangentVarying + '.x, ' + r.animatedTangent + '.x \n' +
'mov ' + r.tangentVarying + '.z, ' + r.animatedNormal + '.x \n' +
'mov ' + r.tangentVarying + '.w, ' + r.normalInput + '.w \n' +
'mov ' + r.bitangentVarying + '.x, ' + r.animatedTangent + '.y \n' +
'mov ' + r.bitangentVarying + '.z, ' + r.animatedNormal + '.y \n' +
'mov ' + r.bitangentVarying + '.w, ' + r.normalInput + '.w \n' +
'mov ' + r.normalVarying + '.x, ' + r.animatedTangent + '.z \n' +
'mov ' + r.normalVarying + '.z, ' + r.animatedNormal + '.z \n' +
'mov ' + r.normalVarying + '.w, ' + r.normalInput + '.w \n' +
'crs ' + temp + '.xyz, ' + r.animatedNormal + ', ' + r.animatedTangent + '\n' +
'mov ' + r.tangentVarying + '.y, ' + temp + '.x \n' +
'mov ' + r.bitangentVarying + '.y, ' + temp + '.y \n' +
'mov ' + r.normalVarying + '.y, ' + temp + '.z \n';
this._registerCache.removeVertexTempUsage(r.animatedTangent);
//Compiles the fragment shader code for tangent-space normal maps.
const t = this._registerCache.getFreeFragmentVectorTemp();
this._registerCache.addFragmentTempUsages(t, 1);
const b = this._registerCache.getFreeFragmentVectorTemp();
this._registerCache.addFragmentTempUsages(b, 1);
const n = this._registerCache.getFreeFragmentVectorTemp();
this._registerCache.addFragmentTempUsages(n, 1);
this._fragmentCode += 'nrm ' + t + '.xyz, ' + r.tangentVarying + '\n' +
'mov ' + t + '.w, ' + r.tangentVarying + '.w \n' +
'nrm ' + b + '.xyz, ' + r.bitangentVarying + '\n' +
'nrm ' + n + '.xyz, ' + r.normalVarying + '\n';
//compile custom fragment code for normal calcs
this._fragmentCode += this._pass._getNormalFragmentCode(this._registerCache, r) +
'm33 ' + r.normalFragment + '.xyz, ' + r.normalFragment + ', ' + t + '\n' +
'mov ' + r.normalFragment + '.w, ' + r.normalVarying + '.w\n';
this._registerCache.removeFragmentTempUsage(b);
this._registerCache.removeFragmentTempUsage(t);
this._registerCache.removeFragmentTempUsage(n);
}
} else {
// no output, world space is enough
this._vertexCode += 'm33 ' + r.normalVarying + '.xyz, ' + r.animatedNormal + ', ' + normalMatrix[0] + '\n' +
'mov ' + r.normalVarying + '.w, ' + r.animatedNormal + '.w\n';
this._fragmentCode += 'nrm ' + r.normalFragment + '.xyz, ' + r.normalVarying + '\n' +
'mov ' + r.normalFragment + '.w, ' + r.normalVarying + '.w\n';
if (this.tangentDependencies > 0) {
r.tangentVarying = this._registerCache.getFreeVarying();
this._vertexCode += 'm33 ' + r.tangentVarying + '.xyz, ' +
r.animatedTangent + ', ' + normalMatrix[0] + '\n' +
'mov ' + r.tangentVarying + '.w, ' + r.animatedTangent + '.w\n';
}
}
if (!this.usesTangentSpace)
this._registerCache.removeVertexTempUsage(r.animatedNormal);
}
private _compileAnimationCode(): void {
//reset code
this._animationVertexCode = '';
this._animationFragmentCode = '';
const r = this._sharedRegisters;
//check to see if GPU animation is used
if (this._usesAnimation) {
const animationSet: IAnimationSet = <IAnimationSet> this._renderMaterial.animationSet;
this._animationVertexCode += animationSet.getAGALVertexCode(this, this._registerCache, r);
if (this.uvDependencies > 0 && !this.usesUVTransform)
this._animationVertexCode += animationSet.getAGALUVCode(this, this._registerCache, r);
if (this.usesFragmentAnimation)
this._animationFragmentCode += animationSet.getAGALFragmentCode(
this, this._registerCache, r.shadedTarget);
} else {
// simply write attributes to targets, do not animate them
// projection will pick up on targets[0] to do the projection
const len: number = r.animatableAttributes.length;
for (let i: number = 0; i < len; ++i)
this._animationVertexCode += 'mov ' +
r.animationTargetRegisters[i] + ', ' +
r.animatableAttributes[i] + '\n';
if (this.uvDependencies > 0 && !this.usesUVTransform)
this._animationVertexCode += 'mov ' + r.animatedUV + ',' + r.uvInput + '\n';
}
}
public setVertexConst(index: number, x: number = 0, y: number = 0, z: number = 0, w: number = 0): void {
index *= 4;
this.vertexConstantData[index++] = x;
this.vertexConstantData[index++] = y;
this.vertexConstantData[index++] = z;
this.vertexConstantData[index] = w;
}
public setVertexConstFromArray(index: number, data: Float32Array): void {
index *= 4;
for (let i: number /*int*/ = 0; i < data.length; i++)
this.vertexConstantData[index++] = data[i];
}
public setVertexConstFromMatrix(index: number, matrix: Matrix3D): void {
index *= 4;
const rawData: Float32Array = matrix._rawData;
this.vertexConstantData[index++] = rawData[0];
this.vertexConstantData[index++] = rawData[4];
this.vertexConstantData[index++] = rawData[8];
this.vertexConstantData[index++] = rawData[12];
this.vertexConstantData[index++] = rawData[1];
this.vertexConstantData[index++] = rawData[5];
this.vertexConstantData[index++] = rawData[9];
this.vertexConstantData[index++] = rawData[13];
this.vertexConstantData[index++] = rawData[2];
this.vertexConstantData[index++] = rawData[6];
this.vertexConstantData[index++] = rawData[10];
this.vertexConstantData[index++] = rawData[14];
this.vertexConstantData[index++] = rawData[3];
this.vertexConstantData[index++] = rawData[7];
this.vertexConstantData[index++] = rawData[11];
this.vertexConstantData[index] = rawData[15];
}
public setFragmentConst(index: number, x: number = 0, y: number = 0, z: number = 0, w: number = 0): void {
index *= 4;
this.fragmentConstantData[index++] = x;
this.fragmentConstantData[index++] = y;
this.fragmentConstantData[index++] = z;
this.fragmentConstantData[index] = w;
}
}