Update mobile version to mobile v2.2.1

The android version just got a much needed update to fix some resolution issues on devices with cutouts.

It turns out the mobile source was actually pretty out of date, like 3 versions out of date! This commit brings it up to date.

All the changes have just been about keeping the game running on modern devices, though. The biggest change was adding the Starling library to the project, which made the game GPU powered and sped the whole thing up.
This commit is contained in:
Terry Cavanagh
2022-12-02 18:19:58 +01:00
parent 86d90a1296
commit 72d018ea04
140 changed files with 30533 additions and 2409 deletions

View File

@@ -0,0 +1,218 @@
// =================================================================================================
//
// Starling Framework
// Copyright Gamua GmbH. All Rights Reserved.
//
// This program is free software. You can redistribute and/or modify it
// in accordance with the terms of the accompanying license agreement.
//
// =================================================================================================
package starling.rendering
{
import flash.geom.Matrix;
import starling.display.Mesh;
import starling.display.MeshBatch;
import starling.utils.MeshSubset;
/** This class manages a list of mesh batches of different types;
* it acts as a "meta" MeshBatch that initiates all rendering.
*/
internal class BatchProcessor
{
private var _batches:Vector.<MeshBatch>;
private var _batchPool:BatchPool;
private var _currentBatch:MeshBatch;
private var _currentStyleType:Class;
private var _onBatchComplete:Function;
private var _cacheToken:BatchToken;
// helper objects
private static var sMeshSubset:MeshSubset = new MeshSubset();
/** Creates a new batch processor. */
public function BatchProcessor()
{
_batches = new <MeshBatch>[];
_batchPool = new BatchPool();
_cacheToken = new BatchToken();
}
/** Disposes all batches (including those in the reusable pool). */
public function dispose():void
{
for each (var batch:MeshBatch in _batches)
batch.dispose();
_batches.length = 0;
_batchPool.purge();
_currentBatch = null;
_onBatchComplete = null;
}
/** Adds a mesh to the current batch, or to a new one if the current one does not support
* it. Whenever the batch changes, <code>onBatchComplete</code> is called for the previous
* one.
*
* @param mesh the mesh to add to the current (or new) batch.
* @param state the render state from which to take the current settings for alpha,
* modelview matrix, and blend mode.
* @param subset the subset of the mesh you want to add, or <code>null</code> for
* the complete mesh.
* @param ignoreTransformations when enabled, the mesh's vertices will be added
* without transforming them in any way (no matter the value of the
* state's <code>modelviewMatrix</code>).
*/
public function addMesh(mesh:Mesh, state:RenderState, subset:MeshSubset=null,
ignoreTransformations:Boolean=false):void
{
if (subset == null)
{
subset = sMeshSubset;
subset.vertexID = subset.indexID = 0;
subset.numVertices = mesh.numVertices;
subset.numIndices = mesh.numIndices;
}
else
{
if (subset.numVertices < 0) subset.numVertices = mesh.numVertices - subset.vertexID;
if (subset.numIndices < 0) subset.numIndices = mesh.numIndices - subset.indexID;
}
if (subset.numVertices > 0)
{
if (_currentBatch == null || !_currentBatch.canAddMesh(mesh, subset.numVertices))
{
finishBatch();
_currentStyleType = mesh.style.type;
_currentBatch = _batchPool.get(_currentStyleType);
_currentBatch.blendMode = state ? state.blendMode : mesh.blendMode;
_cacheToken.setTo(_batches.length);
_batches[_batches.length] = _currentBatch;
}
var matrix:Matrix = state ? state._modelviewMatrix : null;
var alpha:Number = state ? state._alpha : 1.0;
_currentBatch.addMesh(mesh, matrix, alpha, subset, ignoreTransformations);
_cacheToken.vertexID += subset.numVertices;
_cacheToken.indexID += subset.numIndices;
}
}
/** Finishes the current batch, i.e. call the 'onComplete' callback on the batch and
* prepares initialization of a new one. */
public function finishBatch():void
{
var meshBatch:MeshBatch = _currentBatch;
if (meshBatch)
{
_currentBatch = null;
_currentStyleType = null;
if (_onBatchComplete != null)
_onBatchComplete(meshBatch);
}
}
/** Clears all batches and adds them to a pool so they can be reused later. */
public function clear():void
{
var numBatches:int = _batches.length;
for (var i:int=0; i<numBatches; ++i)
_batchPool.put(_batches[i]);
_batches.length = 0;
_currentBatch = null;
_currentStyleType = null;
_cacheToken.reset();
}
/** Returns the batch at a certain index. */
public function getBatchAt(batchID:int):MeshBatch
{
return _batches[batchID];
}
/** Disposes all batches that are currently unused. */
public function trim():void
{
_batchPool.purge();
}
/** Sets all properties of the given token so that it describes the current position
* within this instance. */
public function fillToken(token:BatchToken):BatchToken
{
token.batchID = _cacheToken.batchID;
token.vertexID = _cacheToken.vertexID;
token.indexID = _cacheToken.indexID;
return token;
}
/** The number of batches currently stored in the BatchProcessor. */
public function get numBatches():int { return _batches.length; }
/** This callback is executed whenever a batch is finished and replaced by a new one.
* The finished MeshBatch is passed to the callback. Typically, this callback is used
* to actually render it. */
public function get onBatchComplete():Function { return _onBatchComplete; }
public function set onBatchComplete(value:Function):void { _onBatchComplete = value; }
}
}
import flash.utils.Dictionary;
import starling.display.MeshBatch;
class BatchPool
{
private var _batchLists:Dictionary;
public function BatchPool()
{
_batchLists = new Dictionary();
}
public function purge():void
{
for each (var batchList:Vector.<MeshBatch> in _batchLists)
{
for (var i:int=0; i<batchList.length; ++i)
batchList[i].dispose();
batchList.length = 0;
}
}
public function get(styleType:Class):MeshBatch
{
var batchList:Vector.<MeshBatch> = _batchLists[styleType];
if (batchList == null)
{
batchList = new <MeshBatch>[];
_batchLists[styleType] = batchList;
}
if (batchList.length > 0) return batchList.pop();
else return new MeshBatch();
}
public function put(meshBatch:MeshBatch):void
{
var styleType:Class = meshBatch.style.type;
var batchList:Vector.<MeshBatch> = _batchLists[styleType];
if (batchList == null)
{
batchList = new <MeshBatch>[];
_batchLists[styleType] = batchList;
}
meshBatch.clear();
batchList[batchList.length] = meshBatch;
}
}

View File

@@ -0,0 +1,77 @@
// =================================================================================================
//
// Starling Framework
// Copyright Gamua GmbH. All Rights Reserved.
//
// This program is free software. You can redistribute and/or modify it
// in accordance with the terms of the accompanying license agreement.
//
// =================================================================================================
package starling.rendering
{
import starling.utils.StringUtil;
/** Points to a location within a list of MeshBatches.
*
* <p>Starling uses these tokens in its render cache. Each call to
* <code>painter.pushState()</code> or <code>painter.popState()</code> provides a token
* referencing the current location within the cache. In the next frame, if the relevant
* part of the display tree has not changed, these tokens can be used to render directly
* from the cache instead of constructing new MeshBatches.</p>
*
* @see Painter
*/
public class BatchToken
{
/** The ID of the current MeshBatch. */
public var batchID:int;
/** The ID of the next vertex within the current MeshBatch. */
public var vertexID:int;
/** The ID of the next index within the current MeshBatch. */
public var indexID:int;
/** Creates a new BatchToken. */
public function BatchToken(batchID:int=0, vertexID:int=0, indexID:int=0)
{
setTo(batchID, vertexID, indexID);
}
/** Copies the properties from the given token to this instance. */
public function copyFrom(token:BatchToken):void
{
batchID = token.batchID;
vertexID = token.vertexID;
indexID = token.indexID;
}
/** Changes all properties at once. */
public function setTo(batchID:int=0, vertexID:int=0, indexID:int=0):void
{
this.batchID = batchID;
this.vertexID = vertexID;
this.indexID = indexID;
}
/** Resets all properties to zero. */
public function reset():void
{
batchID = vertexID = indexID = 0;
}
/** Indicates if this token contains the same values as the given one. */
public function equals(other:BatchToken):Boolean
{
return batchID == other.batchID && vertexID == other.vertexID && indexID == other.indexID;
}
/** Creates a String representation of this instance. */
public function toString():String
{
return StringUtil.format("[BatchToken batchID={0} vertexID={1} indexID={2}]",
batchID, vertexID, indexID);
}
}
}

View File

