/* SCE CONFIDENTIAL

 * PlayStation(R)Edge 1.2.0

 * Copyright (C) 2007 Sony Computer Entertainment Inc.

 * All Rights Reserved.

 */



#include "edge/geom/edgegeom.h"

#include "edge/geom/edgegeom_internal.h"

#include "edge/geom/edgegeom_decompress.h"

#include "edge/geom/edgegeom_masks.h"



/* These MUST be here for index decompression */

static const vec_uint4 indexTriangleMaskTable[16] __attribute__((__aligned__(16))) =

{

	{0x06070a0b, 0x10110607, 0x10111213, 0x00000000},	// ACa ACa 0000

	{0x06070a0b, 0x10111011, 0x0a0b1213, 0x00000000},	// ACa CBa 0001

	{0x06070a0b, 0x10110a0b, 0x06071213, 0x00000000},	// ACa BAa 0010

	{0x06070a0b, 0x10111213, 0x14151617, 0x00000000},	// ACa abc 0011

	{0x0a0b0809, 0x10110a0b, 0x10111213, 0x00000000},	// CBa ACa 0100

	{0x0a0b0809, 0x10111011, 0x08091213, 0x00000000},	// CBa CBa 0101

	{0x0a0b0809, 0x10110809, 0x0a0b1213, 0x00000000},	// CBa BAa 0110

	{0x0a0b0809, 0x10111213, 0x14151617, 0x00000000},	// CBa abc 0111

	{0x08090607, 0x10110809, 0x10111213, 0x00000000},	// BAa ACa 1000

	{0x08090607, 0x10111011, 0x06071213, 0x00000000},	// BAa CBa 1001

	{0x08090607, 0x10110607, 0x08091213, 0x00000000},	// BAa BAa 1010

	{0x08090607, 0x10111213, 0x14151617, 0x00000000},	// BAa abc 1011

	{0x10111213, 0x14151011, 0x14151617, 0x00000000},	// abc ACa 1100

	{0x10111213, 0x14151415, 0x12131617, 0x00000000},	// abc CBa 1101

	{0x10111213, 0x14151213, 0x10111617, 0x00000000},	// abc BAa 1110

	{0x10111213, 0x14151617, 0x18191a1b, 0x00000000},	// abc abc 1111

};



static const vec_uint4 indexInputMaskTable[16] __attribute__((__aligned__(16))) =

{

	{0x04050607, 0x08090a0b, 0x0c0d0e0f, 0x10111213},	// ACa ACa 0000

	{0x04050607, 0x08090a0b, 0x0c0d0e0f, 0x10111213},	// ACa CBa 0001

	{0x04050607, 0x08090a0b, 0x0c0d0e0f, 0x10111213},	// ACa BAa 0010

	{0x08090a0b, 0x0c0d0e0f, 0x10111213, 0x14151617},	// ACa abc 0011

	{0x04050607, 0x08090a0b, 0x0c0d0e0f, 0x10111213},	// CBa ACa 0100

	{0x04050607, 0x08090a0b, 0x0c0d0e0f, 0x10111213},	// CBa CBa 0101

	{0x04050607, 0x08090a0b, 0x0c0d0e0f, 0x10111213},	// CBa BAa 0110

	{0x08090a0b, 0x0c0d0e0f, 0x10111213, 0x14151617},	// CBa abc 0111

	{0x04050607, 0x08090a0b, 0x0c0d0e0f, 0x10111213},	// BAa ACa 1000

	{0x04050607, 0x08090a0b, 0x0c0d0e0f, 0x10111213},	// BAa CBa 1001

	{0x04050607, 0x08090a0b, 0x0c0d0e0f, 0x10111213},	// BAa BAa 1010

	{0x08090a0b, 0x0c0d0e0f, 0x10111213, 0x14151617},	// BAa abc 1011

	{0x08090a0b, 0x0c0d0e0f, 0x10111213, 0x14151617},	// abc ACa 1100

	{0x08090a0b, 0x0c0d0e0f, 0x10111213, 0x14151617},	// abc CBa 1101

	{0x08090a0b, 0x0c0d0e0f, 0x10111213, 0x14151617},	// abc BAa 1110

	{0x0c0d0e0f, 0x10111213, 0x14151617, 0x18191a1b},	// abc abc 1111

};



/**

 *	Decompresses compressed indexes

 * 

 * 	There are 4 main loops here, each doing 1 step in the decompression.  There is

 *  additional documentation inside the LibEdge-Overview document.  

 * 

 * 	The first loop decompress an n-bit stream into halfwords.  There are separate

 *  implementations for n <=8 and n > 8, due to the ability of n-bit values to straddle

 *  3 bytes, requiring additional work (9 is included in this group, even though it 

 *  can't straddle three bytes).  

 * 

 * 	For the n <= 8 case:

 * 		1) Load 3 qw

 * 		2) Align such that the first compressed index is aligned to the left of a quadword

 * 		3) Shuffle n-bit values into appropriate half words

 * 		4) Rotate into correct spot in half word

 * 		5) mask off bits n and higher

 * 		6) store

 * 	For the n > 8 case:

 * 		1) Load 6 qw

 * 		2) Align first 3 to front of quadword.  Align second 3 to front of quadword.

 * 		3) Shuffle portions of n-bit values into upper and lower splits.

 * 		4) Shift/rotate each split into place

 * 		5) Select to combine upper and lower bits.

 * 		6) Mask off garbage upper bits.

 * 		7) Store

 * 

 * 	The second loop applies the delta values.  For delta compression, each index decompressed

 * 	from the n-bit stream will have a base value subtracted from it, and will then be added 

 *  half-word-wise to the previous qw of indices.

 * 		1) Load 4 qw

 * 		2) Subtract base offset

 * 		3) Add previous qw of indices

 * 		4) Store

 * 		

 * 	Some additional info before describing the third loop: 

 * 		Actual construction of triangles is done in the 4th loop, using the output of the 3rd

 * 		loop as input.  Triangles are built by reusing previous triangles' indices, together

 * 		with a "new" index, or if none of the old indices can be reused, 3 new indices.  The

 * 		output of the 3rd loop is the stream from which these new indices are selected.

 * 

 * 		The "new index" stream as generated by the Edge Geometry tools is highly sequential; 

 * 		it is, in general, an incrementing series of indices, occasionally interrupted by an

 * 		index seen earlier in the list {1, 2, 3, 4, 1, 5, 6, 7, 3 . . . }.  The stream that

 * 		we've output from the second loop is an ordered list of the out of sequence entries,

 * 		in this case, {1,3}, for the second appearances of those indices.  

 * 

 * 	The third loop decompresses a 1-bit stream which specifies whether the next index in the

 * 	stream should be drawn from the incrementing sequence, or from the out of sequence list

 * 	output by the second loop.  

 * 		1) Load 1-bit control stream and out of sequence indexes

 * 		2) Align control stream

 * 		3) Build masks according to control bits

 * 		4) Shuffle sequence and index streams to produce output

 * 		5) Update sequence qw

 * 		6) Shuffle sequence and next set of indices to produce next output.

 * 		7) Update sequence

 * 		8) Store final output buffers

 * 

 *  The fourth loop builds the final triangle ordering, using the output of the third loop

 * 	as the input of "new" indexes.  A 2-bit pattern specifies which triangle ordering should

 *  be used (ACa, CBa, BAa, or abc, where A,B,C are from the previous triangle and a,b,c are

 *  the next indexes in the input stream).  This loop decompresses 8 triangles at a time.

 * 

 * 		1) Load control bits for 8 triangles (1 halfword)

 * 		2) Load enough indices for 8 triangles of all new indices

 * 		3) Rotate control bits to be used as offsets into lookup tables (4 bit indices)

 * 		4) Load triangle format masks and input stream update masks

 * 		5) Shuffle inputs to make first triangle.

 * 		6) Shuffle inputs to remove used indices.

 * 		7) Repeat 5 and 6 twice.  

 * 		8) Repeat 5 a third time (inputs needn't be realigned, since they'll be reloaded next 

 * 		   time through)

 * 		9) Shuffle triangles together to form final output stream.

 * 		10)Store outputs.

 * 

 * 	At this point we have our final index list, and can free the space previously taken up

 * 	by the block of index data.

 */

#if DECOMPRESSINDEXES_INTRINSICS

void DecompressIndexes(EdgeGeomSpuContext *ctx, const void *indexes)

