Add validation code for texture subresource usage tracking

This patch also add validation tests for texture subresource tracking
for render pass. Resource usage tracking for compute is per each
dispatch() call, I will add it in next patch.

BUG=dawn:157

Change-Id: I6c4b932e317d66521fa428311e727876d0adf4ea
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/17661
Commit-Queue: Yunchao He <yunchao.he@intel.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
This commit is contained in:
Yunchao He 2020-05-04 17:10:49 +00:00 committed by Commit Bot service account
parent a6d7f56cf8
commit 23428ea82f
14 changed files with 272 additions and 37 deletions

View File

@ -588,12 +588,11 @@ namespace dawn_native {
cmd->colorAttachments[i].clearColor =
descriptor->colorAttachments[i].clearColor;
usageTracker.TextureUsedAs(view->GetTexture(),
wgpu::TextureUsage::OutputAttachment);
usageTracker.TextureViewUsedAs(view, wgpu::TextureUsage::OutputAttachment);
if (resolveTarget != nullptr) {
usageTracker.TextureUsedAs(resolveTarget->GetTexture(),
wgpu::TextureUsage::OutputAttachment);
usageTracker.TextureViewUsedAs(resolveTarget,
wgpu::TextureUsage::OutputAttachment);
}
}
@ -614,8 +613,7 @@ namespace dawn_native {
cmd->depthStencilAttachment.stencilStoreOp =
descriptor->depthStencilAttachment->stencilStoreOp;
usageTracker.TextureUsedAs(view->GetTexture(),
wgpu::TextureUsage::OutputAttachment);
usageTracker.TextureViewUsedAs(view, wgpu::TextureUsage::OutputAttachment);
}
cmd->width = width;

View File

@ -306,23 +306,36 @@ namespace dawn_native {
}
// Textures can only be used as single-write or multiple read.
// TODO(cwallez@chromium.org): implement per-subresource tracking
for (size_t i = 0; i < pass.textures.size(); ++i) {
const TextureBase* texture = pass.textures[i];
wgpu::TextureUsage usage = pass.textureUsages[i];
const PassTextureUsage& textureUsage = pass.textureUsages[i];
wgpu::TextureUsage usage = textureUsage.usage;
if (usage & ~texture->GetUsage()) {
return DAWN_VALIDATION_ERROR("Texture missing usage for the pass");
}
// TODO (yunchao.he@intel.com): add read/write usage tracking for compute
// The usage variable for the whole texture is a fast path for texture usage tracking.
// Because in most cases a texture (with or without subresources) is used as
// single-write or multiple read, then we can skip iterating the subresources' usages.
bool readOnly = (usage & kReadOnlyTextureUsages) == usage;
bool singleUse = wgpu::HasZeroOrOneBits(usage);
if (pass.passType == PassType::Render && !readOnly && !singleUse) {
return DAWN_VALIDATION_ERROR(
"Texture used as writable usage and another usage in render pass");
if (pass.passType != PassType::Render || readOnly || singleUse) {
continue;
}
// Inspect the subresources if the usage of the whole texture violates usage validation.
// Every single subresource can only be used as single-write or multiple read.
for (wgpu::TextureUsage subresourceUsage : textureUsage.subresourceUsages) {
bool readOnly = (subresourceUsage & kReadOnlyTextureUsages) == subresourceUsage;
bool singleUse = wgpu::HasZeroOrOneBits(subresourceUsage);
if (!readOnly && !singleUse) {
return DAWN_VALIDATION_ERROR(
"Texture used as writable usage and another usage in render pass");
}
}
}
return {};
}

View File

