blob: 58130e2a055acf4121a5bcd42774a5569cb9791c [file]
// Copyright 2025 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/dawn/native/ResourceTable.h"
#include <utility>
#include "dawn/native/ValidationUtils_autogen.h"
#include "src/dawn/common/Enumerator.h"
#include "src/dawn/common/MatchVariant.h"
#include "src/dawn/common/Range.h"
#include "src/dawn/native/Buffer.h"
#include "src/dawn/native/Device.h"
#include "src/dawn/native/Queue.h"
#include "src/dawn/native/ResourceTableDefaultResources.h"
#include "src/dawn/native/Texture.h"
#include "src/utils/compiler.h"
#include "tint/tint.h"
namespace dawn::native {
namespace {
// Helper to retrieve a Ref<T> out of a variant. Returns an empty Ref if the variant doesn't hold
// Ref<T>, otherwise it returns the variant value.
template <typename T, typename Variant>
Ref<T> GetRef(Variant&& variant) {
auto* p = std::get_if<Ref<T>>(&variant);
if (!p) {
return {};
}
return *p;
}
bool IsVisible(const TextureBase* texture, wgpu::ResourceTableUsage usage) {
return !texture->IsDestroyed() && texture->HasAccess() &&
usage == wgpu::ResourceTableUsage::ReadOnly;
}
MaybeValError ValidateBindingResource(const DeviceBase* device, const BindingResource* resource) {
DAWN_INVALID_IF(resource->nextInChain != nullptr, "nextInChain is not null.");
uint32_t resourceCount = uint32_t{resource->buffer != nullptr} +
uint32_t{resource->textureView != nullptr} +
uint32_t{resource->sampler != nullptr};
DAWN_INVALID_IF(resourceCount != 1,
"%i resources are specified (when there must be exactly 1).", resourceCount);
if (resource->buffer != nullptr) {
// TODO(https://issues.chromium.org/473444515): Support buffers in FullResourceTable.
return DAWN_VALIDATION_ERROR("Buffers are not supported.");
} else if (TextureViewBase* view = resource->textureView) {
// TODO(https://issues.chromium.org/473444515): Support texel buffers in FullResourceTable.
DAWN_TRY(device->ValidateObject(view));
Aspect aspect = view->GetAspects();
DAWN_INVALID_IF(!HasOneBit(aspect),
"Multiple aspects (%s) selected in %s. Expected only 1.", aspect, view);
// TODO(https://issues.chromium.org/473444515): Support storage textures in
// FullResourceTable
DAWN_INVALID_IF(
(view->GetUsage() & kTextureViewOnlyUsages) != wgpu::TextureUsage::TextureBinding,
"%s's usages (%s) are not exactly %s.", view, view->GetUsage() & kTextureViewOnlyUsages,
wgpu::TextureUsage::TextureBinding);
DAWN_INVALID_IF(view->IsYCbCr(), "%s is YCbCr.", view);
} else if (SamplerBase* sampler = resource->sampler) {
DAWN_TRY(device->ValidateObject(sampler));
DAWN_INVALID_IF(sampler->IsYCbCr(), "%s is YCbCr.", sampler);
} else {
DAWN_UNREACHABLE();
}
return {};
}
MaybeValError ValidateUsageBindingResource(const DeviceBase* device,
const UsageBindingResource* resource) {
DAWN_INVALID_IF(resource->nextInChain != nullptr, "nextInChain is not null.");
uint32_t resourceCount =
uint32_t{resource->buffer != nullptr} + uint32_t{resource->texture != nullptr};
DAWN_INVALID_IF(resourceCount != 1,
"%i resources are specified (when there must be exactly 1).", resourceCount);
if (resource->buffer != nullptr) {
// TODO(https://issues.chromium.org/473444515): Support buffers in FullResourceTable.
return DAWN_VALIDATION_ERROR("Buffers are not supported.");
} else if (TextureBase* texture = resource->texture) {
DAWN_TRY(device->ValidateObject(texture));
} else {
DAWN_UNREACHABLE();
}
return {};
}
} // anonymous namespace
MaybeValError ValidateResourceTableDescriptor(const DeviceBase* device,
const ResourceTableDescriptor* descriptor) {
DAWN_CHECK(descriptor);
DAWN_INVALID_IF(!device->HasFeature(Feature::ChromiumExperimentalSamplingResourceTable),
"Resource table used without the %s feature enabled.",
wgpu::FeatureName::ChromiumExperimentalSamplingResourceTable);
DAWN_INVALID_IF(descriptor->nextInChain != nullptr, "nextInChain is not nullptr.");
return {};
}
MaybeValError ValidateSetResourceUsage(const DeviceBase* device,
const ResourceTableBase* table,
const UsageBindingResource* resource,
wgpu::ResourceTableUsage usage) {
DAWN_TRY(device->ValidateObject(table));
DAWN_TRY(ValidateResourceTableUsage(usage));
DAWN_TRY(ValidateUsageBindingResource(device, resource));
return {};
}
ResourceTableBase::ResourceTableBase(DeviceBase* device, const ResourceTableDescriptor* descriptor)
: ApiObjectBase(device, descriptor->label), mAPISize(ResourceTableSlot(descriptor->size)) {
mSlots.resize(mAPISize + ResourceTableDefaultResources::GetCount());
// This checks that the default SlotState constructor used in the resize operation will
// initialize with the typeId of an empty slot.
DAWN_ASSERT(ComputeTypeId({}) == SlotState{}.typeId);
GetObjectTrackingList()->Track(this);
}
ResourceTableBase::ResourceTableBase(DeviceBase* device,
const ResourceTableDescriptor* descriptor,
ObjectBase::ErrorTag tag)
: ApiObjectBase(device, tag, descriptor->label) {
// Create the vector of SlotState even for an error resource table because we need to do state
// tracking used for the validation of synchronous errors. However skip creating it for tables
// above the limit because that's a special error case caught on the content-timeline as well.
if (descriptor->size <= kMaxResourceTableSize) {
mAPISize = ResourceTableSlot(descriptor->size);
mSlots.resize(mAPISize);
} else {
mDestroyed = true;
}
}
// static
Ref<ResourceTableBase> ResourceTableBase::MakeError(DeviceBase* device,
const ResourceTableDescriptor* descriptor) {
return AcquireRef(new ResourceTableBase(device, descriptor, ObjectBase::kError));
}
ObjectType ResourceTableBase::GetType() const {
return ObjectType::ResourceTable;
}
ResourceTableSlot ResourceTableBase::GetAPISize() const {
return mAPISize;
}
ResourceTableSlot ResourceTableBase::GetSizeWithDefaultResources() const {
return mSlots.size();
}
BufferBase* ResourceTableBase::GetMetadataBuffer() const {
DAWN_CHECK(!mDestroyed);
return mMetadataBuffer.Get();
}
bool ResourceTableBase::IsDestroyed() const {
return mDestroyed;
}
MaybeValError ResourceTableBase::ValidateCanUseInSubmitNow() const {
DAWN_CHECK(!IsError());
DAWN_INVALID_IF(IsDestroyed(), "%s used while destroyed.", this);
return {};
}
MaybeError ResourceTableBase::InitializeBase() {
DeviceBase* device = GetDevice();
// Create a storage buffer that will hold the shader-visible metadata for the dynamic array.
uint32_t metadataArrayLength = uint32_t{GetSizeWithDefaultResources()};
BufferDescriptor metadataDesc{
.label = "resource table metadata",
.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst,
.size = sizeof(uint32_t) * (metadataArrayLength + 1),
.mappedAtCreation = true,
};
DAWN_TRY_ASSIGN(mMetadataBuffer, device->CreateBuffer(&metadataDesc));
// Initialize the metadata buffer with the arrayLength and a bunch of zeroes that correspond to
// empty entries.
static_assert(tint::ResourceType{} == tint::ResourceType::kEmpty);
// TODO(https://crbug.com/435317394): We could rely on zero initialization if it is enabled, and
// also apply the initial dirty slots in this mapping instead of on the first use of the
// resource table.
Span<uint32_t> data = mMetadataBuffer->GetMappedRangeSpan<uint32_t>();
// Store APISize at element 0 in the metadata buffer, which will be used in the shader to index
// default resources at APISize + resource type index.
data[0] = uint32_t{mAPISize};
std::ranges::fill(data.subspan(1), 0u);
DAWN_TRY(mMetadataBuffer->Unmap());
// Add the default resources at the end of the table.
ResourceTableDefaultResources* defaultResources;
DAWN_TRY_ASSIGN(defaultResources, device->GetOrCreateResourceTableDefaultsResource());
for (auto [i, defaultResource] : Enumerate(defaultResources->GetResources())) {
BindingResource entryContents;
MatchVariant(
defaultResource,
[&](Ref<TextureViewBase> view) { entryContents.textureView = view.Get(); },
[&](Ref<SamplerBase> sampler) { entryContents.sampler = sampler.Get(); });
Update(mAPISize + i, &entryContents);
}
return {};
}
void ResourceTableBase::DestroyImpl(DestroyReason reason) {
DAWN_CHECK(!mDestroyed);
for (const auto& [texture, _] : mTextureState) {
texture->RemoveResourceTableUse(this);
}
mSlots.clear();
mDirtySlots.clear();
mTextureState.clear();
mDirtyStateTextures.clear();
if (mMetadataBuffer != nullptr) {
mMetadataBuffer->Destroy();
mMetadataBuffer = nullptr;
}
mDestroyed = true;
}
void ResourceTableBase::APIDestroy() {
// Handle error objects directly because Destroy() will skip calling DestroyImpl for them.
if (IsError()) {
mSlots.clear();
mDirtySlots.clear();
mTextureState.clear();
mDirtyStateTextures.clear();
mDestroyed = true;
} else {
Destroy();
}
}
wgpu::Status ResourceTableBase::APIUpdate(uint32_t slotIn, const BindingResource* resource) {
ResourceTableSlot slot = ResourceTableSlot(slotIn);
if (!IsValidSlot(slot)) {
return wgpu::Status::Error;
}
// Prevent replacing a slot that may be in use by the GPU.
if (mSlots[slot].availableAfter > GetDevice()->GetQueue()->GetCompletedCommandSerial()) {
return wgpu::Status::Error;
}
UpdateWithDeviceValidation(slot, resource, "Update");
return wgpu::Status::Success;
}
uint32_t ResourceTableBase::APIInsert(const BindingResource* resource) {
if (IsDestroyed()) {
return wgpu::kInvalidBinding;
}
// TODO(https://crbug.com/435317394): This is O(n) in the number of slots. We could make it
// O(logN) with a heap of the free slots that's maintained over time.
ExecutionSerial completedSerial = GetDevice()->GetQueue()->GetCompletedCommandSerial();
for (ResourceTableSlot slot : Range(mAPISize)) {
if (mSlots[slot].availableAfter > completedSerial) {
continue;
}
UpdateWithDeviceValidation(slot, resource, "Insert");
return uint32_t{slot};
}
// No slot found, return the invalid binding.
return wgpu::kInvalidBinding;
}
wgpu::Status ResourceTableBase::APIRemove(uint32_t slotIn) {
ResourceTableSlot slot = ResourceTableSlot(slotIn);
if (!IsValidSlot(slot)) {
return wgpu::Status::Error;
}
// Always remove the slot, even if a validation error happens, so that we match client-side
// validation.
Remove(slot);
std::ignore = GetDevice()->ConsumedError(GetDevice()->ValidateObject(this),
"validating %s.Remove(%u)", this, slot);
return wgpu::Status::Success;
}
uint32_t ResourceTableBase::APIGetSize() const {
return uint32_t{mAPISize};
}
void ResourceTableBase::APISetResourceUsage(const UsageBindingResource* resource,
wgpu::ResourceTableUsage usage) {
if (GetDevice()->ConsumedError(ValidateSetResourceUsage(GetDevice(), this, resource, usage),
"validating %s.SetResourceUsage(%p, %s)", this, resource,
usage)) {
return;
}
SetResourceUsage(resource, usage);
}
void ResourceTableBase::SetResourceUsage(const UsageBindingResource* resource,
wgpu::ResourceTableUsage usage) {
DAWN_CHECK(resource->buffer == nullptr);
TextureBase* texture = resource->texture;
DAWN_ASSERT(texture != nullptr);
if (IsDestroyed() || texture->IsDestroyed()) {
return;
}
auto iter = mTextureState.find(texture);
// Special-case ReadOnly to not track textures that have no slots since it is the default
// state. This avoids keeping around references to textures that may never be put in the
// resource table.
if (usage == wgpu::ResourceTableUsage::ReadOnly) {
if (iter == mTextureState.end()) {
return;
}
if (iter->second.slots.empty()) {
texture->RemoveResourceTableUse(this);
mTextureState.erase(iter);
return;
}
}
// Track textures with non-default usage even if they are not currently in the resource
// table, so that their usage state is remembered if they are inserted later.
if (iter == mTextureState.end()) {
iter = mTextureState.emplace(texture, TextureState{}).first;
texture->AddResourceTableUse(this);
}
TextureState& textureState = iter->second;
if (textureState.usage != usage) {
textureState.usage = usage;
OnTextureStateChange(texture);
}
}
// static
tint::ResourceType ResourceTableBase::ComputeTypeId(
const std::variant<std::monostate, Ref<TextureViewBase>, Ref<SamplerBase>>& resource) {
return MatchVariant(
resource, [&](std::monostate) { return tint::ResourceType::kEmpty; },
[&](const Ref<SamplerBase>& sampler) {
if (sampler->IsComparison()) {
return tint::ResourceType::kSampler_comparison;
}
if (sampler->IsFiltering()) {
return tint::ResourceType::kSampler_filtering;
}
return tint::ResourceType::kSampler_non_filtering;
},
[&](const Ref<TextureViewBase>& view) {
const TextureBase* texture = view->GetTexture();
if (texture->IsMultisampledTexture()) {
DAWN_CHECK(view->GetDimension() == wgpu::TextureViewDimension::e2D);
switch (view->GetAspects()) {
case Aspect::Color:
switch (view->GetFormat().GetAspectInfo(Aspect::Color).baseType) {
case TextureComponentType::Float:
return tint::ResourceType::kTextureMultisampled2d_f32;
case TextureComponentType::Uint:
return tint::ResourceType::kTextureMultisampled2d_u32;
case TextureComponentType::Sint:
return tint::ResourceType::kTextureMultisampled2d_i32;
default:
DAWN_UNREACHABLE();
}
case Aspect::Depth:
return tint::ResourceType::kTextureDepthMultisampled2d;
default:
DAWN_UNREACHABLE();
}
}
if (view->GetAspects() == Aspect::Depth) {
DAWN_CHECK(!texture->IsMultisampledTexture());
switch (view->GetDimension()) {
case wgpu::TextureViewDimension::e2D:
return tint::ResourceType::kTextureDepth2d;
case wgpu::TextureViewDimension::e2DArray:
return tint::ResourceType::kTextureDepth2dArray;
case wgpu::TextureViewDimension::Cube:
return tint::ResourceType::kTextureDepthCube;
case wgpu::TextureViewDimension::CubeArray:
return tint::ResourceType::kTextureDepthCubeArray;
default:
DAWN_UNREACHABLE();
}
}
const auto& aspect_info = view->GetFormat().GetAspectInfo(view->GetAspects());
bool filterable = (aspect_info.supportedSampleTypes & SampleTypeBit::Float);
switch (aspect_info.baseType) {
case TextureComponentType::Float:
switch (view->GetDimension()) {
case wgpu::TextureViewDimension::e1D: {
if (filterable) {
return tint::ResourceType::kTexture1d_f32_filterable;
}
return tint::ResourceType::kTexture1d_f32_unfilterable;
}
case wgpu::TextureViewDimension::e2D: {
if (filterable) {
return tint::ResourceType::kTexture2d_f32_filterable;
}
return tint::ResourceType::kTexture2d_f32_unfilterable;
}
case wgpu::TextureViewDimension::e2DArray: {
if (filterable) {
return tint::ResourceType::kTexture2dArray_f32_filterable;
}
return tint::ResourceType::kTexture2dArray_f32_unfilterable;
}
case wgpu::TextureViewDimension::Cube: {
if (filterable) {
return tint::ResourceType::kTextureCube_f32_filterable;
}
return tint::ResourceType::kTextureCube_f32_unfilterable;
}
case wgpu::TextureViewDimension::CubeArray: {
if (filterable) {
return tint::ResourceType::kTextureCubeArray_f32_filterable;
}
return tint::ResourceType::kTextureCubeArray_f32_unfilterable;
}
case wgpu::TextureViewDimension::e3D: {
if (filterable) {
return tint::ResourceType::kTexture3d_f32_filterable;
}
return tint::ResourceType::kTexture3d_f32_unfilterable;
}
default:
DAWN_UNREACHABLE();
}
case TextureComponentType::Uint:
switch (view->GetDimension()) {
case wgpu::TextureViewDimension::e1D:
return tint::ResourceType::kTexture1d_u32;
case wgpu::TextureViewDimension::e2D:
return tint::ResourceType::kTexture2d_u32;
case wgpu::TextureViewDimension::e2DArray:
return tint::ResourceType::kTexture2dArray_u32;
case wgpu::TextureViewDimension::Cube:
return tint::ResourceType::kTextureCube_u32;
case wgpu::TextureViewDimension::CubeArray:
return tint::ResourceType::kTextureCubeArray_u32;
case wgpu::TextureViewDimension::e3D:
return tint::ResourceType::kTexture3d_u32;
default:
DAWN_UNREACHABLE();
}
case TextureComponentType::Sint:
switch (view->GetDimension()) {
case wgpu::TextureViewDimension::e1D:
return tint::ResourceType::kTexture1d_i32;
case wgpu::TextureViewDimension::e2D:
return tint::ResourceType::kTexture2d_i32;
case wgpu::TextureViewDimension::e2DArray:
return tint::ResourceType::kTexture2dArray_i32;
case wgpu::TextureViewDimension::Cube:
return tint::ResourceType::kTextureCube_i32;
case wgpu::TextureViewDimension::CubeArray:
return tint::ResourceType::kTextureCubeArray_i32;
case wgpu::TextureViewDimension::e3D:
return tint::ResourceType::kTexture3d_i32;
default:
DAWN_UNREACHABLE();
}
default:
DAWN_UNREACHABLE();
}
});
DAWN_UNREACHABLE();
}
bool ResourceTableBase::IsValidSlot(ResourceTableSlot slot) const {
// Some validation is required to return a synchronous error. It needs to be able to run even on
// error ResourceTables because it must act the same way as an implementation running on top of
// the wire client-side and doesn't know if objects are errors or not.
return !mDestroyed && slot < mAPISize;
}
void ResourceTableBase::Update(ResourceTableSlot slot, const BindingResource* contents) {
DAWN_ASSERT(mSlots[slot].availableAfter <=
GetDevice()->GetQueue()->GetCompletedCommandSerial());
DAWN_CHECK(mSlots[slot].typeId == tint::ResourceType::kEmpty);
mSlots[slot].availableAfter = kMaxExecutionSerial;
SetEntry(slot, contents);
}
void ResourceTableBase::Remove(ResourceTableSlot slot) {
// Prevent all accesses to the slot which means it will be possible to update it once all
// current GPU work is finished.
mSlots[slot].availableAfter = GetDevice()->GetQueue()->GetLastSubmittedCommandSerial();
// Set the entry to be empty, which will unlink previously set resources.
BindingResource nothing = {};
SetEntry(slot, &nothing);
}
void ResourceTableBase::UpdateWithDeviceValidation(ResourceTableSlot slot,
const BindingResource* resource,
std::string_view methodName) {
// Perform validation that produces a validation error, but unconditionally mark the slot as
// used since we need to match client-side validation that doesn't perform these checks.
if (GetDevice()->ConsumedError( //
([&]() -> MaybeValError {
DAWN_TRY(GetDevice()->ValidateObject(this));
return ValidateBindingResource(GetDevice(), resource);
})(),
"validating %s.%s()", this, methodName)) {
BindingResource nothing = {};
Update(slot, &nothing);
} else {
Update(slot, resource);
}
}
void ResourceTableBase::SetEntry(ResourceTableSlot slot, const BindingResource* contents) {
DAWN_CHECK(contents->buffer == nullptr);
SlotState& state = mSlots[slot];
// Check the current state. If it's already set to the input value, early out.
if (auto currView = GetRef<TextureViewBase>(state.resource)) {
if (currView == contents->textureView) {
return;
}
if (currView != nullptr) {
// Remove from mTextureState
TextureBase* currTexture = currView->GetTexture();
TextureState& textureState = mTextureState[currTexture];
[[maybe_unused]] const bool erased = textureState.slots.erase(slot);
DAWN_ASSERT(erased);
// If it's the last slot, stop tracking dirty state for this texture, and remove it
// from mTextureState if it has default usage (ReadOnly). Otherwise, keep the
// mTextureState entry so its non-default usage is remembered if it is added back to
// the table.
if (textureState.slots.empty()) {
if (textureState.usage == wgpu::ResourceTableUsage::ReadOnly) {
currTexture->RemoveResourceTableUse(this);
mTextureState.erase(currTexture);
}
mDirtyStateTextures.erase(currTexture);
}
}
} else if (auto sampler = GetRef<SamplerBase>(state.resource)) {
if (sampler == contents->sampler) {
return;
}
}
// Update to new state
state.resource = {};
// Note: state.lastResource is only updated by AcquireDirtySlotUpdates
if (TextureViewBase* inputView = contents->textureView) {
TextureBase* inputTexture = inputView->GetTexture();
// Add to mTextureState
auto [iter, added] = mTextureState.try_emplace(inputTexture);
TextureState& textureState = iter->second;
if (added) {
// Add the mapping to this table on the texture
inputTexture->AddResourceTableUse(this);
}
// If inputTexture is being (re-)added to its first slot, mark it dirty so its visibility
// and transition are evaluated before the next use. Note that we check `slots.empty()`
// rather than `added` because an entry may already exist with non-default usage.
if (textureState.slots.empty()) {
mDirtyStateTextures.insert(inputTexture);
}
[[maybe_unused]] const bool inserted = textureState.slots.insert(slot).second;
DAWN_ASSERT(inserted);
state.resource = inputView;
} else if (SamplerBase* sampler = contents->sampler) {
state.resource = sampler;
}
state.typeId = ComputeTypeId(state.resource);
state.resourceDirty = true;
MarkStateDirty(slot);
}
MaybeError ResourceTableBase::ApplyDirtySlotUpdatesWith(
const absl::flat_hash_set<TextureBase*>& writableTextures,
absl::FunctionRef<ApplyUpdateFn> ApplyUpdate) {
Updates updates = AcquireDirtySlotUpdates(writableTextures);
DAWN_TRY(
ApplyUpdate(updates.metadataUpdates, updates.resourceDiffs, updates.texturesToTransition));
// The handling of updates.texturesToTransition may cause memory barriers that mark them
// dirty again, when they have just been put in the correct state. Ignore this fake dirtying
// by clearing mDirtyStateTextures.
#if defined(DAWN_ENABLE_ASSERTS)
for (const auto& texture : mDirtyStateTextures) {
DAWN_ASSERT(updates.texturesToTransition.contains(texture));
}
#endif
mDirtyStateTextures = std::move(updates.texturesDirtyAfterUpdate);
return {};
}
ResourceTableBase::Updates ResourceTableBase::MakeResourcesVisibleExcept(
const absl::flat_hash_set<TextureBase*>& writableTextures) {
// This function uses mDirtyStateTextures and writableTextures to figure out visibility for each
// texture in the table, returning the set of textures to transition and updating
// mTextureState's visibility flag (marking slots dirty whenever visibility changes).
Updates updates;
auto HandleDirtyTexture = [&](TextureBase* texture) {
// A texture is removed from mDirtyStateTextures whenever it is removed from mTextureState
// (e.g. when its last slot is removed or when it is destroyed).
auto iter = mTextureState.find(texture);
DAWN_ASSERT(iter != mTextureState.end());
auto& textureState = iter->second;
// A texture is only added to mDirtyStateTextures when it has at least one slot, and removed
// from it when it has no more slots.
DAWN_ASSERT(!textureState.slots.empty());
// Update visible flag
bool visible =
IsVisible(texture, textureState.usage) && !writableTextures.contains(texture);
if (visible) {
updates.texturesToTransition.insert(texture);
}
if (textureState.visible != visible) {
textureState.visible = visible;
// Mark all slots containing this texture dirty so their metadata is updated
for (auto slot : textureState.slots) {
MarkStateDirty(slot);
}
}
};
// First handle the textures that have been dirtied since last draw/dispatch
for (TextureBase* texture : mDirtyStateTextures) {
HandleDirtyTexture(texture);
}
// Now that we've handled them, clear
mDirtyStateTextures.clear();
// Now process writable textures, hiding any that are in the table.
// We also make sure to add them to texturesDirtyAfterUpdate so that if they're not writable
// next call, we unhide them.
for (TextureBase* texture : writableTextures) {
auto iter = mTextureState.find(texture);
if (iter != mTextureState.end() && !iter->second.slots.empty()) {
// We may be recomputing visibility unnecessarily, but it will be possible to optimize.
HandleDirtyTexture(texture);
updates.texturesDirtyAfterUpdate.insert(texture);
}
}
return updates;
}
ResourceTableBase::Updates ResourceTableBase::AcquireDirtySlotUpdates(
const absl::flat_hash_set<TextureBase*>& writableTextures) {
DAWN_CHECK(!mDestroyed);
Updates updates = MakeResourcesVisibleExcept(writableTextures);
for (ResourceTableSlot dirtySlot : mDirtySlots) {
SlotState& state = mSlots[dirtySlot];
DAWN_CHECK(state.metadataDirty);
state.metadataDirty = false;
// Set the value in the table to the type id if visible, otherwise mark empty.
tint::ResourceType effectiveType = state.typeId;
if (auto view = GetRef<TextureViewBase>(state.resource)) {
auto iter = mTextureState.find(view->GetTexture());
DAWN_ASSERT(iter != mTextureState.end());
effectiveType = iter->second.visible ? state.typeId : tint::ResourceType::kEmpty;
}
// Add the update for the metadata buffer.
// Add 1 because the 0th element holds the table size (APISize).
uint32_t offset = sizeof(uint32_t) * (uint32_t{dirtySlot} + 1);
updates.metadataUpdates.push_back({
.slot = dirtySlot,
.offset = offset,
.data = to_underlying(effectiveType),
});
// Compute whether a resource update is needed and skip adding it if unnecessary.
if (!state.resourceDirty) {
continue;
}
state.resourceDirty = false;
ResourceDiff update{
.slot = dirtySlot,
.removed = state.lastResource,
.added = state.resource,
};
// At least one of removed and added must be set
DAWN_ASSERT(!(std::holds_alternative<std::monostate>(update.removed) &&
std::holds_alternative<std::monostate>(update.added)));
updates.resourceDiffs.push_back(update);
// Update the last resource for future slot updates
state.lastResource = state.resource;
}
mDirtySlots.clear();
return updates;
}
void ResourceTableBase::MarkStateDirty(ResourceTableSlot slot) {
if (!mSlots[slot].metadataDirty) {
mDirtySlots.push_back(slot);
mSlots[slot].metadataDirty = true;
}
}
void ResourceTableBase::OnTextureStateChange(TextureBase* texture) {
auto iter = mTextureState.find(texture);
DAWN_ASSERT(iter != mTextureState.end());
if (!iter->second.slots.empty()) {
mDirtyStateTextures.insert(texture);
}
}
void ResourceTableBase::OnTextureDestroyed(TextureBase* texture) {
auto iter = mTextureState.find(texture);
DAWN_ASSERT(iter != mTextureState.end());
// Make sure all slots for this texture are hidden
for (auto slot : iter->second.slots) {
SlotState& state = mSlots[slot];
state.resource = std::monostate{};
state.typeId = tint::ResourceType::kEmpty;
state.resourceDirty = true;
MarkStateDirty(slot);
}
mTextureState.erase(iter);
mDirtyStateTextures.erase(texture);
}
} // namespace dawn::native