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::ReadonlyStorageTexture:
case wgpu::BindingType::WriteonlyStorageTexture: case wgpu::BindingType::WriteonlyStorageTexture:
// TODO (yunchao.he@intel.com): Do the transition for texture's
// subresource via its view.
ToBackend( ToBackend(
static_cast<TextureViewBase*>(mBindings[index][bindingIndex]) static_cast<TextureViewBase*>(mBindings[index][bindingIndex])
->GetTexture()) ->GetTexture())
->TransitionUsageNow(recordingContext, ->TransitionFullUsage(recordingContext,
wgpu::TextureUsage::Storage); wgpu::TextureUsage::Storage);
break; break;
case wgpu::BindingType::StorageTexture: case wgpu::BindingType::StorageTexture:
@ -386,7 +388,8 @@ namespace dawn_native { namespace vulkan {
texture->GetNumMipLevels(), 0, texture->GetNumMipLevels(), 0,
texture->GetArrayLayers()); texture->GetArrayLayers());
} }
texture->TransitionUsageNow(recordingContext, usages.textureUsages[i].usage); texture->TransitionUsageForPass(recordingContext,
usages.textureUsages[i].subresourceUsages);
} }
}; };
const std::vector<PassResourceUsage>& passResourceUsages = GetResourceUsages().perPass; const std::vector<PassResourceUsage>& passResourceUsages = GetResourceUsages().perPass;
@ -437,7 +440,9 @@ namespace dawn_native { namespace vulkan {
ToBackend(src.buffer) ToBackend(src.buffer)
->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopySrc); ->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopySrc);
ToBackend(dst.texture) 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(); VkBuffer srcBuffer = ToBackend(src.buffer)->GetHandle();
VkImage dstImage = ToBackend(dst.texture)->GetHandle(); VkImage dstImage = ToBackend(dst.texture)->GetHandle();
@ -464,7 +469,9 @@ namespace dawn_native { namespace vulkan {
subresource.baseArrayLayer, 1); subresource.baseArrayLayer, 1);
ToBackend(src.texture) ToBackend(src.texture)
->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopySrc); ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopySrc,
subresource.mipLevel, 1, subresource.baseArrayLayer,
1);
ToBackend(dst.buffer) ToBackend(dst.buffer)
->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopyDst); ->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopyDst);
@ -497,9 +504,11 @@ namespace dawn_native { namespace vulkan {
} }
ToBackend(src.texture) ToBackend(src.texture)
->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopySrc); ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopySrc,
src.mipLevel, 1, src.arrayLayer, 1);
ToBackend(dst.texture) 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 // 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 // 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 // Perform the necessary pipeline barriers for the texture to be used with the usage
// requested by the implementation. // requested by the implementation.
CommandRecordingContext* recordingContext = device->GetPendingRecordingContext(); CommandRecordingContext* recordingContext = device->GetPendingRecordingContext();
ToBackend(texture)->TransitionUsageNow(recordingContext, mTextureUsage); ToBackend(texture)->TransitionFullUsage(recordingContext, mTextureUsage);
DAWN_TRY(device->SubmitPendingCommands()); DAWN_TRY(device->SubmitPendingCommands());

View File

