blob: df7d7c2727d1a5783f0a9d0d5358ec6e7afa08fb [file] [log] [blame]
/*
* Mesa 3-D graphics library
*
* Copyright (C) 2014 Intel Corporation. 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 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 OR COPYRIGHT HOLDERS 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.
*
* Authors:
* Jason Ekstrand <jason.ekstrand@intel.com>
*/
#include "glheader.h"
#include "errors.h"
#include "enums.h"
#include "copyimage.h"
#include "teximage.h"
#include "texobj.h"
#include "fbobject.h"
#include "textureview.h"
static bool
prepare_target(struct gl_context *ctx, GLuint name, GLenum *target, int level,
struct gl_texture_object **tex_obj,
struct gl_texture_image **tex_image, GLuint *tmp_tex,
const char *dbg_prefix)
{
struct gl_renderbuffer *rb;
if (name == 0) {
_mesa_error(ctx, GL_INVALID_VALUE,
"glCopyImageSubData(%sName = %d)", dbg_prefix, name);
return false;
}
/*
* INVALID_ENUM is generated
* * if either <srcTarget> or <dstTarget>
* - is not RENDERBUFFER or a valid non-proxy texture target
* - is TEXTURE_BUFFER, or
* - is one of the cubemap face selectors described in table 3.17,
*/
switch (*target) {
case GL_RENDERBUFFER:
/* Not a texture target, but valid */
case GL_TEXTURE_1D:
case GL_TEXTURE_1D_ARRAY:
case GL_TEXTURE_2D:
case GL_TEXTURE_3D:
case GL_TEXTURE_CUBE_MAP:
case GL_TEXTURE_RECTANGLE:
case GL_TEXTURE_2D_ARRAY:
case GL_TEXTURE_CUBE_MAP_ARRAY:
case GL_TEXTURE_2D_MULTISAMPLE:
case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
/* These are all valid */
break;
case GL_TEXTURE_EXTERNAL_OES:
/* Only exists in ES */
case GL_TEXTURE_BUFFER:
default:
_mesa_error(ctx, GL_INVALID_ENUM,
"glCopyImageSubData(%sTarget = %s)", dbg_prefix,
_mesa_lookup_enum_by_nr(*target));
return false;
}
if (*target == GL_RENDERBUFFER) {
rb = _mesa_lookup_renderbuffer(ctx, name);
if (!rb) {
_mesa_error(ctx, GL_INVALID_VALUE,
"glCopyImageSubData(%sName = %u)", dbg_prefix, name);
return false;
}
if (!rb->Name) {
_mesa_error(ctx, GL_INVALID_OPERATION,
"glCopyImageSubData(%sName incomplete)", dbg_prefix);
return false;
}
if (level != 0) {
_mesa_error(ctx, GL_INVALID_VALUE,
"glCopyImageSubData(%sLevel = %u)", dbg_prefix, level);
return false;
}
if (rb->NumSamples > 1)
*target = GL_TEXTURE_2D_MULTISAMPLE;
else
*target = GL_TEXTURE_2D;
*tmp_tex = 0;
_mesa_GenTextures(1, tmp_tex);
if (*tmp_tex == 0)
return false; /* Error already set by GenTextures */
_mesa_BindTexture(*target, *tmp_tex);
*tex_obj = _mesa_lookup_texture(ctx, *tmp_tex);
*tex_image = _mesa_get_tex_image(ctx, *tex_obj, *target, 0);
if (!ctx->Driver.BindRenderbufferTexImage(ctx, rb, *tex_image)) {
_mesa_problem(ctx, "Failed to create texture from renderbuffer");
return false;
}
if (ctx->Driver.FinishRenderTexture && !rb->NeedsFinishRenderTexture) {
rb->NeedsFinishRenderTexture = true;
ctx->Driver.FinishRenderTexture(ctx, rb);
}
} else {
*tex_obj = _mesa_lookup_texture(ctx, name);
if (!*tex_obj) {
_mesa_error(ctx, GL_INVALID_VALUE,
"glCopyImageSubData(%sName = %u)", dbg_prefix, name);
return false;
}
_mesa_test_texobj_completeness(ctx, *tex_obj);
if (!(*tex_obj)->_BaseComplete ||
(level != 0 && !(*tex_obj)->_MipmapComplete)) {
_mesa_error(ctx, GL_INVALID_OPERATION,
"glCopyImageSubData(%sName incomplete)", dbg_prefix);
return false;
}
if ((*tex_obj)->Target != *target) {
_mesa_error(ctx, GL_INVALID_ENUM,
"glCopyImageSubData(%sTarget = %s)", dbg_prefix,
_mesa_lookup_enum_by_nr(*target));
return false;
}
if (level < 0 || level >= MAX_TEXTURE_LEVELS) {
_mesa_error(ctx, GL_INVALID_VALUE,
"glCopyImageSubData(%sLevel = %d)", dbg_prefix, level);
return false;
}
*tex_image = _mesa_select_tex_image(ctx, *tex_obj, *target, level);
if (!*tex_image) {
_mesa_error(ctx, GL_INVALID_VALUE,
"glCopyImageSubData(%sLevel = %u)", dbg_prefix, level);
return false;
}
}
return true;
}
static bool
check_region_bounds(struct gl_context *ctx, struct gl_texture_image *tex_image,
int x, int y, int z, int width, int height, int depth,
const char *dbg_prefix)
{
if (width < 0 || height < 0 || depth < 0) {
_mesa_error(ctx, GL_INVALID_VALUE,
"glCopyImageSubData(%sWidth, %sHeight, or %sDepth is negative)",
dbg_prefix, dbg_prefix, dbg_prefix);
return false;
}
if (x < 0 || y < 0 || z < 0) {
_mesa_error(ctx, GL_INVALID_VALUE,
"glCopyImageSubData(%sX, %sY, or %sZ is negative)",
dbg_prefix, dbg_prefix, dbg_prefix);
return false;
}
if (x + width > tex_image->Width) {
_mesa_error(ctx, GL_INVALID_VALUE,
"glCopyImageSubData(%sX or %sWidth exceeds image bounds)",
dbg_prefix, dbg_prefix);
return false;
}
switch (tex_image->TexObject->Target) {
case GL_TEXTURE_1D:
case GL_TEXTURE_1D_ARRAY:
if (y != 0 || height != 1) {
_mesa_error(ctx, GL_INVALID_VALUE,
"glCopyImageSubData(%sY or %sHeight exceeds image bounds)",
dbg_prefix, dbg_prefix);
return false;
}
break;
default:
if (y + height > tex_image->Height) {
_mesa_error(ctx, GL_INVALID_VALUE,
"glCopyImageSubData(%sY or %sHeight exceeds image bounds)",
dbg_prefix, dbg_prefix);
return false;
}
break;
}
switch (tex_image->TexObject->Target) {
case GL_TEXTURE_1D:
case GL_TEXTURE_2D:
case GL_TEXTURE_2D_MULTISAMPLE:
case GL_TEXTURE_RECTANGLE:
if (z != 0 || depth != 1) {
_mesa_error(ctx, GL_INVALID_VALUE,
"glCopyImageSubData(%sZ or %sDepth exceeds image bounds)",
dbg_prefix, dbg_prefix);
return false;
}
break;
case GL_TEXTURE_CUBE_MAP:
if (z < 0 || z + depth > 6) {
_mesa_error(ctx, GL_INVALID_VALUE,
"glCopyImageSubData(%sZ or %sDepth exceeds image bounds)",
dbg_prefix, dbg_prefix);
return false;
}
break;
case GL_TEXTURE_1D_ARRAY:
if (z < 0 || z + depth > tex_image->Height) {
_mesa_error(ctx, GL_INVALID_VALUE,
"glCopyImageSubData(%sZ or %sDepth exceeds image bounds)",
dbg_prefix, dbg_prefix);
return false;
}
break;
case GL_TEXTURE_CUBE_MAP_ARRAY:
case GL_TEXTURE_2D_ARRAY:
case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
case GL_TEXTURE_3D:
if (z < 0 || z + depth > tex_image->Depth) {
_mesa_error(ctx, GL_INVALID_VALUE,
"glCopyImageSubData(%sZ or %sDepth exceeds image bounds)",
dbg_prefix, dbg_prefix);
return false;
}
break;
}
return true;
}
void GLAPIENTRY
_mesa_CopyImageSubData(GLuint srcName, GLenum srcTarget, GLint srcLevel,
GLint srcX, GLint srcY, GLint srcZ,
GLuint dstName, GLenum dstTarget, GLint dstLevel,
GLint dstX, GLint dstY, GLint dstZ,
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth)
{
GET_CURRENT_CONTEXT(ctx);
GLuint tmpTexNames[2] = { 0, 0 };
struct gl_texture_object *srcTexObj, *dstTexObj;
struct gl_texture_image *srcTexImage, *dstTexImage;
GLuint src_bw, src_bh, dst_bw, dst_bh;
int i, srcNewZ, dstNewZ, Bpt;
if (MESA_VERBOSE & VERBOSE_API)
_mesa_debug(ctx, "glCopyImageSubData(%u, %s, %d, %d, %d, %d, "
"%u, %s, %d, %d, %d, %d, "
"%d, %d, %d)\n",
srcName, _mesa_lookup_enum_by_nr(srcTarget), srcLevel,
srcX, srcY, srcZ,
dstName, _mesa_lookup_enum_by_nr(dstTarget), dstLevel,
dstX, dstY, dstZ,
srcWidth, srcHeight, srcWidth);
if (!ctx->Extensions.ARB_copy_image) {
_mesa_error(ctx, GL_INVALID_OPERATION,
"glCopyImageSubData(extension not available)");
return;
}
if (!prepare_target(ctx, srcName, &srcTarget, srcLevel,
&srcTexObj, &srcTexImage, &tmpTexNames[0], "src"))
goto cleanup;
if (!prepare_target(ctx, dstName, &dstTarget, dstLevel,
&dstTexObj, &dstTexImage, &tmpTexNames[1], "dst"))
goto cleanup;
_mesa_get_format_block_size(srcTexImage->TexFormat, &src_bw, &src_bh);
if ((srcX % src_bw != 0) || (srcY % src_bh != 0) ||
(srcWidth % src_bw != 0) || (srcHeight % src_bh != 0)) {
_mesa_error(ctx, GL_INVALID_VALUE,
"glCopyImageSubData(unaligned src rectangle)");
goto cleanup;
}
_mesa_get_format_block_size(dstTexImage->TexFormat, &dst_bw, &dst_bh);
if ((dstX % dst_bw != 0) || (dstY % dst_bh != 0)) {
_mesa_error(ctx, GL_INVALID_VALUE,
"glCopyImageSubData(unaligned dst rectangle)");
goto cleanup;
}
/* Very simple sanity check. This is sufficient if one of the textures
* is compressed. */
Bpt = _mesa_get_format_bytes(srcTexImage->TexFormat);
if (_mesa_get_format_bytes(dstTexImage->TexFormat) != Bpt) {
_mesa_error(ctx, GL_INVALID_VALUE,
"glCopyImageSubData(internalFormat mismatch)");
goto cleanup;
}
if (!check_region_bounds(ctx, srcTexImage, srcX, srcY, srcZ,
srcWidth, srcHeight, srcDepth, "src"))
goto cleanup;
if (!check_region_bounds(ctx, dstTexImage, dstX, dstY, dstZ,
(srcWidth / src_bw) * dst_bw,
(srcHeight / src_bh) * dst_bh, srcDepth, "dst"))
goto cleanup;
if (_mesa_is_format_compressed(srcTexImage->TexFormat)) {
/* XXX: Technically, we should probaby do some more specific checking
* here. However, this should be sufficient for all compressed
* formats that mesa supports since it is a direct memory copy.
*/
} else if (_mesa_is_format_compressed(dstTexImage->TexFormat)) {
} else if (_mesa_texture_view_compatible_format(ctx,
srcTexImage->InternalFormat,
dstTexImage->InternalFormat)) {
} else {
return; /* Error logged by _mesa_texture_view_compatible_format */
}
for (i = 0; i < srcDepth; ++i) {
if (srcTexObj->Target == GL_TEXTURE_CUBE_MAP) {
srcTexImage = srcTexObj->Image[i + srcZ][srcLevel];
srcNewZ = 0;
} else {
srcNewZ = srcZ + i;
}
if (dstTexObj->Target == GL_TEXTURE_CUBE_MAP) {
dstTexImage = dstTexObj->Image[i + dstZ][dstLevel];
dstNewZ = 0;
} else {
dstNewZ = dstZ + i;
}
ctx->Driver.CopyImageSubData(ctx, srcTexImage, srcX, srcY, srcNewZ,
dstTexImage, dstX, dstY, dstNewZ,
srcWidth, srcHeight);
}
cleanup:
_mesa_DeleteTextures(2, tmpTexNames);
}