/* * 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/u_format.h" #include "util/u_format_rgtc.h" #include "util/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; 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 ((format == PIPE_FORMAT_Z32_FLOAT_S8X24_UINT) && helper->separate_z32s8) { struct pipe_resource t = *templ; struct pipe_resource *stencil; t.format = PIPE_FORMAT_Z32_FLOAT; 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_TRANSFER_READ) && !(usage & (PIPE_TRANSFER_DISCARD_WHOLE_RESOURCE | PIPE_TRANSFER_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); } void *ss_map = pctx->transfer_map(pctx, trans->ss, 0, usage, box, &trans->trans); if (!ss_map) { free(trans); return NULL; } *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 (prsc->format == PIPE_FORMAT_Z32_FLOAT_S8X24_UINT) { 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)) { 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); } } else if (needs_pack(usage) && util_format_description(prsc->format)->layout == UTIL_FORMAT_LAYOUT_RGTC) { 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; struct u_transfer *trans = 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_TRANSFER_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_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_TRANSFER_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); } 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 fake_rgtc, bool msaa_map) { struct u_transfer_helper *helper = calloc(1, sizeof(*helper)); helper->vtbl = vtbl; helper->separate_z32s8 = separate_z32s8; helper->fake_rgtc = fake_rgtc; helper->msaa_map = msaa_map; return helper; } void u_transfer_helper_destroy(struct u_transfer_helper *helper) { free(helper); }