/*
 * Copyright (C) 2007-2010 The Nouveau Project.
 * All Rights Reserved.
 *
 * 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 (including the
 * next paragraph) 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 COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS 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 "nouveau_driver.h"
#include "nv_object.xml.h"
#include "nv_m2mf.xml.h"
#include "nv01_2d.xml.h"
#include "nv04_3d.xml.h"
#include "nouveau_context.h"
#include "nouveau_util.h"
#include "nv04_driver.h"

static inline int
swzsurf_format(gl_format format)
{
	switch (format) {
	case MESA_FORMAT_A8:
	case MESA_FORMAT_L8:
	case MESA_FORMAT_I8:
	case MESA_FORMAT_RGB332:
	case MESA_FORMAT_CI8:
		return NV04_SWIZZLED_SURFACE_FORMAT_COLOR_Y8;

	case MESA_FORMAT_RGB565:
	case MESA_FORMAT_RGB565_REV:
	case MESA_FORMAT_ARGB4444:
	case MESA_FORMAT_ARGB4444_REV:
	case MESA_FORMAT_ARGB1555:
	case MESA_FORMAT_RGBA5551:
	case MESA_FORMAT_ARGB1555_REV:
	case MESA_FORMAT_AL88:
	case MESA_FORMAT_AL88_REV:
	case MESA_FORMAT_YCBCR:
	case MESA_FORMAT_YCBCR_REV:
	case MESA_FORMAT_Z16:
		return NV04_SWIZZLED_SURFACE_FORMAT_COLOR_R5G6B5;

	case MESA_FORMAT_RGBA8888:
	case MESA_FORMAT_RGBA8888_REV:
	case MESA_FORMAT_XRGB8888:
	case MESA_FORMAT_ARGB8888:
	case MESA_FORMAT_ARGB8888_REV:
	case MESA_FORMAT_S8_Z24:
	case MESA_FORMAT_Z24_S8:
	case MESA_FORMAT_Z32:
		return NV04_SWIZZLED_SURFACE_FORMAT_COLOR_A8R8G8B8;

	default:
		assert(0);
	}
}

static inline int
surf2d_format(gl_format format)
{
	switch (format) {
	case MESA_FORMAT_A8:
	case MESA_FORMAT_L8:
	case MESA_FORMAT_I8:
	case MESA_FORMAT_RGB332:
	case MESA_FORMAT_CI8:
		return NV04_CONTEXT_SURFACES_2D_FORMAT_Y8;

	case MESA_FORMAT_RGB565:
	case MESA_FORMAT_RGB565_REV:
	case MESA_FORMAT_ARGB4444:
	case MESA_FORMAT_ARGB4444_REV:
	case MESA_FORMAT_ARGB1555:
	case MESA_FORMAT_RGBA5551:
	case MESA_FORMAT_ARGB1555_REV:
	case MESA_FORMAT_AL88:
	case MESA_FORMAT_AL88_REV:
	case MESA_FORMAT_YCBCR:
	case MESA_FORMAT_YCBCR_REV:
	case MESA_FORMAT_Z16:
		return NV04_CONTEXT_SURFACES_2D_FORMAT_R5G6B5;

	case MESA_FORMAT_RGBA8888:
	case MESA_FORMAT_RGBA8888_REV:
	case MESA_FORMAT_XRGB8888:
	case MESA_FORMAT_ARGB8888:
	case MESA_FORMAT_ARGB8888_REV:
	case MESA_FORMAT_S8_Z24:
	case MESA_FORMAT_Z24_S8:
	case MESA_FORMAT_Z32:
		return NV04_CONTEXT_SURFACES_2D_FORMAT_Y32;

	default:
		assert(0);
	}
}

static inline int
rect_format(gl_format format)
{
	switch (format) {
	case MESA_FORMAT_A8:
	case MESA_FORMAT_L8:
	case MESA_FORMAT_I8:
	case MESA_FORMAT_RGB332:
	case MESA_FORMAT_CI8:
		return NV04_GDI_RECTANGLE_TEXT_COLOR_FORMAT_A8R8G8B8;

	case MESA_FORMAT_RGB565:
	case MESA_FORMAT_RGB565_REV:
	case MESA_FORMAT_ARGB4444:
	case MESA_FORMAT_ARGB4444_REV:
	case MESA_FORMAT_ARGB1555:
	case MESA_FORMAT_RGBA5551:
	case MESA_FORMAT_ARGB1555_REV:
	case MESA_FORMAT_AL88:
	case MESA_FORMAT_AL88_REV:
	case MESA_FORMAT_YCBCR:
	case MESA_FORMAT_YCBCR_REV:
	case MESA_FORMAT_Z16:
		return NV04_GDI_RECTANGLE_TEXT_COLOR_FORMAT_A16R5G6B5;

	case MESA_FORMAT_RGBA8888:
	case MESA_FORMAT_RGBA8888_REV:
	case MESA_FORMAT_XRGB8888:
	case MESA_FORMAT_ARGB8888:
	case MESA_FORMAT_ARGB8888_REV:
	case MESA_FORMAT_S8_Z24:
	case MESA_FORMAT_Z24_S8:
	case MESA_FORMAT_Z32:
		return NV04_GDI_RECTANGLE_TEXT_COLOR_FORMAT_A8R8G8B8;

	default:
		assert(0);
	}
}

static inline int
sifm_format(gl_format format)
{
	switch (format) {
	case MESA_FORMAT_A8:
	case MESA_FORMAT_L8:
	case MESA_FORMAT_I8:
	case MESA_FORMAT_RGB332:
	case MESA_FORMAT_CI8:
		return NV03_SCALED_IMAGE_FROM_MEMORY_COLOR_FORMAT_AY8;

	case MESA_FORMAT_RGB565:
	case MESA_FORMAT_RGB565_REV:
	case MESA_FORMAT_ARGB4444:
	case MESA_FORMAT_ARGB4444_REV:
	case MESA_FORMAT_ARGB1555:
	case MESA_FORMAT_RGBA5551:
	case MESA_FORMAT_ARGB1555_REV:
	case MESA_FORMAT_AL88:
	case MESA_FORMAT_AL88_REV:
	case MESA_FORMAT_YCBCR:
	case MESA_FORMAT_YCBCR_REV:
	case MESA_FORMAT_Z16:
		return NV03_SCALED_IMAGE_FROM_MEMORY_COLOR_FORMAT_R5G6B5;

	case MESA_FORMAT_RGBA8888:
	case MESA_FORMAT_RGBA8888_REV:
	case MESA_FORMAT_XRGB8888:
	case MESA_FORMAT_ARGB8888:
	case MESA_FORMAT_ARGB8888_REV:
	case MESA_FORMAT_S8_Z24:
	case MESA_FORMAT_Z24_S8:
	case MESA_FORMAT_Z32:
		return NV03_SCALED_IMAGE_FROM_MEMORY_COLOR_FORMAT_A8R8G8B8;

	default:
		assert(0);
	}
}

static void
nv04_surface_copy_swizzle(struct gl_context *ctx,
			  struct nouveau_surface *dst,
			  struct nouveau_surface *src,
			  int dx, int dy, int sx, int sy,
			  int w, int h)
{
	struct nouveau_channel *chan = context_chan(ctx);
	struct nouveau_hw_state *hw = &to_nouveau_context(ctx)->hw;
	struct nouveau_grobj *swzsurf = hw->swzsurf;
	struct nouveau_grobj *sifm = hw->sifm;
	struct nouveau_bo_context *bctx = context_bctx(ctx, SURFACE);
	const unsigned bo_flags = NOUVEAU_BO_VRAM | NOUVEAU_BO_GART;
	/* Max width & height may not be the same on all HW, but must be POT */
	const unsigned max_w = 1024;
	const unsigned max_h = 1024;
	unsigned sub_w = w > max_w ? max_w : w;
	unsigned sub_h = h > max_h ? max_h : h;
	unsigned x, y;

        /* Swizzled surfaces must be POT  */
	assert(_mesa_is_pow_two(dst->width) &&
	       _mesa_is_pow_two(dst->height));

	nouveau_bo_marko(bctx, sifm, NV03_SCALED_IMAGE_FROM_MEMORY_DMA_IMAGE,
			 src->bo, bo_flags | NOUVEAU_BO_RD);
	nouveau_bo_marko(bctx, swzsurf, NV04_SWIZZLED_SURFACE_DMA_IMAGE,
			 dst->bo, NOUVEAU_BO_VRAM | NOUVEAU_BO_WR);
	nouveau_bo_markl(bctx, swzsurf, NV04_SWIZZLED_SURFACE_OFFSET,
			 dst->bo, dst->offset, NOUVEAU_BO_VRAM | NOUVEAU_BO_WR);

	BEGIN_RING(chan, swzsurf, NV04_SWIZZLED_SURFACE_FORMAT, 1);
	OUT_RING  (chan, swzsurf_format(dst->format) |
		   log2i(dst->width) << 16 |
		   log2i(dst->height) << 24);

	BEGIN_RING(chan, sifm, NV04_SCALED_IMAGE_FROM_MEMORY_SURFACE, 1);
	OUT_RING  (chan, swzsurf->handle);

	for (y = 0; y < h; y += sub_h) {
		sub_h = MIN2(sub_h, h - y);

		for (x = 0; x < w; x += sub_w) {
			sub_w = MIN2(sub_w, w - x);

			MARK_RING(chan, 15, 1);

			BEGIN_RING(chan, sifm,
				   NV03_SCALED_IMAGE_FROM_MEMORY_COLOR_FORMAT, 8);
			OUT_RING(chan, sifm_format(src->format));
			OUT_RING(chan, NV03_SCALED_IMAGE_FROM_MEMORY_OPERATION_SRCCOPY);
			OUT_RING(chan, (y + dy) << 16 | (x + dx));
			OUT_RING(chan, sub_h << 16 | sub_w);
			OUT_RING(chan, (y + dy) << 16 | (x + dx));
			OUT_RING(chan, sub_h << 16 | sub_w);
			OUT_RING(chan, 1 << 20);
			OUT_RING(chan, 1 << 20);

			BEGIN_RING(chan, sifm,
				   NV03_SCALED_IMAGE_FROM_MEMORY_SIZE, 4);
			OUT_RING(chan, align(sub_h, 2) << 16 | align(sub_w, 2));
			OUT_RING(chan, src->pitch  |
				 NV03_SCALED_IMAGE_FROM_MEMORY_FORMAT_ORIGIN_CENTER |
				 NV03_SCALED_IMAGE_FROM_MEMORY_FORMAT_FILTER_POINT_SAMPLE);
			OUT_RELOCl(chan, src->bo, src->offset +
				   (y + sy) * src->pitch +
				   (x + sx) * src->cpp,
				   bo_flags | NOUVEAU_BO_RD);
			OUT_RING(chan, 0);
		}
	}

	nouveau_bo_context_reset(bctx);

	if (context_chipset(ctx) < 0x10)
		FIRE_RING(chan);
}

