/*
 * Copyright 2007 Stephane Marchesin
 * Copyright 2007 Arthur Huillet
 * Copyright 2007 Peter Winters
 * Copyright 2009 Francisco Jerez
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
#include "config.h"

#include "nv_include.h"

#include "hwdefs/nv_object.xml.h"
#include "hwdefs/nv10_3d.xml.h"
#include "nv04_accel.h"

/* Texture/Render target formats. */
static struct pict_format {
	int exa;
	int hw;
} nv10_tex_format_pot[] = {
	{ PICT_a8,       0x80  },
	{ PICT_r5g6b5,	 0x280 },
	{ PICT_x8r8g8b8, 0x300 },
	{ PICT_a8r8g8b8, 0x300 },
	{},

}, nv10_tex_format_rect[] = {
	{ PICT_a8,       0x980 },
	{ PICT_r5g6b5,   0x880 },
	{ PICT_x8r8g8b8, 0x900 },
	{ PICT_a8r8g8b8, 0x900 },
	{},

}, nv20_tex_format_rect[] = {
	{ PICT_a8,	 0xd80 },
	{ PICT_r5g6b5,   0x880 },
	{ PICT_x8r8g8b8, 0x900 },
	{ PICT_a8r8g8b8, 0x900 },
	{},

}, nv10_rt_format[] = {
	{ PICT_r5g6b5,	 0x103 },
	{ PICT_x8r8g8b8, 0x108 },
	{ PICT_a8r8g8b8, 0x108 },
	{},
};

static int
get_tex_format(NVPtr pNv, PicturePtr pict)
{
	/* If repeat is set we're always handling a 1x1 texture with
	 * ARGB/XRGB destination, in that case we change the format to
	 * use the POT (swizzled) matching format.
	 */
	struct pict_format *format =
		pict->repeat != RepeatNone ? nv10_tex_format_pot :
		pNv->Architecture == NV_ARCH_20 ? nv20_tex_format_rect :
		nv10_tex_format_rect;

	for (; format->hw; format++) {
		if (format->exa == pict->format)
			return format->hw;
	}

	return 0;
}

static int
get_rt_format(PicturePtr pict)
{
	struct pict_format *format = nv10_rt_format;

	for (; format->hw; format++) {
		if (format->exa == pict->format)
			return format->hw;
	}

	return 0;
}

/* Blending functions. */
#define SF(x) NV10_3D_BLEND_FUNC_SRC_##x
#define DF(x) NV10_3D_BLEND_FUNC_DST_##x

static struct pict_op {
	int src;
	int dst;

} nv10_pict_op[] = {
	{ SF(ZERO),		   DF(ZERO) },		      /* Clear */
	{ SF(ONE),		   DF(ZERO) },		      /* Src */
	{ SF(ZERO),		   DF(ONE) },		      /* Dst */
	{ SF(ONE),		   DF(ONE_MINUS_SRC_ALPHA) }, /* Over */
	{ SF(ONE_MINUS_DST_ALPHA), DF(ONE) },		      /* OverReverse */
	{ SF(DST_ALPHA),	   DF(ZERO) },                /* In */
	{ SF(ZERO),		   DF(SRC_ALPHA) },           /* InReverse */
	{ SF(ONE_MINUS_DST_ALPHA), DF(ZERO) },		      /* Out */
	{ SF(ZERO),		   DF(ONE_MINUS_SRC_ALPHA) }, /* OutReverse */
	{ SF(DST_ALPHA),	   DF(ONE_MINUS_SRC_ALPHA) }, /* Atop */
	{ SF(ONE_MINUS_DST_ALPHA), DF(SRC_ALPHA) },	      /* AtopReverse */
	{ SF(ONE_MINUS_DST_ALPHA), DF(ONE_MINUS_SRC_ALPHA) }, /* Xor */
	{ SF(ONE),		   DF(ONE) },		      /* Add */
};

static inline Bool
needs_src_alpha(int op)
{
	return nv10_pict_op[op].dst == DF(ONE_MINUS_SRC_ALPHA)
		|| nv10_pict_op[op].dst == DF(SRC_ALPHA);
}

static inline Bool
needs_src(int op)
{
	return nv10_pict_op[op].src != SF(ZERO);
}

static inline Bool
effective_component_alpha(PicturePtr mask)
{
	return mask && mask->componentAlpha && PICT_FORMAT_RGB(mask->format);
}