@ -225,6 +225,31 @@ namespace dawn_native { namespace vulkan {
return {extent.width, extent.height, extent.depth}; 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 } // namespace
// Converts Dawn texture format to Vulkan formats. // Converts Dawn texture format to Vulkan formats.
@ -594,7 +619,7 @@ namespace dawn_native { namespace vulkan {
// Release the texture // Release the texture
mExternalState = ExternalState::PendingRelease; 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 // Queue submit to signal we are done with the texture
device->GetPendingRecordingContext()->signalSemaphores.push_back(mSignalSemaphore); device->GetPendingRecordingContext()->signalSemaphores.push_back(mSignalSemaphore);
@ -644,65 +669,141 @@ namespace dawn_native { namespace vulkan {
return VulkanAspectMask(GetFormat()); return VulkanAspectMask(GetFormat());
} }
void Texture::TransitionUsageNow(CommandRecordingContext* recordingContext, void Texture::TweakTransitionForExternalUsage(CommandRecordingContext* recordingContext,
wgpu::TextureUsage usage) { std::vector<VkImageMemoryBarrier>* barriers) {
// Avoid encoding barriers when it isn't needed. ASSERT(GetNumMipLevels() == 1 && GetArrayLayers() == 1);
bool lastReadOnly = (mLastUsage & kReadOnlyTextureUsages) == mLastUsage; ASSERT(barriers->size() <= 1);
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;
if (mExternalState == ExternalState::PendingAcquire) { 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 // Transfer texture from external queue to graphics queue
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR; (*barriers)[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR;
barrier.dstQueueFamilyIndex = ToBackend(GetDevice())->GetGraphicsQueueFamily(); (*barriers)[0].dstQueueFamilyIndex = ToBackend(GetDevice())->GetGraphicsQueueFamily();
// Don't override oldLayout to leave it as VK_IMAGE_LAYOUT_UNDEFINED // Don't override oldLayout to leave it as VK_IMAGE_LAYOUT_UNDEFINED
// TODO(http://crbug.com/dawn/200) // TODO(http://crbug.com/dawn/200)
mExternalState = ExternalState::Acquired; mExternalState = ExternalState::Acquired;
} else if (mExternalState == ExternalState::PendingRelease) { } 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 // Transfer texture from graphics queue to external queue
barrier.srcQueueFamilyIndex = ToBackend(GetDevice())->GetGraphicsQueueFamily(); (*barriers)[0].srcQueueFamilyIndex = ToBackend(GetDevice())->GetGraphicsQueueFamily();
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR; (*barriers)[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR;
barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; (*barriers)[0].newLayout = VK_IMAGE_LAYOUT_GENERAL;
mExternalState = ExternalState::Released; mExternalState = ExternalState::Released;
} }
// Move required semaphores into waitSemaphores mLastExternalState = mExternalState;
recordingContext->waitSemaphores.insert(recordingContext->waitSemaphores.end(), recordingContext->waitSemaphores.insert(recordingContext->waitSemaphores.end(),
mWaitRequirements.begin(), mWaitRequirements.end()); mWaitRequirements.begin(), mWaitRequirements.end());
mWaitRequirements.clear(); 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()) ToBackend(GetDevice())
->fn.CmdPipelineBarrier(recordingContext->commandBuffer, srcStages, dstStages, 0, 0, ->fn.CmdPipelineBarrier(recordingContext->commandBuffer, srcStages, dstStages, 0, 0,
nullptr, 0, nullptr, 1, &barrier); nullptr, 0, nullptr, barriers.size(), barriers.data());
}
mLastUsage = usage; void Texture::TransitionUsageNow(CommandRecordingContext* recordingContext,
mLastExternalState = mExternalState; 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, MaybeError Texture::ClearTexture(CommandRecordingContext* recordingContext,
@ -716,7 +817,8 @@ namespace dawn_native { namespace vulkan {
uint8_t clearColor = (clearValue == TextureBase::ClearValue::Zero) ? 0 : 1; uint8_t clearColor = (clearValue == TextureBase::ClearValue::Zero) ? 0 : 1;
float fClearColor = (clearValue == TextureBase::ClearValue::Zero) ? 0.f : 1.f; 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) { if (GetFormat().isRenderable) {
VkImageSubresourceRange range = {}; VkImageSubresourceRange range = {};
range.aspectMask = GetVkAspectMask(); 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 // Transitions the texture to be used as `usage`, recording any necessary barrier in
// `commands`. // `commands`.
// TODO(cwallez@chromium.org): coalesce barriers and do them early when possible. // TODO(cwallez@chromium.org): coalesce barriers and do them early when possible.
void TransitionFullUsage(CommandRecordingContext* recordingContext,
wgpu::TextureUsage usage);
void TransitionUsageNow(CommandRecordingContext* recordingContext, 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, void EnsureSubresourceContentInitialized(CommandRecordingContext* recordingContext,
uint32_t baseMipLevel, uint32_t baseMipLevel,
uint32_t levelCount, uint32_t levelCount,
@ -96,6 +106,9 @@ namespace dawn_native { namespace vulkan {
uint32_t layerCount, uint32_t layerCount,
TextureBase::ClearValue); TextureBase::ClearValue);
void TweakTransitionForExternalUsage(CommandRecordingContext* recordingContext,
std::vector<VkImageMemoryBarrier>* barriers);
VkImage mHandle = VK_NULL_HANDLE; VkImage mHandle = VK_NULL_HANDLE;
ResourceMemoryAllocation mMemoryAllocation; ResourceMemoryAllocation mMemoryAllocation;
VkDeviceMemory mExternalAllocation = VK_NULL_HANDLE; 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 // A usage of none will make sure the texture is transitioned before its first use as
// required by the Vulkan spec. // 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 { 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); 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 // 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());