{

	const qword s_DDDDDDDDDDDDDDDD = si_ilh(0x0303);

	const qword s_GGGGGGGGGGGGGGGG = si_ilh(0x0606);

	const qword s_0G0G0G0G0G0G0G0G = si_ilh(0x8006);

	const qword s_ABCD_qw = (qword)s_ABCD;

	const qword s_BCDD_qw = (qword)s_BCDD;



	qword AAAB = (qword)spu_splats(0x0000AAAB);

	unsigned int numTriangles = si_to_uint(si_and(si_mpy(si_from_ushort(ctx->numVisibleIndexes), AAAB), si_fsmh(si_from_int(0x55))));



	qword pInput = si_from_ptr(indexes);

	qword startData = si_lqd(pInput, 0x00);



	unsigned int num1Bits = si_to_uint(si_and(si_shlqbyi(startData, 2), (qword)spu_splats(0x0000FFFF))) << 3;

	unsigned int bytesBefore2BitStream = 8 + ((num1Bits + 7) >> 3);

	unsigned int num2BitStreamBytes = (((numTriangles + numTriangles) + 7) >> 3);

	unsigned int bytesBeforeNBitStream = bytesBefore2BitStream + num2BitStreamBytes;



	unsigned int qwToWriteTo = (bytesBeforeNBitStream + 0xf) & ~0xf;

	qword pDecompressedNBits = si_a(pInput, si_from_uint(qwToWriteTo));

	qword pEndOfDecompressedSequence;

	qword pDecompressedSequence;



	/* BEGIN - DECOMPRESS N-BIT STREAM */

	{

		qword hwN = si_shufb(startData, startData, s_0G0G0G0G0G0G0G0G);

		qword  bN = si_shufb(startData, startData, s_GGGGGGGGGGGGGGGG);



		unsigned int n = si_to_uint(si_and(hwN, (qword)spu_splats(0x0000ffff)));

		qword numIndexes = si_rotmi(startData, -16);



		if (n > 0) 

		{

			//okay, we skip 4*n bytes every loop iteration.  So, starting point of last iter = (4*n)*(numLoops-1)



			qword numLoops = si_rotmi(si_ai(numIndexes, 31), -5);

	

			qword lastLoop = si_ai(numLoops, -1);

	

			qword bytesPerLoop = si_shlhi(hwN, 2);

			qword pInputStreamOffset = si_and(si_mpy(bytesPerLoop, lastLoop), (qword)spu_splats(0x0000FFFF));

	

			qword pInputStream = si_a(pInput, si_from_uint(bytesBeforeNBitStream));

	

			qword offsetMask = (qword)spu_splats(0x0f0f0f0f);

	

			//we need a pointer to the last outputs we're going to write

			qword pOutputStream0 = pDecompressedNBits;

			qword pOutputStream1 = si_ai(pOutputStream0, 0x10);

			qword pOutputStream2 = si_ai(pOutputStream0, 0x20);

			qword pOutputStream3 = si_ai(pOutputStream0, 0x30);

	

		   //and we need the output offset

			qword pOutputStreamOffset = si_shli(lastLoop, 6);

	

			qword debugDecompressedNBitEnd = si_a(pOutputStream3, si_ai(pOutputStreamOffset, 0x10));

			(void)debugDecompressedNBitEnd;

			EDGE_ASSERT_MSG( !(si_to_uint(si_cgt(debugDecompressedNBitEnd, si_from_uint(ctx->ioBufferEnd)))),

							 ("Edge error: Not enough space in IO buffer for index decompression.  Please rebuild your data with the latest Edge tools\n") );



			qword outputMaskShiftAmount = si_sfhi(hwN, 1);

			qword outputMask = si_roth(si_rotmah((qword)spu_splats(0x80008000), outputMaskShiftAmount), hwN);

	

			const qword multiplier = (qword){0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08};

	

			qword shiftAmountIncHi = si_mpyhh(hwN, multiplier);

			qword shiftAmountIncLo = si_mpy(hwN, multiplier);

			qword shiftAmountInc = si_rotqmbyi(si_or(shiftAmountIncLo, si_shli(shiftAmountIncHi, 16)), -2);

	

			qword mods = si_andhi(shiftAmountInc, 7);

			qword bytePositions = si_rothmi(shiftAmountInc, -3);

	

			//predeclare

			qword s_offset = (qword)spu_splats(0);

	

			qword inputQuad0 = (qword)spu_splats(0);

			qword inputQuad1 = (qword)spu_splats(0);

			qword inputQuad2 = (qword)spu_splats(0);



			qword inputBuffer0 = (qword)spu_splats(0);

			qword inputBuffer1 = (qword)spu_splats(0);

			qword expanded0, expanded1, expanded2, expanded3;

			qword output0, output1, output2, output3;



			qword tmpAmount =  si_sfi(si_shli(si_from_ushort(n), 2), 0);

	

			if (n <= 8) 

			{

				qword pInputStreamOffsetInc = tmpAmount; 

	

				qword rollAmount = si_a(pInputStreamOffsetInc, pInputStreamOffsetInc);

	

				pInputStreamOffset = si_sf(rollAmount, pInputStreamOffset);

				qword pInputStream0 = si_a(pInputStream, rollAmount);

				qword pInputStream1 = si_ai(pInputStream0, 0x10);

				qword pInputStream2 = si_ai(pInputStream0, 0x20);

	

				qword offset = si_andi(si_a((qword)pInputStream0, (qword)pInputStreamOffset), 0xf);

				offset = si_shufb(offset, offset, (qword)s_DDDDDDDDDDDDDDDD);

	

				qword startingPos = si_sfhi((qword)hwN, 8);

				qword leftByte = si_andhi(si_cgth((qword)mods,startingPos), 1);

	

				qword s_mask_lower = si_a((qword)bytePositions, leftByte);

				qword s_mask_upper = si_shlqbyi(si_ahi(s_mask_lower, -1), 1);

				qword s_mask_mask = si_cgthi(s_mask_lower, 0);

				s_mask_upper = si_and(s_mask_mask, s_mask_upper);

	

				qword s_mask = si_or(s_mask_lower, s_mask_upper);

				qword s_mask0 = s_mask;

				qword s_mask1 = si_a(s_mask0, (qword)bN);

				qword s_mask2 = si_a(s_mask1, (qword)bN);

				qword s_mask3 = si_a(s_mask2, (qword)bN);

	

				qword baseShiftAmount = si_sfhi((qword)hwN, 8);

	

				qword shiftAmount = si_sfhi(si_andhi(si_sfh((qword)shiftAmountInc, baseShiftAmount), 7), 0);

	

				qword offsetInc = si_shlhi(si_sfhi((qword)hwN, 16), 2);

	

				offsetInc = si_or(offsetInc, si_shlhi(offsetInc, 8));

	

				qword inPosition0 = (qword)spu_splats(0);

				qword inPosition1 = (qword)spu_splats(0);

				qword inPosition2 = (qword)spu_splats(0);

				qword inPosition3 = (qword)spu_splats(0);

	

				qword pOutputStreamOffsetInc = (qword){ 0x00, 0x00, 0x00, 0x00, 

														0x00, 0x00, 0x00, 0x00, 

														0xFF, 0xFF, 0xFF, 0xC0,

														0xFF, 0xFF, 0xFF, 0xC0};



				do

				{

						inputBuffer0 = si_shufb(inputQuad0, inputQuad1, s_offset);

						inputBuffer1 = si_shufb(inputQuad1, inputQuad2, s_offset);

					

					inputQuad0 = si_lqx(pInputStream0, pInputStreamOffset);

					inputQuad1 = si_lqx(pInputStream1, pInputStreamOffset);

					inputQuad2 = si_lqx(pInputStream2, pInputStreamOffset);

	

							output0 = si_and(inPosition0, outputMask);

							output1 = si_and(inPosition1, outputMask);

							output2 = si_and(inPosition2, outputMask);

							output3 = si_and(inPosition3, outputMask);

	

							si_stqx(output0, pOutputStream0, pOutputStreamOffset);

							si_stqx(output1, pOutputStream1, pOutputStreamOffset);

							si_stqx(output2, pOutputStream2, pOutputStreamOffset);

							si_stqx(output3, pOutputStream3, pOutputStreamOffset);

			

					pInputStreamOffset = si_a(pInputStreamOffset, pInputStreamOffsetInc);

	

						expanded0 = si_shufb(inputBuffer0, inputBuffer1, s_mask0);

						expanded1 = si_shufb(inputBuffer0, inputBuffer1, s_mask1);

						expanded2 = si_shufb(inputBuffer0, inputBuffer1, s_mask2);

						expanded3 = si_shufb(inputBuffer0, inputBuffer1, s_mask3);

	

						inPosition0 = si_rothm(expanded0, shiftAmount);

						inPosition1 = si_rothm(expanded1, shiftAmount);

						inPosition2 = si_rothm(expanded2, shiftAmount);

						inPosition3 = si_rothm(expanded3, shiftAmount);

	

					s_offset = si_a(offset, s_ABCD_qw);

					offset = si_and(si_ah(offset, offsetInc), offsetMask);

	

						pOutputStreamOffset = si_a(pOutputStreamOffset, pOutputStreamOffsetInc);

						pOutputStreamOffsetInc = si_shufb(pOutputStreamOffsetInc, pOutputStreamOffsetInc, s_BCDD_qw);

	

				} while (si_to_int(pInputStreamOffset) > -1);

	

			}

			else

			{

				

				qword rollAmount = si_a(tmpAmount, tmpAmount);

	

				pInputStreamOffset = si_sf(rollAmount, pInputStreamOffset);

				qword pInputStream0 = si_a(pInputStream, rollAmount);

				qword pInputStream1 = si_ai(pInputStream0, 0x10);

				qword pInputStream2 = si_ai(pInputStream0, 0x20);

	

				qword pOutputStreamOffsetInc = (qword){ 0x00, 0x00, 0x00, 0x00, 

														0x00, 0x00, 0x00, 0x00, 

														0xFF, 0xFF, 0xFF, 0xC0,

														0xFF, 0xFF, 0xFF, 0xC0};

	

				qword pInputStreamOffsetInc = si_sfi(si_a(si_from_ushort(n), si_from_ushort(n)), 0);

				pInputStreamOffset = si_sf(pInputStreamOffsetInc, (qword)pInputStreamOffset);

	

				qword offset = si_andi(si_a((qword)pInputStream0, (qword)pInputStreamOffset), 0xf);

				offset = si_shufb(offset, offset, (qword)s_DDDDDDDDDDDDDDDD);

	

				const qword s_DDDDHHHHLLLLPPPP = (qword) si_a((qword)s_BBBBFFFFJJJJNNNN, (qword)spu_splats(0x02020202));

				const qword s_AAAA = (qword)spu_splats(0x10203);

	

				qword baseShiftAmount = si_ahi((qword)hwN, -8);

	

				qword shiftAmount = si_andhi(si_ah(baseShiftAmount, (qword)shiftAmountInc), 7);

	

				qword startingPos = si_sfhi((qword)hwN, 16);

				qword noShift = si_cgth((qword)mods, startingPos);

				qword shufShiftAmount = si_sfhi(si_shli(si_ahi(noShift,1), 3), 0);

				qword shufShiftAmountBDFH = si_shufb(shufShiftAmount, shufShiftAmount, (qword)s_0B0D0F0H);

				qword shufShiftAmountACEG = si_shufb(shufShiftAmount, shufShiftAmount, (qword)s_0A0C0E0G);

	

				qword bigShiftAmount1_int = si_ai(si_shufb(shiftAmount, shiftAmount, (qword)s_0B0D0F0H), -8);

				bigShiftAmount1_int = si_a(bigShiftAmount1_int, si_andi(si_ceqi(bigShiftAmount1_int, -8), 8));

	

				qword bigShiftAmount0 = si_shufb(shiftAmount, shiftAmount, (qword)s_0A0C0E0G);

				qword bigShiftAmount1 = bigShiftAmount1_int;

	

				qword selector = si_fsmh(si_from_int(0x55));

	

				qword offsetInc = si_shlhi(si_sfhi((qword)hwN, 16), 1);

				offsetInc = si_or(offsetInc, si_shlhi(offsetInc, 8));

	

				qword byteBDFH = si_shufb((qword)bytePositions, (qword)bytePositions, s_DDDDHHHHLLLLPPPP);

				qword byteACEG = si_shufb((qword)bytePositions, (qword)bytePositions, (qword)s_BBBBFFFFJJJJNNNN);

	

				qword s_mask00 = si_rotm(si_a(byteACEG, s_AAAA), shufShiftAmountACEG);

				qword s_mask01 = si_rotmi(si_rotm(si_a(byteBDFH, s_AAAA),shufShiftAmountBDFH), -8);

				qword s_mask10 = si_a(s_mask00,(qword)bN);

				qword s_mask11 = si_a(s_mask01,(qword)bN);

	

				qword inputQuad3, inputQuad4, inputQuad5;

				qword inputBuffer2 = (qword)spu_splats(0);

				qword inputBuffer3 = (qword)spu_splats(0);

				qword splitInput00;

				qword splitInput01;

				qword splitInput10;

				qword splitInput11;

				qword splitInput20 = (qword)spu_splats(0);

				qword splitInput21 = (qword)spu_splats(0);

				qword splitInput30 = (qword)spu_splats(0);

				qword splitInput31 = (qword)spu_splats(0);

				qword expanded00 = (qword)spu_splats(0);

				qword expanded01 = (qword)spu_splats(0);

				qword expanded10 = (qword)spu_splats(0);

				qword expanded11 = (qword)spu_splats(0);

				qword expanded20;

				qword expanded21;

				qword expanded30;

				qword expanded31;

	

				do

				{

							expanded20 = si_shl((qword)splitInput20, (qword)bigShiftAmount0);

							expanded21 = si_rot((qword)splitInput21, (qword)bigShiftAmount1);

							expanded30 = si_shl((qword)splitInput30, (qword)bigShiftAmount0);

							expanded31 = si_rot((qword)splitInput31, (qword)bigShiftAmount1);

	

					inputQuad0 = (vec_char16)si_lqx((qword)pInputStream0, (qword)pInputStreamOffset);

					inputQuad1 = (vec_char16)si_lqx((qword)pInputStream1, (qword)pInputStreamOffset);

					inputQuad2 = (vec_char16)si_lqx((qword)pInputStream2, (qword)pInputStreamOffset);

	

							expanded0 = si_selb((qword)expanded00, (qword)expanded01, (qword)selector);

							expanded1 = si_selb((qword)expanded10, (qword)expanded11, (qword)selector);

							expanded2 = si_selb((qword)expanded20, (qword)expanded21, (qword)selector);

							expanded3 = si_selb((qword)expanded30, (qword)expanded31, (qword)selector);

	

						splitInput00 = si_shufb((qword)inputBuffer0, (qword)inputBuffer0, (qword)s_mask00);

						splitInput01 = si_shufb((qword)inputBuffer0, (qword)inputBuffer0, (qword)s_mask01);

						splitInput10 = si_shufb((qword)inputBuffer0, (qword)inputBuffer1, (qword)s_mask10);

						splitInput11 = si_shufb((qword)inputBuffer0, (qword)inputBuffer1, (qword)s_mask11);

						splitInput20 = si_shufb((qword)inputBuffer2, (qword)inputBuffer2, (qword)s_mask00);

						splitInput21 = si_shufb((qword)inputBuffer2, (qword)inputBuffer2, (qword)s_mask01);

						splitInput30 = si_shufb((qword)inputBuffer2, (qword)inputBuffer3, (qword)s_mask10);

						splitInput31 = si_shufb((qword)inputBuffer2, (qword)inputBuffer3, (qword)s_mask11);

	

							output0 = si_and((qword)expanded0, (qword)outputMask);

							output1 = si_and((qword)expanded1, (qword)outputMask);

							output2 = si_and((qword)expanded2, (qword)outputMask);

							output3 = si_and((qword)expanded3, (qword)outputMask);

	

					pInputStreamOffset = si_a((qword)pInputStreamOffset, (qword)pInputStreamOffsetInc);

	

					inputQuad3 = si_lqx((qword)pInputStream0, (qword)pInputStreamOffset);

					inputQuad4 = si_lqx((qword)pInputStream1, (qword)pInputStreamOffset);

					inputQuad5 = si_lqx((qword)pInputStream2, (qword)pInputStreamOffset);

	

						expanded00 = si_shl((qword)splitInput00, (qword)bigShiftAmount0);

						expanded01 = si_rot((qword)splitInput01, (qword)bigShiftAmount1);

						expanded10 = si_shl((qword)splitInput10, (qword)bigShiftAmount0);

						expanded11 = si_rot((qword)splitInput11, (qword)bigShiftAmount1);

	

							si_stqx((qword)output0, (qword)pOutputStream2, (qword)pOutputStreamOffset);

							si_stqx((qword)output1, (qword)pOutputStream3, (qword)pOutputStreamOffset);

							si_stqx((qword)output2, (qword)pOutputStream0, (qword)pOutputStreamOffset);

							si_stqx((qword)output3, (qword)pOutputStream1, (qword)pOutputStreamOffset);

	

					s_offset = si_a((qword)offset, (qword)s_ABCD_qw);

					offset = si_and(si_ah((qword)offset, (qword)offsetInc), (qword)offsetMask);

	

					pInputStreamOffset = si_a((qword)pInputStreamOffset, (qword)pInputStreamOffsetInc);				

	

							pOutputStreamOffset = si_a((qword)pOutputStreamOffset, (qword)pOutputStreamOffsetInc);

	

					inputBuffer0 = si_shufb((qword)inputQuad0, (qword)inputQuad1, (qword)s_offset);

					inputBuffer1 = si_shufb((qword)inputQuad1, (qword)inputQuad2, (qword)s_offset);

	

					s_offset = si_a((qword)offset, (qword)s_ABCD_qw);

					offset = si_and(si_ah((qword)offset, (qword)offsetInc), (qword)offsetMask);

	

							pOutputStreamOffsetInc = si_shufb(pOutputStreamOffsetInc, pOutputStreamOffsetInc, s_BCDD_qw);

					

					inputBuffer2 = si_shufb((qword)inputQuad3, (qword)inputQuad4, (qword)s_offset);

					inputBuffer3 = si_shufb((qword)inputQuad4, (qword)inputQuad5, (qword)s_offset);

	

				} while (si_to_int(pInputStreamOffset) > -1);

			}

		}

		else // n == 0.  Need to copy numIndexes 0's into memory

		{

			uint32_t numIndexesU32 = si_to_uint(numIndexes);

			uint32_t numIters = (numIndexesU32 + 7) >> 3; // 8 per qw

			qword pOut = pDecompressedNBits;

			qword zero = (qword)spu_splats(0);



			for (uint32_t iter = 0; iter < numIters; iter++){

				si_stqd(zero, pOut, 0x00);

				pOut = si_ai(pOut, 0x10);

			}



			//is there a corner case that requires a qw of zeros to be written

			//even if numIndexesU32 == 0?  Old behavior would do it.

		}

	}

	/* END - DECOMPRESS N-BIT STREAM */



	/* BEGIN - DELTA*/



	//preamble



	const qword s_BBBBBBBB = (qword)spu_splats(0x02030203);

	qword deltaOffset = si_shufb(startData, startData, s_BBBBBBBB);

	qword pInputDeltaOffsetInc = (qword)spu_splats(0x00000040);

	qword pOutputDeltaOffsetInc = (qword)spu_splats(0x0);

	//predeclarations



	unsigned int numIndexes = si_to_uint(si_rotmi(startData, -16));

	if (numIndexes > 0) 

	{

		unsigned int deltaCounter = ((numIndexes + 31) >> 5) + 1;

	

		qword pInputDeltaOffset = (qword)spu_splats(0);

		qword pOutputDeltaOffset = (qword)spu_splats(0);

	

		qword inputDelta0; 

		qword inputDelta1; 

		qword inputDelta2; 

		qword inputDelta3;

		qword offsetDelta0 = (qword)spu_splats(0); 

		qword offsetDelta1 = (qword)spu_splats(0); 

		qword offsetDelta2 = (qword)spu_splats(0); 

		qword offsetDelta3 = (qword)spu_splats(0);

		qword outputDelta0; 

		qword outputDelta1; 

		qword outputDelta2;

		qword outputDelta3 = (qword)spu_splats(0);

	

		qword pDeltaInputStream0 = pDecompressedNBits;

		qword pDeltaInputStream1 = si_ai(pDeltaInputStream0, 0x10);

		qword pDeltaInputStream2 = si_ai(pDeltaInputStream0, 0x20);

		qword pDeltaInputStream3 = si_ai(pDeltaInputStream0, 0x30);



		do

		{

	

			inputDelta0 = si_lqx((qword)pDeltaInputStream0, (qword)pInputDeltaOffset);

			inputDelta1 = si_lqx((qword)pDeltaInputStream1, (qword)pInputDeltaOffset);

			inputDelta2 = si_lqx((qword)pDeltaInputStream2, (qword)pInputDeltaOffset);

			inputDelta3 = si_lqx((qword)pDeltaInputStream3, (qword)pInputDeltaOffset);

	

				outputDelta0 = si_ah((qword)offsetDelta0, (qword)outputDelta3);

				outputDelta1 = si_ah((qword)offsetDelta1, (qword)outputDelta0);

				outputDelta2 = si_ah((qword)offsetDelta2, (qword)outputDelta1);

				outputDelta3 = si_ah((qword)offsetDelta3, (qword)outputDelta2);

	

				si_stqx((qword)outputDelta0, (qword)pDeltaInputStream0, (qword)pOutputDeltaOffset);

				si_stqx((qword)outputDelta1, (qword)pDeltaInputStream1, (qword)pOutputDeltaOffset);

				si_stqx((qword)outputDelta2, (qword)pDeltaInputStream2, (qword)pOutputDeltaOffset);

				si_stqx((qword)outputDelta3, (qword)pDeltaInputStream3, (qword)pOutputDeltaOffset);

	

			pInputDeltaOffset = si_a((qword)pInputDeltaOffset, (qword)pInputDeltaOffsetInc);

	

			offsetDelta0 = si_sfh((qword)deltaOffset, (qword)inputDelta0);

			offsetDelta1 = si_sfh((qword)deltaOffset, (qword)inputDelta1);

			offsetDelta2 = si_sfh((qword)deltaOffset, (qword)inputDelta2);

			offsetDelta3 = si_sfh((qword)deltaOffset, (qword)inputDelta3);

	

				pOutputDeltaOffset = si_a((qword)pOutputDeltaOffset, (qword)pOutputDeltaOffsetInc);

				pOutputDeltaOffsetInc = si_shlqbyi(pInputDeltaOffsetInc, 0);

	

		} while((--deltaCounter) != 0);

	}

	/* END - DELTA */



	/* BEGIN - DECOMPRESS 1-BIT STREAM */

	{

		qword pControlStreamOffset = (qword)spu_splats(0x6);

		qword pControlStream = si_ai(pInput, 2);



		//compute output location for sequence

		unsigned int bytesBeforeDecompressedSequence = ((numTriangles * 6) + 0xf) & ~0xf;



		unsigned int debugOneBitDecompressed = bytesBeforeDecompressedSequence + 2 * ((num1Bits + 15) & ~15);

		debugOneBitDecompressed += (unsigned int)indexes;



		EDGE_ASSERT_MSG( debugOneBitDecompressed <= ctx->ioBufferEnd, 

						 ("Edge error: Not enough space in IO buffer for index decompression.  Please rebuild your data with the latest Edge tools\n") );



		qword pOutputStream = si_a(pInput, si_from_uint(bytesBeforeDecompressedSequence));

		pDecompressedSequence = pOutputStream;



		qword pIndexStream = pDecompressedNBits;



		unsigned int counter = ((num1Bits + 15) >> 4) + 1;



		qword controlBits, alignedControlBits;

		qword bitCounts0, bitCounts1;

		qword controlBitsBC;

		qword selector = (qword)spu_splats(0);

		qword maskedBits;

		qword indexContrib = (qword)spu_splats(0);

		qword sequenceInc;

		qword sequenceContrib = (qword)spu_splats(0);

		qword interleavedMask;

		qword s_mask0, s_mask1;

		qword bytesUsed0, bytesUsed1;

		qword fromSequence0, fromSequence1;

		qword fromSequence0BC, fromSequence1BC;

		qword offset, offsetBC;

		qword s_offset;



		qword sequence = (qword){0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07};



		const qword controlBitsMask = (qword){0x00, 0x00, 0x80, 0x80, 0xc0, 0xc0, 0xe0, 0xe0, 0xf0, 0xf0, 0xf8, 0xf8, 0xfc, 0xfc, 0xfe, 0xfe};



		const qword s_BBBBBBBB = (qword)spu_splats(0x02030203);

		const qword s_BBDDFFHHJJLLNNPP = si_orbi((qword)s_AACCEEGGIIKKMMOO, 0x01);

		const qword subtractor = (qword){0x00, 0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07, 0x07};



		const qword eights = (qword)spu_splats(0x08080808);

		const qword thirtyTwo = (qword)spu_splats(0x20);



		bytesUsed0 = (qword)spu_splats(0);

		fromSequence0 = (qword)spu_splats(0);

		bytesUsed1 = (qword)spu_splats(0);

		fromSequence1 = (qword)spu_splats(0);



		qword pOutputStreamInc = (qword)spu_splats(0);



		do

		{

				qword inputQuad0 = si_lqd(pIndexStream, 0x00);

				qword inputQuad1 = si_lqd(pIndexStream, 0x10);



				interleavedMask = si_selb(sequenceContrib, indexContrib, selector);

				interleavedMask = si_a(interleavedMask, interleavedMask);



				s_mask0 = si_shufb(interleavedMask, interleavedMask, (qword)s_AACCEEGGIIKKMMOO);

				s_mask1 = si_shufb(interleavedMask, interleavedMask, s_BBDDFFHHJJLLNNPP);



				bytesUsed0 = si_a(bytesUsed0, bytesUsed0);

				fromSequence0BC = si_shufb(fromSequence0, fromSequence0, s_BBBBBBBB);



				s_mask0 = si_ahi(s_mask0, 0x1);

				s_mask1 = si_ahi(s_mask1, 0x1);



				offset = si_andi(pIndexStream, 0xf);

				offsetBC = si_shufb(offset, offset, s_DDDDDDDDDDDDDDDD);

				s_offset = si_a(s_ABCD_qw, offsetBC);



				pIndexStream = si_a(pIndexStream, bytesUsed0);



				qword inputBuffer0 = si_shufb(inputQuad0, inputQuad1, s_offset);

	

				qword outputBuffer0 = si_shufb(inputBuffer0, sequence, s_mask0);

	

				sequence = si_a(sequence, fromSequence0BC);

				fromSequence1BC = si_shufb(fromSequence1, fromSequence1, s_BBBBBBBB);

	

				qword inputQuad2 = si_lqd(pIndexStream, 0x00);

				qword inputQuad3 = si_lqd(pIndexStream, 0x10);

	

				offset = si_andi(pIndexStream, 0xf);

				offsetBC = si_shufb(offset, offset, s_DDDDDDDDDDDDDDDD);

				s_offset = si_a(s_ABCD_qw, offsetBC);



			controlBits = si_lqx(pControlStream, pControlStreamOffset);

			alignedControlBits = si_rotqby(controlBits, pControlStreamOffset);



				pIndexStream = si_a(pIndexStream, bytesUsed1);

	

				qword inputBuffer1 = si_shufb(inputQuad2, inputQuad3, s_offset);

	

				qword outputBuffer1 = si_shufb(inputBuffer1, sequence, s_mask1);

	

				sequence = si_a(sequence, fromSequence1BC);

	

				counter--;



			pControlStreamOffset = si_ai(pControlStreamOffset, 0x2);



			bitCounts0 = si_cntb(alignedControlBits);

			controlBitsBC = si_shufb(alignedControlBits, alignedControlBits, s_BBBBBBBB);



			selector = si_fsmb(alignedControlBits);

			selector = si_shufb(selector, selector, (qword)s_AIBJCKDLEMFNGOHP);



			maskedBits = si_and(controlBitsBC, controlBitsMask);

			indexContrib = si_cntb(maskedBits);

			sequenceInc = si_sf(indexContrib, subtractor);

			sequenceContrib = si_ah(sequenceInc, eights);



			bytesUsed0 = si_shufb(bitCounts0, bitCounts0, (qword)s_000C000000000000);

			fromSequence0 = si_sfi(bytesUsed0, 8);



				si_stqd(outputBuffer0, pOutputStream, 0x00);

				si_stqd(outputBuffer1, pOutputStream, 0x10);

	

				pOutputStream = si_a(pOutputStream, pOutputStreamInc);

				pOutputStreamInc = thirtyTwo;



			bitCounts1 = si_andi(bitCounts0, 0xf);

			fromSequence1 = si_sfi(bitCounts1, 8);

			bytesUsed1 = si_a(bitCounts1, bitCounts1);



		} while(counter != 0);

		pEndOfDecompressedSequence = pOutputStream;

	}

	/* END - DECOMPRESS 1-BIT STREAM */





	/* BEGIN - RELOCATE 2-BIT TABLE */

	vec_char16 p2BitStream = (vec_char16)spu_add((vec_uint4)pInput, (vec_uint4)si_from_uint(bytesBefore2BitStream));

	{

		unsigned int copyCount = -0x10;

		qword pInputStream = si_ai(p2BitStream, -0x10);

		qword pOutputStream = pEndOfDecompressedSequence;

		qword pOutputStreamInc = (qword)spu_splats(0);

		qword pOutputStreamIncValue = (qword)spu_splats(0x10);



		qword s_offset = (qword)spu_splats(0);

		qword inputQuad0 = (qword)spu_splats(0);

		qword inputQuad1 = (qword)spu_splats(0);



		qword debugCopied2Bits = si_a(pOutputStream, si_from_uint(num2BitStreamBytes));

		debugCopied2Bits = si_andi(si_ai(debugCopied2Bits, 0xf), ~0xf);



		EDGE_ASSERT_MSG( !(si_to_uint(si_cgt(debugCopied2Bits, si_from_uint(ctx->ioBufferEnd)))),

						 ("Edge error: Not enough space in IO buffer for index decompression.  Please rebuild your data with the latest Edge tools\n") );



		do

		{

				qword output = si_shufb(inputQuad0, inputQuad1, s_offset);

				si_stqd(output, pOutputStream, 0x00);

				pInputStream = si_ai(pInputStream, 0x10);

				pOutputStream = si_a(pOutputStream, pOutputStreamInc);

				pOutputStreamInc = pOutputStreamIncValue;



			qword offset = si_andi(pInputStream, 0xf);

			qword offsetBC = si_shufb(offset, offset, s_DDDDDDDDDDDDDDDD);

			s_offset = si_a(offsetBC, s_ABCD_qw);

			inputQuad0 = si_lqd(pInputStream, 0x00);

			inputQuad1 = si_lqd(pInputStream, 0x10);



			copyCount += 0x10;



		} while (copyCount < num2BitStreamBytes);

	}

   

	/* END - COPY 2-BIT TABLE */



	/* BEGIN - DECOMPRESS 2-BIT STREAM */



	{

		const qword pTriangleMasks = si_from_ptr(indexTriangleMaskTable);

		const qword pInputMasks = si_from_ptr(indexInputMaskTable);



		const qword six = (qword)spu_splats(6);



		const qword s_output0 = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x11, 0x12, 0x13};

		const qword s_output1 = {0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17};

		const qword s_output2 = {0x08, 0x09, 0x0a, 0x0b, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b};



		qword pControlStream = (qword)si_ai(pEndOfDecompressedSequence, 0x2);

		qword pControlStreamOffset = (qword)spu_splats(-0x2);



		qword pInputStream = pDecompressedSequence;

		qword pOutputStream = pInput;



		unsigned int triangleCounter = ((numTriangles + 7) >> 3) + 1;



		qword controlHw, alignedControlHw;

		qword newBits, newBitsCountPerByte, newBitsCountPerByteShifted;

		qword newBitsCount = (qword)spu_splats(0);

		qword oldBitsCount;

		qword newBytes = (qword)spu_splats(0);

		qword oldBytes = (qword)spu_splats(0);

		qword byteInc = (qword)spu_splats(0);



		qword inputQuad0 = (qword)spu_splats(0);

		qword inputQuad1 = (qword)spu_splats(0);

		qword inputQuad2 = (qword)spu_splats(0);

		qword inputQuad3 = (qword)spu_splats(0);

		qword offset, offsetBC;

		qword s_offset = (qword)spu_splats(0);



		qword inputBuffer00 = (qword)spu_splats(0); 

		qword inputBuffer10 = (qword)spu_splats(0); 

		qword inputBuffer20 = (qword)spu_splats(0);

		qword inputBuffer01; 

		qword inputBuffer11; 

		qword inputBuffer21;

		qword inputBuffer02; 

		qword inputBuffer12;

		qword inputBuffer03;



		qword pMaskOffset0 = (qword)spu_splats(0); 

		qword pMaskOffset1 = (qword)spu_splats(0); 

		qword pMaskOffset2 = (qword)spu_splats(0); 

		qword pMaskOffset3 = (qword)spu_splats(0);

		qword triangleMask0 = (qword)spu_splats(0); 

		qword triangleMask1 = (qword)spu_splats(0); 

		qword triangleMask2; 

		qword triangleMask3;

		qword inputMask0 = (qword)spu_splats(0); 

		qword inputMask1 = (qword)spu_splats(0); 

		qword inputMask2; 

		qword inputMask3;



		qword triangle01; 

		qword triangle23; 

		qword triangle45; 

		qword triangle67 = (qword)spu_splats(0);

		qword output0; 

		qword output1; 

		qword output2;



		qword AAAA = (qword)spu_splats(0xAAAA);

		qword controlMask3 = (qword)spu_splats(0xf);

		qword controlMask2 = si_shli(controlMask3, 4);

		qword controlMask1 = si_shli(controlMask3, 8);

		qword controlMask0 = si_shli(controlMask3, 12);



		qword pOutputStreamInc = (qword)spu_splats(0);



		do

		{

			controlHw = si_lqx(pControlStream, pControlStreamOffset);

			alignedControlHw = si_rotqby(controlHw, pControlStreamOffset);

			qword alignedControlHwShifted = si_shli(alignedControlHw, 1);

			pControlStreamOffset = si_ai(pControlStreamOffset, 0x2);



				newBytes = si_mpy(newBitsCount, six);



				byteInc = si_a(newBytes, oldBytes);



				pInputStream = si_a(pInputStream, byteInc);



				inputBuffer00 = si_shufb(inputQuad0, inputQuad1, s_offset);

				inputBuffer10 = si_shufb(inputQuad1, inputQuad2, s_offset);

				inputBuffer20 = si_shufb(inputQuad2, inputQuad3, s_offset);



				triangleMask2 = si_lqx(pTriangleMasks, pMaskOffset2);

				inputMask2 = si_lqx(pInputMasks, pMaskOffset2);

	

				triangleMask3 = si_lqx(pTriangleMasks, pMaskOffset3);

				inputMask3 = si_lqx(pInputMasks, pMaskOffset3);



				triangleMask1 = si_lqx(pTriangleMasks, pMaskOffset1);

				inputMask1 = si_lqx(pInputMasks, pMaskOffset1);



				triangle01 = si_shufb(triangle67, inputBuffer00, triangleMask0);

				inputBuffer01 = si_shufb(inputBuffer00, inputBuffer10, inputMask0);

				inputBuffer11 = si_shufb(inputBuffer10, inputBuffer20, inputMask0);

				inputBuffer21 = si_shufb(inputBuffer20, inputBuffer20, inputMask0);

	

				triangle23 = si_shufb(triangle01, inputBuffer01, triangleMask1);   

				inputBuffer02 = si_shufb(inputBuffer01, inputBuffer11, inputMask1);

				inputBuffer12 = si_shufb(inputBuffer11, inputBuffer21, inputMask1);

	

				triangle45 = si_shufb(triangle23, inputBuffer02, triangleMask2);   

				inputBuffer03 = si_shufb(inputBuffer02, inputBuffer12, inputMask2);

	

				triangle67 = si_shufb(triangle45, inputBuffer03, triangleMask3);   

	

				output0 = si_shufb(triangle01, triangle23, s_output0);			 

				output1 = si_shufb(triangle23, triangle45, s_output1);			 

				output2 = si_shufb(triangle45, triangle67, s_output2);			 



			inputQuad0 = si_lqd(pInputStream, 0x00);

			inputQuad1 = si_lqd(pInputStream, 0x10);

			inputQuad2 = si_lqd(pInputStream, 0x20);

			inputQuad3 = si_lqd(pInputStream, 0x30);



				si_stqd(output0, pOutputStream, 0x00);						  

				si_stqd(output1, pOutputStream, 0x10);

				si_stqd(output2, pOutputStream, 0x20);

	

				pOutputStream = si_a(pOutputStream, pOutputStreamInc);

				pOutputStreamInc = si_il(0x30);

	

				triangleCounter--;



			newBits = si_and(si_and(alignedControlHw, alignedControlHwShifted), AAAA);

			newBitsCountPerByte = si_cntb(newBits);

			newBitsCountPerByteShifted = si_rotmi(newBitsCountPerByte, -8);

			newBitsCount = si_a(si_andi(newBitsCountPerByte,0xff), newBitsCountPerByteShifted);

			oldBitsCount = si_sfi(newBitsCount, 8);



			oldBytes = si_a(oldBitsCount, oldBitsCount);



			offset = si_andi(pInputStream, 0xf);

			offsetBC = si_shufb(offset, offset, s_DDDDDDDDDDDDDDDD);

			s_offset = si_a(offsetBC, s_ABCD_qw);



			pMaskOffset0 = si_rotmi(si_and(alignedControlHw, controlMask0), -8);

			pMaskOffset1 = si_rotmi(si_and(alignedControlHw, controlMask1), -4);			

			pMaskOffset2 = si_and(alignedControlHw, controlMask2);

			pMaskOffset3 = si_shli(si_and(alignedControlHw, controlMask3), 4);



			triangleMask0 = si_lqx(pTriangleMasks, pMaskOffset0);

			inputMask0 = si_lqx(pInputMasks, pMaskOffset0);



		} while (triangleCounter != 0);





		void* freePtr = (void*)si_to_ptr(pOutputStream);

		freePtr = (void*)(((unsigned int)freePtr + 0xf) & ~0xf);

		edgeGeomSetFreePtr(ctx, freePtr);

	}



	/* END - DECOMPRESS 2-BIT STREAM */

}

