/* * Copyright 2022 Alyssa Rosenzweig * SPDX-License-Identifier: MIT */ #include "layout.h" static void ail_initialize_linear(struct ail_layout *layout) { /* Select the optimal stride if none is forced */ if (layout->linear_stride_B == 0) { uint32_t minimum_stride_B = util_format_get_stride(layout->format, layout->width_px); layout->linear_stride_B = ALIGN_POT(minimum_stride_B, AIL_CACHELINE); } assert((layout->linear_stride_B % 16) == 0 && "Strides must be aligned"); /* Layer stride must be cache line aligned to pack linear 2D arrays */ layout->layer_stride_B = align64( (uint64_t)layout->linear_stride_B * layout->height_px, AIL_CACHELINE); layout->size_B = layout->layer_stride_B * layout->depth_px; } /* * Get the maximum tile size possible for a given block size. This satisfy * width * height * blocksize = 16384 = page size, so each tile is one page. */ static inline struct ail_tile ail_get_max_tile_size(unsigned blocksize_B) { /* clang-format off */ switch (blocksize_B) { case 1: return (struct ail_tile) { 128, 128 }; case 2: return (struct ail_tile) { 128, 64 }; case 4: return (struct ail_tile) { 64, 64 }; case 8: return (struct ail_tile) { 64, 32 }; case 16: return (struct ail_tile) { 32, 32 }; case 32: return (struct ail_tile) { 32, 16 }; case 64: return (struct ail_tile) { 16, 16 }; default: unreachable("Invalid blocksize"); } /* clang-format on */ } /* * Calculate the number of bytes in a block. This must take both block * dimensions and multisampling into account. */ static uint32_t ail_get_block_size_B(struct ail_layout *layout) { ASSERTED const struct util_format_description *desc = util_format_description(layout->format); assert(((layout->sample_count_sa == 1) || (desc->block.width == 1 && desc->block.height == 1)) && "multisampling and block-compression are mutually-exclusive"); return util_format_get_blocksize(layout->format) * layout->sample_count_sa; } static void ail_initialize_twiddled(struct ail_layout *layout) { unsigned offset_B = 0; unsigned blocksize_B = ail_get_block_size_B(layout); unsigned w_el = util_format_get_nblocksx(layout->format, layout->width_px); unsigned h_el = util_format_get_nblocksy(layout->format, layout->height_px); unsigned bw_px = util_format_get_blockwidth(layout->format); unsigned bh_px = util_format_get_blockheight(layout->format); bool compressed = util_format_is_compressed(layout->format); /* Calculate the tile size used for the large miptree, and the dimensions of * level 0 given that tile size. */ struct ail_tile tilesize_el = ail_get_max_tile_size(blocksize_B); unsigned stx_tiles = DIV_ROUND_UP(w_el, tilesize_el.width_el); unsigned sty_tiles = DIV_ROUND_UP(h_el, tilesize_el.height_el); unsigned sarea_tiles = stx_tiles * sty_tiles; /* Calculate which level the small power-of-two miptree begins at. The * power-of-two miptree is used when either the width or the height is * smaller than a single large tile. */ unsigned pot_level = 0; unsigned pot_w_px = bw_px * w_el; unsigned pot_h_px = bh_px * h_el; do { unsigned pot_w_el = util_format_get_nblocksx(layout->format, pot_w_px); unsigned pot_h_el = util_format_get_nblocksy(layout->format, pot_h_px); if (pot_w_el < tilesize_el.width_el || pot_h_el < tilesize_el.height_el) break; pot_w_px = u_minify(pot_w_px, 1); pot_h_px = u_minify(pot_h_px, 1); pot_level++; } while (1); /* First allocate the large miptree. All tiles in the large miptree are of * size tilesize_el and have their dimensions given by stx/sty/sarea. */ for (unsigned l = 0; l < MIN2(pot_level, layout->levels); ++l) { unsigned tiles = (sarea_tiles >> (2 * l)); bool pad_left = (stx_tiles & BITFIELD_MASK(l)); bool pad_bottom = (sty_tiles & BITFIELD_MASK(l)); bool pad_corner = pad_left && pad_bottom; if (pad_left) tiles += (sty_tiles >> l); if (pad_bottom) tiles += (stx_tiles >> l); if (pad_corner) tiles += 1; unsigned size_el = tiles * tilesize_el.width_el * tilesize_el.height_el; layout->level_offsets_B[l] = offset_B; offset_B = ALIGN_POT(offset_B + (blocksize_B * size_el), AIL_CACHELINE); layout->stride_el[l] = util_format_get_nblocksx( layout->format, u_minify(layout->width_px, l)); /* Compressed textures pad the stride in this case */ if (compressed && pad_left) layout->stride_el[l]++; layout->tilesize_el[l] = tilesize_el; } /* Then begin the POT miptree. Note that we round up to a power-of-two * outside the loop. That ensures correct handling of cases like 33x33 * images, where the round-down error of right-shifting could cause incorrect * tile size calculations. */ unsigned potw_el, poth_el; if (compressed) { /* Compressed formats round then minify instead of minifying then rounding */ potw_el = u_minify(util_next_power_of_two(w_el), pot_level); poth_el = u_minify(util_next_power_of_two(h_el), pot_level); } else { potw_el = util_next_power_of_two(u_minify(w_el, pot_level)); poth_el = util_next_power_of_two(u_minify(h_el, pot_level)); } /* Finally we allocate the POT miptree, starting at level pot_level. Each * level uses the largest power-of-two tile that fits the level. */ for (unsigned l = pot_level; l < layout->levels; ++l) { unsigned size_el = potw_el * poth_el; layout->level_offsets_B[l] = offset_B; offset_B = ALIGN_POT(offset_B + (blocksize_B * size_el), AIL_CACHELINE); /* The tilesize is based on the true mipmap level size, not the POT * rounded size, except for compressed textures */ unsigned tilesize_el; if (compressed) tilesize_el = util_next_power_of_two(MIN2(potw_el, poth_el)); else tilesize_el = util_next_power_of_two(u_minify(MIN2(w_el, h_el), l)); layout->tilesize_el[l] = (struct ail_tile){tilesize_el, tilesize_el}; layout->stride_el[l] = util_format_get_nblocksx( layout->format, u_minify(layout->width_px, l)); potw_el = u_minify(potw_el, 1); poth_el = u_minify(poth_el, 1); } /* Add the end offset so we can easily recover the size of a level */ assert(layout->levels < ARRAY_SIZE(layout->level_offsets_B)); layout->level_offsets_B[layout->levels] = offset_B; /* Align layer size if we have mipmaps and one miptree is larger than one * page */ layout->page_aligned_layers = layout->levels != 1 && offset_B > AIL_PAGESIZE; /* Single-layer images are not padded unless they are Z/S */ bool zs = util_format_is_depth_or_stencil(layout->format); if (layout->depth_px == 1 && !zs) layout->page_aligned_layers = false; /* For writable images, we require page-aligned layers. This appears to be * required for PBE stores, including block stores for colour rendering. * Likewise, we specify the ZLS layer stride in pages, so we need * page-aligned layers for renderable depth/stencil targets. */ layout->page_aligned_layers |= layout->writeable_image; layout->page_aligned_layers |= layout->renderable && layout->depth_px > 1; if (layout->page_aligned_layers) layout->layer_stride_B = ALIGN_POT(offset_B, AIL_PAGESIZE); else layout->layer_stride_B = offset_B; layout->size_B = (uint64_t)layout->layer_stride_B * layout->depth_px; } static void ail_initialize_compression(struct ail_layout *layout) { assert(!util_format_is_compressed(layout->format) && "Compressed pixel formats not supported"); assert(util_format_get_blockwidth(layout->format) == 1); assert(util_format_get_blockheight(layout->format) == 1); unsigned width_sa = ail_effective_width_sa(layout->width_px, layout->sample_count_sa); unsigned height_sa = ail_effective_height_sa(layout->height_px, layout->sample_count_sa); assert(width_sa >= 16 && "Small textures are never compressed"); assert(height_sa >= 16 && "Small textures are never compressed"); layout->metadata_offset_B = layout->size_B; width_sa = ALIGN_POT(width_sa, 16); height_sa = ALIGN_POT(height_sa, 16); unsigned compbuf_B = 0; for (unsigned l = 0; l < layout->levels; ++l) { if (!ail_is_level_compressed(layout, l)) break; layout->level_offsets_compressed_B[l] = compbuf_B; /* The metadata buffer contains 8 bytes per 16x16 compression tile. * Addressing is fully twiddled, so both width and height are padded to * powers-of-two. */ unsigned w_tl = DIV_ROUND_UP(util_next_power_of_two(width_sa), 16); unsigned h_tl = DIV_ROUND_UP(util_next_power_of_two(height_sa), 16); unsigned B_per_tl_2 = 8; compbuf_B += ALIGN_POT(w_tl * h_tl * B_per_tl_2, AIL_CACHELINE); width_sa = DIV_ROUND_UP(width_sa, 2); height_sa = DIV_ROUND_UP(height_sa, 2); } layout->compression_layer_stride_B = compbuf_B; layout->size_B += (uint64_t)(layout->compression_layer_stride_B * layout->depth_px); } void ail_make_miptree(struct ail_layout *layout) { assert(layout->width_px >= 1 && "Invalid dimensions"); assert(layout->height_px >= 1 && "Invalid dimensions"); assert(layout->depth_px >= 1 && "Invalid dimensions"); if (layout->tiling == AIL_TILING_LINEAR) { assert(layout->levels == 1 && "Invalid linear layout"); assert(layout->sample_count_sa == 1 && "Multisampled linear layouts not supported"); assert(util_format_get_blockwidth(layout->format) == 1 && "Strided linear block formats unsupported"); assert(util_format_get_blockheight(layout->format) == 1 && "Strided linear block formats unsupported"); } else { assert(layout->linear_stride_B == 0 && "Invalid nonlinear layout"); assert(layout->levels >= 1 && "Invalid dimensions"); assert(layout->sample_count_sa >= 1 && "Invalid sample count"); } assert(!(layout->writeable_image && layout->tiling == AIL_TILING_TWIDDLED_COMPRESSED) && "Writeable images must not be compressed"); /* Hardware strides are based on the maximum number of levels, so always * allocate them all. */ if (layout->levels > 1) { unsigned major_axis_px = MAX2(layout->width_px, layout->height_px); if (layout->mipmapped_z) major_axis_px = MAX2(major_axis_px, layout->depth_px); layout->levels = util_logbase2(major_axis_px) + 1; } assert(util_format_get_blockdepth(layout->format) == 1 && "Deep formats unsupported"); switch (layout->tiling) { case AIL_TILING_LINEAR: ail_initialize_linear(layout); break; case AIL_TILING_TWIDDLED: ail_initialize_twiddled(layout); break; case AIL_TILING_TWIDDLED_COMPRESSED: ail_initialize_twiddled(layout); ail_initialize_compression(layout); break; default: unreachable("Unsupported tiling"); } layout->size_B = ALIGN_POT(layout->size_B, AIL_CACHELINE); assert(layout->size_B > 0 && "Invalid dimensions"); }