1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2024-12-02 10:24:12 +01:00

[dxvk] Add functions to recreate images with additional usage info

Useful when we don't know in advance which usage flags are required
and don't want to set them preemptively for performance reasons.
This commit is contained in:
Philip Rebohle 2024-09-28 18:31:54 +02:00 committed by Philip Rebohle
parent 53f14e2914
commit bfbb7987b2
4 changed files with 316 additions and 24 deletions

View File

@ -1853,7 +1853,15 @@ namespace dxvk {
void DxvkContext::invalidateImage(
const Rc<DxvkImage>& image,
Rc<DxvkResourceAllocation>&& slice) {
Rc<DxvkResourceAllocation> prevAllocation = image->assignResource(std::move(slice));
invalidateImageWithUsage(image, std::move(slice), DxvkImageUsageInfo());
}
void DxvkContext::invalidateImageWithUsage(
const Rc<DxvkImage>& image,
Rc<DxvkResourceAllocation>&& slice,
const DxvkImageUsageInfo& usageInfo) {
Rc<DxvkResourceAllocation> prevAllocation = image->assignResourceWithUsage(std::move(slice), usageInfo);
m_cmd->trackResource<DxvkAccess::None>(std::move(prevAllocation));
VkImageUsageFlags usage = image->info().usage;
@ -1875,6 +1883,140 @@ namespace dxvk {
}
bool DxvkContext::ensureImageCompatibility(
const Rc<DxvkImage>& image,
const DxvkImageUsageInfo& usageInfo) {
// Check whether image usage, flags and view formats are supported.
// If any of these are false, we need to create a new image.
bool isUsageAndFormatCompatible = (image->info().usage & usageInfo.usage) == usageInfo.usage
&& (image->info().flags & usageInfo.flags) == usageInfo.flags;
for (uint32_t i = 0; i < usageInfo.viewFormatCount && isUsageAndFormatCompatible; i++)
isUsageAndFormatCompatible &= image->isViewCompatible(usageInfo.viewFormats[i]);
// Check if we need to insert a barrier and update image properties
bool isAccessAndLayoutCompatible = (image->info().stages & usageInfo.stages) == usageInfo.stages
&& (image->info().access & usageInfo.access) == usageInfo.access
&& (usageInfo.layout && image->info().layout == usageInfo.layout);
// If everything matches already, no need to do anything.
if (isUsageAndFormatCompatible && isAccessAndLayoutCompatible)
return true;
// Ensure the image is accessible and in its default layout
this->spillRenderPass(true);
this->prepareImage(image, image->getAvailableSubresources());
if (m_execBarriers.isImageDirty(image, image->getAvailableSubresources(), DxvkAccess::Write))
m_execBarriers.recordCommands(m_cmd);
if (isUsageAndFormatCompatible) {
// Emit a barrier. If used in internal passes, this function
// must be called *before* emitting dirty checks there.
VkImageLayout oldLayout = image->info().layout;
VkImageLayout newLayout = usageInfo.layout ? usageInfo.layout : oldLayout;
image->assignResourceWithUsage(image->getAllocation(), usageInfo);
m_execBarriers.accessImage(image, image->getAvailableSubresources(),
oldLayout, image->info().stages, image->info().access,
newLayout, image->info().stages, image->info().access);
m_cmd->trackResource<DxvkAccess::Write>(image);
return true;
}
// Some images have to stay in their place, we can't do much in that case.
if (!image->canRelocate()) {
Logger::err(str::format("DxvkContext: Cannot relocate image:",
"\n Current usage: 0x", std::hex, image->info().usage, ", flags: 0x", image->info().flags, ", ", std::dec, image->info().viewFormatCount, " view formats"
"\n Requested usage: 0x", std::hex, usageInfo.usage, ", flags: 0x", usageInfo.flags, ", ", std::dec, usageInfo.viewFormatCount, " view formats"));
return false;
}
// Enable mutable format bit as necessary. We do not require
// setting this explicitly so that the caller does not have
// to check image formats every time.
VkImageCreateFlags createFlags = 0u;
for (uint32_t i = 0; i < usageInfo.viewFormatCount; i++) {
if (usageInfo.viewFormats[i] != image->info().format) {
createFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
break;
}
}
// Ensure that the image can support the requested usage
VkFormatFeatureFlagBits2 required = 0u;
switch (usageInfo.usage) {
case VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT:
required |= VK_FORMAT_FEATURE_2_COLOR_ATTACHMENT_BIT;
break;
case VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT:
required |= VK_FORMAT_FEATURE_2_DEPTH_STENCIL_ATTACHMENT_BIT;
break;
case VK_IMAGE_USAGE_SAMPLED_BIT:
required |= VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_BIT;
break;
case VK_IMAGE_USAGE_STORAGE_BIT:
required |= VK_FORMAT_FEATURE_2_STORAGE_IMAGE_BIT;
break;
case VK_IMAGE_USAGE_TRANSFER_SRC_BIT:
required |= VK_FORMAT_FEATURE_2_TRANSFER_SRC_BIT;
break;
case VK_IMAGE_USAGE_TRANSFER_DST_BIT:
required |= VK_FORMAT_FEATURE_2_TRANSFER_DST_BIT;
break;
default:
break;
}
// Make sure to use the correct set of feature flags for this image
auto features = m_device->getFormatFeatures(image->info().format);
auto& supported = image->info().tiling == VK_IMAGE_TILING_OPTIMAL
? features.optimal : features.linear;
if ((supported & required) != required) {
// Check if any of the view formats support the required features
for (uint32_t i = 0; i < image->info().viewFormatCount; i++) {
auto extendedFeatures = m_device->getFormatFeatures(image->info().viewFormats[i]);
features.optimal |= extendedFeatures.optimal;
features.linear |= extendedFeatures.linear;
}
for (uint32_t i = 0; i < usageInfo.viewFormatCount; i++) {
auto extendedFeatures = m_device->getFormatFeatures(usageInfo.viewFormats[i]);
features.optimal |= extendedFeatures.optimal;
features.linear |= extendedFeatures.linear;
}
if ((supported & required) != required)
return false;
// We're good, just need to enable extended usage
createFlags |= VK_IMAGE_CREATE_EXTENDED_USAGE_BIT;
}
// Allocate new backing storage and relocate the image
auto storage = image->createResourceWithUsage(usageInfo);
DxvkRelocateImageInfo relocateInfo;
relocateInfo.image = image;
relocateInfo.storage = storage;
relocateInfo.usageInfo = usageInfo;
relocateResources(0, nullptr, 1, &relocateInfo);
return true;
}
void DxvkContext::resolveImage(
const Rc<DxvkImage>& dstImage,
const Rc<DxvkImage>& srcImage,
@ -6421,10 +6563,10 @@ namespace dxvk {
dstBarrier.subresourceRange = info.image->getAvailableSubresources();
VkImageMemoryBarrier2 srcBarrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2 };
dstBarrier.srcStageMask = info.image->info().stages;
dstBarrier.srcAccessMask = info.image->info().access;
dstBarrier.dstStageMask = info.image->info().stages;
dstBarrier.dstAccessMask = info.image->info().access;
srcBarrier.srcStageMask = info.image->info().stages;
srcBarrier.srcAccessMask = info.image->info().access;
srcBarrier.dstStageMask = info.image->info().stages;
srcBarrier.dstAccessMask = info.image->info().access;
srcBarrier.oldLayout = info.image->info().layout;
srcBarrier.newLayout = info.image->pickLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
srcBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
@ -6531,7 +6673,7 @@ namespace dxvk {
m_cmd->trackResource<DxvkAccess::Write>(info.image);
// Invalidate image and emit post-copy barrier to use the correct layout
invalidateImage(info.image, Rc<DxvkResourceAllocation>(info.storage));
invalidateImageWithUsage(info.image, Rc<DxvkResourceAllocation>(info.storage), info.usageInfo);
VkImageMemoryBarrier2 dstBarrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2 };
dstBarrier.srcStageMask = info.image->info().stages;

View File

@ -977,6 +977,35 @@ namespace dxvk {
const Rc<DxvkImage>& image,
Rc<DxvkResourceAllocation>&& slice);
/**
* \brief Invalidates image content and add usage flag
*
* Replaces the backing storage of an image.
* \warning If the image is used by another context,
* invalidating it will result in undefined behaviour.
* \param [in] buffer The buffer to invalidate
* \param [in] slice New buffer slice
* \param [in] usageInfo Added usage info
*/
void invalidateImageWithUsage(
const Rc<DxvkImage>& image,
Rc<DxvkResourceAllocation>&& slice,
const DxvkImageUsageInfo& usageInfo);
/**
* \brief Ensures that an image supports the given usage
*
* No-op if the image already supports the requested properties.
* Otherwise, this will allocate a new backing resource with the
* requested properties and copy the current contents to it.
* \param [in] image Image resource
* \param [in] usageInfo Usage info to add
* \returns \c true if the image can support the given usage
*/
bool ensureImageCompatibility(
const Rc<DxvkImage>& image,
const DxvkImageUsageInfo& usageInfo);
/**
* \brief Updates push constants
*

View File

@ -17,7 +17,7 @@ namespace dxvk {
copyFormatList(createInfo.viewFormatCount, createInfo.viewFormats);
// Determine whether the image is shareable before creating the resource
VkImageCreateInfo imageInfo = getImageCreateInfo();
VkImageCreateInfo imageInfo = getImageCreateInfo(DxvkImageUsageInfo());
m_shared = canShareImage(device, imageInfo, m_info.sharing);
assignResource(createResource());
@ -38,7 +38,7 @@ namespace dxvk {
copyFormatList(createInfo.viewFormatCount, createInfo.viewFormats);
// Create backing storage for existing image resource
VkImageCreateInfo imageInfo = getImageCreateInfo();
VkImageCreateInfo imageInfo = getImageCreateInfo(DxvkImageUsageInfo());
assignResource(m_allocator->importImageResource(imageInfo, imageHandle));
}
@ -48,6 +48,13 @@ namespace dxvk {
}
bool DxvkImage::canRelocate() const {
return !m_imageInfo.mapPtr && !m_shared
&& !m_storage->flags().test(DxvkAllocationFlag::Imported)
&& !(m_info.flags & VK_IMAGE_CREATE_SPARSE_BINDING_BIT);
}
VkSubresourceLayout DxvkImage::querySubresourceLayout(
const VkImageSubresource& subresource) const {
VkSubresourceLayout result = { };
@ -97,17 +104,36 @@ namespace dxvk {
Rc<DxvkResourceAllocation> DxvkImage::createResource() {
const DxvkFormatInfo* formatInfo = lookupFormatInfo(m_info.format);
return createResourceWithUsage(DxvkImageUsageInfo());
}
VkImageCreateInfo imageInfo = getImageCreateInfo();
Rc<DxvkResourceAllocation> DxvkImage::createResourceWithUsage(const DxvkImageUsageInfo& usageInfo) {
const DxvkFormatInfo* formatInfo = lookupFormatInfo(m_info.format);
small_vector<VkFormat, 4> localViewFormats;
VkImageCreateInfo imageInfo = getImageCreateInfo(usageInfo);
// Set up view format list so that drivers can better enable
// compression. Skip for planar formats due to validation errors.
VkImageFormatListCreateInfo formatList = { VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO };
if (!(formatInfo->aspectMask & VK_IMAGE_ASPECT_PLANE_0_BIT)) {
formatList.viewFormatCount = m_info.viewFormatCount;
formatList.pViewFormats = m_info.viewFormats;
if (usageInfo.viewFormatCount) {
for (uint32_t i = 0; i < m_viewFormats.size(); i++)
localViewFormats.push_back(m_viewFormats[i]);
for (uint32_t i = 0; i < usageInfo.viewFormatCount; i++) {
if (!isViewCompatible(usageInfo.viewFormats[i]))
localViewFormats.push_back(usageInfo.viewFormats[i]);
}
formatList.viewFormatCount = localViewFormats.size();
formatList.pViewFormats = localViewFormats.data();
} else {
formatList.viewFormatCount = m_viewFormats.size();
formatList.pViewFormats = m_viewFormats.data();
}
}
if ((m_info.flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) && formatList.viewFormatCount)
@ -142,9 +168,52 @@ namespace dxvk {
}
VkImageCreateInfo DxvkImage::getImageCreateInfo() const {
Rc<DxvkResourceAllocation> DxvkImage::assignResource(
Rc<DxvkResourceAllocation>&& resource) {
return assignResourceWithUsage(std::move(resource), DxvkImageUsageInfo());
}
Rc<DxvkResourceAllocation> DxvkImage::assignResourceWithUsage(
Rc<DxvkResourceAllocation>&& resource,
const DxvkImageUsageInfo& usageInfo) {
Rc<DxvkResourceAllocation> old = std::move(m_storage);
// Self-assignment is possible here if we
// just update the image properties
m_storage = std::move(resource);
if (m_storage != old) {
m_imageInfo = m_storage->getImageInfo();
m_version += 1u;
}
m_info.flags |= usageInfo.flags;
m_info.usage |= usageInfo.usage;
m_info.stages |= usageInfo.stages;
m_info.access |= usageInfo.access;
if (usageInfo.layout != VK_IMAGE_LAYOUT_UNDEFINED)
m_info.layout = usageInfo.layout;
for (uint32_t i = 0; i < usageInfo.viewFormatCount; i++) {
if (!isViewCompatible(usageInfo.viewFormats[i]))
m_viewFormats.push_back(usageInfo.viewFormats[i]);
}
if (!m_viewFormats.empty()) {
m_info.viewFormatCount = m_viewFormats.size();
m_info.viewFormats = m_viewFormats.data();
}
return old;
}
VkImageCreateInfo DxvkImage::getImageCreateInfo(
const DxvkImageUsageInfo& usageInfo) const {
VkImageCreateInfo info = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
info.flags = m_info.flags;
info.flags = m_info.flags | usageInfo.flags;
info.imageType = m_info.type;
info.format = m_info.format;
info.extent = m_info.extent;
@ -152,7 +221,7 @@ namespace dxvk {
info.arrayLayers = m_info.numLayers;
info.samples = m_info.sampleCount;
info.tiling = m_info.tiling;
info.usage = m_info.usage;
info.usage = m_info.usage | usageInfo.usage;
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
info.initialLayout = m_info.initialLayout;

View File

@ -70,6 +70,30 @@ namespace dxvk {
};
/**
* \brief Extra image usage info
*
* Useful when recreating an image with different usage flags.
*/
struct DxvkImageUsageInfo {
// New image flags to add.
VkImageCreateFlags flags = 0u;
// Usage flags to add to the image
VkImageUsageFlags usage = 0u;
// Stage flags to add to the image
VkPipelineStageFlags stages = 0u;
// Access flags to add to the image
VkAccessFlags access = 0u;
// New image layout. If undefined, the
// default layout will not be changed.
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
// Number of new view formats to add
uint32_t viewFormatCount = 0u;
// View formats to add to the compatibility list
const VkFormat* viewFormats = nullptr;
};
/**
* \brief Virtual image view
*
@ -458,6 +482,15 @@ namespace dxvk {
return result;
}
/**
* \brief Checks whether the image can be relocated
*
* Images that are shared, imported from a different API
* or mapped to host address space cannot be relocated.
* \returns \c true if the image can be relocated
*/
bool canRelocate() const;
/**
* \brief Queries memory layout of a subresource
*
@ -489,6 +522,17 @@ namespace dxvk {
*/
Rc<DxvkResourceAllocation> createResource();
/**
* \brief Creates image resource with extra usage
*
* Creates new backing storage with additional usage flags
* enabled. Useful to expand on usage flags after creation.
* \param [in] usage Usage flags to add
* \returns New underlying image resource
*/
Rc<DxvkResourceAllocation> createResourceWithUsage(
const DxvkImageUsageInfo& usage);
/**
* \brief Assigns backing storage to the image
*
@ -496,15 +540,20 @@ namespace dxvk {
* \param [in] resource New backing storage
* \returns Previous backing storage
*/
Rc<DxvkResourceAllocation> assignResource(Rc<DxvkResourceAllocation>&& resource) {
Rc<DxvkResourceAllocation> old = std::move(m_storage);
Rc<DxvkResourceAllocation> assignResource(
Rc<DxvkResourceAllocation>&& resource);
m_storage = std::move(resource);
m_imageInfo = m_storage->getImageInfo();
m_version += 1u;
return old;
}
/**
* \brief Assigns backing storage to the image with extra usage
*
* Implicitly invalidates all image views.
* \param [in] resource New backing storage
* \param [in] usageInfo Added usage info
* \returns Previous backing storage
*/
Rc<DxvkResourceAllocation> assignResourceWithUsage(
Rc<DxvkResourceAllocation>&& resource,
const DxvkImageUsageInfo& usageInfo);
/**
* \brief Retrieves current backing storage
@ -545,7 +594,8 @@ namespace dxvk {
std::unordered_map<DxvkImageViewKey,
DxvkImageView, DxvkHash, DxvkEq> m_views;
VkImageCreateInfo getImageCreateInfo() const;
VkImageCreateInfo getImageCreateInfo(
const DxvkImageUsageInfo& usageInfo) const;
void copyFormatList(
uint32_t formatCount,
@ -567,6 +617,8 @@ namespace dxvk {
Rc<DxvkImage> image;
/// Backing storage to copy to
Rc<DxvkResourceAllocation> storage;
/// Additional image usage
DxvkImageUsageInfo usageInfo;
};