#endif



void edgeGeomDecompressIndexes(EdgeGeomSpuContext *ctx, const void *indexes)

{

	if (ctx->numVisibleIndexes == 0) {

		return;

	}



	switch((ctx->spuConfigInfo.indexesFlavorAndSkinningFlavor >> 4) & 0xF)

	{

	case EDGE_GEOM_INDEXES_COMPRESSED_TRIANGLE_LIST_CW:

		//winding order doesn't matter, hooray!

	case EDGE_GEOM_INDEXES_COMPRESSED_TRIANGLE_LIST_CCW:

#if ENABLE_INDEX_DECOMPRESSION

		DecompressIndexes(ctx, indexes);

#else

		EDGE_ASSERT_MSG(0, ("Edge index decompression must be enabled in edgegeom_config.h!"));

#endif // ENABLE_INDEX_DECOMPRESSION

		break;

	case EDGE_GEOM_INDEXES_U16_TRIANGLE_LIST_CW:

	case EDGE_GEOM_INDEXES_U16_TRIANGLE_LIST_CCW:

	default:

		break;

	}



	ctx->indexesLs = (uint32_t)indexes;

}



#if DECOMPRESSVERTEXESBYDESCRIPTION_INTRINSICS

void DecompressVertexesByDescription(EdgeGeomSpuContext *ctx,

	const void *vertexes, const void *fixedOffsets, const EdgeGeomVertexStreamDescription *streamDesc,

	uint32_t numVertexes, float blendFactor, uint16_t *blendedVertexIndexTable)