static void
nv04_surface_copy_m2mf(struct gl_context *ctx,
		       struct nouveau_surface *dst,
		       struct nouveau_surface *src,
		       int dx, int dy, int sx, int sy,
		       int w, int h)
{
	struct nouveau_channel *chan = context_chan(ctx);
	struct nouveau_hw_state *hw = &to_nouveau_context(ctx)->hw;
	struct nouveau_grobj *m2mf = hw->m2mf;
	struct nouveau_bo_context *bctx = context_bctx(ctx, SURFACE);
	const unsigned bo_flags = NOUVEAU_BO_VRAM | NOUVEAU_BO_GART;
	unsigned dst_offset = dst->offset + dy * dst->pitch + dx * dst->cpp;
	unsigned src_offset = src->offset + sy * src->pitch + sx * src->cpp;

	nouveau_bo_marko(bctx, m2mf, NV04_M2MF_DMA_BUFFER_IN,
			 src->bo, bo_flags | NOUVEAU_BO_RD);
	nouveau_bo_marko(bctx, m2mf, NV04_M2MF_DMA_BUFFER_OUT,
			 dst->bo, bo_flags | NOUVEAU_BO_WR);

	while (h) {
		int count = (h > 2047) ? 2047 : h;

		MARK_RING(chan, 9, 2);

		BEGIN_RING(chan, m2mf, NV04_M2MF_OFFSET_IN, 8);
		OUT_RELOCl(chan, src->bo, src_offset,
			   bo_flags | NOUVEAU_BO_RD);
		OUT_RELOCl(chan, dst->bo, dst_offset,
			   bo_flags | NOUVEAU_BO_WR);
		OUT_RING  (chan, src->pitch);
		OUT_RING  (chan, dst->pitch);
		OUT_RING  (chan, w * src->cpp);
		OUT_RING  (chan, count);
		OUT_RING  (chan, 0x0101);
		OUT_RING  (chan, 0);

		h -= count;
		src_offset += src->pitch * count;
		dst_offset += dst->pitch * count;
	}

	nouveau_bo_context_reset(bctx);

	if (context_chipset(ctx) < 0x10)
		FIRE_RING(chan);
}

