blob: c10c2751f9bce79ae06fcc8c0d957e752b382c3c [file] [log] [blame]
/* Copyright (c) 2015-2025 The Khronos Group Inc.
* Copyright (c) 2015-2025 Valve Corporation
* Copyright (c) 2015-2025 LunarG, Inc.
* Copyright (C) 2015-2024 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "stateless/stateless_validation.h"
#include <vulkan/utility/vk_format_utils.h>
#include "utils/image_utils.h"
#include "utils/math_utils.h"
#include "containers/range.h"
namespace stateless {
bool Device::manual_PreCallValidateAllocateMemory(VkDevice device, const VkMemoryAllocateInfo *pAllocateInfo,
const VkAllocationCallbacks *pAllocator, VkDeviceMemory *pMemory,
const Context &context) const {
bool skip = false;
const auto &error_obj = context.error_obj;
if (!pAllocateInfo) {
return skip;
}
const Location allocate_info_loc = error_obj.location.dot(Field::pAllocateInfo);
auto chained_prio_struct = vku::FindStructInPNextChain<VkMemoryPriorityAllocateInfoEXT>(pAllocateInfo->pNext);
if (chained_prio_struct && (chained_prio_struct->priority < 0.0f || chained_prio_struct->priority > 1.0f)) {
skip |= LogError("VUID-VkMemoryPriorityAllocateInfoEXT-priority-02602", device,
allocate_info_loc.pNext(Struct::VkMemoryPriorityAllocateInfoEXT, Field::priority), "is %f",
chained_prio_struct->priority);
}
auto flags_info = vku::FindStructInPNextChain<VkMemoryAllocateFlagsInfo>(pAllocateInfo->pNext);
const VkMemoryAllocateFlags flags = flags_info ? flags_info->flags : 0;
skip |= ValidateAllocateMemoryExternal(device, *pAllocateInfo, flags, allocate_info_loc);
if (flags) {
const Location flags_loc = allocate_info_loc.pNext(Struct::VkMemoryAllocateFlagsInfo, Field::flags);
if ((flags & VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT) && !enabled_features.bufferDeviceAddressCaptureReplay) {
skip |= LogError("VUID-VkMemoryAllocateInfo-flags-03330", device, flags_loc,
"has VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT set, but "
"bufferDeviceAddressCaptureReplay feature is not enabled.");
}
if ((flags & VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT) && !enabled_features.bufferDeviceAddress) {
skip |= LogError("VUID-VkMemoryAllocateInfo-flags-03331", device, flags_loc,
"has VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT set, but bufferDeviceAddress feature is not enabled.");
}
}
return skip;
}
bool Device::ValidateDeviceImageMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements &memory_requirements,
const Location &loc) const {
bool skip = false;
const auto &create_info = *(memory_requirements.pCreateInfo);
if (vku::FindStructInPNextChain<VkImageSwapchainCreateInfoKHR>(create_info.pNext)) {
skip |= LogError("VUID-VkDeviceImageMemoryRequirements-pCreateInfo-06416", device,
loc.dot(Field::pCreateInfo).dot(Field::pNext), "chain contains VkImageSwapchainCreateInfoKHR.\n%s",
PrintPNextChain(Struct::VkImageCreateInfo, create_info.pNext).c_str());
}
if (vku::FindStructInPNextChain<VkImageDrmFormatModifierExplicitCreateInfoEXT>(create_info.pNext)) {
skip |= LogError("VUID-VkDeviceImageMemoryRequirements-pCreateInfo-06776", device,
loc.dot(Field::pCreateInfo).dot(Field::pNext),
"chain contains VkImageDrmFormatModifierExplicitCreateInfoEXT.\n%s",
PrintPNextChain(Struct::VkImageCreateInfo, create_info.pNext).c_str());
}
if (vkuFormatIsMultiplane(create_info.format) && (create_info.flags & VK_IMAGE_CREATE_DISJOINT_BIT) != 0) {
if (memory_requirements.planeAspect == VK_IMAGE_ASPECT_NONE) {
skip |= LogError("VUID-VkDeviceImageMemoryRequirements-pCreateInfo-06417", device, loc.dot(Field::planeAspect),
"is VK_IMAGE_ASPECT_NONE with a multi-planar format and disjoint flag.");
} else if ((create_info.tiling == VK_IMAGE_TILING_LINEAR || create_info.tiling == VK_IMAGE_TILING_OPTIMAL) &&
!IsOnlyOneValidPlaneAspect(create_info.format, memory_requirements.planeAspect)) {
skip |= LogError("VUID-VkDeviceImageMemoryRequirements-pCreateInfo-06419", device, loc.dot(Field::planeAspect),
"is %s but is invalid for %s.", string_VkImageAspectFlags(memory_requirements.planeAspect).c_str(),
string_VkFormat(create_info.format));
}
}
const uint64_t external_format = GetExternalFormat(memory_requirements.pCreateInfo->pNext);
if (external_format != 0) {
skip |= LogError("VUID-VkDeviceImageMemoryRequirements-pNext-06996", device, loc.dot(Field::pCreateInfo),
"pNext chain contains VkExternalFormatANDROID with externalFormat %" PRIu64 ".", external_format);
}
return skip;
}
bool Device::manual_PreCallValidateGetDeviceImageMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements *pInfo,
VkMemoryRequirements2 *pMemoryRequirements,
const Context &context) const {
bool skip = false;
const auto &error_obj = context.error_obj;
skip |= ValidateDeviceImageMemoryRequirements(device, *pInfo, error_obj.location.dot(Field::pInfo));
return skip;
}
bool Device::manual_PreCallValidateGetDeviceImageSparseMemoryRequirements(
VkDevice device, const VkDeviceImageMemoryRequirements *pInfo, uint32_t *pSparseMemoryRequirementCount,
VkSparseImageMemoryRequirements2 *pSparseMemoryRequirements, const Context &context) const {
bool skip = false;
const auto &error_obj = context.error_obj;
skip |= ValidateDeviceImageMemoryRequirements(device, *pInfo, error_obj.location.dot(Field::pInfo));
return skip;
}
bool Device::manual_PreCallValidateCmdDecompressMemoryEXT(VkCommandBuffer commandBuffer,
const VkDecompressMemoryInfoEXT* pDecompressMemoryInfoEXT,
const Context& context) const {
bool skip = false;
const auto& error_obj = context.error_obj;
if (!enabled_features.memoryDecompression) {
skip |= LogError("VUID-vkCmdDecompressMemoryEXT-memoryDecompression-11761", commandBuffer, error_obj.location,
"The memoryDecompression feature must be enabled.");
}
const auto& props = phys_dev_ext_props.memory_decompression_props;
if ((pDecompressMemoryInfoEXT->decompressionMethod & props.decompressionMethods) == 0) {
skip |= LogError("VUID-VkDecompressMemoryInfoEXT-decompressionMethod-11763", commandBuffer,
error_obj.location.dot(Field::decompressionMethod),
"(0x%" PRIx64 ") is not a supported decompression method bit (supported mask: 0x%" PRIx64 ").",
pDecompressMemoryInfoEXT->decompressionMethod, props.decompressionMethods);
}
if (pDecompressMemoryInfoEXT->decompressionMethod & VK_MEMORY_DECOMPRESSION_METHOD_GDEFLATE_1_0_BIT_EXT) {
const Location memory_info_loc = error_obj.location.dot(Field::pDecompressMemoryInfoEXT);
for (uint32_t i = 0; i < pDecompressMemoryInfoEXT->regionCount; ++i) {
const Location region_loc = memory_info_loc.dot(Field::pRegions, i);
const VkDecompressMemoryRegionEXT mem_region = pDecompressMemoryInfoEXT->pRegions[i];
if (mem_region.decompressedSize > 65536) {
skip |= LogError("VUID-VkDecompressMemoryInfoEXT-decompressionMethod-11762", commandBuffer,
region_loc.dot(Field::decompressedSize),
"(%" PRIu64 ") must be less than or equal to 65536 bytes.", mem_region.decompressedSize);
}
if (mem_region.compressedSize == 0) {
skip |= LogError("VUID-VkDecompressMemoryRegionEXT-compressedSize-11795", commandBuffer,
region_loc.dot(Field::compressedSize), "must not be zero.");
}
if (mem_region.decompressedSize == 0) {
skip |= LogError("VUID-VkDecompressMemoryRegionEXT-decompressedSize-11796", commandBuffer,
region_loc.dot(Field::decompressedSize), "must not be zero.");
}
if (!IsPointerAligned(mem_region.srcAddress, 4)) {
skip |=
LogError("VUID-VkDecompressMemoryRegionEXT-srcAddress-07685", commandBuffer, region_loc.dot(Field::srcAddress),
"(0x%" PRIx64 ") is not 4-byte aligned.", mem_region.srcAddress);
}
if (!IsPointerAligned(mem_region.dstAddress, 4)) {
skip |=
LogError("VUID-VkDecompressMemoryRegionEXT-dstAddress-07687", commandBuffer, region_loc.dot(Field::dstAddress),
"(0x%" PRIx64 ") is not 4-byte aligned.", mem_region.dstAddress);
}
const vvl::range<VkDeviceAddress> src_range{mem_region.srcAddress, mem_region.srcAddress + mem_region.compressedSize};
const vvl::range<VkDeviceAddress> dst_range{mem_region.dstAddress, mem_region.dstAddress + mem_region.decompressedSize};
if (src_range.intersects(dst_range)) {
skip |= LogError("VUID-VkDecompressMemoryRegionEXT-srcAddress-07691", commandBuffer,
region_loc.dot(Field::srcAddress), "range %s overlaps with dstAddress range %s.",
string_range_hex(src_range).c_str(), string_range_hex(dst_range).c_str());
}
}
}
if (!IsPowerOfTwo(pDecompressMemoryInfoEXT->decompressionMethod)) {
skip |= LogError("VUID-VkDecompressMemoryInfoEXT-decompressionMethod-07690", commandBuffer,
error_obj.location.dot(Field::pDecompressMemoryInfoEXT).dot(Field::decompressionMethod),
"(0x%" PRIx64 ") must have a single bit set.", pDecompressMemoryInfoEXT->decompressionMethod);
}
return skip;
}
bool Device::manual_PreCallValidateCmdDecompressMemoryIndirectCountEXT(
VkCommandBuffer commandBuffer, VkMemoryDecompressionMethodFlagsEXT decompressionMethod, VkDeviceAddress indirectCommandsAddress,
VkDeviceAddress indirectCommandsCountAddress, uint32_t maxDecompressionCount, uint32_t stride, const Context& context) const {
bool skip = false;
const auto& error_obj = context.error_obj;
if (!enabled_features.memoryDecompression) {
skip |= LogError("VUID-vkCmdDecompressMemoryIndirectCountEXT-None-07692", commandBuffer, error_obj.location,
"The memoryDecompression feature must be enabled.");
}
const auto& props = phys_dev_ext_props.memory_decompression_props;
if ((decompressionMethod & props.decompressionMethods) == 0) {
skip |= LogError("VUID-vkCmdDecompressMemoryIndirectCountEXT-decompressionMethod-11810", commandBuffer,
error_obj.location.dot(Field::decompressionMethod),
"(0x%" PRIx64 ") is not a supported decompression method bit (supported mask: 0x%" PRIx64 ").",
decompressionMethod, props.decompressionMethods);
}
if (!IsPowerOfTwo(decompressionMethod)) {
skip |= LogError("VUID-vkCmdDecompressMemoryIndirectCountEXT-decompressionMethod-07690", commandBuffer,
error_obj.location.dot(Field::decompressionMethod), "(0x%" PRIx64 ") must have a single bit set.",
decompressionMethod);
}
if (!IsPointerAligned(indirectCommandsAddress, 4)) {
skip |= LogError("VUID-vkCmdDecompressMemoryIndirectCountEXT-indirectCommandsAddress-07695", commandBuffer,
error_obj.location.dot(Field::indirectCommandsAddress), "(0x%" PRIx64 ") is not aligned to 4 bytes.",
indirectCommandsAddress);
}
if (!IsPointerAligned(indirectCommandsCountAddress, 4)) {
skip |= LogError("VUID-vkCmdDecompressMemoryIndirectCountEXT-indirectCommandsCountAddress-07698", commandBuffer,
error_obj.location.dot(Field::indirectCommandsCountAddress), "(0x%" PRIx64 ") is not aligned to 4 bytes.",
indirectCommandsCountAddress);
}
if (!IsIntegerMultipleOf(stride, 4) || stride < sizeof(VkDecompressMemoryRegionEXT)) {
skip |= LogError(
"VUID-vkCmdDecompressMemoryIndirectCountEXT-stride-11767", commandBuffer, error_obj.location.dot(Field::stride),
"(%" PRIu32
") must be a multiple of 4 and must be greater than or equal to size of VkDecompressMemoryRegionEXT (%" PRIu64 ").",
stride, static_cast<uint64_t>(sizeof(VkDecompressMemoryRegionEXT)));
}
if (maxDecompressionCount > props.maxDecompressionIndirectCount) {
skip |= LogError("VUID-vkCmdDecompressMemoryIndirectCountEXT-maxDecompressionCount-11768", commandBuffer,
error_obj.location.dot(Field::maxDecompressionCount),
"(%" PRIu32
") must be less than or equal to "
"VkPhysicalDeviceMemoryDecompressionPropertiesEXT::maxDecompressionIndirectCount (%" PRIu64 ").",
maxDecompressionCount, props.maxDecompressionIndirectCount);
}
return skip;
}
bool Device::manual_PreCallValidateQueueBindSparse(VkQueue queue, uint32_t bindInfoCount, const VkBindSparseInfo *pBindInfo,
VkFence fence, const Context &context) const {
bool skip = false;
const auto &error_obj = context.error_obj;
for (uint32_t bind_info_i = 0; bind_info_i < bindInfoCount; ++bind_info_i) {
const VkBindSparseInfo &bind_info = pBindInfo[bind_info_i];
for (uint32_t image_bind_i = 0; image_bind_i < bind_info.imageBindCount; ++image_bind_i) {
const VkSparseImageMemoryBindInfo &image_bind = bind_info.pImageBinds[image_bind_i];
for (uint32_t bind_i = 0; bind_i < image_bind.bindCount; ++bind_i) {
const VkSparseImageMemoryBind &bind = image_bind.pBinds[bind_i];
if (bind.extent.width == 0) {
const LogObjectList objlist(queue, image_bind.image);
skip |= LogError("VUID-VkSparseImageMemoryBind-extent-09388", objlist,
error_obj.location.dot(Field::pBindInfo, bind_info_i)
.dot(Field::pImageBinds, image_bind_i)
.dot(Field::pBinds, bind_i)
.dot(Field::extent)
.dot(Field::width),
"is zero.");
}
if (bind.extent.height == 0) {
const LogObjectList objlist(queue, image_bind.image);
skip |= LogError("VUID-VkSparseImageMemoryBind-extent-09389", objlist,
error_obj.location.dot(Field::pBindInfo, bind_info_i)
.dot(Field::pImageBinds, image_bind_i)
.dot(Field::pBinds, bind_i)
.dot(Field::extent)
.dot(Field::height),
"is zero.");
}
if (bind.extent.depth == 0) {
const LogObjectList objlist(queue, image_bind.image);
skip |= LogError("VUID-VkSparseImageMemoryBind-extent-09390", objlist,
error_obj.location.dot(Field::pBindInfo, bind_info_i)
.dot(Field::pImageBinds, image_bind_i)
.dot(Field::pBinds, bind_i)
.dot(Field::extent)
.dot(Field::depth),
"is zero.");
}
}
}
}
return skip;
}
bool Device::manual_PreCallValidateSetDeviceMemoryPriorityEXT(VkDevice device, VkDeviceMemory memory, float priority,
const Context &context) const {
bool skip = false;
const auto &error_obj = context.error_obj;
if (!IsBetweenInclusive(priority, 0.0F, 1.0F)) {
skip |= LogError("VUID-vkSetDeviceMemoryPriorityEXT-priority-06258", device, error_obj.location.dot(Field::priority),
"is %f.", priority);
}
return skip;
}
bool Device::manual_PreCallValidateGetDynamicRenderingTilePropertiesQCOM(VkDevice device, const VkRenderingInfo *pRenderingInfo,
VkTilePropertiesQCOM *pProperties,
const Context &context) const {
bool skip = false;
if (const auto tile_memory_size = vku::FindStructInPNextChain<VkTileMemorySizeInfoQCOM>(pRenderingInfo->pNext)) {
const auto &error_obj = context.error_obj;
skip |= ValidateTileMemorySizeInfo(*tile_memory_size, error_obj.location);
}
return skip;
}
bool Device::ValidateTileMemorySizeInfo(const VkTileMemorySizeInfoQCOM &tile_memory_size_info, const Location &loc) const {
bool skip = false;
uint64_t largest_heap_size = 0;
uint32_t heap_index = 0;
for (uint32_t i = 0; i < phys_dev_mem_props.memoryHeapCount; i++) {
if (phys_dev_mem_props.memoryHeaps[i].flags & VK_MEMORY_HEAP_TILE_MEMORY_BIT_QCOM) {
if (phys_dev_mem_props.memoryHeaps[i].size > largest_heap_size) {
largest_heap_size = phys_dev_mem_props.memoryHeaps[i].size;
heap_index = i;
}
}
}
if (tile_memory_size_info.size > largest_heap_size) {
skip |= LogError("VUID-VkTileMemorySizeInfoQCOM-size-10729", device, loc.dot(Field::size),
"(%" PRIu64
") must be less than or equal to %" PRIu64 ", found at memoryHeaps[%" PRIu32 "],"
" the largest VK_MEMORY_HEAP_TILE_MEMORY_BIT_QCOM heap.",
tile_memory_size_info.size, largest_heap_size, heap_index);
}
return skip;
}
} // namespace stateless