{

	if(numVertexes == 0)

		return;



	// Assorted masks

	const qword s_000A000A000A000A = (qword)spu_splats(0x80808000);

	const qword s_000B000B000B000B = si_ori(s_000A000A000A000A, 0x01);

	const qword s_000E000E000E000E = si_ori(s_000A000A000A000A, 0x04);

	const qword s_000G000G000G000G = si_ori(s_000A000A000A000A, 0x06);

	const qword s_000A000C000E000G = (qword)(vec_int4){0x80808000, 0x80808002, 0x80808004, 0x80808006};

	const qword s_000B000D000F000H = si_ai(s_000A000C000E000G, 1);

	const qword s_AAAA = si_ila(0x10203);

	const qword s_DDDDDDDDDDDDDDDD = si_ilh(0x0303);

	const qword mask_0F0F = si_fsmbi(0x0F0F);

	const qword mask_00FF = si_fsmbi(0x00FF);

	// ... and constants

	const qword k1_0f = si_ilhu(0x3f80);

	const qword i16nScale = (qword)spu_splats(2.0f / 65535.0f);

	const qword i16nBias  = (qword)spu_splats(1.0f / 65535.0f);

	const qword u8nScale  = (qword)spu_splats(1.0f / 255.0f);

	const qword u8nBias   = (qword)spu_splats(0.0f);

	const qword x11y11z10BitMask	 = (qword)(vec_int4){0xFFE00000, 0xFFE00000, 0xFFC00000, 0x00000000};

	const qword x11y11z10ShiftValues = (qword)(vec_uint4){5, 2, 0, 0};

	const qword x11y11z10Scale	   = (qword)(vec_int4){0x3F801002, 0x3F801002, 0x3F802008, 0x00000000};

	const qword x11y11z10Bias		= (qword)(vec_int4){0x3A001002, 0x3A001002, 0x3A802008, 0x3F800000};

	const qword f16ExpHoleMask  = si_ilhu(0x7000);

	const qword f16ExpRebias	= si_ilhu(0x3800);

	const qword signBit		 = si_ilhu(0x8000);

	qword pFixedPointOffsets	= si_from_ptr(fixedOffsets);

	const qword mask_FFFF	   = (qword)spu_splats(0xFFFFFFFF);

	const qword uvRotValues	 = (qword)(vec_int4){-4, -6, -4, 0};

	const qword uvFormatSelect  = (qword)(vec_uint4){0xf0, 0x3ff, 0x3ff, 0x0};

	const qword uvFormatMask	= (qword)(vec_uint4){0x0, 0x3f800000, 0x3f800000, 0x0};

	const qword uvScale		 = (qword)spu_splats(0x46352e7c);

	const qword uvBias		  = (qword)spu_splats(0xc6353150);

	const qword uvOutputMask	= (qword)(vec_uint4){0x0, 0x0, 0x0, 0x0c0d0e0f};

	const qword uvOutputSelect  = (qword)(vec_uint4){0x7fffffff, 0x7fffffff, 0x7fffffff, 0x0};

	const qword uvSelect		= (qword)(vec_uint4){0x80000000, 0x80000000, 0x80000000, 0xffffffff};

	const qword uvShuffleAdjust = (qword)(vec_uint4){0x08080808,0x08080808,0x08080808,0x0};

	const qword pUvMaskTable = (qword)si_from_ptr(uvMaskTable);



	// If the blend alpha is non-zero, we're blending.

	const qword alpha = (qword)spu_splats(blendFactor);

	const qword isBlending = si_xorbi(si_fceq(alpha, si_il(0)), 0xFF); // all bits set if alpha != 0

	// We use the spare uniform table as a temporary destination for

	// blend shape deltas, and then blend them into the final

	// destination table as a post-process.

	// If the spare uniform table bit is not set in the SpuConfigInfo flags,

	// then the spare uniform table does not exist!  This is an error!

	if (si_to_int(isBlending) != 0 && (ctx->spuConfigInfo.flagsAndUniformTableCount & EDGE_GEOM_FLAG_INCLUDES_EXTRA_UNIFORM_TABLE) == 0)

	{

		EDGE_PRINTF("ERROR: attempt to use blend shapes without allocating an extra uniform table!\n");

		return;

	}

	const qword deltaUniform = si_shufb(si_from_ptr(ctx->uniformTables[ctx->spuConfigInfo.flagsAndUniformTableCount & 0xF]), s_AAAA, s_AAAA);



	// We assume a multiple of four vertexes

	const qword actualVertexCount = (qword)spu_splats(numVertexes);

	const qword vertexCount  = (qword)spu_splats((numVertexes+3)&~3 );

	const qword uniformTableSize = si_shli(vertexCount, 4);

	const qword vertexStride = (qword)spu_splats((int)streamDesc->stride);

	qword dummy; // writes past the end of the buffer are routed here instead

	const qword pSafeBuffer = si_shufb(si_from_ptr(&dummy), s_AAAA, s_AAAA);;



	// The main decompression loops below process four vertexes per

	// iteration.  These vertexes are not necessarily directly

	// sequential; for efficiency, we process four vertexes which

	// start at the same relative offset within each quadword (that

	// way, we can perform the *exact* same loads & shuffles to

	// extract the relevant attribute from each vertex).  So, rather

	// than looping over all the vertexes from start to finish, we

	// have an outer loop that iterates over each quadword byte offset

	// (0 to 15) and an inner loop that iterates over all vertexes

	// that start at that byte offset (four at a time).

	//

	// The spacing between vertexes with identical byte offsets is

	// dependent upon the vertex stride – in particular, the largest

	// power of two (from 1 to 16) that evenly divides the stride.  If

	// the input stride is 17, its maximum POT divisor is 1, and

	// vertexes that start on the same offset will be 16 vertexes

	// apart.  If the input stride is 32, its maximum POT divisor (up

	// to 16, recall) is 16, and every vertex starts at the same

	// offset.

	//

	// Likewise, the maximum POT divisor determines how many

	// iterations the outer loop needs to run.  For 17-byte vertexes,

	// there will be vertexes starting at every byte offset from 0 to

	// 15, so the outer loop will have 16 iterations, each of which

	// will process 1/16th of the total vertexes.  For 12-byte

	// vertexes (maximum POT divisor: 4), vertexes will only start at

	// offsets 0, 4, 8 and 12, so the outer loop only needs 4

	// iterations, processing 1/4th of the vertexes per iteration.

	// For 32-byte vertexes (maximum POT divisor: 16), all vertexes

	// start at offset 0, so the outer loop needs only one iteration

	// to process all the vertexes.

	int maximumStrideDivisorLog = 0;

	int tempStride = streamDesc->stride;

#ifdef EDGE_GEOM_DEBUG

	if (streamDesc->stride == 0)

	{

		EDGE_PRINTF("ERROR: input/blend stream description has 0 size!\n");

		return;

	}	

#endif

	while((tempStride & 0x1) == 0 && maximumStrideDivisorLog < 4)

	{

		maximumStrideDivisorLog++;

		tempStride >>= 1;

	}

	qword pAttrDestOffsets = (qword)(vec_int4){0, 16, 32, 48};

	pAttrDestOffsets = si_ai(si_shl(pAttrDestOffsets, (qword)spu_splats(4-maximumStrideDivisorLog)), 1);

	const qword pAttrDestsInc = (qword)spu_splats((64 >> maximumStrideDivisorLog)*0x10);

	const qword srcOffsetsInc = si_shl(vertexStride, (qword)spu_splats(6-maximumStrideDivisorLog));

	const uint32_t maxVertIndexOffset = 16 >> maximumStrideDivisorLog;

	const qword pAttrSrcStagger = si_shl(vertexStride, (qword)spu_splats(4-maximumStrideDivisorLog));



	const qword pAttrSrc1 = si_from_ptr(vertexes);

	const qword pAttrSrc3 = si_a(pAttrSrc1, pAttrSrcStagger);

	const qword pAttrSrc5 = si_a(pAttrSrc3, pAttrSrcStagger);

	const qword pAttrSrc7 = si_a(pAttrSrc5, pAttrSrcStagger);

	const qword pAttrSrc2 = si_ai(pAttrSrc1, 16);

	const qword pAttrSrc4 = si_ai(pAttrSrc3, 16);

	const qword pAttrSrc6 = si_ai(pAttrSrc5, 16);

	const qword pAttrSrc8 = si_ai(pAttrSrc7, 16);



	qword isDestBad;

	qword nextAttrDesc = si_from_ptr(streamDesc->blocks);



	const bool isFastStaticPath = (ctx->spuConfigInfo.flagsAndUniformTableCount & EDGE_GEOM_FLAG_STATIC_GEOMETRY_FAST_PATH) != 0;



	// Process each attribute sequentially.

	for(uint32_t iAttr=0; iAttr<streamDesc->numAttributes; ++iAttr)

	{

		// Load the attribute description

		const qword attrDesc = si_rotqby(si_lqd(nextAttrDesc, 0), nextAttrDesc);

		nextAttrDesc = si_ai(nextAttrDesc, 8);

		const int attrId = si_to_int(si_andi(attrDesc, 0xFF));

		// If this segment uses the fast path for static geometry, don't decompress anything except

		// position.

		if ( isFastStaticPath && attrId != EDGE_GEOM_ATTRIBUTE_ID_POSITION)

		{

			continue;

		}

		// Decompress the attribute to the next unassigned uniform

		// table.  If we're blending, then later we'll blend from the

		// delta table to this destination table.

		const qword unusedId = si_shufb(si_from_int(EDGE_GEOM_ATTRIBUTE_ID_UNKNOWN),

				s_DDDDDDDDDDDDDDDD, s_DDDDDDDDDDDDDDDD);

		const qword unusedBits = si_gbb(si_ceqb(unusedId, ctx->uniformTableToAttributeIdMapping));

		const int destTableIndex = si_to_int(si_ai(si_clz(unusedBits), -16));

		qword pDestTable = si_shufb(si_from_ptr(ctx->uniformTables[destTableIndex]),s_AAAA,s_AAAA);

		pDestTable = si_selb(pDestTable, deltaUniform, isBlending);

		// pointer to the end of the destination uniform table

		const qword pUniformEnd = si_a(pDestTable, uniformTableSize);

		// Pointers into the destination table, 16 vertexes apart

		const qword pAttrDestsBase = si_a(pAttrDestOffsets, pDestTable);

		// Offset of the attribute within each vertex

		const qword attribByteOffset = si_rotmi(attrDesc, -24);

		// Size of the attribute (usually componentCount*componentSize, but not always)

		const qword attribSize = si_shufb(attrDesc, attrDesc, s_000E000E000E000E);

		const qword s_000C000C000C000C = si_ori(s_000A000A000A000A, 0x02);

		int32_t attrComponentCount = si_to_int(si_shufb(attrDesc, attrDesc, s_000C000C000C000C));

		// Most attribute types will need to have their components

		// broadcast into the words of their quad before

		// decompressing.  For efficiency, we apply the type-specific

		// broadcast shuffle to the extract mask we calculate below,

		// so that extract and broadcast can be performed in one step.

		qword broadcastMask;

		const int attribFormat = si_to_int(si_shufb(attrDesc, attrDesc, s_000B000B000B000B));



		// Which table should these deltas be blended with? (only relevant when blending)

		const qword id = si_shufb(attrDesc, s_DDDDDDDDDDDDDDDD, s_DDDDDDDDDDDDDDDD);

		const qword matchBits = si_gbb(si_ceqb(id, ctx->uniformTableToAttributeIdMapping));

		const int blendDestTableIndex = si_to_int(si_ai(si_clz(matchBits), -16));

		// SAFETY MEASURE: if the blend destination attribute wasn't found, skip this attribute.

		if (si_to_int(isBlending) != 0 && blendDestTableIndex == 16)

		{

			continue;

		}

				



		switch(attribFormat)

		{

		case EDGE_GEOM_ATTRIBUTE_FORMAT_F32: // F32 will be commented, the rest are fairly boilerplate.

			broadcastMask = (qword)s_ABCD;

			for(uint32_t vertIndexOffset=0; vertIndexOffset<maxVertIndexOffset; vertIndexOffset++)

			{

				qword pAttrDests = si_a(pAttrDestsBase, (qword)spu_splats(vertIndexOffset<<4));

				// source offset starts for the Nth iteration starts at

				// the byte offset of the attribute in the Nth vertex.

				qword srcOffset = si_mpya(si_from_int(vertIndexOffset),vertexStride, attribByteOffset);

				// Build a shuffle mask to extract the attribute, given

				// its byte offset within each quadword.

				const qword quadOffset = si_andi(srcOffset, 0xF);

				const qword extractMask = si_a((qword)s_ABCD,

					si_shufb(quadOffset, srcOffset, s_DDDDDDDDDDDDDDDD));

				const qword extractAndBroadcastMask = si_shufb(extractMask, extractMask, broadcastMask);

				// If the attribute straddles two quadwords, we need to

				// load two quads per vertex to extract the full

				// attribute.  Otherwise, we can just load one.

				const int straddlesQuad = si_to_int(si_cgti(si_a(quadOffset, attribSize), 16));



				// For F32 only, it's faster to do all the end

				// condition testing in the even pipeline

				qword pAttrDest1 = pAttrDests;

				qword pAttrDest2 = si_shlqbyi(pAttrDests, 4);

				qword pAttrDest3 = si_shlqbyi(pAttrDests, 8);

				qword pAttrDest4 = si_shlqbyi(pAttrDests, 12);

				// loop preamble

				qword q1a = si_lqx(pAttrSrc1, srcOffset);

				qword q1b = si_lqx(pAttrSrc2, srcOffset);

				qword q2a = si_lqx(pAttrSrc3, srcOffset);

				qword q2b = si_lqx(pAttrSrc4, srcOffset);

				qword q3a = si_lqx(pAttrSrc5, srcOffset);

				qword q3b = si_lqx(pAttrSrc6, srcOffset);

				qword q4a = si_lqx(pAttrSrc7, srcOffset);

				qword q4b = si_lqx(pAttrSrc8, srcOffset);

				srcOffset = si_a(srcOffset, srcOffsetsInc);

						

				if (straddlesQuad) // slower loop with two loads per vertex

				{

					do

					{

						// extract and broadcast

						qword in1 = si_shufb(q1a, q1b, extractAndBroadcastMask);

						qword in2 = si_shufb(q2a, q2b, extractAndBroadcastMask);

						qword in3 = si_shufb(q3a, q3b, extractAndBroadcastMask);

						qword in4 = si_shufb(q4a, q4b, extractAndBroadcastMask);

						// If any of the destination pointers are past the

						// end of the destination array, they must be

						// redirected to the safe buffer.

						isDestBad = si_cgt(pAttrDest1, pUniformEnd);

						qword isDestBad2 = si_cgt(pAttrDest2, pUniformEnd);

						qword isDestBad3 = si_cgt(pAttrDest3, pUniformEnd);

						qword isDestBad4 = si_cgt(pAttrDest4, pUniformEnd);

						qword pFinalDest1 = si_selb(pAttrDest1, pSafeBuffer, isDestBad);

						qword pFinalDest2 = si_selb(pAttrDest2, pSafeBuffer, isDestBad2);

						qword pFinalDest3 = si_selb(pAttrDest3, pSafeBuffer, isDestBad3);

						qword pFinalDest4 = si_selb(pAttrDest4, pSafeBuffer, isDestBad4);

						// store

						si_stqd(in1, pFinalDest1, 0x00);

						si_stqd(in2, pFinalDest2, 0x00);

						si_stqd(in3, pFinalDest3, 0x00);

						si_stqd(in4, pFinalDest4, 0x00);

						// load

						q1a = si_lqx(pAttrSrc1, srcOffset);

						q1b = si_lqx(pAttrSrc2, srcOffset);

						q2a = si_lqx(pAttrSrc3, srcOffset);

						q2b = si_lqx(pAttrSrc4, srcOffset);

						q3a = si_lqx(pAttrSrc5, srcOffset);

						q3b = si_lqx(pAttrSrc6, srcOffset);

						q4a = si_lqx(pAttrSrc7, srcOffset);

						q4b = si_lqx(pAttrSrc8, srcOffset);

						// increment

						srcOffset = si_a(srcOffset, srcOffsetsInc);

						pAttrDest1 = si_a(pAttrDest1, pAttrDestsInc);

						pAttrDest2 = si_a(pAttrDest2, pAttrDestsInc);

						pAttrDest3 = si_a(pAttrDest3, pAttrDestsInc);

						pAttrDest4 = si_a(pAttrDest4, pAttrDestsInc);

					}

					while(si_to_int(isDestBad) == 0);

				}

				else // faster loop with one load per vertex

				{

					do

					{

						// extract and broadcast

						qword in1 = si_shufb(q1a, q1a, extractAndBroadcastMask);

						qword in2 = si_shufb(q2a, q2a, extractAndBroadcastMask);

						qword in3 = si_shufb(q3a, q3a, extractAndBroadcastMask);

						qword in4 = si_shufb(q4a, q4a, extractAndBroadcastMask);

						// If any of the destination pointers are past the

						// end of the destination array, they must be

						// redirected to the safe buffer.

						isDestBad = si_cgt(pAttrDest1, pUniformEnd);

						qword isDestBad2 = si_cgt(pAttrDest2, pUniformEnd);

						qword isDestBad3 = si_cgt(pAttrDest3, pUniformEnd);

						qword isDestBad4 = si_cgt(pAttrDest4, pUniformEnd);

						qword pFinalDest1 = si_selb(pAttrDest1, pSafeBuffer, isDestBad);

						qword pFinalDest2 = si_selb(pAttrDest2, pSafeBuffer, isDestBad2);

						qword pFinalDest3 = si_selb(pAttrDest3, pSafeBuffer, isDestBad3);

						qword pFinalDest4 = si_selb(pAttrDest4, pSafeBuffer, isDestBad4);

						// store

						si_stqd(in1, pFinalDest1, 0x00);

						si_stqd(in2, pFinalDest2, 0x00);

						si_stqd(in3, pFinalDest3, 0x00);

						si_stqd(in4, pFinalDest4, 0x00);

						// load

						q1a = si_lqx(pAttrSrc1, srcOffset);

						q2a = si_lqx(pAttrSrc3, srcOffset);

						q3a = si_lqx(pAttrSrc5, srcOffset);

						q4a = si_lqx(pAttrSrc7, srcOffset);

						// increment

						srcOffset = si_a(srcOffset, srcOffsetsInc);

						pAttrDest1 = si_a(pAttrDest1, pAttrDestsInc);

						pAttrDest2 = si_a(pAttrDest2, pAttrDestsInc);

						pAttrDest3 = si_a(pAttrDest3, pAttrDestsInc);

						pAttrDest4 = si_a(pAttrDest4, pAttrDestsInc);

					}

					while(si_to_int(isDestBad) == 0);

				}

			}

			break;





		case EDGE_GEOM_ATTRIBUTE_FORMAT_F16:

			broadcastMask = (qword)s_A0B0C0D0;

			for(uint32_t vertIndexOffset=0; vertIndexOffset<maxVertIndexOffset; vertIndexOffset++)

			{

				qword pAttrDests = si_a(pAttrDestsBase, (qword)spu_splats(vertIndexOffset<<4));

				qword srcOffset = si_mpya(si_from_int(vertIndexOffset),vertexStride, attribByteOffset);

				const qword quadOffset = si_andi(srcOffset, 0xF);

				const qword extractMask = si_a((qword)s_ABCD,

					si_shufb(quadOffset, srcOffset, s_DDDDDDDDDDDDDDDD));

				const qword extractAndBroadcastMask = si_shufb(extractMask, extractMask, broadcastMask);



				// loop preamble

				// load

				qword q1a = si_lqx(pAttrSrc1, srcOffset);

				qword q1b = si_lqx(pAttrSrc2, srcOffset);

				qword q2a = si_lqx(pAttrSrc3, srcOffset);

				qword q2b = si_lqx(pAttrSrc4, srcOffset);

				qword q3a = si_lqx(pAttrSrc5, srcOffset);

				qword q3b = si_lqx(pAttrSrc6, srcOffset);

				qword q4a = si_lqx(pAttrSrc7, srcOffset);

				qword q4b = si_lqx(pAttrSrc8, srcOffset);

				// extract and broadcast

				qword in1 = si_shufb(q1a, q1b, extractAndBroadcastMask);

				qword in2 = si_shufb(q2a, q2b, extractAndBroadcastMask);

				qword in3 = si_shufb(q3a, q3b, extractAndBroadcastMask);

				qword in4 = si_shufb(q4a, q4b, extractAndBroadcastMask);

				// convert (stage 1)

				qword dataHole1 = si_andc(si_rotmai(in1, -3), f16ExpHoleMask);

				qword dataHole2 = si_andc(si_rotmai(in2, -3), f16ExpHoleMask);

				qword dataHole3 = si_andc(si_rotmai(in3, -3), f16ExpHoleMask);

				qword dataHole4 = si_andc(si_rotmai(in4, -3), f16ExpHoleMask);

				qword abs1 = si_andc(in1, signBit);

				qword abs2 = si_andc(in2, signBit);

				qword abs3 = si_andc(in3, signBit);

				qword abs4 = si_andc(in4, signBit);

				// increment

				srcOffset = si_a(srcOffset, srcOffsetsInc);

				do

				{

					// convert (stage 2)

					qword expBias1 = si_and(si_cgti(abs1, 0), f16ExpRebias);

					qword expBias2 = si_and(si_cgti(abs2, 0), f16ExpRebias);

					qword expBias3 = si_and(si_cgti(abs3, 0), f16ExpRebias);

					qword expBias4 = si_and(si_cgti(abs4, 0), f16ExpRebias);

					qword out1 = si_a(dataHole1, expBias1);

					qword out2 = si_a(dataHole2, expBias2);

					qword out3 = si_a(dataHole3, expBias3);

					qword out4 = si_a(dataHole4, expBias4);

					// store

					isDestBad = si_cgt(pAttrDests, pUniformEnd);

					qword pFinalDest1 = si_selb(pAttrDests, pSafeBuffer, isDestBad);

					qword pFinalDest2 = si_rotqbyi(pFinalDest1, 4);

					qword pFinalDest3 = si_rotqbyi(pFinalDest1, 8);

					qword pFinalDest4 = si_rotqbyi(pFinalDest1, 12);

					si_stqd(out1, pFinalDest1, 0x00);

					si_stqd(out2, pFinalDest2, 0x00);

					si_stqd(out3, pFinalDest3, 0x00);

					si_stqd(out4, pFinalDest4, 0x00);

					// load

					q1a = si_lqx(pAttrSrc1, srcOffset);

					q1b = si_lqx(pAttrSrc2, srcOffset);

					q2a = si_lqx(pAttrSrc3, srcOffset);

					q2b = si_lqx(pAttrSrc4, srcOffset);

					q3a = si_lqx(pAttrSrc5, srcOffset);

					q3b = si_lqx(pAttrSrc6, srcOffset);

					q4a = si_lqx(pAttrSrc7, srcOffset);

					q4b = si_lqx(pAttrSrc8, srcOffset);

					// extract and broadcast

					qword in1 = si_shufb(q1a, q1b, extractAndBroadcastMask);

					qword in2 = si_shufb(q2a, q2b, extractAndBroadcastMask);

					qword in3 = si_shufb(q3a, q3b, extractAndBroadcastMask);

					qword in4 = si_shufb(q4a, q4b, extractAndBroadcastMask);

					// convert (stage 1)

					dataHole1 = si_andc(si_rotmai(in1, -3), f16ExpHoleMask);

					dataHole2 = si_andc(si_rotmai(in2, -3), f16ExpHoleMask);

					dataHole3 = si_andc(si_rotmai(in3, -3), f16ExpHoleMask);

					dataHole4 = si_andc(si_rotmai(in4, -3), f16ExpHoleMask);

					abs1 = si_andc(in1, signBit);

					abs2 = si_andc(in2, signBit);

					abs3 = si_andc(in3, signBit);

					abs4 = si_andc(in4, signBit);

					// increment

					srcOffset = si_a(srcOffset, srcOffsetsInc);

					pAttrDests = si_a(pAttrDests, pAttrDestsInc);

				}

				while(si_to_int(isDestBad) == 0);

			}

			break;





		case EDGE_GEOM_ATTRIBUTE_FORMAT_I16:

			broadcastMask = (qword)s_A0B0C0D0;

			for(uint32_t vertIndexOffset=0; vertIndexOffset<maxVertIndexOffset; vertIndexOffset++)

			{

				qword pAttrDests = si_a(pAttrDestsBase, (qword)spu_splats(vertIndexOffset<<4));

				qword srcOffset = si_mpya(si_from_int(vertIndexOffset),vertexStride, attribByteOffset);

				const qword quadOffset = si_andi(srcOffset, 0xF);

				const qword extractMask = si_a((qword)s_ABCD,

					si_shufb(quadOffset, srcOffset, s_DDDDDDDDDDDDDDDD));

				const qword extractAndBroadcastMask = si_shufb(extractMask, extractMask, broadcastMask);

				const int straddlesQuad = si_to_int(si_cgti(si_a(quadOffset, attribSize), 16));



				// preamble

				qword q1a = si_lqx(pAttrSrc1, srcOffset);

				qword q1b = si_lqx(pAttrSrc2, srcOffset);

				qword q2a = si_lqx(pAttrSrc3, srcOffset);

				qword q2b = si_lqx(pAttrSrc4, srcOffset);

				qword q3a = si_lqx(pAttrSrc5, srcOffset);

				qword q3b = si_lqx(pAttrSrc6, srcOffset);

				qword q4a = si_lqx(pAttrSrc7, srcOffset);

				qword q4b = si_lqx(pAttrSrc8, srcOffset);

				srcOffset = si_a(srcOffset, srcOffsetsInc);



				if (straddlesQuad) // slower loop with two loads per vertex

				{

					do

					{

						// extract and broadcast

						qword in1 = si_shufb(q1a, q1b, extractAndBroadcastMask);

						qword in2 = si_shufb(q2a, q2b, extractAndBroadcastMask);

						qword in3 = si_shufb(q3a, q3b, extractAndBroadcastMask);

						qword in4 = si_shufb(q4a, q4b, extractAndBroadcastMask);

						// convert	

						qword float1 = si_csflt(in1, 16);

						qword float2 = si_csflt(in2, 16);

						qword float3 = si_csflt(in3, 16);

						qword float4 = si_csflt(in4, 16);

						// store

						isDestBad = si_cgt(pAttrDests, pUniformEnd);

						qword pFinalDest1 = si_selb(pAttrDests, pSafeBuffer, isDestBad);

						qword pFinalDest2 = si_rotqbyi(pFinalDest1, 4);

						qword pFinalDest3 = si_rotqbyi(pFinalDest1, 8);

						qword pFinalDest4 = si_rotqbyi(pFinalDest1, 12);

						si_stqd(float1, pFinalDest1, 0x00);

						si_stqd(float2, pFinalDest2, 0x00);

						si_stqd(float3, pFinalDest3, 0x00);

						si_stqd(float4, pFinalDest4, 0x00);

						// load

						q1a = si_lqx(pAttrSrc1, srcOffset);

						q1b = si_lqx(pAttrSrc2, srcOffset);

						q2a = si_lqx(pAttrSrc3, srcOffset);

						q2b = si_lqx(pAttrSrc4, srcOffset);

						q3a = si_lqx(pAttrSrc5, srcOffset);

						q3b = si_lqx(pAttrSrc6, srcOffset);

						q4a = si_lqx(pAttrSrc7, srcOffset);

						q4b = si_lqx(pAttrSrc8, srcOffset);

						// increment

						srcOffset = si_a(srcOffset, srcOffsetsInc);

						pAttrDests = si_a(pAttrDests, pAttrDestsInc);

					}

					while(si_to_int(isDestBad) == 0);

				}

				else // faster loop with one load per vertex

				{

					do

					{

						// extract and broadcast

						qword in1 = si_shufb(q1a, q1a, extractAndBroadcastMask);

						qword in2 = si_shufb(q2a, q2a, extractAndBroadcastMask);

						qword in3 = si_shufb(q3a, q3a, extractAndBroadcastMask);

						qword in4 = si_shufb(q4a, q4a, extractAndBroadcastMask);

						// convert	

						qword float1 = si_csflt(in1, 16);

						qword float2 = si_csflt(in2, 16);

						qword float3 = si_csflt(in3, 16);

						qword float4 = si_csflt(in4, 16);

						// store

						isDestBad = si_cgt(pAttrDests, pUniformEnd);

						qword pFinalDest1 = si_selb(pAttrDests, pSafeBuffer, isDestBad);

						qword pFinalDest2 = si_rotqbyi(pFinalDest1, 4);

						qword pFinalDest3 = si_rotqbyi(pFinalDest1, 8);

						qword pFinalDest4 = si_rotqbyi(pFinalDest1, 12);					

						si_stqd(float1, pFinalDest1, 0x00);

						si_stqd(float2, pFinalDest2, 0x00);

						si_stqd(float3, pFinalDest3, 0x00);

						si_stqd(float4, pFinalDest4, 0x00);

						// load

						q1a = si_lqx(pAttrSrc1, srcOffset);

						q2a = si_lqx(pAttrSrc3, srcOffset);

						q3a = si_lqx(pAttrSrc5, srcOffset);

						q4a = si_lqx(pAttrSrc7, srcOffset);

						// increment

						srcOffset = si_a(srcOffset, srcOffsetsInc);

						pAttrDests = si_a(pAttrDests, pAttrDestsInc);

					}

					while(si_to_int(isDestBad) == 0);

				}

			}

			break;





		case EDGE_GEOM_ATTRIBUTE_FORMAT_I16N:

			broadcastMask = (qword)s_A0B0C0D0;

			for(uint32_t vertIndexOffset=0; vertIndexOffset<maxVertIndexOffset; vertIndexOffset++)

			{

				qword pAttrDests = si_a(pAttrDestsBase, (qword)spu_splats(vertIndexOffset<<4));

				qword srcOffset = si_mpya(si_from_int(vertIndexOffset),vertexStride, attribByteOffset);

				const qword quadOffset = si_andi(srcOffset, 0xF);

				const qword extractMask = si_a((qword)s_ABCD,

					si_shufb(quadOffset, srcOffset, s_DDDDDDDDDDDDDDDD));

				const qword extractAndBroadcastMask = si_shufb(extractMask, extractMask, broadcastMask);

				const int straddlesQuad = si_to_int(si_cgti(si_a(quadOffset, attribSize), 16));

				// preamble

				qword q1a = si_lqx(pAttrSrc1, srcOffset);

				qword q1b = si_lqx(pAttrSrc2, srcOffset);

				qword q2a = si_lqx(pAttrSrc3, srcOffset);

				qword q2b = si_lqx(pAttrSrc4, srcOffset);

				qword q3a = si_lqx(pAttrSrc5, srcOffset);

				qword q3b = si_lqx(pAttrSrc6, srcOffset);

				qword q4a = si_lqx(pAttrSrc7, srcOffset);

				qword q4b = si_lqx(pAttrSrc8, srcOffset);

				qword in1 = si_shufb(q1a, q1b, extractAndBroadcastMask);

				qword in2 = si_shufb(q2a, q2b, extractAndBroadcastMask);

				qword in3 = si_shufb(q3a, q3b, extractAndBroadcastMask);

				qword in4 = si_shufb(q4a, q4b, extractAndBroadcastMask);

				srcOffset = si_a(srcOffset, srcOffsetsInc);

				if (straddlesQuad) // slower loop with two loads per vertex

				{

					do

					{

						// convert	

						qword float1 = si_fma(si_csflt(in1, 16), i16nScale, i16nBias);

						qword float2 = si_fma(si_csflt(in2, 16), i16nScale, i16nBias);

						qword float3 = si_fma(si_csflt(in3, 16), i16nScale, i16nBias);

						qword float4 = si_fma(si_csflt(in4, 16), i16nScale, i16nBias);

						// store

						isDestBad = si_cgt(pAttrDests, pUniformEnd);

						qword pFinalDest1 = si_selb(pAttrDests, pSafeBuffer, isDestBad);

						qword pFinalDest2 = si_rotqbyi(pFinalDest1, 4);

						qword pFinalDest3 = si_rotqbyi(pFinalDest1, 8);

						qword pFinalDest4 = si_rotqbyi(pFinalDest1, 12);

						si_stqd(float1, pFinalDest1, 0x00);

						si_stqd(float2, pFinalDest2, 0x00);

						si_stqd(float3, pFinalDest3, 0x00);

						si_stqd(float4, pFinalDest4, 0x00);

						// load

						qword q1a = si_lqx(pAttrSrc1, srcOffset);

						qword q1b = si_lqx(pAttrSrc2, srcOffset);

						qword q2a = si_lqx(pAttrSrc3, srcOffset);

						qword q2b = si_lqx(pAttrSrc4, srcOffset);

						qword q3a = si_lqx(pAttrSrc5, srcOffset);

						qword q3b = si_lqx(pAttrSrc6, srcOffset);

						qword q4a = si_lqx(pAttrSrc7, srcOffset);

						qword q4b = si_lqx(pAttrSrc8, srcOffset);

						// extract and broadcast

						in1 = si_shufb(q1a, q1b, extractAndBroadcastMask);

						in2 = si_shufb(q2a, q2b, extractAndBroadcastMask);

						in3 = si_shufb(q3a, q3b, extractAndBroadcastMask);

						in4 = si_shufb(q4a, q4b, extractAndBroadcastMask);

						// increment

						srcOffset = si_a(srcOffset, srcOffsetsInc);

						pAttrDests = si_a(pAttrDests, pAttrDestsInc);

					}

					while(si_to_int(isDestBad) == 0);

				}

				else // faster loop with one load per vertex

				{

					do

					{

						// convert	

						qword float1 = si_fma(si_csflt(in1, 16), i16nScale, i16nBias);

						qword float2 = si_fma(si_csflt(in2, 16), i16nScale, i16nBias);

						qword float3 = si_fma(si_csflt(in3, 16), i16nScale, i16nBias);

						qword float4 = si_fma(si_csflt(in4, 16), i16nScale, i16nBias);

						// store

						isDestBad = si_cgt(pAttrDests, pUniformEnd);

						qword pFinalDest1 = si_selb(pAttrDests, pSafeBuffer, isDestBad);

						qword pFinalDest2 = si_rotqbyi(pFinalDest1, 4);

						qword pFinalDest3 = si_rotqbyi(pFinalDest1, 8);

						qword pFinalDest4 = si_rotqbyi(pFinalDest1, 12);

						si_stqd(float1, pFinalDest1, 0x00);

						si_stqd(float2, pFinalDest2, 0x00);

						si_stqd(float3, pFinalDest3, 0x00);

						si_stqd(float4, pFinalDest4, 0x00);

						// load

						qword q1a = si_lqx(pAttrSrc1, srcOffset);

						qword q2a = si_lqx(pAttrSrc3, srcOffset);

						qword q3a = si_lqx(pAttrSrc5, srcOffset);

						qword q4a = si_lqx(pAttrSrc7, srcOffset);

						// extract and broadcast

						in1 = si_shufb(q1a, q1a, extractAndBroadcastMask);

						in2 = si_shufb(q2a, q2a, extractAndBroadcastMask);

						in3 = si_shufb(q3a, q3a, extractAndBroadcastMask);

						in4 = si_shufb(q4a, q4a, extractAndBroadcastMask);

						// increment

						srcOffset = si_a(srcOffset, srcOffsetsInc);

						pAttrDests = si_a(pAttrDests, pAttrDestsInc);

					}

					while(si_to_int(isDestBad) == 0);

				}

			}

			break;





		case EDGE_GEOM_ATTRIBUTE_FORMAT_U8:

			broadcastMask = (qword)s_0A000B000C000D00;

			for(uint32_t vertIndexOffset=0; vertIndexOffset<maxVertIndexOffset; vertIndexOffset++)

			{

				qword pAttrDests = si_a(pAttrDestsBase, (qword)spu_splats(vertIndexOffset<<4));

				qword srcOffset = si_mpya(si_from_int(vertIndexOffset),vertexStride, attribByteOffset);

				const qword quadOffset = si_andi(srcOffset, 0xF);

				const qword extractMask = si_a((qword)s_ABCD,

					si_shufb(quadOffset, srcOffset, s_DDDDDDDDDDDDDDDD));

				const qword extractAndBroadcastMask = si_shufb(extractMask, extractMask, broadcastMask);

				const int straddlesQuad = si_to_int(si_cgti(si_a(quadOffset, attribSize), 16));



				// preamble

				qword q1a = si_lqx(pAttrSrc1, srcOffset);

				qword q1b = si_lqx(pAttrSrc2, srcOffset);

				qword q2a = si_lqx(pAttrSrc3, srcOffset);

				qword q2b = si_lqx(pAttrSrc4, srcOffset);

				qword q3a = si_lqx(pAttrSrc5, srcOffset);

				qword q3b = si_lqx(pAttrSrc6, srcOffset);

				qword q4a = si_lqx(pAttrSrc7, srcOffset);

				qword q4b = si_lqx(pAttrSrc8, srcOffset);

				srcOffset = si_a(srcOffset, srcOffsetsInc);

				if (straddlesQuad) // slower loop with two loads per vertex

				{

					do

					{

						// extract and broadcast

						qword in1 = si_shufb(q1a, q1b, extractAndBroadcastMask);

						qword in2 = si_shufb(q2a, q2b, extractAndBroadcastMask);

						qword in3 = si_shufb(q3a, q3b, extractAndBroadcastMask);

						qword in4 = si_shufb(q4a, q4b, extractAndBroadcastMask);

						// convert	

						qword float1 = si_cuflt(in1, 16);

						qword float2 = si_cuflt(in2, 16);

						qword float3 = si_cuflt(in3, 16);

						qword float4 = si_cuflt(in4, 16);

						// store

						isDestBad = si_cgt(pAttrDests, pUniformEnd);

						qword pFinalDest1 = si_selb(pAttrDests, pSafeBuffer, isDestBad);

						qword pFinalDest2 = si_rotqbyi(pFinalDest1, 4);

						qword pFinalDest3 = si_rotqbyi(pFinalDest1, 8);

						qword pFinalDest4 = si_rotqbyi(pFinalDest1, 12);

						si_stqd(float1, pFinalDest1, 0x00);

						si_stqd(float2, pFinalDest2, 0x00);

						si_stqd(float3, pFinalDest3, 0x00);

						si_stqd(float4, pFinalDest4, 0x00);

						// load

						q1a = si_lqx(pAttrSrc1, srcOffset);

						q1b = si_lqx(pAttrSrc2, srcOffset);

						q2a = si_lqx(pAttrSrc3, srcOffset);

						q2b = si_lqx(pAttrSrc4, srcOffset);

						q3a = si_lqx(pAttrSrc5, srcOffset);

						q3b = si_lqx(pAttrSrc6, srcOffset);

						q4a = si_lqx(pAttrSrc7, srcOffset);

						q4b = si_lqx(pAttrSrc8, srcOffset);

						// increment

						srcOffset = si_a(srcOffset, srcOffsetsInc);

						pAttrDests = si_a(pAttrDests, pAttrDestsInc);

					}

					while(si_to_int(isDestBad) == 0);

				}

				else // faster loop with one load per vertex

				{

					do

					{

						// extract and broadcast

						qword in1 = si_shufb(q1a, q1a, extractAndBroadcastMask);

						qword in2 = si_shufb(q2a, q2a, extractAndBroadcastMask);

						qword in3 = si_shufb(q3a, q3a, extractAndBroadcastMask);

						qword in4 = si_shufb(q4a, q4a, extractAndBroadcastMask);

						// convert	

						qword float1 = si_cuflt(in1, 16);

						qword float2 = si_cuflt(in2, 16);

						qword float3 = si_cuflt(in3, 16);

						qword float4 = si_cuflt(in4, 16);

						// store

						isDestBad = si_cgt(pAttrDests, pUniformEnd);

						qword pFinalDest1 = si_selb(pAttrDests, pSafeBuffer, isDestBad);

						qword pFinalDest2 = si_rotqbyi(pFinalDest1, 4);

						qword pFinalDest3 = si_rotqbyi(pFinalDest1, 8);

						qword pFinalDest4 = si_rotqbyi(pFinalDest1, 12);

						si_stqd(float1, pFinalDest1, 0x00);

						si_stqd(float2, pFinalDest2, 0x00);

						si_stqd(float3, pFinalDest3, 0x00);

						si_stqd(float4, pFinalDest4, 0x00);

						// load

						q1a = si_lqx(pAttrSrc1, srcOffset);

						q2a = si_lqx(pAttrSrc3, srcOffset);

						q3a = si_lqx(pAttrSrc5, srcOffset);

						q4a = si_lqx(pAttrSrc7, srcOffset);

						// increment

						srcOffset = si_a(srcOffset, srcOffsetsInc);

						pAttrDests = si_a(pAttrDests, pAttrDestsInc);

					}

					while(si_to_int(isDestBad) == 0);

				}

			}

			break;





		case EDGE_GEOM_ATTRIBUTE_FORMAT_U8N:

			broadcastMask = (qword)s_0A000B000C000D00;

			for(uint32_t vertIndexOffset=0; vertIndexOffset<maxVertIndexOffset; vertIndexOffset++)

			{

				qword pAttrDests = si_a(pAttrDestsBase, (qword)spu_splats(vertIndexOffset<<4));

				qword srcOffset = si_mpya(si_from_int(vertIndexOffset),vertexStride, attribByteOffset);

				const qword quadOffset = si_andi(srcOffset, 0xF);

				const qword extractMask = si_a((qword)s_ABCD,

					si_shufb(quadOffset, srcOffset, s_DDDDDDDDDDDDDDDD));

				const qword extractAndBroadcastMask = si_shufb(extractMask, extractMask, broadcastMask);

				const int straddlesQuad = si_to_int(si_cgti(si_a(quadOffset, attribSize), 16));

				// preamble

				qword q1a = si_lqx(pAttrSrc1, srcOffset);

				qword q1b = si_lqx(pAttrSrc2, srcOffset);

				qword q2a = si_lqx(pAttrSrc3, srcOffset);

				qword q2b = si_lqx(pAttrSrc4, srcOffset);

				qword q3a = si_lqx(pAttrSrc5, srcOffset);

				qword q3b = si_lqx(pAttrSrc6, srcOffset);

				qword q4a = si_lqx(pAttrSrc7, srcOffset);

				qword q4b = si_lqx(pAttrSrc8, srcOffset);

				qword in1 = si_shufb(q1a, q1b, extractAndBroadcastMask);

				qword in2 = si_shufb(q2a, q2b, extractAndBroadcastMask);

				qword in3 = si_shufb(q3a, q3b, extractAndBroadcastMask);

				qword in4 = si_shufb(q4a, q4b, extractAndBroadcastMask);

				srcOffset = si_a(srcOffset, srcOffsetsInc);

				if (straddlesQuad) // slower loop with two loads per vertex

				{

					do

					{

						// convert	

						qword float1 = si_fma(si_cuflt(in1, 16), u8nScale, u8nBias);

						qword float2 = si_fma(si_cuflt(in2, 16), u8nScale, u8nBias);

						qword float3 = si_fma(si_cuflt(in3, 16), u8nScale, u8nBias);

						qword float4 = si_fma(si_cuflt(in4, 16), u8nScale, u8nBias);

						// store

						isDestBad = si_cgt(pAttrDests, pUniformEnd);

						qword pFinalDest1 = si_selb(pAttrDests, pSafeBuffer, isDestBad);

						qword pFinalDest2 = si_rotqbyi(pFinalDest1, 4);

						qword pFinalDest3 = si_rotqbyi(pFinalDest1, 8);

						qword pFinalDest4 = si_rotqbyi(pFinalDest1, 12);

						si_stqd(float1, pFinalDest1, 0x00);

						si_stqd(float2, pFinalDest2, 0x00);

						si_stqd(float3, pFinalDest3, 0x00);

						si_stqd(float4, pFinalDest4, 0x00);

						// load

						qword q1a = si_lqx(pAttrSrc1, srcOffset);

						qword q1b = si_lqx(pAttrSrc2, srcOffset);

						qword q2a = si_lqx(pAttrSrc3, srcOffset);

						qword q2b = si_lqx(pAttrSrc4, srcOffset);

						qword q3a = si_lqx(pAttrSrc5, srcOffset);

						qword q3b = si_lqx(pAttrSrc6, srcOffset);

						qword q4a = si_lqx(pAttrSrc7, srcOffset);

						qword q4b = si_lqx(pAttrSrc8, srcOffset);

						// extract and broadcast

						in1 = si_shufb(q1a, q1b, extractAndBroadcastMask);

						in2 = si_shufb(q2a, q2b, extractAndBroadcastMask);

						in3 = si_shufb(q3a, q3b, extractAndBroadcastMask);

						in4 = si_shufb(q4a, q4b, extractAndBroadcastMask);

						// increment

						srcOffset = si_a(srcOffset, srcOffsetsInc);

						pAttrDests = si_a(pAttrDests, pAttrDestsInc);

					}

					while(si_to_int(isDestBad) == 0);

				}

				else // faster loop with one load per vertex

				{

					do

					{

						// convert	

						qword float1 = si_fma(si_cuflt(in1, 16), u8nScale, u8nBias);

						qword float2 = si_fma(si_cuflt(in2, 16), u8nScale, u8nBias);

						qword float3 = si_fma(si_cuflt(in3, 16), u8nScale, u8nBias);

						qword float4 = si_fma(si_cuflt(in4, 16), u8nScale, u8nBias);

						// store

						isDestBad = si_cgt(pAttrDests, pUniformEnd);

						qword pFinalDest1 = si_selb(pAttrDests, pSafeBuffer, isDestBad);

						qword pFinalDest2 = si_rotqbyi(pFinalDest1, 4);

						qword pFinalDest3 = si_rotqbyi(pFinalDest1, 8);

						qword pFinalDest4 = si_rotqbyi(pFinalDest1, 12);

						si_stqd(float1, pFinalDest1, 0x00);

						si_stqd(float2, pFinalDest2, 0x00);

						si_stqd(float3, pFinalDest3, 0x00);

						si_stqd(float4, pFinalDest4, 0x00);

						// load

						qword q1a = si_lqx(pAttrSrc1, srcOffset);

						qword q2a = si_lqx(pAttrSrc3, srcOffset);

						qword q3a = si_lqx(pAttrSrc5, srcOffset);

						qword q4a = si_lqx(pAttrSrc7, srcOffset);

						// extract and broadcast

						in1 = si_shufb(q1a, q1a, extractAndBroadcastMask);

						in2 = si_shufb(q2a, q2a, extractAndBroadcastMask);

						in3 = si_shufb(q3a, q3a, extractAndBroadcastMask);

						in4 = si_shufb(q4a, q4a, extractAndBroadcastMask);

						// increment

						srcOffset = si_a(srcOffset, srcOffsetsInc);

						pAttrDests = si_a(pAttrDests, pAttrDestsInc);

					}

					while(si_to_int(isDestBad) == 0);

				}

			}

			break;





		case EDGE_GEOM_ATTRIBUTE_FORMAT_X11Y11Z10N:

			broadcastMask = (qword)s_CD00BC00AB000000;

			// The attribute count of X11Y11Z10N attributes is usually

			// 1, which is what the RSX expects. However, for

			// decompression purposes (especially the

			// position-specific masking code, way below), we need it

			// to be 3.

			attrComponentCount = 3;

			for(uint32_t vertIndexOffset=0; vertIndexOffset<maxVertIndexOffset; vertIndexOffset++)

			{

				qword pAttrDests = si_a(pAttrDestsBase, (qword)spu_splats(vertIndexOffset<<4));

				qword srcOffset = si_mpya(si_from_int(vertIndexOffset),vertexStride, attribByteOffset);

				const qword quadOffset = si_andi(srcOffset, 0xF);

				const qword extractMask = si_a((qword)s_ABCD,

					si_shufb(quadOffset, srcOffset, s_DDDDDDDDDDDDDDDD));

				const qword extractAndBroadcastMask = si_shufb(extractMask, extractMask, broadcastMask);

				// preamble

				qword q1a = si_lqx(pAttrSrc1, srcOffset);

				qword q1b = si_lqx(pAttrSrc2, srcOffset);

				qword q2a = si_lqx(pAttrSrc3, srcOffset);

				qword q2b = si_lqx(pAttrSrc4, srcOffset);

				qword q3a = si_lqx(pAttrSrc5, srcOffset);

				qword q3b = si_lqx(pAttrSrc6, srcOffset);

				qword q4a = si_lqx(pAttrSrc7, srcOffset);

				qword q4b = si_lqx(pAttrSrc8, srcOffset);

				qword in1 = si_shufb(q1a, q1b, extractAndBroadcastMask);

				qword in2 = si_shufb(q2a, q2b, extractAndBroadcastMask);

				qword in3 = si_shufb(q3a, q3b, extractAndBroadcastMask);

				qword in4 = si_shufb(q4a, q4b, extractAndBroadcastMask);

				qword shift1 = si_shl(in1, x11y11z10ShiftValues);

				qword shift2 = si_shl(in2, x11y11z10ShiftValues);

				qword shift3 = si_shl(in3, x11y11z10ShiftValues);

				qword shift4 = si_shl(in4, x11y11z10ShiftValues);

				srcOffset = si_a(srcOffset, srcOffsetsInc);

				do

				{

					// convert (part 2)

					qword bits1 = si_and(shift1, x11y11z10BitMask);

					qword bits2 = si_and(shift2, x11y11z10BitMask);

					qword bits3 = si_and(shift3, x11y11z10BitMask);

					qword bits4 = si_and(shift4, x11y11z10BitMask);

					qword float1 = si_csflt(bits1, 31);

					qword float2 = si_csflt(bits2, 31);

					qword float3 = si_csflt(bits3, 31);

					qword float4 = si_csflt(bits4, 31);

					qword out1 = si_fma(float1, x11y11z10Scale, x11y11z10Bias);

					qword out2 = si_fma(float2, x11y11z10Scale, x11y11z10Bias);

					qword out3 = si_fma(float3, x11y11z10Scale, x11y11z10Bias);

					qword out4 = si_fma(float4, x11y11z10Scale, x11y11z10Bias);

					// store

					isDestBad = si_cgt(pAttrDests, pUniformEnd);

					qword pFinalDest1 = si_selb(pAttrDests, pSafeBuffer, isDestBad);

					qword pFinalDest2 = si_rotqbyi(pFinalDest1, 4);

					qword pFinalDest3 = si_rotqbyi(pFinalDest1, 8);

					qword pFinalDest4 = si_rotqbyi(pFinalDest1, 12);

					si_stqd(out1, pFinalDest1, 0x00);

					si_stqd(out2, pFinalDest2, 0x00);

					si_stqd(out3, pFinalDest3, 0x00);

					si_stqd(out4, pFinalDest4, 0x00);

					// load

					qword q1a = si_lqx(pAttrSrc1, srcOffset);

					qword q1b = si_lqx(pAttrSrc2, srcOffset);

					qword q2a = si_lqx(pAttrSrc3, srcOffset);

					qword q2b = si_lqx(pAttrSrc4, srcOffset);

					qword q3a = si_lqx(pAttrSrc5, srcOffset);

					qword q3b = si_lqx(pAttrSrc6, srcOffset);

					qword q4a = si_lqx(pAttrSrc7, srcOffset);

					qword q4b = si_lqx(pAttrSrc8, srcOffset);

					// extract and broadcast

					qword in1 = si_shufb(q1a, q1b, extractAndBroadcastMask);

					qword in2 = si_shufb(q2a, q2b, extractAndBroadcastMask);

					qword in3 = si_shufb(q3a, q3b, extractAndBroadcastMask);

					qword in4 = si_shufb(q4a, q4b, extractAndBroadcastMask);

					// convert (part 1)

					shift1 = si_shl(in1, x11y11z10ShiftValues);

					shift2 = si_shl(in2, x11y11z10ShiftValues);

					shift3 = si_shl(in3, x11y11z10ShiftValues);

					shift4 = si_shl(in4, x11y11z10ShiftValues);

					// increment

					srcOffset = si_a(srcOffset, srcOffsetsInc);

					pAttrDests = si_a(pAttrDests, pAttrDestsInc);

				}

				while(si_to_int(isDestBad) == 0);

			}

			break;





		case EDGE_GEOM_ATTRIBUTE_FORMAT_FIXED_POINT:

			{

				const qword fixedBlockOffset = si_shufb(attrDesc, attrDesc, s_000G000G000G000G);

				qword fixedBlock = si_a(si_from_ptr(streamDesc->blocks), fixedBlockOffset);

				fixedBlock = si_rotqby(si_lqd(fixedBlock, 0), fixedBlock);

				const qword fixedIntBits	  = si_shufb(fixedBlock, fixedBlock, s_000A000C000E000G);

				const qword fixedMantissaBits = si_shufb(fixedBlock, fixedBlock, s_000B000D000F000H);

				

				const qword totalBits = si_a(fixedIntBits, fixedMantissaBits);

				const qword totalUnusedBits = si_sfi(totalBits, 32);

				const qword rshifts = si_sfi(totalUnusedBits, 0);

				const qword garbageMask = si_rotm(mask_FFFF, rshifts);

				

				const qword rshift1 = si_selb( si_sfi(totalUnusedBits, 7),

					rshifts, si_il(7));

				const qword rshift2 = si_shlqbyi(rshift1, 4);

				const qword rshift3 = si_shlqbyi(rshift1, 8);

				const qword fixedIntShift = si_sfi(fixedMantissaBits, 0);

				const qword fixedMantissaShift = si_sfi(fixedMantissaBits, 32);

				

				const qword valueOffset = si_lqd(pFixedPointOffsets, 0x00);

				pFixedPointOffsets = si_ai(pFixedPointOffsets, 0x10);



				broadcastMask = (qword)s_ABCD;

				for(uint32_t vertIndexOffset=0; vertIndexOffset<maxVertIndexOffset; vertIndexOffset++)

				{

					qword pAttrDests = si_a(pAttrDestsBase, (qword)spu_splats(vertIndexOffset<<4));

					qword srcOffset = si_mpya(si_from_int(vertIndexOffset),vertexStride,

						attribByteOffset);

					const qword quadOffset = si_andi(srcOffset, 0xF);

					const qword extractMask = si_a((qword)s_ABCD,

						si_shufb(quadOffset, srcOffset, s_DDDDDDDDDDDDDDDD));

					const qword extractAndBroadcastMask = si_shufb(extractMask, extractMask,

						broadcastMask);

					

					// preamble

					qword q1a = si_lqx(pAttrSrc1, srcOffset);

					qword q1b = si_lqx(pAttrSrc2, srcOffset);

					qword q2a = si_lqx(pAttrSrc3, srcOffset);

					qword q2b = si_lqx(pAttrSrc4, srcOffset);

					qword q3a = si_lqx(pAttrSrc5, srcOffset);

					qword q3b = si_lqx(pAttrSrc6, srcOffset);

					qword q4a = si_lqx(pAttrSrc7, srcOffset);

					qword q4b = si_lqx(pAttrSrc8, srcOffset);

					qword in1 = si_shufb(q1a, q1b, extractAndBroadcastMask);

					qword in2 = si_shufb(q2a, q2b, extractAndBroadcastMask);

					qword in3 = si_shufb(q3a, q3b, extractAndBroadcastMask);

					qword in4 = si_shufb(q4a, q4b, extractAndBroadcastMask);

					qword word1_1 = si_rotqmbi(si_rotqmbybi(in1, rshift1), rshift1);

					qword word2_1 = si_rotqmbi(si_rotqmbybi(in2, rshift1), rshift1);

					qword word3_1 = si_rotqmbi(si_rotqmbybi(in3, rshift1), rshift1);

					qword word4_1 = si_rotqmbi(si_rotqmbybi(in4, rshift1), rshift1);

					qword word1_2 = si_rotqmbi(si_rotqmbybi(word1_1, rshift2), rshift2);

					qword word2_2 = si_rotqmbi(si_rotqmbybi(word2_1, rshift2), rshift2);

					qword word3_2 = si_rotqmbi(si_rotqmbybi(word3_1, rshift2), rshift2);

					qword word4_2 = si_rotqmbi(si_rotqmbybi(word4_1, rshift2), rshift2);

					qword word1_3 = si_rotqmbi(si_rotqmbybi(word1_2, rshift3), rshift3);

					qword word2_3 = si_rotqmbi(si_rotqmbybi(word2_2, rshift3), rshift3);

					qword word3_3 = si_rotqmbi(si_rotqmbybi(word3_2, rshift3), rshift3);

					qword word4_3 = si_rotqmbi(si_rotqmbybi(word4_2, rshift3), rshift3);

					qword word1_4 = si_rotm(word1_3, rshifts);

					qword word2_4 = si_rotm(word2_3, rshifts);

					qword word3_4 = si_rotm(word3_3, rshifts);

					qword word4_4 = si_rotm(word4_3, rshifts);

					srcOffset = si_a(srcOffset, srcOffsetsInc);

					

					do

					{

						qword fixed1 = si_selb( si_selb(word1_1, word1_2, mask_0F0F),

							si_selb(word1_3, word1_4, mask_0F0F), mask_00FF);

						qword fixed2 = si_selb( si_selb(word2_1, word2_2, mask_0F0F),

							si_selb(word2_3, word2_4, mask_0F0F), mask_00FF);

						qword fixed3 = si_selb( si_selb(word3_1, word3_2, mask_0F0F),

							si_selb(word3_3, word3_4, mask_0F0F), mask_00FF);

						qword fixed4 = si_selb( si_selb(word4_1, word4_2, mask_0F0F),

							si_selb(word4_3, word4_4, mask_0F0F), mask_00FF);

						// Clear the unused bits of each word, preserving only the bits that

						// interest us.  Then add the fixed-point offsets for this segment,

						// to bring each component back into its original range.

						fixed1 = si_a(si_and(fixed1, garbageMask), valueOffset);

						fixed2 = si_a(si_and(fixed2, garbageMask), valueOffset);

						fixed3 = si_a(si_and(fixed3, garbageMask), valueOffset);

						fixed4 = si_a(si_and(fixed4, garbageMask), valueOffset);

						// Shift the mantissa bits to the left and the

						// int bits to the right, convert each one to

						// a float separately, and add the results.

						// Don't forget to add the fixed value offset

						// back in *after* the fixed-to-float

						// conversion!

						qword fixedInt1 = si_csflt(si_rotma(fixed1, fixedIntShift), 0);

						qword fixedInt2 = si_csflt(si_rotma(fixed2, fixedIntShift), 0);

						qword fixedInt3 = si_csflt(si_rotma(fixed3, fixedIntShift), 0);

						qword fixedInt4 = si_csflt(si_rotma(fixed4, fixedIntShift), 0);

						qword fixedMantissa1 = si_cuflt(si_shl(fixed1, fixedMantissaShift), 32);

						qword fixedMantissa2 = si_cuflt(si_shl(fixed2, fixedMantissaShift), 32);

						qword fixedMantissa3 = si_cuflt(si_shl(fixed3, fixedMantissaShift), 32);

						qword fixedMantissa4 = si_cuflt(si_shl(fixed4, fixedMantissaShift), 32);

						qword out1 = si_fa(fixedInt1, fixedMantissa1);

						qword out2 = si_fa(fixedInt2, fixedMantissa2);

						qword out3 = si_fa(fixedInt3, fixedMantissa3);

						qword out4 = si_fa(fixedInt4, fixedMantissa4);

						// store

						isDestBad = si_cgt(pAttrDests, pUniformEnd);

						qword pFinalDest1 = si_selb(pAttrDests, pSafeBuffer, isDestBad);

						qword pFinalDest2 = si_rotqbyi(pFinalDest1, 4);

						qword pFinalDest3 = si_rotqbyi(pFinalDest1, 8);

						qword pFinalDest4 = si_rotqbyi(pFinalDest1, 12);

						si_stqd(out1, pFinalDest1, 0x00);

						si_stqd(out2, pFinalDest2, 0x00);

						si_stqd(out3, pFinalDest3, 0x00);

						si_stqd(out4, pFinalDest4, 0x00);

						// load

						q1a = si_lqx(pAttrSrc1, srcOffset);

						q1b = si_lqx(pAttrSrc2, srcOffset);

						q2a = si_lqx(pAttrSrc3, srcOffset);

						q2b = si_lqx(pAttrSrc4, srcOffset);

						q3a = si_lqx(pAttrSrc5, srcOffset);

						q3b = si_lqx(pAttrSrc6, srcOffset);

						q4a = si_lqx(pAttrSrc7, srcOffset);

						q4b = si_lqx(pAttrSrc8, srcOffset);

						// extract and broadcast

						in1 = si_shufb(q1a, q1b, extractAndBroadcastMask);

						in2 = si_shufb(q2a, q2b, extractAndBroadcastMask);

						in3 = si_shufb(q3a, q3b, extractAndBroadcastMask);

						in4 = si_shufb(q4a, q4b, extractAndBroadcastMask);

						// convert	

						// Right-shift each word to be right-aligned in its

						// own quadword, and then combine the

						// four words from each quad back into one

						// quad again.

						word1_1 = si_rotqmbi(si_rotqmbybi(in1, rshift1), rshift1);

						word2_1 = si_rotqmbi(si_rotqmbybi(in2, rshift1), rshift1);

						word3_1 = si_rotqmbi(si_rotqmbybi(in3, rshift1), rshift1);

						word4_1 = si_rotqmbi(si_rotqmbybi(in4, rshift1), rshift1);

						word1_2 = si_rotqmbi(si_rotqmbybi(word1_1, rshift2), rshift2);

						word2_2 = si_rotqmbi(si_rotqmbybi(word2_1, rshift2), rshift2);

						word3_2 = si_rotqmbi(si_rotqmbybi(word3_1, rshift2), rshift2);

						word4_2 = si_rotqmbi(si_rotqmbybi(word4_1, rshift2), rshift2);

						word1_3 = si_rotqmbi(si_rotqmbybi(word1_2, rshift3), rshift3);

						word2_3 = si_rotqmbi(si_rotqmbybi(word2_2, rshift3), rshift3);

						word3_3 = si_rotqmbi(si_rotqmbybi(word3_2, rshift3), rshift3);

						word4_3 = si_rotqmbi(si_rotqmbybi(word4_2, rshift3), rshift3);

						word1_4 = si_rotm(word1_3, rshifts);

						word2_4 = si_rotm(word2_3, rshifts);

						word3_4 = si_rotm(word3_3, rshifts);

						word4_4 = si_rotm(word4_3, rshifts);

						// increment

						srcOffset = si_a(srcOffset, srcOffsetsInc);

						pAttrDests = si_a(pAttrDests, pAttrDestsInc);

					}

					while(si_to_int(isDestBad) == 0);

				}

			}

			break;





		case EDGE_GEOM_ATTRIBUTE_FORMAT_UNIT_VECTOR:

			broadcastMask = (qword)s_00C000AB00BC0000;

			for(uint32_t vertIndexOffset=0; vertIndexOffset<maxVertIndexOffset; vertIndexOffset++)

			{

				qword pAttrDests = si_a(pAttrDestsBase, (qword)spu_splats(vertIndexOffset<<4));

				qword srcOffset = si_mpya(si_from_int(vertIndexOffset),vertexStride, attribByteOffset);

				const qword quadOffset = si_andi(srcOffset, 0xF);

				const qword extractMask = si_a((qword)s_ABCD,

					si_shufb(quadOffset, srcOffset, s_DDDDDDDDDDDDDDDD));

				const qword extractAndBroadcastMask = si_shufb(extractMask, extractMask, broadcastMask);

				// preamble -- iteration 1, stages 1 and 2

				qword q1a = si_lqx(pAttrSrc1, srcOffset);

				qword q1b = si_lqx(pAttrSrc2, srcOffset);

				qword q2a = si_lqx(pAttrSrc3, srcOffset);

				qword q2b = si_lqx(pAttrSrc4, srcOffset);

				qword q3a = si_lqx(pAttrSrc5, srcOffset);

				qword q3b = si_lqx(pAttrSrc6, srcOffset);

				qword q4a = si_lqx(pAttrSrc7, srcOffset);

				qword q4b = si_lqx(pAttrSrc8, srcOffset);

				qword in1 = si_shufb(q1a, q1b, extractAndBroadcastMask);

				qword in2 = si_shufb(q2a, q2b, extractAndBroadcastMask);

				qword in3 = si_shufb(q3a, q3b, extractAndBroadcastMask);

				qword in4 = si_shufb(q4a, q4b, extractAndBroadcastMask);

				qword int1 = si_selb(uvFormatMask, si_rotm(in1, uvRotValues), uvFormatSelect);

				qword int2 = si_selb(uvFormatMask, si_rotm(in2, uvRotValues), uvFormatSelect);

				qword int3 = si_selb(uvFormatMask, si_rotm(in3, uvRotValues), uvFormatSelect);

				qword int4 = si_selb(uvFormatMask, si_rotm(in4, uvRotValues), uvFormatSelect);

				qword float1 = si_fma(si_shufb(int1, int2, (qword)s_BCbc), uvScale, uvBias);

				qword float2 = si_fma(si_shufb(int3, int4, (qword)s_BCbc), uvScale, uvBias);

				qword square1 = si_fm(float1, float1);

				qword square2 = si_fm(float2, float2);

				qword toSqrt1 = si_fs(k1_0f, si_fa(square1, si_shlqbyi(square1, 4)));

				qword toSqrt2 = si_fs(k1_0f, si_fa(square2, si_shlqbyi(square2, 4)));

				qword complexMask1 = si_lqx(pUvMaskTable, int1);

				qword complexMask2 = si_lqx(pUvMaskTable, int2);

				qword complexMask3 = si_lqx(pUvMaskTable, int3);

				qword complexMask4 = si_lqx(pUvMaskTable, int4);

				qword toOut1 = si_selb(uvOutputMask, complexMask1, uvOutputSelect);

				qword toOut2 = si_selb(uvOutputMask, complexMask2, uvOutputSelect);

				qword toOut3 = si_selb(uvOutputMask, complexMask3, uvOutputSelect);

				qword toOut4 = si_selb(uvOutputMask, complexMask4, uvOutputSelect);

				qword rsqrt1 = si_fi(toSqrt1, si_frsqest(toSqrt1));

				qword rsqrt2 = si_fi(toSqrt2, si_frsqest(toSqrt2));

				qword float1_ = float1;

				qword float2_ = float2;

				srcOffset = si_a(srcOffset, srcOffsetsInc);

				// preamble -- iteration 2, stage 1

				q1a = si_lqx(pAttrSrc1, srcOffset);

				q1b = si_lqx(pAttrSrc2, srcOffset);

				q2a = si_lqx(pAttrSrc3, srcOffset);

				q2b = si_lqx(pAttrSrc4, srcOffset);

				q3a = si_lqx(pAttrSrc5, srcOffset);

				q3b = si_lqx(pAttrSrc6, srcOffset);

				q4a = si_lqx(pAttrSrc7, srcOffset);

				q4b = si_lqx(pAttrSrc8, srcOffset);

				in1 = si_shufb(q1a, q1b, extractAndBroadcastMask);

				in2 = si_shufb(q2a, q2b, extractAndBroadcastMask);

				in3 = si_shufb(q3a, q3b, extractAndBroadcastMask);

				in4 = si_shufb(q4a, q4b, extractAndBroadcastMask);

				int1 = si_selb(uvFormatMask, si_rotm(in1, uvRotValues), uvFormatSelect);

				int2 = si_selb(uvFormatMask, si_rotm(in2, uvRotValues), uvFormatSelect);

				int3 = si_selb(uvFormatMask, si_rotm(in3, uvRotValues), uvFormatSelect);

				int4 = si_selb(uvFormatMask, si_rotm(in4, uvRotValues), uvFormatSelect);

				float1 = si_fma(si_shufb(int1, int2, (qword)s_BCbc), uvScale, uvBias);

				float2 = si_fma(si_shufb(int3, int4, (qword)s_BCbc), uvScale, uvBias);

				srcOffset = si_a(srcOffset, srcOffsetsInc);

				

				do

				{

					//// stage 3

					qword sqrt1 = si_fm(toSqrt1, rsqrt1);

					qword sqrt2 = si_fm(toSqrt2, rsqrt2);

					qword toOut2_ = si_a(toOut2, uvShuffleAdjust);

					qword toOut4_ = si_a(toOut4, uvShuffleAdjust);

					qword out1 = si_shufb(si_selb(sqrt1, complexMask1, uvSelect), float1_, toOut1);

					qword out2 = si_shufb(si_selb(sqrt1, complexMask2, uvSelect), float1_, toOut2_);

					qword out3 = si_shufb(si_selb(sqrt2, complexMask3, uvSelect), float2_, toOut3);

					qword out4 = si_shufb(si_selb(sqrt2, complexMask4, uvSelect), float2_, toOut4_);

					// store

					isDestBad = si_cgt(pAttrDests, pUniformEnd);

					qword pFinalDest1 = si_selb(pAttrDests, pSafeBuffer, isDestBad);

					qword pFinalDest2 = si_rotqbyi(pFinalDest1, 4);

					qword pFinalDest3 = si_rotqbyi(pFinalDest1, 8);

					qword pFinalDest4 = si_rotqbyi(pFinalDest1, 12);

					si_stqd(out1, pFinalDest1, 0x00);

					si_stqd(out2, pFinalDest2, 0x00);

					si_stqd(out3, pFinalDest3, 0x00);

					si_stqd(out4, pFinalDest4, 0x00);



					//// stage 2

					square1 = si_fm(float1, float1);

					square2 = si_fm(float2, float2);

					toSqrt1 = si_fs(k1_0f, si_fa(square1, si_shlqbyi(square1, 4)));

					toSqrt2 = si_fs(k1_0f, si_fa(square2, si_shlqbyi(square2, 4)));

					complexMask1 = si_lqx(pUvMaskTable, int1);

					complexMask2 = si_lqx(pUvMaskTable, int2);

					complexMask3 = si_lqx(pUvMaskTable, int3);

					complexMask4 = si_lqx(pUvMaskTable, int4);

					toOut1 = si_selb(uvOutputMask, complexMask1, uvOutputSelect);

					toOut2 = si_selb(uvOutputMask, complexMask2, uvOutputSelect);

					toOut3 = si_selb(uvOutputMask, complexMask3, uvOutputSelect);

					toOut4 = si_selb(uvOutputMask, complexMask4, uvOutputSelect);

					rsqrt1 = si_fi(toSqrt1, si_frsqest(toSqrt1));

					rsqrt2 = si_fi(toSqrt2, si_frsqest(toSqrt2));

					float1_ = float1;

					float2_ = float2;



					//// stage 1

					// load

					q1a = si_lqx(pAttrSrc1, srcOffset);

					q1b = si_lqx(pAttrSrc2, srcOffset);

					q2a = si_lqx(pAttrSrc3, srcOffset);

					q2b = si_lqx(pAttrSrc4, srcOffset);

					q3a = si_lqx(pAttrSrc5, srcOffset);

					q3b = si_lqx(pAttrSrc6, srcOffset);

					q4a = si_lqx(pAttrSrc7, srcOffset);

					q4b = si_lqx(pAttrSrc8, srcOffset);

					// extract and broadcast

					in1 = si_shufb(q1a, q1b, extractAndBroadcastMask);

					in2 = si_shufb(q2a, q2b, extractAndBroadcastMask);

					in3 = si_shufb(q3a, q3b, extractAndBroadcastMask);

					in4 = si_shufb(q4a, q4b, extractAndBroadcastMask);

					// convert	

					int1 = si_selb(uvFormatMask, si_rotm(in1, uvRotValues), uvFormatSelect);

					int2 = si_selb(uvFormatMask, si_rotm(in2, uvRotValues), uvFormatSelect);

					int3 = si_selb(uvFormatMask, si_rotm(in3, uvRotValues), uvFormatSelect);

					int4 = si_selb(uvFormatMask, si_rotm(in4, uvRotValues), uvFormatSelect);

					float1 = si_fma(si_shufb(int1, int2, (qword)s_BCbc), uvScale, uvBias);

					float2 = si_fma(si_shufb(int3, int4, (qword)s_BCbc), uvScale, uvBias);



					// increment

					srcOffset = si_a(srcOffset, srcOffsetsInc);

					pAttrDests = si_a(pAttrDests, pAttrDestsInc);

				}

				while(si_to_int(isDestBad) == 0);

			}

			break;



		default:

			break;

		}



		// If the position table contained fewer than 4 components, we

		// need to mask in specific values for the missing components,

		// or else the results could be unexpected.

		if (attrId == EDGE_GEOM_ATTRIBUTE_ID_POSITION && attrComponentCount < 4)

		{

			const qword missingComponents = si_to_int(isBlending) ? si_il(0) : (qword)(vec_float4){0,0,0,1.0f};

			const qword missingComponentMask = si_fsm(si_from_int(0xF >> attrComponentCount));

			const qword pPos1 = si_a(uniformTableSize, pDestTable);

			const qword pPos2 = si_ai(pPos1, 0x10);

			const qword pPos3 = si_ai(pPos1, 0x20);

			const qword pPos4 = si_ai(pPos1, 0x30);

			qword inOffset = si_sfi(uniformTableSize, 0);

			qword outOffset = inOffset;

				

			qword in1 = si_lqx(pPos1, inOffset);

			qword in2 = si_lqx(pPos2, inOffset);

			qword in3 = si_lqx(pPos3, inOffset);

			qword in4 = si_lqx(pPos4, inOffset);

			inOffset = si_ai(inOffset, 0x40);

			do

			{

				qword out1 = si_selb(in1, missingComponents, missingComponentMask);

				qword out2 = si_selb(in2, missingComponents, missingComponentMask);

				qword out3 = si_selb(in3, missingComponents, missingComponentMask);

				qword out4 = si_selb(in4, missingComponents, missingComponentMask);

				in1		= si_lqx(pPos1, inOffset);

				in2		= si_lqx(pPos2, inOffset);

				in3		= si_lqx(pPos3, inOffset);

				in4		= si_lqx(pPos4, inOffset);

				inOffset = si_ai(inOffset, 0x40);

				si_stqx(out1, pPos1, outOffset);

				si_stqx(out2, pPos2, outOffset);

				si_stqx(out3, pPos3, outOffset);

				si_stqx(out4, pPos4, outOffset);

				outOffset = si_ai(outOffset, 0x40);

			}

			while(si_to_int(outOffset) != 0);

		}



		if (si_to_int(isBlending) != 0)

		{

			// The unused entries in the final block of four deltas

			// must be reset to zero, so that they can be safely

			// processed in the loop below without corrupting any

			// data. It is also necessary that the corresponding

			// entries in the blended vertex table be zero; this is

			// taken care of when that table is decompressed.

			qword pUniformEndMinusOne = si_ai(pUniformEnd, -1);

			qword pInvalidDelta1 = si_a(pDestTable, si_shli(actualVertexCount,4));

			qword pInvalidDelta2 = si_ai(pInvalidDelta1, 0x10);

			qword pInvalidDelta3 = si_ai(pInvalidDelta1, 0x20);

			pInvalidDelta1 = si_selb(pInvalidDelta1, pSafeBuffer,

				si_cgt(pInvalidDelta1, pUniformEndMinusOne));

			pInvalidDelta2 = si_selb(pInvalidDelta2, pSafeBuffer,

				si_cgt(pInvalidDelta2, pUniformEndMinusOne));

			pInvalidDelta3 = si_selb(pInvalidDelta3, pSafeBuffer,

				si_cgt(pInvalidDelta3, pUniformEndMinusOne));

			si_stqd(si_il(0), pInvalidDelta1, 0);

			si_stqd(si_il(0), pInvalidDelta2, 0);

			si_stqd(si_il(0), pInvalidDelta3, 0);

			

			qword pBlendedVertexesTable = si_from_ptr(blendedVertexIndexTable);

			qword hasBlendedVertexesTable = si_shufb(si_clgti(pBlendedVertexesTable, 0), s_AAAA, s_AAAA);

			qword bvtOffset = si_from_int(0);



			// If the blended vertexes table is NULL, it means that there are exactly as many

			// deltas as there are vertexes, and that they should simply be applied in order.

			// We use this variable to simulate a blended vertexes table containing all vertex

			// offsets in ascending order.

			qword incrementingOffsets = (qword)(vec_ushort8){0x0000,0x0010,0x0020,0x0030,

															 0xFFFF,0xFFFF,0xFFFF,0xFFFF};



			const qword deltaMask = si_fsm(si_from_int( 0xF << (4-attrComponentCount) ));



			const qword pBlendDest = si_from_ptr(ctx->uniformTables[blendDestTableIndex]);

			const qword pDeltas1 = si_a(uniformTableSize, pDestTable);

			const qword pDeltas2 = si_ai(pDeltas1, 0x10);

			const qword pDeltas3 = si_ai(pDeltas1, 0x20);

			const qword pDeltas4 = si_ai(pDeltas1, 0x30);

			qword inOffset = si_sfi(uniformTableSize, 0);			



			qword loadedOffsets = si_rotqby(si_lqx(pBlendedVertexesTable, bvtOffset), bvtOffset);

			qword destOffset2 = si_selb(incrementingOffsets, loadedOffsets, hasBlendedVertexesTable);

			qword destOffset1 = si_rotmi(destOffset2, -16);

			qword destOffset4 = si_rotqbyi(destOffset2, 4);

			qword destOffset3 = si_rotmi(destOffset4, -16);

			qword destOffset1_out = destOffset1;

			qword destOffset2_out = destOffset2;

			qword destOffset3_out = destOffset3;

			qword destOffset4_out = destOffset4;

			incrementingOffsets = si_ahi(incrementingOffsets, 0x40);

			bvtOffset = si_ai(bvtOffset, 8);

				

			qword delta1 = si_and(si_lqx(pDeltas1, inOffset), deltaMask);

			qword delta2 = si_and(si_lqx(pDeltas2, inOffset), deltaMask);

			qword delta3 = si_and(si_lqx(pDeltas3, inOffset), deltaMask);

			qword delta4 = si_and(si_lqx(pDeltas4, inOffset), deltaMask);

			qword dest1  = si_lqx(pBlendDest, destOffset1);

			qword dest2  = si_lqx(pBlendDest, destOffset2);

			qword dest3  = si_lqx(pBlendDest, destOffset3);

			qword dest4  = si_lqx(pBlendDest, destOffset4);

			inOffset = si_ai(inOffset, 0x40);

			do

			{

				qword out1 = si_fma(delta1, alpha, dest1);

				qword out2 = si_fma(delta2, alpha, dest2);

				qword out3 = si_fma(delta3, alpha, dest3);

				qword out4 = si_fma(delta4, alpha, dest4);

				// The stores must be "backwards" (4,3,2,1), since the delta

				// table is padded with 0 deltas against vertex 0.  If

				// vertex 0 is actually being blended, running the

				// stores backwards will prevent the correct value

				// from being overwritten by a zero blend.

				si_stqx(out4, pBlendDest, destOffset4_out);

				si_stqx(out3, pBlendDest, destOffset3_out);

				si_stqx(out2, pBlendDest, destOffset2_out);

				si_stqx(out1, pBlendDest, destOffset1_out);



				qword loadedOffsets = si_rotqby(si_lqx(pBlendedVertexesTable, bvtOffset), bvtOffset);

				qword destOffset2 = si_selb(incrementingOffsets, loadedOffsets, hasBlendedVertexesTable);

				qword destOffset1 = si_rotmi(destOffset2, -16);

				qword destOffset4 = si_rotqbyi(destOffset2, 4);

				qword destOffset3 = si_rotmi(destOffset4, -16);

				destOffset1_out = destOffset1;

				destOffset2_out = destOffset2;

				destOffset3_out = destOffset3;

				destOffset4_out = destOffset4;

				incrementingOffsets = si_ahi(incrementingOffsets, 0x40);

				bvtOffset = si_ai(bvtOffset, 8);



				delta1 = si_and(si_lqx(pDeltas1, inOffset), deltaMask);

				delta2 = si_and(si_lqx(pDeltas2, inOffset), deltaMask);

				delta3 = si_and(si_lqx(pDeltas3, inOffset), deltaMask);

				delta4 = si_and(si_lqx(pDeltas4, inOffset), deltaMask);

				dest1	  = si_lqx(pBlendDest, destOffset1);

				dest2	  = si_lqx(pBlendDest, destOffset2);

				dest3	  = si_lqx(pBlendDest, destOffset3);

				dest4	  = si_lqx(pBlendDest, destOffset4);

				inOffset = si_ai(inOffset, 0x40);

			}

			while(si_to_int(inOffset) != 0x40);

		}

		else

		{

			// Set the uniform table pointers based on the attribute's

			// ID.

			switch(attrId)

			{

			case EDGE_GEOM_ATTRIBUTE_ID_POSITION:

				ctx->positionTable = (void*)si_to_ptr(pDestTable);

				break;

			case EDGE_GEOM_ATTRIBUTE_ID_NORMAL:

				ctx->normalTable   = (void*)si_to_ptr(pDestTable);

				break;

			case EDGE_GEOM_ATTRIBUTE_ID_TANGENT:

				ctx->tangentTable  = (void*)si_to_ptr(pDestTable);

				break;

			case EDGE_GEOM_ATTRIBUTE_ID_BINORMAL:

				ctx->binormalTable = (void*)si_to_ptr(pDestTable);

				break;

			}



			// Also update the uniform-index-to-attribute-ID table

			ctx->uniformTableToAttributeIdMapping = si_shufb(attrDesc,

				ctx->uniformTableToAttributeIdMapping,

				si_cbd(si_from_int(destTableIndex), 0));

		}



	}

}

