Freeze texture transitions while attached; consolidate OutputAttachment usage (#67)

* lock usages for attachments during subpasses
* refactor IsTextureTransitionPossible
* change attachment usages to OutputAttachment
* make SetBindGroup validation lazier
This commit is contained in:
Kai Ninomiya 2017-07-07 16:06:14 -07:00 committed by GitHub
parent 794d4faece
commit cb2d6d8553
9 changed files with 90 additions and 59 deletions

View File

@ -59,8 +59,8 @@ void initTextures() {
.SetExtent(640, 480, 1) .SetExtent(640, 480, 1)
.SetFormat(nxt::TextureFormat::R8G8B8A8Unorm) .SetFormat(nxt::TextureFormat::R8G8B8A8Unorm)
.SetMipLevels(1) .SetMipLevels(1)
.SetAllowedUsage(nxt::TextureUsageBit::ColorAttachment | nxt::TextureUsageBit::Sampled) .SetAllowedUsage(nxt::TextureUsageBit::OutputAttachment | nxt::TextureUsageBit::Sampled)
.SetInitialUsage(nxt::TextureUsageBit::ColorAttachment) .SetInitialUsage(nxt::TextureUsageBit::OutputAttachment)
.GetResult(); .GetResult();
renderTargetView = renderTarget.CreateTextureViewBuilder().GetResult(); renderTargetView = renderTarget.CreateTextureViewBuilder().GetResult();
@ -185,20 +185,18 @@ void frame() {
static const uint32_t vertexBufferOffsets[1] = {0}; static const uint32_t vertexBufferOffsets[1] = {0};
nxt::CommandBuffer commands = device.CreateCommandBufferBuilder() nxt::CommandBuffer commands = device.CreateCommandBufferBuilder()
.BeginRenderPass(renderpass, framebuffer) .BeginRenderPass(renderpass, framebuffer)
// renderTarget is not transitioned here because it's implicit in
// BeginRenderPass or AdvanceSubpass.
.BeginRenderSubpass() .BeginRenderSubpass()
// renderTarget implicitly locked to to Attachment usage (if not already frozen)
.SetPipeline(pipeline) .SetPipeline(pipeline)
.SetVertexBuffers(0, 1, &vertexBuffer, vertexBufferOffsets) .SetVertexBuffers(0, 1, &vertexBuffer, vertexBufferOffsets)
.DrawArrays(3, 1, 0, 0) .DrawArrays(3, 1, 0, 0)
.EndRenderSubpass() .EndRenderSubpass()
// renderTarget must be transitioned here because it's Sampled, not // renderTarget usage unlocked, but left in Attachment usage
// ColorAttachment or InputAttachment.
.TransitionTextureUsage(renderTarget, nxt::TextureUsageBit::Sampled)
.BeginRenderSubpass() .BeginRenderSubpass()
.SetPipeline(pipelinePost) .SetPipeline(pipelinePost)
.SetBindGroup(0, bindGroup)
.SetVertexBuffers(0, 1, &vertexBufferQuad, vertexBufferOffsets) .SetVertexBuffers(0, 1, &vertexBufferQuad, vertexBufferOffsets)
.TransitionTextureUsage(renderTarget, nxt::TextureUsageBit::Sampled)
.SetBindGroup(0, bindGroup)
.DrawArrays(6, 1, 0, 0) .DrawArrays(6, 1, 0, 0)
.EndRenderSubpass() .EndRenderSubpass()
.EndRenderPass() .EndRenderPass()

View File

@ -921,8 +921,7 @@
{"value": 2, "name": "transfer dst"}, {"value": 2, "name": "transfer dst"},
{"value": 4, "name": "sampled"}, {"value": 4, "name": "sampled"},
{"value": 8, "name": "storage"}, {"value": 8, "name": "storage"},
{"value": 16, "name": "color attachment"}, {"value": 16, "name": "output attachment"}
{"value": 32, "name": "depth stencil attachment"}
] ]
}, },
"texture view": { "texture view": {

View File

@ -74,7 +74,7 @@ namespace backend {
} }
// Compute the lazily computed aspects // Compute the lazily computed aspects
if (!RecomputeHaveAspectBindGroups()) { if (!RecomputeHaveAspectBindGroups()) {
builder->HandleError("Some bind groups are not set"); builder->HandleError("Bind group state not valid");
return false; return false;
} }
return true; return true;
@ -148,15 +148,11 @@ namespace backend {
} }
auto* texture = tv->GetTexture(); auto* texture = tv->GetTexture();
if (texture->HasFrozenUsage(nxt::TextureUsageBit::ColorAttachment)) { if (!EnsureTextureUsage(texture, nxt::TextureUsageBit::OutputAttachment)) {
continue; builder->HandleError("Unable to ensure texture has OutputAttachment usage");
}
if (!texture->IsTransitionPossible(nxt::TextureUsageBit::ColorAttachment)) {
builder->HandleError("Can't transition attachment to ColorAttachment usage");
return false; return false;
} }
mostRecentTextureUsages[texture] = nxt::TextureUsageBit::ColorAttachment; texturesAttached.insert(texture);
texturesTransitioned.insert(texture);
} }
aspects.set(VALIDATION_ASPECT_RENDER_SUBPASS); aspects.set(VALIDATION_ASPECT_RENDER_SUBPASS);
@ -186,9 +182,9 @@ namespace backend {
if (texture->IsFrozen()) { if (texture->IsFrozen()) {
continue; continue;
} }
mostRecentTextureUsages[texture] = nxt::TextureUsageBit::None;
} }
// Everything in texturesAttached should be for the current render subpass.
texturesAttached.clear();
currentSubpass += 1; currentSubpass += 1;
aspects.reset(VALIDATION_ASPECT_RENDER_SUBPASS); aspects.reset(VALIDATION_ASPECT_RENDER_SUBPASS);
@ -274,14 +270,11 @@ namespace backend {
} }
bool CommandBufferStateTracker::SetBindGroup(uint32_t index, BindGroupBase* bindgroup) { bool CommandBufferStateTracker::SetBindGroup(uint32_t index, BindGroupBase* bindgroup) {
if (bindgroup->GetLayout() != lastPipeline->GetLayout()->GetBindGroupLayout(index)) {
builder->HandleError("Bind group layout mismatch");
return false;
}
if (!ValidateBindGroupUsages(bindgroup)) { if (!ValidateBindGroupUsages(bindgroup)) {
return false; return false;
} }
bindgroupsSet.set(index); bindgroupsSet.set(index);
bindgroups[index] = bindgroup;
return true; return true;
} }
@ -336,11 +329,13 @@ namespace backend {
} }
bool CommandBufferStateTracker::TransitionTextureUsage(TextureBase* texture, nxt::TextureUsageBit usage) { bool CommandBufferStateTracker::TransitionTextureUsage(TextureBase* texture, nxt::TextureUsageBit usage) {
if (!IsTextureTransitionPossible(texture, usage)) { if (!IsExplicitTextureTransitionPossible(texture, usage)) {
if (texture->IsFrozen()) { if (texture->IsFrozen()) {
builder->HandleError("Texture transition not possible (usage is frozen)"); builder->HandleError("Texture transition not possible (usage is frozen)");
} else if (!TextureBase::IsUsagePossible(texture->GetAllowedUsage(), usage)) { } else if (!TextureBase::IsUsagePossible(texture->GetAllowedUsage(), usage)) {
builder->HandleError("Texture transition not possible (usage not allowed)"); builder->HandleError("Texture transition not possible (usage not allowed)");
} else if (texturesAttached.find(texture) != texturesAttached.end()) {
builder->HandleError("Texture transition not possible (texture is in use as a framebuffer attachment)");
} else { } else {
builder->HandleError("Texture transition not possible"); builder->HandleError("Texture transition not possible");
} }
@ -352,6 +347,18 @@ namespace backend {
return true; return true;
} }
bool CommandBufferStateTracker::EnsureTextureUsage(TextureBase* texture, nxt::TextureUsageBit usage) {
if (texture->HasFrozenUsage(nxt::TextureUsageBit::OutputAttachment)) {
return true;
}
if (!IsInternalTextureTransitionPossible(texture, nxt::TextureUsageBit::OutputAttachment)) {
return false;
}
mostRecentTextureUsages[texture] = nxt::TextureUsageBit::OutputAttachment;
texturesTransitioned.insert(texture);
return true;
}
bool CommandBufferStateTracker::BufferHasGuaranteedUsageBit(BufferBase* buffer, nxt::BufferUsageBit usage) const { bool CommandBufferStateTracker::BufferHasGuaranteedUsageBit(BufferBase* buffer, nxt::BufferUsageBit usage) const {
ASSERT(usage != nxt::BufferUsageBit::None && nxt::HasZeroOrOneBits(usage)); ASSERT(usage != nxt::BufferUsageBit::None && nxt::HasZeroOrOneBits(usage));
if (buffer->HasFrozenUsage(usage)) { if (buffer->HasFrozenUsage(usage)) {
@ -370,38 +377,50 @@ namespace backend {
return it != mostRecentTextureUsages.end() && (it->second & usage); return it != mostRecentTextureUsages.end() && (it->second & usage);
}; };
bool CommandBufferStateTracker::IsTextureTransitionPossible(TextureBase* texture, nxt::TextureUsageBit usage) const { bool CommandBufferStateTracker::IsInternalTextureTransitionPossible(TextureBase* texture, nxt::TextureUsageBit usage) const {
const nxt::TextureUsageBit attachmentUsages =
nxt::TextureUsageBit::ColorAttachment |
nxt::TextureUsageBit::DepthStencilAttachment;
ASSERT(usage != nxt::TextureUsageBit::None && nxt::HasZeroOrOneBits(usage)); ASSERT(usage != nxt::TextureUsageBit::None && nxt::HasZeroOrOneBits(usage));
if (usage & attachmentUsages) { if (texturesAttached.find(texture) != texturesAttached.end()) {
return false; return false;
} }
auto it = mostRecentTextureUsages.find(texture);
if (it != mostRecentTextureUsages.end()) {
if (it->second & attachmentUsages) {
return false;
}
}
return texture->IsTransitionPossible(usage); return texture->IsTransitionPossible(usage);
}; };
bool CommandBufferStateTracker::IsExplicitTextureTransitionPossible(TextureBase* texture, nxt::TextureUsageBit usage) const {
const nxt::TextureUsageBit attachmentUsages =
nxt::TextureUsageBit::OutputAttachment;
if (usage & attachmentUsages) {
return false;
}
return IsInternalTextureTransitionPossible(texture, usage);
}
bool CommandBufferStateTracker::RecomputeHaveAspectBindGroups() { bool CommandBufferStateTracker::RecomputeHaveAspectBindGroups() {
if (aspects[VALIDATION_ASPECT_BIND_GROUPS]) { if (aspects[VALIDATION_ASPECT_BIND_GROUPS]) {
return true; return true;
} }
if (bindgroupsSet.all()) { // Assumes we have a pipeline already
aspects.set(VALIDATION_ASPECT_BIND_GROUPS); if (!bindgroupsSet.all()) {
return true; printf("%s:%d\n", __FUNCTION__, __LINE__);
return false;
} }
return false; for (size_t i = 0; i < bindgroups.size(); ++i) {
if (auto* bindgroup = bindgroups[i]) {
// TODO(kainino@chromium.org): bind group compatibility
if (bindgroup->GetLayout() != lastPipeline->GetLayout()->GetBindGroupLayout(i)) {
printf("%s:%d: i=%zu\n", __FUNCTION__, __LINE__, i);
return false;
}
}
}
aspects.set(VALIDATION_ASPECT_BIND_GROUPS);
return true;
} }
bool CommandBufferStateTracker::RecomputeHaveAspectVertexBuffers() { bool CommandBufferStateTracker::RecomputeHaveAspectVertexBuffers() {
if (aspects[VALIDATION_ASPECT_VERTEX_BUFFERS]) { if (aspects[VALIDATION_ASPECT_VERTEX_BUFFERS]) {
return true; return true;
} }
// Assumes we have a pipeline already
auto requiredInputs = lastPipeline->GetInputState()->GetInputsSetMask(); auto requiredInputs = lastPipeline->GetInputState()->GetInputsSetMask();
if ((inputsSet & ~requiredInputs).none()) { if ((inputsSet & ~requiredInputs).none()) {
aspects.set(VALIDATION_ASPECT_VERTEX_BUFFERS); aspects.set(VALIDATION_ASPECT_VERTEX_BUFFERS);
@ -476,7 +495,7 @@ namespace backend {
} }
// Compute the lazily computed aspects // Compute the lazily computed aspects
if (!RecomputeHaveAspectBindGroups()) { if (!RecomputeHaveAspectBindGroups()) {
builder->HandleError("Some bind groups are not set"); builder->HandleError("Bind group state not valid");
return false; return false;
} }
if (!RecomputeHaveAspectVertexBuffers()) { if (!RecomputeHaveAspectVertexBuffers()) {
@ -494,5 +513,6 @@ namespace backend {
1 << VALIDATION_ASPECT_VERTEX_BUFFERS | 1 << VALIDATION_ASPECT_VERTEX_BUFFERS |
1 << VALIDATION_ASPECT_INDEX_BUFFER); 1 << VALIDATION_ASPECT_INDEX_BUFFER);
aspects &= pipelineDependentAspectsInverse; aspects &= pipelineDependentAspectsInverse;
bindgroups.fill(nullptr);
} }
} }

View File

@ -18,6 +18,7 @@
#include "backend/CommandBuffer.h" #include "backend/CommandBuffer.h"
#include "common/Constants.h" #include "common/Constants.h"
#include <array>
#include <bitset> #include <bitset>
#include <map> #include <map>
#include <set> #include <set>
@ -48,12 +49,14 @@ namespace backend {
bool SetVertexBuffer(uint32_t index, BufferBase* buffer); bool SetVertexBuffer(uint32_t index, BufferBase* buffer);
bool TransitionBufferUsage(BufferBase* buffer, nxt::BufferUsageBit usage); bool TransitionBufferUsage(BufferBase* buffer, nxt::BufferUsageBit usage);
bool TransitionTextureUsage(TextureBase* texture, nxt::TextureUsageBit usage); bool TransitionTextureUsage(TextureBase* texture, nxt::TextureUsageBit usage);
bool EnsureTextureUsage(TextureBase* texture, nxt::TextureUsageBit usage);
// These collections are copied to the CommandBuffer at build time. // These collections are copied to the CommandBuffer at build time.
// These pointers will remain valid since they are referenced by // These pointers will remain valid since they are referenced by
// the bind groups which are referenced by this command buffer. // the bind groups which are referenced by this command buffer.
std::set<BufferBase*> buffersTransitioned; std::set<BufferBase*> buffersTransitioned;
std::set<TextureBase*> texturesTransitioned; std::set<TextureBase*> texturesTransitioned;
std::set<TextureBase*> texturesAttached;
private: private:
enum ValidationAspect { enum ValidationAspect {
@ -71,7 +74,8 @@ namespace backend {
// Usage helper functions // Usage helper functions
bool BufferHasGuaranteedUsageBit(BufferBase* buffer, nxt::BufferUsageBit usage) const; bool BufferHasGuaranteedUsageBit(BufferBase* buffer, nxt::BufferUsageBit usage) const;
bool TextureHasGuaranteedUsageBit(TextureBase* texture, nxt::TextureUsageBit usage) const; bool TextureHasGuaranteedUsageBit(TextureBase* texture, nxt::TextureUsageBit usage) const;
bool IsTextureTransitionPossible(TextureBase* texture, nxt::TextureUsageBit usage) const; bool IsInternalTextureTransitionPossible(TextureBase* texture, nxt::TextureUsageBit usage) const;
bool IsExplicitTextureTransitionPossible(TextureBase* texture, nxt::TextureUsageBit usage) const;
// Queries for lazily evaluated aspects // Queries for lazily evaluated aspects
bool RecomputeHaveAspectBindGroups(); bool RecomputeHaveAspectBindGroups();
@ -88,6 +92,7 @@ namespace backend {
ValidationAspects aspects; ValidationAspects aspects;
std::bitset<kMaxBindGroups> bindgroupsSet; std::bitset<kMaxBindGroups> bindgroupsSet;
std::array<BindGroupBase*, kMaxBindGroups> bindgroups = {};
std::bitset<kMaxVertexInputs> inputsSet; std::bitset<kMaxVertexInputs> inputsSet;
PipelineBase* lastPipeline = nullptr; PipelineBase* lastPipeline = nullptr;

View File

@ -92,6 +92,13 @@ namespace backend {
currentUsage = usage; currentUsage = usage;
} }
bool TextureBase::IsDepthFormat(nxt::TextureFormat format) {
switch (format) {
case nxt::TextureFormat::R8G8B8A8Unorm:
return false;
}
}
void TextureBase::TransitionUsage(nxt::TextureUsageBit usage) { void TextureBase::TransitionUsage(nxt::TextureUsageBit usage) {
if (!IsTransitionPossible(usage)) { if (!IsTransitionPossible(usage)) {
device->HandleError("Texture frozen or usage not allowed"); device->HandleError("Texture frozen or usage not allowed");

View File

@ -43,6 +43,8 @@ namespace backend {
bool IsTransitionPossible(nxt::TextureUsageBit usage) const; bool IsTransitionPossible(nxt::TextureUsageBit usage) const;
void UpdateUsageInternal(nxt::TextureUsageBit usage); void UpdateUsageInternal(nxt::TextureUsageBit usage);
static bool IsDepthFormat(nxt::TextureFormat format);
DeviceBase* GetDevice(); DeviceBase* GetDevice();
// NXT API // NXT API

View File

@ -21,7 +21,7 @@ namespace backend {
namespace d3d12 { namespace d3d12 {
namespace { namespace {
D3D12_RESOURCE_STATES D3D12TextureUsage(nxt::TextureUsageBit usage) { D3D12_RESOURCE_STATES D3D12TextureUsage(nxt::TextureUsageBit usage, nxt::TextureFormat format) {
D3D12_RESOURCE_STATES resourceState = D3D12_RESOURCE_STATE_COMMON; D3D12_RESOURCE_STATES resourceState = D3D12_RESOURCE_STATE_COMMON;
if (usage & nxt::TextureUsageBit::TransferSrc) { if (usage & nxt::TextureUsageBit::TransferSrc) {
@ -36,27 +36,27 @@ namespace d3d12 {
if (usage & nxt::TextureUsageBit::Storage) { if (usage & nxt::TextureUsageBit::Storage) {
resourceState |= D3D12_RESOURCE_STATE_UNORDERED_ACCESS; resourceState |= D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
} }
if (usage & nxt::TextureUsageBit::ColorAttachment) { if (usage & nxt::TextureUsageBit::OutputAttachment) {
resourceState |= D3D12_RESOURCE_STATE_RENDER_TARGET; resourceState |= D3D12_RESOURCE_STATE_RENDER_TARGET;
} if (TextureBase::IsDepthFormat(format)) {
if (usage & nxt::TextureUsageBit::DepthStencilAttachment) { resourceState |= D3D12_RESOURCE_STATE_DEPTH_WRITE;
resourceState |= D3D12_RESOURCE_STATE_DEPTH_WRITE; }
} }
return resourceState; return resourceState;
} }
D3D12_RESOURCE_FLAGS D3D12ResourceFlags(nxt::TextureUsageBit usage) { D3D12_RESOURCE_FLAGS D3D12ResourceFlags(nxt::TextureUsageBit usage, nxt::TextureFormat format) {
D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE; D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE;
if (usage & nxt::TextureUsageBit::Storage) { if (usage & nxt::TextureUsageBit::Storage) {
flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
} }
if (usage & nxt::TextureUsageBit::ColorAttachment) { if (usage & nxt::TextureUsageBit::OutputAttachment) {
flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
} if (TextureBase::IsDepthFormat(format)) {
if (usage & nxt::TextureUsageBit::DepthStencilAttachment) { flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; }
} }
return flags; return flags;
@ -91,9 +91,9 @@ namespace d3d12 {
resourceDescriptor.SampleDesc.Count = 1; resourceDescriptor.SampleDesc.Count = 1;
resourceDescriptor.SampleDesc.Quality = 0; resourceDescriptor.SampleDesc.Quality = 0;
resourceDescriptor.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; resourceDescriptor.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
resourceDescriptor.Flags = D3D12ResourceFlags(GetUsage()); resourceDescriptor.Flags = D3D12ResourceFlags(GetUsage(), GetFormat());
resource = device->GetResourceAllocator()->Allocate(D3D12_HEAP_TYPE_DEFAULT, resourceDescriptor, D3D12TextureUsage(GetUsage())); resource = device->GetResourceAllocator()->Allocate(D3D12_HEAP_TYPE_DEFAULT, resourceDescriptor, D3D12TextureUsage(GetUsage(), GetFormat()));
} }
Texture::~Texture() { Texture::~Texture() {
@ -109,8 +109,8 @@ namespace d3d12 {
} }
bool Texture::GetResourceTransitionBarrier(nxt::TextureUsageBit currentUsage, nxt::TextureUsageBit targetUsage, D3D12_RESOURCE_BARRIER* barrier) { bool Texture::GetResourceTransitionBarrier(nxt::TextureUsageBit currentUsage, nxt::TextureUsageBit targetUsage, D3D12_RESOURCE_BARRIER* barrier) {
D3D12_RESOURCE_STATES stateBefore = D3D12TextureUsage(currentUsage); D3D12_RESOURCE_STATES stateBefore = D3D12TextureUsage(currentUsage, GetFormat());
D3D12_RESOURCE_STATES stateAfter = D3D12TextureUsage(targetUsage); D3D12_RESOURCE_STATES stateAfter = D3D12TextureUsage(targetUsage, GetFormat());
if (stateBefore == stateAfter) { if (stateBefore == stateAfter) {
return false; return false;

View File

@ -38,7 +38,7 @@ namespace metal {
result |= MTLTextureUsageShaderRead; result |= MTLTextureUsageShaderRead;
} }
if (usage & (nxt::TextureUsageBit::ColorAttachment | nxt::TextureUsageBit::DepthStencilAttachment)) { if (usage & (nxt::TextureUsageBit::OutputAttachment)) {
result |= MTLTextureUsageRenderTarget; result |= MTLTextureUsageRenderTarget;
} }

View File

@ -48,8 +48,8 @@ class InputStateTest : public NXTTest {
.SetExtent(kRTSize, kRTSize, 1) .SetExtent(kRTSize, kRTSize, 1)
.SetFormat(nxt::TextureFormat::R8G8B8A8Unorm) .SetFormat(nxt::TextureFormat::R8G8B8A8Unorm)
.SetMipLevels(1) .SetMipLevels(1)
.SetAllowedUsage(nxt::TextureUsageBit::ColorAttachment | nxt::TextureUsageBit::TransferSrc) .SetAllowedUsage(nxt::TextureUsageBit::OutputAttachment | nxt::TextureUsageBit::TransferSrc)
.SetInitialUsage(nxt::TextureUsageBit::ColorAttachment) .SetInitialUsage(nxt::TextureUsageBit::OutputAttachment)
.GetResult(); .GetResult();
renderTargetView = renderTarget.CreateTextureViewBuilder().GetResult(); renderTargetView = renderTarget.CreateTextureViewBuilder().GetResult();