| /* SPDX-License-Identifier: GPL-2.0 OR MIT */ |
| /********************************************************** |
| * |
| * Copyright (c) 2021-2024 Broadcom. All Rights Reserved. The term |
| * “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. |
| * |
| * 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 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. |
| * |
| **********************************************************/ |
| |
| #ifndef VMW_SURFACE_CACHE_H |
| #define VMW_SURFACE_CACHE_H |
| |
| #include "device_include/svga3d_surfacedefs.h" |
| |
| #include <drm/vmwgfx_drm.h> |
| |
| #define SVGA3D_FLAGS_UPPER_32(svga3d_flags) ((svga3d_flags) >> 32) |
| #define SVGA3D_FLAGS_LOWER_32(svga3d_flags) \ |
| ((svga3d_flags) & ((uint64_t)U32_MAX)) |
| |
| static inline u32 clamped_umul32(u32 a, u32 b) |
| { |
| uint64_t tmp = (uint64_t) a*b; |
| return (tmp > (uint64_t) ((u32) -1)) ? (u32) -1 : tmp; |
| } |
| |
| /** |
| * vmw_surface_get_desc - Look up the appropriate SVGA3dSurfaceDesc for the |
| * given format. |
| */ |
| static inline const SVGA3dSurfaceDesc * |
| vmw_surface_get_desc(SVGA3dSurfaceFormat format) |
| { |
| if (format < ARRAY_SIZE(g_SVGA3dSurfaceDescs)) |
| return &g_SVGA3dSurfaceDescs[format]; |
| |
| return &g_SVGA3dSurfaceDescs[SVGA3D_FORMAT_INVALID]; |
| } |
| |
| /** |
| * vmw_surface_get_mip_size - Given a base level size and the mip level, |
| * compute the size of the mip level. |
| */ |
| static inline struct drm_vmw_size |
| vmw_surface_get_mip_size(struct drm_vmw_size base_level, u32 mip_level) |
| { |
| struct drm_vmw_size size = { |
| .width = max_t(u32, base_level.width >> mip_level, 1), |
| .height = max_t(u32, base_level.height >> mip_level, 1), |
| .depth = max_t(u32, base_level.depth >> mip_level, 1) |
| }; |
| |
| return size; |
| } |
| |
| static inline void |
| vmw_surface_get_size_in_blocks(const SVGA3dSurfaceDesc *desc, |
| const struct drm_vmw_size *pixel_size, |
| SVGA3dSize *block_size) |
| { |
| block_size->width = __KERNEL_DIV_ROUND_UP(pixel_size->width, |
| desc->blockSize.width); |
| block_size->height = __KERNEL_DIV_ROUND_UP(pixel_size->height, |
| desc->blockSize.height); |
| block_size->depth = __KERNEL_DIV_ROUND_UP(pixel_size->depth, |
| desc->blockSize.depth); |
| } |
| |
| static inline bool |
| vmw_surface_is_planar_surface(const SVGA3dSurfaceDesc *desc) |
| { |
| return (desc->blockDesc & SVGA3DBLOCKDESC_PLANAR_YUV) != 0; |
| } |
| |
| static inline u32 |
| vmw_surface_calculate_pitch(const SVGA3dSurfaceDesc *desc, |
| const struct drm_vmw_size *size) |
| { |
| u32 pitch; |
| SVGA3dSize blocks; |
| |
| vmw_surface_get_size_in_blocks(desc, size, &blocks); |
| |
| pitch = blocks.width * desc->pitchBytesPerBlock; |
| |
| return pitch; |
| } |
| |
| /** |
| * vmw_surface_get_image_buffer_size - Calculates image buffer size. |
| * |
| * Return the number of bytes of buffer space required to store one image of a |
| * surface, optionally using the specified pitch. |
| * |
| * If pitch is zero, it is assumed that rows are tightly packed. |
| * |
| * This function is overflow-safe. If the result would have overflowed, instead |
| * we return MAX_UINT32. |
| */ |
| static inline u32 |
| vmw_surface_get_image_buffer_size(const SVGA3dSurfaceDesc *desc, |
| const struct drm_vmw_size *size, |
| u32 pitch) |
| { |
| SVGA3dSize image_blocks; |
| u32 slice_size, total_size; |
| |
| vmw_surface_get_size_in_blocks(desc, size, &image_blocks); |
| |
| if (vmw_surface_is_planar_surface(desc)) { |
| total_size = clamped_umul32(image_blocks.width, |
| image_blocks.height); |
| total_size = clamped_umul32(total_size, image_blocks.depth); |
| total_size = clamped_umul32(total_size, desc->bytesPerBlock); |
| return total_size; |
| } |
| |
| if (pitch == 0) |
| pitch = vmw_surface_calculate_pitch(desc, size); |
| |
| slice_size = clamped_umul32(image_blocks.height, pitch); |
| total_size = clamped_umul32(slice_size, image_blocks.depth); |
| |
| return total_size; |
| } |
| |
| /** |
| * vmw_surface_get_serialized_size - Get the serialized size for the image. |
| */ |
| static inline u32 |
| vmw_surface_get_serialized_size(SVGA3dSurfaceFormat format, |
| struct drm_vmw_size base_level_size, |
| u32 num_mip_levels, |
| u32 num_layers) |
| { |
| const SVGA3dSurfaceDesc *desc = vmw_surface_get_desc(format); |
| u32 total_size = 0; |
| u32 mip; |
| |
| for (mip = 0; mip < num_mip_levels; mip++) { |
| struct drm_vmw_size size = |
| vmw_surface_get_mip_size(base_level_size, mip); |
| total_size += vmw_surface_get_image_buffer_size(desc, |
| &size, 0); |
| } |
| |
| return total_size * num_layers; |
| } |
| |
| /** |
| * vmw_surface_get_serialized_size_extended - Returns the number of bytes |
| * required for a surface with given parameters. Support for sample count. |
| */ |
| static inline u32 |
| vmw_surface_get_serialized_size_extended(SVGA3dSurfaceFormat format, |
| struct drm_vmw_size base_level_size, |
| u32 num_mip_levels, |
| u32 num_layers, |
| u32 num_samples) |
| { |
| uint64_t total_size = |
| vmw_surface_get_serialized_size(format, |
| base_level_size, |
| num_mip_levels, |
| num_layers); |
| total_size *= max_t(u32, 1, num_samples); |
| |
| return min_t(uint64_t, total_size, (uint64_t)U32_MAX); |
| } |
| |
| /** |
| * vmw_surface_get_pixel_offset - Compute the offset (in bytes) to a pixel |
| * in an image (or volume). |
| * |
| * @width: The image width in pixels. |
| * @height: The image height in pixels |
| */ |
| static inline u32 |
| vmw_surface_get_pixel_offset(SVGA3dSurfaceFormat format, |
| u32 width, u32 height, |
| u32 x, u32 y, u32 z) |
| { |
| const SVGA3dSurfaceDesc *desc = vmw_surface_get_desc(format); |
| const u32 bw = desc->blockSize.width, bh = desc->blockSize.height; |
| const u32 bd = desc->blockSize.depth; |
| const u32 rowstride = __KERNEL_DIV_ROUND_UP(width, bw) * |
| desc->bytesPerBlock; |
| const u32 imgstride = __KERNEL_DIV_ROUND_UP(height, bh) * rowstride; |
| const u32 offset = (z / bd * imgstride + |
| y / bh * rowstride + |
| x / bw * desc->bytesPerBlock); |
| return offset; |
| } |
| |
| static inline u32 |
| vmw_surface_get_image_offset(SVGA3dSurfaceFormat format, |
| struct drm_vmw_size baseLevelSize, |
| u32 numMipLevels, |
| u32 face, |
| u32 mip) |
| |
| { |
| u32 offset; |
| u32 mipChainBytes; |
| u32 mipChainBytesToLevel; |
| u32 i; |
| const SVGA3dSurfaceDesc *desc; |
| struct drm_vmw_size mipSize; |
| u32 bytes; |
| |
| desc = vmw_surface_get_desc(format); |
| |
| mipChainBytes = 0; |
| mipChainBytesToLevel = 0; |
| for (i = 0; i < numMipLevels; i++) { |
| mipSize = vmw_surface_get_mip_size(baseLevelSize, i); |
| bytes = vmw_surface_get_image_buffer_size(desc, &mipSize, 0); |
| mipChainBytes += bytes; |
| if (i < mip) |
| mipChainBytesToLevel += bytes; |
| } |
| |
| offset = mipChainBytes * face + mipChainBytesToLevel; |
| |
| return offset; |
| } |
| |
| |
| /** |
| * vmw_surface_is_gb_screen_target_format - Is the specified format usable as |
| * a ScreenTarget? |
| * (with just the GBObjects cap-bit |
| * set) |
| * @format: format to queried |
| * |
| * RETURNS: |
| * true if queried format is valid for screen targets |
| */ |
| static inline bool |
| vmw_surface_is_gb_screen_target_format(SVGA3dSurfaceFormat format) |
| { |
| return (format == SVGA3D_X8R8G8B8 || |
| format == SVGA3D_A8R8G8B8 || |
| format == SVGA3D_R5G6B5 || |
| format == SVGA3D_X1R5G5B5 || |
| format == SVGA3D_A1R5G5B5 || |
| format == SVGA3D_P8); |
| } |
| |
| |
| /** |
| * vmw_surface_is_dx_screen_target_format - Is the specified format usable as |
| * a ScreenTarget? |
| * (with DX10 enabled) |
| * |
| * @format: format to queried |
| * |
| * Results: |
| * true if queried format is valid for screen targets |
| */ |
| static inline bool |
| vmw_surface_is_dx_screen_target_format(SVGA3dSurfaceFormat format) |
| { |
| return (format == SVGA3D_R8G8B8A8_UNORM || |
| format == SVGA3D_B8G8R8A8_UNORM || |
| format == SVGA3D_B8G8R8X8_UNORM); |
| } |
| |
| |
| /** |
| * vmw_surface_is_screen_target_format - Is the specified format usable as a |
| * ScreenTarget? |
| * (for some combination of caps) |
| * |
| * @format: format to queried |
| * |
| * Results: |
| * true if queried format is valid for screen targets |
| */ |
| static inline bool |
| vmw_surface_is_screen_target_format(SVGA3dSurfaceFormat format) |
| { |
| if (vmw_surface_is_gb_screen_target_format(format)) { |
| return true; |
| } |
| return vmw_surface_is_dx_screen_target_format(format); |
| } |
| |
| /** |
| * struct vmw_surface_mip - Mimpmap level information |
| * @bytes: Bytes required in the backing store of this mipmap level. |
| * @img_stride: Byte stride per image. |
| * @row_stride: Byte stride per block row. |
| * @size: The size of the mipmap. |
| */ |
| struct vmw_surface_mip { |
| size_t bytes; |
| size_t img_stride; |
| size_t row_stride; |
| struct drm_vmw_size size; |
| |
| }; |
| |
| /** |
| * struct vmw_surface_cache - Cached surface information |
| * @desc: Pointer to the surface descriptor |
| * @mip: Array of mipmap level information. Valid size is @num_mip_levels. |
| * @mip_chain_bytes: Bytes required in the backing store for the whole chain |
| * of mip levels. |
| * @sheet_bytes: Bytes required in the backing store for a sheet |
| * representing a single sample. |
| * @num_mip_levels: Valid size of the @mip array. Number of mipmap levels in |
| * a chain. |
| * @num_layers: Number of slices in an array texture or number of faces in |
| * a cubemap texture. |
| */ |
| struct vmw_surface_cache { |
| const SVGA3dSurfaceDesc *desc; |
| struct vmw_surface_mip mip[DRM_VMW_MAX_MIP_LEVELS]; |
| size_t mip_chain_bytes; |
| size_t sheet_bytes; |
| u32 num_mip_levels; |
| u32 num_layers; |
| }; |
| |
| /** |
| * struct vmw_surface_loc - Surface location |
| * @sheet: The multisample sheet. |
| * @sub_resource: Surface subresource. Defined as layer * num_mip_levels + |
| * mip_level. |
| * @x: X coordinate. |
| * @y: Y coordinate. |
| * @z: Z coordinate. |
| */ |
| struct vmw_surface_loc { |
| u32 sheet; |
| u32 sub_resource; |
| u32 x, y, z; |
| }; |
| |
| /** |
| * vmw_surface_subres - Compute the subresource from layer and mipmap. |
| * @cache: Surface layout data. |
| * @mip_level: The mipmap level. |
| * @layer: The surface layer (face or array slice). |
| * |
| * Return: The subresource. |
| */ |
| static inline u32 vmw_surface_subres(const struct vmw_surface_cache *cache, |
| u32 mip_level, u32 layer) |
| { |
| return cache->num_mip_levels * layer + mip_level; |
| } |
| |
| /** |
| * vmw_surface_setup_cache - Build a surface cache entry |
| * @size: The surface base level dimensions. |
| * @format: The surface format. |
| * @num_mip_levels: Number of mipmap levels. |
| * @num_layers: Number of layers. |
| * @cache: Pointer to a struct vmw_surface_cach object to be filled in. |
| * |
| * Return: Zero on success, -EINVAL on invalid surface layout. |
| */ |
| static inline int vmw_surface_setup_cache(const struct drm_vmw_size *size, |
| SVGA3dSurfaceFormat format, |
| u32 num_mip_levels, |
| u32 num_layers, |
| u32 num_samples, |
| struct vmw_surface_cache *cache) |
| { |
| const SVGA3dSurfaceDesc *desc; |
| u32 i; |
| |
| memset(cache, 0, sizeof(*cache)); |
| cache->desc = desc = vmw_surface_get_desc(format); |
| cache->num_mip_levels = num_mip_levels; |
| cache->num_layers = num_layers; |
| for (i = 0; i < cache->num_mip_levels; i++) { |
| struct vmw_surface_mip *mip = &cache->mip[i]; |
| |
| mip->size = vmw_surface_get_mip_size(*size, i); |
| mip->bytes = vmw_surface_get_image_buffer_size |
| (desc, &mip->size, 0); |
| mip->row_stride = |
| __KERNEL_DIV_ROUND_UP(mip->size.width, |
| desc->blockSize.width) * |
| desc->bytesPerBlock * num_samples; |
| if (!mip->row_stride) |
| goto invalid_dim; |
| |
| mip->img_stride = |
| __KERNEL_DIV_ROUND_UP(mip->size.height, |
| desc->blockSize.height) * |
| mip->row_stride; |
| if (!mip->img_stride) |
| goto invalid_dim; |
| |
| cache->mip_chain_bytes += mip->bytes; |
| } |
| cache->sheet_bytes = cache->mip_chain_bytes * num_layers; |
| if (!cache->sheet_bytes) |
| goto invalid_dim; |
| |
| return 0; |
| |
| invalid_dim: |
| VMW_DEBUG_USER("Invalid surface layout for dirty tracking.\n"); |
| return -EINVAL; |
| } |
| |
| /** |
| * vmw_surface_get_loc - Get a surface location from an offset into the |
| * backing store |
| * @cache: Surface layout data. |
| * @loc: Pointer to a struct vmw_surface_loc to be filled in. |
| * @offset: Offset into the surface backing store. |
| */ |
| static inline void |
| vmw_surface_get_loc(const struct vmw_surface_cache *cache, |
| struct vmw_surface_loc *loc, |
| size_t offset) |
| { |
| const struct vmw_surface_mip *mip = &cache->mip[0]; |
| const SVGA3dSurfaceDesc *desc = cache->desc; |
| u32 layer; |
| int i; |
| |
| loc->sheet = offset / cache->sheet_bytes; |
| offset -= loc->sheet * cache->sheet_bytes; |
| |
| layer = offset / cache->mip_chain_bytes; |
| offset -= layer * cache->mip_chain_bytes; |
| for (i = 0; i < cache->num_mip_levels; ++i, ++mip) { |
| if (mip->bytes > offset) |
| break; |
| offset -= mip->bytes; |
| } |
| |
| loc->sub_resource = vmw_surface_subres(cache, i, layer); |
| loc->z = offset / mip->img_stride; |
| offset -= loc->z * mip->img_stride; |
| loc->z *= desc->blockSize.depth; |
| loc->y = offset / mip->row_stride; |
| offset -= loc->y * mip->row_stride; |
| loc->y *= desc->blockSize.height; |
| loc->x = offset / desc->bytesPerBlock; |
| loc->x *= desc->blockSize.width; |
| } |
| |
| /** |
| * vmw_surface_inc_loc - Clamp increment a surface location with one block |
| * size |
| * in each dimension. |
| * @loc: Pointer to a struct vmw_surface_loc to be incremented. |
| * |
| * When computing the size of a range as size = end - start, the range does not |
| * include the end element. However a location representing the last byte |
| * of a touched region in the backing store *is* included in the range. |
| * This function modifies such a location to match the end definition |
| * given as start + size which is the one used in a SVGA3dBox. |
| */ |
| static inline void |
| vmw_surface_inc_loc(const struct vmw_surface_cache *cache, |
| struct vmw_surface_loc *loc) |
| { |
| const SVGA3dSurfaceDesc *desc = cache->desc; |
| u32 mip = loc->sub_resource % cache->num_mip_levels; |
| const struct drm_vmw_size *size = &cache->mip[mip].size; |
| |
| loc->sub_resource++; |
| loc->x += desc->blockSize.width; |
| if (loc->x > size->width) |
| loc->x = size->width; |
| loc->y += desc->blockSize.height; |
| if (loc->y > size->height) |
| loc->y = size->height; |
| loc->z += desc->blockSize.depth; |
| if (loc->z > size->depth) |
| loc->z = size->depth; |
| } |
| |
| /** |
| * vmw_surface_min_loc - The start location in a subresource |
| * @cache: Surface layout data. |
| * @sub_resource: The subresource. |
| * @loc: Pointer to a struct vmw_surface_loc to be filled in. |
| */ |
| static inline void |
| vmw_surface_min_loc(const struct vmw_surface_cache *cache, |
| u32 sub_resource, |
| struct vmw_surface_loc *loc) |
| { |
| loc->sheet = 0; |
| loc->sub_resource = sub_resource; |
| loc->x = loc->y = loc->z = 0; |
| } |
| |
| /** |
| * vmw_surface_min_loc - The end location in a subresource |
| * @cache: Surface layout data. |
| * @sub_resource: The subresource. |
| * @loc: Pointer to a struct vmw_surface_loc to be filled in. |
| * |
| * Following the end definition given in vmw_surface_inc_loc(), |
| * Compute the end location of a surface subresource. |
| */ |
| static inline void |
| vmw_surface_max_loc(const struct vmw_surface_cache *cache, |
| u32 sub_resource, |
| struct vmw_surface_loc *loc) |
| { |
| const struct drm_vmw_size *size; |
| u32 mip; |
| |
| loc->sheet = 0; |
| loc->sub_resource = sub_resource + 1; |
| mip = sub_resource % cache->num_mip_levels; |
| size = &cache->mip[mip].size; |
| loc->x = size->width; |
| loc->y = size->height; |
| loc->z = size->depth; |
| } |
| |
| |
| #endif /* VMW_SURFACE_CACHE_H */ |