/* * Copyright 2024 Valve Corporation * Copyright 2024 Alyssa Rosenzweig * Copyright 2022-2023 Collabora Ltd. and Red Hat Inc. * SPDX-License-Identifier: MIT */ #include "hk_sampler.h" #include "hk_device.h" #include "hk_entrypoints.h" #include "hk_physical_device.h" #include "vk_enum_to_str.h" #include "vk_format.h" #include "vk_sampler.h" #include "asahi/genxml/agx_pack.h" static inline uint32_t translate_address_mode(VkSamplerAddressMode addr_mode) { #define MODE(VK, AGX_) [VK_SAMPLER_ADDRESS_MODE_##VK] = AGX_WRAP_##AGX_ static const uint8_t translate[] = { MODE(REPEAT, REPEAT), MODE(MIRRORED_REPEAT, MIRRORED_REPEAT), MODE(CLAMP_TO_EDGE, CLAMP_TO_EDGE), MODE(CLAMP_TO_BORDER, CLAMP_TO_BORDER), MODE(MIRROR_CLAMP_TO_EDGE, MIRRORED_CLAMP_TO_EDGE), }; #undef MODE assert(addr_mode < ARRAY_SIZE(translate)); return translate[addr_mode]; } static uint32_t translate_texsamp_compare_op(VkCompareOp op) { #define OP(VK, AGX_) [VK_COMPARE_OP_##VK] = AGX_COMPARE_FUNC_##AGX_ static const uint8_t translate[] = { OP(NEVER, NEVER), OP(LESS, LESS), OP(EQUAL, EQUAL), OP(LESS_OR_EQUAL, LEQUAL), OP(GREATER, GREATER), OP(NOT_EQUAL, NOT_EQUAL), OP(GREATER_OR_EQUAL, GEQUAL), OP(ALWAYS, ALWAYS), }; #undef OP assert(op < ARRAY_SIZE(translate)); return translate[op]; } static enum agx_filter translate_filter(VkFilter filter) { static_assert((enum agx_filter)VK_FILTER_NEAREST == AGX_FILTER_NEAREST); static_assert((enum agx_filter)VK_FILTER_LINEAR == AGX_FILTER_LINEAR); return (enum agx_filter)filter; } static enum agx_mip_filter translate_mipfilter(VkSamplerMipmapMode mode) { switch (mode) { case VK_SAMPLER_MIPMAP_MODE_NEAREST: return AGX_MIP_FILTER_NEAREST; case VK_SAMPLER_MIPMAP_MODE_LINEAR: return AGX_MIP_FILTER_LINEAR; default: unreachable("Invalid filter"); } } static bool uses_border(const VkSamplerCreateInfo *info) { return info->addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER || info->addressModeV == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER || info->addressModeW == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; } static enum agx_border_colour is_border_color_custom(VkBorderColor color) { /* TODO: for now, opaque black is treated as custom due to rgba4 swizzling * issues, could be optimized though. */ switch (color) { case VK_BORDER_COLOR_INT_OPAQUE_BLACK: case VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK: case VK_BORDER_COLOR_INT_CUSTOM_EXT: case VK_BORDER_COLOR_FLOAT_CUSTOM_EXT: return true; default: return false; } } /* Translate an American VkBorderColor into a Canadian agx_border_colour */ static enum agx_border_colour translate_border_color(VkBorderColor color, bool custom_to_1) { switch (color) { case VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK: case VK_BORDER_COLOR_INT_TRANSPARENT_BLACK: return AGX_BORDER_COLOUR_TRANSPARENT_BLACK; case VK_BORDER_COLOR_INT_OPAQUE_WHITE: case VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE: return AGX_BORDER_COLOUR_OPAQUE_WHITE; default: assert(is_border_color_custom(color)); return custom_to_1 ? AGX_BORDER_COLOUR_OPAQUE_WHITE : AGX_BORDER_COLOUR_TRANSPARENT_BLACK; } } static void pack_sampler(const struct hk_physical_device *pdev, const struct VkSamplerCreateInfo *info, bool custom_to_1, struct agx_sampler_packed *out) { agx_pack(out, SAMPLER, cfg) { cfg.minimum_lod = info->minLod; cfg.maximum_lod = info->maxLod; cfg.magnify = translate_filter(info->magFilter); cfg.minify = translate_filter(info->minFilter); cfg.mip_filter = translate_mipfilter(info->mipmapMode); cfg.wrap_s = translate_address_mode(info->addressModeU); cfg.wrap_t = translate_address_mode(info->addressModeV); cfg.wrap_r = translate_address_mode(info->addressModeW); cfg.pixel_coordinates = info->unnormalizedCoordinates; cfg.seamful_cube_maps = info->flags & VK_SAMPLER_CREATE_NON_SEAMLESS_CUBE_MAP_BIT_EXT; if (info->compareEnable) { cfg.compare_func = translate_texsamp_compare_op(info->compareOp); cfg.compare_enable = true; } if (info->anisotropyEnable) { cfg.maximum_anisotropy = util_next_power_of_two(MAX2(info->maxAnisotropy, 1)); } else { cfg.maximum_anisotropy = 1; } if (uses_border(info)) { cfg.border_colour = translate_border_color(info->borderColor, custom_to_1); } } } VKAPI_ATTR VkResult VKAPI_CALL hk_CreateSampler(VkDevice device, const VkSamplerCreateInfo *info /* pCreateInfo */, const VkAllocationCallbacks *pAllocator, VkSampler *pSampler) { VK_FROM_HANDLE(hk_device, dev, device); struct hk_physical_device *pdev = hk_device_physical(dev); struct hk_sampler *sampler; VkResult result; sampler = vk_sampler_create(&dev->vk, info, pAllocator, sizeof(*sampler)); if (!sampler) return vk_error(dev, VK_ERROR_OUT_OF_HOST_MEMORY); struct agx_sampler_packed samp; pack_sampler(pdev, info, true, &samp); /* LOD bias passed in the descriptor set */ sampler->lod_bias_fp16 = _mesa_float_to_half(info->mipLodBias); result = hk_sampler_heap_add(dev, samp, &sampler->planes[sampler->plane_count].hw); if (result != VK_SUCCESS) { hk_DestroySampler(device, hk_sampler_to_handle(sampler), pAllocator); return result; } sampler->plane_count++; /* In order to support CONVERSION_SEPARATE_RECONSTRUCTION_FILTER_BIT, we * need multiple sampler planes: at minimum we will need one for luminance * (the default), and one for chroma. Each sampler plane needs its own * sampler table entry. However, sampler table entries are very rare on * G13, and each plane would burn one of those. So we make sure to allocate * only the minimum amount that we actually need (i.e., either 1 or 2), and * then just copy the last sampler plane out as far as we need to fill the * number of image planes. */ if (sampler->vk.ycbcr_conversion) { assert(!uses_border(info) && "consequence of VUID-VkSamplerCreateInfo-addressModeU-01646"); const VkFilter chroma_filter = sampler->vk.ycbcr_conversion->state.chroma_filter; if (info->magFilter != chroma_filter || info->minFilter != chroma_filter) { VkSamplerCreateInfo plane2_info = *info; plane2_info.magFilter = chroma_filter; plane2_info.minFilter = chroma_filter; pack_sampler(pdev, &plane2_info, false, &samp); result = hk_sampler_heap_add( dev, samp, &sampler->planes[sampler->plane_count].hw); if (result != VK_SUCCESS) { hk_DestroySampler(device, hk_sampler_to_handle(sampler), pAllocator); return result; } sampler->plane_count++; } } else if (uses_border(info)) { /* If the sampler uses custom border colours, we need both clamp-to-1 * and clamp-to-0 variants. We treat these as planes. */ pack_sampler(pdev, info, false, &samp); result = hk_sampler_heap_add(dev, samp, &sampler->planes[sampler->plane_count].hw); if (result != VK_SUCCESS) { hk_DestroySampler(device, hk_sampler_to_handle(sampler), pAllocator); return result; } sampler->plane_count++; /* We also need to record the border. * * If there is a border colour component mapping, we need to swizzle with * it. Otherwise, we can assume there's nothing to do. */ VkClearColorValue bc = sampler->vk.border_color_value; const VkSamplerBorderColorComponentMappingCreateInfoEXT *swiz_info = vk_find_struct_const( info->pNext, SAMPLER_BORDER_COLOR_COMPONENT_MAPPING_CREATE_INFO_EXT); if (swiz_info) { const bool is_int = vk_border_color_is_int(info->borderColor); bc = vk_swizzle_color_value(bc, swiz_info->components, is_int); } sampler->custom_border = bc; sampler->has_border = true; } *pSampler = hk_sampler_to_handle(sampler); return VK_SUCCESS; } VKAPI_ATTR void VKAPI_CALL hk_DestroySampler(VkDevice device, VkSampler _sampler, const VkAllocationCallbacks *pAllocator) { VK_FROM_HANDLE(hk_device, dev, device); VK_FROM_HANDLE(hk_sampler, sampler, _sampler); if (!sampler) return; for (uint8_t plane = 0; plane < sampler->plane_count; plane++) { hk_sampler_heap_remove(dev, sampler->planes[plane].hw); } vk_sampler_destroy(&dev->vk, pAllocator, &sampler->vk); }