@@ -0,0 +1,381 @@
// =================================================================================================
//
// Starling Framework
// Copyright Gamua GmbH. All Rights Reserved.
//
// This program is free software. You can redistribute and/or modify it
// in accordance with the terms of the accompanying license agreement.
//
// =================================================================================================
package starling.rendering
{
import flash.display3D.Context3D;
import flash.display3D.Context3DProgramType;
import flash.display3D.IndexBuffer3D;
import flash.display3D.VertexBuffer3D;
import flash.events.Event;
import flash.geom.Matrix3D;
import flash.utils.Dictionary;
import flash.utils.getQualifiedClassName;
import starling.core.Starling;
import starling.errors.MissingContextError;
import starling.utils.execute;
/** An effect encapsulates all steps of a Stage3D draw operation. It configures the
* render context and sets up shader programs as well as index- and vertex-buffers, thus
* providing the basic mechanisms of all low-level rendering.
*
* <p><strong>Using the Effect class</strong></p>
*
* <p>Effects are mostly used by the <code>MeshStyle</code> and <code>FragmentFilter</code>
* classes. When you extend those classes, you'll be required to provide a custom effect.
* Setting it up for rendering is done by the base class, though, so you rarely have to
* initiate the rendering yourself. Nevertheless, it's good to know how an effect is doing
* its work.</p>
*
* <p>Using an effect always follows steps shown in the example below. You create the
* effect, configure it, upload vertex data and then: draw!</p>
*
* <listing>
* // create effect
* var effect:MeshEffect = new MeshEffect();
*
* // configure effect
* effect.mvpMatrix3D = painter.state.mvpMatrix3D;
* effect.texture = getHeroTexture();
* effect.color = 0xf0f0f0;
*
* // upload vertex data
* effect.uploadIndexData(indexData);
* effect.uploadVertexData(vertexData);
*
* // draw!
* effect.render(0, numTriangles);</listing>
*
* <p>Note that the <code>VertexData</code> being uploaded has to be created with the same
* format as the one returned by the effect's <code>vertexFormat</code> property.</p>
*
* <p><strong>Extending the Effect class</strong></p>
*
* <p>The base <code>Effect</code>-class can only render white triangles, which is not much
* use in itself. However, it is designed to be extended; subclasses can easily implement any
* kinds of shaders.</p>
*
* <p>Normally, you won't extend this class directly, but either <code>FilterEffect</code>
* or <code>MeshEffect</code>, depending on your needs (i.e. if you want to create a new
* fragment filter or a new mesh style). Whichever base class you're extending, you should
* override the following methods:</p>
*
* <ul>
* <li><code>createProgram():Program</code> — must create the actual program containing
* vertex- and fragment-shaders. A program will be created only once for each render
* context; this is taken care of by the base class.</li>
* <li><code>get programVariantName():uint</code> (optional) — override this if your
* effect requires different programs, depending on its settings. The recommended
* way to do this is via a bit-mask that uniquely encodes the current settings.</li>
* <li><code>get vertexFormat():String</code> (optional) — must return the
* <code>VertexData</code> format that this effect requires for its vertices. If
* the effect does not require any special attributes, you can leave this out.</li>
* <li><code>beforeDraw(context:Context3D):void</code> — Set up your context by
* configuring program constants and buffer attributes.</li>
* <li><code>afterDraw(context:Context3D):void</code> — Will be called directly after
* <code>context.drawTriangles()</code>. Clean up any context configuration here.</li>
* </ul>
*
* <p>Furthermore, you need to add properties that manage the data you require on rendering,
* e.g. the texture(s) that should be used, program constants, etc. I recommend looking at
* the implementations of Starling's <code>FilterEffect</code> and <code>MeshEffect</code>
* classes to see how to approach sub-classing.</p>
*
* @see FilterEffect
* @see MeshEffect
* @see starling.styles.MeshStyle
* @see starling.filters.FragmentFilter
* @see starling.utils.RenderUtil
*/
public class Effect
{
/** The vertex format expected by <code>uploadVertexData</code>:
* <code>"position:float2"</code> */
public static const VERTEX_FORMAT:VertexDataFormat =
VertexDataFormat.fromString("position:float2");
private var _vertexBuffer:VertexBuffer3D;
private var _vertexBufferSize:int; // in bytes
private var _indexBuffer:IndexBuffer3D;
private var _indexBufferSize:int; // in number of indices
private var _indexBufferUsesQuadLayout:Boolean;
private var _mvpMatrix3D:Matrix3D;
private var _onRestore:Function;
private var _programBaseName:String;
// helper objects
private static var sProgramNameCache:Dictionary = new Dictionary();
/** Creates a new effect. */
public function Effect()
{
_mvpMatrix3D = new Matrix3D();
_programBaseName = getQualifiedClassName(this);
// Handle lost context (using conventional Flash event for weak listener support)
Starling.current.stage3D.addEventListener(Event.CONTEXT3D_CREATE,
onContextCreated, false, 20, true);
}
/** Purges the index- and vertex-buffers. */
public function dispose():void
{
Starling.current.stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreated);
purgeBuffers();
}
private function onContextCreated(event:Event):void
{
purgeBuffers();
execute(_onRestore, this);
}
/** Purges one or both of the vertex- and index-buffers. */
public function purgeBuffers(vertexBuffer:Boolean=true, indexBuffer:Boolean=true):void
{
// We wrap the dispose calls in a try/catch block to work around a stage3D problem.
// Since they are not re-used later, that shouldn't have any evil side effects.
if (_vertexBuffer && vertexBuffer)
{
try { _vertexBuffer.dispose(); } catch (e:Error) {}
_vertexBuffer = null;
}
if (_indexBuffer && indexBuffer)
{
try { _indexBuffer.dispose(); } catch (e:Error) {}
_indexBuffer = null;
}
}
/** Uploads the given index data to the internal index buffer. If the buffer is too
* small, a new one is created automatically.
*
* @param indexData The IndexData instance to upload.
* @param bufferUsage The expected buffer usage. Use one of the constants defined in
* <code>Context3DBufferUsage</code>. Only used when the method call
* causes the creation of a new index buffer.
*/
public function uploadIndexData(indexData:IndexData,
bufferUsage:String="staticDraw"):void
{
var numIndices:int = indexData.numIndices;
var isQuadLayout:Boolean = indexData.useQuadLayout;
var wasQuadLayout:Boolean = _indexBufferUsesQuadLayout;
if (_indexBuffer)
{
if (numIndices <= _indexBufferSize)
{
if (!isQuadLayout || !wasQuadLayout)
{
indexData.uploadToIndexBuffer(_indexBuffer);
_indexBufferUsesQuadLayout = isQuadLayout && numIndices == _indexBufferSize;
}
}
else
purgeBuffers(false, true);
}
if (_indexBuffer == null)
{
_indexBuffer = indexData.createIndexBuffer(true, bufferUsage);
_indexBufferSize = numIndices;
_indexBufferUsesQuadLayout = isQuadLayout;
}
}
/** Uploads the given vertex data to the internal vertex buffer. If the buffer is too
* small, a new one is created automatically.
*
* @param vertexData The VertexData instance to upload.
* @param bufferUsage The expected buffer usage. Use one of the constants defined in
* <code>Context3DBufferUsage</code>. Only used when the method call
* causes the creation of a new vertex buffer.
*/
public function uploadVertexData(vertexData:VertexData,
bufferUsage:String="staticDraw"):void
{
if (_vertexBuffer)
{
if (vertexData.size <= _vertexBufferSize)
vertexData.uploadToVertexBuffer(_vertexBuffer);
else
purgeBuffers(true, false);
}
if (_vertexBuffer == null)
{
_vertexBuffer = vertexData.createVertexBuffer(true, bufferUsage);
_vertexBufferSize = vertexData.size;
}
}
// rendering
/** Draws the triangles described by the index- and vertex-buffers, or a range of them.
* This calls <code>beforeDraw</code>, <code>context.drawTriangles</code>, and
* <code>afterDraw</code>, in this order. */
public function render(firstIndex:int=0, numTriangles:int=-1):void
{
if (numTriangles < 0) numTriangles = _indexBufferSize / 3;
if (numTriangles == 0) return;
var context:Context3D = Starling.context;
if (context == null) throw new MissingContextError();
beforeDraw(context);
context.drawTriangles(indexBuffer, firstIndex, numTriangles);
afterDraw(context);
}
/** This method is called by <code>render</code>, directly before
* <code>context.drawTriangles</code>. It activates the program and sets up
* the context with the following constants and attributes:
*
* <ul>
* <li><code>vc0-vc3</code> — MVP matrix</li>
* <li><code>va0</code> — vertex position (xy)</li>
* </ul>
*/
protected function beforeDraw(context:Context3D):void
{
program.activate(context);
vertexFormat.setVertexBufferAt(0, vertexBuffer, "position");
context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, mvpMatrix3D, true);
}
/** This method is called by <code>render</code>, directly after
* <code>context.drawTriangles</code>. Resets vertex buffer attributes.
*/
protected function afterDraw(context:Context3D):void
{
context.setVertexBufferAt(0, null);
}
// program management
/** Creates the program (a combination of vertex- and fragment-shader) used to render
* the effect with the current settings. Override this method in a subclass to create
* your shaders. This method will only be called once; the program is automatically stored
* in the <code>Painter</code> and re-used by all instances of this effect.
*
* <p>The basic implementation always outputs pure white.</p>
*/
protected function createProgram():Program
{
var vertexShader:String = [
"m44 op, va0, vc0", // 4x4 matrix transform to output clipspace
"seq v0, va0, va0" // this is a hack that always produces "1"
].join("\n");
var fragmentShader:String =
"mov oc, v0"; // output color: white
return Program.fromSource(vertexShader, fragmentShader);
}
/** Override this method if the effect requires a different program depending on the
* current settings. Ideally, you do this by creating a bit mask encoding all the options.
* This method is called often, so do not allocate any temporary objects when overriding.
*
* @default 0
*/
protected function get programVariantName():uint
{
return 0;
}
/** Returns the base name for the program.
* @default the fully qualified class name
*/
protected function get programBaseName():String { return _programBaseName; }
protected function set programBaseName(value:String):void { _programBaseName = value; }
/** Returns the full name of the program, which is used to register it at the current
* <code>Painter</code>.
*
* <p>The default implementation efficiently combines the program's base and variant
* names (e.g. <code>LightEffect#42</code>). It shouldn't be necessary to override
* this method.</p>
*/
protected function get programName():String
{
var baseName:String = this.programBaseName;
var variantName:uint = this.programVariantName;
var nameCache:Dictionary = sProgramNameCache[baseName];
if (nameCache == null)
{
nameCache = new Dictionary();
sProgramNameCache[baseName] = nameCache;
}
var name:String = nameCache[variantName];
if (name == null)
{
if (variantName) name = baseName + "#" + variantName.toString(16);
else name = baseName;
nameCache[variantName] = name;
}
return name;
}
/** Returns the current program, either by creating a new one (via
* <code>createProgram</code>) or by getting it from the <code>Painter</code>.
* Do not override this method! Instead, implement <code>createProgram</code>. */
protected function get program():Program
{
var name:String = this.programName;
var painter:Painter = Starling.painter;
var program:Program = painter.getProgram(name);
if (program == null)
{
program = createProgram();
painter.registerProgram(name, program);
}
return program;
}
// properties
/** The function that you provide here will be called after a context loss.
* Call both "upload..." methods from within the callback to restore any vertex or
* index buffers. The callback will be executed with the effect as its sole parameter. */
public function get onRestore():Function { return _onRestore; }
public function set onRestore(value:Function):void { _onRestore = value; }
/** The data format that this effect requires from the VertexData that it renders:
* <code>"position:float2"</code> */
public function get vertexFormat():VertexDataFormat { return VERTEX_FORMAT; }
/** The MVP (modelview-projection) matrix transforms vertices into clipspace. */
public function get mvpMatrix3D():Matrix3D { return _mvpMatrix3D; }
public function set mvpMatrix3D(value:Matrix3D):void { _mvpMatrix3D.copyFrom(value); }
/** The internally used index buffer used on rendering. */
protected function get indexBuffer():IndexBuffer3D { return _indexBuffer; }
/** The current size of the index buffer (in number of indices). */
protected function get indexBufferSize():int { return _indexBufferSize; }
/** The internally used vertex buffer used on rendering. */
protected function get vertexBuffer():VertexBuffer3D { return _vertexBuffer; }
/** The current size of the vertex buffer (in blocks of 32 bits). */
protected function get vertexBufferSize():int { return _vertexBufferSize; }
}
}

View File

@@ -0,0 +1,147 @@
// =================================================================================================
//
// Starling Framework
// Copyright Gamua GmbH. All Rights Reserved.
//
// This program is free software. You can redistribute and/or modify it
// in accordance with the terms of the accompanying license agreement.
//
// =================================================================================================
package starling.rendering
{
import flash.display3D.Context3D;
import starling.textures.Texture;
import starling.textures.TextureSmoothing;
import starling.utils.RenderUtil;
/** An effect drawing a mesh of textured vertices.
* This is the standard effect that is the base for all fragment filters;
* if you want to create your own fragment filters, you will have to extend this class.
*
* <p>For more information about the usage and creation of effects, please have a look at
* the documentation of the parent class, "Effect".</p>
*
* @see Effect
* @see MeshEffect
* @see starling.filters.FragmentFilter
*/
public class FilterEffect extends Effect
{
/** The vertex format expected by <code>uploadVertexData</code>:
* <code>"position:float2, texCoords:float2"</code> */
public static const VERTEX_FORMAT:VertexDataFormat =
Effect.VERTEX_FORMAT.extend("texCoords:float2");
/** The AGAL code for the standard vertex shader that most filters will use.
* It simply transforms the vertex coordinates to clip-space and passes the texture
* coordinates to the fragment program (as 'v0'). */
public static const STD_VERTEX_SHADER:String =
"m44 op, va0, vc0 \n"+ // 4x4 matrix transform to output clip-space
"mov v0, va1"; // pass texture coordinates to fragment program
private var _texture:Texture;
private var _textureSmoothing:String;
private var _textureRepeat:Boolean;
/** Creates a new FilterEffect instance. */
public function FilterEffect()
{
_textureSmoothing = TextureSmoothing.BILINEAR;
}
/** Override this method if the effect requires a different program depending on the
* current settings. Ideally, you do this by creating a bit mask encoding all the options.
* This method is called often, so do not allocate any temporary objects when overriding.
*
* <p>Reserve 4 bits for the variant name of the base class.</p>
*/
override protected function get programVariantName():uint
{
return RenderUtil.getTextureVariantBits(_texture);
}
/** @private */
override protected function createProgram():Program
{
if (_texture)
{
var vertexShader:String = STD_VERTEX_SHADER;
var fragmentShader:String = tex("oc", "v0", 0, _texture);
return Program.fromSource(vertexShader, fragmentShader);
}
else
{
return super.createProgram();
}
}
/** This method is called by <code>render</code>, directly before
* <code>context.drawTriangles</code>. It activates the program and sets up
* the context with the following constants and attributes:
*
* <ul>
* <li><code>vc0-vc3</code> — MVP matrix</li>
* <li><code>va0</code> — vertex position (xy)</li>
* <li><code>va1</code> — texture coordinates (uv)</li>
* <li><code>fs0</code> — texture</li>
* </ul>
*/
override protected function beforeDraw(context:Context3D):void
{
super.beforeDraw(context);
if (_texture)
{
var repeat:Boolean = _textureRepeat && _texture.root.isPotTexture;
RenderUtil.setSamplerStateAt(0, _texture.mipMapping, _textureSmoothing, repeat);
context.setTextureAt(0, _texture.base);
vertexFormat.setVertexBufferAt(1, vertexBuffer, "texCoords");
}
}
/** This method is called by <code>render</code>, directly after
* <code>context.drawTriangles</code>. Resets texture and vertex buffer attributes. */
override protected function afterDraw(context:Context3D):void
{
if (_texture)
{
context.setTextureAt(0, null);
context.setVertexBufferAt(1, null);
}
super.afterDraw(context);
}
/** Creates an AGAL source string with a <code>tex</code> operation, including an options
* list with the appropriate format flag. This is just a convenience method forwarding
* to the respective RenderUtil method.
*
* @see starling.utils.RenderUtil#createAGALTexOperation()
*/
protected static function tex(resultReg:String, uvReg:String, sampler:int, texture:Texture,
convertToPmaIfRequired:Boolean=true):String
{
return RenderUtil.createAGALTexOperation(resultReg, uvReg, sampler, texture,
convertToPmaIfRequired);
}
/** The data format that this effect requires from the VertexData that it renders:
* <code>"position:float2, texCoords:float2"</code> */
override public function get vertexFormat():VertexDataFormat { return VERTEX_FORMAT; }
/** The texture to be mapped onto the vertices. */
public function get texture():Texture { return _texture; }
public function set texture(value:Texture):void { _texture = value; }
/** The smoothing filter that is used for the texture. @default bilinear */
public function get textureSmoothing():String { return _textureSmoothing; }
public function set textureSmoothing(value:String):void { _textureSmoothing = value; }
/** Indicates if pixels at the edges will be repeated or clamped.
* Only works for power-of-two textures. @default false */
public function get textureRepeat():Boolean { return _textureRepeat; }
public function set textureRepeat(value:Boolean):void { _textureRepeat = value; }
}
}

