dawn-cmake/src/dawn_native/Texture.cpp
Corentin Wallez 8a5325ca9a Texture: Change to mArrayLayerCount to mSize.depth
This makes the tracking in TextureBase match the shape of
wgpu::TextureDescriptor.

GetSize() becomes a bit more surprising because the depth can sometimes
be the array size, so new getters GetWidth(), GetHeight() and GetDepth()
are added.

Some simplifications to the backend texture creation code are included
that will make it less error prone to add support for 1D / 3D textures.

Bug: dawn:22

Change-Id: I33b6ca99af9d58fc88f5f626cfd5e2e62a8b45cb
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/23103
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
2020-06-15 09:57:51 +00:00

634 lines
24 KiB
C++

// Copyright 2017 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 "dawn_native/Texture.h"
#include <algorithm>
#include "common/Assert.h"
#include "common/Constants.h"
#include "common/Math.h"
#include "dawn_native/Device.h"
#include "dawn_native/PassResourceUsage.h"
#include "dawn_native/ValidationUtils_autogen.h"
namespace dawn_native {
namespace {
// TODO(jiawei.shao@intel.com): implement texture view format compatibility rule
MaybeError ValidateTextureViewFormatCompatibility(const TextureBase* texture,
const TextureViewDescriptor* descriptor) {
if (texture->GetFormat().format != descriptor->format) {
return DAWN_VALIDATION_ERROR(
"The format of texture view is not compatible to the original texture");
}
return {};
}
// TODO(jiawei.shao@intel.com): support validation on all texture view dimensions
bool IsTextureViewDimensionCompatibleWithTextureDimension(
wgpu::TextureViewDimension textureViewDimension,
wgpu::TextureDimension textureDimension) {
switch (textureViewDimension) {
case wgpu::TextureViewDimension::e2D:
case wgpu::TextureViewDimension::e2DArray:
case wgpu::TextureViewDimension::Cube:
case wgpu::TextureViewDimension::CubeArray:
return textureDimension == wgpu::TextureDimension::e2D;
default:
UNREACHABLE();
return false;
}
}
// TODO(jiawei.shao@intel.com): support validation on all texture view dimensions
bool IsArrayLayerValidForTextureViewDimension(
wgpu::TextureViewDimension textureViewDimension,
uint32_t textureViewArrayLayer) {
switch (textureViewDimension) {
case wgpu::TextureViewDimension::e2D:
return textureViewArrayLayer == 1u;
case wgpu::TextureViewDimension::e2DArray:
return true;
case wgpu::TextureViewDimension::Cube:
return textureViewArrayLayer == 6u;
case wgpu::TextureViewDimension::CubeArray:
return textureViewArrayLayer % 6 == 0;
default:
UNREACHABLE();
return false;
}
}
bool IsTextureSizeValidForTextureViewDimension(
wgpu::TextureViewDimension textureViewDimension,
const Extent3D& textureSize) {
switch (textureViewDimension) {
case wgpu::TextureViewDimension::Cube:
case wgpu::TextureViewDimension::CubeArray:
return textureSize.width == textureSize.height;
case wgpu::TextureViewDimension::e2D:
case wgpu::TextureViewDimension::e2DArray:
return true;
default:
UNREACHABLE();
return false;
}
}
// TODO(jiawei.shao@intel.com): support more sample count.
MaybeError ValidateSampleCount(const TextureDescriptor* descriptor, const Format* format) {
if (!IsValidSampleCount(descriptor->sampleCount)) {
return DAWN_VALIDATION_ERROR("The sample count of the texture is not supported.");
}
if (descriptor->sampleCount > 1) {
if (descriptor->mipLevelCount > 1) {
return DAWN_VALIDATION_ERROR(
"The mipmap level count of a multisampled texture must be 1.");
}
// Multisampled 2D array texture is not supported because on Metal it requires the
// version of macOS be greater than 10.14.
if (descriptor->size.depth > 1) {
return DAWN_VALIDATION_ERROR(
"Multisampled textures with depth > 1 are not supported.");
}
if (format->isCompressed) {
return DAWN_VALIDATION_ERROR(
"The sample counts of the textures in BC formats must be 1.");
}
if (descriptor->usage & wgpu::TextureUsage::Storage) {
return DAWN_VALIDATION_ERROR(
"The sample counts of the storage textures must be 1.");
}
}
return {};
}
MaybeError ValidateTextureViewDimensionCompatibility(
const TextureBase* texture,
const TextureViewDescriptor* descriptor) {
if (!IsArrayLayerValidForTextureViewDimension(descriptor->dimension,
descriptor->arrayLayerCount)) {
return DAWN_VALIDATION_ERROR(
"The dimension of the texture view is not compatible with the layer count");
}
if (!IsTextureViewDimensionCompatibleWithTextureDimension(descriptor->dimension,
texture->GetDimension())) {
return DAWN_VALIDATION_ERROR(
"The dimension of the texture view is not compatible with the dimension of the"
"original texture");
}
if (!IsTextureSizeValidForTextureViewDimension(descriptor->dimension,
texture->GetSize())) {
return DAWN_VALIDATION_ERROR(
"The dimension of the texture view is not compatible with the size of the"
"original texture");
}
return {};
}
MaybeError ValidateTextureSize(const TextureDescriptor* descriptor, const Format* format) {
ASSERT(descriptor->size.width != 0 && descriptor->size.height != 0);
if (descriptor->size.width > kMaxTextureSize ||
descriptor->size.height > kMaxTextureSize) {
return DAWN_VALIDATION_ERROR("Texture max size exceeded");
}
if (Log2(std::max(descriptor->size.width, descriptor->size.height)) + 1 <
descriptor->mipLevelCount) {
return DAWN_VALIDATION_ERROR("Texture has too many mip levels");
}
if (format->isCompressed && (descriptor->size.width % format->blockWidth != 0 ||
descriptor->size.height % format->blockHeight != 0)) {
return DAWN_VALIDATION_ERROR(
"The size of the texture is incompatible with the texture format");
}
if (descriptor->dimension == wgpu::TextureDimension::e2D &&
descriptor->size.depth > kMaxTexture2DArrayLayers) {
return DAWN_VALIDATION_ERROR("Texture 2D array layer count exceeded");
}
if (descriptor->mipLevelCount > kMaxTexture2DMipLevels) {
return DAWN_VALIDATION_ERROR("Max texture 2D mip level exceeded");
}
return {};
}
MaybeError ValidateTextureUsage(const TextureDescriptor* descriptor, const Format* format) {
DAWN_TRY(dawn_native::ValidateTextureUsage(descriptor->usage));
constexpr wgpu::TextureUsage kValidCompressedUsages = wgpu::TextureUsage::Sampled |
wgpu::TextureUsage::CopySrc |
wgpu::TextureUsage::CopyDst;
if (format->isCompressed && (descriptor->usage & (~kValidCompressedUsages))) {
return DAWN_VALIDATION_ERROR(
"Compressed texture format is incompatible with the texture usage");
}
if (!format->isRenderable &&
(descriptor->usage & wgpu::TextureUsage::OutputAttachment)) {
return DAWN_VALIDATION_ERROR(
"Non-renderable format used with OutputAttachment usage");
}
if (!format->supportsStorageUsage &&
(descriptor->usage & wgpu::TextureUsage::Storage)) {
return DAWN_VALIDATION_ERROR("Format cannot be used in storage textures");
}
return {};
}
} // anonymous namespace
MaybeError ValidateTextureDescriptor(const DeviceBase* device,
const TextureDescriptor* descriptor) {
if (descriptor == nullptr) {
return DAWN_VALIDATION_ERROR("Texture descriptor is nullptr");
}
if (descriptor->nextInChain != nullptr) {
return DAWN_VALIDATION_ERROR("nextInChain must be nullptr");
}
const Format* format;
DAWN_TRY_ASSIGN(format, device->GetInternalFormat(descriptor->format));
DAWN_TRY(ValidateTextureUsage(descriptor, format));
DAWN_TRY(ValidateTextureDimension(descriptor->dimension));
DAWN_TRY(ValidateSampleCount(descriptor, format));
// TODO(jiawei.shao@intel.com): check stuff based on the dimension
if (descriptor->size.width == 0 || descriptor->size.height == 0 ||
descriptor->size.depth == 0 || descriptor->mipLevelCount == 0) {
return DAWN_VALIDATION_ERROR("Cannot create an empty texture");
}
if (descriptor->dimension != wgpu::TextureDimension::e2D) {
return DAWN_VALIDATION_ERROR("Texture dimension must be 2D (for now)");
}
DAWN_TRY(ValidateTextureSize(descriptor, format));
return {};
}
MaybeError ValidateTextureViewDescriptor(const TextureBase* texture,
const TextureViewDescriptor* descriptor) {
if (descriptor->nextInChain != nullptr) {
return DAWN_VALIDATION_ERROR("nextInChain must be nullptr");
}
// Parent texture should have been already validated.
ASSERT(texture);
ASSERT(!texture->IsError());
if (texture->GetTextureState() == TextureBase::TextureState::Destroyed) {
return DAWN_VALIDATION_ERROR("Destroyed texture used to create texture view");
}
DAWN_TRY(ValidateTextureViewDimension(descriptor->dimension));
if (descriptor->dimension == wgpu::TextureViewDimension::e1D ||
descriptor->dimension == wgpu::TextureViewDimension::e3D) {
return DAWN_VALIDATION_ERROR("Texture view dimension must be 2D compatible.");
}
DAWN_TRY(ValidateTextureFormat(descriptor->format));
DAWN_TRY(ValidateTextureAspect(descriptor->aspect));
if (descriptor->aspect != wgpu::TextureAspect::All) {
return DAWN_VALIDATION_ERROR("Texture aspect must be 'all'");
}
// TODO(jiawei.shao@intel.com): check stuff based on resource limits
if (descriptor->arrayLayerCount == 0 || descriptor->mipLevelCount == 0) {
return DAWN_VALIDATION_ERROR("Cannot create an empty texture view");
}
if (uint64_t(descriptor->baseArrayLayer) + uint64_t(descriptor->arrayLayerCount) >
uint64_t(texture->GetArrayLayers())) {
return DAWN_VALIDATION_ERROR("Texture view array-layer out of range");
}
if (uint64_t(descriptor->baseMipLevel) + uint64_t(descriptor->mipLevelCount) >
uint64_t(texture->GetNumMipLevels())) {
return DAWN_VALIDATION_ERROR("Texture view mip-level out of range");
}
DAWN_TRY(ValidateTextureViewFormatCompatibility(texture, descriptor));
DAWN_TRY(ValidateTextureViewDimensionCompatibility(texture, descriptor));
return {};
}
TextureViewDescriptor GetTextureViewDescriptorWithDefaults(
const TextureBase* texture,
const TextureViewDescriptor* descriptor) {
ASSERT(texture);
TextureViewDescriptor desc = {};
if (descriptor) {
desc = *descriptor;
}
// The default value for the view dimension depends on the texture's dimension with a
// special case for 2DArray being chosen automatically if arrayLayerCount is unspecified.
if (desc.dimension == wgpu::TextureViewDimension::Undefined) {
switch (texture->GetDimension()) {
case wgpu::TextureDimension::e1D:
desc.dimension = wgpu::TextureViewDimension::e1D;
break;
case wgpu::TextureDimension::e2D:
if (texture->GetArrayLayers() > 1u && desc.arrayLayerCount == 0) {
desc.dimension = wgpu::TextureViewDimension::e2DArray;
} else {
desc.dimension = wgpu::TextureViewDimension::e2D;
}
break;
case wgpu::TextureDimension::e3D:
desc.dimension = wgpu::TextureViewDimension::e3D;
break;
default:
UNREACHABLE();
}
}
if (desc.format == wgpu::TextureFormat::Undefined) {
desc.format = texture->GetFormat().format;
}
if (desc.arrayLayerCount == 0) {
desc.arrayLayerCount = texture->GetArrayLayers() - desc.baseArrayLayer;
}
if (desc.mipLevelCount == 0) {
desc.mipLevelCount = texture->GetNumMipLevels() - desc.baseMipLevel;
}
return desc;
}
ResultOrError<TextureDescriptor> FixTextureDescriptor(DeviceBase* device,
const TextureDescriptor* desc) {
TextureDescriptor fixedDesc = *desc;
if (desc->arrayLayerCount != 1) {
if (desc->size.depth != 1) {
return DAWN_VALIDATION_ERROR("arrayLayerCount and size.depth cannot both be != 1");
} else {
fixedDesc.size.depth = fixedDesc.arrayLayerCount;
fixedDesc.arrayLayerCount = 1;
device->EmitDeprecationWarning(
"wgpu::TextureDescriptor::arrayLayerCount is deprecated in favor of "
"::size::depth");
}
}
return {std::move(fixedDesc)};
}
bool IsValidSampleCount(uint32_t sampleCount) {
switch (sampleCount) {
case 1:
case 4:
return true;
default:
return false;
}
}
// static
SubresourceRange SubresourceRange::SingleSubresource(uint32_t baseMipLevel,
uint32_t baseArrayLayer) {
return {baseMipLevel, 1, baseArrayLayer, 1};
}
// TextureBase
TextureBase::TextureBase(DeviceBase* device,
const TextureDescriptor* descriptor,
TextureState state)
: ObjectBase(device),
mDimension(descriptor->dimension),
mFormat(device->GetValidInternalFormat(descriptor->format)),
mSize(descriptor->size),
mMipLevelCount(descriptor->mipLevelCount),
mSampleCount(descriptor->sampleCount),
mUsage(descriptor->usage),
mState(state) {
uint32_t subresourceCount = GetSubresourceCount();
mIsSubresourceContentInitializedAtIndex = std::vector<bool>(subresourceCount, false);
// Add readonly storage usage if the texture has a storage usage. The validation rules in
// ValidatePassResourceUsage will make sure we don't use both at the same time.
if (mUsage & wgpu::TextureUsage::Storage) {
mUsage |= kReadonlyStorageTexture;
}
}
static Format kUnusedFormat;
TextureBase::TextureBase(DeviceBase* device, ObjectBase::ErrorTag tag)
: ObjectBase(device, tag), mFormat(kUnusedFormat) {
}
// static
TextureBase* TextureBase::MakeError(DeviceBase* device) {
return new TextureBase(device, ObjectBase::kError);
}
wgpu::TextureDimension TextureBase::GetDimension() const {
ASSERT(!IsError());
return mDimension;
}
// TODO(jiawei.shao@intel.com): return more information about texture format
const Format& TextureBase::GetFormat() const {
ASSERT(!IsError());
return mFormat;
}
const Extent3D& TextureBase::GetSize() const {
ASSERT(!IsError());
return mSize;
}
uint32_t TextureBase::GetWidth() const {
ASSERT(!IsError());
return mSize.width;
}
uint32_t TextureBase::GetHeight() const {
ASSERT(!IsError());
ASSERT(mDimension == wgpu::TextureDimension::e2D ||
mDimension == wgpu::TextureDimension::e3D);
return mSize.height;
}
uint32_t TextureBase::GetDepth() const {
ASSERT(!IsError());
ASSERT(mDimension == wgpu::TextureDimension::e3D);
return mSize.depth;
}
uint32_t TextureBase::GetArrayLayers() const {
ASSERT(!IsError());
// TODO(cwallez@chromium.org): Update for 1D / 3D textures when they are supported.
ASSERT(mDimension == wgpu::TextureDimension::e2D);
return mSize.depth;
}
uint32_t TextureBase::GetNumMipLevels() const {
ASSERT(!IsError());
return mMipLevelCount;
}
SubresourceRange TextureBase::GetAllSubresources() const {
ASSERT(!IsError());
return {0, mMipLevelCount, 0, GetArrayLayers()};
}
uint32_t TextureBase::GetSampleCount() const {
ASSERT(!IsError());
return mSampleCount;
}
uint32_t TextureBase::GetSubresourceCount() const {
ASSERT(!IsError());
return mMipLevelCount * mSize.depth;
}
wgpu::TextureUsage TextureBase::GetUsage() const {
ASSERT(!IsError());
return mUsage;
}
TextureBase::TextureState TextureBase::GetTextureState() const {
ASSERT(!IsError());
return mState;
}
uint32_t TextureBase::GetSubresourceIndex(uint32_t mipLevel, uint32_t arraySlice) const {
ASSERT(arraySlice <= kMaxTexture2DArrayLayers);
ASSERT(mipLevel <= kMaxTexture2DMipLevels);
static_assert(kMaxTexture2DMipLevels <=
std::numeric_limits<uint32_t>::max() / kMaxTexture2DArrayLayers,
"texture size overflows uint32_t");
return GetNumMipLevels() * arraySlice + mipLevel;
}
bool TextureBase::IsSubresourceContentInitialized(const SubresourceRange& range) const {
ASSERT(!IsError());
for (uint32_t arrayLayer = range.baseArrayLayer;
arrayLayer < range.baseArrayLayer + range.layerCount; ++arrayLayer) {
for (uint32_t mipLevel = range.baseMipLevel;
mipLevel < range.baseMipLevel + range.levelCount; ++mipLevel) {
uint32_t subresourceIndex = GetSubresourceIndex(mipLevel, arrayLayer);
ASSERT(subresourceIndex < mIsSubresourceContentInitializedAtIndex.size());
if (!mIsSubresourceContentInitializedAtIndex[subresourceIndex]) {
return false;
}
}
}
return true;
}
void TextureBase::SetIsSubresourceContentInitialized(bool isInitialized,
const SubresourceRange& range) {
ASSERT(!IsError());
for (uint32_t arrayLayer = range.baseArrayLayer;
arrayLayer < range.baseArrayLayer + range.layerCount; ++arrayLayer) {
for (uint32_t mipLevel = range.baseMipLevel;
mipLevel < range.baseMipLevel + range.levelCount; ++mipLevel) {
uint32_t subresourceIndex = GetSubresourceIndex(mipLevel, arrayLayer);
ASSERT(subresourceIndex < mIsSubresourceContentInitializedAtIndex.size());
mIsSubresourceContentInitializedAtIndex[subresourceIndex] = isInitialized;
}
}
}
MaybeError TextureBase::ValidateCanUseInSubmitNow() const {
ASSERT(!IsError());
if (mState == TextureState::Destroyed) {
return DAWN_VALIDATION_ERROR("Destroyed texture used in a submit");
}
return {};
}
bool TextureBase::IsMultisampledTexture() const {
ASSERT(!IsError());
return mSampleCount > 1;
}
Extent3D TextureBase::GetMipLevelVirtualSize(uint32_t level) const {
Extent3D extent = {std::max(mSize.width >> level, 1u), 1u, 1u};
if (mDimension == wgpu::TextureDimension::e1D) {
return extent;
}
extent.height = std::max(mSize.height >> level, 1u);
if (mDimension == wgpu::TextureDimension::e2D) {
return extent;
}
extent.depth = std::max(mSize.depth >> level, 1u);
return extent;
}
Extent3D TextureBase::GetMipLevelPhysicalSize(uint32_t level) const {
Extent3D extent = GetMipLevelVirtualSize(level);
// Compressed Textures will have paddings if their width or height is not a multiple of
// 4 at non-zero mipmap levels.
if (mFormat.isCompressed) {
// TODO(jiawei.shao@intel.com): check if there are any overflows.
uint32_t blockWidth = mFormat.blockWidth;
uint32_t blockHeight = mFormat.blockHeight;
extent.width = (extent.width + blockWidth - 1) / blockWidth * blockWidth;
extent.height = (extent.height + blockHeight - 1) / blockHeight * blockHeight;
}
return extent;
}
TextureViewBase* TextureBase::CreateView(const TextureViewDescriptor* descriptor) {
return GetDevice()->CreateTextureView(this, descriptor);
}
void TextureBase::Destroy() {
if (GetDevice()->ConsumedError(ValidateDestroy())) {
return;
}
ASSERT(!IsError());
DestroyInternal();
}
void TextureBase::DestroyImpl() {
}
void TextureBase::DestroyInternal() {
DestroyImpl();
mState = TextureState::Destroyed;
}
MaybeError TextureBase::ValidateDestroy() const {
DAWN_TRY(GetDevice()->ValidateObject(this));
return {};
}
// TextureViewBase
TextureViewBase::TextureViewBase(TextureBase* texture, const TextureViewDescriptor* descriptor)
: ObjectBase(texture->GetDevice()),
mTexture(texture),
mFormat(GetDevice()->GetValidInternalFormat(descriptor->format)),
mDimension(descriptor->dimension),
mRange({descriptor->baseMipLevel, descriptor->mipLevelCount, descriptor->baseArrayLayer,
descriptor->arrayLayerCount}) {
}
TextureViewBase::TextureViewBase(DeviceBase* device, ObjectBase::ErrorTag tag)
: ObjectBase(device, tag), mFormat(kUnusedFormat) {
}
// static
TextureViewBase* TextureViewBase::MakeError(DeviceBase* device) {
return new TextureViewBase(device, ObjectBase::kError);
}
const TextureBase* TextureViewBase::GetTexture() const {
ASSERT(!IsError());
return mTexture.Get();
}
TextureBase* TextureViewBase::GetTexture() {
ASSERT(!IsError());
return mTexture.Get();
}
const Format& TextureViewBase::GetFormat() const {
ASSERT(!IsError());
return mFormat;
}
wgpu::TextureViewDimension TextureViewBase::GetDimension() const {
ASSERT(!IsError());
return mDimension;
}
uint32_t TextureViewBase::GetBaseMipLevel() const {
ASSERT(!IsError());
return mRange.baseMipLevel;
}
uint32_t TextureViewBase::GetLevelCount() const {
ASSERT(!IsError());
return mRange.levelCount;
}
uint32_t TextureViewBase::GetBaseArrayLayer() const {
ASSERT(!IsError());
return mRange.baseArrayLayer;
}
uint32_t TextureViewBase::GetLayerCount() const {
ASSERT(!IsError());
return mRange.layerCount;
}
const SubresourceRange& TextureViewBase::GetSubresourceRange() const {
ASSERT(!IsError());
return mRange;
}
} // namespace dawn_native