@ -27,6 +27,15 @@ namespace dawn_native {
enum class PassType { Render, Compute };
// Describe the usage of the whole texture and its subresources.
// subresourceUsages vector is used to track every subresource's usage within a texture.
// usage variable is used the track the whole texture even though it can be deduced from
// subresources' usages. This is designed deliberately to track texture usage in a fast path.
struct PassTextureUsage {
wgpu::TextureUsage usage;
std::vector<wgpu::TextureUsage> subresourceUsages;
};
// Which resources are used by pass and how they are used. The command buffer validation
// pre-computes this information so that backends with explicit barriers don't have to
// re-compute it.
@ -36,7 +45,7 @@ namespace dawn_native {
std::vector<wgpu::BufferUsage> bufferUsages;
std::vector<TextureBase*> textures;
std::vector<wgpu::TextureUsage> textureUsages;
std::vector<PassTextureUsage> textureUsages;
};
using PerPassUsages = std::vector<PassResourceUsage>;

View File

@ -27,10 +27,53 @@ namespace dawn_native {
mBufferUsages[buffer] |= usage;
}
void PassResourceUsageTracker::TextureUsedAs(TextureBase* texture, wgpu::TextureUsage usage) {
// std::map's operator[] will create the key and return 0 if the key didn't exist
// before.
mTextureUsages[texture] |= usage;
void PassResourceUsageTracker::TextureViewUsedAs(TextureViewBase* view,
wgpu::TextureUsage usage) {
TextureBase* texture = view->GetTexture();
uint32_t baseMipLevel = view->GetBaseMipLevel();
uint32_t levelCount = view->GetLevelCount();
uint32_t baseArrayLayer = view->GetBaseArrayLayer();
uint32_t layerCount = view->GetLayerCount();
// std::map's operator[] will create the key and return a PassTextureUsage with usage = 0
// and an empty vector for subresourceUsages.
// TODO (yunchao.he@intel.com): optimize this
PassTextureUsage& textureUsage = mTextureUsages[texture];
// Set usage for the whole texture
textureUsage.usage |= usage;
// Set usages for subresources
uint32_t subresourceCount =
texture->GetSubresourceIndex(texture->GetNumMipLevels(), texture->GetArrayLayers());
if (!textureUsage.subresourceUsages.size()) {
textureUsage.subresourceUsages =
std::vector<wgpu::TextureUsage>(subresourceCount, wgpu::TextureUsage::None);
}
for (uint32_t mipLevel = baseMipLevel; mipLevel < baseMipLevel + levelCount; ++mipLevel) {
for (uint32_t arrayLayer = baseArrayLayer; arrayLayer < baseArrayLayer + layerCount;
++arrayLayer) {
uint32_t subresourceIndex = texture->GetSubresourceIndex(mipLevel, arrayLayer);
textureUsage.subresourceUsages[subresourceIndex] |= usage;
}
}
}
void PassResourceUsageTracker::AddTextureUsage(TextureBase* texture,
const PassTextureUsage& textureUsage) {
PassTextureUsage& passTextureUsage = mTextureUsages[texture];
passTextureUsage.usage |= textureUsage.usage;
uint32_t subresourceCount =
texture->GetSubresourceIndex(texture->GetNumMipLevels(), texture->GetArrayLayers());
ASSERT(textureUsage.subresourceUsages.size() == subresourceCount);
if (!passTextureUsage.subresourceUsages.size()) {
passTextureUsage.subresourceUsages = textureUsage.subresourceUsages;
return;
}
for (uint32_t i = 0; i < subresourceCount; ++i) {
passTextureUsage.subresourceUsages[i] |= textureUsage.subresourceUsages[i];
}
}
// Returns the per-pass usage for use by backends for APIs with explicit barriers.
@ -49,7 +92,7 @@ namespace dawn_native {
for (auto& it : mTextureUsages) {
result.textures.push_back(it.first);
result.textureUsages.push_back(it.second);
result.textureUsages.push_back(std::move(it.second));
}
mBufferUsages.clear();

View File

@ -34,7 +34,8 @@ namespace dawn_native {
public:
PassResourceUsageTracker(PassType passType);
void BufferUsedAs(BufferBase* buffer, wgpu::BufferUsage usage);
void TextureUsedAs(TextureBase* texture, wgpu::TextureUsage usage);
void TextureViewUsedAs(TextureViewBase* texture, wgpu::TextureUsage usage);
void AddTextureUsage(TextureBase* texture, const PassTextureUsage& textureUsage);
// Returns the per-pass usage for use by backends for APIs with explicit barriers.
PassResourceUsage AcquireResourceUsage();
@ -42,7 +43,7 @@ namespace dawn_native {
private:
PassType mPassType;
std::map<BufferBase*, wgpu::BufferUsage> mBufferUsages;
std::map<TextureBase*, wgpu::TextureUsage> mTextureUsages;
std::map<TextureBase*, PassTextureUsage> mTextureUsages;
};
} // namespace dawn_native

View File

@ -47,9 +47,8 @@ namespace dawn_native {
}
case wgpu::BindingType::SampledTexture: {
TextureBase* texture =
group->GetBindingAsTextureView(bindingIndex)->GetTexture();
usageTracker->TextureUsedAs(texture, wgpu::TextureUsage::Sampled);
TextureViewBase* view = group->GetBindingAsTextureView(bindingIndex);
usageTracker->TextureViewUsedAs(view, wgpu::TextureUsage::Sampled);
break;
}
@ -64,16 +63,14 @@ namespace dawn_native {
break;
case wgpu::BindingType::ReadonlyStorageTexture: {
TextureBase* texture =
group->GetBindingAsTextureView(bindingIndex)->GetTexture();
usageTracker->TextureUsedAs(texture, kReadonlyStorageTexture);
TextureViewBase* view = group->GetBindingAsTextureView(bindingIndex);
usageTracker->TextureViewUsedAs(view, kReadonlyStorageTexture);
break;
}
case wgpu::BindingType::WriteonlyStorageTexture: {
TextureBase* texture =
group->GetBindingAsTextureView(bindingIndex)->GetTexture();
usageTracker->TextureUsedAs(texture, wgpu::TextureUsage::Storage);
TextureViewBase* view = group->GetBindingAsTextureView(bindingIndex);
usageTracker->TextureViewUsedAs(view, wgpu::TextureUsage::Storage);
break;
}

View File

@ -153,8 +153,9 @@ namespace dawn_native {
for (uint32_t i = 0; i < usages.buffers.size(); ++i) {
mUsageTracker.BufferUsedAs(usages.buffers[i], usages.bufferUsages[i]);
}
for (uint32_t i = 0; i < usages.textures.size(); ++i) {
mUsageTracker.TextureUsedAs(usages.textures[i], usages.textureUsages[i]);
mUsageTracker.AddTextureUsage(usages.textures[i], usages.textureUsages[i]);
}
}

View File

@ -475,7 +475,7 @@ namespace dawn_native { namespace d3d12 {
// Clear textures that are not output attachments. Output attachments will be
// cleared during record render pass if the texture subresource has not been
// initialized before the render pass.
if (!(usages.textureUsages[i] & wgpu::TextureUsage::OutputAttachment)) {
if (!(usages.textureUsages[i].usage & wgpu::TextureUsage::OutputAttachment)) {
texture->EnsureSubresourceContentInitialized(commandContext, 0,
texture->GetNumMipLevels(), 0,
texture->GetArrayLayers());
@ -488,10 +488,10 @@ namespace dawn_native { namespace d3d12 {
D3D12_RESOURCE_BARRIER barrier;
if (ToBackend(usages.textures[i])
->TrackUsageAndGetResourceBarrier(commandContext, &barrier,
usages.textureUsages[i])) {
usages.textureUsages[i].usage)) {
barriers.push_back(barrier);
}
textureUsages |= usages.textureUsages[i];
textureUsages |= usages.textureUsages[i].usage;
}
if (barriers.size()) {

View File

@ -691,7 +691,7 @@ namespace dawn_native { namespace metal {
// Clear textures that are not output attachments. Output attachments will be
// cleared in CreateMTLRenderPassDescriptor by setting the loadop to clear when the
// texture subresource has not been initialized before the render pass.
if (!(usages.textureUsages[i] & wgpu::TextureUsage::OutputAttachment)) {
if (!(usages.textureUsages[i].usage & wgpu::TextureUsage::OutputAttachment)) {
texture->EnsureSubresourceContentInitialized(0, texture->GetNumMipLevels(), 0,
texture->GetArrayLayers());
}

View File

@ -421,7 +421,7 @@ namespace dawn_native { namespace opengl {
// Clear textures that are not output attachments. Output attachments will be
// cleared in BeginRenderPass by setting the loadop to clear when the
// texture subresource has not been initialized before the render pass.
if (!(usages.textureUsages[i] & wgpu::TextureUsage::OutputAttachment)) {
if (!(usages.textureUsages[i].usage & wgpu::TextureUsage::OutputAttachment)) {
texture->EnsureSubresourceContentInitialized(0, texture->GetNumMipLevels(), 0,
texture->GetArrayLayers());
}

View File

@ -381,12 +381,12 @@ namespace dawn_native { namespace vulkan {
// Clear textures that are not output attachments. Output attachments will be
// cleared in RecordBeginRenderPass by setting the loadop to clear when the
// texture subresource has not been initialized before the render pass.
if (!(usages.textureUsages[i] & wgpu::TextureUsage::OutputAttachment)) {
if (!(usages.textureUsages[i].usage & wgpu::TextureUsage::OutputAttachment)) {
texture->EnsureSubresourceContentInitialized(recordingContext, 0,
texture->GetNumMipLevels(), 0,
texture->GetArrayLayers());
}
texture->TransitionUsageNow(recordingContext, usages.textureUsages[i]);
texture->TransitionUsageNow(recordingContext, usages.textureUsages[i].usage);
}
};
const std::vector<PassResourceUsage>& passResourceUsages = GetResourceUsages().perPass;

View File

@ -193,6 +193,7 @@ test("dawn_unittests") {
"unittests/validation/SamplerValidationTests.cpp",
"unittests/validation/ShaderModuleValidationTests.cpp",
"unittests/validation/StorageTextureValidationTests.cpp",
"unittests/validation/TextureSubresourceTests.cpp",
"unittests/validation/TextureValidationTests.cpp",
"unittests/validation/TextureViewValidationTests.cpp",
"unittests/validation/ToggleValidationTests.cpp",

View File

@ -1121,10 +1121,15 @@ namespace {
// TODO (yunchao.he@intel.com):
// * useless bindings in bind groups. For example, a bind group includes bindings for compute
// stage, but the bind group is used in render pass.
//
// * more read write tracking tests for texture which need readonly storage texture and
// writeonly storage texture support
//
// * resource write and read dependency
// 1) across passes (render + render, compute + compute, compute and render mixed) is valid
// 2) across draws/dispatches is invalid
//
// * Add tests for multiple encoders upon the same resource simultaneously. This situation fits
// some cases like VR, multi-threading, etc.
} // anonymous namespace

View File

@ -0,0 +1,167 @@
// Copyright 2020 The Dawn Authors
//
// 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 "utils/WGPUHelpers.h"
#include "tests/unittests/validation/ValidationTest.h"
namespace {
class TextureSubresourceTest : public ValidationTest {
public:
static constexpr uint32_t kSize = 32u;
static constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::RGBA8Unorm;
wgpu::Texture CreateTexture(uint32_t mipLevelCount,
uint32_t arrayLayerCount,
wgpu::TextureUsage usage) {
wgpu::TextureDescriptor texDesc;
texDesc.dimension = wgpu::TextureDimension::e2D;
texDesc.size = {kSize, kSize, 1};
texDesc.arrayLayerCount = arrayLayerCount;
texDesc.sampleCount = 1;
texDesc.mipLevelCount = mipLevelCount;
texDesc.usage = usage;
texDesc.format = kFormat;
return device.CreateTexture(&texDesc);
}
wgpu::TextureView CreateTextureView(wgpu::Texture texture,
uint32_t baseMipLevel,
uint32_t baseArrayLayer) {
wgpu::TextureViewDescriptor viewDesc;
viewDesc.format = kFormat;
viewDesc.baseArrayLayer = baseArrayLayer;
viewDesc.arrayLayerCount = 1;
viewDesc.baseMipLevel = baseMipLevel;
viewDesc.mipLevelCount = 1;
viewDesc.dimension = wgpu::TextureViewDimension::e2D;
return texture.CreateView(&viewDesc);
}
void TestRenderPass(const wgpu::TextureView& renderView,
const wgpu::TextureView& samplerView) {
// Create bind group
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Vertex, wgpu::BindingType::SampledTexture}});
utils::ComboRenderPassDescriptor renderPassDesc({renderView});
// It is valid to read from and write into different subresources of the same texture
{
wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, bgl, {{0, samplerView}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
pass.SetBindGroup(0, bindGroup);
pass.EndPass();
encoder.Finish();
}
// It is valid to has multiple read from a subresource and one single write into another
// subresource
{
wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, bgl, {{0, samplerView}});
wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout(
device,
{{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::ReadonlyStorageTexture,
false, false, wgpu::TextureViewDimension::Undefined,
wgpu::TextureViewDimension::Undefined, wgpu::TextureComponentType::Float,
kFormat}});
wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(device, bgl1, {{0, samplerView}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
pass.SetBindGroup(0, bindGroup);
pass.SetBindGroup(1, bindGroup1);
pass.EndPass();
encoder.Finish();
}
// It is invalid to read and write into the same subresources
{
wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, bgl, {{0, renderView}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
pass.SetBindGroup(0, bindGroup);
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// It is valid to write into and then read from the same level of a texture in different
// render passes
{
wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, bgl, {{0, samplerView}});
wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout(
device,
{{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::WriteonlyStorageTexture,
false, false, wgpu::TextureViewDimension::Undefined,
wgpu::TextureViewDimension::Undefined, wgpu::TextureComponentType::Float,
kFormat}});
wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(device, bgl1, {{0, samplerView}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass1 = encoder.BeginRenderPass(&renderPassDesc);
pass1.SetBindGroup(0, bindGroup1);
pass1.EndPass();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
pass.SetBindGroup(0, bindGroup);
pass.EndPass();
encoder.Finish();
}
}
};
// Test different mipmap levels
TEST_F(TextureSubresourceTest, MipmapLevelsTest) {
// Create texture with 2 mipmap levels and 1 layer
wgpu::Texture texture =
CreateTexture(2, 1,
wgpu::TextureUsage::Sampled | wgpu::TextureUsage::OutputAttachment |
wgpu::TextureUsage::Storage);
// Create two views on different mipmap levels.
wgpu::TextureView samplerView = CreateTextureView(texture, 0, 0);
wgpu::TextureView renderView = CreateTextureView(texture, 1, 0);
TestRenderPass(samplerView, renderView);
}
// Test different array layers
TEST_F(TextureSubresourceTest, ArrayLayersTest) {
// Create texture with 1 mipmap level and 2 layers
wgpu::Texture texture =
CreateTexture(1, 2,
wgpu::TextureUsage::Sampled | wgpu::TextureUsage::OutputAttachment |
wgpu::TextureUsage::Storage);
// Create two views on different layers.
wgpu::TextureView samplerView = CreateTextureView(texture, 0, 0);
wgpu::TextureView renderView = CreateTextureView(texture, 0, 1);
TestRenderPass(samplerView, renderView);
}
// TODO (yunchao.he@intel.com):
// * Add tests for compute, in which texture subresource is traced per dispatch.
//
// * Add tests for multiple encoders upon the same resource simultaneously. This situation fits
// some cases like VR, multi-threading, etc.
//
// * Add tests for conflicts between usages in two render bundles used in the same pass.
} // anonymous namespace