#endif



static void DecompressVertexesByFormatId(EdgeGeomSpuContext *ctx, const void *vertexes,

	const void *fixedOffsets, uint8_t inputFormatId,

	const EdgeGeomVertexStreamDescription *inputStreamDesc,

	EdgeGeomDecompressVertexStreamCallback decompressInputCallback)

{

	if(ctx->spuConfigInfo.numVertexes == 0) {

		return;

	}



	// This sets up some global decompression structures which are

	// independent of the input vertex format.  To reduce code bloat, it's

	// done once outside the loop instead of being duplicated inside

	// each case block.

	EDGE_GEOM_DECOMPRESS_INIT_GLOBAL_COMMON(ctx, vertexes, ctx->spuConfigInfo.numVertexes,

		fixedOffsets);



	switch(inputFormatId)

	{

	case EDGE_GEOM_SPU_VERTEX_FORMAT_F32c3:

		{

			// This format is special-cased for performance

			EDGE_GEOM_DECOMPRESS_SPECIALCASE_F32_3(ctx, vertexes, ctx->spuConfigInfo.numVertexes);

		}

		break;

	case EDGE_GEOM_SPU_VERTEX_FORMAT_F32c3_X11Y11Z10N_X11Y11Z10N:

		{

			EDGE_GEOM_DECOMPRESS_INIT_GLOBAL(20);

			

			EDGE_GEOM_DECOMPRESS_INIT_F32(EDGE_GEOM_ATTRIBUTE_ID_POSITION, 0, 3);

			EDGE_GEOM_DECOMPRESS_INIT_X11Y11Z10N(EDGE_GEOM_ATTRIBUTE_ID_NORMAL, 12, 3);

			EDGE_GEOM_DECOMPRESS_INIT_X11Y11Z10N(EDGE_GEOM_ATTRIBUTE_ID_TANGENT, 16, 3);

			

			EDGE_GEOM_DECOMPRESS_LOAD_COMMON();

			do

			{

				EDGE_GEOM_DECOMPRESS_LOOP_START();

				

				EDGE_GEOM_DECOMPRESS_LOOP_F32(EDGE_GEOM_ATTRIBUTE_ID_POSITION);

				EDGE_GEOM_DECOMPRESS_LOOP_X11Y11Z10N(EDGE_GEOM_ATTRIBUTE_ID_NORMAL);

				EDGE_GEOM_DECOMPRESS_LOOP_X11Y11Z10N(EDGE_GEOM_ATTRIBUTE_ID_TANGENT);

				

				EDGE_GEOM_DECOMPRESS_LOOP_END();

			} while (!EDGE_GEOM_DECOMPRESS_LOOP_DONE());

			

			EDGE_GEOM_DECOMPRESS_FINALIZE_F32(EDGE_GEOM_ATTRIBUTE_ID_POSITION);

			EDGE_GEOM_DECOMPRESS_FINALIZE_X11Y11Z10N(EDGE_GEOM_ATTRIBUTE_ID_NORMAL);

			EDGE_GEOM_DECOMPRESS_FINALIZE_X11Y11Z10N(EDGE_GEOM_ATTRIBUTE_ID_TANGENT);

		}

		break;

	case EDGE_GEOM_SPU_VERTEX_FORMAT_F32c3_X11Y11Z10N_I16Nc4:

		{

			EDGE_GEOM_DECOMPRESS_INIT_GLOBAL(24);

			

			EDGE_GEOM_DECOMPRESS_INIT_F32(EDGE_GEOM_ATTRIBUTE_ID_POSITION, 0, 3);

			EDGE_GEOM_DECOMPRESS_INIT_X11Y11Z10N(EDGE_GEOM_ATTRIBUTE_ID_NORMAL, 12, 3);

			EDGE_GEOM_DECOMPRESS_INIT_I16N(EDGE_GEOM_ATTRIBUTE_ID_TANGENT, 16, 4);

			

			EDGE_GEOM_DECOMPRESS_LOAD_COMMON();

			do

			{

				EDGE_GEOM_DECOMPRESS_LOOP_START();

				

				EDGE_GEOM_DECOMPRESS_LOOP_F32(EDGE_GEOM_ATTRIBUTE_ID_POSITION);

				EDGE_GEOM_DECOMPRESS_LOOP_X11Y11Z10N(EDGE_GEOM_ATTRIBUTE_ID_NORMAL);

				EDGE_GEOM_DECOMPRESS_LOOP_I16N(EDGE_GEOM_ATTRIBUTE_ID_TANGENT);

				

				EDGE_GEOM_DECOMPRESS_LOOP_END();

			} while (!EDGE_GEOM_DECOMPRESS_LOOP_DONE());

			

			EDGE_GEOM_DECOMPRESS_FINALIZE_F32(EDGE_GEOM_ATTRIBUTE_ID_POSITION);

			EDGE_GEOM_DECOMPRESS_FINALIZE_X11Y11Z10N(EDGE_GEOM_ATTRIBUTE_ID_NORMAL);

			EDGE_GEOM_DECOMPRESS_FINALIZE_I16N(EDGE_GEOM_ATTRIBUTE_ID_TANGENT);

		}

		break;

	case EDGE_GEOM_SPU_VERTEX_FORMAT_F32c3_X11Y11Z10N_X11Y11Z10N_X11Y11Z10N:

		{

			EDGE_GEOM_DECOMPRESS_INIT_GLOBAL(24);

			

			EDGE_GEOM_DECOMPRESS_INIT_F32(EDGE_GEOM_ATTRIBUTE_ID_POSITION, 0, 3);

			EDGE_GEOM_DECOMPRESS_INIT_X11Y11Z10N(EDGE_GEOM_ATTRIBUTE_ID_NORMAL, 12, 3);

			EDGE_GEOM_DECOMPRESS_INIT_X11Y11Z10N(EDGE_GEOM_ATTRIBUTE_ID_TANGENT, 16, 3);

			EDGE_GEOM_DECOMPRESS_INIT_X11Y11Z10N(EDGE_GEOM_ATTRIBUTE_ID_BINORMAL, 20, 3);

			

			EDGE_GEOM_DECOMPRESS_LOAD_COMMON();

			do

			{

				EDGE_GEOM_DECOMPRESS_LOOP_START();

				

				EDGE_GEOM_DECOMPRESS_LOOP_F32(EDGE_GEOM_ATTRIBUTE_ID_POSITION);

				EDGE_GEOM_DECOMPRESS_LOOP_X11Y11Z10N(EDGE_GEOM_ATTRIBUTE_ID_NORMAL);

				EDGE_GEOM_DECOMPRESS_LOOP_X11Y11Z10N(EDGE_GEOM_ATTRIBUTE_ID_TANGENT);

				EDGE_GEOM_DECOMPRESS_LOOP_X11Y11Z10N(EDGE_GEOM_ATTRIBUTE_ID_BINORMAL);

				

				EDGE_GEOM_DECOMPRESS_LOOP_END();

			} while (!EDGE_GEOM_DECOMPRESS_LOOP_DONE());

	

			EDGE_GEOM_DECOMPRESS_FINALIZE_F32(EDGE_GEOM_ATTRIBUTE_ID_POSITION);

			EDGE_GEOM_DECOMPRESS_FINALIZE_X11Y11Z10N(EDGE_GEOM_ATTRIBUTE_ID_NORMAL);

			EDGE_GEOM_DECOMPRESS_FINALIZE_X11Y11Z10N(EDGE_GEOM_ATTRIBUTE_ID_TANGENT);

			EDGE_GEOM_DECOMPRESS_FINALIZE_X11Y11Z10N(EDGE_GEOM_ATTRIBUTE_ID_BINORMAL);

		}

		break;

	case EDGE_GEOM_SPU_VERTEX_FORMAT_FIXED_UNITVEC_UNITVEC:

		{

			EDGE_GEOM_DECOMPRESS_INIT_GLOBAL(12);

			

			EDGE_GEOM_DECOMPRESS_INIT_FIXED_POINT(EDGE_GEOM_ATTRIBUTE_ID_POSITION, 0, 3, 4, 12, 4, 12, 4, 12, 0, 0);

			EDGE_GEOM_DECOMPRESS_INIT_UNIT_VECTOR(EDGE_GEOM_ATTRIBUTE_ID_NORMAL, 6, 3);

			EDGE_GEOM_DECOMPRESS_INIT_UNIT_VECTOR(EDGE_GEOM_ATTRIBUTE_ID_TANGENT, 9, 3);

			

			EDGE_GEOM_DECOMPRESS_LOAD_COMMON();

			do

			{

				EDGE_GEOM_DECOMPRESS_LOOP_START();

				

				EDGE_GEOM_DECOMPRESS_LOOP_FIXED_POINT(EDGE_GEOM_ATTRIBUTE_ID_POSITION);

				EDGE_GEOM_DECOMPRESS_LOOP_UNIT_VECTOR(EDGE_GEOM_ATTRIBUTE_ID_NORMAL);

				EDGE_GEOM_DECOMPRESS_LOOP_UNIT_VECTOR(EDGE_GEOM_ATTRIBUTE_ID_TANGENT);

				

				EDGE_GEOM_DECOMPRESS_LOOP_END();

			} while (!EDGE_GEOM_DECOMPRESS_LOOP_DONE());

			

			EDGE_GEOM_DECOMPRESS_FINALIZE_FIXED_POINT(EDGE_GEOM_ATTRIBUTE_ID_POSITION);

			EDGE_GEOM_DECOMPRESS_FINALIZE_UNIT_VECTOR(EDGE_GEOM_ATTRIBUTE_ID_NORMAL);

			EDGE_GEOM_DECOMPRESS_FINALIZE_UNIT_VECTOR(EDGE_GEOM_ATTRIBUTE_ID_TANGENT);



		}

		break;

	case EDGE_GEOM_SPU_VERTEX_FORMAT_FIXED_UNITVEC_UNITVEC_UNITVEC:

		{

			EDGE_GEOM_DECOMPRESS_INIT_GLOBAL(15);

			

			EDGE_GEOM_DECOMPRESS_INIT_FIXED_POINT(EDGE_GEOM_ATTRIBUTE_ID_POSITION, 0, 3, 4, 12, 4, 12, 4, 12, 0, 0);

			EDGE_GEOM_DECOMPRESS_INIT_UNIT_VECTOR(EDGE_GEOM_ATTRIBUTE_ID_NORMAL, 6, 3);

			EDGE_GEOM_DECOMPRESS_INIT_UNIT_VECTOR(EDGE_GEOM_ATTRIBUTE_ID_TANGENT, 9, 3);

			EDGE_GEOM_DECOMPRESS_INIT_UNIT_VECTOR(EDGE_GEOM_ATTRIBUTE_ID_BINORMAL, 12, 3);

			

			EDGE_GEOM_DECOMPRESS_LOAD_COMMON();

			do

			{

				EDGE_GEOM_DECOMPRESS_LOOP_START();

				

				EDGE_GEOM_DECOMPRESS_LOOP_FIXED_POINT(EDGE_GEOM_ATTRIBUTE_ID_POSITION);

				EDGE_GEOM_DECOMPRESS_LOOP_UNIT_VECTOR(EDGE_GEOM_ATTRIBUTE_ID_NORMAL);

				EDGE_GEOM_DECOMPRESS_LOOP_UNIT_VECTOR(EDGE_GEOM_ATTRIBUTE_ID_TANGENT);

				EDGE_GEOM_DECOMPRESS_LOOP_UNIT_VECTOR(EDGE_GEOM_ATTRIBUTE_ID_BINORMAL);

				

				EDGE_GEOM_DECOMPRESS_LOOP_END();

			} while (!EDGE_GEOM_DECOMPRESS_LOOP_DONE());

			

			EDGE_GEOM_DECOMPRESS_FINALIZE_FIXED_POINT(EDGE_GEOM_ATTRIBUTE_ID_POSITION);

			EDGE_GEOM_DECOMPRESS_FINALIZE_UNIT_VECTOR(EDGE_GEOM_ATTRIBUTE_ID_NORMAL);

			EDGE_GEOM_DECOMPRESS_FINALIZE_UNIT_VECTOR(EDGE_GEOM_ATTRIBUTE_ID_TANGENT);

			EDGE_GEOM_DECOMPRESS_FINALIZE_UNIT_VECTOR(EDGE_GEOM_ATTRIBUTE_ID_BINORMAL);

		}

		break;

	case EDGE_GEOM_SPU_VERTEX_FORMAT_EMPTY:

		break;

	case 0xFF: // 0xFF is the designated custom format ID

	default:

		{

			// callback function takes precedence over stream

			// description. If the callback returns non-zero, the

			// stream description version will be called.

			bool success = false;

			if(decompressInputCallback != 0)

			{

				uint32_t err = decompressInputCallback(ctx, vertexes, ctx->spuConfigInfo.numVertexes,

					fixedOffsets, inputFormatId);

				success = (err == 0);

			}

#if ENABLE_DECOMPRESS_BY_DESCRIPTION

			if (!success && inputStreamDesc != 0)

			{

				DecompressVertexesByDescription(ctx, vertexes, fixedOffsets,

					inputStreamDesc, ctx->spuConfigInfo.numVertexes);

				success = true;

			}

#endif // ENABLE_DECOMPRESS_BY_DESCRIPTION

			if (!success)

			{

				// unknown format

				EDGE_ASSERT_MSG( false, ("EDGE error: unknown input vertex format ID %d for vertex stream", inputFormatId) );

			}

			break;

		}

	}

}





