/*
* Copyright (C) 2009 Francisco Jerez.
* 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 "nouveau_context.h"
#include "nouveau_texture.h"
#include "nouveau_fbo.h"
#include "nouveau_util.h"
#include "main/pbo.h"
#include "main/texobj.h"
#include "main/texstore.h"
#include "main/texformat.h"
#include "main/texcompress.h"
#include "main/texgetimage.h"
#include "main/mipmap.h"
#include "main/teximage.h"
#include "drivers/common/meta.h"
#include "swrast/s_texfetch.h"
static struct gl_texture_object *
nouveau_texture_new(struct gl_context *ctx, GLuint name, GLenum target)
{
struct nouveau_texture *nt = CALLOC_STRUCT(nouveau_texture);
_mesa_initialize_texture_object(ctx, &nt->base, name, target);
return &nt->base;
}
static void
nouveau_texture_free(struct gl_context *ctx, struct gl_texture_object *t)
{
struct nouveau_texture *nt = to_nouveau_texture(t);
int i;
for (i = 0; i < MAX_TEXTURE_LEVELS; i++)
nouveau_surface_ref(NULL, &nt->surfaces[i]);
_mesa_delete_texture_object(ctx, t);
}
static struct gl_texture_image *
nouveau_teximage_new(struct gl_context *ctx)
{
struct nouveau_teximage *nti = CALLOC_STRUCT(nouveau_teximage);
return &nti->base.Base;
}
static void
nouveau_teximage_free(struct gl_context *ctx, struct gl_texture_image *ti)
{
struct nouveau_teximage *nti = to_nouveau_teximage(ti);
nouveau_surface_ref(NULL, &nti->surface);
}
static void
nouveau_map_texture_image(struct gl_context *ctx,
struct gl_texture_image *ti,
GLuint slice,
GLuint x, GLuint y, GLuint w, GLuint h,
GLbitfield mode,
GLubyte **map,
GLint *stride)
{
struct nouveau_teximage *nti = to_nouveau_teximage(ti);
struct nouveau_surface *s = &nti->surface;
struct nouveau_surface *st = &nti->transfer.surface;
struct nouveau_client *client = context_client(ctx);
/* Nouveau has no support for 3D or cubemap textures. */
assert(slice == 0);
if (s->bo) {
if (!(mode & GL_MAP_READ_BIT) &&
nouveau_pushbuf_refd(context_push(ctx), s->bo)) {
unsigned size;
/*
* Heuristic: use a bounce buffer to pipeline
* teximage transfers.
*/
st->layout = LINEAR;
st->format = s->format;
st->cpp = s->cpp;
st->width = w;
st->height = h;
st->pitch = s->pitch;
nti->transfer.x = x;
nti->transfer.y = y;
size = get_format_blocksy(st->format, h) * st->pitch;
*map = nouveau_get_scratch(ctx, size,
&st->bo, &st->offset);
*stride = st->pitch;
} else {
int ret, flags = 0;
if (mode & GL_MAP_READ_BIT)
flags |= NOUVEAU_BO_RD;
if (mode & GL_MAP_WRITE_BIT)
flags |= NOUVEAU_BO_WR;
if (!s->bo->map) {
ret = nouveau_bo_map(s->bo, flags, client);
assert(!ret);
}
*map = s->bo->map +
get_format_blocksy(s->format, y) * s->pitch +
get_format_blocksx(s->format, x) * s->cpp;
*stride = s->pitch;
}
} else {
*map = nti->base.Buffer +
get_format_blocksy(s->format, y) * s->pitch +
get_format_blocksx(s->format, x) * s->cpp;
*stride = s->pitch;
}
}
static void
nouveau_unmap_texture_image(struct gl_context *ctx, struct gl_texture_image *ti,
GLuint slice)
{
struct nouveau_teximage *nti = to_nouveau_teximage(ti);
struct nouveau_surface *s = &nti->surface;
struct nouveau_surface *st = &nti->transfer.surface;
if (st->bo) {
context_drv(ctx)->surface_copy(ctx, s, st, nti->transfer.x,
nti->transfer.y, 0, 0,
st->width, st->height);
nouveau_surface_ref(NULL, st);
}
}
static mesa_format
nouveau_choose_tex_format(struct gl_context *ctx, GLenum target,
GLint internalFormat,
GLenum srcFormat, GLenum srcType)
{
switch (internalFormat) {
case 4:
case GL_RGBA:
case GL_RGBA2:
case GL_RGBA4:
case GL_RGBA8:
case GL_RGBA12:
case GL_RGBA16:
case GL_RGB10_A2:
case GL_COMPRESSED_RGBA:
return MESA_FORMAT_B8G8R8A8_UNORM;
case GL_RGB5_A1:
return MESA_FORMAT_B5G5R5A1_UNORM;
case GL_RGB:
case GL_RGB8:
case GL_RGB10:
case GL_RGB12:
case GL_RGB16:
case GL_COMPRESSED_RGB:
return MESA_FORMAT_B8G8R8X8_UNORM;
case 3:
case GL_R3_G3_B2:
case GL_RGB4:
case GL_RGB5:
return MESA_FORMAT_B5G6R5_UNORM;
case 2:
case GL_LUMINANCE_ALPHA:
case GL_LUMINANCE4_ALPHA4:
case GL_LUMINANCE6_ALPHA2:
case GL_LUMINANCE12_ALPHA4:
case GL_LUMINANCE12_ALPHA12:
case GL_LUMINANCE16_ALPHA16:
case GL_LUMINANCE8_ALPHA8:
case GL_COMPRESSED_LUMINANCE_ALPHA:
return MESA_FORMAT_B8G8R8A8_UNORM;
case 1:
case GL_LUMINANCE:
case GL_LUMINANCE4:
case GL_LUMINANCE12:
case GL_LUMINANCE16:
case GL_LUMINANCE8:
case GL_COMPRESSED_LUMINANCE:
return MESA_FORMAT_L_UNORM8;
case GL_ALPHA:
case GL_ALPHA4:
case GL_ALPHA12:
case GL_ALPHA16:
case GL_ALPHA8:
case GL_COMPRESSED_ALPHA:
return MESA_FORMAT_A_UNORM8;
case GL_INTENSITY:
case GL_INTENSITY4:
case GL_INTENSITY12:
case GL_INTENSITY16:
case GL_INTENSITY8:
case GL_COMPRESSED_INTENSITY:
return MESA_FORMAT_I_UNORM8;
case GL_RGB_S3TC:
case GL_RGB4_S3TC:
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
return MESA_FORMAT_RGB_DXT1;
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
return MESA_FORMAT_RGBA_DXT1;
case GL_RGBA_S3TC:
case GL_RGBA4_S3TC:
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
return MESA_FORMAT_RGBA_DXT3;
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
return MESA_FORMAT_RGBA_DXT5;
default:
assert(0);
}
}
static GLboolean
teximage_fits(struct gl_texture_object *t, int level)
{
struct nouveau_surface *s = &to_nouveau_texture(t)->surfaces[level];
struct gl_texture_image *ti = t->Image[0][level];
if (!ti || !to_nouveau_teximage(ti)->surface.bo)
return GL_FALSE;
if (level == t->BaseLevel && (s->offset & 0x7f))
return GL_FALSE;
return t->Target == GL_TEXTURE_RECTANGLE ||
(s->bo && s->format == ti->TexFormat &&
s->width == ti->Width && s->height == ti->Height);
}
static GLboolean
validate_teximage(struct gl_context *ctx, struct gl_texture_object *t,
int level, int x, int y, int z,
int width, int height, int depth)
{
struct gl_texture_image *ti = t->Image[0][level];
if (teximage_fits(t, level)) {
struct nouveau_surface *ss = to_nouveau_texture(t)->surfaces;
struct nouveau_surface *s = &to_nouveau_teximage(ti)->surface;
if (t->Target == GL_TEXTURE_RECTANGLE)
nouveau_surface_ref(s, &ss[level]);
else
context_drv(ctx)->surface_copy(ctx, &ss[level], s,
x, y, x, y,
width, height);
return GL_TRUE;
}
return GL_FALSE;
}
static int
get_last_level(struct gl_texture_object *t)
{
struct gl_texture_image *base = t->Image[0][t->BaseLevel];
if (t->Sampler.MinFilter == GL_NEAREST ||
t->Sampler.MinFilter == GL_LINEAR || !base)
return t->BaseLevel;
else
return MIN2(t->BaseLevel + base->MaxNumLevels - 1, t->MaxLevel);
}
static void
relayout_texture(struct gl_context *ctx, struct gl_texture_object *t)
{
struct gl_texture_image *base = t->Image[0][t->BaseLevel];
if (base && t->Target != GL_TEXTURE_RECTANGLE) {
struct nouveau_surface *ss = to_nouveau_texture(t)->surfaces;
struct nouveau_surface *s = &to_nouveau_teximage(base)->surface;
int i, ret, last = get_last_level(t);
enum nouveau_surface_layout layout =
(_mesa_is_format_compressed(s->format) ? LINEAR : SWIZZLED);
unsigned size, pitch, offset = 0,
width = s->width,
height = s->height;
/* Deallocate the old storage. */
for (i = 0; i < MAX_TEXTURE_LEVELS; i++)
nouveau_bo_ref(NULL, &ss[i].bo);
/* Relayout the mipmap tree. */
for (i = t->BaseLevel; i <= last; i++) {
pitch = _mesa_format_row_stride(s->format, width);
size = get_format_blocksy(s->format, height) * pitch;
/* Images larger than 16B have to be aligned. */
if (size > 16)
offset = align(offset, 64);
ss[i] = (struct nouveau_surface) {
.offset = offset,
.layout = layout,
.format = s->format,
.width = width,
.height = height,
.cpp = s->cpp,
.pitch = pitch,
};
offset += size;
width = minify(width, 1);
height = minify(height, 1);
}
if (t->BaseLevel <= last) {
/* Get new storage. */
size = align(offset, 64);
assert(size);
ret = nouveau_bo_new(context_dev(ctx), NOUVEAU_BO_MAP |
NOUVEAU_BO_GART | NOUVEAU_BO_VRAM,
0, size, NULL, &ss[last].bo);
assert(!ret);
for (i = t->BaseLevel; i < last; i++)
nouveau_bo_ref(ss[last].bo, &ss[i].bo);
}
}
}
GLboolean
nouveau_texture_validate(struct gl_context *ctx, struct gl_texture_object *t)
{
struct nouveau_texture *nt = to_nouveau_texture(t);
int i, last = get_last_level(t);
if (!teximage_fits(t, t->BaseLevel) ||
!teximage_fits(t, last))
return GL_FALSE;
if (nt->dirty) {
nt->dirty = GL_FALSE;
/* Copy the teximages to the actual miptree. */
for (i = t->BaseLevel; i <= last; i++) {
struct nouveau_surface *s = &nt->surfaces[i];
validate_teximage(ctx, t, i, 0, 0, 0,
s->width, s->height, 1);
}
PUSH_KICK(context_push(ctx));
}
return GL_TRUE;
}
void
nouveau_texture_reallocate(struct gl_context *ctx, struct gl_texture_object *t)
{
if (!teximage_fits(t, t->BaseLevel) ||
!teximage_fits(t, get_last_level(t))) {
texture_dirty(t);
relayout_texture(ctx, t);
nouveau_texture_validate(ctx, t);
}
}
static unsigned
get_teximage_placement(struct gl_texture_image *ti)
{
if (ti->TexFormat == MESA_FORMAT_A_UNORM8 ||
ti->TexFormat == MESA_FORMAT_L_UNORM8 ||
ti->TexFormat == MESA_FORMAT_I_UNORM8)
/* 1 cpp formats will have to be swizzled by the CPU,
* so leave them in system RAM for now. */
return NOUVEAU_BO_MAP;
else
return NOUVEAU_BO_GART | NOUVEAU_BO_MAP;
}
static void
nouveau_compressed_copy(struct gl_context *ctx, GLint dims,
struct gl_texture_image *ti,
GLsizei width, GLsizei height, GLsizei depth,
const GLvoid *src, GLvoid *dst, int row_stride)
{
struct compressed_pixelstore store;
int i;
_mesa_compute_compressed_pixelstore(dims, ti->TexFormat,
width, height, depth,
&ctx->Unpack, &store);
src += store.SkipBytes;
assert(store.CopySlices == 1);
/* copy rows of blocks */
for (i = 0; i < store.CopyRowsPerSlice; i++) {
memcpy(dst, src, store.CopyBytesPerRow);
dst += row_stride;
src += store.TotalBytesPerRow;
}
}
static void
nouveau_teximage(struct gl_context *ctx, GLint dims,
struct gl_texture_image *ti,
GLsizei imageSize,
GLenum format, GLenum type, const GLvoid *pixels,
const struct gl_pixelstore_attrib *packing,
GLboolean compressed)
{
struct gl_texture_object *t = ti->TexObject;
const GLuint level = ti->Level;
struct nouveau_surface *s = &to_nouveau_teximage(ti)->surface;
struct nouveau_teximage *nti = to_nouveau_teximage(ti);
int ret;
GLuint depth = compressed ? 1 : ti->Depth;
/* Allocate a new bo for the image. */
nouveau_surface_alloc(ctx, s, LINEAR, get_teximage_placement(ti),
ti->TexFormat, ti->Width, ti->Height);
nti->base.RowStride = s->pitch / s->cpp;
if (compressed)
pixels = _mesa_validate_pbo_compressed_teximage(ctx,
dims, imageSize,
pixels, packing, "glCompressedTexImage");
else
pixels = _mesa_validate_pbo_teximage(ctx,
dims, ti->Width, ti->Height, depth, format, type,
pixels, packing, "glTexImage");
if (pixels) {
GLubyte *map;
int row_stride;
/* Store the pixel data. */
nouveau_map_texture_image(ctx, ti, 0,
0, 0, ti->Width, ti->Height,
GL_MAP_WRITE_BIT,
&map, &row_stride);
if (compressed) {
nouveau_compressed_copy(ctx, dims, ti,
ti->Width, ti->Height, depth,
pixels, map, row_stride);
} else {
ret = _mesa_texstore(ctx, dims, ti->_BaseFormat,
ti->TexFormat,
row_stride,
&map,
ti->Width, ti->Height, depth,
format, type, pixels, packing);
assert(ret);
}
nouveau_unmap_texture_image(ctx, ti, 0);
_mesa_unmap_teximage_pbo(ctx, packing);
if (!validate_teximage(ctx, t, level, 0, 0, 0,
ti->Width, ti->Height, depth))
/* It doesn't fit, mark it as dirty. */
texture_dirty(t);
}
if (level == t->BaseLevel) {
if (!teximage_fits(t, level))
relayout_texture(ctx, t);
nouveau_texture_validate(ctx, t);
}
context_dirty_i(ctx, TEX_OBJ, ctx->Texture.CurrentUnit);
context_dirty_i(ctx, TEX_ENV, ctx->Texture.CurrentUnit);
}
static void
nouveau_teximage_123d(struct gl_context *ctx, GLuint dims,
struct gl_texture_image *ti,
GLenum format, GLenum type, const GLvoid *pixels,
const struct gl_pixelstore_attrib *packing)
{
nouveau_teximage(ctx, dims, ti, 0, format, type, pixels,
packing, GL_FALSE);
}
static void
nouveau_compressed_teximage(struct gl_context *ctx, GLuint dims,
struct gl_texture_image *ti,
GLsizei imageSize, const GLvoid *data)
{
nouveau_teximage(ctx, 2, ti, imageSize, 0, 0, data,
&ctx->Unpack, GL_TRUE);
}
static GLboolean
nouveau_teximage_alloc(struct gl_context *ctx, struct gl_texture_image *ti)
{
nouveau_teximage(ctx, 3, ti, 0, 0, 0, NULL,
&ctx->DefaultPacking,
_mesa_is_format_compressed(ti->TexFormat));
return GL_TRUE;
}
static void
nouveau_texsubimage(struct gl_context *ctx, GLint dims,
struct gl_texture_image *ti,
GLint xoffset, GLint yoffset, GLint zoffset,
GLint width, GLint height, GLint depth,
GLsizei imageSize,
GLenum format, GLenum type, const void *pixels,
const struct gl_pixelstore_attrib *packing,
GLboolean compressed)
{
int ret;
if (compressed)
pixels = _mesa_validate_pbo_compressed_teximage(ctx,
dims, imageSize,
pixels, packing, "glCompressedTexSubImage");
else
pixels = _mesa_validate_pbo_teximage(ctx,
dims, width, height, depth, format, type,
pixels, packing, "glTexSubImage");
if (pixels) {
GLubyte *map;
int row_stride;
nouveau_map_texture_image(ctx, ti, 0,
xoffset, yoffset, width, height,
GL_MAP_WRITE_BIT, &map, &row_stride);
if (compressed) {
nouveau_compressed_copy(ctx, dims, ti,
width, height, depth,
pixels, map, row_stride);
} else {
ret = _mesa_texstore(ctx, dims, ti->_BaseFormat,
ti->TexFormat,
row_stride, &map,
width, height, depth,
format, type, pixels, packing);
assert(ret);
}
nouveau_unmap_texture_image(ctx, ti, 0);
_mesa_unmap_teximage_pbo(ctx, packing);
}
if (!to_nouveau_texture(ti->TexObject)->dirty)
validate_teximage(ctx, ti->TexObject, ti->Level,
xoffset, yoffset, zoffset,
width, height, depth);
}
static void
nouveau_texsubimage_123d(struct gl_context *ctx, GLuint dims,
struct gl_texture_image *ti,
GLint xoffset, GLint yoffset, GLint zoffset,
GLint width, GLint height, GLint depth,
GLenum format, GLenum type, const void *pixels,
const struct gl_pixelstore_attrib *packing)
{
nouveau_texsubimage(ctx, dims, ti, xoffset, yoffset, zoffset,
width, height, depth, 0, format, type, pixels,
packing, GL_FALSE);
}
static void
nouveau_compressed_texsubimage(struct gl_context *ctx, GLuint dims,
struct gl_texture_image *ti,
GLint xoffset, GLint yoffset, GLint zoffset,
GLsizei width, GLint height, GLint depth,
GLenum format,
GLint imageSize, const void *data)
{
nouveau_texsubimage(ctx, dims, ti, xoffset, yoffset, zoffset,
width, height, depth, imageSize, format, 0, data,
&ctx->Unpack, GL_TRUE);
}
static void
nouveau_bind_texture(struct gl_context *ctx, GLuint texUnit,
GLenum target, struct gl_texture_object *t)
{
context_dirty_i(ctx, TEX_OBJ, texUnit);
context_dirty_i(ctx, TEX_ENV, texUnit);
}
static mesa_format
get_texbuffer_format(struct gl_renderbuffer *rb, GLint format)
{
struct nouveau_surface *s = &to_nouveau_renderbuffer(rb)->surface;
if (s->cpp < 4)
return s->format;
else if (format == __DRI_TEXTURE_FORMAT_RGBA)
return MESA_FORMAT_B8G8R8A8_UNORM;
else
return MESA_FORMAT_B8G8R8X8_UNORM;
}
void
nouveau_set_texbuffer(__DRIcontext *dri_ctx,
GLint target, GLint format,
__DRIdrawable *draw)
{
struct nouveau_context *nctx = dri_ctx->driverPrivate;
struct gl_context *ctx = &nctx->base;
struct gl_framebuffer *fb = draw->driverPrivate;
struct gl_renderbuffer *rb =
fb->Attachment[BUFFER_FRONT_LEFT].Renderbuffer;
struct gl_texture_object *t = _mesa_get_current_tex_object(ctx, target);
struct gl_texture_image *ti;
struct nouveau_teximage *nti;
struct nouveau_surface *s;
_mesa_lock_texture(ctx, t);
ti = _mesa_get_tex_image(ctx, t, target, 0);
nti = to_nouveau_teximage(ti);
s = &to_nouveau_teximage(ti)->surface;
/* Update the texture surface with the given drawable. */
nouveau_update_renderbuffers(dri_ctx, draw);
nouveau_surface_ref(&to_nouveau_renderbuffer(rb)->surface, s);
s->format = get_texbuffer_format(rb, format);
/* Update the image fields. */
_mesa_init_teximage_fields(ctx, ti, s->width, s->height,
1, 0, s->cpp, s->format);
nti->base.RowStride = s->pitch / s->cpp;
/* Try to validate it. */
if (!validate_teximage(ctx, t, 0, 0, 0, 0, s->width, s->height, 1))
nouveau_texture_reallocate(ctx, t);
context_dirty_i(ctx, TEX_OBJ, ctx->Texture.CurrentUnit);
context_dirty_i(ctx, TEX_ENV, ctx->Texture.CurrentUnit);
_mesa_unlock_texture(ctx, t);
}
void
nouveau_texture_functions_init(struct dd_function_table *functions)
{
functions->NewTextureObject = nouveau_texture_new;
functions->DeleteTexture = nouveau_texture_free;
functions->NewTextureImage = nouveau_teximage_new;
functions->FreeTextureImageBuffer = nouveau_teximage_free;
functions->AllocTextureImageBuffer = nouveau_teximage_alloc;
functions->ChooseTextureFormat = nouveau_choose_tex_format;
functions->TexImage = nouveau_teximage_123d;
functions->TexSubImage = nouveau_texsubimage_123d;
functions->CompressedTexImage = nouveau_compressed_teximage;
functions->CompressedTexSubImage = nouveau_compressed_texsubimage;
functions->BindTexture = nouveau_bind_texture;
functions->MapTextureImage = nouveau_map_texture_image;
functions->UnmapTextureImage = nouveau_unmap_texture_image;
}