Using Adobe’s new compressed texture format should be as simple as replacing some PNG and JPEG images with ATFs their tools created, but it’s not. If you don’t know what you’re doing, the process can be pretty confusing. Today’s article walks you through the steps to upgrade a Stage3D-using app to make use of ATF textures.

As mentioned in the intro, the first steps are pretty straightforward:

  1. Convert some PNG files to ATF
  2. Replace your Texture.uploadFromBitmapData calls to Texture.uploadCompressedTextureFromByteArray
  3. Target Flash Player 11.4’s playerglobal.swc
  4. Replace your old version of Adobe’s AGALMiniAssembler class with the new one

If you get this error, you forgot to target Flash 11.4:

ArgumentError: Error #3677: Texture decoding failed. Internal error.
	at flash.display3D.textures::Texture/uploadCompressedTextureFromByteArray()
	at YourApp/yourFunction()

But even after correctly following all those steps you’ll get this error:

Error: Error #3606: Sampler 0 format does not match texture format.
	at flash.display3D::Context3D/drawTriangles()
	at YourApp/yourFunction()

This means that we missed a step which has no example in Adobe’s introductory post:

  1. Modify your AGAL fragment shader code to specify what kind of texture you’re sampling

You must specify “dxt1” for opaque ATF textures and “dxt5” for transparent ATF textures. Don’t worry about the specific compression algorithm being called out here since these are really just synonyms; the correct texture compression algorithm will still be used depending on the device your app is running on. Here’s what a simple fragment shader used to look like:

tex oc, v0, fs0 <2d,linear,mipnone,clamp>

And here’s what it needs to look like when we use ATF textures (in this case an opaque one):

tex oc, v0, fs0 <2d,linear,mipnone,clamp,dxt1>

You don’t need to specify anything if you’re still using uncompressed BitmapData-sourced textures. However, it seems that you can specify “dxt1” and still sample from an uncompressed texture. Will this work on every possible device? I don’t know, but it sure does work on my Mac OS X 10.8 running an Intel HD Graphics 4000 or an NVIDIA GeForce GT 650M. If you can can get away with always specifying it, that may simplify your shaders somewhat. You won’t need to create two versions of each shader: one for ATF textures and one for uncompressed textures.

To demonstrate the final product, I’ve made a small (<200 lines) test app that simply displays a texture flat on the stage:

	import flash.display3D.Context3DTextureFormat;
	import flash.display.BitmapData;
	import flash.display3D.IndexBuffer3D;
	import flash.display3D.VertexBuffer3D;
	import flash.display3D.Program3D;
	import flash.display3D.textures.Texture;
	import flash.utils.ByteArray;
	import com.adobe.utils.AGALMiniAssembler;
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Context3DVertexBufferFormat;
	import flash.display.Bitmap;
	import flash.display.Sprite;
	import flash.display.Stage3D;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display3D.Context3D;
	import flash.display3D.Context3DRenderMode;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;
	public class UsingATF extends Sprite
		private static const PAD:Number = 5;
		private static const TEXTURE_BITMAPDATA:Class;
		[Embed(source="earth.atf", mimeType="application/octet-stream")]
		private static const TEXTURE_ATF:Class;
		private var program:Program3D;
		private var posUV:VertexBuffer3D;
		private var tris:IndexBuffer3D;
		private var textureBitmapData:Texture;
		private var textureATF:Texture;
		private var curTexture:Texture;
		private var context3D:Context3D;
		private var modeDisplay:TextField;
		public function UsingATF()
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.frameRate = 60;
			var stage3D:Stage3D = stage.stage3Ds[0];
			stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreated);
		protected function onContextCreated(ev:Event): void
			var stage3D:Stage3D = stage.stage3Ds[0];
			stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreated);
			context3D = stage3D.context3D;            
			context3D.configureBackBuffer(stage.stageWidth, stage.stageHeight, 0);
			context3D.enableErrorChecking = true;
			makeButtons("BitmapData", "ATF");
			var assembler:AGALMiniAssembler = new AGALMiniAssembler();
				"mov op, va0\n" +
				"mov v0, va1"
			var vertexProgram:ByteArray = assembler.agalcode;
				"tex oc, v0, fs0 <2d,linear,mipnone,clamp,dxt1>"
			var fragmentProgram:ByteArray = assembler.agalcode;
			program = context3D.createProgram();
			program.upload(vertexProgram, fragmentProgram);
			posUV = context3D.createVertexBuffer(4, 5);
				new <Number>[
					// X,  Y, Z, U, V
					-1,   -1, 0, 0, 1,
					-1,    1, 0, 0, 0,
					 1,    1, 0, 1, 0,
					 1,   -1, 0, 1, 1
				], 0, 4
			// Create the triangles index buffer
			tris = context3D.createIndexBuffer(6);
				new <uint>[
					0, 1, 2,
					2, 3, 0
				], 0, 6
			var bmd:BitmapData = (new TEXTURE_BITMAPDATA() as Bitmap).bitmapData;
			textureBitmapData = context3D.createTexture(
			var atfBytes:ByteArray = new TEXTURE_ATF() as ByteArray;
			textureATF = context3D.createTexture(
			textureATF.uploadCompressedTextureFromByteArray(atfBytes, 0);
			curTexture = textureBitmapData;
			modeDisplay = new TextField();
			modeDisplay.autoSize = TextFieldAutoSize.LEFT;
			modeDisplay.defaultTextFormat = new TextFormat("_sans", 36);
			modeDisplay.text = "BitmapData";
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
		private function makeButtons(...labels): Number
			var curX:Number = PAD;
			var curY:Number = stage.stageHeight - PAD;
			for each (var label:String in labels)
				var tf:TextField = new TextField();
				tf.mouseEnabled = false;
				tf.selectable = false;
				tf.defaultTextFormat = new TextFormat("_sans");
				tf.autoSize = TextFieldAutoSize.LEFT;
				tf.text = label; = "lbl";
				var button:Sprite = new Sprite();
				button.buttonMode = true;;, 0, tf.width+PAD, tf.height+PAD);;;, 0, tf.width+PAD, tf.height+PAD);
				button.addEventListener(MouseEvent.CLICK, onButton);
				if (curX + button.width > stage.stageWidth - PAD)
					curX = PAD;
					curY -= button.height + PAD;
				button.x = curX;
				button.y = curY - button.height;
				curX += button.width + PAD;
			return curY - button.height;
		private function onButton(ev:MouseEvent): void
			var mode:String ="lbl").text;
			switch (mode)
				case "BitmapData":
					curTexture = textureBitmapData;
					modeDisplay.text = "BitmapData";
				case "ATF":
					curTexture = textureATF;
					modeDisplay.text = "ATF";
		private function onEnterFrame(ev:Event): void
			context3D.clear(0.5, 0.5, 0.5);
			context3D.setTextureAt(0, curTexture);
			context3D.setVertexBufferAt(0, posUV, 0, Context3DVertexBufferFormat.FLOAT_3);
			context3D.setVertexBufferAt(1, posUV, 3, Context3DVertexBufferFormat.FLOAT_2);

Launch the test app
Download the test app (source code, textures, binaries)

Spot a bug in the article? Have a question or suggestion? Post a comment!