static Bool
check_texture(NVPtr pNv, PicturePtr pict)
{
	int w = 1, h = 1;

	if (pict->pDrawable) {
		w = pict->pDrawable->width;
		h = pict->pDrawable->height;
	} else {
		if (pict->pSourcePict->type != SourcePictTypeSolidFill)
			NOUVEAU_FALLBACK("gradient pictures unsupported\n");
	}

	if (w > 2046 || h > 2046)
		NOUVEAU_FALLBACK("picture too large, %dx%d\n", w, h);

	if (!get_tex_format(pNv, pict))
		return FALSE;

	if (pict->filter != PictFilterNearest &&
	    pict->filter != PictFilterBilinear)
		return FALSE;

	/* We cannot repeat on NV10 because NPOT textures do not
	 * support this. unfortunately. */
	if (pict->repeat != RepeatNone)
		/* we can repeat 1x1 textures */
		if (!(w == 1 && h == 1))
			return FALSE;

	return TRUE;
}

static Bool
check_render_target(PicturePtr pict)
{
	int w = pict->pDrawable->width;
	int h = pict->pDrawable->height;

	if (w > 4096 || h > 4096)
		return FALSE;

	if (!get_rt_format(pict))
		return FALSE;

	return TRUE;
}

static Bool
check_pict_op(int op)
{
	/* We do no saturate, disjoint, conjoint, though we
	 * could do e.g. DisjointClear which really is
	 * Clear. */
	return op < PictOpSaturate;
}

#if 0
static void
print_fallback_info(char *reason, int op, PicturePtr src, PicturePtr mask,
		    PicturePtr dst)
{
	char out2[4096];
	char *out = out2;

	sprintf(out, "%s  ", reason);
	out += strlen(out);

	switch (op) {
	case PictOpClear:
		sprintf(out, "PictOpClear ");
		break;
	case PictOpSrc:
		sprintf(out, "PictOpSrc ");
		break;
	case PictOpDst:
		sprintf(out, "PictOpDst ");
		break;
	case PictOpOver:
		sprintf(out, "PictOpOver ");
		break;
	case PictOpOutReverse:
		sprintf(out, "PictOpOutReverse ");
		break;
	case PictOpAdd:
		sprintf(out, "PictOpAdd ");
		break;
	default:
		sprintf(out, "PictOp%d ", op);
	}
	out += strlen(out);

	switch (src->format) {
	case PICT_a8r8g8b8:
		sprintf(out, "A8R8G8B8 ");
		break;
	case PICT_x8r8g8b8:
		sprintf(out, "X8R8G8B8 ");
		break;
	case PICT_x8b8g8r8:
		sprintf(out, "X8B8G8R8 ");
		break;
	case PICT_r5g6b5:
		sprintf(out, "R5G6B5 ");
		break;
	case PICT_a8:
		sprintf(out, "A8 ");
		break;
	case PICT_a1:
		sprintf(out, "A1 ");
		break;
	default:
		sprintf(out, "%x ", src->format);
	}
	out += strlen(out);

	sprintf(out, "(%dx%d) ", src->pDrawable->width,
		src->pDrawable->height);
	if (src->repeat != RepeatNone)
		strcat(out, "R ");
	strcat(out, "-> ");
	out += strlen(out);

	switch (dst->format) {
	case PICT_a8r8g8b8:
		sprintf(out, "A8R8G8B8 ");
		break;
	case PICT_x8r8g8b8:
		sprintf(out, "X8R8G8B8  ");
		break;
	case PICT_x8b8g8r8:
		sprintf(out, "X8B8G8R8  ");
		break;
	case PICT_r5g6b5:
		sprintf(out, "R5G6B5 ");
		break;
	case PICT_a8:
		sprintf(out, "A8  ");
		break;
	case PICT_a1:
		sprintf(out, "A1  ");
		break;
	default:
		sprintf(out, "%x  ", dst->format);
	}
	out += strlen(out);

	sprintf(out, "(%dx%d) ", dst->pDrawable->width,
		dst->pDrawable->height);
	if (dst->repeat != RepeatNone)
		strcat(out, "R ");
	out += strlen(out);

	if (!mask)
		sprintf(out, "& NONE");
	else {
		switch (mask->format) {
		case PICT_a8r8g8b8:
			sprintf(out, "& A8R8G8B8 ");
			break;
		case PICT_x8r8g8b8:
			sprintf(out, "& X8R8G8B8  ");
			break;
		case PICT_x8b8g8r8:
			sprintf(out, "& X8B8G8R8  ");
			break;
		case PICT_a8:
			sprintf(out, "& A8  ");
			break;
		case PICT_a1:
			sprintf(out, "& A1  ");
			break;
		default:
			sprintf(out, "& %x  ", mask->format);
		}
		out += strlen(out);

		sprintf(out, "(%dx%d) ", mask->pDrawable->width,
			mask->pDrawable->height);
		if (mask->repeat != RepeatNone)
			strcat(out, "R ");
		if (mask->componentAlpha)
			strcat(out, "C ");
		out += strlen(out);
	}
	strcat(out, "\n");

	xf86DrvMsg(0, X_INFO, "%s", out2);
}
#else
#define print_fallback_info(...)
#endif