/**

 * 	Decompress vertices into uniform tables

 *  @param ctx EdgeGeom job context

 * 	@param vertexesA first input stream

 * 	@param fixedOffsetsA array of offsets for fixed point attributes for the first stream

 * 	@param vertexesB second input stream

 * 	@param fixedOffsetsB array of offsets for fixed point attributes for the second stream

 */

void edgeGeomDecompressVertexes(EdgeGeomSpuContext *ctx, const void *vertexesA, const void *fixedOffsetsA, const void *vertexesB /*= 0*/, const void *fixedOffsetsB /*= 0*/)

{

	ctx->vertexesLs = (uint32_t)vertexesA;



	DecompressVertexesByFormatId(ctx, vertexesA, fixedOffsetsA, ctx->spuConfigInfo.inputVertexFormatId,

		ctx->customFormatInfo.inputStreamDescA, ctx->customFormatInfo.decompressInputCallbackA);

	if (vertexesB != 0)

	{

		DecompressVertexesByFormatId(ctx, vertexesB, fixedOffsetsB,

			ctx->spuConfigInfo.secondaryInputVertexFormatId, ctx->customFormatInfo.inputStreamDescB,

			ctx->customFormatInfo.decompressInputCallbackB);

	}



	// Set the free ptr to the beginning of the earliest input vertex stream

	const void *firstVerts = (const void*)(vertexesB ? ((vertexesA < vertexesB) ? vertexesA : vertexesB) : vertexesA);

	edgeGeomSetFreePtr(ctx, firstVerts);

}



