/* * Copyright 2016 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 * 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 "sp_context.h" #include "sp_image.h" #include "sp_texture.h" #include "util/u_format.h" /* * Get the offset into the base image * first element for a buffer or layer/level for texture. */ static uint32_t get_image_offset(const struct softpipe_resource *spr, const struct pipe_image_view *iview, enum pipe_format format, unsigned r_coord) { int base_layer = 0; if (spr->base.target == PIPE_BUFFER) return iview->u.buf.offset; if (spr->base.target == PIPE_TEXTURE_1D_ARRAY || spr->base.target == PIPE_TEXTURE_2D_ARRAY || spr->base.target == PIPE_TEXTURE_CUBE_ARRAY || spr->base.target == PIPE_TEXTURE_CUBE || spr->base.target == PIPE_TEXTURE_3D) base_layer = r_coord + iview->u.tex.first_layer; return softpipe_get_tex_image_offset(spr, iview->u.tex.level, base_layer); } /* * Does this texture instruction have a layer or depth parameter. */ static inline bool has_layer_or_depth(unsigned tgsi_tex_instr) { return (tgsi_tex_instr == TGSI_TEXTURE_3D || tgsi_tex_instr == TGSI_TEXTURE_CUBE || tgsi_tex_instr == TGSI_TEXTURE_1D_ARRAY || tgsi_tex_instr == TGSI_TEXTURE_2D_ARRAY || tgsi_tex_instr == TGSI_TEXTURE_CUBE_ARRAY || tgsi_tex_instr == TGSI_TEXTURE_2D_ARRAY_MSAA); } /* * Is this texture instruction a single non-array coordinate. */ static inline bool has_1coord(unsigned tgsi_tex_instr) { return (tgsi_tex_instr == TGSI_TEXTURE_BUFFER || tgsi_tex_instr == TGSI_TEXTURE_1D || tgsi_tex_instr == TGSI_TEXTURE_1D_ARRAY); } /* * check the bounds vs w/h/d */ static inline bool bounds_check(int width, int height, int depth, int s, int t, int r) { if (s < 0 || s >= width) return false; if (t < 0 || t >= height) return false; if (r < 0 || r >= depth) return false; return true; } /* * Checks if the texture target compatible with the image resource * pipe target. */ static inline bool has_compat_target(unsigned pipe_target, unsigned tgsi_target) { switch (pipe_target) { case PIPE_TEXTURE_1D: if (tgsi_target == TGSI_TEXTURE_1D) return true; break; case PIPE_TEXTURE_2D: if (tgsi_target == TGSI_TEXTURE_2D) return true; break; case PIPE_TEXTURE_RECT: if (tgsi_target == TGSI_TEXTURE_RECT) return true; break; case PIPE_TEXTURE_3D: if (tgsi_target == TGSI_TEXTURE_3D || tgsi_target == TGSI_TEXTURE_2D) return true; break; case PIPE_TEXTURE_CUBE: if (tgsi_target == TGSI_TEXTURE_CUBE || tgsi_target == TGSI_TEXTURE_2D) return true; break; case PIPE_TEXTURE_1D_ARRAY: if (tgsi_target == TGSI_TEXTURE_1D || tgsi_target == TGSI_TEXTURE_1D_ARRAY) return true; break; case PIPE_TEXTURE_2D_ARRAY: if (tgsi_target == TGSI_TEXTURE_2D || tgsi_target == TGSI_TEXTURE_2D_ARRAY) return true; break; case PIPE_TEXTURE_CUBE_ARRAY: if (tgsi_target == TGSI_TEXTURE_CUBE || tgsi_target == TGSI_TEXTURE_CUBE_ARRAY || tgsi_target == TGSI_TEXTURE_2D) return true; break; case PIPE_BUFFER: return (tgsi_target == TGSI_TEXTURE_BUFFER); } return false; } static bool get_dimensions(const struct pipe_image_view *iview, const struct softpipe_resource *spr, unsigned tgsi_tex_instr, enum pipe_format pformat, unsigned *width, unsigned *height, unsigned *depth) { if (tgsi_tex_instr == TGSI_TEXTURE_BUFFER) { *width = iview->u.buf.size / util_format_get_blocksize(pformat); *height = 1; *depth = 1; /* * Bounds check the buffer size from the view * and the buffer size from the underlying buffer. */ if (util_format_get_stride(pformat, *width) > util_format_get_stride(spr->base.format, spr->base.width0)) return false; } else { unsigned level; level = spr->base.target == PIPE_BUFFER ? 0 : iview->u.tex.level; *width = u_minify(spr->base.width0, level); *height = u_minify(spr->base.height0, level); if (spr->base.target == PIPE_TEXTURE_3D) *depth = u_minify(spr->base.depth0, level); else *depth = spr->base.array_size; /* Make sure the resource and view have compatiable formats */ if (util_format_get_blocksize(pformat) > util_format_get_blocksize(spr->base.format)) return false; } return true; } static void fill_coords(const struct tgsi_image_params *params, unsigned index, const int s[TGSI_QUAD_SIZE], const int t[TGSI_QUAD_SIZE], const int r[TGSI_QUAD_SIZE], int *s_coord, int *t_coord, int *r_coord) { *s_coord = s[index]; *t_coord = has_1coord(params->tgsi_tex_instr) ? 0 : t[index]; *r_coord = has_layer_or_depth(params->tgsi_tex_instr) ? (params->tgsi_tex_instr == TGSI_TEXTURE_1D_ARRAY ? t[index] : r[index]) : 0; } /* * Implement the image LOAD operation. */ static void sp_tgsi_load(const struct tgsi_image *image, const struct tgsi_image_params *params, const int s[TGSI_QUAD_SIZE], const int t[TGSI_QUAD_SIZE], const int r[TGSI_QUAD_SIZE], const int sample[TGSI_QUAD_SIZE], float rgba[TGSI_NUM_CHANNELS][TGSI_QUAD_SIZE]) { struct sp_tgsi_image *sp_img = (struct sp_tgsi_image *)image; struct pipe_image_view *iview; struct softpipe_resource *spr; unsigned width, height, depth; unsigned stride; int c, j; char *data_ptr; unsigned offset = 0; if (params->unit >= PIPE_MAX_SHADER_IMAGES) goto fail_write_all_zero; iview = &sp_img->sp_iview[params->unit]; spr = (struct softpipe_resource *)iview->resource; if (!spr) goto fail_write_all_zero; if (!has_compat_target(spr->base.target, params->tgsi_tex_instr)) goto fail_write_all_zero; if (!get_dimensions(iview, spr, params->tgsi_tex_instr, params->format, &width, &height, &depth)) return; stride = util_format_get_stride(params->format, width); for (j = 0; j < TGSI_QUAD_SIZE; j++) { int s_coord, t_coord, r_coord; bool fill_zero = false; if (!(params->execmask & (1 << j))) fill_zero = true; fill_coords(params, j, s, t, r, &s_coord, &t_coord, &r_coord); if (!bounds_check(width, height, depth, s_coord, t_coord, r_coord)) fill_zero = true; if (fill_zero) { int nc = util_format_get_nr_components(params->format); int ival = util_format_is_pure_integer(params->format); for (c = 0; c < 4; c++) { rgba[c][j] = 0; if (c == 3 && nc < 4) { if (ival) ((int32_t *)rgba[c])[j] = 1; else rgba[c][j] = 1.0; } } continue; } offset = get_image_offset(spr, iview, params->format, r_coord); data_ptr = (char *)spr->data + offset; if (util_format_is_pure_sint(params->format)) { int32_t sdata[4]; util_format_read_4i(params->format, sdata, 0, data_ptr, stride, s_coord, t_coord, 1, 1); for (c = 0; c < 4; c++) ((int32_t *)rgba[c])[j] = sdata[c]; } else if (util_format_is_pure_uint(params->format)) { uint32_t sdata[4]; util_format_read_4ui(params->format, sdata, 0, data_ptr, stride, s_coord, t_coord, 1, 1); for (c = 0; c < 4; c++) ((uint32_t *)rgba[c])[j] = sdata[c]; } else { float sdata[4]; util_format_read_4f(params->format, sdata, 0, data_ptr, stride, s_coord, t_coord, 1, 1); for (c = 0; c < 4; c++) rgba[c][j] = sdata[c]; } } return; fail_write_all_zero: for (j = 0; j < TGSI_QUAD_SIZE; j++) { for (c = 0; c < 4; c++) rgba[c][j] = 0; } return; } /* * Implement the image STORE operation. */ static void sp_tgsi_store(const struct tgsi_image *image, const struct tgsi_image_params *params, const int s[TGSI_QUAD_SIZE], const int t[TGSI_QUAD_SIZE], const int r[TGSI_QUAD_SIZE], const int sample[TGSI_QUAD_SIZE], float rgba[TGSI_NUM_CHANNELS][TGSI_QUAD_SIZE]) { struct sp_tgsi_image *sp_img = (struct sp_tgsi_image *)image; struct pipe_image_view *iview; struct softpipe_resource *spr; unsigned width, height, depth; unsigned stride; char *data_ptr; int j, c; unsigned offset = 0; unsigned pformat = params->format; if (params->unit >= PIPE_MAX_SHADER_IMAGES) return; iview = &sp_img->sp_iview[params->unit]; spr = (struct softpipe_resource *)iview->resource; if (!spr) return; if (!has_compat_target(spr->base.target, params->tgsi_tex_instr)) return; if (params->format == PIPE_FORMAT_NONE) pformat = spr->base.format; if (!get_dimensions(iview, spr, params->tgsi_tex_instr, pformat, &width, &height, &depth)) return; stride = util_format_get_stride(pformat, width); for (j = 0; j < TGSI_QUAD_SIZE; j++) { int s_coord, t_coord, r_coord; if (!(params->execmask & (1 << j))) continue; fill_coords(params, j, s, t, r, &s_coord, &t_coord, &r_coord); if (!bounds_check(width, height, depth, s_coord, t_coord, r_coord)) continue; offset = get_image_offset(spr, iview, pformat, r_coord); data_ptr = (char *)spr->data + offset; if (util_format_is_pure_sint(pformat)) { int32_t sdata[4]; for (c = 0; c < 4; c++) sdata[c] = ((int32_t *)rgba[c])[j]; util_format_write_4i(pformat, sdata, 0, data_ptr, stride, s_coord, t_coord, 1, 1); } else if (util_format_is_pure_uint(pformat)) { uint32_t sdata[4]; for (c = 0; c < 4; c++) sdata[c] = ((uint32_t *)rgba[c])[j]; util_format_write_4ui(pformat, sdata, 0, data_ptr, stride, s_coord, t_coord, 1, 1); } else { float sdata[4]; for (c = 0; c < 4; c++) sdata[c] = rgba[c][j]; util_format_write_4f(pformat, sdata, 0, data_ptr, stride, s_coord, t_coord, 1, 1); } } } /* * Implement atomic operations on unsigned integers. */ static void handle_op_uint(const struct pipe_image_view *iview, const struct tgsi_image_params *params, bool just_read, char *data_ptr, uint qi, unsigned stride, unsigned opcode, int s, int t, float rgba[TGSI_NUM_CHANNELS][TGSI_QUAD_SIZE], float rgba2[TGSI_NUM_CHANNELS][TGSI_QUAD_SIZE]) { uint c; int nc = util_format_get_nr_components(params->format); unsigned sdata[4]; util_format_read_4ui(params->format, sdata, 0, data_ptr, stride, s, t, 1, 1); if (just_read) { for (c = 0; c < nc; c++) { ((uint32_t *)rgba[c])[qi] = sdata[c]; } return; } switch (opcode) { case TGSI_OPCODE_ATOMUADD: for (c = 0; c < nc; c++) { unsigned temp = sdata[c]; sdata[c] += ((uint32_t *)rgba[c])[qi]; ((uint32_t *)rgba[c])[qi] = temp; } break; case TGSI_OPCODE_ATOMXCHG: for (c = 0; c < nc; c++) { unsigned temp = sdata[c]; sdata[c] = ((uint32_t *)rgba[c])[qi]; ((uint32_t *)rgba[c])[qi] = temp; } break; case TGSI_OPCODE_ATOMCAS: for (c = 0; c < nc; c++) { unsigned dst_x = sdata[c]; unsigned cmp_x = ((uint32_t *)rgba[c])[qi]; unsigned src_x = ((uint32_t *)rgba2[c])[qi]; unsigned temp = sdata[c]; sdata[c] = (dst_x == cmp_x) ? src_x : dst_x; ((uint32_t *)rgba[c])[qi] = temp; } break; case TGSI_OPCODE_ATOMAND: for (c = 0; c < nc; c++) { unsigned temp = sdata[c]; sdata[c] &= ((uint32_t *)rgba[c])[qi]; ((uint32_t *)rgba[c])[qi] = temp; } break; case TGSI_OPCODE_ATOMOR: for (c = 0; c < nc; c++) { unsigned temp = sdata[c]; sdata[c] |= ((uint32_t *)rgba[c])[qi]; ((uint32_t *)rgba[c])[qi] = temp; } break; case TGSI_OPCODE_ATOMXOR: for (c = 0; c < nc; c++) { unsigned temp = sdata[c]; sdata[c] ^= ((uint32_t *)rgba[c])[qi]; ((uint32_t *)rgba[c])[qi] = temp; } break; case TGSI_OPCODE_ATOMUMIN: for (c = 0; c < nc; c++) { unsigned dst_x = sdata[c]; unsigned src_x = ((uint32_t *)rgba[c])[qi]; sdata[c] = MIN2(dst_x, src_x); ((uint32_t *)rgba[c])[qi] = dst_x; } break; case TGSI_OPCODE_ATOMUMAX: for (c = 0; c < nc; c++) { unsigned dst_x = sdata[c]; unsigned src_x = ((uint32_t *)rgba[c])[qi]; sdata[c] = MAX2(dst_x, src_x); ((uint32_t *)rgba[c])[qi] = dst_x; } break; case TGSI_OPCODE_ATOMIMIN: for (c = 0; c < nc; c++) { int dst_x = sdata[c]; int src_x = ((uint32_t *)rgba[c])[qi]; sdata[c] = MIN2(dst_x, src_x); ((uint32_t *)rgba[c])[qi] = dst_x; } break; case TGSI_OPCODE_ATOMIMAX: for (c = 0; c < nc; c++) { int dst_x = sdata[c]; int src_x = ((uint32_t *)rgba[c])[qi]; sdata[c] = MAX2(dst_x, src_x); ((uint32_t *)rgba[c])[qi] = dst_x; } break; default: assert(!"Unexpected TGSI opcode in sp_tgsi_op"); break; } util_format_write_4ui(params->format, sdata, 0, data_ptr, stride, s, t, 1, 1); } /* * Implement atomic operations on signed integers. */ static void handle_op_int(const struct pipe_image_view *iview, const struct tgsi_image_params *params, bool just_read, char *data_ptr, uint qi, unsigned stride, unsigned opcode, int s, int t, float rgba[TGSI_NUM_CHANNELS][TGSI_QUAD_SIZE], float rgba2[TGSI_NUM_CHANNELS][TGSI_QUAD_SIZE]) { uint c; int nc = util_format_get_nr_components(params->format); int sdata[4]; util_format_read_4i(params->format, sdata, 0, data_ptr, stride, s, t, 1, 1); if (just_read) { for (c = 0; c < nc; c++) { ((int32_t *)rgba[c])[qi] = sdata[c]; } return; } switch (opcode) { case TGSI_OPCODE_ATOMUADD: for (c = 0; c < nc; c++) { int temp = sdata[c]; sdata[c] += ((int32_t *)rgba[c])[qi]; ((int32_t *)rgba[c])[qi] = temp; } break; case TGSI_OPCODE_ATOMXCHG: for (c = 0; c < nc; c++) { int temp = sdata[c]; sdata[c] = ((int32_t *)rgba[c])[qi]; ((int32_t *)rgba[c])[qi] = temp; } break; case TGSI_OPCODE_ATOMCAS: for (c = 0; c < nc; c++) { int dst_x = sdata[c]; int cmp_x = ((int32_t *)rgba[c])[qi]; int src_x = ((int32_t *)rgba2[c])[qi]; int temp = sdata[c]; sdata[c] = (dst_x == cmp_x) ? src_x : dst_x; ((int32_t *)rgba[c])[qi] = temp; } break; case TGSI_OPCODE_ATOMAND: for (c = 0; c < nc; c++) { int temp = sdata[c]; sdata[c] &= ((int32_t *)rgba[c])[qi]; ((int32_t *)rgba[c])[qi] = temp; } break; case TGSI_OPCODE_ATOMOR: for (c = 0; c < nc; c++) { int temp = sdata[c]; sdata[c] |= ((int32_t *)rgba[c])[qi]; ((int32_t *)rgba[c])[qi] = temp; } break; case TGSI_OPCODE_ATOMXOR: for (c = 0; c < nc; c++) { int temp = sdata[c]; sdata[c] ^= ((int32_t *)rgba[c])[qi]; ((int32_t *)rgba[c])[qi] = temp; } break; case TGSI_OPCODE_ATOMUMIN: for (c = 0; c < nc; c++) { int dst_x = sdata[c]; int src_x = ((int32_t *)rgba[c])[qi]; sdata[c] = MIN2(dst_x, src_x); ((int32_t *)rgba[c])[qi] = dst_x; } break; case TGSI_OPCODE_ATOMUMAX: for (c = 0; c < nc; c++) { int dst_x = sdata[c]; int src_x = ((int32_t *)rgba[c])[qi]; sdata[c] = MAX2(dst_x, src_x); ((int32_t *)rgba[c])[qi] = dst_x; } break; case TGSI_OPCODE_ATOMIMIN: for (c = 0; c < nc; c++) { int dst_x = sdata[c]; int src_x = ((int32_t *)rgba[c])[qi]; sdata[c] = MIN2(dst_x, src_x); ((int32_t *)rgba[c])[qi] = dst_x; } break; case TGSI_OPCODE_ATOMIMAX: for (c = 0; c < nc; c++) { int dst_x = sdata[c]; int src_x = ((int32_t *)rgba[c])[qi]; sdata[c] = MAX2(dst_x, src_x); ((int32_t *)rgba[c])[qi] = dst_x; } break; default: assert(!"Unexpected TGSI opcode in sp_tgsi_op"); break; } util_format_write_4i(params->format, sdata, 0, data_ptr, stride, s, t, 1, 1); } /* GLES OES_shader_image_atomic.txt allows XCHG on R32F */ static void handle_op_r32f_xchg(const struct pipe_image_view *iview, const struct tgsi_image_params *params, bool just_read, char *data_ptr, uint qi, unsigned stride, unsigned opcode, int s, int t, float rgba[TGSI_NUM_CHANNELS][TGSI_QUAD_SIZE]) { float sdata[4]; uint c; int nc = 1; util_format_read_4f(params->format, sdata, 0, data_ptr, stride, s, t, 1, 1); if (just_read) { for (c = 0; c < nc; c++) { ((int32_t *)rgba[c])[qi] = sdata[c]; } return; } for (c = 0; c < nc; c++) { int temp = sdata[c]; sdata[c] = ((float *)rgba[c])[qi]; ((float *)rgba[c])[qi] = temp; } util_format_write_4f(params->format, sdata, 0, data_ptr, stride, s, t, 1, 1); } /* * Implement atomic image operations. */ static void sp_tgsi_op(const struct tgsi_image *image, const struct tgsi_image_params *params, unsigned opcode, const int s[TGSI_QUAD_SIZE], const int t[TGSI_QUAD_SIZE], const int r[TGSI_QUAD_SIZE], const int sample[TGSI_QUAD_SIZE], float rgba[TGSI_NUM_CHANNELS][TGSI_QUAD_SIZE], float rgba2[TGSI_NUM_CHANNELS][TGSI_QUAD_SIZE]) { struct sp_tgsi_image *sp_img = (struct sp_tgsi_image *)image; struct pipe_image_view *iview; struct softpipe_resource *spr; unsigned width, height, depth; unsigned stride; int j, c; unsigned offset; char *data_ptr; if (params->unit >= PIPE_MAX_SHADER_IMAGES) return; iview = &sp_img->sp_iview[params->unit]; spr = (struct softpipe_resource *)iview->resource; if (!spr) goto fail_write_all_zero; if (!has_compat_target(spr->base.target, params->tgsi_tex_instr)) goto fail_write_all_zero; if (!get_dimensions(iview, spr, params->tgsi_tex_instr, params->format, &width, &height, &depth)) goto fail_write_all_zero; stride = util_format_get_stride(spr->base.format, width); for (j = 0; j < TGSI_QUAD_SIZE; j++) { int s_coord, t_coord, r_coord; bool just_read = false; fill_coords(params, j, s, t, r, &s_coord, &t_coord, &r_coord); if (!bounds_check(width, height, depth, s_coord, t_coord, r_coord)) { int nc = util_format_get_nr_components(params->format); int ival = util_format_is_pure_integer(params->format); int c; for (c = 0; c < 4; c++) { rgba[c][j] = 0; if (c == 3 && nc < 4) { if (ival) ((int32_t *)rgba[c])[j] = 1; else rgba[c][j] = 1.0; } } continue; } /* just readback the value for atomic if execmask isn't set */ if (!(params->execmask & (1 << j))) { just_read = true; } offset = get_image_offset(spr, iview, params->format, r_coord); data_ptr = (char *)spr->data + offset; /* we should see atomic operations on r32 formats */ if (util_format_is_pure_uint(params->format)) handle_op_uint(iview, params, just_read, data_ptr, j, stride, opcode, s_coord, t_coord, rgba, rgba2); else if (util_format_is_pure_sint(params->format)) handle_op_int(iview, params, just_read, data_ptr, j, stride, opcode, s_coord, t_coord, rgba, rgba2); else if (params->format == PIPE_FORMAT_R32_FLOAT && opcode == TGSI_OPCODE_ATOMXCHG) handle_op_r32f_xchg(iview, params, just_read, data_ptr, j, stride, opcode, s_coord, t_coord, rgba); else assert(0); } return; fail_write_all_zero: for (j = 0; j < TGSI_QUAD_SIZE; j++) { for (c = 0; c < 4; c++) rgba[c][j] = 0; } return; } static void sp_tgsi_get_dims(const struct tgsi_image *image, const struct tgsi_image_params *params, int dims[4]) { struct sp_tgsi_image *sp_img = (struct sp_tgsi_image *)image; struct pipe_image_view *iview; struct softpipe_resource *spr; int level; if (params->unit >= PIPE_MAX_SHADER_IMAGES) return; iview = &sp_img->sp_iview[params->unit]; spr = (struct softpipe_resource *)iview->resource; if (!spr) return; if (params->tgsi_tex_instr == TGSI_TEXTURE_BUFFER) { dims[0] = iview->u.buf.size / util_format_get_blocksize(iview->format); dims[1] = dims[2] = dims[3] = 0; return; } level = iview->u.tex.level; dims[0] = u_minify(spr->base.width0, level); switch (params->tgsi_tex_instr) { case TGSI_TEXTURE_1D_ARRAY: dims[1] = iview->u.tex.last_layer - iview->u.tex.first_layer + 1; /* fallthrough */ case TGSI_TEXTURE_1D: return; case TGSI_TEXTURE_2D_ARRAY: dims[2] = iview->u.tex.last_layer - iview->u.tex.first_layer + 1; /* fallthrough */ case TGSI_TEXTURE_2D: case TGSI_TEXTURE_CUBE: case TGSI_TEXTURE_RECT: dims[1] = u_minify(spr->base.height0, level); return; case TGSI_TEXTURE_3D: dims[1] = u_minify(spr->base.height0, level); dims[2] = u_minify(spr->base.depth0, level); return; case TGSI_TEXTURE_CUBE_ARRAY: dims[1] = u_minify(spr->base.height0, level); dims[2] = (iview->u.tex.last_layer - iview->u.tex.first_layer + 1) / 6; break; default: assert(!"unexpected texture target in sp_get_dims()"); return; } } struct sp_tgsi_image * sp_create_tgsi_image(void) { struct sp_tgsi_image *img = CALLOC_STRUCT(sp_tgsi_image); if (!img) return NULL; img->base.load = sp_tgsi_load; img->base.store = sp_tgsi_store; img->base.op = sp_tgsi_op; img->base.get_dims = sp_tgsi_get_dims; return img; };