Bool
NV10EXACheckComposite(int op, PicturePtr src, PicturePtr mask, PicturePtr dst)
{
	ScrnInfoPtr pScrn = xf86ScreenToScrn(dst->pDrawable->pScreen);
	NVPtr pNv = NVPTR(pScrn);

	if (!check_pict_op(op)) {
		print_fallback_info("pictop", op, src, mask, dst);
		return FALSE;
	}

	if (!check_render_target(dst)) {
		print_fallback_info("dst", op, src, mask, dst);
		return FALSE;
	}

	if (!check_texture(pNv, src)) {
		print_fallback_info("src", op, src, mask, dst);
		return FALSE;
	}

	if (mask) {
		if (!check_texture(pNv, mask)) {
			print_fallback_info("mask", op, src,
					    mask, dst);
			return FALSE;
		}

		if (effective_component_alpha(mask) &&
		    needs_src(op) && needs_src_alpha(op)) {
			print_fallback_info("ca-mask", op, src,
					    mask, dst);
			return FALSE;
		}
	}

	print_fallback_info("Accelerating", op, src, mask, dst);
	return TRUE;
}

static Bool
setup_texture(NVPtr pNv, int unit, PicturePtr pict, PixmapPtr pixmap)
{
	struct nouveau_pushbuf *push = pNv->pushbuf;
	struct nouveau_bo *bo = nouveau_pixmap_bo(pixmap);
	unsigned reloc = NOUVEAU_BO_VRAM | NOUVEAU_BO_GART | NOUVEAU_BO_RD;
	unsigned h = pict->pDrawable->height;
	unsigned w = pict->pDrawable->width;
	unsigned format;

	format = NV10_3D_TEX_FORMAT_WRAP_T_CLAMP_TO_EDGE |
		 NV10_3D_TEX_FORMAT_WRAP_S_CLAMP_TO_EDGE |
		 log2i(w) << 20 | log2i(h) << 16 |
		 1 << 12 | /* lod == 1 */
		 get_tex_format(pNv, pict) |
		 0x50 /* UNK */;

	/* NPOT_SIZE expects an even number for width, we can round up uneven
	 * numbers here because EXA always gives 64 byte aligned pixmaps and
	 * for all formats we support 64 bytes represents an even number of
	 * pixels
	 */
	w = (w + 1) & ~1;

	BEGIN_NV04(push, NV10_3D(TEX_OFFSET(unit)), 1);
	PUSH_MTHDl(push, NV10_3D(TEX_OFFSET(unit)), bo, 0, reloc);
	BEGIN_NV04(push, NV10_3D(TEX_FORMAT(unit)), 1);
	PUSH_MTHDs(push, NV10_3D(TEX_FORMAT(unit)), bo, format, reloc,
			 NV10_3D_TEX_FORMAT_DMA0,
			 NV10_3D_TEX_FORMAT_DMA1);
	BEGIN_NV04(push, NV10_3D(TEX_ENABLE(unit)), 1 );
	PUSH_DATA (push, NV10_3D_TEX_ENABLE_ENABLE);
	BEGIN_NV04(push, NV10_3D(TEX_NPOT_PITCH(unit)), 1);
	PUSH_DATA (push, exaGetPixmapPitch(pixmap) << 16);
	BEGIN_NV04(push, NV10_3D(TEX_NPOT_SIZE(unit)), 1);
	PUSH_DATA (push, (w << 16) | h);
	BEGIN_NV04(push, NV10_3D(TEX_FILTER(unit)), 1);
	if (pict->filter == PictFilterNearest)
		PUSH_DATA(push, NV10_3D_TEX_FILTER_MAGNIFY_NEAREST |
				NV10_3D_TEX_FILTER_MINIFY_NEAREST);
	else
		PUSH_DATA(push, NV10_3D_TEX_FILTER_MAGNIFY_LINEAR |
				NV10_3D_TEX_FILTER_MINIFY_LINEAR);
	if (pict->transform) {
		BEGIN_NV04(push, NV10_3D(TEX_MATRIX_ENABLE(unit)), 1);
		PUSH_DATA (push, 1);
		BEGIN_NV04(push, NV10_3D(TEX_MATRIX(unit, 0)), 16);
		PUSH_DATAf(push, xFixedToFloat(pict->transform->matrix[0][0]));
		PUSH_DATAf(push, xFixedToFloat(pict->transform->matrix[0][1]));
		PUSH_DATAf(push, 0.f);
		PUSH_DATAf(push, xFixedToFloat(pict->transform->matrix[0][2]));
		PUSH_DATAf(push, xFixedToFloat(pict->transform->matrix[1][0]));
		PUSH_DATAf(push, xFixedToFloat(pict->transform->matrix[1][1]));
		PUSH_DATAf(push, 0.f);
		PUSH_DATAf(push, xFixedToFloat(pict->transform->matrix[1][2]));
		PUSH_DATAf(push, 0.0f);
		PUSH_DATAf(push, 0.0f);
		PUSH_DATAf(push, 0.0f);
		PUSH_DATAf(push, 0.0f);
		PUSH_DATAf(push, xFixedToFloat(pict->transform->matrix[2][0]));
		PUSH_DATAf(push, xFixedToFloat(pict->transform->matrix[2][1]));
		PUSH_DATAf(push, 0.0f);
		PUSH_DATAf(push, xFixedToFloat(pict->transform->matrix[2][2]));
	} else {
		BEGIN_NV04(push, NV10_3D(TEX_MATRIX_ENABLE(unit)), 1);
		PUSH_DATA (push, 0);
	}

	return TRUE;
}