typedef unsigned (*get_offset_t)(struct nouveau_surface *s,
				 unsigned x, unsigned y);

static unsigned
get_linear_offset(struct nouveau_surface *s, unsigned x, unsigned y)
{
	return x * s->cpp + y * s->pitch;
}

static unsigned
get_swizzled_offset(struct nouveau_surface *s, unsigned x, unsigned y)
{
	unsigned k = log2i(MIN2(s->width, s->height));

	unsigned u = (x & 0x001) << 0 |
		(x & 0x002) << 1 |
		(x & 0x004) << 2 |
		(x & 0x008) << 3 |
		(x & 0x010) << 4 |
		(x & 0x020) << 5 |
		(x & 0x040) << 6 |
		(x & 0x080) << 7 |
		(x & 0x100) << 8 |
		(x & 0x200) << 9 |
		(x & 0x400) << 10 |
		(x & 0x800) << 11;

	unsigned v = (y & 0x001) << 1 |
		(y & 0x002) << 2 |
		(y & 0x004) << 3 |
		(y & 0x008) << 4 |
		(y & 0x010) << 5 |
		(y & 0x020) << 6 |
		(y & 0x040) << 7 |
		(y & 0x080) << 8 |
		(y & 0x100) << 9 |
		(y & 0x200) << 10 |
		(y & 0x400) << 11 |
		(y & 0x800) << 12;

	return s->cpp * (((u | v) & ~(~0 << 2*k)) |
			 (x & (~0 << k)) << k |
			 (y & (~0 << k)) << k);
}

