summaryrefslogtreecommitdiffstats
path: root/src/mesa/state_tracker/st_cb_copyimage.c
diff options
context:
space:
mode:
authorMarek Olšák <marek.olsak@amd.com>2015-08-24 02:55:20 +0200
committerMarek Olšák <marek.olsak@amd.com>2015-10-28 11:52:17 +0100
commitf04f13622f3e71bee057d60a6be9c53b92b56cc9 (patch)
tree8543fdb40ed4dfce18fbe4e2e3887633c7361391 /src/mesa/state_tracker/st_cb_copyimage.c
parentce9db16e1c101204cdb6b2482aa1b4c7d0c59d41 (diff)
downloadexternal_mesa3d-f04f13622f3e71bee057d60a6be9c53b92b56cc9.zip
external_mesa3d-f04f13622f3e71bee057d60a6be9c53b92b56cc9.tar.gz
external_mesa3d-f04f13622f3e71bee057d60a6be9c53b92b56cc9.tar.bz2
st/mesa: implement ARB_copy_image
I wonder if the craziness was worth it. Reviewed-by: Brian Paul <brianp@vmware.com>
Diffstat (limited to 'src/mesa/state_tracker/st_cb_copyimage.c')
-rw-r--r--src/mesa/state_tracker/st_cb_copyimage.c578
1 files changed, 578 insertions, 0 deletions
diff --git a/src/mesa/state_tracker/st_cb_copyimage.c b/src/mesa/state_tracker/st_cb_copyimage.c
new file mode 100644
index 0000000..c94a0b2
--- /dev/null
+++ b/src/mesa/state_tracker/st_cb_copyimage.c
@@ -0,0 +1,578 @@
+/*
+ * Copyright 2015 Advanced Micro Devices, Inc.
+ *
+ * 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
+ * on the rights to use, copy, modify, merge, publish, distribute, sub
+ * license, 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 NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHOR(S) AND/OR THEIR 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 "state_tracker/st_context.h"
+#include "state_tracker/st_cb_copyimage.h"
+#include "state_tracker/st_cb_fbo.h"
+#include "state_tracker/st_texture.h"
+
+#include "util/u_box.h"
+#include "util/u_format.h"
+#include "util/u_inlines.h"
+
+
+/**
+ * Return an equivalent canonical format without "X" channels.
+ *
+ * Copying between incompatible formats is easier when the format is
+ * canonicalized, meaning that it is in a standard form.
+ *
+ * The returned format has the same component sizes and swizzles as
+ * the source format, the type is changed to UINT or UNORM, depending on
+ * which one has the most swizzle combinations in their group.
+ *
+ * If it's not an array format, return a memcpy-equivalent array format.
+ *
+ * The key feature is that swizzled versions of formats of the same
+ * component size always return the same component type.
+ *
+ * X returns A.
+ * Luminance, intensity, alpha, depth, stencil, and 8-bit and 16-bit packed
+ * formats are not supported. (same as ARB_copy_image)
+ */
+static enum pipe_format
+get_canonical_format(enum pipe_format format)
+{
+ const struct util_format_description *desc =
+ util_format_description(format);
+
+ /* Packed formats. Return the equivalent array format. */
+ if (format == PIPE_FORMAT_R11G11B10_FLOAT ||
+ format == PIPE_FORMAT_R9G9B9E5_FLOAT)
+ return get_canonical_format(PIPE_FORMAT_R8G8B8A8_UINT);
+
+ if (desc->nr_channels == 4 &&
+ desc->channel[0].size == 10 &&
+ desc->channel[1].size == 10 &&
+ desc->channel[2].size == 10 &&
+ desc->channel[3].size == 2) {
+ if (desc->swizzle[0] == UTIL_FORMAT_SWIZZLE_X &&
+ desc->swizzle[1] == UTIL_FORMAT_SWIZZLE_Y &&
+ desc->swizzle[2] == UTIL_FORMAT_SWIZZLE_Z)
+ return get_canonical_format(PIPE_FORMAT_R8G8B8A8_UINT);
+
+ return PIPE_FORMAT_NONE;
+ }
+
+#define RETURN_FOR_SWIZZLE1(x, format) \
+ if (desc->swizzle[0] == UTIL_FORMAT_SWIZZLE_##x) \
+ return format
+
+#define RETURN_FOR_SWIZZLE2(x, y, format) \
+ if (desc->swizzle[0] == UTIL_FORMAT_SWIZZLE_##x && \
+ desc->swizzle[1] == UTIL_FORMAT_SWIZZLE_##y) \
+ return format
+
+#define RETURN_FOR_SWIZZLE3(x, y, z, format) \
+ if (desc->swizzle[0] == UTIL_FORMAT_SWIZZLE_##x && \
+ desc->swizzle[1] == UTIL_FORMAT_SWIZZLE_##y && \
+ desc->swizzle[2] == UTIL_FORMAT_SWIZZLE_##z) \
+ return format
+
+#define RETURN_FOR_SWIZZLE4(x, y, z, w, format) \
+ if (desc->swizzle[0] == UTIL_FORMAT_SWIZZLE_##x && \
+ desc->swizzle[1] == UTIL_FORMAT_SWIZZLE_##y && \
+ desc->swizzle[2] == UTIL_FORMAT_SWIZZLE_##z && \
+ desc->swizzle[3] == UTIL_FORMAT_SWIZZLE_##w) \
+ return format
+
+ /* Array formats. */
+ if (desc->is_array) {
+ switch (desc->nr_channels) {
+ case 1:
+ switch (desc->channel[0].size) {
+ case 8:
+ RETURN_FOR_SWIZZLE1(X, PIPE_FORMAT_R8_UINT);
+ break;
+
+ case 16:
+ RETURN_FOR_SWIZZLE1(X, PIPE_FORMAT_R16_UINT);
+ break;
+
+ case 32:
+ RETURN_FOR_SWIZZLE1(X, PIPE_FORMAT_R32_UINT);
+ break;
+ }
+ break;
+
+ case 2:
+ switch (desc->channel[0].size) {
+ case 8:
+ /* All formats in each group must be of the same type.
+ * We can't use UINT for R8G8 while using UNORM for G8R8.
+ */
+ RETURN_FOR_SWIZZLE2(X, Y, PIPE_FORMAT_R8G8_UNORM);
+ RETURN_FOR_SWIZZLE2(Y, X, PIPE_FORMAT_G8R8_UNORM);
+ break;
+
+ case 16:
+ RETURN_FOR_SWIZZLE2(X, Y, PIPE_FORMAT_R16G16_UNORM);
+ RETURN_FOR_SWIZZLE2(Y, X, PIPE_FORMAT_G16R16_UNORM);
+ break;
+
+ case 32:
+ RETURN_FOR_SWIZZLE2(X, Y, PIPE_FORMAT_R32G32_UINT);
+ break;
+ }
+ break;
+
+ case 3:
+ switch (desc->channel[0].size) {
+ case 8:
+ RETURN_FOR_SWIZZLE3(X, Y, Z, PIPE_FORMAT_R8G8B8_UINT);
+ break;
+
+ case 16:
+ RETURN_FOR_SWIZZLE3(X, Y, Z, PIPE_FORMAT_R16G16B16_UINT);
+ break;
+
+ case 32:
+ RETURN_FOR_SWIZZLE3(X, Y, Z, PIPE_FORMAT_R32G32B32_UINT);
+ break;
+ }
+ break;
+
+ case 4:
+ switch (desc->channel[0].size) {
+ case 8:
+ RETURN_FOR_SWIZZLE4(X, Y, Z, W, PIPE_FORMAT_R8G8B8A8_UNORM);
+ RETURN_FOR_SWIZZLE4(X, Y, Z, 1, PIPE_FORMAT_R8G8B8A8_UNORM);
+ RETURN_FOR_SWIZZLE4(Z, Y, X, W, PIPE_FORMAT_B8G8R8A8_UNORM);
+ RETURN_FOR_SWIZZLE4(Z, Y, X, 1, PIPE_FORMAT_B8G8R8A8_UNORM);
+ RETURN_FOR_SWIZZLE4(W, Z, Y, X, PIPE_FORMAT_A8B8G8R8_UNORM);
+ RETURN_FOR_SWIZZLE4(1, Z, Y, X, PIPE_FORMAT_A8B8G8R8_UNORM);
+ RETURN_FOR_SWIZZLE4(W, X, Y, Z, PIPE_FORMAT_A8R8G8B8_UNORM);
+ RETURN_FOR_SWIZZLE4(1, X, Y, Z, PIPE_FORMAT_A8R8G8B8_UNORM);
+ break;
+
+ case 16:
+ RETURN_FOR_SWIZZLE4(X, Y, Z, W, PIPE_FORMAT_R16G16B16A16_UINT);
+ RETURN_FOR_SWIZZLE4(X, Y, Z, 1, PIPE_FORMAT_R16G16B16A16_UINT);
+ break;
+
+ case 32:
+ RETURN_FOR_SWIZZLE4(X, Y, Z, W, PIPE_FORMAT_R32G32B32A32_UINT);
+ RETURN_FOR_SWIZZLE4(X, Y, Z, 1, PIPE_FORMAT_R32G32B32A32_UINT);
+ break;
+ }
+ }
+
+ assert(!"unknown array format");
+ return PIPE_FORMAT_NONE;
+ }
+
+ assert(!"unknown packed format");
+ return PIPE_FORMAT_NONE;
+}
+
+/**
+ * Return true if the swizzle is XYZW in case of a 4-channel format,
+ * XY in case of a 2-channel format, or X in case of a 1-channel format.
+ */
+static bool
+has_identity_swizzle(const struct util_format_description *desc)
+{
+ int i;
+
+ for (i = 0; i < desc->nr_channels; i++)
+ if (desc->swizzle[i] != UTIL_FORMAT_SWIZZLE_X + i)
+ return false;
+
+ return true;
+}
+
+/**
+ * Return a canonical format for the given bits and channel size.
+ */
+static enum pipe_format
+canonical_format_from_bits(unsigned bits, unsigned channel_size)
+{
+ switch (bits) {
+ case 8:
+ if (channel_size == 8)
+ return get_canonical_format(PIPE_FORMAT_R8_UINT);
+ break;
+
+ case 16:
+ if (channel_size == 8)
+ return get_canonical_format(PIPE_FORMAT_R8G8_UINT);
+ if (channel_size == 16)
+ return get_canonical_format(PIPE_FORMAT_R16_UINT);
+ break;
+
+ case 32:
+ if (channel_size == 8)
+ return get_canonical_format(PIPE_FORMAT_R8G8B8A8_UINT);
+ if (channel_size == 16)
+ return get_canonical_format(PIPE_FORMAT_R16G16_UINT);
+ if (channel_size == 32)
+ return get_canonical_format(PIPE_FORMAT_R32_UINT);
+ break;
+
+ case 64:
+ if (channel_size == 16)
+ return get_canonical_format(PIPE_FORMAT_R16G16B16A16_UINT);
+ if (channel_size == 32)
+ return get_canonical_format(PIPE_FORMAT_R32G32_UINT);
+ break;
+
+ case 128:
+ if (channel_size == 32)
+ return get_canonical_format(PIPE_FORMAT_R32G32B32A32_UINT);
+ break;
+ }
+
+ assert(!"impossible format");
+ return PIPE_FORMAT_NONE;
+}
+
+static void
+blit(struct pipe_context *pipe,
+ struct pipe_resource *dst,
+ enum pipe_format dst_format,
+ unsigned dst_level,
+ unsigned dstx, unsigned dsty, unsigned dstz,
+ struct pipe_resource *src,
+ enum pipe_format src_format,
+ unsigned src_level,
+ const struct pipe_box *src_box)
+{
+ struct pipe_blit_info blit = {{0}};
+
+ blit.src.resource = src;
+ blit.dst.resource = dst;
+ blit.src.format = src_format;
+ blit.dst.format = dst_format;
+ blit.src.level = src_level;
+ blit.dst.level = dst_level;
+ blit.src.box = *src_box;
+ u_box_3d(dstx, dsty, dstz, src_box->width, src_box->height,
+ src_box->depth, &blit.dst.box);
+ blit.mask = PIPE_MASK_RGBA;
+ blit.filter = PIPE_TEX_FILTER_NEAREST;
+
+ pipe->blit(pipe, &blit);
+}
+
+static void
+swizzled_copy(struct pipe_context *pipe,
+ struct pipe_resource *dst,
+ unsigned dst_level,
+ unsigned dstx, unsigned dsty, unsigned dstz,
+ struct pipe_resource *src,
+ unsigned src_level,
+ const struct pipe_box *src_box)
+{
+ const struct util_format_description *src_desc, *dst_desc;
+ unsigned bits;
+ enum pipe_format blit_src_format, blit_dst_format;
+
+ /* Get equivalent canonical formats. Those are always array formats and
+ * copying between compatible canonical formats behaves either like
+ * memcpy or like swizzled memcpy. The idea is that we won't have to care
+ * about the channel type from this point on.
+ * Only the swizzle and channel size.
+ */
+ blit_src_format = get_canonical_format(src->format);
+ blit_dst_format = get_canonical_format(dst->format);
+
+ assert(blit_src_format != PIPE_FORMAT_NONE);
+ assert(blit_dst_format != PIPE_FORMAT_NONE);
+
+ src_desc = util_format_description(blit_src_format);
+ dst_desc = util_format_description(blit_dst_format);
+
+ assert(src_desc->block.bits == dst_desc->block.bits);
+ bits = src_desc->block.bits;
+
+ if (dst_desc->channel[0].size == src_desc->channel[0].size) {
+ /* Only the swizzle is different, which means we can just blit,
+ * e.g. RGBA -> BGRA.
+ */
+ } else if (has_identity_swizzle(src_desc)) {
+ /* Src is unswizzled and dst can be swizzled, so src is typecast
+ * to an equivalent dst-compatible format.
+ * e.g. R32 -> BGRA8 is realized as RGBA8 -> BGRA8
+ */
+ blit_src_format =
+ canonical_format_from_bits(bits, dst_desc->channel[0].size);
+ } else if (has_identity_swizzle(dst_desc)) {
+ /* Dst is unswizzled and src can be swizzled, so dst is typecast
+ * to an equivalent src-compatible format.
+ * e.g. BGRA8 -> R32 is realized as BGRA8 -> RGBA8
+ */
+ blit_dst_format =
+ canonical_format_from_bits(bits, src_desc->channel[0].size);
+ } else {
+ assert(!"This should have been handled by handle_complex_copy.");
+ return;
+ }
+
+ blit(pipe, dst, blit_dst_format, dst_level, dstx, dsty, dstz,
+ src, blit_src_format, src_level, src_box);
+}
+
+static bool
+same_size_and_swizzle(const struct util_format_description *d1,
+ const struct util_format_description *d2)
+{
+ int i;
+
+ if (d1->layout != d2->layout ||
+ d1->nr_channels != d2->nr_channels ||
+ d1->is_array != d2->is_array)
+ return false;
+
+ for (i = 0; i < d1->nr_channels; i++) {
+ if (d1->channel[i].size != d2->channel[i].size)
+ return false;
+
+ if (d1->swizzle[i] <= UTIL_FORMAT_SWIZZLE_W &&
+ d2->swizzle[i] <= UTIL_FORMAT_SWIZZLE_W &&
+ d1->swizzle[i] != d2->swizzle[i])
+ return false;
+ }
+
+ return true;
+}
+
+static struct pipe_resource *
+create_texture(struct pipe_screen *screen, enum pipe_format format,
+ unsigned width, unsigned height, unsigned depth)
+{
+ struct pipe_resource templ;
+
+ memset(&templ, 0, sizeof(templ));
+ templ.format = format;
+ templ.width0 = width;
+ templ.height0 = height;
+ templ.depth0 = 1;
+ templ.array_size = depth;
+ templ.usage = PIPE_USAGE_DEFAULT;
+ templ.bind = PIPE_BIND_SAMPLER_VIEW | PIPE_BIND_RENDER_TARGET;
+
+ if (depth > 1)
+ templ.target = PIPE_TEXTURE_2D_ARRAY;
+ else
+ templ.target = PIPE_TEXTURE_2D;
+
+ return screen->resource_create(screen, &templ);
+}
+
+/**
+ * Handle complex format conversions using 2 blits with a temporary texture
+ * in between, e.g. blitting from B10G10R10A2 to G16R16.
+ *
+ * This example is implemented this way:
+ * 1) First, blit from B10G10R10A2 to R10G10B10A2, which is canonical, so it
+ * can be reinterpreted as a different canonical format of the same bpp,
+ * such as R16G16. This blit only swaps R and B 10-bit components.
+ * 2) Finally, blit the result, which is R10G10B10A2, as R16G16 to G16R16.
+ * This blit only swaps R and G 16-bit components.
+ */
+static bool
+handle_complex_copy(struct pipe_context *pipe,
+ struct pipe_resource *dst,
+ unsigned dst_level,
+ unsigned dstx, unsigned dsty, unsigned dstz,
+ struct pipe_resource *src,
+ unsigned src_level,
+ const struct pipe_box *src_box,
+ enum pipe_format noncanon_format,
+ enum pipe_format canon_format)
+{
+ struct pipe_box temp_box;
+ struct pipe_resource *temp = NULL;
+ const struct util_format_description *src_desc, *dst_desc;
+ const struct util_format_description *canon_desc, *noncanon_desc;
+ bool src_is_canon;
+ bool src_is_noncanon;
+ bool dst_is_canon;
+ bool dst_is_noncanon;
+
+ src_desc = util_format_description(src->format);
+ dst_desc = util_format_description(dst->format);
+ canon_desc = util_format_description(canon_format);
+ noncanon_desc = util_format_description(noncanon_format);
+
+ src_is_canon = same_size_and_swizzle(src_desc, canon_desc);
+ dst_is_canon = same_size_and_swizzle(dst_desc, canon_desc);
+ src_is_noncanon = same_size_and_swizzle(src_desc, noncanon_desc);
+ dst_is_noncanon = same_size_and_swizzle(dst_desc, noncanon_desc);
+
+ if (src_is_noncanon) {
+ /* Simple case - only types differ (e.g. UNORM and UINT). */
+ if (dst_is_noncanon) {
+ blit(pipe, dst, noncanon_format, dst_level, dstx, dsty, dstz, src,
+ noncanon_format, src_level, src_box);
+ return true;
+ }
+
+ /* Simple case - only types and swizzles differ. */
+ if (dst_is_canon) {
+ blit(pipe, dst, canon_format, dst_level, dstx, dsty, dstz, src,
+ noncanon_format, src_level, src_box);
+ return true;
+ }
+
+ /* Use the temporary texture. Src is converted to a canonical format,
+ * then proceed the generic swizzled_copy.
+ */
+ temp = create_texture(pipe->screen, canon_format, src_box->width,
+ src_box->height, src_box->depth);
+
+ u_box_3d(0, 0, 0, src_box->width, src_box->height, src_box->depth,
+ &temp_box);
+
+ blit(pipe, temp, canon_format, 0, 0, 0, 0, src, noncanon_format,
+ src_level, src_box);
+ swizzled_copy(pipe, dst, dst_level, dstx, dsty, dstz, temp, 0,
+ &temp_box);
+ pipe_resource_reference(&temp, NULL);
+ return true;
+ }
+
+ if (dst_is_noncanon) {
+ /* Simple case - only types and swizzles differ. */
+ if (src_is_canon) {
+ blit(pipe, dst, noncanon_format, dst_level, dstx, dsty, dstz, src,
+ canon_format, src_level, src_box);
+ return true;
+ }
+
+ /* Use the temporary texture. First, use the generic copy, but use
+ * a canonical format in the destination. Then convert */
+ temp = create_texture(pipe->screen, canon_format, src_box->width,
+ src_box->height, src_box->depth);
+
+ u_box_3d(0, 0, 0, src_box->width, src_box->height, src_box->depth,
+ &temp_box);
+
+ swizzled_copy(pipe, temp, 0, 0, 0, 0, src, src_level, src_box);
+ blit(pipe, dst, noncanon_format, dst_level, dstx, dsty, dstz, temp,
+ canon_format, 0, &temp_box);
+ pipe_resource_reference(&temp, NULL);
+ return true;
+ }
+
+ return false;
+}
+
+static void
+copy_image(struct pipe_context *pipe,
+ struct pipe_resource *dst,
+ unsigned dst_level,
+ unsigned dstx, unsigned dsty, unsigned dstz,
+ struct pipe_resource *src,
+ unsigned src_level,
+ const struct pipe_box *src_box)
+{
+ if (src->format == dst->format ||
+ util_format_is_compressed(src->format) ||
+ util_format_is_compressed(dst->format)) {
+ pipe->resource_copy_region(pipe, dst, dst_level, dstx, dsty, dstz,
+ src, src_level, src_box);
+ return;
+ }
+
+ /* Copying to/from B10G10R10*2 needs 2 blits with R10G10B10A2
+ * as a temporary texture in between.
+ */
+ if (handle_complex_copy(pipe, dst, dst_level, dstx, dsty, dstz, src,
+ src_level, src_box, PIPE_FORMAT_B10G10R10A2_UINT,
+ PIPE_FORMAT_R10G10B10A2_UINT))
+ return;
+
+ /* Copying to/from G8R8 needs 2 blits with R8G8 as a temporary texture
+ * in between.
+ */
+ if (handle_complex_copy(pipe, dst, dst_level, dstx, dsty, dstz, src,
+ src_level, src_box, PIPE_FORMAT_G8R8_UNORM,
+ PIPE_FORMAT_R8G8_UNORM))
+ return;
+
+ /* Copying to/from G16R16 needs 2 blits with R16G16 as a temporary texture
+ * in between.
+ */
+ if (handle_complex_copy(pipe, dst, dst_level, dstx, dsty, dstz, src,
+ src_level, src_box, PIPE_FORMAT_G16R16_UNORM,
+ PIPE_FORMAT_R16G16_UNORM))
+ return;
+
+ /* Only allow non-identity swizzling on RGBA8 formats. */
+
+ /* Simple copy, memcpy with swizzling, no format conversion. */
+ swizzled_copy(pipe, dst, dst_level, dstx, dsty, dstz, src, src_level,
+ src_box);
+}
+
+static void
+st_CopyImageSubData(struct gl_context *ctx,
+ struct gl_texture_image *src_image,
+ struct gl_renderbuffer *src_renderbuffer,
+ int src_x, int src_y, int src_z,
+ struct gl_texture_image *dst_image,
+ struct gl_renderbuffer *dst_renderbuffer,
+ int dst_x, int dst_y, int dst_z,
+ int src_width, int src_height)
+{
+ struct st_context *st = st_context(ctx);
+ struct pipe_context *pipe = st->pipe;
+ struct pipe_resource *src_res, *dst_res;
+ struct pipe_box box;
+ int src_level, dst_level;
+
+ if (src_image) {
+ struct st_texture_image *src = st_texture_image(src_image);
+ src_res = src->pt;
+ src_level = src_image->Level;
+ src_z += src_image->Face;
+ } else {
+ struct st_renderbuffer *src = st_renderbuffer(src_renderbuffer);
+ src_res = src->texture;
+ src_level = 0;
+ }
+
+ if (dst_image) {
+ struct st_texture_image *dst = st_texture_image(dst_image);
+ dst_res = dst->pt;
+ dst_level = dst_image->Level;
+ dst_z += dst_image->Face;
+ } else {
+ struct st_renderbuffer *dst = st_renderbuffer(dst_renderbuffer);
+ dst_res = dst->texture;
+ dst_level = 0;
+ }
+
+ u_box_2d_zslice(src_x, src_y, src_z, src_width, src_height, &box);
+
+ copy_image(pipe, dst_res, dst_level, dst_x, dst_y, dst_z,
+ src_res, src_level, &box);
+}
+
+void
+st_init_copy_image_functions(struct dd_function_table *functions)
+{
+ functions->CopyImageSubData = st_CopyImageSubData;
+}