static Bool
setup_render_target(NVPtr pNv, PicturePtr pict, PixmapPtr pixmap)
{
	struct nouveau_pushbuf *push = pNv->pushbuf;
	struct nouveau_bo *bo = nouveau_pixmap_bo(pixmap);

	BEGIN_NV04(push, NV10_3D(RT_FORMAT), 3);
	PUSH_DATA (push, get_rt_format(pict));
	PUSH_DATA (push, (exaGetPixmapPitch(pixmap) << 16 |
			  exaGetPixmapPitch(pixmap)));
	PUSH_MTHDl(push, NV10_3D(COLOR_OFFSET), bo, 0,
			 NOUVEAU_BO_VRAM | NOUVEAU_BO_RDWR);
	return TRUE;
}

static void
setup_blend_function(NVPtr pNv, PicturePtr pdpict, PicturePtr pmpict, int alu)
{
	struct nouveau_pushbuf *push = pNv->pushbuf;
	struct pict_op *op = &nv10_pict_op[alu];
	int src_factor = op->src;
	int dst_factor = op->dst;

	if (src_factor == SF(ONE_MINUS_DST_ALPHA) &&
	    !PICT_FORMAT_A(pdpict->format))
		/* ONE_MINUS_DST_ALPHA doesn't always do the right thing for
		 * framebuffers without alpha channel. But it's the same as
		 * ZERO in that case.
		 */
		src_factor = SF(ZERO);

	if (effective_component_alpha(pmpict)) {
		if (dst_factor == DF(SRC_ALPHA))
			dst_factor = DF(SRC_COLOR);
		else if (dst_factor == DF(ONE_MINUS_SRC_ALPHA))
			dst_factor = DF(ONE_MINUS_SRC_COLOR);
	}

	BEGIN_NV04(push, NV10_3D(BLEND_FUNC_SRC), 2);
	PUSH_DATA (push, src_factor);
	PUSH_DATA (push, dst_factor);
	BEGIN_NV04(push, NV10_3D(BLEND_FUNC_ENABLE), 1);
	PUSH_DATA (push, 1);
}