static void
nv04_surface_copy_cpu(struct gl_context *ctx,
		      struct nouveau_surface *dst,
		      struct nouveau_surface *src,
		      int dx, int dy, int sx, int sy,
		      int w, int h)
{
	int x, y;
	get_offset_t get_dst = (dst->layout == SWIZZLED ?
				get_swizzled_offset : get_linear_offset);
	get_offset_t get_src = (src->layout == SWIZZLED ?
				get_swizzled_offset : get_linear_offset);
	void *dp, *sp;

	nouveau_bo_map(dst->bo, NOUVEAU_BO_WR);
	nouveau_bo_map(src->bo, NOUVEAU_BO_RD);

	dp = dst->bo->map + dst->offset;
	sp = src->bo->map + src->offset;

	for (y = 0; y < h; y++) {
		for (x = 0; x < w; x++) {
			memcpy(dp + get_dst(dst, dx + x, dy + y),
			       sp + get_src(src, sx + x, sy + y), dst->cpp);
		}
	}

	nouveau_bo_unmap(src->bo);
	nouveau_bo_unmap(dst->bo);
}

void
nv04_surface_copy(struct gl_context *ctx,
		  struct nouveau_surface *dst,
		  struct nouveau_surface *src,
		  int dx, int dy, int sx, int sy,
		  int w, int h)
{
	/* Linear texture copy. */
	if ((src->layout == LINEAR && dst->layout == LINEAR) ||
	    dst->width <= 2 || dst->height <= 1) {
		nv04_surface_copy_m2mf(ctx, dst, src, dx, dy, sx, sy, w, h);
		return;
	}

	/* Swizzle using sifm+swzsurf. */
        if (src->layout == LINEAR && dst->layout == SWIZZLED &&
	    dst->cpp != 1 && !(dst->offset & 63)) {
		nv04_surface_copy_swizzle(ctx, dst, src, dx, dy, sx, sy, w, h);
		return;
	}

	/* Fallback to CPU copy. */
	nv04_surface_copy_cpu(ctx, dst, src, dx, dy, sx, sy, w, h);
}

