blob: 0160a02f7d6ec6c3e3227f40acd913ff96fc715a [file] [log] [blame]
/* Copyright (c) 2015-2026 The Khronos Group Inc.
* Copyright (c) 2015-2026 Valve Corporation
* Copyright (c) 2015-2026 LunarG, Inc.
* Copyright (C) 2015-2025 Google Inc.
* Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved.
* Modifications Copyright (C) 2022 RasterGrid Kft.
*
* 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.
*/
#pragma once
#include <variant>
#include "state_tracker/device_memory_state.h"
#include "state_tracker/image_layout_map.h"
namespace vvl {
class DeviceState;
class ImageSubState;
class ImageViewSubState;
class Swapchain;
class VideoProfileDesc;
} // namespace vvl
struct DeviceExtensions;
// Transfer VkImageSubresourceRange into VkImageSubresourceLayers struct
static inline VkImageSubresourceLayers LayersFromRange(const VkImageSubresourceRange &subresource_range) {
VkImageSubresourceLayers subresource_layers;
subresource_layers.aspectMask = subresource_range.aspectMask;
subresource_layers.baseArrayLayer = subresource_range.baseArrayLayer;
subresource_layers.layerCount = subresource_range.layerCount;
subresource_layers.mipLevel = subresource_range.baseMipLevel;
return subresource_layers;
}
// Transfer VkImageSubresourceLayers into VkImageSubresourceRange struct
static inline VkImageSubresourceRange RangeFromLayers(const VkImageSubresourceLayers &subresource_layers) {
VkImageSubresourceRange subresource_range;
subresource_range.aspectMask = subresource_layers.aspectMask;
subresource_range.baseArrayLayer = subresource_layers.baseArrayLayer;
subresource_range.layerCount = subresource_layers.layerCount;
subresource_range.baseMipLevel = subresource_layers.mipLevel;
subresource_range.levelCount = 1;
return subresource_range;
}
namespace vvl {
// State for VkImage objects.
// Parent -> child relationships in the object usage tree:
// 1. Normal images:
// vvl::Image [1] -> [1] vvl::DeviceMemory
//
// 2. Sparse images:
// vvl::Image [1] -> [N] vvl::DeviceMemory
//
// 3. VK_IMAGE_CREATE_ALIAS_BIT images:
// vvl::Image [N] -> [1] vvl::DeviceMemory
// All other images using the same device memory are in the aliasing_images set.
//
// 4. Swapchain images
// vvl::Image [N] -> [1] vvl::Swapchain
// All other images using the same swapchain and swapchain_image_index are in the aliasing_images set.
// Note that the images for *every* image_index will show up as parents of the swapchain,
// so swapchain_image_index values must be compared.
//
class Image : public Bindable, public SubStateManager<ImageSubState> {
public:
const vku::safe_VkImageCreateInfo safe_create_info;
const VkImageCreateInfo &create_info;
bool shared_presentable; // True for a front-buffered swapchain image
bool layout_locked; // A front-buffered image that has been presented can never have layout transitioned
const uint64_t ahb_format; // External Android format, if provided
const VkImageSubresourceRange full_range; // The normalized ISR for all levels, layers, and aspects
const VkSwapchainKHR create_from_swapchain;
const bool owned_by_swapchain;
std::shared_ptr<vvl::Swapchain> bind_swapchain;
uint32_t swapchain_image_index;
const VkFormatFeatureFlags2 format_features;
// Need to memory requirements for each plane if image is disjoint
const bool disjoint; // True if image was created with VK_IMAGE_CREATE_DISJOINT_BIT
static constexpr int kMaxPlanes = 3;
using MemoryReqs = std::array<VkMemoryRequirements, kMaxPlanes>;
const MemoryReqs requirements;
const bool sparse_residency;
const std::vector<VkSparseImageMemoryRequirements> sparse_requirements;
VkImageFormatProperties image_format_properties = {};
#ifdef VK_USE_PLATFORM_METAL_EXT
const bool metal_image_export;
const bool metal_io_surface_export;
#endif // VK_USE_PLATFORM_METAL
const subresource_adapter::RangeEncoder subresource_encoder; // Subresource resolution encoder
const VkDevice store_device_as_workaround; // TODO REMOVE WHEN encoder can be const
// Tracks current layouts of image subresources. Can be used by multiple threads, so should be locked when in use.
// Record time validation can't use this map. Global image layout is known only during queue submit time.
// When image is aliased with another compatible image this map an its lock are shared between images.
std::shared_ptr<ImageLayoutMap> layout_map;
std::shared_ptr<std::shared_mutex> layout_map_lock;
ReadLockGuard LayoutMapReadLock() const { return ReadLockGuard(*layout_map_lock); }
WriteLockGuard LayoutMapWriteLock() { return WriteLockGuard(*layout_map_lock); }
vvl::unordered_set<std::shared_ptr<const vvl::VideoProfileDesc>> supported_video_profiles;
Image(const DeviceState &dev_data, VkImage handle, const VkImageCreateInfo *pCreateInfo, VkFormatFeatureFlags2 features);
Image(const DeviceState &dev_data, VkImage handle, const VkImageCreateInfo *pCreateInfo, VkSwapchainKHR swapchain,
uint32_t swapchain_index, VkFormatFeatureFlags2 features);
Image(Image const &rh_obj) = delete;
std::shared_ptr<const Image> shared_from_this() const { return SharedFromThisImpl(this); }
std::shared_ptr<Image> shared_from_this() { return SharedFromThisImpl(this); }
VkImage VkHandle() const { return handle_.Cast<VkImage>(); }
bool HasAHBFormat() const { return ahb_format != 0; }
bool IsCompatibleAliasing(const Image *other_image_state) const;
// returns true if this image could be using the same memory as another image
bool HasAliasFlag() const { return 0 != (create_info.flags & VK_IMAGE_CREATE_ALIAS_BIT); }
bool CanAlias() const { return HasAliasFlag() || bind_swapchain; }
bool IsCreateInfoEqual(const VkImageCreateInfo &other_create_info) const;
bool IsCreateInfoDedicatedAllocationImageAliasingCompatible(const VkImageCreateInfo &other_create_info) const;
bool IsSwapchainImage() const { return create_from_swapchain != VK_NULL_HANDLE; }
// TODO - need to understand if VkBindImageMemorySwapchainInfoKHR counts as "bound"
bool HasBeenBound() const { return (MemoryState() != nullptr) || (bind_swapchain); }
inline bool IsImageTypeEqual(const VkImageCreateInfo &other_create_info) const {
return create_info.imageType == other_create_info.imageType;
}
inline bool IsFormatEqual(const VkImageCreateInfo &other_create_info) const {
return create_info.format == other_create_info.format;
}
inline bool IsMipLevelsEqual(const VkImageCreateInfo &other_create_info) const {
return create_info.mipLevels == other_create_info.mipLevels;
}
inline bool IsUsageEqual(const VkImageCreateInfo &other_create_info) const {
return create_info.usage == other_create_info.usage;
}
inline bool IsSamplesEqual(const VkImageCreateInfo &other_create_info) const {
return create_info.samples == other_create_info.samples;
}
inline bool IsTilingEqual(const VkImageCreateInfo &other_create_info) const {
return create_info.tiling == other_create_info.tiling;
}
inline bool IsArrayLayersEqual(const VkImageCreateInfo &other_create_info) const {
return create_info.arrayLayers == other_create_info.arrayLayers;
}
inline bool IsInitialLayoutEqual(const VkImageCreateInfo &other_create_info) const {
return create_info.initialLayout == other_create_info.initialLayout;
}
inline bool IsSharingModeEqual(const VkImageCreateInfo &other_create_info) const {
return create_info.sharingMode == other_create_info.sharingMode;
}
inline bool IsExtentEqual(const VkImageCreateInfo &other_create_info) const {
return (create_info.extent.width == other_create_info.extent.width) &&
(create_info.extent.height == other_create_info.extent.height) &&
(create_info.extent.depth == other_create_info.extent.depth);
}
inline bool IsQueueFamilyIndicesEqual(const VkImageCreateInfo &other_create_info) const {
return (create_info.queueFamilyIndexCount == other_create_info.queueFamilyIndexCount) &&
(create_info.queueFamilyIndexCount == 0 ||
memcmp(create_info.pQueueFamilyIndices, other_create_info.pQueueFamilyIndices,
create_info.queueFamilyIndexCount * sizeof(create_info.pQueueFamilyIndices[0])) == 0);
}
~Image() {
if (!Destroyed()) {
Destroy();
}
}
void SetSwapchain(std::shared_ptr<vvl::Swapchain> &swapchain, uint32_t swapchain_index);
void Destroy() override;
// Returns the effective extent of the provided subresource, adjusted for mip level and array depth.
VkExtent3D GetEffectiveSubresourceExtent(const VkImageSubresourceLayers &sub) const;
VkExtent3D GetEffectiveSubresourceExtent(const VkImageSubresource &sub) const;
VkExtent3D GetEffectiveSubresourceExtent(const VkImageSubresourceRange &range) const;
std::string DescribeSubresourceLayers(const VkImageSubresourceLayers &subresource) const;
VkImageSubresourceRange NormalizeSubresourceRange(const VkImageSubresourceRange &range) const;
uint32_t NormalizeLayerCount(const VkImageSubresourceLayers &resource) const;
void SetInitialLayoutMap();
void SetImageLayout(const VkImageSubresourceRange &range, VkImageLayout layout);
// This function is only used for comparing Imported External Dedicated Memory
bool CompareCreateInfo(const Image &other) const;
template <typename UnaryPredicate>
bool AnyAliasBindingOf(const StateObject::NodeMap &bindings, const UnaryPredicate &pred) const {
for (auto &entry : bindings) {
if (entry.first.type == kVulkanObjectTypeImage) {
auto state_object = entry.second.lock();
if (state_object) {
auto other_image = static_cast<Image *>(state_object.get());
if ((other_image != this) && other_image->IsCompatibleAliasing(this)) {
if (pred(*other_image)) return true;
}
}
}
}
return false;
}
template <typename UnaryPredicate>
bool AnyImageAliasOf(const UnaryPredicate &pred) const {
// Look for another aliasing image and
// ObjectBindings() is thread safe since returns by value, and once
// the weak_ptr is successfully locked, the other image state won't
// be freed out from under us.
for (auto const &memory_state : GetBoundMemoryStates()) {
if (AnyAliasBindingOf(memory_state->ObjectBindings(), pred)) return true;
}
return false;
}
template <typename RegionType>
VkDeviceSize GetBufferSizeFromCopyImage(const RegionType &region) const;
protected:
void NotifyInvalidate(const StateObject::NodeList &invalid_nodes, bool unlink) override;
private:
// Subresource encoder need to take into account that 3d image can have a separate layout
// per slice, if supported by the implementation. This adjusts the layout range so
// layouts map can address each slice.
VkImageSubresourceRange GetSubresourceEncoderRange(const DeviceState &device_state, const VkImageSubresourceRange &full_range);
std::variant<std::monostate, BindableNoMemoryTracker, BindableLinearMemoryTracker, BindableSparseMemoryTracker,
BindableMultiplanarMemoryTracker>
tracker_;
};
class ImageSubState {
public:
explicit ImageSubState(Image &img) : base(img) {}
ImageSubState(const ImageSubState &) = delete;
ImageSubState &operator=(const ImageSubState &) = delete;
virtual ~ImageSubState() {}
virtual void Destroy() {}
virtual void NotifyInvalidate(const StateObject::NodeList &invalid_nodes, bool unlink) {}
// Called by Image::SetSwapchain when image gets associated with swapchain
virtual void SetSwapchain(vvl::Swapchain &swapchain) {}
Image &base;
};
// State for VkImageView objects.
// Parent -> child relationships in the object usage tree:
// ImageView [N] -> [1] vv::Image
class ImageView : public StateObject, public SubStateManager<ImageViewSubState> {
public:
const vku::safe_VkImageViewCreateInfo safe_create_info;
const VkImageViewCreateInfo &create_info;
std::shared_ptr<vvl::Image> image_state;
#ifdef VK_USE_PLATFORM_METAL_EXT
const bool metal_imageview_export;
#endif // VK_USE_PLATFORM_METAL_EXT
const bool is_depth_sliced;
const VkImageSubresourceRange normalized_subresource_range;
const subresource_adapter::RangeGenerator range_generator;
const VkSampleCountFlagBits samples;
// VK_NULL_HANDLE if it doesn't have one chained in the pNext at creation time
const VkSamplerYcbcrConversion sampler_conversion;
const VkFilterCubicImageViewImageFormatPropertiesEXT filter_cubic_props;
const float min_lod;
const VkFormatFeatureFlags2 format_features;
const VkImageUsageFlags inherited_usage; // from spec #resources-image-inherited-usage
ImageView(const DeviceState &device_state, const std::shared_ptr<vvl::Image> &image_state, VkImageView handle,
const VkImageViewCreateInfo *ci, VkFormatFeatureFlags2 ff,
const VkFilterCubicImageViewImageFormatPropertiesEXT &cubic_props);
ImageView(const ImageView &rh_obj) = delete;
VkImageView VkHandle() const { return handle_.Cast<VkImageView>(); }
void LinkChildNodes() override {
// Connect child node(s), which cannot safely be done in the constructor.
image_state->AddParent(this);
}
virtual ~ImageView() {
if (!Destroyed()) {
Destroy();
}
}
bool OverlapSubresource(const ImageView &compare_view) const;
void Destroy() override;
void NotifyInvalidate(const StateObject::NodeList &invalid_nodes, bool unlink) override;
uint32_t GetAttachmentLayerCount() const;
bool Invalid() const override { return Destroyed() || !image_state || image_state->Invalid(); }
static VkImageSubresourceRange NormalizeImageViewSubresourceRange(const Image &image_state,
const VkImageViewCreateInfo &image_view_ci);
// The range that defines indexing space of all possible image layouts for this image view.
// It is used by the RangeGenerator and the image layout maps.
// In the general case, it is different than the number of subresources (described by
// normalized_subresource_range), so when dealing with image layouts this function should
// always be used instead
VkImageSubresourceRange GetRangeGeneratorRange(const DeviceExtensions &extensions) const;
std::string DescribeImageUsage(const Logger& logger) const;
};
class ImageViewSubState {
public:
explicit ImageViewSubState(ImageView &view) : base(view) {}
ImageViewSubState(const ImageViewSubState &) = delete;
ImageViewSubState &operator=(const ImageViewSubState &) = delete;
virtual ~ImageViewSubState() {}
virtual void Destroy() {}
virtual void NotifyInvalidate(const StateObject::NodeList &invalid_nodes, bool unlink) {}
ImageView &base;
};
} // namespace vvl