View File

@@ -0,0 +1,552 @@
// =================================================================================================
//
// Starling Framework
// Copyright Gamua GmbH. All Rights Reserved.
//
// This program is free software. You can redistribute and/or modify it
// in accordance with the terms of the accompanying license agreement.
//
// =================================================================================================
package starling.rendering
{
import flash.display3D.Context3D;
import flash.display3D.IndexBuffer3D;
import flash.errors.EOFError;
import flash.utils.ByteArray;
import flash.utils.Endian;
import starling.core.Starling;
import starling.errors.MissingContextError;
import starling.utils.StringUtil;
/** The IndexData class manages a raw list of vertex indices, allowing direct upload
* to Stage3D index buffers. <em>You only have to work with this class if you're writing
* your own rendering code (e.g. if you create custom display objects).</em>
*
* <p>To render objects with Stage3D, you have to organize vertices and indices in so-called
* vertex- and index-buffers. Vertex buffers store the coordinates of the vertices that make
* up an object; index buffers reference those vertices to determine which vertices spawn
* up triangles. Those buffers reside in graphics memory and can be accessed very
* efficiently by the GPU.</p>
*
* <p>Before you can move data into the buffers, you have to set it up in conventional
* memory — that is, in a Vector or a ByteArray. Since it's quite cumbersome to manually
* create and manipulate those data structures, the IndexData and VertexData classes provide
* a simple way to do just that. The data is stored in a ByteArray (one index or vertex after
* the other) that can easily be uploaded to a buffer.</p>
*
* <strong>Basic Quad Layout</strong>
*
* <p>In many cases, the indices we are working with will reference just quads, i.e.
* triangles composing rectangles. That means that many IndexData instances will contain
* similar or identical data — a great opportunity for optimization!</p>
*
* <p>If an IndexData instance follows a specific layout, it will be recognized
* automatically and many operations can be executed much faster. In Starling, that
* layout is called "basic quad layout". In order to recognize this specific sequence,
* the indices of each quad have to use the following order:</p>
*
* <pre>n, n+1, n+2, n+1, n+3, n+2</pre>
*
* <p>The subsequent quad has to use <code>n+4</code> as starting value, the next one
* <code>n+8</code>, etc. Here is an example with 3 quads / 6 triangles:</p>
*
* <pre>0, 1, 2, 1, 3, 2, 4, 5, 6, 5, 7, 6, 8, 9, 10, 9, 11, 10</pre>
*
* <p>If you are describing quad-like meshes, make sure to always use this layout.</p>
*
* @see VertexData
*/
public class IndexData
{
/** The number of bytes per index element. */
private static const INDEX_SIZE:int = 2;
private var _rawData:ByteArray;
private var _numIndices:int;
private var _initialCapacity:int;
private var _useQuadLayout:Boolean;
// basic quad layout
private static var sQuadData:ByteArray = new ByteArray();
private static var sQuadDataNumIndices:uint = 0;
// helper objects
private static var sVector:Vector.<uint> = new <uint>[];
private static var sTrimData:ByteArray = new ByteArray();
/** Creates an empty IndexData instance with the given capacity (in indices).
*
* @param initialCapacity
*
* The initial capacity affects just the way the internal ByteArray is allocated, not the
* <code>numIndices</code> value, which will always be zero when the constructor returns.
* The reason for this behavior is the peculiar way in which ByteArrays organize their
* memory:
*
* <p>The first time you set the length of a ByteArray, it will adhere to that:
* a ByteArray with length 20 will take up 20 bytes (plus some overhead). When you change
* it to a smaller length, it will stick to the original value, e.g. with a length of 10
* it will still take up 20 bytes. However, now comes the weird part: change it to
* anything above the original length, and it will allocate 4096 bytes!</p>
*
* <p>Thus, be sure to always make a generous educated guess, depending on the planned
* usage of your IndexData instances.</p>
*/
public function IndexData(initialCapacity:int=48)
{
_numIndices = 0;
_initialCapacity = initialCapacity;
_useQuadLayout = true;
}
/** Explicitly frees up the memory used by the ByteArray, thus removing all indices.
* Quad layout will be restored (until adding data violating that layout). */
public function clear():void
{
if (_rawData)
_rawData.clear();
_numIndices = 0;
_useQuadLayout = true;
}
/** Creates a duplicate of the IndexData object. */
public function clone():IndexData
{
var clone:IndexData = new IndexData(_numIndices);
if (!_useQuadLayout)
{
clone.switchToGenericData();
clone._rawData.writeBytes(_rawData);
}
clone._numIndices = _numIndices;
return clone;
}
/** Copies the index data (or a range of it, defined by 'indexID' and 'numIndices')
* of this instance to another IndexData object, starting at a certain target index.
* If the target is not big enough, it will grow to fit all the new indices.
*
* <p>By passing a non-zero <code>offset</code>, you can raise all copied indices
* by that value in the target object.</p>
*/
public function copyTo(target:IndexData, targetIndexID:int=0, offset:int=0,
indexID:int=0, numIndices:int=-1):void
{
if (numIndices < 0 || indexID + numIndices > _numIndices)
numIndices = _numIndices - indexID;
var sourceData:ByteArray, targetData:ByteArray;
var newNumIndices:int = targetIndexID + numIndices;
if (target._numIndices < newNumIndices)
{
target._numIndices = newNumIndices;
if (sQuadDataNumIndices < newNumIndices)
ensureQuadDataCapacity(newNumIndices);
}
if (_useQuadLayout)
{
if (target._useQuadLayout)
{
var keepsQuadLayout:Boolean = true;
var distance:int = targetIndexID - indexID;
var distanceInQuads:int = distance / 6;
var offsetInQuads:int = offset / 4;
// This code is executed very often. If it turns out that both IndexData
// instances use a quad layout, we don't need to do anything here.
//
// When "distance / 6 == offset / 4 && distance % 6 == 0 && offset % 4 == 0",
// the copy operation preserves the quad layout. In that case, we can exit
// right away. The code below is a little more complex, though, to avoid the
// (surprisingly costly) mod-operations.
if (distanceInQuads == offsetInQuads && (offset & 3) == 0 &&
distanceInQuads * 6 == distance)
{
keepsQuadLayout = true;
}
else if (numIndices > 2)
{
keepsQuadLayout = false;
}
else
{
for (var i:int=0; i<numIndices; ++i)
keepsQuadLayout &&=
getBasicQuadIndexAt(indexID + i) + offset ==
getBasicQuadIndexAt(targetIndexID + i);
}
if (keepsQuadLayout) return;
else target.switchToGenericData();
}
sourceData = sQuadData;
targetData = target._rawData;
if ((offset & 3) == 0) // => offset % 4 == 0
{
indexID += 6 * offset / 4;
offset = 0;
ensureQuadDataCapacity(indexID + numIndices);
}
}
else
{
if (target._useQuadLayout)
target.switchToGenericData();
sourceData = _rawData;
targetData = target._rawData;
}
targetData.position = targetIndexID * INDEX_SIZE;
if (offset == 0)
targetData.writeBytes(sourceData, indexID * INDEX_SIZE, numIndices * INDEX_SIZE);
else
{
sourceData.position = indexID * INDEX_SIZE;
// by reading junks of 32 instead of 16 bits, we can spare half the time
while (numIndices > 1)
{
var indexAB:uint = sourceData.readUnsignedInt();
var indexA:uint = ((indexAB & 0xffff0000) >> 16) + offset;
var indexB:uint = ((indexAB & 0x0000ffff) ) + offset;
targetData.writeUnsignedInt(indexA << 16 | indexB);
numIndices -= 2;
}
if (numIndices)
targetData.writeShort(sourceData.readUnsignedShort() + offset);
}
}
/** Sets an index at the specified position. */
public function setIndex(indexID:int, index:uint):void
{
if (_numIndices < indexID + 1)
numIndices = indexID + 1;
if (_useQuadLayout)
{
if (getBasicQuadIndexAt(indexID) == index) return;
else switchToGenericData();
}
_rawData.position = indexID * INDEX_SIZE;
_rawData.writeShort(index);
}
/** Reads the index from the specified position. */
public function getIndex(indexID:int):int
{
if (_useQuadLayout)
{
if (indexID < _numIndices)
return getBasicQuadIndexAt(indexID);
else
throw new EOFError();
}
else
{
_rawData.position = indexID * INDEX_SIZE;
return _rawData.readUnsignedShort();
}
}
/** Adds an offset to all indices in the specified range. */
public function offsetIndices(offset:int, indexID:int=0, numIndices:int=-1):void
{
if (numIndices < 0 || indexID + numIndices > _numIndices)
numIndices = _numIndices - indexID;
var endIndex:int = indexID + numIndices;
for (var i:int=indexID; i<endIndex; ++i)
setIndex(i, getIndex(i) + offset);
}
/** Appends three indices representing a triangle. Reference the vertices clockwise,
* as this defines the front side of the triangle. */
public function addTriangle(a:uint, b:uint, c:uint):void
{
if (_useQuadLayout)
{
if (a == getBasicQuadIndexAt(_numIndices))
{
var oddTriangleID:Boolean = (_numIndices & 1) != 0;
var evenTriangleID:Boolean = !oddTriangleID;
if ((evenTriangleID && b == a + 1 && c == b + 1) ||
(oddTriangleID && c == a + 1 && b == c + 1))
{
_numIndices += 3;
ensureQuadDataCapacity(_numIndices);
return;
}
}
switchToGenericData();
}
_rawData.position = _numIndices * INDEX_SIZE;
_rawData.writeShort(a);
_rawData.writeShort(b);
_rawData.writeShort(c);
_numIndices += 3;
}
/** Appends two triangles spawning up the quad with the given indices.
* The indices of the vertices are arranged like this:
*
* <pre>
* a - b
* | / |
* c - d
* </pre>
*
* <p>To make sure the indices will follow the basic quad layout, make sure each
* parameter increments the one before it (e.g. <code>0, 1, 2, 3</code>).</p>
*/
public function addQuad(a:uint, b:uint, c:uint, d:uint):void
{
if (_useQuadLayout)
{
if (a == getBasicQuadIndexAt(_numIndices) &&
b == a + 1 && c == b + 1 && d == c + 1)
{
_numIndices += 6;
ensureQuadDataCapacity(_numIndices);
return;
}
else switchToGenericData();
}
_rawData.position = _numIndices * INDEX_SIZE;
_rawData.writeShort(a);
_rawData.writeShort(b);
_rawData.writeShort(c);
_rawData.writeShort(b);
_rawData.writeShort(d);
_rawData.writeShort(c);
_numIndices += 6;
}
/** Creates a vector containing all indices. If you pass an existing vector to the method,
* its contents will be overwritten. */
public function toVector(out:Vector.<uint>=null):Vector.<uint>
{
if (out == null) out = new Vector.<uint>(_numIndices);
else out.length = _numIndices;
var rawData:ByteArray = _useQuadLayout ? sQuadData : _rawData;
rawData.position = 0;
for (var i:int=0; i<_numIndices; ++i)
out[i] = rawData.readUnsignedShort();
return out;
}
/** Returns a string representation of the IndexData object,
* including a comma-separated list of all indices. */
public function toString():String
{
var string:String = StringUtil.format("[IndexData numIndices={0} indices=\"{1}\"]",
_numIndices, toVector(sVector).join());
sVector.length = 0;
return string;
}
// private helpers
private function switchToGenericData():void
{
if (_useQuadLayout)
{
_useQuadLayout = false;
if (_rawData == null)
{
_rawData = new ByteArray();
_rawData.endian = Endian.LITTLE_ENDIAN;
_rawData.length = _initialCapacity * INDEX_SIZE; // -> allocated memory
_rawData.length = _numIndices * INDEX_SIZE; // -> actual length
}
if (_numIndices)
_rawData.writeBytes(sQuadData, 0, _numIndices * INDEX_SIZE);
}
}
/** Makes sure that the ByteArray containing the normalized, basic quad data contains at
* least <code>numIndices</code> indices. The array might grow, but it will never be
* made smaller. */
private function ensureQuadDataCapacity(numIndices:int):void
{
if (sQuadDataNumIndices >= numIndices) return;
var i:int;
var oldNumQuads:int = sQuadDataNumIndices / 6;
var newNumQuads:int = Math.ceil(numIndices / 6);
sQuadData.endian = Endian.LITTLE_ENDIAN;
sQuadData.position = sQuadData.length;
sQuadDataNumIndices = newNumQuads * 6;
for (i = oldNumQuads; i < newNumQuads; ++i)
{
sQuadData.writeShort(4 * i);
sQuadData.writeShort(4 * i + 1);
sQuadData.writeShort(4 * i + 2);
sQuadData.writeShort(4 * i + 1);
sQuadData.writeShort(4 * i + 3);
sQuadData.writeShort(4 * i + 2);
}
}
/** Returns the index that's expected at this position if following basic quad layout. */
private static function getBasicQuadIndexAt(indexID:int):int
{
var quadID:int = indexID / 6;
var posInQuad:int = indexID - quadID * 6; // => indexID % 6
var offset:int;
if (posInQuad == 0) offset = 0;
else if (posInQuad == 1 || posInQuad == 3) offset = 1;
else if (posInQuad == 2 || posInQuad == 5) offset = 2;
else offset = 3;
return quadID * 4 + offset;
}
// IndexBuffer helpers
/** Creates an index buffer object with the right size to fit the complete data.
* Optionally, the current data is uploaded right away. */
public function createIndexBuffer(upload:Boolean=false,
bufferUsage:String="staticDraw"):IndexBuffer3D
{
var context:Context3D = Starling.context;
if (context == null) throw new MissingContextError();
if (_numIndices == 0) return null;
var buffer:IndexBuffer3D = context.createIndexBuffer(_numIndices, bufferUsage);
if (upload) uploadToIndexBuffer(buffer);
return buffer;
}
/** Uploads the complete data (or a section of it) to the given index buffer. */
public function uploadToIndexBuffer(buffer:IndexBuffer3D, indexID:int=0, numIndices:int=-1):void
{
if (numIndices < 0 || indexID + numIndices > _numIndices)
numIndices = _numIndices - indexID;
if (numIndices > 0)
buffer.uploadFromByteArray(rawData, 0, indexID, numIndices);
}
/** Optimizes the ByteArray so that it has exactly the required capacity, without
* wasting any memory. If your IndexData object grows larger than the initial capacity
* you passed to the constructor, call this method to avoid the 4k memory problem. */
public function trim():void
{
if (_useQuadLayout) return;
sTrimData.length = _rawData.length;
sTrimData.position = 0;
sTrimData.writeBytes(_rawData);
_rawData.clear();
_rawData.length = sTrimData.length;
_rawData.writeBytes(sTrimData);
sTrimData.clear();
}
// properties
/** The total number of indices.
*
* <p>If this instance contains only standardized, basic quad indices, resizing
* will automatically fill up with appropriate quad indices. Otherwise, it will fill
* up with zeroes.</p>
*
* <p>If you set the number of indices to zero, quad layout will be restored.</p> */
public function get numIndices():int { return _numIndices; }
public function set numIndices(value:int):void
{
if (value != _numIndices)
{
if (_useQuadLayout) ensureQuadDataCapacity(value);
else _rawData.length = value * INDEX_SIZE;
if (value == 0) _useQuadLayout = true;
_numIndices = value;
}
}
/** The number of triangles that can be spawned up with the contained indices.
* (In other words: the number of indices divided by three.) */
public function get numTriangles():int { return _numIndices / 3; }
public function set numTriangles(value:int):void { numIndices = value * 3; }
/** The number of quads that can be spawned up with the contained indices.
* (In other words: the number of triangles divided by two.) */
public function get numQuads():int { return _numIndices / 6; }
public function set numQuads(value:int):void { numIndices = value * 6; }
/** The number of bytes required for each index value. */
public function get indexSizeInBytes():int { return INDEX_SIZE; }
/** Indicates if all indices are following the basic quad layout.
*
* <p>This property is automatically updated if an index is set to a value that violates
* basic quad layout. Once the layout was violated, the instance will always stay that
* way, even if you fix that violating value later. Only calling <code>clear</code> or
* manually enabling the property will restore quad layout.</p>
*
* <p>If you enable this property on an instance, all indices will immediately be
* replaced with indices following standard quad layout.</p>
*
* <p>Please look at the class documentation for more information about that kind
* of layout, and why it is important.</p>
*
* @default true
*/
public function get useQuadLayout():Boolean { return _useQuadLayout; }
public function set useQuadLayout(value:Boolean):void
{
if (value != _useQuadLayout)
{
if (value)
{
ensureQuadDataCapacity(_numIndices);
_rawData.length = 0;
_useQuadLayout = true;
}
else switchToGenericData();
}
}
/** The raw index data; not a copy! Beware: the referenced ByteArray may change any time.
* Never store a reference to it, and never modify its contents manually. */
public function get rawData():ByteArray
{
if (_useQuadLayout) return sQuadData;
else return _rawData;
}
}
}