#define RCSRC_COL(i)  (0x01 + (unit))
#define RCSRC_TEX(i)  (0x08 + (unit))
#define RCSEL_COLOR   (0x00)
#define RCSEL_ALPHA   (0x10)
#define RCINP_ZERO    (0x00)
#define RCINP_ONE     (0x20)
#define RCINP_A__SHIFT 24
#define RCINP_B__SHIFT 16

static Bool
setup_picture(NVPtr pNv, PicturePtr pict, PixmapPtr pixmap, int unit,
	      uint32_t *color, uint32_t *alpha)
{
	struct nouveau_pushbuf *push = pNv->pushbuf;
	uint32_t shift, source;

	if (pict && pict->pDrawable) {
		if (!setup_texture(pNv, unit, pict, pixmap))
			return FALSE;
		source = RCSRC_TEX(unit);
	} else
	if (pict) {
		BEGIN_NV04(push, NV10_3D(RC_COLOR(unit)), 1);
		PUSH_DATA (push, pict->pSourcePict->solidFill.color);
		source = RCSRC_COL(unit);
	}

	if (pict && PICT_FORMAT_RGB(pict->format))
		*color = RCSEL_COLOR | source;
	else
		*color = RCSEL_COLOR | RCINP_ZERO;

	if (pict && PICT_FORMAT_A(pict->format))
		*alpha = RCSEL_ALPHA | source;
	else
		*alpha = RCSEL_ALPHA | RCINP_ONE;

	if (unit)
		shift = RCINP_B__SHIFT;
	else
		shift = RCINP_A__SHIFT;
	*color <<= shift;
	*alpha <<= shift;
	return TRUE;
}

Bool
NV10EXAPrepareComposite(int op,
			PicturePtr pict_src,
			PicturePtr pict_mask,
			PicturePtr pict_dst,
			PixmapPtr src,
			PixmapPtr mask,
			PixmapPtr dst)
{
	ScrnInfoPtr pScrn = xf86ScreenToScrn(dst->drawable.pScreen);
	NVPtr pNv = NVPTR(pScrn);
	struct nouveau_pushbuf *push = pNv->pushbuf;
	uint32_t sc, sa, mc, ma;

	if (!PUSH_SPACE(push, 128))
		return FALSE;
	PUSH_RESET(push);

	/* setup render target and blending */
	if (!setup_render_target(pNv, pict_dst, dst))
		return FALSE;
	setup_blend_function(pNv, pict_dst, pict_mask, op);

	/* select picture sources */
	if (!setup_picture(pNv, pict_src, src, 0, &sc, &sa))
		return FALSE;
	if (!setup_picture(pNv, pict_mask, mask, 1, &mc, &ma))
		return FALSE;

	/* configure register combiners */
	BEGIN_NV04(push, NV10_3D(RC_IN_ALPHA(0)), 1);
	PUSH_DATA (push, sa | ma);
	BEGIN_NV04(push, NV10_3D(RC_IN_RGB(0)), 1);
	if (effective_component_alpha(pict_mask)) {
		if (needs_src_alpha(op))
			PUSH_DATA(push, sa | mc);
		else
			PUSH_DATA(push, sc | mc);
	} else {
		PUSH_DATA(push, sc | ma);
	}

	nouveau_pushbuf_bufctx(push, pNv->bufctx);
	if (nouveau_pushbuf_validate(push)) {
		nouveau_pushbuf_bufctx(push, NULL);
		return FALSE;
	}

	pNv->pspict = pict_src;
	pNv->pmpict = pict_mask;
	return TRUE;
}

static inline void
PUSH_VTX2s(struct nouveau_pushbuf *push,
	   int x1, int y1, int x2, int y2, int dx, int dy)
{
	BEGIN_NV04(push, NV10_3D(VERTEX_TX0_2I), 1);
	PUSH_DATA (push, ((y1 & 0xffff) << 16) | (x1 & 0xffff));
	BEGIN_NV04(push, NV10_3D(VERTEX_TX1_2I), 1);
	PUSH_DATA (push, ((y2 & 0xffff) << 16) | (x2 & 0xffff));
	BEGIN_NV04(push, NV10_3D(VERTEX_POS_3F_X), 3);
	PUSH_DATAf(push, dx);
	PUSH_DATAf(push, dy);
	PUSH_DATAf(push, 0.0);
}