void
nv04_surface_fill(struct gl_context *ctx,
		  struct nouveau_surface *dst,
		  unsigned mask, unsigned value,
		  int dx, int dy, int w, int h)
{
	struct nouveau_channel *chan = context_chan(ctx);
	struct nouveau_hw_state *hw = &to_nouveau_context(ctx)->hw;
	struct nouveau_grobj *surf2d = hw->surf2d;
	struct nouveau_grobj *patt = hw->patt;
	struct nouveau_grobj *rect = hw->rect;
	unsigned bo_flags = NOUVEAU_BO_VRAM | NOUVEAU_BO_GART;

	MARK_RING (chan, 19, 4);

	BEGIN_RING(chan, surf2d, NV04_CONTEXT_SURFACES_2D_DMA_IMAGE_SOURCE, 2);
	OUT_RELOCo(chan, dst->bo, bo_flags | NOUVEAU_BO_WR);
	OUT_RELOCo(chan, dst->bo, bo_flags | NOUVEAU_BO_WR);
	BEGIN_RING(chan, surf2d, NV04_CONTEXT_SURFACES_2D_FORMAT, 4);
	OUT_RING  (chan, surf2d_format(dst->format));
	OUT_RING  (chan, (dst->pitch << 16) | dst->pitch);
	OUT_RELOCl(chan, dst->bo, dst->offset, bo_flags | NOUVEAU_BO_WR);
	OUT_RELOCl(chan, dst->bo, dst->offset, bo_flags | NOUVEAU_BO_WR);

	BEGIN_RING(chan, patt, NV04_IMAGE_PATTERN_COLOR_FORMAT, 1);
	OUT_RING  (chan, rect_format(dst->format));
	BEGIN_RING(chan, patt, NV04_IMAGE_PATTERN_MONOCHROME_COLOR1, 1);
	OUT_RING  (chan, mask | ~0ll << (8 * dst->cpp));

	BEGIN_RING(chan, rect, NV04_GDI_RECTANGLE_TEXT_COLOR_FORMAT, 1);
	OUT_RING  (chan, rect_format(dst->format));
	BEGIN_RING(chan, rect, NV04_GDI_RECTANGLE_TEXT_COLOR1_A, 1);
	OUT_RING  (chan, value);
	BEGIN_RING(chan, rect,
		   NV04_GDI_RECTANGLE_TEXT_UNCLIPPED_RECTANGLE_POINT(0), 2);
	OUT_RING  (chan, (dx << 16) | dy);
	OUT_RING  (chan, ( w << 16) |  h);

	if (context_chipset(ctx) < 0x10)
		FIRE_RING(chan);
}

void
nv04_surface_takedown(struct gl_context *ctx)
{
	struct nouveau_hw_state *hw = &to_nouveau_context(ctx)->hw;

	nouveau_grobj_free(&hw->swzsurf);
	nouveau_grobj_free(&hw->sifm);
	nouveau_grobj_free(&hw->rect);
	nouveau_grobj_free(&hw->rop);
	nouveau_grobj_free(&hw->patt);
	nouveau_grobj_free(&hw->surf2d);
	nouveau_grobj_free(&hw->m2mf);
	nouveau_notifier_free(&hw->ntfy);
}