View File

@@ -0,0 +1,145 @@
// =================================================================================================
//
// Starling Framework
// Copyright Gamua GmbH. All Rights Reserved.
//
// This program is free software. You can redistribute and/or modify it
// in accordance with the terms of the accompanying license agreement.
//
// =================================================================================================
package starling.rendering
{
import flash.display3D.Context3D;
import flash.display3D.Context3DProgramType;
import flash.utils.getQualifiedClassName;
import starling.utils.RenderUtil;
/** An effect drawing a mesh of textured, colored vertices.
* This is the standard effect that is the base for all mesh styles;
* if you want to create your own mesh styles, you will have to extend this class.
*
* <p>For more information about the usage and creation of effects, please have a look at
* the documentation of the root class, "Effect".</p>
*
* @see Effect
* @see FilterEffect
* @see starling.styles.MeshStyle
*/
public class MeshEffect extends FilterEffect
{
/** The vertex format expected by <code>uploadVertexData</code>:
* <code>"position:float2, texCoords:float2, color:bytes4"</code> */
public static const VERTEX_FORMAT:VertexDataFormat =
FilterEffect.VERTEX_FORMAT.extend("color:bytes4");
private var _alpha:Number;
private var _tinted:Boolean;
private var _optimizeIfNotTinted:Boolean;
// helper objects
private static var sRenderAlpha:Vector.<Number> = new Vector.<Number>(4, true);
/** Creates a new MeshEffect instance. */
public function MeshEffect()
{
// Non-tinted meshes may be rendered with a simpler fragment shader, which brings
// a huge performance benefit on some low-end hardware. However, I don't want
// subclasses to become any more complicated because of this optimization (they
// probably use much longer shaders, anyway), so I only apply this optimization if
// this is actually the "MeshEffect" class.
_alpha = 1.0;
_optimizeIfNotTinted = getQualifiedClassName(this) == "starling.rendering::MeshEffect";
}
/** @private */
override protected function get programVariantName():uint
{
var noTinting:uint = uint(_optimizeIfNotTinted && !_tinted && _alpha == 1.0);
return super.programVariantName | (noTinting << 3);
}
/** @private */
override protected function createProgram():Program
{
var vertexShader:String, fragmentShader:String;
if (texture)
{
if (_optimizeIfNotTinted && !_tinted && _alpha == 1.0)
return super.createProgram();
vertexShader =
"m44 op, va0, vc0 \n" + // 4x4 matrix transform to output clip-space
"mov v0, va1 \n" + // pass texture coordinates to fragment program
"mul v1, va2, vc4 \n"; // multiply alpha (vc4) with color (va2), pass to fp
fragmentShader =
tex("ft0", "v0", 0, texture) +
"mul oc, ft0, v1 \n"; // multiply color with texel color
}
else
{
vertexShader =
"m44 op, va0, vc0 \n" + // 4x4 matrix transform to output clipspace
"mul v0, va2, vc4 \n"; // multiply alpha (vc4) with color (va2)
fragmentShader =
"mov oc, v0 \n"; // output color
}
return Program.fromSource(vertexShader, fragmentShader);
}
/** This method is called by <code>render</code>, directly before
* <code>context.drawTriangles</code>. It activates the program and sets up
* the context with the following constants and attributes:
*
* <ul>
* <li><code>vc0-vc3</code> — MVP matrix</li>
* <li><code>vc4</code> — alpha value (same value for all components)</li>
* <li><code>va0</code> — vertex position (xy)</li>
* <li><code>va1</code> — texture coordinates (uv)</li>
* <li><code>va2</code> — vertex color (rgba), using premultiplied alpha</li>
* <li><code>fs0</code> — texture</li>
* </ul>
*/
override protected function beforeDraw(context:Context3D):void
{
super.beforeDraw(context);
sRenderAlpha[0] = sRenderAlpha[1] = sRenderAlpha[2] = sRenderAlpha[3] = _alpha;
context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 4, sRenderAlpha);
if (_tinted || _alpha != 1.0 || !_optimizeIfNotTinted || texture == null)
vertexFormat.setVertexBufferAt(2, vertexBuffer, "color");
}
/** This method is called by <code>render</code>, directly after
* <code>context.drawTriangles</code>. Resets texture and vertex buffer attributes. */
override protected function afterDraw(context:Context3D):void
{
context.setVertexBufferAt(2, null);
super.afterDraw(context);
}
/** The data format that this effect requires from the VertexData that it renders:
* <code>"position:float2, texCoords:float2, color:bytes4"</code> */
override public function get vertexFormat():VertexDataFormat { return VERTEX_FORMAT; }
/** The alpha value of the object rendered by the effect. Must be taken into account
* by all subclasses. */
public function get alpha():Number { return _alpha; }
public function set alpha(value:Number):void { _alpha = value; }
/** Indicates if the rendered vertices are tinted in any way, i.e. if there are vertices
* that have a different color than fully opaque white. The base <code>MeshEffect</code>
* class uses this information to simplify the fragment shader if possible. May be
* ignored by subclasses. */
public function get tinted():Boolean { return _tinted; }
public function set tinted(value:Boolean):void { _tinted = value; }
}
}

View File

