#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "../rwbase.h"
#include "../rwerror.h"
#include "../rwplg.h"
#include "../rwpipeline.h"
#include "../rwobjects.h"
#include "../rwengine.h"
#include "rwps2.h"

#define PLUGIN_ID ID_DRIVER

#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))

namespace rw {
namespace ps2 {

int32 nativeRasterOffset;

#define MAXLEVEL(r) ((r)->tex1low >> 2)
static bool32 noNewStyleRasters;

enum Psm {
	PSMCT32  = 0x0,
	PSMCT24  = 0x1,
	PSMCT16  = 0x2,
	PSMCT16S = 0xA,
	PSMT8    = 0x13,
	PSMT4    = 0x14,
	PSMT8H   = 0x1B,
	PSMT4HL  = 0x24,
	PSMT4HH  = 0x2C,
	PSMZ32   = 0x30,
	PSMZ24   = 0x31,
	PSMZ16   = 0x32,
	PSMZ16S  = 0x3A
};

// i don't really understand this, stolen from RW
static void
transferMinSize(int32 psm, int32 flags, int32 *minw, int32 *minh)
{
	*minh = 1;
	switch(psm){
	case PSMCT32:
	case PSMZ32:
		*minw = 2; // 32 bit
		break;
	case PSMCT16:
	case PSMCT16S:
	case PSMZ16:
	case PSMZ16S:
		*minw = 4; // 16 bit
		break;
	case PSMCT24:
	case PSMT8:
	case PSMT4:
	case PSMT8H:
	case PSMT4HL:
	case PSMT4HH:
	case PSMZ24:
		*minw = 8; // everything else
		break;
	}
	if(flags & 0x2 && psm == PSMT8){
		*minw = 16;
		*minh = 4;
	}
	if(flags & 0x4 && psm == PSMT4){
		*minw = 32;
		*minh = 4;
	}
}

#define ALIGN(x,a) ((x) + (a)-1 & ~((a)-1))
#define ALIGN16(x) ((x) + 0xF & ~0xF)
#define ALIGN64(x) ((x) + 0x3F & ~0x3F)
#define NSIZE(dim,pagedim) (((dim) + (pagedim)-1)/(pagedim))

void*
mallocalign(size_t size, int32 alignment)
{
	void *p;
	void **pp;
	p = rwMalloc(size + alignment + sizeof(void*), MEMDUR_EVENT | ID_RASTERPS2);
	if(p == nil) return nil;
	pp = (void**)(((uintptr)p + sizeof(void*) + alignment)&~(alignment-1));
	pp[-1] = p;
	return (void*)pp;
}

void
freealign(void *p)
{
	void *pp;
	if(p == nil) return;
	pp = ((void**)p)[-1];
	rwFree(pp);
}

// TODO: these depend on video mode, set in deviceSystem!
int32 cameraFormat = Raster::C8888;
int32 cameraDepth = 32;
int32 cameraZDepth = 16;

int32 defaultMipMapKL = 0xFC0;
int32 maxMipLevels = 7;

int32
getRasterFormat(Raster *raster)
{
	int32 palformat, pixelformat, mipmapflags;
	pixelformat = raster->format & 0xF00;
	palformat = raster->format & 0x6000;
	mipmapflags = raster->format & 0x9000;
	switch(raster->type){
	case Raster::ZBUFFER:
		if(palformat || mipmapflags){
			RWERROR((ERR_INVRASTER));
			return 0;
		}
		if(raster->depth && raster->depth != cameraZDepth){
			RWERROR((ERR_INVRASTER));
			return 0;
		}
		raster->depth = cameraZDepth;
		if(pixelformat){
			if((raster->depth == 16 && pixelformat != Raster::D16) ||
			   (raster->depth == 32 && pixelformat != Raster::D32)){
				RWERROR((ERR_INVRASTER));
				return 0;
			}
		}
		pixelformat = raster->depth == 16 ? Raster::D16 : Raster::D32;
		raster->format = pixelformat;
		break;
	case Raster::CAMERA:
		if(palformat || mipmapflags){
			RWERROR((ERR_INVRASTER));
			return 0;
		}
		if(raster->depth && raster->depth != cameraDepth){
			RWERROR((ERR_INVRASTER));
			return 0;
		}
		raster->depth = cameraDepth;
		if(pixelformat && pixelformat != cameraFormat){
			RWERROR((ERR_INVRASTER));
			return 0;
		}
		pixelformat = cameraFormat;
		raster->format = pixelformat;
		break;
	case Raster::NORMAL:
	case Raster::CAMERATEXTURE:
		if(palformat || mipmapflags){
			RWERROR((ERR_INVRASTER));
			return 0;
		}
		/* fallthrough */
	case Raster::TEXTURE:
		// Find raster format by depth if none was given
		if(pixelformat == 0)
			switch(raster->depth){
			case 4:
				pixelformat = Raster::C1555;
				palformat = Raster::PAL4;
				break;
			case 8:
				pixelformat = Raster::C1555;
				palformat = Raster::PAL8;
				break;
			case 24:
			// unsafe
			//	pixelformat = Raster::C888;
			//	palformat = 0;
			//	break;
			case 32:
				pixelformat = Raster::C8888;
				palformat = 0;
				break;
			default:
				pixelformat = Raster::C1555;
				palformat = 0;
				break;
			}
		raster->format = pixelformat | palformat | mipmapflags;
		// Sanity check raster format and depth; set depth if none given
		if(palformat){
			if(palformat == Raster::PAL8){
				if(raster->depth && raster->depth != 8){
					RWERROR((ERR_INVRASTER));
					return 0;
				}
				raster->depth = 8;
				if(pixelformat != Raster::C1555 && pixelformat != Raster::C8888){
					RWERROR((ERR_INVRASTER));
					return 0;
				}
			}else if(palformat == Raster::PAL4){
				if(raster->depth && raster->depth != 4){
					RWERROR((ERR_INVRASTER));
					return 0;
				}
				raster->depth = 4;
				if(pixelformat != Raster::C1555 && pixelformat != Raster::C8888){
					RWERROR((ERR_INVRASTER));
					return 0;
				}
			}else{
				RWERROR((ERR_INVRASTER));
				return 0;
			}
		}else if(pixelformat == Raster::C1555){
			if(raster->depth && raster->depth != 16){
				RWERROR((ERR_INVRASTER));
				return 0;
			}
			raster->depth = 16;
		}else if(pixelformat == Raster::C8888){
			if(raster->depth && raster->depth != 32){
				RWERROR((ERR_INVRASTER));
				return 0;
			}
			raster->depth = 32;
		}else if(pixelformat == Raster::C888){
			assert(0 && "24 bit rasters not supported");
			if(raster->depth && raster->depth != 24){
				RWERROR((ERR_INVRASTER));
				return 0;
			}
			raster->depth = 24;
		}else{
			RWERROR((ERR_INVRASTER));
			return 0;
		}
		break;
	default:
		RWERROR((ERR_INVRASTER));
		return 0;
	}
	return 1;
}

/*
 * Memory units:
 * Column: 64 bytes (single cycle access)
 * Block: 256 bytes, 64 words, 4 columns. texture base pointers
 * Page: 8 kbytes, 2 kwords, 128 columns, 32 blocks. frame buffer base pointers
 * entire memory: 4 mbytes, 64k columns, 16k blocks, 512 pages
 *
 * PSMT4:     128x128 pixels, 4x8 blocks per page, 32x16 pixels per block
 * PSMT8:     128x64  pixels, 8x4 blocks per page, 16x16 pixels per block
 * PSMCT16(S): 64x64  pixels, 4x8 blocks per page, 16x8  pixels per block
 * PSMCT24:    64x32  pixels, 8x4 blocks per page,  8x8  pixels per block
 * PSMCT32:    64x32  pixels, 8x4 blocks per page,  8x8  pixels per block
 *
 *   Layout of blocks in page:
 *
 *   PSMCT24, PSMCT32, PSMT8
 *   +----+----+----+----+----+----+----+----+
 *   |  0 |  1 |  4 |  5 | 16 | 17 | 20 | 21 |
 *   +----+----+----+----+----+----+----+----+
 *   |  2 |  3 |  6 |  7 | 18 | 19 | 22 | 23 |
 *   +----+----+----+----+----+----+----+----+
 *   |  8 |  9 | 12 | 13 | 24 | 25 | 28 | 29 |
 *   +----+----+----+----+----+----+----+----+
 *   | 10 | 11 | 14 | 15 | 26 | 27 | 30 | 31 |
 *   +----+----+----+----+----+----+----+----+
 *
 *   PSMCT16, PSMT4
 *   +----+----+----+----+
 *   |  0 |  2 |  8 | 10 |
 *   +----+----+----+----+
 *   |  1 |  3 |  9 | 11 |
 *   +----+----+----+----+
 *   |  4 |  6 | 12 | 14 |
 *   +----+----+----+----+
 *   |  5 |  7 | 13 | 15 |
 *   +----+----+----+----+
 *   | 16 | 18 | 24 | 26 |
 *   +----+----+----+----+
 *   | 17 | 19 | 25 | 27 |
 *   +----+----+----+----+
 *   | 20 | 22 | 28 | 30 |
 *   +----+----+----+----+
 *   | 21 | 23 | 29 | 31 |
 *   +----+----+----+----+
 *
 *   PSMCT16S
 *   +----+----+----+----+
 *   |  0 |  2 | 16 | 18 |
 *   +----+----+----+----+
 *   |  1 |  3 | 17 | 19 |
 *   +----+----+----+----+
 *   |  8 | 10 | 24 | 26 |
 *   +----+----+----+----+
 *   |  9 | 11 | 25 | 27 |
 *   +----+----+----+----+
 *   |  4 |  6 | 20 | 22 |
 *   +----+----+----+----+
 *   |  5 |  7 | 21 | 23 |
 *   +----+----+----+----+
 *   | 12 | 14 | 28 | 30 |
 *   +----+----+----+----+
 *   | 13 | 15 | 29 | 31 |
 *   +----+----+----+----+
 *
 */

static uint8 blockmap_PSMCT32[32] = {
	 0,  1,  4,  5, 16, 17, 20, 21,
	 2,  3,  6,  7, 18, 19, 22, 23,
	 8,  9, 12, 13, 24, 25, 28, 29,
	10, 11, 14, 15, 26, 27, 30, 31,
};
static uint8 blockmap_PSMCT16[32] = {
	 0,  2,  8, 10,
	 1,  3,  9, 11,
	 4,  6, 12, 14,
	 5,  7, 13, 15,
	16, 18, 24, 26,
	17, 19, 25, 27,
	20, 22, 28, 30,
	21, 23, 29, 31,
};
static uint8 blockmap_PSMCT16S[32] = {
	 0,  2, 16, 18,
	 1,  3, 17, 19,
	 8, 10, 24, 26,
	 9, 11, 25, 27,
	 4,  6, 20, 22,
	 5,  7, 21, 23,
	12, 14, 28, 30,
	13, 15, 29, 31,
};
static uint8 blockmap_PSMZ32[32] = {
	24, 25, 28, 29,  8,  9, 12, 13,
	26, 27, 30, 31, 10, 11, 14, 15,
	16, 17, 20, 21,  0,  1,  4,  5,
	18, 19, 22, 23,  2,  3,  6,  7,
};
static uint8 blockmap_PSMZ16[32] = {
	24, 26, 16, 18,
	25, 27, 17, 19,
	28, 30, 20, 22,
	29, 31, 21, 23,
	 8, 10,  0,  2,
	 9, 11,  1,  3,
	12, 14,  4,  6,
	13, 15,  5,  7,
};
static uint8 blockmap_PSMZ16S[32] = {
	24, 26,  8, 10,
	25, 27,  9, 11,
	16, 18,  0,  2,
	17, 19,  1,  3,
	28, 30, 12, 14,
	29, 31, 13, 15,
	20, 22,  4,  6,
	21, 23,  5,  7,
};

static uint8 blockmaprev_PSMCT32[32] = {
	 0,  1,  8,  9,  2,  3, 10, 11,
	16, 17, 24, 25, 18, 19, 26, 27,
	 4,  5, 12, 13,  6,  7, 14, 15,
	20, 21, 28, 29, 22, 23, 30, 31,
};
static uint8 blockmaprev_PSMCT16[32] = {
	 0,  4,  1,  5,
	 8, 12,  9, 13,
	 2,  6,  3,  7,
	10, 14, 11, 15,
	16, 20, 17, 21,
	24, 28, 25, 29,
	18, 22, 19, 23,
	26, 30, 27, 31,
};

/* Suffixes used:
 * _Px - pixels
 * _W - width units (pixels/64)
 * _B - blocks
 * _P - pages
 */

/* Layout mipmaps and palette in GS memory */
static void
calcOffsets(int32 width_Px, int32 height_Px, int32 psm, uint64 *bufferBase_B, uint64 *bufferWidth_W, uint32 *trxpos, uint32 *totalSize, uint32 *paletteBase)
{
	uint32 pageWidth_Px, pageHeight_Px;
	uint32 blockWidth_Px, blockHeight_Px;
	uint32 mindim_Px;
	int32 nlevels;
	int32 n;
	uint32 mipw_Px, miph_Px;
	uint32 lastmipw_Px, lastmiph_Px;
	uint32 bufferHeight_P[8];
	uint32 bufferPage_B[8];	// address of page in which the level is allocated
	uint32 xoff_Px, yoff_Px;	// x/y offset the last level starts at
	// Whenever we allocate horizontally inside a page,
	//  keep track of the region below it on this stack.
	uint32 sp;
	uint32 xoffstack_Px[8];	// actually unused...
	uint32 widthstack_Px[8];
	uint32 heightstack_Px[8];
	uint32 basestack_B[8];
	uint32 flag;

	switch(psm){
	case PSMCT32:
	case PSMCT24:
	case PSMT8H:
	case PSMT4HL:
	case PSMT4HH:
	case PSMZ32:
	case PSMZ24:
		pageWidth_Px = 64;
		pageHeight_Px = 32;
		blockWidth_Px = 8;
		blockHeight_Px = 8;
		break;
	case PSMT8:
		pageWidth_Px = 128;
		pageHeight_Px = 64;
		blockWidth_Px = 16;
		blockHeight_Px = 16;
		break;
	case PSMT4:
		pageWidth_Px = 128;
		pageHeight_Px = 128;
		blockWidth_Px = 32;
		blockHeight_Px = 16;
		break;
	case PSMCT16:
	case PSMCT16S:
	case PSMZ16:
	case PSMZ16S:
	default:
		pageWidth_Px = 64;
		pageHeight_Px = 64;
		blockWidth_Px = 16;
		blockHeight_Px = 8;
		break;
	}

	mindim_Px = min(width_Px, height_Px);
	for(nlevels = 1; mindim_Px > 8; nlevels++){
		if(nlevels >= maxMipLevels)
			break;
		mindim_Px /= 2;
	}

#define PAGEWIDTH_B (pageWidth_Px/blockWidth_Px)	// number of horizontal blocks per page
#define NBLKX(dim) (NSIZE((dim), blockWidth_Px))
#define NBLKY(dim) (NSIZE((dim), blockHeight_Px))
#define NPGX(dim) (NSIZE((dim), pageWidth_Px))
#define NPGY(dim) (NSIZE((dim), pageHeight_Px))
#define REALWIDTH(w) (max((w), blockWidth_Px))
#define REALHEIGHT(w) (max((w), blockHeight_Px))

	bufferBase_B[0] = 0;
	bufferWidth_W[0] = NPGX(width_Px)*pageWidth_Px/64;
	bufferHeight_P[0] = NPGY(height_Px);
	bufferPage_B[0] = 0;
	lastmipw_Px = width_Px;
	lastmiph_Px = height_Px;
	sp = 0;
	xoff_Px = 0;
	yoff_Px = 0;
	flag = 0;
	// Calculate info for all mipmap levels.
	// mipwidth/height are actually the dimensions of level n-1!
	// This code was reversed from RW and is rather complicated...
	// partially because it's not clear what the assumptions are,
	// can width/height be non-powers of 2?
	for(n = 1; n < nlevels; n++){
		mipw_Px = lastmipw_Px/2;
		miph_Px = lastmiph_Px/2;
		if(lastmipw_Px >= pageWidth_Px){
			if(lastmiph_Px >= pageHeight_Px){
				// CASE 0
				// We allocate full pages
				// This is the only place bufferWidth can change. Similarly bufferBase_2, which is related
				bufferBase_B[n] = bufferBase_B[n-1] + (lastmipw_Px/blockWidth_Px)*(lastmiph_Px/blockHeight_Px);
				bufferPage_B[n] = bufferBase_B[n];
				bufferWidth_W[n] = NPGX(mipw_Px)*pageWidth_Px/64;
				bufferHeight_P[n] = NPGY(miph_Px);
				xoff_Px = 0;
				yoff_Px = 0;
			}else{
				// CASE 1
				// Allocate vertically in the current page
				bufferPage_B[n] = bufferPage_B[n-1];
				bufferHeight_P[n] = bufferHeight_P[n-1];
				bufferWidth_W[n] = bufferWidth_W[n-1];
				// How do we know pageHeight - yoff - REALHEIGHT(lastmiph) >= miph?
				// And how is this condition ever false?
				// Assuming lastmipw >= pageWidth for any number of levels, lastmiph must be pageHeight/2
				// or lower to reach this code. No dimension is lower than 8. Then consequent mipmaps
				// will have heights halved but even with PSMT4 we will only (vertically) fill the
				// page with the last mipmap and not go beyond...
				if(REALHEIGHT(lastmiph_Px) + yoff_Px < pageHeight_Px){
					// CASE 2
					yoff_Px += REALHEIGHT(lastmiph_Px);
					bufferBase_B[n] = bufferBase_B[n-1] +
						PAGEWIDTH_B * NBLKY(lastmiph_Px) *
						bufferWidth_W[n]*64/pageWidth_Px;	// number of horizontal pages for level
				}else{
					// CASE 3
					// Can this happen?
					xoff_Px += REALWIDTH(lastmipw_Px);
					bufferBase_B[n] = bufferBase_B[n-1] + NBLKX(lastmipw_Px);
				}
			}
		}else if(lastmiph_Px >= pageHeight_Px){
			// CASE 4
			// Allocate horizontally
			bufferPage_B[n] = bufferPage_B[n-1];
			bufferHeight_P[n] = bufferHeight_P[n-1];
			bufferWidth_W[n] = bufferWidth_W[n-1];
			if(REALWIDTH(lastmipw_Px) + xoff_Px < pageWidth_Px){
				// CASE 5
				xoffstack_Px[sp] = xoff_Px;	// unused...
				heightstack_Px[sp] = REALHEIGHT(lastmiph_Px);
				widthstack_Px[sp] = REALWIDTH(lastmipw_Px);
				basestack_B[sp] = bufferBase_B[n-1] +  NBLKY(lastmiph_Px) * PAGEWIDTH_B;
				sp++;
				xoff_Px += REALWIDTH(lastmipw_Px);
				bufferBase_B[n] = bufferBase_B[n-1] + NBLKX(lastmipw_Px);
			}else if(sp){
				// CASE 7
				bufferBase_B[n] = basestack_B[sp-1];
				if(REALWIDTH(mipw_Px) < widthstack_Px[sp-1]){
					// CASE 9
					basestack_B[sp-1] += NBLKX(mipw_Px);
					widthstack_Px[sp-1] -= REALWIDTH(mipw_Px);
				}else if(REALHEIGHT(miph_Px) < heightstack_Px[sp-1]){
					// CASE 8
					basestack_B[sp-1] += NBLKY(miph_Px) * PAGEWIDTH_B;
					heightstack_Px[sp-1] -= REALHEIGHT(miph_Px);
				}else{
					// CASE 10
					sp--;
				}
				flag = 1;
			}else{
				// CASE 6
				yoff_Px += REALHEIGHT(lastmiph_Px);
				bufferBase_B[n] = bufferBase_B[n-1] + PAGEWIDTH_B*NBLKY(lastmiph_Px);
			}
		}else{
			// CASE 11
			bufferHeight_P[n] = bufferHeight_P[n-1];
			bufferPage_B[n] = bufferPage_B[n-1];
			bufferWidth_W[n] = bufferWidth_W[n-1];
			if(REALWIDTH(lastmipw_Px) + xoff_Px < bufferWidth_W[n-1]*64){
				// CASE 12
				xoffstack_Px[sp] = xoff_Px;	// unused...
				widthstack_Px[sp] = REALWIDTH(lastmipw_Px);
				heightstack_Px[sp] = REALHEIGHT(lastmiph_Px);
				basestack_B[sp] = bufferBase_B[n-1] + PAGEWIDTH_B * NBLKY(lastmiph_Px);
				sp++;
				xoff_Px += REALWIDTH(lastmipw_Px);
				bufferBase_B[n] = bufferBase_B[n-1] + NBLKX(lastmipw_Px);
			}else if(REALHEIGHT(lastmiph_Px) + yoff_Px < pageHeight_Px*bufferHeight_P[n] && flag == 0){
				// CASE 13
				bufferBase_B[n] = bufferBase_B[n-1] + PAGEWIDTH_B * NBLKY(lastmiph_Px);
				yoff_Px += blockHeight_Px ? lastmiph_Px : 0;	// how exactly can blockHeight be 0?? This looks wrong...
				flag = n;
			}else{
				// CASE 14
				bufferBase_B[n] = basestack_B[sp-1];
				if(REALWIDTH(mipw_Px) < widthstack_Px[sp-1]){
					// CASE 15
					basestack_B[sp-1] += NBLKX(mipw_Px);
					widthstack_Px[sp-1] -= REALWIDTH(mipw_Px);
				}else if(REALHEIGHT(miph_Px) < heightstack_Px[sp-1]){
					// CASE 16
					basestack_B[sp-1] += PAGEWIDTH_B * NBLKY(miph_Px);
					heightstack_Px[sp-1] -= REALHEIGHT(miph_Px);
				}else{
					// CASE 17
					sp--;
				}
			}
		}
		lastmipw_Px = mipw_Px;
		lastmiph_Px = miph_Px;
	}

	// Calculate position of palette.
	uint32 paletteBase_B = 0;
	uint64 bufwidth_Px = bufferWidth_W[nlevels-1]*64;
	uint64 bufheight_Px = bufferHeight_P[nlevels-1]*pageHeight_Px;
	// != means > really
	if(bufwidth_Px != lastmipw_Px || bufheight_Px != lastmiph_Px){
		if(psm == PSMT8){
			// 2x2 blocks at the end of the page (even for PSMCT16S)
			paletteBase_B = bufferPage_B[nlevels-1] +
				((bufwidth_Px/pageWidth_Px)*bufferHeight_P[nlevels-1] << 5)	// total number of blocks
				- (bufheight_Px/pageWidth_Px) * PAGEWIDTH_B	// one block up
				- 2;	// two blocks left
		}else if(psm == PSMT4){
			// One block at the end of the page
			paletteBase_B = bufferPage_B[nlevels-1] +
				((bufwidth_Px/pageWidth_Px) * bufferHeight_P[nlevels-1] << 5)
				- 1;
		}
	}else{
		if(psm == PSMT8 || psm == PSMT4){
			paletteBase_B = bufferPage_B[nlevels-1] +
				(bufwidth_Px/blockWidth_Px) * (bufheight_Px/blockHeight_Px);
		}
	}

	uint32 bufwidth_W = bufferWidth_W[0];
	uint32 bufpage_B = bufferPage_B[0];
	uint32 pixeloff;
	for(n = 0; n < nlevels; n++){
		// Calculate TRXPOS register (DSAX and DSAY, shifted up later)
		// Start of buffer on current page (x in pixels, y in blocks)
		pixeloff = (bufferBase_B[n] - bufpage_B) * blockWidth_Px;
		// y coordinate of first pixel
		yoff_Px = (pixeloff / (bufwidth_W*64)) * blockHeight_Px;
		// x coordinate of first pixel
		xoff_Px =  pixeloff % (bufwidth_W*64);
		if(bufferWidth_W[n] == bufwidth_W &&
		   // Not quite sure what's the meaning of this.
		   // DSAY is 11 bits, but so is DSAX and it is not checked?
		   yoff_Px < 0x800){
			trxpos[n] = yoff_Px<<16 | xoff_Px;
		}else{
			bufwidth_W = bufferWidth_W[n];
			bufpage_B = bufferPage_B[n];
			trxpos[n] = 0;
		}

		// If using more than one page we have to swizzle rows inside page rows
		if(bufwidth_W*64 / pageWidth_Px > 1){
			uint32 bufpagestride_B = bufwidth_W*64 * 32 / pageWidth_Px;	// one row of pages
			uint32 bufwidth_B = bufwidth_W*64 / blockWidth_Px;	// one row of blocks
			// To illustrate assume:
			// - 8x4 block pages
			// - texture is 4 pages wide
			// Then the lower bits of an input block address look like: RRRPPCC
			// where the C bits are the block's column inside a page
			//       the P bits are the block's page horizontally
			//       the R bits are the block's row in a row of pages
			// We want to swap P and R: PPRRRCC
			bufferBase_B[n] =
				(bufferBase_B[n] & ~((uint64)bufpagestride_B - PAGEWIDTH_B))	// mask out R and P
				| ((bufferBase_B[n] & (bufwidth_B - PAGEWIDTH_B)) * (bufpagestride_B/bufwidth_B))	// extract P and shift left
				| ((bufferBase_B[n] & (bufpagestride_B - bufwidth_B)) / (bufwidth_B/PAGEWIDTH_B));	// extract R and shift right
		}

		// Always have to swizzle blocks inside pages. We use a lookup, RW does bit operations
		switch(psm){
		case PSMCT32:
		case PSMCT24:
		case PSMT8:
		case PSMT8H:
		case PSMT4HL:
		case PSMT4HH:
			// ABCDE -> CADBE
			bufferBase_B[n] = (bufferBase_B[n]&~0x1F) | (uint64)blockmap_PSMCT32[bufferBase_B[n]&0x1F];
			break;
		case PSMT4:
		case PSMCT16:
			// ABCDE -> ADBEC
			bufferBase_B[n] = (bufferBase_B[n]&~0x1F) | (uint64)blockmap_PSMCT16[bufferBase_B[n]&0x1F];
			break;
		case PSMCT16S:
			// ABCDE -> DBAEC
			bufferBase_B[n] = (bufferBase_B[n]&~0x1F) | (uint64)blockmap_PSMCT16S[bufferBase_B[n]&0x1F];
			break;
		case PSMZ32:
		case PSMZ24:
			// ABCDE -> ~C~ADBE
			bufferBase_B[n] = (bufferBase_B[n]&~0x1F) | (uint64)blockmap_PSMZ32[bufferBase_B[n]&0x1F];
			break;
		case PSMZ16:
			// ABCDE -> ~A~DBEC
			bufferBase_B[n] = (bufferBase_B[n]&~0x1F) | (uint64)blockmap_PSMZ16[bufferBase_B[n]&0x1F];
			break;
		case PSMZ16S:
			// ABCDE -> ~D~BAEC
			bufferBase_B[n] = (bufferBase_B[n]&~0x1F) | (uint64)blockmap_PSMZ16S[bufferBase_B[n]&0x1F];
			break;
		default: break;
		}
	}

	// Same dance as above, with the palette
	if(bufwidth_W*64 / pageWidth_Px > 1){
		uint32 bufpagestride_B = bufwidth_W*64 * 32 / pageWidth_Px;	// one row of pages
		uint32 bufwidth_B = bufwidth_W*64 / blockWidth_Px;	// one row of blocks
		paletteBase_B =
			(paletteBase_B & ~(bufpagestride_B - PAGEWIDTH_B))	// mask out R and P
			| ((paletteBase_B & (bufwidth_B - PAGEWIDTH_B)) * (bufpagestride_B/bufwidth_B))	// extract P and shift left
			| ((paletteBase_B & (bufpagestride_B - bufwidth_B)) / (bufwidth_B/PAGEWIDTH_B));	// extract R and shift right
	}
	switch(psm){
	case PSMCT32:
	case PSMCT24:
	case PSMT8:
	case PSMT8H:
	case PSMT4HL:
	case PSMT4HH:
		paletteBase_B = (paletteBase_B&~0x1F) | (uint64)blockmap_PSMCT32[paletteBase_B&0x1F];
		break;
	case PSMT4:
	case PSMCT16:
		paletteBase_B = (paletteBase_B&~0x1F) | (uint64)blockmap_PSMCT16[paletteBase_B&0x1F];
		break;
	case PSMCT16S:
		paletteBase_B = (paletteBase_B&~0x1F) | (uint64)blockmap_PSMCT16S[paletteBase_B&0x1F];
		break;
	case PSMZ32:
	case PSMZ24:
		paletteBase_B = (paletteBase_B&~0x1F) | (uint64)blockmap_PSMZ32[paletteBase_B&0x1F];
		break;
	case PSMZ16:
		paletteBase_B = (paletteBase_B&~0x1F) | (uint64)blockmap_PSMZ16[paletteBase_B&0x1F];
		break;
	case PSMZ16S:
		paletteBase_B = (paletteBase_B&~0x1F) | (uint64)blockmap_PSMZ16S[paletteBase_B&0x1F];
		break;
	default: break;
	}
	*paletteBase = paletteBase_B;
	*totalSize = bufferPage_B[nlevels-1] +	// start of last buffer`
		bufferWidth_W[nlevels-1]*64/blockWidth_Px *	// number of horizontal blocks in last level
		pageHeight_Px*bufferHeight_P[nlevels-1]/blockHeight_Px;	// number of vertical blocks in last level
	*totalSize *= 64;	// to words

#undef BLKSTRIDE
#undef NBLKX
#undef NBLKY
#undef NPGX
#undef NPGY
#undef REALWIDTH
#undef REALHEIGHT
}

static Raster*
rasterCreateTexture(Raster *raster)
{
	// We use a map for fast lookup, even for impossible depths
	static int32 pageWidths[32] = {
		128, 128, 128, 128,
		128, 128, 128, 128,
		64, 64, 64, 64, 64, 64, 64, 64,
		64, 64, 64, 64, 64, 64, 64, 64,
		64, 64, 64, 64, 64, 64, 64, 64,
	};
	static int32 pageHeights[32] = {
		128, 128, 128, 128,
		64, 64, 64, 64,
		64, 64, 64, 64, 64, 64, 64, 64,
		32, 32, 32, 32, 32, 32, 32, 32,
		32, 32, 32, 32, 32, 32, 32, 32,
	};
	enum {
		TCC_RGBA = 1 << 2,
		CLD_1 = 1 << 29,

		WD2BLK = 64,	// words per block
		WD2PG = 2048,	// words per page
	};
	int32 pixelformat, palformat;
	// TEX0 fields (not all)
	int64 tbw	= 0;	// texture buffer width, texels/64
	int64 psm	= 0;	// pixel storage mode
	int64 tw	= 0;	// texture width exponent, width = 2^tw
	int64 th	= 0;	// texture height exponent, height = 2^th
	int64 tcc	= 0;	// texture color component, 0 = rgb, 1 = rgba
	int64 cpsm	= 0;	// CLUT pixel storage mode
	int64 cld	= 0;	// CLUT buffer load control

	uint64 bufferWidth[7];	// in number of pixels / 64
	uint64 bufferBase[7];	// block address
	uint32 trxpos_hi[8];
	int32 width, height, depth;
	int32 pageWidth, pageHeight;
	int32 paletteWidth, paletteHeight, paletteDepth;
	int32 palettePagewidth, palettePageheight;


	Ps2Raster *ras = GETPS2RASTEREXT(raster);
	pixelformat = raster->format & 0xF00;
	palformat = raster->format & 0x6000;
	width = raster->width;
	height = raster->height;
	depth = raster->depth;
	// RW's code does not seem to quite work with 24 bit rasters
	// so make sure we're not generating them for safety
	assert(depth != 24);

	ras->flags = 0;
	ras->data = nil;
	ras->dataSize = 0;

	// RW doesn't seem to check this, hm...
	if(raster->flags & Raster::DONTALLOCATE)
		return raster;

	//printf("%x %x %x %x\n", raster->format, raster->flags, raster->type, noNewStyleRasters);
	pageWidth = pageWidths[depth-1];
	pageHeight = pageHeights[depth-1];

	int32 s;
	tw = 0;
	for(s = 1; s < width; s *= 2)
		tw++;
	th = 0;
	for(s = 1; s < height; s *= 2)
		th++;
	ras->kl = defaultMipMapKL;
	// unk2[0] = 1
	//printf("%d %d %d %d\n", raster->width, logw, raster->height, logh);

	// round up to page width, set TBW, TW, TH
	tbw = max(width,pageWidth)/64;

	// set PSM, TCC, CLD, CPSM and figure out palette format
	if(palformat){
		if(palformat == Raster::PAL8){
			psm = PSMT8;
			paletteWidth = 16;
			paletteHeight = 16;
		}else if(palformat == Raster::PAL4){
			psm = PSMT4;
			paletteWidth = 8;
			paletteHeight = 2;
		}else{
			// can't happen, sanity check in getRasterFormat
			return nil;;
		}
		tcc = 1;	// RGBA
		cld = 1;
		if(pixelformat == Raster::C1555){
			paletteDepth = 2;
			cpsm = PSMCT16S;
			palettePagewidth = 64;
			palettePageheight = 64;
		}else if(pixelformat == Raster::C8888){
			paletteDepth = 4;
			cpsm = PSMCT32;
			palettePagewidth = 64;
			palettePageheight = 32;
		}else
			// can't happen, sanity check in getRasterFormat
			return nil;;
	}else{
		paletteWidth = 0;
		paletteHeight = 0;
		paletteDepth = 0;
		palettePagewidth = 0;
		palettePageheight = 0;
		if(pixelformat == Raster::C8888){
			psm = PSMCT32;
			tcc = 1;	// RGBA
		}else if(pixelformat == Raster::C888){
			psm = PSMCT24;
			tcc = 0;	// RGB
		}else if(pixelformat == Raster::C1555){
			psm = PSMCT16S;
			tcc = 1;	// RGBA
		}else
			// can't happen, sanity check in getRasterFormat
			return nil;;
	}

	for(int i = 0; i < 7; i++){
		bufferWidth[i] = 1;
		bufferBase[i] = 0;
	}

	int32 mipw, miph;
	int32 w, h;
	int32 n;
	int32 nPagW, nPagH;
	raster->stride = width*depth/8;

	if(raster->format & Raster::MIPMAP){
		// NOTE: much of this code seems to be totally useless.
		// calcOffsets overwrites what we calculate here. I wonder
		// why this code even is in RW. Maybe it's older code that used
		// the GS' automatic base pointer calculation?

		// see the left columns in the maps above
		static uint32 blockOffset32_24_8[8] = { 0, 2, 2, 8, 8, 10, 10, 32 };
		static uint32 blockOffset16_4[8] = { 0, 1, 4, 5, 16, 17, 20, 21 };
		static uint32 blockOffset16S[8] = { 0, 1, 8, 9, 4, 5, 12, 13 };
		uint64 lastBufferWidth;
		mipw = width;
		miph = height;
		lastBufferWidth = max(pageWidth, width)/64;
		ras->pixelSize = 0;
		int32 lastaddress = 0;	// word address
		int32 nextaddress = 0;	// word address
		int32 stride;	// in bytes
		for(n = 0; mipw != 0 && miph != 0 && n < maxMipLevels; n++){
			if(width >= 8 && height >= 8 && (mipw < 8 || miph < 8))
				break;
			ras->pixelSize += ALIGN64(mipw*miph*depth/8);
			bufferWidth[n] = max(pageWidth, mipw)/64;
			stride = bufferWidth[n]*64*depth/8;

			// If buffer width changes, align next address to page
			if(bufferWidth[n] != lastBufferWidth){
				nPagW = ((width >> (n-1)) + pageWidth-1)/pageWidth;
				nPagH = ((height >> (n-1)) + pageHeight-1)/pageHeight;
				nextaddress = (lastaddress + nPagW*nPagH*WD2PG) & ~(WD2PG-1);
			}
			lastBufferWidth = bufferWidth[n];
			nextaddress = ALIGN64(nextaddress);	// this should already be the case...
			uint32 b = nextaddress>>(11-3) & 7;	// upper three bits of block-in-page address
			switch(psm){
			case PSMCT32:
			case PSMCT24:	
			case PSMT8:
				b = blockOffset32_24_8[b];
				break;
			case PSMCT16:
			case PSMT4:
				b = blockOffset16_4[b];
				break;
			case PSMCT16S:
				b = blockOffset16S[b];
				break;
			default:
				// can't happen
				break;
			}
			// shift to page address, then to block address and add offset inside page
			bufferBase[n] = b + (nextaddress>>11 << 5);

			lastaddress = nextaddress;
			nextaddress = ALIGN64(miph*stride/4 + lastaddress);

			mipw /= 2;
			miph /= 2;
		}

		// Do the real work here
		uint32 paletteBase;
		uint32 totalSize;
		calcOffsets(width, height, psm, bufferBase, bufferWidth, trxpos_hi, &totalSize, &paletteBase);

		ras->paletteSize = paletteWidth*paletteHeight*paletteDepth;
		ras->miptbp1 = 
			  bufferWidth[1]<<14 | (bufferBase[1] & 0x3FFF)<<0
			| bufferWidth[2]<<34 | (bufferBase[2] & 0x3FFF)<<20
			| bufferWidth[3]<<54 | (bufferBase[3] & 0x3FFF)<<40;
		ras->miptbp2 = 
			  bufferWidth[4]<<14 | (bufferBase[4] & 0x3FFF)<<0
			| bufferWidth[5]<<34 | (bufferBase[5] & 0x3FFF)<<20
			| bufferWidth[6]<<54 | (bufferBase[6] & 0x3FFF)<<40;
		ras->tex1low = (n-1)<<2;
		ras->totalSize = totalSize;
		if(ras->paletteSize){
			ras->paletteBase = paletteBase;
			if(ras->paletteBase*64 == ras->totalSize)
				ras->totalSize += WD2PG;
		}else
			ras->paletteBase = 0;
	}else{
		// No mipmaps

		ras->pixelSize = ALIGN16(raster->stride*raster->height);
		ras->paletteSize = paletteWidth*paletteHeight*paletteDepth;
		ras->miptbp1 = 1ULL<<54 | 1ULL<<34 | 1ULL<<14;
		ras->miptbp2 = 1ULL<<54 | 1ULL<<34 | 1ULL<<14;
		ras->tex1low = 0;	// one mipmap level

		// find out number of pages needed
		nPagW = (width + pageWidth-1)/pageWidth;
		nPagH = (height + pageHeight-1)/pageHeight;

		// calculate buffer width in units of pixels/64
		bufferBase[0] = 0;
		trxpos_hi[0] = 0;
		bufferWidth[0] = nPagW*pageWidth / 64;

		// calculate whole buffer size in words
		ras->totalSize = nPagW*nPagH*WD2PG;

		// calculate palette offset on GS in units of words/64
		if(ras->paletteSize){
			// Maximum palette size is 256 words.
			// If there is still room, use it!
			// If dimensions don't fill a page, we have at least
			// half a page left, enough for any palette
//TODO: this was not always done it seems but even gta3's 3.1 seems to??
			if(pageWidth*nPagW > width ||
			   pageHeight*nPagH > height){
				ras->paletteBase = (ras->totalSize - 256) / WD2BLK;
			}else{
				// Otherwise allocate more space...
				ras->paletteBase = ras->totalSize / WD2BLK;
				// ...using the same calculation as above.
				// WHY? we never need more than one page!
				nPagW = (paletteWidth + palettePagewidth-1)/palettePagewidth;
				nPagH = (paletteHeight + palettePageheight-1)/palettePageheight;
				ras->totalSize += nPagW*nPagH*WD2PG;
			}
		}else
			ras->paletteBase = 0;
	}
	ras->tex0 = tbw << 14 |
		psm << 20 |
		tw << 26 |
		th << 30 |
		tcc << 34 |
		cpsm << 51 |
		0ULL << 55 |	// csm0
		0ULL << 56 |	// entry offset
		cld << 61;



	// allocate data and fill with GIF packets
	ras->pixelSize = ALIGN16(ras->pixelSize);
	int32 numLevels = MAXLEVEL(ras)+1;
	// No GIF packet because we either don't want it (pre 0x310 rasters)
	// or the data wouldn't fit into a DMA packet
	if(noNewStyleRasters ||
	   (raster->width*raster->height*raster->depth/8/0x10) >= 0x7FFF){
		ras->dataSize = ras->paletteSize+ras->pixelSize;
		uint8 *data = (uint8*)mallocalign(ras->dataSize, 0x40);
		assert(data);
		ras->data = data;
		raster->pixels = data;
		if(ras->paletteSize)
			raster->palette = data + ras->pixelSize;
		if(raster->depth == 8)
			ras->flags |= Ps2Raster::SWIZZLED8;
	}else{
		ras->flags |= Ps2Raster::NEWSTYLE;
		uint64 paltrxpos = 0;
		uint32 dsax = trxpos_hi[numLevels-1] & 0x7FF;
		uint32 dsay = trxpos_hi[numLevels-1]>>16 & 0x7FF;
		// Set swizzle flags and calculate TRXPOS for palette
		if(psm == PSMT8){
			ras->flags |= Ps2Raster::SWIZZLED8;
			if(cpsm == PSMCT32 && bufferWidth[numLevels-1] == 2){	// one page
				// unswizzle the starting block of the last buffer and palette
				uint32 bufbase_B = (bufferBase[numLevels-1]&~0x1F) | (uint64)blockmaprev_PSMCT32[bufferBase[numLevels-1]&0x1F];
				uint32 palbase_B = (ras->paletteBase&~0x1F) | (uint64)blockmaprev_PSMCT32[ras->paletteBase&0x1F];
				// find start of page of last level (16,16 are PSMT8 block dimensions)
				uint32 page_B = bufbase_B - 8*(dsay/16) - dsax/16;
				// find palette DSAX/Y (in PSMCT32!)
				dsay = (palbase_B - page_B)/8 * 8;	// block/blocksPerPageX * blockHeight
				dsax = (palbase_B - page_B)*8 % 64;	// block*blockWidth % pageWidth
				if(dsay < 0x800)
					paltrxpos = dsay<<16 | dsax;
			}
		}
		if(psm == PSMT4){
			// swizzle flag depends on version :/
			// but which version? ....
			if(rw::version > 0x31000){
				ras->flags |= Ps2Raster::SWIZZLED4;
				// Where can this come from? if anything we're using PSMCT16S
				// Looks like they wanted to swizzle palettes too...
				if(cpsm == PSMCT16){
					// unswizzle the starting block of the last buffer and palette
					uint32 bufbase_B = (bufferBase[numLevels-1]&~0x1F) | (uint64)blockmaprev_PSMCT16[bufferBase[numLevels-1]&0x1F];
					uint32 palbase_B = (ras->paletteBase&~0x1F) | (uint64)blockmaprev_PSMCT16[ras->paletteBase&0x1F];
					// find start of page of last level (32,16 are PSMT4 block dimensions)
					uint32 page_B = bufbase_B - 4*(dsay/32) - dsax/16;
					// find palette DSAX/Y (in PSMCT16!)
					dsay = (palbase_B - page_B)/4 * 8;	// block/blocksPerPageX * blockHeight
					dsax = (palbase_B - page_B)*16 % 128;	// block*blockWidth % pageWidth
					if(dsay < 0x800)
						paltrxpos = dsay<<16 | dsax;
				}
			}
		}
		ras->pixelSize = 0x50*numLevels;	// GIF packets
		int32 minW, minH;
		transferMinSize(psm, ras->flags, &minW, &minH);
		w = raster->width;
		h = raster->height;
		n = numLevels;
		while(n--){
			mipw = max(w, minW);
			miph = max(h, minH);
			ras->pixelSize += ALIGN16(mipw*miph*raster->depth/8);
			w /= 2;
			h /= 2;
		}
		if(ras->paletteSize){
			if(rw::version > 0x31000 && paletteHeight == 2)
				paletteHeight = 3;
			ras->paletteSize = 0x50 +
			    paletteDepth*paletteWidth*paletteHeight;
		}
		// One transfer per buffer width, 4 qwords:
		// DMAcnt(2) [NOP, DIRECT]
		//   GIF tag  A+D
		//     BITBLTBUF
		// DMAref(pixel data) [NOP, DIRECT]
		uint32 extrasize = 0x10;	// PixelPtr
		int32 numTransfers = 0;
		for(n = 0; n < numLevels; n++)
			if(trxpos_hi[n] == 0){
				extrasize += 0x40;
				numTransfers++;
			}
		if(ras->paletteSize){
			extrasize += 0x40;
			numTransfers++;
		}
		// What happens here?
		if(ras->paletteSize && paltrxpos == 0)
			ras->dataSize = ALIGN(ras->pixelSize,128) + ALIGN(ras->paletteSize,64) + extrasize + 0x70;
		else
			ras->dataSize = ALIGN(ras->paletteSize+ras->pixelSize,64) + extrasize + 0x70;
		uint8 *data = (uint8*)mallocalign(ras->dataSize, 0x40);
		uint32 *xferchain = (uint32*)(data + 0x10);
		assert(data);
		ras->data = data;
		Ps2Raster::PixelPtr *pp = (Ps2Raster::PixelPtr*)data;
		pp->numTransfers = numTransfers;
		pp->numTotalTransfers = numTransfers;
		pp->pixels = (uint8*)ALIGN((uintptr)data + extrasize, 128);
		raster->pixels = (uint8*)pp;
		if(ras->paletteSize)
			raster->palette = pp->pixels + ALIGN(ras->pixelSize, 128) + 0x50;
		uint32 *p = (uint32*)pp->pixels;
		w = raster->width;
		h = raster->height;
		for(n = 0; n < numLevels; n++){
			mipw = max(w, minW);
			miph = max(h, minH);

			// GIF tag
			*p++ = 3;          // NLOOP = 3
			*p++ = 0x10000000; // NREG = 1
			*p++ = 0xE;        // A+D
			*p++ = 0;

			// TRXPOS
			if((ras->flags & Ps2Raster::SWIZZLED8 && psm == PSMT8) ||
			   (ras->flags & Ps2Raster::SWIZZLED4 && psm == PSMT4)){
				*p++ = 0;	// SSAX/Y is always 0
				*p++ = (trxpos_hi[n] & ~0x10001)/2;	// divide both DSAX/Y by 2
			}else{
				*p++ = 0;
				*p++ = trxpos_hi[n];
			}
			*p++ = 0x51;
			*p++ = 0;

			// TRXREG
			if((ras->flags & Ps2Raster::SWIZZLED8 && psm == PSMT8) ||
			   (ras->flags & Ps2Raster::SWIZZLED4 && psm == PSMT4)){
				*p++ = mipw/2;
				*p++ = miph/2;
			}else{
				*p++ = mipw;
				*p++ = miph;
			}
			*p++ = 0x52;
			*p++ = 0;

			// TRXDIR
			*p++ = 0;	// host -> local
			*p++ = 0;
			*p++ = 0x53;
			*p++ = 0;

			// GIF tag
			uint32 sz = ALIGN16(mipw*miph*raster->depth/8)/16;
			*p++ = sz & 0x7FFF;
			*p++ = 0x08000000; // IMAGE
			*p++ = 0;
			*p++ = 0;

			if(trxpos_hi[n] == 0){
				// Add a transfer, see above for layout

				*xferchain++ = 0x10000002;	// DMAcnt, 2 qwords
				*xferchain++ = 0;
				*xferchain++ = 0;		// VIF nop
				*xferchain++ = 0x50000002;	// VIF DIRECT 2 qwords

				// GIF tag
				*xferchain++ = 1;          // NLOOP = 1
				*xferchain++ = 0x10000000; // NREG = 1
				*xferchain++ = 0xE;        // A+D
				*xferchain++ = 0;

				// BITBLTBUF
				if(ras->flags & Ps2Raster::SWIZZLED8 && psm == PSMT8){
					// PSMT8 is swizzled to PSMCT32 and dimensions are halved
					*xferchain++ = PSMCT32<<24 | bufferWidth[n]/2<<16;	// src buffer
					*xferchain++ = PSMCT32<<24 | bufferWidth[n]/2<<16 | bufferBase[n];	// dst buffer
				}else if(ras->flags & Ps2Raster::SWIZZLED4 && psm == PSMT4){
					// PSMT4 is swizzled to PSMCT16 and dimensions are halved
					*xferchain++ = PSMCT16<<24 | bufferWidth[n]/2<<16;	// src buffer
					*xferchain++ = PSMCT16<<24 | bufferWidth[n]/2<<16 | bufferBase[n];	// dst buffer
				}else{
					*xferchain++ = psm<<24 | bufferWidth[n]<<16;	// src buffer
					*xferchain++ = psm<<24 | bufferWidth[n]<<16 | bufferBase[n];	// dst buffer
				}
				*xferchain++ = 0x50;
				*xferchain++ = 0;

				*xferchain++ = 0x30000000 | sz+5;	// DMAref
				// this obviously only works with 32 bit pointers, but it's only needed on the PS2 anyway
				*xferchain++ = (uint32)(uintptr)p - 0x50;
				*xferchain++ = 0;		// VIF nop
				*xferchain++ = 0x50000000 | sz+5;	// VIF DIRECT
			}else{
				// Add to existing transfer
				xferchain[-4] = 0x30000000 | (xferchain[-4]&0xFFFF) + sz+5;	// last DMAref
				xferchain[-1] = 0x50000000 | (xferchain[-1]&0xFFFF) + sz+5;	// last DIRECT
			}

			p += sz*4;
			w /= 2;
			h /= 2;
		}

		if(ras->paletteSize){
			// huh?
			if(paltrxpos)
				raster->palette = (uint8*)p + 0x50;
			p = (uint32*)(raster->palette - 0x50);
			// GIF tag
			*p++ = 3;          // NLOOP = 3
			*p++ = 0x10000000; // NREG = 1
			*p++ = 0xE;        // A+D
			*p++ = 0;

			// TRXPOS
			*(uint64*)p = (uint64)paltrxpos<<32;
			p += 2;
			*p++ = 0x51;
			*p++ = 0;

			// TRXREG
			*p++ = paletteWidth;
			*p++ = paletteHeight;
			*p++ = 0x52;
			*p++ = 0;

			// TRXDIR
			*p++ = 0;	// host -> local
			*p++ = 0;
			*p++ = 0x53;
			*p++ = 0;

			// GIF tag
			uint32 sz = ALIGN16(ras->paletteSize - 0x50)/16;
			*p++ = sz & 0x7FFF;
			*p++ = 0x08000000; // IMAGE
			*p++ = 0;
			*p++ = 0;

			// Transfer
			*xferchain++ = 0x10000002;	// DMAcnt, 2 qwords
			*xferchain++ = 0;
			*xferchain++ = 0;		// VIF nop
			*xferchain++ = 0x50000002;	// VIF DIRECT 2 qwords

			// GIF tag
			*xferchain++ = 1;          // NLOOP = 1
			*xferchain++ = 0x10000000; // NREG = 1
			*xferchain++ = 0xE;        // A+D
			*xferchain++ = 0;

			// BITBLTBUF
			if(paltrxpos == 0){
				*xferchain++ = cpsm<<24 | 1<<16;	// src buffer
				*xferchain++ = cpsm<<24 | 1<<16 | ras->paletteBase;	// dst buffer
				*xferchain++ = 0x50;
				*xferchain++ = 0;
			}else{
				// copy last pixel bitbltbuf...if uploading palette separately it's still the same buffer
				xferchain[0] = xferchain[-16];
				xferchain[1] = xferchain[-15];
				xferchain[2] = xferchain[-14];
				xferchain[3] = xferchain[-13];
				xferchain += 4;
				// Add to last transfer
				xferchain[-16] = 0x30000000 | (xferchain[-16]&0xFFFF) + sz+5;	// last DMAref
				xferchain[-13] = 0x50000000 | (xferchain[-13]&0xFFFF) + sz+5;	// last DIRECT
				pp->numTransfers--;
			}

			*xferchain++ = 0x30000000 | sz+5;	// DMAref
			// this obviously only works with 32 bit pointers, but it's only needed on the PS2 anyway
			*xferchain++ = (uint32)(uintptr)p - 0x50;
			*xferchain++ = 0;		// VIF nop
			*xferchain++ = 0x50000000 | sz+5;	// VIF DIRECT
		}
	}
	raster->originalPixels = raster->pixels;
	raster->originalStride = raster->stride;
	if(ras->flags & Ps2Raster::NEWSTYLE)
		raster->pixels = ((Ps2Raster::PixelPtr*)raster->pixels)->pixels + 0x50;
	return raster;
}

Raster*
rasterCreate(Raster *raster)
{
	if(!getRasterFormat(raster))
		return nil;

	// init raster
	raster->pixels = nil;
	raster->palette = nil;
	raster->originalWidth = raster->width;
	raster->originalHeight = raster->height;
	raster->originalPixels = raster->pixels;
	if(raster->width == 0 || raster->height == 0){
		raster->flags = Raster::DONTALLOCATE;
		raster->stride = 0;
		raster->originalStride = 0;
		return raster;
	}

	switch(raster->type){
	case Raster::NORMAL:
	case Raster::TEXTURE:
		return rasterCreateTexture(raster);
	case Raster::ZBUFFER:
		// TODO. only RW_PS2
		// get info from video mode
		raster->flags = Raster::DONTALLOCATE;
		return raster;
	case Raster::CAMERA:
		// TODO. only RW_PS2
		// get info from video mode
		raster->flags = Raster::DONTALLOCATE;
		return raster;
	case Raster::CAMERATEXTURE:
		// TODO. only RW_PS2
		// check width/height and fall through to texture
		return nil;
	}
	return nil;
}

static uint32
swizzle(uint32 x, uint32 y, uint32 logw)
{
#define X(n) ((x>>(n))&1)
#define Y(n) ((y>>(n))&1)

	uint32 nx, ny, n;
	x ^= (Y(1)^Y(2))<<2;
	nx = (x&7) | ((x>>1)&~7);
	ny = (y&1) | ((y>>1)&~1);
	n = Y(1) | X(3)<<1;
	return n | nx<<2 | ny<<(logw-1+2);
}

void
unswizzleRaster(Raster *raster)
{
	uint8 tmpbuf[1024*4];	// 1024x4px, maximum possible width
	uint32 mask;
	int32 x, y, w, h;
	int32 i;
	int32 logw;
	Ps2Raster *natras = GETPS2RASTEREXT(raster);
	uint8 *px;

	if((raster->format & (Raster::PAL4|Raster::PAL8)) == 0)
		return;

	int minw, minh;
	transferMinSize(raster->format & Raster::PAL4 ? PSMT4 : PSMT8, natras->flags, &minw, &minh);
	w = max(raster->width, minw);
	h = max(raster->height, minh);
	px = raster->pixels;
	logw = 0;
	for(i = 1; i < w; i *= 2) logw++;
	mask = (1<<(logw+2))-1;

	if(raster->format & Raster::PAL4 && natras->flags & Ps2Raster::SWIZZLED4){
		for(y = 0; y < h; y += 4){
			memcpy(tmpbuf, &px[y<<(logw-1)], 2*w);
			for(i = 0; i < 4; i++)
				for(x = 0; x < w; x++){
					uint32 a = ((y+i)<<logw)+x;
					uint32 s = swizzle(x, y+i, logw)&mask;
					uint8 c = s & 1 ? tmpbuf[s>>1] >> 4 : tmpbuf[s>>1] & 0xF;
					px[a>>1] = a & 1 ? (px[a>>1]&0xF) | c<<4 : (px[a>>1]&0xF0) | c;
				}
		}
	}else if(raster->format & Raster::PAL8 && natras->flags & Ps2Raster::SWIZZLED8){
		for(y = 0; y < h; y += 4){
			memcpy(tmpbuf, &px[y<<logw], 4*w);
			for(i = 0; i < 4; i++)
				for(x = 0; x < w; x++){
					uint32 a = ((y+i)<<logw)+x;
					uint32 s = swizzle(x, y+i, logw)&mask;
					px[a] = tmpbuf[s];
				}
		}
	}
}

void
swizzleRaster(Raster *raster)
{
	uint8 tmpbuf[1024*4];	// 1024x4px, maximum possible width
	uint32 mask;
	int32 x, y, w, h;
	int32 i;
	int32 logw;
	Ps2Raster *natras = GETPS2RASTEREXT(raster);
	uint8 *px;

	if((raster->format & (Raster::PAL4|Raster::PAL8)) == 0)
		return;

	int minw, minh;
	transferMinSize(raster->format & Raster::PAL4 ? PSMT4 : PSMT8, natras->flags, &minw, &minh);
	w = max(raster->width, minw);
	h = max(raster->height, minh);
	px = raster->pixels;
	logw = 0;
	for(i = 1; i < raster->width; i *= 2) logw++;
	mask = (1<<(logw+2))-1;

	if(raster->format & Raster::PAL4 && natras->flags & Ps2Raster::SWIZZLED4){
		for(y = 0; y < h; y += 4){
			for(i = 0; i < 4; i++)
				for(x = 0; x < w; x++){
					uint32 a = ((y+i)<<logw)+x;
					uint32 s = swizzle(x, y+i, logw)&mask;
					uint8 c = a & 1 ? px[a>>1] >> 4 : px[a>>1] & 0xF;
					tmpbuf[s>>1] = s & 1 ? (tmpbuf[s>>1]&0xF) | c<<4 : (tmpbuf[s>>1]&0xF0) | c;
				}
			memcpy(&px[y<<(logw-1)], tmpbuf, 2*w);
		}
	}else if(raster->format & Raster::PAL8 && natras->flags & Ps2Raster::SWIZZLED8){
		for(y = 0; y < h; y += 4){
			for(i = 0; i < 4; i++)
				for(x = 0; x < w; x++){
					uint32 a = ((y+i)<<logw)+x;
					uint32 s = swizzle(x, y+i, logw)&mask;
					tmpbuf[s] = px[a];
				}
			memcpy(&px[y<<logw], tmpbuf, 4*w);
		}
	}
}

uint8*
rasterLock(Raster *raster, int32 level, int32 lockMode)
{
	Ps2Raster *natras = GETPS2RASTEREXT(raster);
	assert(raster->depth != 24);

	if(level > 0){
		int32 minw, minh;
		int32 mipw, miph;
		transferMinSize(raster->format & Raster::PAL4 ? PSMT4 : PSMT8, natras->flags, &minw, &minh);
		while(level--){
			mipw = max(raster->width, minw);
			miph = max(raster->height, minh);
			raster->pixels += ALIGN16(mipw*miph*raster->depth/8) + 0x50;
			raster->width /= 2;
			raster->height /= 2;
		}
	}

	if((lockMode & Raster::LOCKNOFETCH) == 0)
		unswizzleRaster(raster);
	if(lockMode & Raster::LOCKREAD) raster->privateFlags |= Raster::PRIVATELOCK_READ;
	if(lockMode & Raster::LOCKWRITE) raster->privateFlags |= Raster::PRIVATELOCK_WRITE;
	return raster->pixels;
}

void
rasterUnlock(Raster *raster, int32 level)
{
	Ps2Raster *natras = GETPS2RASTEREXT(raster);
	if(raster->format & (Raster::PAL4 | Raster::PAL8))
		swizzleRaster(raster);

	raster->width = raster->originalWidth;
	raster->height = raster->originalHeight;
	raster->pixels = raster->originalPixels;
	raster->stride = raster->originalStride;
	if(natras->flags & Ps2Raster::NEWSTYLE)
		raster->pixels = ((Ps2Raster::PixelPtr*)raster->pixels)->pixels + 0x50;

	raster->privateFlags &= ~(Raster::PRIVATELOCK_READ|Raster::PRIVATELOCK_WRITE);
	// TODO: generate mipmaps
}

static void
convertCSM1_16(uint32 *pal)
{
	int i, j;
	uint32 tmp;
	for(i = 0; i < 256; i++)
		// palette index bits 0x08 and 0x10 are flipped
		if((i & 0x18) == 0x10){
			j = i ^ 0x18;
			tmp = pal[i];
			pal[i] = pal[j];
			pal[j] = tmp;
		}
}

static void
convertCSM1_32(uint32 *pal)
{
	int i, j;
	uint32 tmp;
	for(i = 0; i < 256; i++)
		// palette index bits 0x08 and 0x10 are flipped
		if((i & 0x18) == 0x10){
			j = i ^ 0x18;
			tmp = pal[i];
			pal[i] = pal[j];
			pal[j] = tmp;
		}
}

static void
convertPalette(Raster *raster)
{
	if(raster->format & Raster::PAL8){
		if((raster->format & 0xF00) == Raster::C8888)
			convertCSM1_32((uint32*)raster->palette);
		else if((raster->format & 0xF00) == Raster::C8888)
			convertCSM1_16((uint32*)raster->palette);
	}
}

// NB: RW doesn't convert the palette when locking/unlocking
uint8*
rasterLockPalette(Raster *raster, int32 lockMode)
{
	if((raster->format & (Raster::PAL4 | Raster::PAL8)) == 0)
		return nil;
	if((lockMode & Raster::LOCKNOFETCH) == 0)
		convertPalette(raster);
	if(lockMode & Raster::LOCKREAD) raster->privateFlags |= Raster::PRIVATELOCK_READ_PALETTE;
	if(lockMode & Raster::LOCKWRITE) raster->privateFlags |= Raster::PRIVATELOCK_WRITE_PALETTE;
	return raster->palette;
}

void
rasterUnlockPalette(Raster *raster)
{
	if(raster->format & (Raster::PAL4 | Raster::PAL8))
		convertPalette(raster);
	raster->privateFlags &= ~(Raster::PRIVATELOCK_READ_PALETTE|Raster::PRIVATELOCK_WRITE_PALETTE);
}

// Almost the same as d3d9 and gl3 function
bool32
imageFindRasterFormat(Image *img, int32 type,
	int32 *pWidth, int32 *pHeight, int32 *pDepth, int32 *pFormat)
{
	int32 width, height, depth, format;

	assert((type&0xF) == Raster::TEXTURE);

	for(width = 1; width < img->width; width <<= 1);
	for(height = 1; height < img->height; height <<= 1);

	depth = img->depth;

	switch(depth){
	case 32:
	case 24:
		// C888 24 bit is unsafe
		format = Raster::C8888;
		depth = 32;
		break;
	case 16:
		format = Raster::C1555;
		break;
	case 8:
		format = Raster::PAL8 | Raster::C8888;
		break;
	case 4:
		format = Raster::PAL4 | Raster::C8888;
		break;
	default:
		RWERROR((ERR_INVRASTER));
		return 0;
	}

	format |= type;

	*pWidth = width;
	*pHeight = height;
	*pDepth = depth;
	*pFormat = format;

	return 1;
}

bool32
rasterFromImage(Raster *raster, Image *image)
{
	Ps2Raster *natras = GETPS2RASTEREXT(raster);

	int32 pallength = 0;
	switch(image->depth){
	case 24:
	case 32:
		if(raster->format != Raster::C8888 &&
		   raster->format != Raster::C888)	// unsafe already
			goto err;
		break;
	case 16:
		if(raster->format != Raster::C1555) goto err;
		break;
	case 8:
		if(raster->format != (Raster::PAL8 | Raster::C8888)) goto err;
		pallength = 256;
		break;
	case 4:
		if(raster->format != (Raster::PAL4 | Raster::C8888)) goto err;
		pallength = 16;
		break;
	default:
	err:
		RWERROR((ERR_INVRASTER));
		return 0;
	}

	// unsafe
	if((raster->format&0xF00) == Raster::C888){
		RWERROR((ERR_INVRASTER));
		return 0;
	}

	uint8 *in, *out;
	if(image->depth <= 8){
		in = image->palette;
		out = raster->lockPalette(Raster::LOCKWRITE|Raster::LOCKNOFETCH);
		memcpy(out, in, 4*pallength);
		for(int32 i = 0; i < pallength; i++){
			out[3] = out[3]*128/255;
			out += 4;
		}
		raster->unlockPalette();
	}

	int minw, minh;
	int tw;
	transferMinSize(image->depth == 4 ? PSMT4 : PSMT8, natras->flags, &minw, &minh);
	tw = max(image->width, minw);
	uint8 *src = image->pixels;
	out = raster->lock(0, Raster::LOCKWRITE|Raster::LOCKNOFETCH);
	if(image->depth == 4){
		compressPal4(out, tw/2, src, image->stride, image->width, image->height);
	}else if(image->depth == 8){
		copyPal8(out, tw, src, image->stride, image->width, image->height);
	}else{
		for(int32 y = 0; y < image->height; y++){
			in = src;
			for(int32 x = 0; x < image->width; x++){
				switch(image->depth){
				case 16:
					conv_ARGB1555_from_ABGR1555(out, in);
					out += 2;
					break;
				case 24:
					out[0] = in[0];
					out[1] = in[1];
					out[2] = in[2];
					out[3] = 0x80;
					out += 4;
					break;
				case 32:
					out[0] = in[0];
					out[1] = in[1];
					out[2] = in[2];
					out[3] = in[3]*128/255;
					out += 4;
					break;
				}
				in += image->bpp;
			}
			src += image->stride;
		}
	}
	raster->unlock(0);
	return 1;
}

Image*
rasterToImage(Raster *raster)
{
	Image *image;
	int depth;
	Ps2Raster *natras = GETPS2RASTEREXT(raster);

	int32 rasterFormat = raster->format & 0xF00;
	switch(rasterFormat){
	case Raster::C1555:
		depth = 16;
		break;
	case Raster::C8888:
		depth = 32;
		break;
	case Raster::C888:
		depth = 24;
		break;
	case Raster::C555:
		depth = 16;
		break;

	default:
	case Raster::C565:
	case Raster::C4444:
	case Raster::LUM8:
		assert(0 && "unsupported ps2 raster format");
	}
	int32 pallength = 0;
	if((raster->format & Raster::PAL4) == Raster::PAL4){
		depth = 4;
		pallength = 16;
	}else if((raster->format & Raster::PAL8) == Raster::PAL8){
		depth = 8;
		pallength = 256;
	}

	uint8 *in, *out;
	image = Image::create(raster->width, raster->height, depth);
	image->allocate();

	if(pallength){
		out = image->palette;
		in = raster->lockPalette(Raster::LOCKREAD);
		if(rasterFormat == Raster::C8888){
			memcpy(out, in, pallength*4);
			for(int32 i = 0; i < pallength; i++){
				out[3] = out[3]*255/128;
				out += 4;
			}
		}else
			memcpy(out, in, pallength*2);
		raster->unlockPalette();
	}

	int minw, minh;
	int tw;
	transferMinSize(depth == 4 ? PSMT4 : PSMT8, natras->flags, &minw, &minh);
	tw = max(raster->width, minw);
	uint8 *dst = image->pixels;
	in = raster->lock(0, Raster::LOCKREAD);
	if(depth == 4){
		expandPal4(dst, image->stride, in, tw/2, raster->width, raster->height);
	}else if(depth == 8){
		copyPal8(dst, image->stride, in, tw, raster->width, raster->height);
	}else{
		for(int32 y = 0; y < image->height; y++){
			out = dst;
			for(int32 x = 0; x < image->width; x++){
				switch(raster->format & 0xF00){
				case Raster::C8888:
					out[0] = in[0];
					out[1] = in[1];
					out[2] = in[2];
					out[3] = in[3]*255/128;
					in += 4;
					break;
				case Raster::C888:
					out[0] = in[0];
					out[1] = in[1];
					out[2] = in[2];
					in += 4;
					break;
				case Raster::C1555:
					conv_ARGB1555_from_ABGR1555(out, in);
					in += 2;
					break;
				case Raster::C555:
					conv_ARGB1555_from_ABGR1555(out, in);
					out[1] |= 0x80;
					in += 2;
					break;
				default:
					assert(0 && "unknown ps2 raster format");
					break;
				}
				out += image->bpp;
			}
			dst += image->stride;
		}
	}
	raster->unlock(0);

	return image;
}

int32
rasterNumLevels(Raster *raster)
{
	Ps2Raster *ras = GETPS2RASTEREXT(raster);
	if(raster->pixels == nil) return 0;
	if(raster->format & Raster::MIPMAP)
		return MAXLEVEL(ras)+1;
	return 1;
}

static void*
createNativeRaster(void *object, int32 offset, int32)
{
	Ps2Raster *raster = GETPS2RASTEREXT(object);
	raster->tex0 = 0;
	raster->paletteBase = 0;
	raster->kl = defaultMipMapKL;
	raster->tex1low = 0;
	raster->unk2 = 0;
	raster->miptbp1 = 0;
	raster->miptbp2 = 0;
	raster->pixelSize = 0;
	raster->paletteSize = 0;
	raster->totalSize = 0;
	raster->flags = 0;

	raster->dataSize = 0;
	raster->data = nil;
	return object;
}

static void*
destroyNativeRaster(void *object, int32 offset, int32)
{
	Ps2Raster *raster = GETPS2RASTEREXT(object);
	freealign(raster->data);
	return object;
}

static void*
copyNativeRaster(void *dst, void *src, int32 offset, int32)
{
	Ps2Raster *dstraster = GETPS2RASTEREXT(dst);
	Ps2Raster *srcraster = GETPS2RASTEREXT(src);
	*dstraster = *srcraster;
	return dst;
}

static Stream*
readMipmap(Stream *stream, int32, void *object, int32 offset, int32)
{
	uint16 val = stream->readI32();
	Texture *tex = (Texture*)object;
	if(tex->raster == nil)
		return stream;
	Ps2Raster *raster = GETPS2RASTEREXT(tex->raster);
	raster->kl = val;
	return stream;
}

static Stream*
writeMipmap(Stream *stream, int32, void *object, int32 offset, int32)
{
	Texture *tex = (Texture*)object;
	if(tex->raster){
		stream->writeI32(defaultMipMapKL);
		return stream;
	}
	Ps2Raster *raster = GETPS2RASTEREXT(tex->raster);
	stream->writeI32(raster->kl);
	return stream;
}

static int32
getSizeMipmap(void*, int32, int32)
{
	return rw::platform == PLATFORM_PS2 ? 4 : 0;
}

void
registerNativeRaster(void)
{
	nativeRasterOffset = Raster::registerPlugin(sizeof(Ps2Raster),
	                                            ID_RASTERPS2,
	                                            createNativeRaster,
	                                            destroyNativeRaster,
	                                            copyNativeRaster);

	Texture::registerPlugin(0, ID_SKYMIPMAP, nil, nil, nil);
	Texture::registerPluginStream(ID_SKYMIPMAP, readMipmap, writeMipmap, getSizeMipmap);
}

void
printTEX0(uint64 tex0)
{
	printf("%016lX ", tex0);
	uint32 tbp0 = tex0 & 0x3FFF; tex0 >>= 14;
	uint32 tbw = tex0 & 0x3F; tex0 >>= 6;
	uint32 psm = tex0 & 0x3F; tex0 >>= 6;
	uint32 tw = tex0 & 0xF; tex0 >>= 4;
	uint32 th = tex0 & 0xF; tex0 >>= 4;
	uint32 tcc = tex0 & 0x1; tex0 >>= 1;
	uint32 tfx = tex0 & 0x3; tex0 >>= 2;
	uint32 cbp = tex0 & 0x3FFF; tex0 >>= 14;
	uint32 cpsm = tex0 & 0xF; tex0 >>= 4;
	uint32 csm = tex0 & 0x1; tex0 >>= 1;
	uint32 csa = tex0 & 0x1F; tex0 >>= 5;
	uint32 cld = tex0 & 0x7;
	printf("TBP0:%4X TBW:%2X PSM:%2X TW:%X TH:%X TCC:%X TFX:%X CBP:%4X CPSM:%X CSM:%X CSA:%2X CLD:%X\n",
		tbp0, tbw, psm, tw, th, tcc, tfx, cbp, cpsm, csm, csa, cld);
}

void
printTEX1(uint64 tex1)
{
	printf("%016lX ", tex1);
	uint32 lcm = tex1 & 0x1; tex1 >>= 2;
	uint32 mxl = tex1 & 0x7; tex1 >>= 3;
	uint32 mmag = tex1 & 0x1; tex1 >>= 1;
	uint32 mmin = tex1 & 0x7; tex1 >>= 3;
	uint32 mtba = tex1 & 0x1; tex1 >>= 10;
	uint32 l = tex1 & 0x3; tex1 >>= 13;
	uint32 k = tex1 & 0xFFF;
	printf("LCM:%X MXL:%X MMAG:%X MMIN:%X MTBA:%X L:%X K:%X\n",
		lcm, mxl, mmag, mmin, mtba, l, k);
}

void
calcTEX1(Raster *raster, uint64 *tex1, int32 filter)
{
	enum {
		NEAREST = 0,
		LINEAR,
		NEAREST_MIPMAP_NEAREST,
		NEAREST_MIPMAP_LINEAR,
		LINEAR_MIPMAP_NEAREST,
		LINEAR_MIPMAP_LINEAR,
	};
	Ps2Raster *natras = GETPS2RASTEREXT(raster);
	uint64 t1 = natras->tex1low;
	uint64 k = natras->kl & 0xFFF;
	uint64 l = (natras->kl >> 12) & 0x3;
	t1 |= k << 32;
	t1 |= l << 19;
	switch(filter){
	case Texture::NEAREST:
		t1 |= (NEAREST << 5) |
		      (NEAREST << 6);
		break;
	case Texture::LINEAR:
		t1 |= (LINEAR << 5) |
		      (LINEAR << 6);
		break;
	case Texture::MIPNEAREST:
		t1 |= (NEAREST << 5) |
		      (NEAREST_MIPMAP_NEAREST << 6);
		break;
	case Texture::MIPLINEAR:
		t1 |= (LINEAR << 5) |
		      (LINEAR_MIPMAP_NEAREST << 6);
		break;
	case Texture::LINEARMIPNEAREST:
		t1 |= (NEAREST << 5) |
		      (NEAREST_MIPMAP_LINEAR << 6);
		break;
	case Texture::LINEARMIPLINEAR:
		t1 |= (LINEAR << 5) |
		      (LINEAR_MIPMAP_LINEAR << 6);
		break;
	}
	*tex1 = t1;
}

struct StreamRasterExt
{
	int32 width;
	int32 height;
	int32 depth;
	uint16 rasterFormat;
	int16  version;
	uint64 tex0;
	uint32 paletteOffset;
	uint32 tex1low;
	uint64 miptbp1;
	uint64 miptbp2;
	uint32 pixelSize;
	uint32 paletteSize;
	uint32 totalSize;
	uint32 mipmapVal;
};

Texture*
readNativeTexture(Stream *stream)
{
	uint32 length, oldversion, version;
	uint32 fourcc;
	Raster *raster;
	Ps2Raster *natras;
	if(!findChunk(stream, ID_STRUCT, nil, nil)){
		RWERROR((ERR_CHUNK, "STRUCT"));
		return nil;
	}
	fourcc = stream->readU32();
	if(fourcc != FOURCC_PS2){
		RWERROR((ERR_PLATFORM, fourcc));
		return nil;
	}
	Texture *tex = Texture::create(nil);
	if(tex == nil)
		return nil;

	// Texture
	tex->filterAddressing = stream->readU32();
	if(!findChunk(stream, ID_STRING, &length, nil)){
		RWERROR((ERR_CHUNK, "STRING"));
		goto fail;
	}
	stream->read8(tex->name, length);
	if(!findChunk(stream, ID_STRING, &length, nil)){
		RWERROR((ERR_CHUNK, "STRING"));
		goto fail;
	}
	stream->read8(tex->mask, length);

	// Raster
	StreamRasterExt streamExt;
	oldversion = rw::version;
	if(!findChunk(stream, ID_STRUCT, nil, nil)){
		RWERROR((ERR_CHUNK, "STRUCT"));
		goto fail;
	}
	if(!findChunk(stream, ID_STRUCT, nil, &version)){
		RWERROR((ERR_CHUNK, "STRUCT"));
		goto fail;
	}
	ASSERTLITTLE;
	stream->read8(&streamExt, 0x40);
/*
printf("%X %X %X %X %X %016llX %X %X %016llX %016llX %X %X %X %X\n",
streamExt.width,
streamExt.height,
streamExt.depth,
streamExt.rasterFormat,
streamExt.version,
streamExt.tex0,
streamExt.paletteOffset,
streamExt.tex1low,
streamExt.miptbp1,
streamExt.miptbp2,
streamExt.pixelSize,
streamExt.paletteSize,
streamExt.totalSize,
streamExt.mipmapVal);
*/
	noNewStyleRasters = streamExt.version < 2;
	rw::version = version;
	raster = Raster::create(streamExt.width, streamExt.height,
	                        streamExt.depth, streamExt.rasterFormat,
	                        PLATFORM_PS2);
	noNewStyleRasters = 0;
	rw::version = oldversion;
	tex->raster = raster;
	natras = GETPS2RASTEREXT(raster);
//printf("%X %X\n", natras->paletteBase, natras->tex1low);
//	printf("%08X%08X %08X%08X %08X%08X\n",
//	       (uint32)natras->tex0, (uint32)(natras->tex0>>32),
//	       (uint32)natras->miptbp1, (uint32)(natras->miptbp1>>32),
//	       (uint32)natras->miptbp2, (uint32)(natras->miptbp2>>32));
//	printTEX0(natras->tex0);
	uint64 tex1;
	calcTEX1(raster, &tex1, tex->filterAddressing & 0xF);
//	printTEX1(tex1);

	// TODO: GTA SA LD_OTB.txd loses here
	assert(natras->pixelSize >= streamExt.pixelSize);
	assert(natras->paletteSize >= streamExt.paletteSize);

//if(natras->tex0 != streamExt.tex0){
//printf("TEX0: %016llX\n      %016llX\n", natras->tex0, streamExt.tex0);
//printTEX0(natras->tex0);
//printTEX0(streamExt.tex0);
//fflush(stdout);
//}
//if(natras->tex1low != streamExt.tex1low)
//printf("TEX1: %08X\n      %08X\n", natras->tex1low, streamExt.tex1low);
//if(natras->miptbp1 != streamExt.miptbp1)
//printf("MIP1: %016llX\n      %016llX\n", natras->miptbp1, streamExt.miptbp1);
//if(natras->miptbp2 != streamExt.miptbp2)
//printf("MIP2: %016llX\n      %016llX\n", natras->miptbp2, streamExt.miptbp2);
//if(natras->paletteBase != streamExt.paletteOffset)
//printf("PAL: %08X\n     %08X\n", natras->paletteBase, streamExt.paletteOffset);
//if(natras->pixelSize != streamExt.pixelSize)
//printf("PXS: %08X\n     %08X\n", natras->pixelSize, streamExt.pixelSize);
//if(natras->paletteSize != streamExt.paletteSize)
//printf("PLS: %08X\n     %08X\n", natras->paletteSize, streamExt.paletteSize);
//if(natras->totalSize != streamExt.totalSize)
//printf("TSZ: %08X\n     %08X\n", natras->totalSize, streamExt.totalSize);

	// junk addresses, no need to store them
	streamExt.tex0 &= ~0x3FFFULL;
	streamExt.tex0 &= ~(0x3FFFULL << 37);

	assert(natras->tex0 == streamExt.tex0);
	natras->tex0 = streamExt.tex0;
	assert(natras->paletteBase == streamExt.paletteOffset);
	natras->paletteBase = streamExt.paletteOffset;
	assert(natras->tex1low == streamExt.tex1low);
	natras->tex1low = streamExt.tex1low;
	assert(natras->miptbp1 == streamExt.miptbp1);
	natras->miptbp1 = streamExt.miptbp1;
	assert(natras->miptbp2 == streamExt.miptbp2);
	natras->miptbp2 = streamExt.miptbp2;
	assert(natras->pixelSize == streamExt.pixelSize);
	natras->pixelSize = streamExt.pixelSize;
	assert(natras->paletteSize == streamExt.paletteSize);
	natras->paletteSize = streamExt.paletteSize;
	assert(natras->totalSize == streamExt.totalSize);
	natras->totalSize = streamExt.totalSize;
	natras->kl = streamExt.mipmapVal;
//printf("%X %X\n", natras->paletteBase, natras->tex1low);
//	printf("%08X%08X %08X%08X %08X%08X\n",
//	       (uint32)natras->tex0, (uint32)(natras->tex0>>32),
//	       (uint32)natras->miptbp1, (uint32)(natras->miptbp1>>32),
//	       (uint32)natras->miptbp2, (uint32)(natras->miptbp2>>32));
//	printTEX0(natras->tex0);
	calcTEX1(raster, &tex1, tex->filterAddressing & 0xF);
//	printTEX1(tex1);

	// this is weird stuff
	if(streamExt.version < 2){
		if(streamExt.version == 1){
			// Version 1 has swizzled 8 bit textures
			if(!(natras->flags & Ps2Raster::NEWSTYLE))
				natras->flags |= Ps2Raster::SWIZZLED8;
			else
				assert(0 && "can't happen");
		}else{
			// Version 0 has no swizzling at all
			if(!(natras->flags & Ps2Raster::NEWSTYLE))
				natras->flags &= ~Ps2Raster::SWIZZLED8;
			else
				assert(0 && "can't happen");
		}
	}

	if(!findChunk(stream, ID_STRUCT, &length, nil)){
		RWERROR((ERR_CHUNK, "STRUCT"));
		goto fail;
	}
	if(streamExt.version < 2){
		stream->read8(raster->pixels, length);
	}else{
		stream->read8(((Ps2Raster::PixelPtr*)raster->originalPixels)->pixels, natras->pixelSize);
		stream->read8(raster->palette-0x50, natras->paletteSize);
	}
//printf("\n");
	return tex;

fail:
	tex->destroy();
	return nil;
}

void
writeNativeTexture(Texture *tex, Stream *stream)
{
	Raster *raster = tex->raster;
	Ps2Raster *ras = GETPS2RASTEREXT(raster);
	writeChunkHeader(stream, ID_STRUCT, 8);
	stream->writeU32(FOURCC_PS2);
	stream->writeU32(tex->filterAddressing);
	int32 len = strlen(tex->name)+4 & ~3;
	writeChunkHeader(stream, ID_STRING, len);
	stream->write8(tex->name, len);
	len = strlen(tex->mask)+4 & ~3;
	writeChunkHeader(stream, ID_STRING, len);
	stream->write8(tex->mask, len);

	int32 sz = ras->pixelSize + ras->paletteSize;
	writeChunkHeader(stream, ID_STRUCT, 12 + 64 + 12 + sz);
	writeChunkHeader(stream, ID_STRUCT, 64);
	StreamRasterExt streamExt;
	streamExt.width = raster->width;
	streamExt.height = raster->height;
	streamExt.depth = raster->depth;
	streamExt.rasterFormat = raster->format | raster->type;
	streamExt.version = 0;
	if(ras->flags == Ps2Raster::SWIZZLED8 && raster->depth == 8)
		streamExt.version = 1;
	if(ras->flags & Ps2Raster::NEWSTYLE)
		streamExt.version = 2;
	streamExt.tex0 = ras->tex0;
	streamExt.paletteOffset = ras->paletteBase;
	streamExt.tex1low = ras->tex1low;
	streamExt.miptbp1 = ras->miptbp1;
	streamExt.miptbp2 = ras->miptbp2;
	streamExt.pixelSize = ras->pixelSize;
	streamExt.paletteSize = ras->paletteSize;
	streamExt.totalSize = ras->totalSize;
	streamExt.mipmapVal = ras->kl;
	ASSERTLITTLE;
	stream->write8(&streamExt, 64);

	writeChunkHeader(stream, ID_STRUCT, sz);
	if(streamExt.version < 2){
		stream->write8(raster->pixels, sz);
	}else{
		stream->write8(((Ps2Raster::PixelPtr*)raster->originalPixels)->pixels, ras->pixelSize);
		stream->write8(raster->palette-0x50, ras->paletteSize);
	}
}

uint32
getSizeNativeTexture(Texture *tex)
{
	uint32 size = 12 + 8;
	size += 12 + strlen(tex->name)+4 & ~3;
	size += 12 + strlen(tex->mask)+4 & ~3;
	size += 12;
	size += 12 + 64;
	Ps2Raster *ras = GETPS2RASTEREXT(tex->raster);
	size += 12 + ras->pixelSize + ras->paletteSize;
	return size;
}

}
}
