| /* |
| * Copyright © 2017 Red Hat |
| * |
| * 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 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. |
| */ |
| |
| #include "pipe/p_screen.h" |
| |
| #include "util/u_box.h" |
| #include "util/format/u_format.h" |
| #include "util/format/u_format_rgtc.h" |
| #include "util/format/u_format_zs.h" |
| #include "util/u_inlines.h" |
| #include "util/u_transfer_helper.h" |
| |
| |
| struct u_transfer_helper { |
| const struct u_transfer_vtbl *vtbl; |
| bool separate_z32s8; /**< separate z32 and s8 */ |
| bool separate_stencil; /**< separate stencil for all formats */ |
| bool fake_rgtc; |
| bool msaa_map; |
| }; |
| |
| static inline bool handle_transfer(struct pipe_resource *prsc) |
| { |
| struct u_transfer_helper *helper = prsc->screen->transfer_helper; |
| |
| if (helper->vtbl->get_internal_format) { |
| enum pipe_format internal_format = |
| helper->vtbl->get_internal_format(prsc); |
| if (internal_format != prsc->format) |
| return true; |
| } |
| |
| if (helper->msaa_map && (prsc->nr_samples > 1)) |
| return true; |
| |
| return false; |
| } |
| |
| /* The pipe_transfer ptr could either be the driver's, or u_transfer, |
| * depending on whether we are intervening or not. Check handle_transfer() |
| * before dereferencing. |
| */ |
| struct u_transfer { |
| struct pipe_transfer base; |
| /* Note that in case of MSAA resolve for transfer plus z32s8 or fake rgtc |
| * we end up with stacked u_transfer's. The MSAA resolve case doesn't call |
| * helper->vtbl fxns directly, but calls back to pctx->transfer_map()/etc |
| * so the format related handling can work in conjunction with MSAA resolve. |
| */ |
| struct pipe_transfer *trans; /* driver's transfer */ |
| struct pipe_transfer *trans2; /* 2nd transfer for s8 stencil buffer in z32s8 */ |
| void *ptr, *ptr2; /* ptr to trans, and trans2 */ |
| void *staging; /* staging buffer */ |
| struct pipe_resource *ss; /* staging resource for MSAA resolves */ |
| }; |
| |
| static inline struct u_transfer * |
| u_transfer(struct pipe_transfer *ptrans) |
| { |
| debug_assert(handle_transfer(ptrans->resource)); |
| return (struct u_transfer *)ptrans; |
| } |
| |
| struct pipe_resource * |
| u_transfer_helper_resource_create(struct pipe_screen *pscreen, |
| const struct pipe_resource *templ) |
| { |
| struct u_transfer_helper *helper = pscreen->transfer_helper; |
| enum pipe_format format = templ->format; |
| struct pipe_resource *prsc; |
| |
| if ((helper->separate_stencil && util_format_is_depth_and_stencil(format)) || |
| (format == PIPE_FORMAT_Z32_FLOAT_S8X24_UINT && helper->separate_z32s8)) { |
| struct pipe_resource t = *templ; |
| struct pipe_resource *stencil; |
| |
| t.format = util_format_get_depth_only(format); |
| |
| prsc = helper->vtbl->resource_create(pscreen, &t); |
| if (!prsc) |
| return NULL; |
| |
| prsc->format = format; /* frob the format back to the "external" format */ |
| |
| t.format = PIPE_FORMAT_S8_UINT; |
| stencil = helper->vtbl->resource_create(pscreen, &t); |
| |
| if (!stencil) { |
| helper->vtbl->resource_destroy(pscreen, prsc); |
| return NULL; |
| } |
| |
| helper->vtbl->set_stencil(prsc, stencil); |
| } else if ((util_format_description(format)->layout == UTIL_FORMAT_LAYOUT_RGTC) && |
| helper->fake_rgtc) { |
| struct pipe_resource t = *templ; |
| t.format = PIPE_FORMAT_R8G8B8A8_UNORM; |
| |
| prsc = helper->vtbl->resource_create(pscreen, &t); |
| if (!prsc) |
| return NULL; |
| |
| prsc->format = format; /* frob the format back to the "external" format */ |
| } else { |
| /* normal case, no special handling: */ |
| prsc = helper->vtbl->resource_create(pscreen, templ); |
| if (!prsc) |
| return NULL; |
| } |
| |
| return prsc; |
| } |
| |
| void |
| u_transfer_helper_resource_destroy(struct pipe_screen *pscreen, |
| struct pipe_resource *prsc) |
| { |
| struct u_transfer_helper *helper = pscreen->transfer_helper; |
| |
| if (helper->vtbl->get_stencil) { |
| struct pipe_resource *stencil = helper->vtbl->get_stencil(prsc); |
| |
| pipe_resource_reference(&stencil, NULL); |
| } |
| |
| helper->vtbl->resource_destroy(pscreen, prsc); |
| } |
| |
| static bool needs_pack(unsigned usage) |
| { |
| return (usage & PIPE_MAP_READ) && |
| !(usage & (PIPE_MAP_DISCARD_WHOLE_RESOURCE | PIPE_MAP_DISCARD_RANGE)); |
| } |
| |
| /* In the case of transfer_map of a multi-sample resource, call back into |
| * pctx->transfer_map() to map the staging resource, to handle cases of |
| * MSAA + separate_z32s8 or fake_rgtc |
| */ |
| static void * |
| transfer_map_msaa(struct pipe_context *pctx, |
| struct pipe_resource *prsc, |
| unsigned level, unsigned usage, |
| const struct pipe_box *box, |
| struct pipe_transfer **pptrans) |
| { |
| struct pipe_screen *pscreen = pctx->screen; |
| struct u_transfer *trans = calloc(1, sizeof(*trans)); |
| if (!trans) |
| return NULL; |
| struct pipe_transfer *ptrans = &trans->base; |
| |
| pipe_resource_reference(&ptrans->resource, prsc); |
| ptrans->level = level; |
| ptrans->usage = usage; |
| ptrans->box = *box; |
| |
| struct pipe_resource tmpl = { |
| .target = prsc->target, |
| .format = prsc->format, |
| .width0 = box->width, |
| .height0 = box->height, |
| .depth0 = 1, |
| .array_size = 1, |
| }; |
| trans->ss = pscreen->resource_create(pscreen, &tmpl); |
| if (!trans->ss) { |
| free(trans); |
| return NULL; |
| } |
| |
| if (needs_pack(usage)) { |
| struct pipe_blit_info blit; |
| memset(&blit, 0, sizeof(blit)); |
| |
| blit.src.resource = ptrans->resource; |
| blit.src.format = ptrans->resource->format; |
| blit.src.level = ptrans->level; |
| blit.src.box = *box; |
| |
| blit.dst.resource = trans->ss; |
| blit.dst.format = trans->ss->format; |
| blit.dst.box.width = box->width; |
| blit.dst.box.height = box->height; |
| blit.dst.box.depth = 1; |
| |
| blit.mask = util_format_get_mask(prsc->format); |
| blit.filter = PIPE_TEX_FILTER_NEAREST; |
| |
| pctx->blit(pctx, &blit); |
| } |
| |
| struct pipe_box map_box = *box; |
| map_box.x = 0; |
| map_box.y = 0; |
| |
| void *ss_map = pctx->transfer_map(pctx, trans->ss, 0, usage, &map_box, |
| &trans->trans); |
| if (!ss_map) { |
| free(trans); |
| return NULL; |
| } |
| |
| ptrans->stride = trans->trans->stride; |
| *pptrans = ptrans; |
| return ss_map; |
| } |
| |
| void * |
| u_transfer_helper_transfer_map(struct pipe_context *pctx, |
| struct pipe_resource *prsc, |
| unsigned level, unsigned usage, |
| const struct pipe_box *box, |
| struct pipe_transfer **pptrans) |
| { |
| struct u_transfer_helper *helper = pctx->screen->transfer_helper; |
| struct u_transfer *trans; |
| struct pipe_transfer *ptrans; |
| enum pipe_format format = prsc->format; |
| unsigned width = box->width; |
| unsigned height = box->height; |
| |
| if (!handle_transfer(prsc)) |
| return helper->vtbl->transfer_map(pctx, prsc, level, usage, box, pptrans); |
| |
| if (helper->msaa_map && (prsc->nr_samples > 1)) |
| return transfer_map_msaa(pctx, prsc, level, usage, box, pptrans); |
| |
| debug_assert(box->depth == 1); |
| |
| trans = calloc(1, sizeof(*trans)); |
| if (!trans) |
| return NULL; |
| |
| ptrans = &trans->base; |
| pipe_resource_reference(&ptrans->resource, prsc); |
| ptrans->level = level; |
| ptrans->usage = usage; |
| ptrans->box = *box; |
| ptrans->stride = util_format_get_stride(format, box->width); |
| ptrans->layer_stride = ptrans->stride * box->height; |
| |
| trans->staging = malloc(ptrans->layer_stride); |
| if (!trans->staging) |
| goto fail; |
| |
| trans->ptr = helper->vtbl->transfer_map(pctx, prsc, level, usage, box, |
| &trans->trans); |
| if (!trans->ptr) |
| goto fail; |
| |
| if (util_format_is_depth_and_stencil(prsc->format)) { |
| struct pipe_resource *stencil = helper->vtbl->get_stencil(prsc); |
| trans->ptr2 = helper->vtbl->transfer_map(pctx, stencil, level, |
| usage, box, &trans->trans2); |
| |
| if (needs_pack(usage)) { |
| switch (prsc->format) { |
| case PIPE_FORMAT_Z32_FLOAT_S8X24_UINT: |
| util_format_z32_float_s8x24_uint_pack_z_float(trans->staging, |
| ptrans->stride, |
| trans->ptr, |
| trans->trans->stride, |
| width, height); |
| util_format_z32_float_s8x24_uint_pack_s_8uint(trans->staging, |
| ptrans->stride, |
| trans->ptr2, |
| trans->trans2->stride, |
| width, height); |
| break; |
| case PIPE_FORMAT_Z24_UNORM_S8_UINT: |
| util_format_z24_unorm_s8_uint_pack_separate(trans->staging, |
| ptrans->stride, |
| trans->ptr, |
| trans->trans->stride, |
| trans->ptr2, |
| trans->trans2->stride, |
| width, height); |
| break; |
| default: |
| unreachable("Unexpected format"); |
| } |
| } |
| } else if (util_format_description(prsc->format)->layout == UTIL_FORMAT_LAYOUT_RGTC) { |
| if (needs_pack(usage)) { |
| switch (prsc->format) { |
| case PIPE_FORMAT_RGTC1_UNORM: |
| case PIPE_FORMAT_RGTC1_SNORM: |
| case PIPE_FORMAT_LATC1_UNORM: |
| case PIPE_FORMAT_LATC1_SNORM: |
| util_format_rgtc1_unorm_pack_rgba_8unorm(trans->staging, |
| ptrans->stride, |
| trans->ptr, |
| trans->trans->stride, |
| width, height); |
| break; |
| case PIPE_FORMAT_RGTC2_UNORM: |
| case PIPE_FORMAT_RGTC2_SNORM: |
| case PIPE_FORMAT_LATC2_UNORM: |
| case PIPE_FORMAT_LATC2_SNORM: |
| util_format_rgtc2_unorm_pack_rgba_8unorm(trans->staging, |
| ptrans->stride, |
| trans->ptr, |
| trans->trans->stride, |
| width, height); |
| break; |
| default: |
| assert(!"Unexpected format"); |
| break; |
| } |
| } |
| } else { |
| unreachable("bleh"); |
| } |
| |
| *pptrans = ptrans; |
| return trans->staging; |
| |
| fail: |
| if (trans->trans) |
| helper->vtbl->transfer_unmap(pctx, trans->trans); |
| if (trans->trans2) |
| helper->vtbl->transfer_unmap(pctx, trans->trans2); |
| pipe_resource_reference(&ptrans->resource, NULL); |
| free(trans->staging); |
| free(trans); |
| return NULL; |
| } |
| |
| static void |
| flush_region(struct pipe_context *pctx, struct pipe_transfer *ptrans, |
| const struct pipe_box *box) |
| { |
| struct u_transfer_helper *helper = pctx->screen->transfer_helper; |
| /* using the function here hits an assert for the deinterleave cases */ |
| struct u_transfer *trans = (struct u_transfer *)ptrans; |
| enum pipe_format iformat, format = ptrans->resource->format; |
| unsigned width = box->width; |
| unsigned height = box->height; |
| void *src, *dst; |
| |
| if (!(ptrans->usage & PIPE_MAP_WRITE)) |
| return; |
| |
| if (trans->ss) { |
| struct pipe_blit_info blit; |
| memset(&blit, 0, sizeof(blit)); |
| |
| blit.src.resource = trans->ss; |
| blit.src.format = trans->ss->format; |
| blit.src.box = *box; |
| |
| blit.dst.resource = ptrans->resource; |
| blit.dst.format = ptrans->resource->format; |
| blit.dst.level = ptrans->level; |
| |
| u_box_2d(ptrans->box.x + box->x, |
| ptrans->box.y + box->y, |
| box->width, box->height, |
| &blit.dst.box); |
| |
| blit.mask = util_format_get_mask(ptrans->resource->format); |
| blit.filter = PIPE_TEX_FILTER_NEAREST; |
| |
| pctx->blit(pctx, &blit); |
| |
| return; |
| } |
| |
| iformat = helper->vtbl->get_internal_format(ptrans->resource); |
| |
| src = (uint8_t *)trans->staging + |
| (box->y * ptrans->stride) + |
| (box->x * util_format_get_blocksize(format)); |
| dst = (uint8_t *)trans->ptr + |
| (box->y * trans->trans->stride) + |
| (box->x * util_format_get_blocksize(iformat)); |
| |
| switch (format) { |
| case PIPE_FORMAT_Z32_FLOAT_S8X24_UINT: |
| util_format_z32_float_s8x24_uint_unpack_z_float(dst, |
| trans->trans->stride, |
| src, |
| ptrans->stride, |
| width, height); |
| /* fallthru */ |
| case PIPE_FORMAT_X32_S8X24_UINT: |
| dst = (uint8_t *)trans->ptr2 + |
| (box->y * trans->trans2->stride) + |
| (box->x * util_format_get_blocksize(PIPE_FORMAT_S8_UINT)); |
| |
| util_format_z32_float_s8x24_uint_unpack_s_8uint(dst, |
| trans->trans2->stride, |
| src, |
| ptrans->stride, |
| width, height); |
| break; |
| case PIPE_FORMAT_Z24_UNORM_S8_UINT: |
| /* just do a strided 32-bit copy for depth; s8 can become garbage x8 */ |
| util_format_z32_unorm_unpack_z_32unorm(dst, trans->trans->stride, |
| src, ptrans->stride, |
| width, height); |
| /* fallthru */ |
| case PIPE_FORMAT_X24S8_UINT: |
| dst = (uint8_t *)trans->ptr2 + |
| (box->y * trans->trans2->stride) + |
| (box->x * util_format_get_blocksize(PIPE_FORMAT_S8_UINT)); |
| |
| util_format_z24_unorm_s8_uint_unpack_s_8uint(dst, trans->trans2->stride, |
| src, ptrans->stride, |
| width, height); |
| break; |
| |
| case PIPE_FORMAT_RGTC1_UNORM: |
| case PIPE_FORMAT_RGTC1_SNORM: |
| case PIPE_FORMAT_LATC1_UNORM: |
| case PIPE_FORMAT_LATC1_SNORM: |
| util_format_rgtc1_unorm_unpack_rgba_8unorm(dst, |
| trans->trans->stride, |
| src, |
| ptrans->stride, |
| width, height); |
| break; |
| case PIPE_FORMAT_RGTC2_UNORM: |
| case PIPE_FORMAT_RGTC2_SNORM: |
| case PIPE_FORMAT_LATC2_UNORM: |
| case PIPE_FORMAT_LATC2_SNORM: |
| util_format_rgtc2_unorm_unpack_rgba_8unorm(dst, |
| trans->trans->stride, |
| src, |
| ptrans->stride, |
| width, height); |
| break; |
| default: |
| assert(!"Unexpected staging transfer type"); |
| break; |
| } |
| } |
| |
| void |
| u_transfer_helper_transfer_flush_region(struct pipe_context *pctx, |
| struct pipe_transfer *ptrans, |
| const struct pipe_box *box) |
| { |
| struct u_transfer_helper *helper = pctx->screen->transfer_helper; |
| |
| if (handle_transfer(ptrans->resource)) { |
| struct u_transfer *trans = u_transfer(ptrans); |
| |
| flush_region(pctx, ptrans, box); |
| |
| /* handle MSAA case, since there could be multiple levels of |
| * wrapped transfer, call pctx->transfer_flush_region() |
| * instead of helper->vtbl->transfer_flush_region() |
| */ |
| if (trans->ss) { |
| pctx->transfer_flush_region(pctx, trans->trans, box); |
| return; |
| } |
| |
| helper->vtbl->transfer_flush_region(pctx, trans->trans, box); |
| if (trans->trans2) |
| helper->vtbl->transfer_flush_region(pctx, trans->trans2, box); |
| |
| } else { |
| helper->vtbl->transfer_flush_region(pctx, ptrans, box); |
| } |
| } |
| |
| void |
| u_transfer_helper_transfer_unmap(struct pipe_context *pctx, |
| struct pipe_transfer *ptrans) |
| { |
| struct u_transfer_helper *helper = pctx->screen->transfer_helper; |
| |
| if (handle_transfer(ptrans->resource)) { |
| struct u_transfer *trans = u_transfer(ptrans); |
| |
| if (!(ptrans->usage & PIPE_MAP_FLUSH_EXPLICIT)) { |
| struct pipe_box box; |
| u_box_2d(0, 0, ptrans->box.width, ptrans->box.height, &box); |
| flush_region(pctx, ptrans, &box); |
| } |
| |
| /* in MSAA case, there could be multiple levels of wrapping |
| * so don't call helper->vtbl->transfer_unmap() directly |
| */ |
| if (trans->ss) { |
| pctx->transfer_unmap(pctx, trans->trans); |
| pipe_resource_reference(&trans->ss, NULL); |
| } else { |
| helper->vtbl->transfer_unmap(pctx, trans->trans); |
| if (trans->trans2) |
| helper->vtbl->transfer_unmap(pctx, trans->trans2); |
| } |
| |
| pipe_resource_reference(&ptrans->resource, NULL); |
| |
| free(trans->staging); |
| free(trans); |
| } else { |
| helper->vtbl->transfer_unmap(pctx, ptrans); |
| } |
| } |
| |
| struct u_transfer_helper * |
| u_transfer_helper_create(const struct u_transfer_vtbl *vtbl, |
| bool separate_z32s8, |
| bool separate_stencil, |
| bool fake_rgtc, |
| bool msaa_map) |
| { |
| struct u_transfer_helper *helper = calloc(1, sizeof(*helper)); |
| |
| helper->vtbl = vtbl; |
| helper->separate_z32s8 = separate_z32s8; |
| helper->separate_stencil = separate_stencil; |
| helper->fake_rgtc = fake_rgtc; |
| helper->msaa_map = msaa_map; |
| |
| return helper; |
| } |
| |
| void |
| u_transfer_helper_destroy(struct u_transfer_helper *helper) |
| { |
| free(helper); |
| } |
| |
| |
| /* these two functions 'deinterleave' are meant to be used without the corresponding |
| * resource_create/destroy hooks, as they perform the interleaving on-the-fly |
| * |
| * drivers should expect to be passed the same buffer repeatedly with the format changed |
| * to indicate which component is being mapped |
| */ |
| void * |
| u_transfer_helper_deinterleave_transfer_map(struct pipe_context *pctx, |
| struct pipe_resource *prsc, |
| unsigned level, unsigned usage, |
| const struct pipe_box *box, |
| struct pipe_transfer **pptrans) |
| { |
| struct u_transfer_helper *helper = pctx->screen->transfer_helper; |
| struct u_transfer *trans; |
| struct pipe_transfer *ptrans; |
| enum pipe_format format = prsc->format; |
| unsigned width = box->width; |
| unsigned height = box->height; |
| |
| if (!((helper->separate_stencil && util_format_is_depth_and_stencil(format)) || |
| (format == PIPE_FORMAT_Z32_FLOAT_S8X24_UINT && helper->separate_z32s8))) |
| return helper->vtbl->transfer_map(pctx, prsc, level, usage, box, pptrans); |
| |
| debug_assert(box->depth == 1); |
| |
| trans = calloc(1, sizeof(*trans)); |
| if (!trans) |
| return NULL; |
| |
| ptrans = &trans->base; |
| pipe_resource_reference(&ptrans->resource, prsc); |
| ptrans->level = level; |
| ptrans->usage = usage; |
| ptrans->box = *box; |
| ptrans->stride = util_format_get_stride(format, box->width); |
| ptrans->layer_stride = ptrans->stride * box->height; |
| |
| trans->staging = malloc(ptrans->layer_stride); |
| if (!trans->staging) |
| goto fail; |
| |
| trans->ptr = helper->vtbl->transfer_map(pctx, prsc, level, usage | PIPE_MAP_DEPTH_ONLY, box, |
| &trans->trans); |
| if (!trans->ptr) |
| goto fail; |
| |
| trans->ptr2 = helper->vtbl->transfer_map(pctx, prsc, level, |
| usage | PIPE_MAP_STENCIL_ONLY, box, &trans->trans2); |
| if (needs_pack(usage)) { |
| switch (prsc->format) { |
| case PIPE_FORMAT_Z32_FLOAT_S8X24_UINT: |
| util_format_z32_float_s8x24_uint_pack_z_float(trans->staging, |
| ptrans->stride, |
| trans->ptr, |
| trans->trans->stride, |
| width, height); |
| util_format_z32_float_s8x24_uint_pack_s_8uint(trans->staging, |
| ptrans->stride, |
| trans->ptr2, |
| trans->trans2->stride, |
| width, height); |
| break; |
| case PIPE_FORMAT_Z24_UNORM_S8_UINT: |
| util_format_z24_unorm_s8_uint_pack_separate(trans->staging, |
| ptrans->stride, |
| trans->ptr, |
| trans->trans->stride, |
| trans->ptr2, |
| trans->trans2->stride, |
| width, height); |
| break; |
| default: |
| unreachable("Unexpected format"); |
| } |
| } |
| |
| *pptrans = ptrans; |
| return trans->staging; |
| |
| fail: |
| if (trans->trans) |
| helper->vtbl->transfer_unmap(pctx, trans->trans); |
| if (trans->trans2) |
| helper->vtbl->transfer_unmap(pctx, trans->trans2); |
| pipe_resource_reference(&ptrans->resource, NULL); |
| free(trans->staging); |
| free(trans); |
| return NULL; |
| } |
| |
| void |
| u_transfer_helper_deinterleave_transfer_unmap(struct pipe_context *pctx, |
| struct pipe_transfer *ptrans) |
| { |
| struct u_transfer_helper *helper = pctx->screen->transfer_helper; |
| enum pipe_format format = ptrans->resource->format; |
| |
| if ((helper->separate_stencil && util_format_is_depth_and_stencil(format)) || |
| (format == PIPE_FORMAT_Z32_FLOAT_S8X24_UINT && helper->separate_z32s8)) { |
| struct u_transfer *trans = (struct u_transfer *)ptrans; |
| |
| if (!(ptrans->usage & PIPE_MAP_FLUSH_EXPLICIT)) { |
| struct pipe_box box; |
| u_box_2d(0, 0, ptrans->box.width, ptrans->box.height, &box); |
| flush_region(pctx, ptrans, &box); |
| } |
| |
| helper->vtbl->transfer_unmap(pctx, trans->trans); |
| if (trans->trans2) |
| helper->vtbl->transfer_unmap(pctx, trans->trans2); |
| |
| pipe_resource_reference(&ptrans->resource, NULL); |
| |
| free(trans->staging); |
| free(trans); |
| } else { |
| helper->vtbl->transfer_unmap(pctx, ptrans); |
| } |
| } |