GLboolean
nv04_surface_init(struct gl_context *ctx)
{
	struct nouveau_channel *chan = context_chan(ctx);
	struct nouveau_hw_state *hw = &to_nouveau_context(ctx)->hw;
	unsigned handle = 0x88000000, class;
	int ret;

	/* Notifier object. */
	ret = nouveau_notifier_alloc(chan, handle++, 1, &hw->ntfy);
	if (ret)
		goto fail;

	/* Memory to memory format. */
	ret = nouveau_grobj_alloc(chan, handle++, NV04_M2MF, &hw->m2mf);
	if (ret)
		goto fail;

	BEGIN_RING(chan, hw->m2mf, NV04_M2MF_DMA_NOTIFY, 1);
	OUT_RING  (chan, hw->ntfy->handle);

	/* Context surfaces 2D. */
	if (context_chipset(ctx) < 0x10)
		class = NV04_CONTEXT_SURFACES_2D;
	else
		class = NV10_CONTEXT_SURFACES_2D;

	ret = nouveau_grobj_alloc(chan, handle++, class, &hw->surf2d);
	if (ret)
		goto fail;

	/* Raster op. */
	ret = nouveau_grobj_alloc(chan, handle++, NV03_CONTEXT_ROP, &hw->rop);
	if (ret)
		goto fail;

	BEGIN_RING(chan, hw->rop, NV03_CONTEXT_ROP_DMA_NOTIFY, 1);
	OUT_RING  (chan, hw->ntfy->handle);

	BEGIN_RING(chan, hw->rop, NV03_CONTEXT_ROP_ROP, 1);
	OUT_RING  (chan, 0xca); /* DPSDxax in the GDI speech. */

	/* Image pattern. */
	ret = nouveau_grobj_alloc(chan, handle++, NV04_IMAGE_PATTERN,
				  &hw->patt);
	if (ret)
		goto fail;

	BEGIN_RING(chan, hw->patt, NV04_IMAGE_PATTERN_DMA_NOTIFY, 1);
	OUT_RING  (chan, hw->ntfy->handle);

	BEGIN_RING(chan, hw->patt, NV04_IMAGE_PATTERN_MONOCHROME_FORMAT, 3);
	OUT_RING  (chan, NV04_IMAGE_PATTERN_MONOCHROME_FORMAT_LE);
	OUT_RING  (chan, NV04_IMAGE_PATTERN_MONOCHROME_SHAPE_8X8);
	OUT_RING  (chan, NV04_IMAGE_PATTERN_PATTERN_SELECT_MONO);

	BEGIN_RING(chan, hw->patt, NV04_IMAGE_PATTERN_MONOCHROME_COLOR0, 4);
	OUT_RING  (chan, 0);
	OUT_RING  (chan, 0);
	OUT_RING  (chan, ~0);
	OUT_RING  (chan, ~0);

	/* GDI rectangle text. */
	ret = nouveau_grobj_alloc(chan, handle++, NV04_GDI_RECTANGLE_TEXT,
				  &hw->rect);
	if (ret)
		goto fail;

	BEGIN_RING(chan, hw->rect, NV04_GDI_RECTANGLE_TEXT_DMA_NOTIFY, 1);
	OUT_RING  (chan, hw->ntfy->handle);
	BEGIN_RING(chan, hw->rect, NV04_GDI_RECTANGLE_TEXT_SURFACE, 1);
	OUT_RING  (chan, hw->surf2d->handle);
	BEGIN_RING(chan, hw->rect, NV04_GDI_RECTANGLE_TEXT_ROP, 1);
	OUT_RING  (chan, hw->rop->handle);
	BEGIN_RING(chan, hw->rect, NV04_GDI_RECTANGLE_TEXT_PATTERN, 1);
	OUT_RING  (chan, hw->patt->handle);

	BEGIN_RING(chan, hw->rect, NV04_GDI_RECTANGLE_TEXT_OPERATION, 1);
	OUT_RING  (chan, NV04_GDI_RECTANGLE_TEXT_OPERATION_ROP_AND);
	BEGIN_RING(chan, hw->rect,
		   NV04_GDI_RECTANGLE_TEXT_MONOCHROME_FORMAT, 1);
	OUT_RING  (chan, NV04_GDI_RECTANGLE_TEXT_MONOCHROME_FORMAT_LE);

	/* Swizzled surface. */
	if (context_chipset(ctx) < 0x20)
		class = NV04_SWIZZLED_SURFACE;
	else
		class = NV20_SWIZZLED_SURFACE;

	ret = nouveau_grobj_alloc(chan, handle++, class, &hw->swzsurf);
	if (ret)
		goto fail;

	/* Scaled image from memory. */
	if  (context_chipset(ctx) < 0x10)
		class = NV04_SCALED_IMAGE_FROM_MEMORY;
	else
		class = NV10_SCALED_IMAGE_FROM_MEMORY;

	ret = nouveau_grobj_alloc(chan, handle++, class, &hw->sifm);
	if (ret)
		goto fail;

	if (context_chipset(ctx) >= 0x10) {
		BEGIN_RING(chan, hw->sifm,
			   NV05_SCALED_IMAGE_FROM_MEMORY_COLOR_CONVERSION, 1);
		OUT_RING(chan, NV05_SCALED_IMAGE_FROM_MEMORY_COLOR_CONVERSION_TRUNCATE);
	}

	return GL_TRUE;

fail:
	nv04_surface_takedown(ctx);
	return GL_FALSE;
}