@@ -0,0 +1,909 @@
// =================================================================================================
//
// Starling Framework
// Copyright Gamua GmbH. All Rights Reserved.
//
// This program is free software. You can redistribute and/or modify it
// in accordance with the terms of the accompanying license agreement.
//
// =================================================================================================
package starling.rendering
{
import flash.display.Stage3D;
import flash.display3D.Context3D;
import flash.display3D.Context3DCompareMode;
import flash.display3D.Context3DStencilAction;
import flash.display3D.Context3DTriangleFace;
import flash.display3D.textures.TextureBase;
import flash.errors.IllegalOperationError;
import flash.geom.Matrix;
import flash.geom.Matrix3D;
import flash.geom.Rectangle;
import flash.geom.Vector3D;
import flash.utils.Dictionary;
import starling.core.starling_internal;
import starling.display.BlendMode;
import starling.display.DisplayObject;
import starling.display.Mesh;
import starling.display.MeshBatch;
import starling.display.Quad;
import starling.events.Event;
import starling.textures.Texture;
import starling.utils.MathUtil;
import starling.utils.MatrixUtil;
import starling.utils.MeshSubset;
import starling.utils.Pool;
import starling.utils.RectangleUtil;
import starling.utils.RenderUtil;
import starling.utils.SystemUtil;
use namespace starling_internal;
/** A class that orchestrates rendering of all Starling display objects.
*
* <p>A Starling instance contains exactly one 'Painter' instance that should be used for all
* rendering purposes. Each frame, it is passed to the render methods of all rendered display
* objects. To access it outside a render method, call <code>Starling.painter</code>.</p>
*
* <p>The painter is responsible for drawing all display objects to the screen. At its
* core, it is a wrapper for many Context3D methods, but that's not all: it also provides
* a convenient state mechanism, supports masking and acts as middleman between display
* objects and renderers.</p>
*
* <strong>The State Stack</strong>
*
* <p>The most important concept of the Painter class is the state stack. A RenderState
* stores a combination of settings that are currently used for rendering, e.g. the current
* projection- and modelview-matrices and context-related settings. It can be accessed
* and manipulated via the <code>state</code> property. Use the methods
* <code>pushState</code> and <code>popState</code> to store a specific state and restore
* it later. That makes it easy to write rendering code that doesn't have any side effects.</p>
*
* <listing>
* painter.pushState(); // save a copy of the current state on the stack
* painter.state.renderTarget = renderTexture;
* painter.state.transformModelviewMatrix(object.transformationMatrix);
* painter.state.alpha = 0.5;
* painter.prepareToDraw(); // apply all state settings at the render context
* drawSomething(); // insert Stage3D rendering code here
* painter.popState(); // restores previous state</listing>
*
* @see RenderState
*/
public class Painter
{
// the key for the programs stored in 'sharedData'
private static const PROGRAM_DATA_NAME:String = "starling.rendering.Painter.Programs";
// members
private var _stage3D:Stage3D;
private var _context:Context3D;
private var _shareContext:Boolean;
private var _drawCount:int;
private var _frameID:uint;
private var _pixelSize:Number;
private var _enableErrorChecking:Boolean;
private var _stencilReferenceValues:Dictionary;
private var _clipRectStack:Vector.<Rectangle>;
private var _batchCacheExclusions:Vector.<DisplayObject>;
private var _batchProcessor:BatchProcessor;
private var _batchProcessorCurr:BatchProcessor; // current processor
private var _batchProcessorPrev:BatchProcessor; // previous processor (cache)
private var _batchProcessorSpec:BatchProcessor; // special processor (no cache)
private var _actualRenderTarget:TextureBase;
private var _actualCulling:String;
private var _actualBlendMode:String;
private var _backBufferWidth:Number;
private var _backBufferHeight:Number;
private var _backBufferScaleFactor:Number;
private var _state:RenderState;
private var _stateStack:Vector.<RenderState>;
private var _stateStackPos:int;
private var _stateStackLength:int;
// shared data
private static var sSharedData:Dictionary = new Dictionary();
// helper objects
private static var sMatrix:Matrix = new Matrix();
private static var sPoint3D:Vector3D = new Vector3D();
private static var sMatrix3D:Matrix3D = new Matrix3D();
private static var sClipRect:Rectangle = new Rectangle();
private static var sBufferRect:Rectangle = new Rectangle();
private static var sScissorRect:Rectangle = new Rectangle();
private static var sMeshSubset:MeshSubset = new MeshSubset();
// construction
/** Creates a new Painter object. Normally, it's not necessary to create any custom
* painters; instead, use the global painter found on the Starling instance. */
public function Painter(stage3D:Stage3D)
{
_stage3D = stage3D;
_stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreated, false, 40, true);
_context = _stage3D.context3D;
_shareContext = _context && _context.driverInfo != "Disposed";
_backBufferWidth = _context ? _context.backBufferWidth : 0;
_backBufferHeight = _context ? _context.backBufferHeight : 0;
_backBufferScaleFactor = _pixelSize = 1.0;
_stencilReferenceValues = new Dictionary(true);
_clipRectStack = new <Rectangle>[];
_batchProcessorCurr = new BatchProcessor();
_batchProcessorCurr.onBatchComplete = drawBatch;
_batchProcessorPrev = new BatchProcessor();
_batchProcessorPrev.onBatchComplete = drawBatch;
_batchProcessorSpec = new BatchProcessor();
_batchProcessorSpec.onBatchComplete = drawBatch;
_batchProcessor = _batchProcessorCurr;
_batchCacheExclusions = new Vector.<DisplayObject>();
_state = new RenderState();
_state.onDrawRequired = finishMeshBatch;
_stateStack = new <RenderState>[];
_stateStackPos = -1;
_stateStackLength = 0;
}
/** Disposes all mesh batches, programs, and - if it is not being shared -
* the render context. */
public function dispose():void
{
_batchProcessorCurr.dispose();
_batchProcessorPrev.dispose();
_batchProcessorSpec.dispose();
if (!_shareContext)
{
_context.dispose(false);
sSharedData = new Dictionary();
}
}
// context handling
/** Requests a context3D object from the stage3D object.
* This is called by Starling internally during the initialization process.
* You normally don't need to call this method yourself. (For a detailed description
* of the parameters, look at the documentation of the method with the same name in the
* "RenderUtil" class.)
*
* @see starling.utils.RenderUtil
*/
public function requestContext3D(renderMode:String, profile:*):void
{
RenderUtil.requestContext3D(_stage3D, renderMode, profile);
}
private function onContextCreated(event:Object):void
{
_context = _stage3D.context3D;
_context.enableErrorChecking = _enableErrorChecking;
_context.setDepthTest(false, Context3DCompareMode.ALWAYS);
_actualBlendMode = null;
_actualCulling = null;
}
/** Sets the viewport dimensions and other attributes of the rendering buffer.
* Starling will call this method internally, so most apps won't need to mess with this.
*
* <p>Beware: if <code>shareContext</code> is enabled, the method will only update the
* painter's context-related information (like the size of the back buffer), but won't
* make any actual changes to the context.</p>
*
* @param viewPort the position and size of the area that should be rendered
* into, in pixels.
* @param contentScaleFactor only relevant for Desktop (!) HiDPI screens. If you want
* to support high resolutions, pass the 'contentScaleFactor'
* of the Flash stage; otherwise, '1.0'.
* @param antiAlias from 0 (none) to 16 (very high quality).
* @param enableDepthAndStencil indicates whether the depth and stencil buffers should
* be enabled. Note that on AIR, you also have to enable
* this setting in the app-xml (application descriptor);
* otherwise, this setting will be silently ignored.
*/
public function configureBackBuffer(viewPort:Rectangle, contentScaleFactor:Number,
antiAlias:int, enableDepthAndStencil:Boolean):void
{
if (!_shareContext)
{
enableDepthAndStencil &&= SystemUtil.supportsDepthAndStencil;
// Changing the stage3D position might move the back buffer to invalid bounds
// temporarily. To avoid problems, we set it to the smallest possible size first.
if (_context.profile == "baselineConstrained")
_context.configureBackBuffer(32, 32, antiAlias, enableDepthAndStencil);
// If supporting HiDPI mode would exceed the maximum buffer size
// (can happen e.g in software mode), we stick to the low resolution.
if (viewPort.width * contentScaleFactor > _context.maxBackBufferWidth ||
viewPort.height * contentScaleFactor > _context.maxBackBufferHeight)
{
contentScaleFactor = 1.0;
}
_stage3D.x = viewPort.x;
_stage3D.y = viewPort.y;
_context.configureBackBuffer(viewPort.width, viewPort.height,
antiAlias, enableDepthAndStencil, contentScaleFactor != 1.0);
}
_backBufferWidth = viewPort.width;
_backBufferHeight = viewPort.height;
_backBufferScaleFactor = contentScaleFactor;
}
// program management
/** Registers a program under a certain name.
* If the name was already used, the previous program is overwritten. */
public function registerProgram(name:String, program:Program):void
{
deleteProgram(name);
programs[name] = program;
}
/** Deletes the program of a certain name. */
public function deleteProgram(name:String):void
{
var program:Program = getProgram(name);
if (program)
{
program.dispose();
delete programs[name];
}
}
/** Returns the program registered under a certain name, or null if no program with
* this name has been registered. */
public function getProgram(name:String):Program
{
return programs[name] as Program;
}
/** Indicates if a program is registered under a certain name. */
public function hasProgram(name:String):Boolean
{
return name in programs;
}
// state stack
/** Pushes the current render state to a stack from which it can be restored later.
*
* <p>If you pass a BatchToken, it will be updated to point to the current location within
* the render cache. That way, you can later reference this location to render a subset of
* the cache.</p>
*/
public function pushState(token:BatchToken=null):void
{
_stateStackPos++;
if (_stateStackLength < _stateStackPos + 1) _stateStack[_stateStackLength++] = new RenderState();
if (token) _batchProcessor.fillToken(token);
_stateStack[_stateStackPos].copyFrom(_state);
}
/** Modifies the current state with a transformation matrix, alpha factor, and blend mode.
*
* @param transformationMatrix Used to transform the current <code>modelviewMatrix</code>.
* @param alphaFactor Multiplied with the current alpha value.
* @param blendMode Replaces the current blend mode; except for "auto", which
* means the current value remains unchanged.
*/
public function setStateTo(transformationMatrix:Matrix, alphaFactor:Number=1.0,
blendMode:String="auto"):void
{
if (transformationMatrix) MatrixUtil.prependMatrix(_state._modelviewMatrix, transformationMatrix);
if (alphaFactor != 1.0) _state._alpha *= alphaFactor;
if (blendMode != BlendMode.AUTO) _state.blendMode = blendMode;
}
/** Restores the render state that was last pushed to the stack. If this changes
* blend mode, clipping rectangle, render target or culling, the current batch
* will be drawn right away.
*
* <p>If you pass a BatchToken, it will be updated to point to the current location within
* the render cache. That way, you can later reference this location to render a subset of
* the cache.</p>
*/
public function popState(token:BatchToken=null):void
{
if (_stateStackPos < 0)
throw new IllegalOperationError("Cannot pop empty state stack");
_state.copyFrom(_stateStack[_stateStackPos]); // -> might cause 'finishMeshBatch'
_stateStackPos--;
if (token) _batchProcessor.fillToken(token);
}
// masks
/** Draws a display object into the stencil buffer, incrementing the buffer on each
* used pixel. The stencil reference value is incremented as well; thus, any subsequent
* stencil tests outside of this area will fail.
*
* <p>If 'mask' is part of the display list, it will be drawn at its conventional stage
* coordinates. Otherwise, it will be drawn with the current modelview matrix.</p>
*
* <p>As an optimization, this method might update the clipping rectangle of the render
* state instead of utilizing the stencil buffer. This is possible when the mask object
* is of type <code>starling.display.Quad</code> and is aligned parallel to the stage
* axes.</p>
*
* <p>Note that masking breaks the render cache; the masked object must be redrawn anew
* in the next frame. If you pass <code>maskee</code>, the method will automatically
* call <code>excludeFromCache(maskee)</code> for you.</p>
*/
public function drawMask(mask:DisplayObject, maskee:DisplayObject=null):void
{
if (_context == null) return;
finishMeshBatch();
if (isRectangularMask(mask, maskee, sMatrix))
{
mask.getBounds(mask, sClipRect);
RectangleUtil.getBounds(sClipRect, sMatrix, sClipRect);
pushClipRect(sClipRect);
}
else
{
_context.setStencilActions(Context3DTriangleFace.FRONT_AND_BACK,
Context3DCompareMode.EQUAL, Context3DStencilAction.INCREMENT_SATURATE);
renderMask(mask);
stencilReferenceValue++;
_context.setStencilActions(Context3DTriangleFace.FRONT_AND_BACK,
Context3DCompareMode.EQUAL, Context3DStencilAction.KEEP);
}
excludeFromCache(maskee);
}
/** Draws a display object into the stencil buffer, decrementing the
* buffer on each used pixel. This effectively erases the object from the stencil buffer,
* restoring the previous state. The stencil reference value will be decremented.
*
* <p>Note: if the mask object meets the requirements of using the clipping rectangle,
* it will be assumed that this erase operation undoes the clipping rectangle change
* caused by the corresponding <code>drawMask()</code> call.</p>
*/
public function eraseMask(mask:DisplayObject, maskee:DisplayObject=null):void
{
if (_context == null) return;
finishMeshBatch();
if (isRectangularMask(mask, maskee, sMatrix))
{
popClipRect();
}
else
{
_context.setStencilActions(Context3DTriangleFace.FRONT_AND_BACK,
Context3DCompareMode.EQUAL, Context3DStencilAction.DECREMENT_SATURATE);
renderMask(mask);
stencilReferenceValue--;
_context.setStencilActions(Context3DTriangleFace.FRONT_AND_BACK,
Context3DCompareMode.EQUAL, Context3DStencilAction.KEEP);
}
}
private function renderMask(mask:DisplayObject):void
{
var wasCacheEnabled:Boolean = cacheEnabled;
pushState();
cacheEnabled = false;
_state.alpha = 0.0;
var matrix:Matrix = null;
var matrix3D:Matrix3D = null;
if (mask.stage)
{
_state.setModelviewMatricesToIdentity();
if (mask.is3D) matrix3D = mask.getTransformationMatrix3D(null, sMatrix3D);
else matrix = mask.getTransformationMatrix(null, sMatrix);
}
else
{
if (mask.is3D) matrix3D = mask.transformationMatrix3D;
else matrix = mask.transformationMatrix;
}
if (matrix3D) _state.transformModelviewMatrix3D(matrix3D);
else _state.transformModelviewMatrix(matrix);
mask.render(this);
finishMeshBatch();
cacheEnabled = wasCacheEnabled;
popState();
}
private function pushClipRect(clipRect:Rectangle):void
{
var stack:Vector.<Rectangle> = _clipRectStack;
var stackLength:uint = stack.length;
var intersection:Rectangle = Pool.getRectangle();
if (stackLength)
RectangleUtil.intersect(stack[stackLength - 1], clipRect, intersection);
else
intersection.copyFrom(clipRect);
stack[stackLength] = intersection;
_state.clipRect = intersection;
}
private function popClipRect():void
{
var stack:Vector.<Rectangle> = _clipRectStack;
var stackLength:uint = stack.length;
if (stackLength == 0)
throw new Error("Trying to pop from empty clip rectangle stack");
stackLength--;
Pool.putRectangle(stack.pop());
_state.clipRect = stackLength ? stack[stackLength - 1] : null;
}
/** Figures out if the mask can be represented by a scissor rectangle; this is possible
* if it's just a simple (untextured) quad that is parallel to the stage axes. The 'out'
* parameter will be filled with the transformation matrix required to move the mask into
* stage coordinates. */
private function isRectangularMask(mask:DisplayObject, maskee:DisplayObject, out:Matrix):Boolean
{
var quad:Quad = mask as Quad;
var is3D:Boolean = mask.is3D || (maskee && maskee.is3D && mask.stage == null);
if (quad && !is3D && quad.texture == null)
{
if (mask.stage) mask.getTransformationMatrix(null, out);
else
{
out.copyFrom(mask.transformationMatrix);
out.concat(_state.modelviewMatrix);
}
return (MathUtil.isEquivalent(out.a, 0) && MathUtil.isEquivalent(out.d, 0)) ||
(MathUtil.isEquivalent(out.b, 0) && MathUtil.isEquivalent(out.c, 0));
}
return false;
}
// mesh rendering
/** Adds a mesh to the current batch of unrendered meshes. If the current batch is not
* compatible with the mesh, all previous meshes are rendered at once and the batch
* is cleared.
*
* @param mesh The mesh to batch.
* @param subset The range of vertices to be batched. If <code>null</code>, the complete
* mesh will be used.
*/
public function batchMesh(mesh:Mesh, subset:MeshSubset=null):void
{
_batchProcessor.addMesh(mesh, _state, subset);
}
/** Finishes the current mesh batch and prepares the next one. */
public function finishMeshBatch():void
{
_batchProcessor.finishBatch();
}
/** Completes all unfinished batches, cleanup procedures. */
public function finishFrame():void
{
if (_frameID % 99 == 0) _batchProcessorCurr.trim(); // odd number -> alternating processors
if (_frameID % 150 == 0) _batchProcessorSpec.trim();
_batchProcessor.finishBatch();
_batchProcessor = _batchProcessorSpec; // no cache between frames
processCacheExclusions();
}
private function processCacheExclusions():void
{
var i:int, length:int = _batchCacheExclusions.length;
for (i=0; i<length; ++i) _batchCacheExclusions[i].excludeFromCache();
_batchCacheExclusions.length = 0;
}
/** Resets the current state, state stack, batch processor, stencil reference value,
* clipping rectangle, and draw count. Furthermore, depth testing is disabled. */
public function nextFrame():void
{
// update batch processors
_batchProcessor = swapBatchProcessors();
_batchProcessor.clear();
_batchProcessorSpec.clear();
// enforce reset of basic context settings
_actualBlendMode = null;
_actualCulling = null;
_context.setDepthTest(false, Context3DCompareMode.ALWAYS);
// reset everything else
stencilReferenceValue = 0;
_clipRectStack.length = 0;
_drawCount = 0;
_stateStackPos = -1;
_state.reset();
}
private function swapBatchProcessors():BatchProcessor
{
var tmp:BatchProcessor = _batchProcessorPrev;
_batchProcessorPrev = _batchProcessorCurr;
return _batchProcessorCurr = tmp;
}
/** Draws all meshes from the render cache between <code>startToken</code> and
* (but not including) <code>endToken</code>. The render cache contains all meshes
* rendered in the previous frame. */
public function drawFromCache(startToken:BatchToken, endToken:BatchToken):void
{
var meshBatch:MeshBatch;
var subset:MeshSubset = sMeshSubset;
if (!startToken.equals(endToken))
{
pushState();
for (var i:int = startToken.batchID; i <= endToken.batchID; ++i)
{
meshBatch = _batchProcessorPrev.getBatchAt(i);
subset.setTo(); // resets subset
if (i == startToken.batchID)
{
subset.vertexID = startToken.vertexID;
subset.indexID = startToken.indexID;
subset.numVertices = meshBatch.numVertices - subset.vertexID;
subset.numIndices = meshBatch.numIndices - subset.indexID;
}
if (i == endToken.batchID)
{
subset.numVertices = endToken.vertexID - subset.vertexID;
subset.numIndices = endToken.indexID - subset.indexID;
}
if (subset.numVertices)
{
_state.alpha = 1.0;
_state.blendMode = meshBatch.blendMode;
_batchProcessor.addMesh(meshBatch, _state, subset, true);
}
}
popState();
}
}
/** Prevents the object from being drawn from the render cache in the next frame.
* Different to <code>setRequiresRedraw()</code>, this does not indicate that the object
* has changed in any way, but just that it doesn't support being drawn from cache.
*
* <p>Note that when a container is excluded from the render cache, its children will
* still be cached! This just means that batching is interrupted at this object when
* the display tree is traversed.</p>
*/
public function excludeFromCache(object:DisplayObject):void
{
if (object) _batchCacheExclusions[_batchCacheExclusions.length] = object;
}
private function drawBatch(meshBatch:MeshBatch):void
{
pushState();
state.blendMode = meshBatch.blendMode;
state.modelviewMatrix.identity();
state.alpha = 1.0;
meshBatch.render(this);
popState();
}
// helper methods
/** Applies all relevant state settings to at the render context. This includes
* blend mode, render target and clipping rectangle. Always call this method before
* <code>context.drawTriangles()</code>.
*/
public function prepareToDraw():void
{
applyBlendMode();
applyRenderTarget();
applyClipRect();
applyCulling();
}
/** Clears the render context with a certain color and alpha value. Since this also
* clears the stencil buffer, the stencil reference value is also reset to '0'. */
public function clear(rgb:uint=0, alpha:Number=0.0):void
{
applyRenderTarget();
stencilReferenceValue = 0;
RenderUtil.clear(rgb, alpha);
}
/** Resets the render target to the back buffer and displays its contents. */
public function present():void
{
_state.renderTarget = null;
_actualRenderTarget = null;
_context.present();
}
private function applyBlendMode():void
{
var blendMode:String = _state.blendMode;
if (blendMode != _actualBlendMode)
{
BlendMode.get(_state.blendMode).activate();
_actualBlendMode = blendMode;
}
}
private function applyCulling():void
{
var culling:String = _state.culling;
if (culling != _actualCulling)
{
_context.setCulling(culling);
_actualCulling = culling;
}
}
private function applyRenderTarget():void
{
var target:TextureBase = _state.renderTargetBase;
if (target != _actualRenderTarget)
{
if (target)
{
var antiAlias:int = _state.renderTargetAntiAlias;
var depthAndStencil:Boolean = _state.renderTargetSupportsDepthAndStencil;
_context.setRenderToTexture(target, depthAndStencil, antiAlias);
}
else
_context.setRenderToBackBuffer();
_context.setStencilReferenceValue(stencilReferenceValue);
_actualRenderTarget = target;
}
}
private function applyClipRect():void
{
var clipRect:Rectangle = _state.clipRect;
if (clipRect)
{
var width:int, height:int;
var projMatrix:Matrix3D = _state.projectionMatrix3D;
var renderTarget:Texture = _state.renderTarget;
if (renderTarget)
{
width = renderTarget.root.nativeWidth;
height = renderTarget.root.nativeHeight;
}
else
{
width = _backBufferWidth;
height = _backBufferHeight;
}
// convert to pixel coordinates (matrix transformation ends up in range [-1, 1])
MatrixUtil.transformCoords3D(projMatrix, clipRect.x, clipRect.y, 0.0, sPoint3D);
sPoint3D.project(); // eliminate w-coordinate
sClipRect.x = (sPoint3D.x * 0.5 + 0.5) * width;
sClipRect.y = (0.5 - sPoint3D.y * 0.5) * height;
MatrixUtil.transformCoords3D(projMatrix, clipRect.right, clipRect.bottom, 0.0, sPoint3D);
sPoint3D.project(); // eliminate w-coordinate
sClipRect.right = (sPoint3D.x * 0.5 + 0.5) * width;
sClipRect.bottom = (0.5 - sPoint3D.y * 0.5) * height;
sBufferRect.setTo(0, 0, width, height);
RectangleUtil.intersect(sClipRect, sBufferRect, sScissorRect);
// an empty rectangle is not allowed, so we set it to the smallest possible size
if (sScissorRect.width < 1 || sScissorRect.height < 1)
sScissorRect.setTo(0, 0, 1, 1);
_context.setScissorRectangle(sScissorRect);
}
else
{
_context.setScissorRectangle(null);
}
}
// properties
/** Indicates the number of stage3D draw calls. */
public function get drawCount():int { return _drawCount; }
public function set drawCount(value:int):void { _drawCount = value; }
/** The current stencil reference value of the active render target. This value
* is typically incremented when drawing a mask and decrementing when erasing it.
* The painter keeps track of one stencil reference value per render target.
* Only change this value if you know what you're doing!
*/
public function get stencilReferenceValue():uint
{
var key:Object = _state.renderTarget ? _state.renderTargetBase : this;
if (key in _stencilReferenceValues) return _stencilReferenceValues[key];
else return 0;
}
public function set stencilReferenceValue(value:uint):void
{
var key:Object = _state.renderTarget ? _state.renderTargetBase : this;
_stencilReferenceValues[key] = value;
if (contextValid)
_context.setStencilReferenceValue(value);
}
/** Indicates if the render cache is enabled. Normally, this should be left at the default;
* however, some custom rendering logic might require to change this property temporarily.
* Also note that the cache is automatically reactivated each frame, right before the
* render process.
*
* @default true
*/
public function get cacheEnabled():Boolean { return _batchProcessor == _batchProcessorCurr; }
public function set cacheEnabled(value:Boolean):void
{
if (value != cacheEnabled)
{
finishMeshBatch();
if (value) _batchProcessor = _batchProcessorCurr;
else _batchProcessor = _batchProcessorSpec;
}
}
/** The current render state, containing some of the context settings, projection- and
* modelview-matrix, etc. Always returns the same instance, even after calls to "pushState"
* and "popState".
*
* <p>When you change the current RenderState, and this change is not compatible with
* the current render batch, the batch will be concluded right away. Thus, watch out
* for changes of blend mode, clipping rectangle, render target or culling.</p>
*/
public function get state():RenderState { return _state; }
/** The Stage3D instance this painter renders into. */
public function get stage3D():Stage3D { return _stage3D; }
/** The Context3D instance this painter renders into. */
public function get context():Context3D { return _context; }
/** Returns the index of the current frame <strong>if</strong> the render cache is enabled;
* otherwise, returns zero. To get the frameID regardless of the render cache, call
* <code>Starling.frameID</code> instead. */
public function set frameID(value:uint):void { _frameID = value; }
public function get frameID():uint
{
return _batchProcessor == _batchProcessorCurr ? _frameID : 0;
}
/** The size (in points) that represents one pixel in the back buffer. */
public function get pixelSize():Number { return _pixelSize; }
public function set pixelSize(value:Number):void { _pixelSize = value; }
/** Indicates if another Starling instance (or another Stage3D framework altogether)
* uses the same render context. @default false */
public function get shareContext():Boolean { return _shareContext; }
public function set shareContext(value:Boolean):void { _shareContext = value; }
/** Indicates if Stage3D render methods will report errors. Activate only when needed,
* as this has a negative impact on performance. @default false */
public function get enableErrorChecking():Boolean { return _enableErrorChecking; }
public function set enableErrorChecking(value:Boolean):void
{
_enableErrorChecking = value;
if (_context) _context.enableErrorChecking = value;
}
/** Returns the current width of the back buffer. In most cases, this value is in pixels;
* however, if the app is running on an HiDPI display with an activated
* 'supportHighResolutions' setting, you have to multiply with 'backBufferPixelsPerPoint'
* for the actual pixel count. Alternatively, use the Context3D-property with the
* same name: it will return the exact pixel values. */
public function get backBufferWidth():int { return _backBufferWidth; }
/** Returns the current height of the back buffer. In most cases, this value is in pixels;
* however, if the app is running on an HiDPI display with an activated
* 'supportHighResolutions' setting, you have to multiply with 'backBufferPixelsPerPoint'
* for the actual pixel count. Alternatively, use the Context3D-property with the
* same name: it will return the exact pixel values. */
public function get backBufferHeight():int { return _backBufferHeight; }
/** The number of pixels per point returned by the 'backBufferWidth/Height' properties.
* Except for desktop HiDPI displays with an activated 'supportHighResolutions' setting,
* this will always return '1'. */
public function get backBufferScaleFactor():Number { return _backBufferScaleFactor; }
/** Indicates if the Context3D object is currently valid (i.e. it hasn't been lost or
* disposed). */
public function get contextValid():Boolean
{
if (_context)
{
const driverInfo:String = _context.driverInfo;
return driverInfo != null && driverInfo != "" && driverInfo != "Disposed";
}
else return false;
}
/** The Context3D profile of the current render context, or <code>null</code>
* if the context has not been created yet. */
public function get profile():String
{
if (_context) return _context.profile;
else return null;
}
/** A dictionary that can be used to save custom data related to the render context.
* If you need to share data that is bound to the render context (e.g. textures), use
* this dictionary instead of creating a static class variable. That way, the data will
* be available for all Starling instances that use this stage3D / context. */
public function get sharedData():Dictionary
{
var data:Dictionary = sSharedData[stage3D] as Dictionary;
if (data == null)
{
data = new Dictionary();
sSharedData[stage3D] = data;
}
return data;
}
private function get programs():Dictionary
{
var programs:Dictionary = sharedData[PROGRAM_DATA_NAME] as Dictionary;
if (programs == null)
{
programs = new Dictionary();
sharedData[PROGRAM_DATA_NAME] = programs;
}
return programs;
}
}
}

