* \return true if success, false if error
*/
static bool
-prepare_target(struct gl_context *ctx, GLuint name, GLenum target,
- int level, int z, int depth,
- struct gl_texture_image **tex_image,
- struct gl_renderbuffer **renderbuffer,
- mesa_format *format,
- GLenum *internalFormat,
- GLuint *width,
- GLuint *height,
- const char *dbg_prefix)
+prepare_target_err(struct gl_context *ctx, GLuint name, GLenum target,
+ int level, int z, int depth,
+ struct gl_texture_image **tex_image,
+ struct gl_renderbuffer **renderbuffer,
+ mesa_format *format,
+ GLenum *internalFormat,
+ GLuint *width,
+ GLuint *height,
+ GLuint *num_samples,
+ const char *dbg_prefix,
+ bool is_arb_version)
{
+ const char *suffix = is_arb_version ? "" : "NV";
+
if (name == 0) {
_mesa_error(ctx, GL_INVALID_VALUE,
- "glCopyImageSubData(%sName = %d)", dbg_prefix, name);
+ "glCopyImageSubData%s(%sName = %d)", suffix, dbg_prefix, name);
return false;
}
case GL_TEXTURE_BUFFER:
default:
_mesa_error(ctx, GL_INVALID_ENUM,
- "glCopyImageSubData(%sTarget = %s)", dbg_prefix,
+ "glCopyImageSubData%s(%sTarget = %s)", suffix, dbg_prefix,
_mesa_enum_to_string(target));
return false;
}
if (!rb) {
_mesa_error(ctx, GL_INVALID_VALUE,
- "glCopyImageSubData(%sName = %u)", dbg_prefix, name);
+ "glCopyImageSubData%s(%sName = %u)", suffix, dbg_prefix, name);
return false;
}
if (!rb->Name) {
_mesa_error(ctx, GL_INVALID_OPERATION,
- "glCopyImageSubData(%sName incomplete)", dbg_prefix);
+ "glCopyImageSubData%s(%sName incomplete)", suffix, dbg_prefix);
return false;
}
if (level != 0) {
_mesa_error(ctx, GL_INVALID_VALUE,
- "glCopyImageSubData(%sLevel = %u)", dbg_prefix, level);
+ "glCopyImageSubData%s(%sLevel = %u)", suffix, dbg_prefix, level);
return false;
}
*internalFormat = rb->InternalFormat;
*width = rb->Width;
*height = rb->Height;
+ *num_samples = rb->NumSamples;
*tex_image = NULL;
} else {
struct gl_texture_object *texObj = _mesa_lookup_texture(ctx, name);
if (!texObj) {
+ /*
+ * From GL_ARB_copy_image specification:
+ * "INVALID_VALUE is generated if either <srcName> or <dstName> does
+ * not correspond to a valid renderbuffer or texture object according
+ * to the corresponding target parameter."
+ */
_mesa_error(ctx, GL_INVALID_VALUE,
- "glCopyImageSubData(%sName = %u)", dbg_prefix, name);
+ "glCopyImageSubData%s(%sName = %u)", suffix, dbg_prefix, name);
return false;
}
+ /* The ARB_copy_image specification says:
+ *
+ * "INVALID_OPERATION is generated if either object is a texture and
+ * the texture is not complete (as defined in section 3.9.14)"
+ *
+ * The cited section says:
+ *
+ * "Using the preceding definitions, a texture is complete unless any
+ * of the following conditions hold true: [...]
+ *
+ * * The minification filter requires a mipmap (is neither NEAREST
+ * nor LINEAR), and the texture is not mipmap complete."
+ *
+ * This imposes the bizarre restriction that glCopyImageSubData requires
+ * mipmap completion based on the sampler minification filter, even
+ * though the call fundamentally ignores the sampler. Additionally, it
+ * doesn't work with texture units, so it can't consider any bound
+ * separate sampler objects. It appears that you're supposed to use
+ * the sampler object which is built-in to the texture object.
+ *
+ * dEQP and the Android CTS mandate this behavior, and the Khronos
+ * GL and ES working groups both affirmed that this is unfortunate but
+ * correct. See https://cvs.khronos.org/bugzilla/show_bug.cgi?id=16224.
+ *
+ * Integer textures with filtering cause another completeness snag:
+ *
+ * "Any of:
+ * – The internal format of the texture is integer (see table 8.12).
+ * – The internal format is STENCIL_INDEX.
+ * – The internal format is DEPTH_STENCIL, and the value of
+ * DEPTH_STENCIL_TEXTURE_MODE for the texture is STENCIL_INDEX.
+ * and either the magnification filter is not NEAREST, or the
+ * minification filter is neither NEAREST nor
+ * NEAREST_MIPMAP_NEAREST."
+ *
+ * However, applications in the wild (such as "Total War: WARHAMMER")
+ * appear to call glCopyImageSubData with integer textures and the
+ * default mipmap filters of GL_LINEAR and GL_NEAREST_MIPMAP_LINEAR,
+ * which would be considered incomplete, but expect this to work. In
+ * fact, until VK-GL-CTS commit fef80039ff875a51806b54d151c5f2d0c12da,
+ * the GL 4.5 CTS contained three tests which did the exact same thing
+ * by accident, and all conformant implementations allowed it.
+ *
+ * A proposal was made to amend the spec to say "is not complete (as
+ * defined in section <X>, but ignoring format-based completeness
+ * rules)" to allow this case. It makes some sense, given that
+ * glCopyImageSubData copies raw data without considering format.
+ * While the official edits have not yet been made, the OpenGL
+ * working group agreed with the idea of allowing this behavior.
+ *
+ * To ignore formats, we check texObj->_MipmapComplete directly
+ * rather than calling _mesa_is_texture_complete().
+ */
_mesa_test_texobj_completeness(ctx, texObj);
- if (!texObj->_BaseComplete ||
- (level != 0 && !texObj->_MipmapComplete)) {
+ const bool texture_complete_aside_from_formats =
+ _mesa_is_mipmap_filter(&texObj->Sampler) ? texObj->_MipmapComplete
+ : texObj->_BaseComplete;
+ if (!texture_complete_aside_from_formats) {
_mesa_error(ctx, GL_INVALID_OPERATION,
- "glCopyImageSubData(%sName incomplete)", dbg_prefix);
+ "glCopyImageSubData%s(%sName incomplete)", suffix, dbg_prefix);
return false;
}
/* Note that target will not be a cube face name */
if (texObj->Target != target) {
/*
- * From GL_ARB_copy_image specification:
- * "INVALID_VALUE is generated if either <srcName> or <dstName> does
- * not correspond to a valid renderbuffer or texture object according
- * to the corresponding target parameter."
+ * From GL_ARB_copy_image_specification:
+ * "INVALID_ENUM is generated if the target does not match the type
+ * of the object."
*/
- _mesa_error(ctx, GL_INVALID_VALUE,
- "glCopyImageSubData(%sTarget = %s)", dbg_prefix,
+ _mesa_error(ctx, GL_INVALID_ENUM,
+ "glCopyImageSubData%s(%sTarget = %s)", suffix, dbg_prefix,
_mesa_enum_to_string(target));
return false;
}
if (level < 0 || level >= MAX_TEXTURE_LEVELS) {
_mesa_error(ctx, GL_INVALID_VALUE,
- "glCopyImageSubData(%sLevel = %d)", dbg_prefix, level);
+ "glCopyImageSubData%s(%sLevel = %d)", suffix, dbg_prefix, level);
return false;
}
for (i = 0; i < depth; i++) {
if (!texObj->Image[z+i][level]) {
/* missing cube face */
- _mesa_error(ctx, GL_INVALID_OPERATION,
+ _mesa_error(ctx, GL_INVALID_VALUE,
"glCopyImageSubData(missing cube face)");
return false;
}
if (!*tex_image) {
_mesa_error(ctx, GL_INVALID_VALUE,
- "glCopyImageSubData(%sLevel = %u)", dbg_prefix, level);
+ "glCopyImageSubData%s(%sLevel = %u)", suffix, dbg_prefix, level);
return false;
}
*internalFormat = (*tex_image)->InternalFormat;
*width = (*tex_image)->Width;
*height = (*tex_image)->Height;
+ *num_samples = (*tex_image)->NumSamples;
}
return true;
}
+static void
+prepare_target(struct gl_context *ctx, GLuint name, GLenum target,
+ int level, int z,
+ struct gl_texture_image **texImage,
+ struct gl_renderbuffer **renderbuffer)
+{
+ if (target == GL_RENDERBUFFER) {
+ struct gl_renderbuffer *rb = _mesa_lookup_renderbuffer(ctx, name);
+
+ *renderbuffer = rb;
+ *texImage = NULL;
+ } else {
+ struct gl_texture_object *texObj = _mesa_lookup_texture(ctx, name);
+
+ if (target == GL_TEXTURE_CUBE_MAP) {
+ *texImage = texObj->Image[z][level];
+ }
+ else {
+ *texImage = _mesa_select_tex_image(texObj, target, level);
+ }
+
+ *renderbuffer = NULL;
+ }
+}
/**
* Check that the x,y,z,width,height,region is within the texture image
const struct gl_texture_image *tex_image,
const struct gl_renderbuffer *renderbuffer,
int x, int y, int z, int width, int height, int depth,
- const char *dbg_prefix)
+ const char *dbg_prefix,
+ bool is_arb_version)
{
int surfWidth, surfHeight, surfDepth;
+ const char *suffix = is_arb_version ? "" : "NV";
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);
+ "glCopyImageSubData%s(%sWidth, %sHeight, or %sDepth is negative)",
+ suffix, 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);
+ "glCopyImageSubData%s(%sX, %sY, or %sZ is negative)",
+ suffix, dbg_prefix, dbg_prefix, dbg_prefix);
return false;
}
if (x + width > surfWidth) {
_mesa_error(ctx, GL_INVALID_VALUE,
- "glCopyImageSubData(%sX or %sWidth exceeds image bounds)",
- dbg_prefix, dbg_prefix);
+ "glCopyImageSubData%s(%sX or %sWidth exceeds image bounds)",
+ suffix, dbg_prefix, dbg_prefix);
return false;
}
if (y + height > surfHeight) {
_mesa_error(ctx, GL_INVALID_VALUE,
- "glCopyImageSubData(%sY or %sHeight exceeds image bounds)",
- dbg_prefix, dbg_prefix);
+ "glCopyImageSubData%s(%sY or %sHeight exceeds image bounds)",
+ suffix, dbg_prefix, dbg_prefix);
return false;
}
if (z < 0 || z + depth > surfDepth) {
_mesa_error(ctx, GL_INVALID_VALUE,
- "glCopyImageSubData(%sZ or %sDepth exceeds image bounds)",
- dbg_prefix, dbg_prefix);
+ "glCopyImageSubData%s(%sZ or %sDepth exceeds image bounds)",
+ suffix, dbg_prefix, dbg_prefix);
return false;
}
return false;
}
+static void
+copy_image_subdata(struct gl_context *ctx,
+ struct gl_texture_image *srcTexImage,
+ struct gl_renderbuffer *srcRenderbuffer,
+ int srcX, int srcY, int srcZ, int srcLevel,
+ struct gl_texture_image *dstTexImage,
+ struct gl_renderbuffer *dstRenderbuffer,
+ int dstX, int dstY, int dstZ, int dstLevel,
+ int srcWidth, int srcHeight, int srcDepth)
+{
+ /* loop over 2D slices/faces/layers */
+ for (int i = 0; i < srcDepth; ++i) {
+ int newSrcZ = srcZ + i;
+ int newDstZ = dstZ + i;
+
+ if (srcTexImage &&
+ srcTexImage->TexObject->Target == GL_TEXTURE_CUBE_MAP) {
+ /* need to update srcTexImage pointer for the cube face */
+ assert(srcZ + i < MAX_FACES);
+ srcTexImage = srcTexImage->TexObject->Image[srcZ + i][srcLevel];
+ assert(srcTexImage);
+ newSrcZ = 0;
+ }
+
+ if (dstTexImage &&
+ dstTexImage->TexObject->Target == GL_TEXTURE_CUBE_MAP) {
+ /* need to update dstTexImage pointer for the cube face */
+ assert(dstZ + i < MAX_FACES);
+ dstTexImage = dstTexImage->TexObject->Image[dstZ + i][dstLevel];
+ assert(dstTexImage);
+ newDstZ = 0;
+ }
+
+ ctx->Driver.CopyImageSubData(ctx,
+ srcTexImage, srcRenderbuffer,
+ srcX, srcY, newSrcZ,
+ dstTexImage, dstRenderbuffer,
+ dstX, dstY, newDstZ,
+ srcWidth, srcHeight);
+ }
+}
+
+void GLAPIENTRY
+_mesa_CopyImageSubData_no_error(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)
+{
+ struct gl_texture_image *srcTexImage, *dstTexImage;
+ struct gl_renderbuffer *srcRenderbuffer, *dstRenderbuffer;
+
+ GET_CURRENT_CONTEXT(ctx);
+
+ prepare_target(ctx, srcName, srcTarget, srcLevel, srcZ, &srcTexImage,
+ &srcRenderbuffer);
+
+ prepare_target(ctx, dstName, dstTarget, dstLevel, dstZ, &dstTexImage,
+ &dstRenderbuffer);
+
+ copy_image_subdata(ctx, srcTexImage, srcRenderbuffer, srcX, srcY, srcZ,
+ srcLevel, dstTexImage, dstRenderbuffer, dstX, dstY, dstZ,
+ dstLevel, srcWidth, srcHeight, srcDepth);
+}
+
void GLAPIENTRY
_mesa_CopyImageSubData(GLuint srcName, GLenum srcTarget, GLint srcLevel,
GLint srcX, GLint srcY, GLint srcZ,
GLenum srcIntFormat, dstIntFormat;
GLuint src_w, src_h, dst_w, dst_h;
GLuint src_bw, src_bh, dst_bw, dst_bh;
+ GLuint src_num_samples, dst_num_samples;
int dstWidth, dstHeight, dstDepth;
- int i;
if (MESA_VERBOSE & VERBOSE_API)
_mesa_debug(ctx, "glCopyImageSubData(%u, %s, %d, %d, %d, %d, "
return;
}
- if (!prepare_target(ctx, srcName, srcTarget, srcLevel, srcZ, srcDepth,
- &srcTexImage, &srcRenderbuffer, &srcFormat,
- &srcIntFormat, &src_w, &src_h, "src"))
+ if (!prepare_target_err(ctx, srcName, srcTarget, srcLevel, srcZ, srcDepth,
+ &srcTexImage, &srcRenderbuffer, &srcFormat,
+ &srcIntFormat, &src_w, &src_h, &src_num_samples,
+ "src",true))
return;
- if (!prepare_target(ctx, dstName, dstTarget, dstLevel, dstZ, srcDepth,
- &dstTexImage, &dstRenderbuffer, &dstFormat,
- &dstIntFormat, &dst_w, &dst_h, "dst"))
+ if (!prepare_target_err(ctx, dstName, dstTarget, dstLevel, dstZ, srcDepth,
+ &dstTexImage, &dstRenderbuffer, &dstFormat,
+ &dstIntFormat, &dst_w, &dst_h, &dst_num_samples,
+ "dst",true))
return;
_mesa_get_format_block_size(srcFormat, &src_bw, &src_bh);
if (!check_region_bounds(ctx, srcTarget, srcTexImage, srcRenderbuffer,
srcX, srcY, srcZ, srcWidth, srcHeight, srcDepth,
- "src"))
+ "src", true))
return;
if (!check_region_bounds(ctx, dstTarget, dstTexImage, dstRenderbuffer,
dstX, dstY, dstZ, dstWidth, dstHeight, dstDepth,
- "dst"))
+ "dst", true))
return;
+ /* Section 18.3.2 (Copying Between Images) of the OpenGL 4.5 Core Profile
+ * spec says:
+ *
+ * An INVALID_OPERATION error is generated if either object is a texture
+ * and the texture is not complete, if the source and destination internal
+ * formats are not compatible, or if the number of samples do not match.
+ */
if (!copy_format_compatible(ctx, srcIntFormat, dstIntFormat)) {
_mesa_error(ctx, GL_INVALID_OPERATION,
"glCopyImageSubData(internalFormat mismatch)");
return;
}
- /* loop over 2D slices/faces/layers */
- for (i = 0; i < srcDepth; ++i) {
- int newSrcZ = srcZ + i;
- int newDstZ = dstZ + i;
+ if (src_num_samples != dst_num_samples) {
+ _mesa_error(ctx, GL_INVALID_OPERATION,
+ "glCopyImageSubData(number of samples mismatch)");
+ return;
+ }
- if (srcTexImage &&
- srcTexImage->TexObject->Target == GL_TEXTURE_CUBE_MAP) {
- /* need to update srcTexImage pointer for the cube face */
- assert(srcZ + i < MAX_FACES);
- srcTexImage = srcTexImage->TexObject->Image[srcZ + i][srcLevel];
- assert(srcTexImage);
- newSrcZ = 0;
- }
+ copy_image_subdata(ctx, srcTexImage, srcRenderbuffer, srcX, srcY, srcZ,
+ srcLevel, dstTexImage, dstRenderbuffer, dstX, dstY, dstZ,
+ dstLevel, srcWidth, srcHeight, srcDepth);
+}
- if (dstTexImage &&
- dstTexImage->TexObject->Target == GL_TEXTURE_CUBE_MAP) {
- /* need to update dstTexImage pointer for the cube face */
- assert(dstZ + i < MAX_FACES);
- dstTexImage = dstTexImage->TexObject->Image[dstZ + i][dstLevel];
- assert(dstTexImage);
- newDstZ = 0;
- }
+void GLAPIENTRY
+_mesa_CopyImageSubDataNV_no_error(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)
+{
+ struct gl_texture_image *srcTexImage, *dstTexImage;
+ struct gl_renderbuffer *srcRenderbuffer, *dstRenderbuffer;
- ctx->Driver.CopyImageSubData(ctx,
- srcTexImage, srcRenderbuffer,
- srcX, srcY, newSrcZ,
- dstTexImage, dstRenderbuffer,
- dstX, dstY, newDstZ,
- srcWidth, srcHeight);
+ GET_CURRENT_CONTEXT(ctx);
+
+ prepare_target(ctx, srcName, srcTarget, srcLevel, srcZ, &srcTexImage,
+ &srcRenderbuffer);
+
+ prepare_target(ctx, dstName, dstTarget, dstLevel, dstZ, &dstTexImage,
+ &dstRenderbuffer);
+
+ copy_image_subdata(ctx, srcTexImage, srcRenderbuffer, srcX, srcY, srcZ,
+ srcLevel, dstTexImage, dstRenderbuffer, dstX, dstY, dstZ,
+ dstLevel, srcWidth, srcHeight, srcDepth);
+}
+
+void GLAPIENTRY
+_mesa_CopyImageSubDataNV(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);
+ struct gl_texture_image *srcTexImage, *dstTexImage;
+ struct gl_renderbuffer *srcRenderbuffer, *dstRenderbuffer;
+ mesa_format srcFormat, dstFormat;
+ GLenum srcIntFormat, dstIntFormat;
+ GLuint src_w, src_h, dst_w, dst_h;
+ GLuint src_bw, src_bh, dst_bw, dst_bh;
+ GLuint src_num_samples, dst_num_samples;
+
+ if (MESA_VERBOSE & VERBOSE_API)
+ _mesa_debug(ctx, "glCopyImageSubDataNV(%u, %s, %d, %d, %d, %d, "
+ "%u, %s, %d, %d, %d, %d, "
+ "%d, %d, %d)\n",
+ srcName, _mesa_enum_to_string(srcTarget), srcLevel,
+ srcX, srcY, srcZ,
+ dstName, _mesa_enum_to_string(dstTarget), dstLevel,
+ dstX, dstY, dstZ,
+ srcWidth, srcHeight, srcDepth);
+
+ if (!ctx->Extensions.NV_copy_image) {
+ _mesa_error(ctx, GL_INVALID_OPERATION,
+ "glCopyImageSubDataNV(extension not available)");
+ return;
+ }
+
+ if (!prepare_target_err(ctx, srcName, srcTarget, srcLevel, srcZ, srcDepth,
+ &srcTexImage, &srcRenderbuffer, &srcFormat,
+ &srcIntFormat, &src_w, &src_h, &src_num_samples,
+ "src", false))
+ return;
+
+ if (!prepare_target_err(ctx, dstName, dstTarget, dstLevel, dstZ, srcDepth,
+ &dstTexImage, &dstRenderbuffer, &dstFormat,
+ &dstIntFormat, &dst_w, &dst_h, &dst_num_samples,
+ "dst", false))
+ return;
+
+ /*
+ * The NV_copy_image spec says:
+ *
+ * INVALID_OPERATION is generated if either object is a texture
+ * and the texture is not consistent, or if the source and destination
+ * internal formats or number of samples do not match.
+ *
+ * In the absence of any definition of texture consistency the texture
+ * completeness check, which is affected in the prepare_target_err function,
+ * is used instead in keeping with the ARB version.
+ * The check related to the internal format here is different from the ARB
+ * version which adds the ability to copy between images which have
+ * different formats where the formats are compatible for texture views.
+ */
+ if (srcIntFormat != dstIntFormat) {
+ _mesa_error(ctx, GL_INVALID_OPERATION,
+ "glCopyImageSubDataNV(internalFormat mismatch)");
+ return;
}
+
+ if (src_num_samples != dst_num_samples) {
+ _mesa_error(ctx, GL_INVALID_OPERATION,
+ "glCopyImageSubDataNV(number of samples mismatch)");
+ return;
+ }
+
+ /*
+ * The NV_copy_image spec says:
+ *
+ * INVALID_VALUE is generated if the image format is compressed
+ * and the dimensions of the subregion fail to meet the alignment
+ * constraints of the format.
+ *
+ * The check here is identical to the ARB version.
+ */
+ _mesa_get_format_block_size(srcFormat, &src_bw, &src_bh);
+ if ((srcX % src_bw != 0) || (srcY % src_bh != 0) ||
+ (srcWidth % src_bw != 0 && (srcX + srcWidth) != src_w) ||
+ (srcHeight % src_bh != 0 && (srcY + srcHeight) != src_h)) {
+ _mesa_error(ctx, GL_INVALID_VALUE,
+ "glCopyImageSubDataNV(unaligned src rectangle)");
+ return;
+ }
+
+ _mesa_get_format_block_size(dstFormat, &dst_bw, &dst_bh);
+ if ((dstX % dst_bw != 0) || (dstY % dst_bh != 0)) {
+ _mesa_error(ctx, GL_INVALID_VALUE,
+ "glCopyImageSubDataNV(unaligned dst rectangle)");
+ return;
+ }
+
+ /*
+ * The NV_copy_image spec says:
+ *
+ * INVALID_VALUE is generated if the dimensions of the either subregion
+ * exceeds the boundaries of the corresponding image object.
+ *
+ * The check here is similar to the ARB version except for the fact that
+ * block sizes are not considered owing to the fact that copying across
+ * compressed and uncompressed formats is not supported.
+ */
+ if (!check_region_bounds(ctx, srcTarget, srcTexImage, srcRenderbuffer,
+ srcX, srcY, srcZ, srcWidth, srcHeight, srcDepth,
+ "src", false))
+ return;
+
+ if (!check_region_bounds(ctx, dstTarget, dstTexImage, dstRenderbuffer,
+ dstX, dstY, dstZ, srcWidth, srcHeight, srcDepth,
+ "dst", false))
+ return;
+
+ copy_image_subdata(ctx, srcTexImage, srcRenderbuffer, srcX, srcY, srcZ,
+ srcLevel, dstTexImage, dstRenderbuffer, dstX, dstY, dstZ,
+ dstLevel, srcWidth, srcHeight, srcDepth);
}