void
NV10EXAComposite(PixmapPtr pix_dst,
		 int sx, int sy, int mx, int my, int dx, int dy, int w, int h)
{
	ScrnInfoPtr pScrn = xf86ScreenToScrn(pix_dst->drawable.pScreen);
	NVPtr pNv = NVPTR(pScrn);
	struct nouveau_pushbuf *push = pNv->pushbuf;

	if (!PUSH_SPACE(push, 64))
		return;

	BEGIN_NV04(push, NV10_3D(VERTEX_BEGIN_END), 1);
	PUSH_DATA (push, NV10_3D_VERTEX_BEGIN_END_QUADS);
	PUSH_VTX2s(push, sx, sy, mx, my, dx, dy);
	PUSH_VTX2s(push, sx + w, sy, mx + w, my, dx + w, dy);
	PUSH_VTX2s(push, sx + w, sy + h, mx + w, my + h, dx + w, dy + h);
	PUSH_VTX2s(push, sx, sy + h, mx, my + h, dx, dy + h);
	BEGIN_NV04(push, NV10_3D(VERTEX_BEGIN_END), 1);
	PUSH_DATA (push, NV10_3D_VERTEX_BEGIN_END_STOP);
}

void
NV10EXADoneComposite(PixmapPtr dst)
{
	ScrnInfoPtr pScrn = xf86ScreenToScrn(dst->drawable.pScreen);
	nouveau_pushbuf_bufctx(NVPTR(pScrn)->pushbuf, NULL);
}

