blob: 9b7d5486d1f678f3ac25aab809b4dcd7c67272c8 [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 "dawn/native/DynamicArrayState.h"
#include "dawn/common/Enumerator.h"
#include "dawn/native/Buffer.h"
#include "dawn/native/Device.h"
#include "tint/api/common/resource_type.h"
namespace dawn::native {
namespace {
// Compute the tint::ResourceType that should be in the metadata buffer for the binding.
tint::ResourceType ComputeTypeId(const TextureViewBase* view) {
if (view == nullptr) {
return tint::ResourceType::kEmpty;
}
const TextureBase* texture = view->GetTexture();
// TODO(https://crbug.com/435317394): In the future we should allow the same compatibility rules
// that exist between TextureView and BGLEntry. This means that a depth texture can be either a
// texture_depth_2d, or a texture_2d<f32> (unfilterable). We should also find a way to
// differentiate unfilterable and filterable texture_2d<f32>.
if (texture->IsMultisampledTexture()) {
DAWN_ASSERT(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_ASSERT(!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();
}
}
switch (view->GetFormat().GetAspectInfo(view->GetAspects()).baseType) {
case TextureComponentType::Float:
switch (view->GetDimension()) {
case wgpu::TextureViewDimension::e1D:
return tint::ResourceType::kTexture1d_f32;
case wgpu::TextureViewDimension::e2D:
return tint::ResourceType::kTexture2d_f32;
case wgpu::TextureViewDimension::e2DArray:
return tint::ResourceType::kTexture2dArray_f32;
case wgpu::TextureViewDimension::Cube:
return tint::ResourceType::kTextureCube_f32;
case wgpu::TextureViewDimension::CubeArray:
return tint::ResourceType::kTextureCubeArray_f32;
case wgpu::TextureViewDimension::e3D:
return tint::ResourceType::kTexture3d_f32;
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();
}
}
} // anonymous namespace
DynamicArrayState::DynamicArrayState(BindingIndex size) {
mBindings.resize(size);
DAWN_ASSERT(ComputeTypeId(nullptr) == BindingState{}.typeId);
mBindingState.resize(size);
}
MaybeError DynamicArrayState::Initialize(DeviceBase* device) {
// TODO(https://crbug.com/435317394): Default bindings will be included in mBindings in the
// future such that we should use the dynamicArraySize passed in the BindGroup creation instead
// of the size of mBindings.
uint32_t metadataArrayLength = uint32_t(mBindings.size());
// Create a storage buffer that will hold the shader-visible metadata for the dynamic array.
BufferDescriptor metadataDesc{
.label = "binding array 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.
DAWN_ASSERT(uint32_t(tint::ResourceType::kEmpty) == 0);
// TODO(https://crbug.com/435317394): We could rely on zero initialization if it is enabled, and
// also apply the initial dirty bindings in this mapping instead of one the first use of the
// dynamic binding array.
uint32_t* data = static_cast<uint32_t*>(mMetadataBuffer->GetMappedRange(0, metadataDesc.size));
*data = metadataArrayLength;
memset(data + 1, 0, metadataDesc.size - sizeof(uint32_t));
DAWN_TRY(mMetadataBuffer->Unmap());
return {};
}
void DynamicArrayState::Destroy() {
DAWN_ASSERT(!mDestroyed);
for (auto [i, view] : Enumerate(mBindings)) {
if (view != nullptr) {
view->GetTexture()->RemoveDynamicArraySlot(this, i);
}
}
mBindings.clear();
mBindingState.clear();
mDirtyBindings.clear();
mMetadataBuffer->Destroy();
mMetadataBuffer = nullptr;
mDestroyed = true;
}
BindingIndex DynamicArrayState::GetSize() const {
DAWN_ASSERT(!mDestroyed);
return mBindings.size();
}
ityp::span<BindingIndex, const Ref<TextureViewBase>> DynamicArrayState::GetBindings() const {
DAWN_ASSERT(!mDestroyed);
return {mBindings.data(), mBindings.size()};
}
BufferBase* DynamicArrayState::GetMetadataBuffer() const {
DAWN_ASSERT(!mDestroyed);
return mMetadataBuffer.Get();
}
bool DynamicArrayState::IsDestroyed() const {
return mDestroyed;
}
void DynamicArrayState::Update(BindingIndex i, TextureViewBase* view) {
DAWN_ASSERT(!mDestroyed);
if (mBindings[i] == view) {
return;
}
// Update the mBindings slot but also the mapping to the slot that are stored in the textures.
if (mBindings[i] != nullptr) {
mBindings[i]->GetTexture()->RemoveDynamicArraySlot(this, i);
}
if (view != nullptr) {
view->GetTexture()->AddDynamicArraySlot(this, i);
}
mBindings[i] = view;
// Update the mBindingState with information for the updated binding.
tint::ResourceType typeId = ComputeTypeId(view);
bool pinned = view != nullptr && view->GetTexture()->HasPinnedUsage();
BindingState& state = mBindingState[i];
if (state.typeId != typeId || state.pinned != pinned) {
state.typeId = typeId;
state.pinned = pinned;
MarkStateDirty(i);
}
}
void DynamicArrayState::OnPinned(BindingIndex i, TextureBase* texture) {
DAWN_ASSERT(!mDestroyed);
DAWN_ASSERT(mBindings[i] != nullptr);
DAWN_ASSERT(mBindings[i]->GetTexture() == texture);
DAWN_ASSERT(!mBindingState[i].pinned);
mBindingState[i].pinned = true;
MarkStateDirty(i);
}
void DynamicArrayState::OnUnpinned(BindingIndex i, TextureBase* texture) {
DAWN_ASSERT(!mDestroyed);
DAWN_ASSERT(mBindings[i] != nullptr);
DAWN_ASSERT(mBindings[i]->GetTexture() == texture);
DAWN_ASSERT(mBindingState[i].pinned);
mBindingState[i].pinned = false;
MarkStateDirty(i);
}
bool DynamicArrayState::HasDirtyBindings() const {
return !mDirtyBindings.empty();
}
std::vector<DynamicArrayState::BindingStateUpdate> DynamicArrayState::AcquireDirtyBindingUpdates() {
DAWN_ASSERT(!mDestroyed);
std::vector<BindingStateUpdate> updates;
for (BindingIndex dirtyIndex : mDirtyBindings) {
DAWN_ASSERT(mBindingState[dirtyIndex].dirty);
mBindingState[dirtyIndex].dirty = false;
tint::ResourceType effectiveType = mBindingState[dirtyIndex].pinned
? mBindingState[dirtyIndex].typeId
: tint::ResourceType::kEmpty;
size_t offset = sizeof(uint32_t) * (uint32_t(dirtyIndex) + 1);
updates.push_back({
.offset = uint32_t(offset),
.data = uint32_t(effectiveType),
});
}
mDirtyBindings.clear();
return updates;
}
void DynamicArrayState::MarkStateDirty(BindingIndex i) {
if (!mBindingState[i].dirty) {
mDirtyBindings.push_back(i);
mBindingState[i].dirty = true;
}
}
} // namespace dawn::native