View File

@@ -0,0 +1,104 @@
// =================================================================================================
//
// Starling Framework
// Copyright Gamua GmbH. All Rights Reserved.
//
// This program is free software. You can redistribute and/or modify it
// in accordance with the terms of the accompanying license agreement.
//
// =================================================================================================
package starling.rendering
{
import com.adobe.utils.AGALMiniAssembler;
import flash.display3D.Context3D;
import flash.display3D.Context3DProgramType;
import flash.display3D.Program3D;
import flash.events.Event;
import flash.utils.ByteArray;
import starling.core.Starling;
import starling.errors.MissingContextError;
/** A Program represents a pair of a fragment- and vertex-shader.
*
* <p>This class is a convenient replacement for Stage3Ds "Program3D" class. Its main
* advantage is that it survives a context loss; furthermore, it makes it simple to
* create a program from AGAL source without having to deal with the assembler.</p>
*
* <p>It is recommended to store programs in Starling's "Painter" instance via the methods
* <code>registerProgram</code> and <code>getProgram</code>. That way, your programs may
* be shared among different display objects or even Starling instances.</p>
*
* @see Painter
*/
public class Program
{
private var _vertexShader:ByteArray;
private var _fragmentShader:ByteArray;
private var _program3D:Program3D;
private static var sAssembler:AGALMiniAssembler = new AGALMiniAssembler();
/** Creates a program from the given AGAL (Adobe Graphics Assembly Language) bytecode. */
public function Program(vertexShader:ByteArray, fragmentShader:ByteArray)
{
_vertexShader = vertexShader;
_fragmentShader = fragmentShader;
// Handle lost context (using conventional Flash event for weak listener support)
Starling.current.stage3D.addEventListener(Event.CONTEXT3D_CREATE,
onContextCreated, false, 30, true);
}
/** Disposes the internal Program3D instance. */
public function dispose():void
{
Starling.current.stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreated);
disposeProgram();
}
/** Creates a new Program instance from AGAL assembly language. */
public static function fromSource(vertexShader:String, fragmentShader:String,
agalVersion:uint=1):Program
{
return new Program(
sAssembler.assemble(Context3DProgramType.VERTEX, vertexShader, agalVersion),
sAssembler.assemble(Context3DProgramType.FRAGMENT, fragmentShader, agalVersion));
}
/** Activates the program on the given context. If you don't pass a context, the current
* Starling context will be used. */
public function activate(context:Context3D=null):void
{
if (context == null)
{
context = Starling.context;
if (context == null) throw new MissingContextError();
}
if (_program3D == null)
{
_program3D = context.createProgram();
_program3D.upload(_vertexShader, _fragmentShader);
}
context.setProgram(_program3D);
}
private function onContextCreated(event:Event):void
{
disposeProgram();
}
private function disposeProgram():void
{
if (_program3D)
{
_program3D.dispose();
_program3D = null;
}
}
}
}