Bool
NVAccelInitNV10TCL(ScrnInfoPtr pScrn)
{
	NVPtr pNv = NVPTR(pScrn);
	struct nouveau_pushbuf *push = pNv->pushbuf;
	struct nv04_fifo *fifo = pNv->channel->data;
	uint32_t class = 0;
	int i;

	if (((pNv->dev->chipset & 0xf0) != NV_ARCH_10) &&
	    ((pNv->dev->chipset & 0xf0) != NV_ARCH_20))
		return FALSE;

	if (pNv->dev->chipset >= 0x20 || pNv->dev->chipset == 0x1a)
		class = NV15_3D_CLASS;
	else if (pNv->dev->chipset >= 0x17)
		class = NV17_3D_CLASS;
	else if (pNv->dev->chipset >= 0x11)
		class = NV15_3D_CLASS;
	else
		class = NV10_3D_CLASS;

	if (nouveau_object_new(pNv->channel, Nv3D, class, NULL, 0, &pNv->Nv3D))
		return FALSE;

	if (!PUSH_SPACE(push, 256))
		return FALSE;

	BEGIN_NV04(push, NV01_SUBC(3D, OBJECT), 1);
	PUSH_DATA (push, pNv->Nv3D->handle);
	BEGIN_NV04(push, NV10_3D(DMA_NOTIFY), 1);
	PUSH_DATA (push, pNv->NvNull->handle);

	BEGIN_NV04(push, NV10_3D(DMA_TEXTURE0), 2);
	PUSH_DATA (push, fifo->vram);
	PUSH_DATA (push, fifo->gart);

	BEGIN_NV04(push, NV10_3D(DMA_COLOR), 2);
	PUSH_DATA (push, fifo->vram);
	PUSH_DATA (push, fifo->vram);

	BEGIN_NV04(push, NV04_GRAPH(3D, NOP), 1);
	PUSH_DATA (push, 0);

	BEGIN_NV04(push, NV10_3D(RT_HORIZ), 2);
	PUSH_DATA (push, 2048 << 16 | 0);
	PUSH_DATA (push, 2048 << 16 | 0);

	BEGIN_NV04(push, NV10_3D(ZETA_OFFSET), 1);
	PUSH_DATA (push, 0);

	BEGIN_NV04(push, NV10_3D(VIEWPORT_CLIP_MODE), 1);
	PUSH_DATA (push, 0);

	BEGIN_NV04(push, NV10_3D(VIEWPORT_CLIP_HORIZ(0)), 1);
	PUSH_DATA (push, 0x7ff << 16 | 0x800);
	BEGIN_NV04(push, NV10_3D(VIEWPORT_CLIP_VERT(0)), 1);
	PUSH_DATA (push, 0x7ff << 16 | 0x800);

	for (i = 1; i < 8; i++) {
		BEGIN_NV04(push, NV10_3D(VIEWPORT_CLIP_HORIZ(i)), 1);
		PUSH_DATA (push, 0);
		BEGIN_NV04(push, NV10_3D(VIEWPORT_CLIP_VERT(i)), 1);
		PUSH_DATA (push, 0);
	}

	BEGIN_NV04(push, NV10_3D(UNK0290), 1);
	PUSH_DATA (push, (0x10<<16)|1);
	BEGIN_NV04(push, NV10_3D(UNK03F4), 1);
	PUSH_DATA (push, 0);

	BEGIN_NV04(push, NV04_GRAPH(3D, NOP), 1);
	PUSH_DATA (push, 0);

	if (class != NV10_3D_CLASS) {
		/* For nv11, nv17 */
		BEGIN_NV04(push, SUBC_3D(NV15_3D_FLIP_SET_READ), 3);
		PUSH_DATA (push, 0);
		PUSH_DATA (push, 1);
		PUSH_DATA (push, 2);

		BEGIN_NV04(push, NV15_BLIT(FLIP_SET_READ), 3);
		PUSH_DATA (push, 0);
		PUSH_DATA (push, 1);
		PUSH_DATA (push, 2);

		BEGIN_NV04(push, NV04_GRAPH(3D, NOP), 1);
		PUSH_DATA (push, 0);
	}

	BEGIN_NV04(push, NV04_GRAPH(3D, NOP), 1);
	PUSH_DATA (push, 0);

	/* Set state */
	BEGIN_NV04(push, NV10_3D(FOG_ENABLE), 1);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(ALPHA_FUNC_ENABLE), 1);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(ALPHA_FUNC_FUNC), 2);
	PUSH_DATA (push, 0x207);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(TEX_ENABLE(0)), 2);
	PUSH_DATA (push, 0);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(RC_IN_ALPHA(0)), 6);
	PUSH_DATA (push, 0);
	PUSH_DATA (push, 0);
	PUSH_DATA (push, 0);
	PUSH_DATA (push, 0);
	PUSH_DATA (push, 0);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(RC_OUT_ALPHA(0)), 6);
	PUSH_DATA (push, 0x00000c00);
	PUSH_DATA (push, 0);
	PUSH_DATA (push, 0x00000c00);
	PUSH_DATA (push, 0x18000000);
	PUSH_DATA (push, 0x300c0000);
	PUSH_DATA (push, 0x00001c80);
	BEGIN_NV04(push, NV10_3D(BLEND_FUNC_ENABLE), 1);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(DITHER_ENABLE), 2);
	PUSH_DATA (push, 1);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(LINE_SMOOTH_ENABLE), 1);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(VERTEX_WEIGHT_ENABLE), 2);
	PUSH_DATA (push, 0);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(BLEND_FUNC_SRC), 4);
	PUSH_DATA (push, 1);
	PUSH_DATA (push, 0);
	PUSH_DATA (push, 0);
	PUSH_DATA (push, 0x8006);
	BEGIN_NV04(push, NV10_3D(STENCIL_MASK), 8);
	PUSH_DATA (push, 0xff);
	PUSH_DATA (push, 0x207);
	PUSH_DATA (push, 0);
	PUSH_DATA (push, 0xff);
	PUSH_DATA (push, 0x1e00);
	PUSH_DATA (push, 0x1e00);
	PUSH_DATA (push, 0x1e00);
	PUSH_DATA (push, 0x1d01);
	BEGIN_NV04(push, NV10_3D(NORMALIZE_ENABLE), 1);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(FOG_ENABLE), 2);
	PUSH_DATA (push, 0);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(LIGHT_MODEL), 1);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(SEPARATE_SPECULAR_ENABLE), 1);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(ENABLED_LIGHTS), 1);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(POLYGON_OFFSET_POINT_ENABLE), 3);
	PUSH_DATA (push, 0);
	PUSH_DATA (push, 0);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(DEPTH_FUNC), 1);
	PUSH_DATA (push, 0x201);
	BEGIN_NV04(push, NV10_3D(DEPTH_WRITE_ENABLE), 1);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(DEPTH_TEST_ENABLE), 1);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(POLYGON_OFFSET_FACTOR), 2);
	PUSH_DATA (push, 0);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(POINT_SIZE), 1);
	PUSH_DATA (push, 8);
	BEGIN_NV04(push, NV10_3D(POINT_PARAMETERS_ENABLE), 2);
	PUSH_DATA (push, 0);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(LINE_WIDTH), 1);
	PUSH_DATA (push, 8);
	BEGIN_NV04(push, NV10_3D(LINE_SMOOTH_ENABLE), 1);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(POLYGON_MODE_FRONT), 2);
	PUSH_DATA (push, 0x1b02);
	PUSH_DATA (push, 0x1b02);
	BEGIN_NV04(push, NV10_3D(CULL_FACE), 2);
	PUSH_DATA (push, 0x405);
	PUSH_DATA (push, 0x901);
	BEGIN_NV04(push, NV10_3D(POLYGON_SMOOTH_ENABLE), 1);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(CULL_FACE_ENABLE), 1);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(TEX_GEN_MODE(0, 0)), 8);
	for (i = 0; i < 8; i++)
		PUSH_DATA (push, 0);

	BEGIN_NV04(push, NV10_3D(FOG_COEFF(0)), 3);
	PUSH_DATA (push, 0x3fc00000);	/* -1.50 */
	PUSH_DATA (push, 0xbdb8aa0a);	/* -0.09 */
	PUSH_DATA (push, 0);		/*  0.00 */

	BEGIN_NV04(push, NV04_GRAPH(3D, NOP), 1);
	PUSH_DATA (push, 0);

	BEGIN_NV04(push, NV10_3D(FOG_MODE), 2);
	PUSH_DATA (push, 0x802);
	PUSH_DATA (push, 2);
	/* for some reason VIEW_MATRIX_ENABLE need to be 6 instead of 4 when
	 * using texturing, except when using the texture matrix
	 */
	BEGIN_NV04(push, NV10_3D(VIEW_MATRIX_ENABLE), 1);
	PUSH_DATA (push, 6);
	BEGIN_NV04(push, NV10_3D(COLOR_MASK), 1);
	PUSH_DATA (push, 0x01010101);

	BEGIN_NV04(push, NV10_3D(PROJECTION_MATRIX(0)), 16);
	for(i = 0; i < 16; i++)
		PUSH_DATAf(push, i/4 == i%4 ? 1.0 : 0.0);

	BEGIN_NV04(push, NV10_3D(DEPTH_RANGE_NEAR), 2);
	PUSH_DATA (push, 0);
	PUSH_DATAf(push, 65536.0);

	BEGIN_NV04(push, NV10_3D(VIEWPORT_TRANSLATE_X), 4);
	PUSH_DATAf(push, -2048.0);
	PUSH_DATAf(push, -2048.0);
	PUSH_DATAf(push, 0);
	PUSH_DATA (push, 0);

	/* Set vertex component */
	BEGIN_NV04(push, NV10_3D(VERTEX_COL_4F_R), 4);
	PUSH_DATAf(push, 1.0);
	PUSH_DATAf(push, 1.0);
	PUSH_DATAf(push, 1.0);
	PUSH_DATAf(push, 1.0);
	BEGIN_NV04(push, NV10_3D(VERTEX_COL2_3F_R), 3);
	PUSH_DATA (push, 0);
	PUSH_DATA (push, 0);
	PUSH_DATA (push, 0);
	BEGIN_NV04(push, NV10_3D(VERTEX_NOR_3F_X), 3);
	PUSH_DATA (push, 0);
	PUSH_DATA (push, 0);
	PUSH_DATAf(push, 1.0);
	BEGIN_NV04(push, NV10_3D(VERTEX_TX0_4F_S), 4);
	PUSH_DATAf(push, 0.0);
	PUSH_DATAf(push, 0.0);
	PUSH_DATAf(push, 0.0);
	PUSH_DATAf(push, 1.0);
	BEGIN_NV04(push, NV10_3D(VERTEX_TX1_4F_S), 4);
	PUSH_DATAf(push, 0.0);
	PUSH_DATAf(push, 0.0);
	PUSH_DATAf(push, 0.0);
	PUSH_DATAf(push, 1.0);
	BEGIN_NV04(push, NV10_3D(VERTEX_FOG_1F), 1);
	PUSH_DATAf(push, 0.0);
	BEGIN_NV04(push, NV10_3D(EDGEFLAG_ENABLE), 1);
	PUSH_DATA (push, 1);

	return TRUE;
}