#if DECOMPRESSBLENDSHAPERUNTABLE_INTRINSICS

void DecompressBlendShapeRunTable(uint16_t *runTable, uint32_t runTableSize,

	uint32_t numDeltas)

{

	if (runTableSize == 0)

		return;



	const qword s_AAAAAAAA = si_ilh(0x0001);

	const qword s_BBBBBBBB = si_ilh(0x0203);

	const qword s_0A0A0A0A = si_iohl( si_ilhu(0x8080), 0x0001 );

	const qword s_DDDDDDDDDDDDDDDD = si_ilh(0x0303);

	const qword s_ABCDEFGH = si_xorbi((qword)s_ABCD, 0x20); // add 0x20 to every byte so we can safely subtract from this mask



	const qword pTable0 = si_from_ptr(runTable);

	const qword pTable1 = si_ai(pTable0, 0x10);

	const qword pTable2 = si_ai(pTable0, 0x20);

	const qword pTable3 = si_ai(pTable0, 0x30);

	const qword pTable4 = si_ai(pTable0, 0x40);



	qword outOffset = si_from_int(numDeltas * 2); // starts at the first byte past the end of the decompressed table.  It needs to be decremented every iteration by runSize*2

	qword inOffset = si_ai(si_from_int(runTableSize), -2); // offset to the last valid element in the run table



	// These five quads contain the odd integers from 1 to 79 in ascending order.

	// Initially they're loaded with 0 to 78 (to help generate the offsets0-3 values below),

	// but we add 1 to each entry immediately afterwards.

	qword indexes0 = (qword)(vec_ushort8){0, 2, 4, 6, 8, 10, 12, 14};

	qword indexes1 = si_ahi(indexes0, 16);

	qword indexes2 = si_ahi(indexes0, 32);

	qword indexes3 = si_ahi(indexes0, 48);

	qword indexes4 = si_ahi(indexes0, 64);



	// These four quads contain the contents of indexes0-3, multiplied

	// by 8 (to be byte offsets instead of qword indexes)

	const qword offsets0 = si_shlhi(indexes0, 3);

	const qword offsets1 = si_shlhi(indexes1, 3);

	const qword offsets2 = si_shlhi(indexes2, 3);

	const qword offsets3 = si_shlhi(indexes3, 3);

	// Add 1 to each entry of indexes0-4, as promised above.  Now they're 2*N+1 for N=0..39.

	indexes0 = si_ahi(indexes0, 1);

	indexes1 = si_ahi(indexes1, 1);

	indexes2 = si_ahi(indexes2, 1);

	indexes3 = si_ahi(indexes3, 1);

	indexes4 = si_ahi(indexes4, 1);



	// this can be any value; it's just used in shuffle masks that only reference one of their arguments

	const qword unused = pTable0;



	// All unused indices in the final qword of the decompressed table

	// need to be initialized to 0.  Be careful not to overwrite any

	// valid compressed data in the process!



	// Build a mask that preserves any compressed table entries

	qword finalTableSizeMod16 = si_andi(outOffset, 0xF);

	qword toZeroMaskGen = si_rotma( si_ilhu(0xFFFF), si_sfi( finalTableSizeMod16, 0) );

	toZeroMaskGen = si_selb( toZeroMaskGen, si_ilh(0xFFFF), si_ceqi(finalTableSizeMod16, 0) );

	qword toZeroMask = si_fsmb(toZeroMaskGen);

	// Apply the mask

	qword finalQword = si_lqx(pTable0, si_ai(outOffset, -1));

	si_stqx(si_and(finalQword, toZeroMask), pTable0, si_ai(outOffset, -1));



	// PHASE 1, ITERATION 1



	// Load the next 2-byte run table entry.  The top 11 bits are

	// the index of the first vertex in the run; the low 5 bits

	// are the run size minus 1 (effectively [1-32]).  The

	// runFirst portion is extracted, right-shifted 1 bit (to make

	// it a byte offset), and broadcast to every halfword in the

	// quad.  The runSize portion is extracted, shifted up by 1

	// (to make it a byte size) and broadcast to every halfword in

	// the quad.

	qword runQuad = si_rotqby(si_lqx(pTable0, inOffset), inOffset);

	inOffset = si_ai(inOffset, -2);

	qword runFirstTemp = si_rotqmbii(si_andhi(runQuad,  -32), -1); // -32 = 0xFFE0

	qword runSizeTemp  = si_shlqbii( si_ahi(si_andhi(runQuad, 0x1F), 1), 1);

	qword runSizeLowHalf = si_shufb(runSizeTemp, unused, s_0A0A0A0A);

	qword runFirst = si_shufb(runFirstTemp, unused, s_AAAAAAAA);

	qword runSize  = si_shufb(runSizeTemp,  unused, s_AAAAAAAA);

		

	// When decrementing outOffset, we need to mask off the high

	// halfword of runSize to avoid subtracting more than we

	// intend to.

	outOffset = si_sf(runSizeLowHalf, outOffset);

	

	// Add runFirst to the offsets0-3 quads to get our potential set of output offsets

	qword outOffsets0 = si_ah(offsets0, runFirst);

	qword outOffsets1 = si_ah(offsets1, runFirst);

	qword outOffsets2 = si_ah(offsets2, runFirst);

	qword outOffsets3 = si_ah(offsets3, runFirst);

	



	// PHASE 2, ITERATION 1



	// Determine the shuffle mask used to rotate the output quads to the proper offset.

	qword outOffsetMod16 = si_andi(outOffset, 0x0F);

	qword outOffsetMod16Halfs = si_shufb(outOffsetMod16, unused, s_BBBBBBBB);

	qword outOffsetMod16Bytes = si_shufb(outOffsetMod16, unused, s_DDDDDDDDDDDDDDDD);

	qword s_outRotate = si_sf(outOffsetMod16Bytes, s_ABCDEFGH);

	

	// Build a byte-mask of five output quads (representing the

	// quad-aligned region of the output buffer containing this

	// run), where an 0xFF indicates that the corresponding output

	// buffer byte should maintain its current value, and an 0x00

	// indicates the corresponding byte will be overwritten by the

	// run.

	qword outputByteMask0 = si_clgth( si_sfh(outOffsetMod16Halfs, indexes0), runSize );

	qword outputByteMask1 = si_clgth( si_sfh(outOffsetMod16Halfs, indexes1), runSize );

	qword outputByteMask2 = si_clgth( si_sfh(outOffsetMod16Halfs, indexes2), runSize );

	qword outputByteMask3 = si_clgth( si_sfh(outOffsetMod16Halfs, indexes3), runSize );

	qword outputByteMask4 = si_clgth( si_sfh(outOffsetMod16Halfs, indexes4), runSize );

	

	// Shuffle the output offsets into their final quad-relative positions using s_outRotate.

	// The invalid elements will be masked out later, before final output.

	qword out0 = si_shufb(outOffsets0, unused,      s_outRotate);

	qword out1 = si_shufb(outOffsets1, outOffsets0, s_outRotate);

	qword out2 = si_shufb(outOffsets2, outOffsets1, s_outRotate);

	qword out3 = si_shufb(outOffsets3, outOffsets2, s_outRotate);

	qword out4 = si_shufb(unused,      outOffsets3, s_outRotate);

	

	qword outOffset_fwd = outOffset;



	// PHASE 1, ITERATION 2



	runQuad = si_rotqby(si_lqx(pTable0, inOffset), inOffset);

	inOffset = si_ai(inOffset, -2);

	runFirstTemp = si_rotqmbii(si_andhi(runQuad,  -32), -1); // -32 = 0xFFE0

	runSizeTemp  = si_shlqbii( si_ahi(si_andhi(runQuad, 0x1F), 1),  1);

	runSizeLowHalf = si_shufb(runSizeTemp, unused, s_0A0A0A0A);

	runFirst = si_shufb(runFirstTemp, unused, s_AAAAAAAA);

	runSize  = si_shufb(runSizeTemp,  unused, s_AAAAAAAA);

		

	// When decrementing outOffset, we need to mask off the high

	// halfword of runSize to avoid subtracting more than we

	// intend to.

	outOffset = si_sf(runSizeLowHalf, outOffset);

	

	// Add runFirst to the offsets0-3 quads to get our potential set of output offsets

	outOffsets0 = si_ah(offsets0, runFirst);

	outOffsets1 = si_ah(offsets1, runFirst);

	outOffsets2 = si_ah(offsets2, runFirst);

	outOffsets3 = si_ah(offsets3, runFirst);



	qword numIterations = si_from_int(runTableSize/2);

	do

	{

		// PHASE 3



		// Load the current contents of the five quads spanning this run's destination

		qword in0 = si_lqx(pTable0, outOffset_fwd);

		qword in1 = si_lqx(pTable1, outOffset_fwd);

		qword in2 = si_lqx(pTable2, outOffset_fwd);

		qword in3 = si_lqx(pTable3, outOffset_fwd);

		qword in4 = si_lqx(pTable4, outOffset_fwd);

		

		// Use the output byte mask to select between in0-4 and out0-4

		qword finalOut0 = si_selb(out0, in0, outputByteMask0);

		qword finalOut1 = si_selb(out1, in1, outputByteMask1);

		qword finalOut2 = si_selb(out2, in2, outputByteMask2);

		qword finalOut3 = si_selb(out3, in3, outputByteMask3);

		qword finalOut4 = si_selb(out4, in4, outputByteMask4);

		

		// Write the five run quads back to local store

		si_stqx(finalOut0, pTable0, outOffset_fwd);

		si_stqx(finalOut1, pTable1, outOffset_fwd);

		si_stqx(finalOut2, pTable2, outOffset_fwd);

		si_stqx(finalOut3, pTable3, outOffset_fwd);

		si_stqx(finalOut4, pTable4, outOffset_fwd);



		//-------------

		// PHASE 2

		

		// Determine the shuffle mask used to rotate the output quads to the proper offset.

		outOffsetMod16 = si_andi(outOffset, 0x0F);

		outOffsetMod16Halfs = si_shufb(outOffsetMod16, unused, s_BBBBBBBB);

		outOffsetMod16Bytes = si_shufb(outOffsetMod16, unused, s_DDDDDDDDDDDDDDDD);

		s_outRotate = si_sf(outOffsetMod16Bytes, s_ABCDEFGH);

		

		// Build a byte-mask of five output quads (representing the

		// quad-aligned region of the output buffer containing this

		// run), where an 0xFF indicates that the corresponding output

		// buffer byte should maintain its current value, and an 0x00

		// indicates the corresponding byte will be overwritten by the

		// run.

		outputByteMask0 = si_clgth( si_sfh(outOffsetMod16Halfs, indexes0), runSize );

		outputByteMask1 = si_clgth( si_sfh(outOffsetMod16Halfs, indexes1), runSize );

		outputByteMask2 = si_clgth( si_sfh(outOffsetMod16Halfs, indexes2), runSize );

		outputByteMask3 = si_clgth( si_sfh(outOffsetMod16Halfs, indexes3), runSize );

		outputByteMask4 = si_clgth( si_sfh(outOffsetMod16Halfs, indexes4), runSize );

		

		// Shuffle the output offsets into their final quad-relative positions using s_outRotate.

		// The invalid elements will be masked out later, before final output.

		out0 = si_shufb(outOffsets0, unused,      s_outRotate);

		out1 = si_shufb(outOffsets1, outOffsets0, s_outRotate);

		out2 = si_shufb(outOffsets2, outOffsets1, s_outRotate);

		out3 = si_shufb(outOffsets3, outOffsets2, s_outRotate);

		out4 = si_shufb(unused,      outOffsets3, s_outRotate);

		

		outOffset_fwd = outOffset;

		

		//-------------------------

		// PHASE 1



		// Load the next 2-byte run table entry.  The top 11 bits are

		// the index of the first vertex in the run; the low 5 bits

		// are the run size minus 1 (effectively [1-32]).  The

		// runFirst portion is extracted, right-shifted 1 bit (to make

		// it a byte offset), and broadcast to every halfword in the

		// quad.  The runSize portion is extracted, incremented, shifted up by 1

		// (to make it a byte size) and broadcast to every halfword in

		// the quad.

		runQuad = si_rotqby(si_lqx(pTable0, inOffset), inOffset);

		inOffset = si_ai(inOffset, -2);

		runFirstTemp = si_rotqmbii(si_andhi(runQuad,  -32), -1); // -32 = 0xFFE0

		runSizeTemp  = si_shlqbii( si_ahi(si_andhi(runQuad, 0x1F), 1),  1);

		runSizeLowHalf = si_shufb(runSizeTemp, unused, s_0A0A0A0A);

		runFirst = si_shufb(runFirstTemp, unused, s_AAAAAAAA);

		runSize  = si_shufb(runSizeTemp,  unused, s_AAAAAAAA);

		

		// When decrementing outOffset, we need to mask off the high

		// halfword of runSize to avoid subtracting more than we

		// intend to.

		outOffset = si_sf(runSizeLowHalf, outOffset);

		

		// Add runFirst to the offsets0-3 quads to get our potential set of output offsets

		outOffsets0 = si_ah(offsets0, runFirst);

		outOffsets1 = si_ah(offsets1, runFirst);

		outOffsets2 = si_ah(offsets2, runFirst);

		outOffsets3 = si_ah(offsets3, runFirst);



		numIterations = si_ai(numIterations, -1);

	}

	while(si_to_int(numIterations) != 0);

}