View File

@@ -0,0 +1,400 @@
// =================================================================================================
//
// Starling Framework
// Copyright Gamua GmbH. All Rights Reserved.
//
// This program is free software. You can redistribute and/or modify it
// in accordance with the terms of the accompanying license agreement.
//
// =================================================================================================
package starling.rendering
{
import flash.display3D.Context3DTriangleFace;
import flash.display3D.textures.TextureBase;
import flash.geom.Matrix;
import flash.geom.Matrix3D;
import flash.geom.Rectangle;
import flash.geom.Vector3D;
import starling.display.BlendMode;
import starling.textures.Texture;
import starling.utils.MathUtil;
import starling.utils.MatrixUtil;
import starling.utils.Pool;
import starling.utils.RectangleUtil;
/** The RenderState stores a combination of settings that are currently used for rendering.
* This includes modelview and transformation matrices as well as context3D related settings.
*
* <p>Starling's Painter instance stores a reference to the current RenderState.
* Via a stack mechanism, you can always save a specific state and restore it later.
* That makes it easy to write rendering code that doesn't have any side effects.</p>
*
* <p>Beware that any context-related settings are not applied on the context
* right away, but only after calling <code>painter.prepareToDraw()</code>.
* However, the Painter recognizes changes to those settings and will finish the current
* batch right away if necessary.</p>
*
* <strong>Matrix Magic</strong>
*
* <p>On rendering, Starling traverses the display tree, constantly moving from one
* coordinate system to the next. Each display object stores its vertex coordinates
* in its local coordinate system; on rendering, they must be moved to a global,
* 2D coordinate space (the so-called "clip-space"). To handle these calculations,
* the RenderState contains a set of matrices.</p>
*
* <p>By multiplying vertex coordinates with the <code>modelviewMatrix</code>, you'll get the
* coordinates in "screen-space", or in other words: in stage coordinates. (Optionally,
* there's also a 3D version of this matrix. It comes into play when you're working with
* <code>Sprite3D</code> containers.)</p>
*
* <p>By feeding the result of the previous transformation into the
* <code>projectionMatrix</code>, you'll end up with so-called "clipping coordinates",
* which are in the range <code>[-1, 1]</code> (just as needed by the graphics pipeline).
* If you've got vertices in the 3D space, this matrix will also execute a perspective
* projection.</p>
*
* <p>Finally, there's the <code>mvpMatrix</code>, which is short for
* "modelviewProjectionMatrix". This is simply a combination of <code>modelview-</code> and
* <code>projectionMatrix</code>, combining the effects of both. Pass this matrix
* to the vertex shader and all your vertices will automatically end up at the right
* position.</p>
*
* @see Painter
* @see starling.display.Sprite3D
*/
public class RenderState
{
/** @private */ internal var _alpha:Number;
/** @private */ internal var _blendMode:String;
/** @private */ internal var _modelviewMatrix:Matrix;
private static const CULLING_VALUES:Vector.<String> = new <String>
[Context3DTriangleFace.NONE, Context3DTriangleFace.FRONT,
Context3DTriangleFace.BACK, Context3DTriangleFace.FRONT_AND_BACK];
private var _miscOptions:uint;
private var _clipRect:Rectangle;
private var _renderTarget:Texture;
private var _onDrawRequired:Function;
private var _modelviewMatrix3D:Matrix3D;
private var _projectionMatrix3D:Matrix3D;
private var _projectionMatrix3DRev:uint;
private var _mvpMatrix3D:Matrix3D;
// helper objects
private static var sMatrix3D:Matrix3D = new Matrix3D();
private static var sProjectionMatrix3DRev:uint = 0;
/** Creates a new render state with the default settings. */
public function RenderState()
{
reset();
}
/** Duplicates all properties of another instance on the current instance. */
public function copyFrom(renderState:RenderState):void
{
if (_onDrawRequired != null)
{
var currentTarget:TextureBase = _renderTarget ? _renderTarget.base : null;
var nextTarget:TextureBase = renderState._renderTarget ? renderState._renderTarget.base : null;
var cullingChanges:Boolean = (_miscOptions & 0xf00) != (renderState._miscOptions & 0xf00);
var clipRectChanges:Boolean = _clipRect || renderState._clipRect ?
!RectangleUtil.compare(_clipRect, renderState._clipRect) : false;
if (_blendMode != renderState._blendMode ||
currentTarget != nextTarget || clipRectChanges || cullingChanges)
{
_onDrawRequired();
}
}
_alpha = renderState._alpha;
_blendMode = renderState._blendMode;
_renderTarget = renderState._renderTarget;
_miscOptions = renderState._miscOptions;
_modelviewMatrix.copyFrom(renderState._modelviewMatrix);
if (_projectionMatrix3DRev != renderState._projectionMatrix3DRev)
{
_projectionMatrix3DRev = renderState._projectionMatrix3DRev;
_projectionMatrix3D.copyFrom(renderState._projectionMatrix3D);
}
if (_modelviewMatrix3D || renderState._modelviewMatrix3D)
this.modelviewMatrix3D = renderState._modelviewMatrix3D;
if (_clipRect || renderState._clipRect)
this.clipRect = renderState._clipRect;
}
/** Resets the RenderState to the default settings.
* (Check each property documentation for its default value.) */
public function reset():void
{
this.alpha = 1.0;
this.blendMode = BlendMode.NORMAL;
this.culling = Context3DTriangleFace.NONE;
this.modelviewMatrix3D = null;
this.renderTarget = null;
this.clipRect = null;
_projectionMatrix3DRev = 0;
if (_modelviewMatrix) _modelviewMatrix.identity();
else _modelviewMatrix = new Matrix();
if (_projectionMatrix3D) _projectionMatrix3D.identity();
else _projectionMatrix3D = new Matrix3D();
if (_mvpMatrix3D == null) _mvpMatrix3D = new Matrix3D();
}
// matrix methods / properties
/** Prepends the given matrix to the 2D modelview matrix. */
public function transformModelviewMatrix(matrix:Matrix):void
{
MatrixUtil.prependMatrix(_modelviewMatrix, matrix);
}
/** Prepends the given matrix to the 3D modelview matrix.
* The current contents of the 2D modelview matrix is stored in the 3D modelview matrix
* before doing so; the 2D modelview matrix is then reset to the identity matrix.
*/
public function transformModelviewMatrix3D(matrix:Matrix3D):void
{
if (_modelviewMatrix3D == null)
_modelviewMatrix3D = Pool.getMatrix3D();
_modelviewMatrix3D.prepend(MatrixUtil.convertTo3D(_modelviewMatrix, sMatrix3D));
_modelviewMatrix3D.prepend(matrix);
_modelviewMatrix.identity();
}
/** Creates a perspective projection matrix suitable for 2D and 3D rendering.
*
* <p>The first 4 parameters define which area of the stage you want to view (the camera
* will 'zoom' to exactly this region). The final 3 parameters determine the perspective
* in which you're looking at the stage.</p>
*
* <p>The stage is always on the rectangle that is spawned up between x- and y-axis (with
* the given size). All objects that are exactly on that rectangle (z equals zero) will be
* rendered in their true size, without any distortion.</p>
*
* <p>If you pass only the first 4 parameters, the camera will be set up above the center
* of the stage, with a field of view of 1.0 rad.</p>
*/
public function setProjectionMatrix(x:Number, y:Number, width:Number, height:Number,
stageWidth:Number=0, stageHeight:Number=0,
cameraPos:Vector3D=null):void
{
_projectionMatrix3DRev = ++sProjectionMatrix3DRev;
MatrixUtil.createPerspectiveProjectionMatrix(
x, y, width, height, stageWidth, stageHeight, cameraPos, _projectionMatrix3D);
}
/** This method needs to be called whenever <code>projectionMatrix3D</code> was changed
* other than via <code>setProjectionMatrix</code>.
*/
public function setProjectionMatrixChanged():void
{
_projectionMatrix3DRev = ++sProjectionMatrix3DRev;
}
/** Changes the modelview matrices (2D and, if available, 3D) to identity matrices.
* An object transformed an identity matrix performs no transformation.
*/
public function setModelviewMatricesToIdentity():void
{
_modelviewMatrix.identity();
if (_modelviewMatrix3D) _modelviewMatrix3D.identity();
}
/** Returns the current 2D modelview matrix.
* CAUTION: Use with care! Each call returns the same instance.
* @default identity matrix */
public function get modelviewMatrix():Matrix { return _modelviewMatrix; }
public function set modelviewMatrix(value:Matrix):void { _modelviewMatrix.copyFrom(value); }
/** Returns the current 3D modelview matrix, if there have been 3D transformations.
* CAUTION: Use with care! Each call returns the same instance.
* @default null */
public function get modelviewMatrix3D():Matrix3D { return _modelviewMatrix3D; }
public function set modelviewMatrix3D(value:Matrix3D):void
{
if (value)
{
if (_modelviewMatrix3D == null) _modelviewMatrix3D = Pool.getMatrix3D(false);
_modelviewMatrix3D.copyFrom(value);
}
else if (_modelviewMatrix3D)
{
Pool.putMatrix3D(_modelviewMatrix3D);
_modelviewMatrix3D = null;
}
}
/** Returns the current projection matrix. You can use the method 'setProjectionMatrix3D'
* to set it up in an intuitive way.
* CAUTION: Use with care! Each call returns the same instance. If you modify the matrix
* in place, you have to call <code>setProjectionMatrixChanged</code>.
* @default identity matrix */
public function get projectionMatrix3D():Matrix3D { return _projectionMatrix3D; }
public function set projectionMatrix3D(value:Matrix3D):void
{
setProjectionMatrixChanged();
_projectionMatrix3D.copyFrom(value);
}
/** Calculates the product of modelview and projection matrix and stores it in a 3D matrix.
* CAUTION: Use with care! Each call returns the same instance. */
public function get mvpMatrix3D():Matrix3D
{
_mvpMatrix3D.copyFrom(_projectionMatrix3D);
if (_modelviewMatrix3D) _mvpMatrix3D.prepend(_modelviewMatrix3D);
_mvpMatrix3D.prepend(MatrixUtil.convertTo3D(_modelviewMatrix, sMatrix3D));
return _mvpMatrix3D;
}
// other methods
/** Changes the the current render target.
*
* @param target Either a texture or <code>null</code> to render into the back buffer.
* @param enableDepthAndStencil Indicates if depth and stencil testing will be available.
* This parameter affects only texture targets.
* @param antiAlias The anti-aliasing quality (range: <code>0 - 16</code>).
* This parameter affects only texture targets. Note that at the time
* of this writing, AIR supports anti-aliasing only on Desktop.
*/
public function setRenderTarget(target:Texture, enableDepthAndStencil:Boolean=true,
antiAlias:int=0):void
{
var currentTarget:TextureBase = _renderTarget ? _renderTarget.base : null;
var newTarget:TextureBase = target ? target.base : null;
var newOptions:uint = MathUtil.min(antiAlias, 16) | uint(enableDepthAndStencil) << 4;
var optionsChange:Boolean = newOptions != (_miscOptions & 0xff);
if (currentTarget != newTarget || optionsChange)
{
if (_onDrawRequired != null) _onDrawRequired();
_renderTarget = target;
_miscOptions = (_miscOptions & 0xffffff00) | newOptions;
}
}
// other properties
/** The current, cumulated alpha value. Beware that, in a standard 'render' method,
* this already includes the current object! The value is the product of current object's
* alpha value and all its parents. @default 1.0
*/
public function get alpha():Number { return _alpha; }
public function set alpha(value:Number):void { _alpha = value; }
/** The blend mode to be used on rendering. A value of "auto" is ignored, since it
* means that the mode should remain unchanged.
*
* @default BlendMode.NORMAL
* @see starling.display.BlendMode
*/
public function get blendMode():String { return _blendMode; }
public function set blendMode(value:String):void
{
if (value != BlendMode.AUTO && _blendMode != value)
{
if (_onDrawRequired != null) _onDrawRequired();
_blendMode = value;
}
}
/** The texture that is currently being rendered into, or <code>null</code>
* to render into the back buffer. On assignment, calls <code>setRenderTarget</code>
* with its default parameters. */
public function get renderTarget():Texture { return _renderTarget; }
public function set renderTarget(value:Texture):void { setRenderTarget(value); }
/** @private */
internal function get renderTargetBase():TextureBase
{
return _renderTarget ? _renderTarget.base : null;
}
/** Sets the triangle culling mode. Allows to exclude triangles from rendering based on
* their orientation relative to the view plane.
* @default Context3DTriangleFace.NONE
*/
public function get culling():String
{
var index:int = (_miscOptions & 0xf00) >> 8;
return CULLING_VALUES[index];
}
public function set culling(value:String):void
{
if (this.culling != value)
{
if (_onDrawRequired != null) _onDrawRequired();
var index:int = CULLING_VALUES.indexOf(value);
if (index == -1) throw new ArgumentError("Invalid culling mode");
_miscOptions = (_miscOptions & 0xfffff0ff) | (index << 8);
}
}
/** The clipping rectangle can be used to limit rendering in the current render target to
* a certain area. This method expects the rectangle in stage coordinates. To prevent
* any clipping, assign <code>null</code>.
*
* @default null
*/
public function get clipRect():Rectangle { return _clipRect; }
public function set clipRect(value:Rectangle):void
{
if (!RectangleUtil.compare(_clipRect, value))
{
if (_onDrawRequired != null) _onDrawRequired();
if (value)
{
if (_clipRect == null) _clipRect = Pool.getRectangle();
_clipRect.copyFrom(value);
}
else if (_clipRect)
{
Pool.putRectangle(_clipRect);
_clipRect = null;
}
}
}
/** The anti-alias setting used when setting the current render target
* via <code>setRenderTarget</code>. */
public function get renderTargetAntiAlias():int
{
return _miscOptions & 0xf;
}
/** Indicates if the render target (set via <code>setRenderTarget</code>)
* has its depth and stencil buffers enabled. */
public function get renderTargetSupportsDepthAndStencil():Boolean
{
return (_miscOptions & 0xf0) != 0;
}
/** Indicates if there have been any 3D transformations.
* Returns <code>true</code> if the 3D modelview matrix contains a value. */
public function get is3D():Boolean { return _modelviewMatrix3D != null; }
/** @private
*
* This callback is executed whenever a state change requires a draw operation.
* This is the case if blend mode, render target, culling or clipping rectangle
* are changing. */
internal function get onDrawRequired():Function { return _onDrawRequired; }
internal function set onDrawRequired(value:Function):void { _onDrawRequired = value; }
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
// =================================================================================================
//
// Starling Framework
// Copyright Gamua GmbH. All Rights Reserved.
//
// This program is free software. You can redistribute and/or modify it
// in accordance with the terms of the accompanying license agreement.
//
// =================================================================================================
package starling.rendering
{
/** Holds the properties of a single attribute in a VertexDataFormat instance.
* The member variables must never be changed; they are only <code>public</code>
* for performance reasons. */
internal class VertexDataAttribute
{
private static const FORMAT_SIZES:Object = {
"bytes4": 4,
"float1": 4,
"float2": 8,
"float3": 12,
"float4": 16
};
public var name:String;
public var format:String;
public var isColor:Boolean;
public var offset:int; // in bytes
public var size:int; // in bytes
/** Creates a new instance with the given properties. */
public function VertexDataAttribute(name:String, format:String, offset:int)
{
if (!(format in FORMAT_SIZES))
throw new ArgumentError(
"Invalid attribute format: " + format + ". " +
"Use one of the following: 'float1'-'float4', 'bytes4'");
this.name = name;
this.format = format;
this.offset = offset;
this.size = FORMAT_SIZES[format];
this.isColor = name.indexOf("color") != -1 || name.indexOf("Color") != -1
}
}
}

View File

@@ -0,0 +1,275 @@
// =================================================================================================
//
// Starling Framework
// Copyright Gamua GmbH. All Rights Reserved.
//
// This program is free software. You can redistribute and/or modify it
// in accordance with the terms of the accompanying license agreement.
//
// =================================================================================================
package starling.rendering
{
import flash.display3D.VertexBuffer3D;
import flash.utils.Dictionary;
import starling.core.Starling;
import starling.utils.StringUtil;
/** Describes the memory layout of VertexData instances, as used for every single vertex.
*
* <p>The format is set up via a simple String. Here is an example:</p>
*
* <listing>
* format = VertexDataFormat.fromString("position:float2, color:bytes4");</listing>
*
* <p>This String describes two attributes: "position" and "color". The keywords after
* the colons depict the format and size of the data that each attribute uses; in this
* case, we store two floats for the position (taking up the x- and y-coordinates) and four
* bytes for the color. (The available formats are the same as those defined in the
* <code>Context3DVertexBufferFormat</code> class:
* <code>float1, float2, float3, float4, bytes4</code>.)</p>
*
* <p>You cannot create a VertexData instance with its constructor; instead, you must use the
* static <code>fromString</code>-method. The reason for this behavior: the class maintains
* a cache, and a call to <code>fromString</code> will return an existing instance if an
* equivalent format has already been created in the past. That saves processing time and
* memory.</p>
*
* <p>VertexDataFormat instances are immutable, i.e. they are solely defined by their format
* string and cannot be changed later.</p>
*
* @see VertexData
*/
public class VertexDataFormat
{
private var _format:String;
private var _vertexSize:int;
private var _attributes:Vector.<VertexDataAttribute>;
// format cache
private static var sFormats:Dictionary = new Dictionary();
/** Don't use the constructor, but call <code>VertexDataFormat.fromString</code> instead.
* This allows for efficient format caching. */
public function VertexDataFormat()
{
_attributes = new Vector.<VertexDataAttribute>();
}
/** Creates a new VertexDataFormat instance from the given String, or returns one from
* the cache (if an equivalent String has already been used before).
*
* @param format
*
* Describes the attributes of each vertex, consisting of a comma-separated
* list of attribute names and their format, e.g.:
*
* <pre>"position:float2, texCoords:float2, color:bytes4"</pre>
*
* <p>This set of attributes will be allocated for each vertex, and they will be
* stored in exactly the given order.</p>
*
* <ul>
* <li>Names are used to access the specific attributes of a vertex. They are
* completely arbitrary.</li>
* <li>The available formats can be found in the <code>Context3DVertexBufferFormat</code>
* class in the <code>flash.display3D</code> package.</li>
* <li>Both names and format strings are case-sensitive.</li>
* <li>Always use <code>bytes4</code> for color data that you want to access with the
* respective methods.</li>
* <li>Furthermore, the attribute names of colors should include the string "color"
* (or the uppercase variant). If that's the case, the "alpha" channel of the color
* will automatically be initialized with "1.0" when the VertexData object is
* created or resized.</li>
* </ul>
*/
public static function fromString(format:String):VertexDataFormat
{
if (format in sFormats) return sFormats[format];
else
{
var instance:VertexDataFormat = new VertexDataFormat();
instance.parseFormat(format);
var normalizedFormat:String = instance._format;
if (normalizedFormat in sFormats)
instance = sFormats[normalizedFormat];
sFormats[format] = instance;
sFormats[normalizedFormat] = instance;
return instance;
}
}
/** Creates a new VertexDataFormat instance by appending the given format string
* to the current instance's format. */
public function extend(format:String):VertexDataFormat
{
return fromString(_format + ", " + format);
}
// query methods
/** Returns the size of a certain vertex attribute in bytes. */
public function getSize(attrName:String):int
{
return getAttribute(attrName).size;
}
/** Returns the size of a certain vertex attribute in 32 bit units. */
public function getSizeIn32Bits(attrName:String):int
{
return getAttribute(attrName).size / 4;
}
/** Returns the offset (in bytes) of an attribute within a vertex. */
public function getOffset(attrName:String):int
{
return getAttribute(attrName).offset;
}
/** Returns the offset (in 32 bit units) of an attribute within a vertex. */
public function getOffsetIn32Bits(attrName:String):int
{
return getAttribute(attrName).offset / 4;
}
/** Returns the format of a certain vertex attribute, identified by its name.
* Typical values: <code>float1, float2, float3, float4, bytes4</code>. */
public function getFormat(attrName:String):String
{
return getAttribute(attrName).format;
}
/** Returns the name of the attribute at the given position within the vertex format. */
public function getName(attrIndex:int):String
{
return _attributes[attrIndex].name;
}
/** Indicates if the format contains an attribute with the given name. */
public function hasAttribute(attrName:String):Boolean
{
var numAttributes:int = _attributes.length;
for (var i:int=0; i<numAttributes; ++i)
if (_attributes[i].name == attrName) return true;
return false;
}
// context methods
/** Specifies which vertex data attribute corresponds to a single vertex shader
* program input. This wraps the <code>Context3D</code>-method with the same name,
* automatically replacing <code>attrName</code> with the corresponding values for
* <code>bufferOffset</code> and <code>format</code>. */
public function setVertexBufferAt(index:int, buffer:VertexBuffer3D, attrName:String):void
{
var attribute:VertexDataAttribute = getAttribute(attrName);
Starling.context.setVertexBufferAt(index, buffer, attribute.offset / 4, attribute.format);
}
// parsing
private function parseFormat(format:String):void
{
if (format != null && format != "")
{
_attributes.length = 0;
_format = "";
var parts:Array = format.split(",");
var numParts:int = parts.length;
var offset:int = 0;
for (var i:int=0; i<numParts; ++i)
{
var attrDesc:String = parts[i];
var attrParts:Array = attrDesc.split(":");
if (attrParts.length != 2)
throw new ArgumentError("Missing colon: " + attrDesc);
var attrName:String = StringUtil.trim(attrParts[0]);
var attrFormat:String = StringUtil.trim(attrParts[1]);
if (attrName.length == 0 || attrFormat.length == 0)
throw new ArgumentError(("Invalid format string: " + attrDesc));
var attribute:VertexDataAttribute =
new VertexDataAttribute(attrName, attrFormat, offset);
offset += attribute.size;
_format += (i == 0 ? "" : ", ") + attribute.name + ":" + attribute.format;
_attributes[_attributes.length] = attribute; // avoid 'push'
}
_vertexSize = offset;
}
else
{
_format = "";
}
}
/** Returns the normalized format string. */
public function toString():String
{
return _format;
}
// internal methods
/** @private */
internal function getAttribute(attrName:String):VertexDataAttribute
{
var i:int, attribute:VertexDataAttribute;
var numAttributes:int = _attributes.length;
for (i=0; i<numAttributes; ++i)
{
attribute = _attributes[i];
if (attribute.name == attrName) return attribute;
}
return null;
}
/** @private */
internal function get attributes():Vector.<VertexDataAttribute>
{
return _attributes;
}
// properties
/** Returns the normalized format string. */
public function get formatString():String
{
return _format;
}
/** The size (in bytes) of each vertex. */
public function get vertexSize():int
{
return _vertexSize;
}
/** The size (in 32 bit units) of each vertex. */
public function get vertexSizeIn32Bits():int
{
return _vertexSize / 4;
}
/** The number of attributes per vertex. */
public function get numAttributes():int
{
return _attributes.length;
}
}
}