Implement texture subresource on Vulkan

This change implemented texture subresource on Vulkan. It added a new
function to handle barriers for texture subresource for bind groups.
It also simplified barriers which are set for texture clear and copy.

Before this patch, all barriers are done upon all mip levels and all
array layers. With this patch, barriers are done upon particular mip
level(s) and array layer(s).

We may need more texture subresource end2end tests for copy and clear
opterations. I will visit that later.

Bug: dawn:157

Change-Id: Ie2247c6315326494f2d3736334e84b2867a16c17
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/22024
Commit-Queue: Yunchao He <yunchao.he@intel.com>
Reviewed-by: Austin Eng <enga@chromium.org>
This commit is contained in:
Yunchao He 2020-05-27 00:10:33 +00:00 committed by Commit Bot service account
parent 04701f9f1e
commit 0cd6533d1a
5 changed files with 185 additions and 55 deletions

View File

@ -150,11 +150,13 @@ namespace dawn_native { namespace vulkan {
case wgpu::BindingType::ReadonlyStorageTexture:
case wgpu::BindingType::WriteonlyStorageTexture:
// TODO (yunchao.he@intel.com): Do the transition for texture's
// subresource via its view.
ToBackend(
static_cast<TextureViewBase*>(mBindings[index][bindingIndex])
->GetTexture())
->TransitionUsageNow(recordingContext,
wgpu::TextureUsage::Storage);
->TransitionFullUsage(recordingContext,
wgpu::TextureUsage::Storage);
break;
case wgpu::BindingType::StorageTexture:
@ -386,7 +388,8 @@ namespace dawn_native { namespace vulkan {
texture->GetNumMipLevels(), 0,
texture->GetArrayLayers());
}
texture->TransitionUsageNow(recordingContext, usages.textureUsages[i].usage);
texture->TransitionUsageForPass(recordingContext,
usages.textureUsages[i].subresourceUsages);
}
};
const std::vector<PassResourceUsage>& passResourceUsages = GetResourceUsages().perPass;
@ -437,7 +440,9 @@ namespace dawn_native { namespace vulkan {
ToBackend(src.buffer)
->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopySrc);
ToBackend(dst.texture)
->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst);
->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst,
subresource.mipLevel, 1, subresource.baseArrayLayer,
1);
VkBuffer srcBuffer = ToBackend(src.buffer)->GetHandle();
VkImage dstImage = ToBackend(dst.texture)->GetHandle();
@ -464,7 +469,9 @@ namespace dawn_native { namespace vulkan {
subresource.baseArrayLayer, 1);
ToBackend(src.texture)
->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopySrc);
->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopySrc,
subresource.mipLevel, 1, subresource.baseArrayLayer,
1);
ToBackend(dst.buffer)
->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopyDst);
@ -497,9 +504,11 @@ namespace dawn_native { namespace vulkan {
}
ToBackend(src.texture)
->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopySrc);
->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopySrc,
src.mipLevel, 1, src.arrayLayer, 1);
ToBackend(dst.texture)
->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst);
->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst,
dst.mipLevel, 1, dst.arrayLayer, 1);
// In some situations we cannot do texture-to-texture copies with vkCmdCopyImage
// because as Vulkan SPEC always validates image copies with the virtual size of

View File