#endif



/**

 * 	For each blend shape, DMAs blend shape infos and deltas to local store

 * 	and blends vertices.  

 *  @param ctx EdgeGeom job context

 * 	@param numShapes the number of shapes

 * 	@param shapeInfosEa the effective address of the first shape info structure.

 */

void edgeGeomProcessBlendShapes(EdgeGeomSpuContext *ctx, uint32_t numShapes, uint32_t shapeInfosEa)

{

	// Blend shapes are not supported in the static geometry path!

	if (ctx->spuConfigInfo.flagsAndUniformTableCount & EDGE_GEOM_FLAG_STATIC_GEOMETRY_FAST_PATH) {

		return;

	}



	uint32_t freePtr = (uint32_t)ctx->freePtr;



	bool interruptsEnabled = spu_readch(SPU_RdMachStat) & 1;

	

	// TODO: Double buffering the input here would improve performance.

	for(uint32_t i = 0; i < numShapes; ++i)

	{

		spu_idisable();



		// dma in the shape Infos to freePtr

		EDGE_DMA_GET(freePtr, shapeInfosEa, sizeof(EdgeGeomBlendShapeInfo), ctx->inputDmaTag, 0, 0);



		// dma in the info->dmaTag w/ barrier

		EDGE_DMA_LIST_GETB(freePtr+sizeof(EdgeGeomBlendShapeInfo), 0,

			(const EdgeDmaListElement*)freePtr, 8, ctx->inputDmaTag, 0, 0);



		// wait for dmas

		EDGE_DMA_WAIT_TAG_STATUS_ALL(1 << ctx->inputDmaTag);



		if(interruptsEnabled)

			spu_ienable();



		EdgeGeomBlendShapeInfo *info = (EdgeGeomBlendShapeInfo *)freePtr;



		// The PPU should not be passing a DMA tag of 0.  If it did, we definitely don't want

		// to process this shape any further, as we'll be working with old/undefined data.

		if (info->dmaTag != 0)

		{

			// The first 2 quads of the actual data is a header, full of four-byte table sizes

			uint32_t *shapeTableSizes = (uint32_t*) ( freePtr + sizeof(EdgeGeomBlendShapeInfo) );

			EdgeGeomVertexStreamDescription *blendStreamDesc =

				(EdgeGeomVertexStreamDescription*)( (uint32_t)shapeTableSizes + 32);

			// All blend shapes use stream description decompression; the format ID field is ignored.

			//if (ctx->spuConfigInfo.vertexDeltaFormatId == 0xFF)

			{

				// EDGE_ASSERT(tableSizes[0] > 0);

				ctx->customFormatInfo.blendStreamDesc = blendStreamDesc;

			}

			void *fixedOffsets = (void*)( (uint32_t)blendStreamDesc + shapeTableSizes[0] );

			void *vertexDeltas = (void*)( (uint32_t)fixedOffsets + shapeTableSizes[1] );

			uint16_t *blendedVertexTable = (uint16_t*)( (uint32_t)vertexDeltas + shapeTableSizes[2] );



			// Each blend shape buffer contains only the non-zero

			// deltas, and an optional (compressed) table of vertex

			// indexes corresponding to those deltas.  The latter

			// table must be decompressed before we use it.

			uint32_t runTableSize = shapeTableSizes[3];

			uint32_t numDeltas = shapeTableSizes[4];

			if (runTableSize > 0)

				DecompressBlendShapeRunTable(blendedVertexTable, runTableSize, numDeltas);

			else

				blendedVertexTable = 0;



			// Only blend if it will actually have an effect.  The PPU should already be checking

			// for zero alphas and not including those shapes in the DMA list, but it never hurts to

			// be cautious!

			if (numDeltas > 0 && info->alpha != 0)

			{

				DecompressVertexesByDescription(ctx, vertexDeltas, fixedOffsets,

					ctx->customFormatInfo.blendStreamDesc, numDeltas, info->alpha, blendedVertexTable);

			}

		}



		shapeInfosEa += sizeof(EdgeGeomBlendShapeInfo);

	}

}