@ -59,7 +59,7 @@ namespace dawn_native { namespace vulkan {
// Perform the necessary pipeline barriers for the texture to be used with the usage
// requested by the implementation.
CommandRecordingContext* recordingContext = device->GetPendingRecordingContext();
ToBackend(texture)->TransitionUsageNow(recordingContext, mTextureUsage);
ToBackend(texture)->TransitionFullUsage(recordingContext, mTextureUsage);
DAWN_TRY(device->SubmitPendingCommands());

View File

@ -225,6 +225,31 @@ namespace dawn_native { namespace vulkan {
return {extent.width, extent.height, extent.depth};
}
VkImageMemoryBarrier BuildMemoryBarrier(const Format& format,
const VkImage& image,
wgpu::TextureUsage lastUsage,
wgpu::TextureUsage usage,
uint32_t mipLevel,
uint32_t arrayLayer) {
VkImageMemoryBarrier barrier;
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.pNext = nullptr;
barrier.srcAccessMask = VulkanAccessFlags(lastUsage, format);
barrier.dstAccessMask = VulkanAccessFlags(usage, format);
barrier.oldLayout = VulkanImageLayout(lastUsage, format);
barrier.newLayout = VulkanImageLayout(usage, format);
barrier.image = image;
barrier.subresourceRange.aspectMask = VulkanAspectMask(format);
barrier.subresourceRange.baseMipLevel = mipLevel;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = arrayLayer;
barrier.subresourceRange.layerCount = 1;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
return barrier;
}
} // namespace
// Converts Dawn texture format to Vulkan formats.
@ -594,7 +619,7 @@ namespace dawn_native { namespace vulkan {
// Release the texture
mExternalState = ExternalState::PendingRelease;
TransitionUsageNow(device->GetPendingRecordingContext(), wgpu::TextureUsage::None);
TransitionFullUsage(device->GetPendingRecordingContext(), wgpu::TextureUsage::None);
// Queue submit to signal we are done with the texture
device->GetPendingRecordingContext()->signalSemaphores.push_back(mSignalSemaphore);
@ -644,65 +669,141 @@ namespace dawn_native { namespace vulkan {
return VulkanAspectMask(GetFormat());
}
void Texture::TransitionUsageNow(CommandRecordingContext* recordingContext,
wgpu::TextureUsage usage) {
// Avoid encoding barriers when it isn't needed.
bool lastReadOnly = (mLastUsage & kReadOnlyTextureUsages) == mLastUsage;
if (lastReadOnly && mLastUsage == usage && mLastExternalState == mExternalState) {
return;
}
const Format& format = GetFormat();
VkPipelineStageFlags srcStages = VulkanPipelineStage(mLastUsage, format);
VkPipelineStageFlags dstStages = VulkanPipelineStage(usage, format);
VkImageMemoryBarrier barrier;
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.pNext = nullptr;
barrier.srcAccessMask = VulkanAccessFlags(mLastUsage, format);
barrier.dstAccessMask = VulkanAccessFlags(usage, format);
barrier.oldLayout = VulkanImageLayout(mLastUsage, format);
barrier.newLayout = VulkanImageLayout(usage, format);
barrier.image = mHandle;
// This transitions the whole resource but assumes it is a 2D texture
ASSERT(GetDimension() == wgpu::TextureDimension::e2D);
barrier.subresourceRange.aspectMask = VulkanAspectMask(format);
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = GetNumMipLevels();
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = GetArrayLayers();
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
void Texture::TweakTransitionForExternalUsage(CommandRecordingContext* recordingContext,
std::vector<VkImageMemoryBarrier>* barriers) {
ASSERT(GetNumMipLevels() == 1 && GetArrayLayers() == 1);
ASSERT(barriers->size() <= 1);
if (mExternalState == ExternalState::PendingAcquire) {
if (!barriers->size()) {
barriers->push_back(BuildMemoryBarrier(GetFormat(), mHandle,
wgpu::TextureUsage::None,
wgpu::TextureUsage::None, 0, 0));
}
// Transfer texture from external queue to graphics queue
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR;
barrier.dstQueueFamilyIndex = ToBackend(GetDevice())->GetGraphicsQueueFamily();
(*barriers)[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR;
(*barriers)[0].dstQueueFamilyIndex = ToBackend(GetDevice())->GetGraphicsQueueFamily();
// Don't override oldLayout to leave it as VK_IMAGE_LAYOUT_UNDEFINED
// TODO(http://crbug.com/dawn/200)
mExternalState = ExternalState::Acquired;
} else if (mExternalState == ExternalState::PendingRelease) {
if (!barriers->size()) {
barriers->push_back(BuildMemoryBarrier(GetFormat(), mHandle,
wgpu::TextureUsage::None,
wgpu::TextureUsage::None, 0, 0));
}
// Transfer texture from graphics queue to external queue
barrier.srcQueueFamilyIndex = ToBackend(GetDevice())->GetGraphicsQueueFamily();
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR;
barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
(*barriers)[0].srcQueueFamilyIndex = ToBackend(GetDevice())->GetGraphicsQueueFamily();
(*barriers)[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR;
(*barriers)[0].newLayout = VK_IMAGE_LAYOUT_GENERAL;
mExternalState = ExternalState::Released;
}
// Move required semaphores into waitSemaphores
mLastExternalState = mExternalState;
recordingContext->waitSemaphores.insert(recordingContext->waitSemaphores.end(),
mWaitRequirements.begin(), mWaitRequirements.end());
mWaitRequirements.clear();
}
void Texture::TransitionFullUsage(CommandRecordingContext* recordingContext,
wgpu::TextureUsage usage) {
TransitionUsageNow(recordingContext, usage, 0, GetNumMipLevels(), 0, GetArrayLayers());
}
void Texture::TransitionUsageForPass(CommandRecordingContext* recordingContext,
const std::vector<wgpu::TextureUsage>& subresourceUsages) {
std::vector<VkImageMemoryBarrier> barriers;
const Format& format = GetFormat();
wgpu::TextureUsage allUsages = wgpu::TextureUsage::None;
wgpu::TextureUsage allLastUsages = wgpu::TextureUsage::None;
ASSERT(subresourceUsages.size() == GetSubresourceCount());
// This transitions assume it is a 2D texture
ASSERT(GetDimension() == wgpu::TextureDimension::e2D);
for (uint32_t arrayLayer = 0; arrayLayer < GetArrayLayers(); ++arrayLayer) {
for (uint32_t mipLevel = 0; mipLevel < GetNumMipLevels(); ++mipLevel) {
uint32_t index = GetSubresourceIndex(mipLevel, arrayLayer);
// Avoid encoding barriers when it isn't needed.
if (subresourceUsages[index] == wgpu::TextureUsage::None) {
continue;
}
bool lastReadOnly = (mLastSubresourceUsages[index] & kReadOnlyTextureUsages) ==
mLastSubresourceUsages[index];
if (lastReadOnly && mLastSubresourceUsages[index] == subresourceUsages[index] &&
mLastExternalState == mExternalState) {
continue;
}
barriers.push_back(
BuildMemoryBarrier(format, mHandle, mLastSubresourceUsages[index],
subresourceUsages[index], mipLevel, arrayLayer));
allUsages |= subresourceUsages[index];
allLastUsages |= mLastSubresourceUsages[index];
mLastSubresourceUsages[index] = subresourceUsages[index];
}
}
if (mExternalState != ExternalState::InternalOnly) {
TweakTransitionForExternalUsage(recordingContext, &barriers);
}
VkPipelineStageFlags srcStages = VulkanPipelineStage(allLastUsages, format);
VkPipelineStageFlags dstStages = VulkanPipelineStage(allUsages, format);
ToBackend(GetDevice())
->fn.CmdPipelineBarrier(recordingContext->commandBuffer, srcStages, dstStages, 0, 0,
nullptr, 0, nullptr, 1, &barrier);
nullptr, 0, nullptr, barriers.size(), barriers.data());
}
mLastUsage = usage;
mLastExternalState = mExternalState;
void Texture::TransitionUsageNow(CommandRecordingContext* recordingContext,
wgpu::TextureUsage usage,
uint32_t baseMipLevel,
uint32_t levelCount,
uint32_t baseArrayLayer,
uint32_t layerCount) {
std::vector<VkImageMemoryBarrier> barriers;
const Format& format = GetFormat();
wgpu::TextureUsage allLastUsages = wgpu::TextureUsage::None;
// This transitions assume it is a 2D texture
ASSERT(GetDimension() == wgpu::TextureDimension::e2D);
for (uint32_t arrayLayer = 0; arrayLayer < layerCount; ++arrayLayer) {
for (uint32_t mipLevel = 0; mipLevel < levelCount; ++mipLevel) {
uint32_t index =
GetSubresourceIndex(baseMipLevel + mipLevel, baseArrayLayer + arrayLayer);
wgpu::TextureUsage lastUsage = mLastSubresourceUsages[index];
// Avoid encoding barriers when it isn't needed.
bool lastReadOnly = (lastUsage & kReadOnlyTextureUsages) == lastUsage;
if (lastReadOnly && lastUsage == usage && mLastExternalState == mExternalState) {
return;
}
barriers.push_back(BuildMemoryBarrier(format, mHandle, lastUsage, usage,
baseMipLevel + mipLevel,
baseArrayLayer + arrayLayer));
allLastUsages |= lastUsage;
mLastSubresourceUsages[index] = usage;
}
}
if (mExternalState != ExternalState::InternalOnly) {
TweakTransitionForExternalUsage(recordingContext, &barriers);
}
VkPipelineStageFlags srcStages = VulkanPipelineStage(allLastUsages, format);
VkPipelineStageFlags dstStages = VulkanPipelineStage(usage, format);
ToBackend(GetDevice())
->fn.CmdPipelineBarrier(recordingContext->commandBuffer, srcStages, dstStages, 0, 0,
nullptr, 0, nullptr, barriers.size(), barriers.data());
}
MaybeError Texture::ClearTexture(CommandRecordingContext* recordingContext,
@ -716,7 +817,8 @@ namespace dawn_native { namespace vulkan {
uint8_t clearColor = (clearValue == TextureBase::ClearValue::Zero) ? 0 : 1;
float fClearColor = (clearValue == TextureBase::ClearValue::Zero) ? 0.f : 1.f;
TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst);
TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst, baseMipLevel, levelCount,
baseArrayLayer, layerCount);
if (GetFormat().isRenderable) {
VkImageSubresourceRange range = {};
range.aspectMask = GetVkAspectMask();

View File

@ -63,8 +63,18 @@ namespace dawn_native { namespace vulkan {
// Transitions the texture to be used as `usage`, recording any necessary barrier in
// `commands`.
// TODO(cwallez@chromium.org): coalesce barriers and do them early when possible.
void TransitionFullUsage(CommandRecordingContext* recordingContext,
wgpu::TextureUsage usage);
void TransitionUsageNow(CommandRecordingContext* recordingContext,
wgpu::TextureUsage usage);
wgpu::TextureUsage usage,
uint32_t baseMipLevel,
uint32_t levelCount,
uint32_t baseArrayLayer,
uint32_t layerCount);
void TransitionUsageForPass(CommandRecordingContext* recordingContext,
const std::vector<wgpu::TextureUsage>& subresourceUsages);
void EnsureSubresourceContentInitialized(CommandRecordingContext* recordingContext,
uint32_t baseMipLevel,
uint32_t levelCount,
@ -96,6 +106,9 @@ namespace dawn_native { namespace vulkan {
uint32_t layerCount,
TextureBase::ClearValue);
void TweakTransitionForExternalUsage(CommandRecordingContext* recordingContext,
std::vector<VkImageMemoryBarrier>* barriers);
VkImage mHandle = VK_NULL_HANDLE;
ResourceMemoryAllocation mMemoryAllocation;
VkDeviceMemory mExternalAllocation = VK_NULL_HANDLE;
@ -115,7 +128,8 @@ namespace dawn_native { namespace vulkan {
// A usage of none will make sure the texture is transitioned before its first use as
// required by the Vulkan spec.
wgpu::TextureUsage mLastUsage = wgpu::TextureUsage::None;
std::vector<wgpu::TextureUsage> mLastSubresourceUsages =
std::vector<wgpu::TextureUsage>(GetSubresourceCount(), wgpu::TextureUsage::None);
};
class TextureView final : public TextureViewBase {

View File

@ -188,7 +188,12 @@ TEST_P(TextureSubresourceTest, ArrayLayersTest) {
EXPECT_TEXTURE_RGBA8_EQ(&bottomLeft, texture, 0, kSize - 1, 1, 1, 0, 1);
}
// TODO (yunchao.he@intel.com): add tests for storage texture and sampler across miplevel or
// TODO (yunchao.he@intel.com):
// * add tests for storage texture and sampler across miplevel or
// arraylayer dimensions in the same texture
//
// * add tests for copy operation upon texture subresource if needed
//
// * add tests for clear operation upon texture subresource if needed
DAWN_INSTANTIATE_TEST(TextureSubresourceTest, MetalBackend(), OpenGLBackend());
DAWN_INSTANTIATE_TEST(TextureSubresourceTest, MetalBackend(), OpenGLBackend(), VulkanBackend());