blob: 6e9c6933f7093e66a3736bfbf80efb0ff605a83b [file] [log] [blame]
// Copyright 2017 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/d3d12/TextureD3D12.h"
#include <algorithm>
#include <utility>
#include "absl/numeric/bits.h"
#include "dawn/common/Constants.h"
#include "dawn/common/Math.h"
#include "dawn/native/ChainUtils.h"
#include "dawn/native/DynamicUploader.h"
#include "dawn/native/EnumMaskIterator.h"
#include "dawn/native/Error.h"
#include "dawn/native/IntegerTypes.h"
#include "dawn/native/ResourceMemoryAllocation.h"
#include "dawn/native/ToBackend.h"
#include "dawn/native/d3d/D3DError.h"
#include "dawn/native/d3d12/BufferD3D12.h"
#include "dawn/native/d3d12/CommandRecordingContext.h"
#include "dawn/native/d3d12/DeviceD3D12.h"
#include "dawn/native/d3d12/Forward.h"
#include "dawn/native/d3d12/HeapD3D12.h"
#include "dawn/native/d3d12/QueueD3D12.h"
#include "dawn/native/d3d12/ResourceAllocatorManagerD3D12.h"
#include "dawn/native/d3d12/SharedFenceD3D12.h"
#include "dawn/native/d3d12/SharedTextureMemoryD3D12.h"
#include "dawn/native/d3d12/StagingDescriptorAllocatorD3D12.h"
#include "dawn/native/d3d12/TextureCopySplitter.h"
#include "dawn/native/d3d12/UtilsD3D12.h"
namespace dawn::native::d3d12 {
namespace {
D3D12_RESOURCE_STATES D3D12TextureUsage(wgpu::TextureUsage usage, const Format& format) {
D3D12_RESOURCE_STATES resourceState = D3D12_RESOURCE_STATE_COMMON;
if (usage & kPresentTextureUsage) {
// The present usage is only used internally by the swapchain and is never used in
// combination with other usages.
DAWN_ASSERT(usage == kPresentTextureUsage);
return D3D12_RESOURCE_STATE_PRESENT;
}
if (usage & wgpu::TextureUsage::CopySrc) {
resourceState |= D3D12_RESOURCE_STATE_COPY_SOURCE;
}
if (usage & wgpu::TextureUsage::CopyDst) {
resourceState |= D3D12_RESOURCE_STATE_COPY_DEST;
}
if (usage & (wgpu::TextureUsage::TextureBinding | kReadOnlyStorageTexture)) {
resourceState |= D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCE;
}
if (usage & (wgpu::TextureUsage::StorageBinding | kWriteOnlyStorageTexture)) {
resourceState |= D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
}
if (usage & wgpu::TextureUsage::RenderAttachment) {
if (format.HasDepthOrStencil()) {
resourceState |= D3D12_RESOURCE_STATE_DEPTH_WRITE;
} else {
resourceState |= D3D12_RESOURCE_STATE_RENDER_TARGET;
}
}
if (usage & kReadOnlyRenderAttachment) {
// There is no STENCIL_READ state. Readonly for stencil is bundled with DEPTH_READ.
resourceState |= D3D12_RESOURCE_STATE_DEPTH_READ;
}
return resourceState;
}
D3D12_RESOURCE_FLAGS D3D12ResourceFlags(wgpu::TextureUsage usage, const Format& format) {
D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE;
if (usage & wgpu::TextureUsage::StorageBinding) {
flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
}
if (usage & wgpu::TextureUsage::RenderAttachment) {
if (format.HasDepthOrStencil()) {
flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
} else {
flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
}
}
DAWN_ASSERT(!(flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) ||
flags == D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL);
return flags;
}
D3D12_RESOURCE_DIMENSION D3D12TextureDimension(wgpu::TextureDimension dimension) {
switch (dimension) {
case wgpu::TextureDimension::Undefined:
DAWN_UNREACHABLE();
case wgpu::TextureDimension::e1D:
return D3D12_RESOURCE_DIMENSION_TEXTURE1D;
case wgpu::TextureDimension::e2D:
return D3D12_RESOURCE_DIMENSION_TEXTURE2D;
case wgpu::TextureDimension::e3D:
return D3D12_RESOURCE_DIMENSION_TEXTURE3D;
}
}
} // namespace
MaybeError ValidateTextureCanBeWrapped(ID3D12Resource* d3d12Resource,
const UnpackedPtr<TextureDescriptor>& dawnDescriptor) {
const D3D12_RESOURCE_DESC d3dDescriptor = d3d12Resource->GetDesc();
DAWN_INVALID_IF(
(dawnDescriptor->size.width != d3dDescriptor.Width) ||
(dawnDescriptor->size.height != d3dDescriptor.Height) ||
(dawnDescriptor->size.depthOrArrayLayers != 1),
"D3D12 texture size (Width: %u, Height: %u, DepthOrArraySize: 1) doesn't match Dawn "
"descriptor size (width: %u, height: %u, depthOrArrayLayers: %u).",
d3dDescriptor.Width, d3dDescriptor.Height, dawnDescriptor->size.width,
dawnDescriptor->size.height, dawnDescriptor->size.depthOrArrayLayers);
const DXGI_FORMAT dxgiFormatFromDescriptor = d3d::DXGITextureFormat(dawnDescriptor->format);
DAWN_INVALID_IF(dxgiFormatFromDescriptor != d3dDescriptor.Format,
"D3D12 texture format (%x) is not compatible with Dawn descriptor format (%s).",
d3dDescriptor.Format, dawnDescriptor->format);
DAWN_INVALID_IF(d3dDescriptor.MipLevels != 1,
"D3D12 texture number of miplevels (%u) is not 1.", d3dDescriptor.MipLevels);
DAWN_INVALID_IF(d3dDescriptor.DepthOrArraySize != 1, "D3D12 texture array size (%u) is not 1.",
d3dDescriptor.DepthOrArraySize);
// Shared textures cannot be multi-sample so no need to check those.
DAWN_ASSERT(d3dDescriptor.SampleDesc.Count == 1);
DAWN_ASSERT(d3dDescriptor.SampleDesc.Quality == 0);
return {};
}
// https://docs.microsoft.com/en-us/windows/win32/api/d3d12/ne-d3d12-d3d12_shared_resource_compatibility_tier
MaybeError ValidateVideoTextureCanBeShared(Device* device, DXGI_FORMAT textureFormat) {
const bool supportsSharedResourceCapabilityTier1 =
device->GetDeviceInfo().supportsSharedResourceCapabilityTier1;
switch (textureFormat) {
// MSDN docs are not correct, NV12 requires at-least tier 1.
case DXGI_FORMAT_NV12:
if (supportsSharedResourceCapabilityTier1) {
return {};
}
break;
default:
break;
}
return DAWN_VALIDATION_ERROR("DXGI format does not support cross-API sharing.");
}
// static
ResultOrError<Ref<Texture>> Texture::Create(Device* device,
const UnpackedPtr<TextureDescriptor>& descriptor) {
Ref<Texture> dawnTexture = AcquireRef(new Texture(device, descriptor));
DAWN_INVALID_IF(dawnTexture->GetFormat().IsMultiPlanar(),
"Cannot create a multi-planar formatted texture directly");
DAWN_TRY(dawnTexture->InitializeAsInternalTexture());
return std::move(dawnTexture);
}
// static
ResultOrError<Ref<Texture>> Texture::CreateExternalImage(
Device* device,
const UnpackedPtr<TextureDescriptor>& descriptor,
ComPtr<IUnknown> d3dTexture,
std::vector<FenceAndSignalValue> waitFences,
bool isSwapChainTexture,
bool isInitialized) {
Ref<Texture> dawnTexture = AcquireRef(new Texture(device, descriptor));
DAWN_TRY(dawnTexture->InitializeAsExternalTexture(std::move(d3dTexture), std::move(waitFences),
isSwapChainTexture));
// Importing a multi-planar format must be initialized. This is required because
// a shared multi-planar format cannot be initialized by Dawn.
DAWN_INVALID_IF(
!isInitialized && dawnTexture->GetFormat().IsMultiPlanar(),
"Cannot create a texture with a multi-planar format (%s) with uninitialized data.",
dawnTexture->GetFormat().format);
dawnTexture->SetIsSubresourceContentInitialized(isInitialized,
dawnTexture->GetAllSubresources());
return std::move(dawnTexture);
}
// static
ResultOrError<Ref<Texture>> Texture::Create(Device* device,
const UnpackedPtr<TextureDescriptor>& descriptor,
ComPtr<ID3D12Resource> d3d12Texture) {
Ref<Texture> dawnTexture = AcquireRef(new Texture(device, descriptor));
DAWN_TRY(dawnTexture->InitializeAsSwapChainTexture(std::move(d3d12Texture)));
return std::move(dawnTexture);
}
// static
ResultOrError<Ref<Texture>> Texture::CreateFromSharedTextureMemory(
SharedTextureMemory* memory,
const UnpackedPtr<TextureDescriptor>& descriptor) {
Device* device = ToBackend(memory->GetDevice());
Ref<Texture> texture = AcquireRef(new Texture(device, descriptor));
DAWN_TRY(texture->InitializeAsExternalTexture(memory->GetD3DResource(), {}, false));
texture->mSharedTextureMemoryContents = memory->GetContents();
return texture;
}
MaybeError Texture::InitializeAsExternalTexture(ComPtr<IUnknown> d3dTexture,
std::vector<FenceAndSignalValue> waitFences,
bool isSwapChainTexture) {
ComPtr<ID3D12Resource> d3d12Texture;
DAWN_TRY(CheckHRESULT(d3dTexture.As(&d3d12Texture), "texture is not a valid ID3D12Resource"));
D3D12_RESOURCE_DESC desc = d3d12Texture->GetDesc();
mD3D12ResourceFlags = desc.Flags;
DAWN_ASSERT(mD3D12ResourceFlags & D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS);
AllocationInfo info;
info.mMethod = AllocationMethod::kExternal;
// When creating the ResourceHeapAllocation, the resource heap is set to nullptr because the
// texture is owned externally. The texture's owning entity must remain responsible for
// memory management.
mResourceAllocation = {info, 0, std::move(d3d12Texture), nullptr};
mWaitFences = std::move(waitFences);
mSwapChainTexture = isSwapChainTexture;
SetLabelHelper("Dawn_ExternalTexture");
return {};
}
MaybeError Texture::InitializeAsInternalTexture() {
D3D12_RESOURCE_DESC resourceDescriptor;
resourceDescriptor.Dimension = D3D12TextureDimension(GetDimension());
resourceDescriptor.Alignment = 0;
const Extent3D& size = GetBaseSize();
resourceDescriptor.Width = size.width;
resourceDescriptor.Height = size.height;
resourceDescriptor.DepthOrArraySize = size.depthOrArrayLayers;
Device* device = ToBackend(GetDevice());
// When the depth stencil texture is created on a not-zeroed heap, its first usage will also be
// copy destination when it is initialized with a non-zero value, which also triggered the issue
// about copying data into a placed depth stencil texture with a dirty memory, so in this
// situation the workaround should also be enabled.
bool applyForceClearCopyableDepthStencilTextureOnCreationToggle =
device->IsToggleEnabled(Toggle::D3D12ForceClearCopyableDepthStencilTextureOnCreation) &&
GetFormat().HasDepthOrStencil() &&
((GetInternalUsage() & wgpu::TextureUsage::CopyDst) ||
(device->IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting) &&
device->IsToggleEnabled(Toggle::D3D12CreateNotZeroedHeap)));
if (applyForceClearCopyableDepthStencilTextureOnCreationToggle) {
AddInternalUsage(wgpu::TextureUsage::RenderAttachment);
}
// This will need to be much more nuanced when WebGPU has
// texture view compatibility rules.
const bool needsTypelessFormat =
(GetDevice()->IsToggleEnabled(Toggle::D3D12AlwaysUseTypelessFormatsForCastableTexture) &&
GetViewFormats().any()) ||
(GetFormat().HasDepthOrStencil() &&
(GetInternalUsage() & wgpu::TextureUsage::TextureBinding) != 0);
DXGI_FORMAT dxgiFormat = needsTypelessFormat
? d3d::DXGITypelessTextureFormat(GetFormat().format)
: d3d::DXGITextureFormat(GetFormat().format);
resourceDescriptor.MipLevels = static_cast<UINT16>(GetNumMipLevels());
resourceDescriptor.Format = dxgiFormat;
resourceDescriptor.SampleDesc.Count = GetSampleCount();
resourceDescriptor.SampleDesc.Quality = 0;
resourceDescriptor.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
resourceDescriptor.Flags = D3D12ResourceFlags(GetInternalUsage(), GetFormat());
mD3D12ResourceFlags = resourceDescriptor.Flags;
uint32_t bytesPerBlock = 0;
if (GetFormat().IsColor()) {
bytesPerBlock = GetFormat().GetAspectInfo(wgpu::TextureAspect::All).block.byteSize;
}
bool forceAllocateAsCommittedResource =
(device->IsToggleEnabled(
Toggle::DisableSubAllocationFor2DTextureWithCopyDstOrRenderAttachment)) &&
GetDimension() == wgpu::TextureDimension::e2D &&
(GetInternalUsage() & (wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment));
DAWN_TRY_ASSIGN(mResourceAllocation,
device->AllocateMemory(D3D12_HEAP_TYPE_DEFAULT, resourceDescriptor,
D3D12_RESOURCE_STATE_COMMON, bytesPerBlock,
forceAllocateAsCommittedResource));
SetLabelImpl();
if (applyForceClearCopyableDepthStencilTextureOnCreationToggle ||
device->IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting)) {
CommandRecordingContext* commandContext;
DAWN_TRY_ASSIGN(commandContext, ToBackend(device->GetQueue())->GetPendingCommandContext());
ClearValue clearValue =
device->IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting)
? ClearValue::NonZero
: ClearValue::Zero;
DAWN_TRY(ClearTexture(commandContext, GetAllSubresources(), clearValue));
}
return {};
}
MaybeError Texture::InitializeAsSwapChainTexture(ComPtr<ID3D12Resource> d3d12Texture) {
AllocationInfo info;
info.mMethod = AllocationMethod::kExternal;
D3D12_RESOURCE_DESC desc = d3d12Texture->GetDesc();
mD3D12ResourceFlags = desc.Flags;
// When creating the ResourceHeapAllocation, the resource heap is set to nullptr because the
// texture is owned externally. The texture's owning entity must remain responsible for
// memory management.
mResourceAllocation = {info, 0, std::move(d3d12Texture), nullptr};
SetLabelHelper("Dawn_SwapChainTexture");
return {};
}
Texture::Texture(Device* device, const UnpackedPtr<TextureDescriptor>& descriptor)
: Base(device, descriptor), mSubresourceStateAndDecay(InitialSubresourceStateAndDecay()) {}
Texture::~Texture() = default;
void Texture::DestroyImpl() {
TextureBase::DestroyImpl();
ToBackend(GetDevice())->DeallocateMemory(mResourceAllocation);
// Set mSwapChainTexture to false to prevent ever calling ID3D12SharingContract::Present again.
mSwapChainTexture = false;
}
ResultOrError<ExecutionSerial> Texture::EndAccess() {
DAWN_ASSERT(mD3D12ResourceFlags & D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS);
Queue* queue = ToBackend(GetDevice()->GetQueue());
// Synchronize if texture access wasn't synchronized already due to ExecuteCommandLists.
if (!mSignalFenceValue.has_value()) {
// If there were pending commands that used this texture mSignalFenceValue will be set,
// but if it's still not set, generate a signal fence after waiting on wait fences.
if (!mSignalFenceValue.has_value()) {
DAWN_TRY(SynchronizeImportedTextureBeforeUse());
DAWN_TRY(SynchronizeImportedTextureAfterUse());
}
// Make the queue signal the fence in finite time.
DAWN_TRY(queue->NextSerial());
}
ExecutionSerial ret = mSignalFenceValue.value();
DAWN_ASSERT(ret <= queue->GetLastSubmittedCommandSerial());
// Explicitly call reset() since std::move() on optional doesn't make it std::nullopt.
mSignalFenceValue.reset();
return ret;
}
DXGI_FORMAT Texture::GetD3D12Format() const {
return d3d::DXGITextureFormat(GetFormat().format);
}
ID3D12Resource* Texture::GetD3D12Resource() const {
return mResourceAllocation.GetD3D12Resource();
}
D3D12_RESOURCE_FLAGS Texture::GetD3D12ResourceFlags() const {
return mD3D12ResourceFlags;
}
DXGI_FORMAT Texture::GetD3D12CopyableSubresourceFormat(Aspect aspect) const {
DAWN_ASSERT(GetFormat().aspects & aspect);
switch (GetFormat().format) {
case wgpu::TextureFormat::Depth24PlusStencil8:
case wgpu::TextureFormat::Depth32FloatStencil8:
case wgpu::TextureFormat::Stencil8:
switch (aspect) {
case Aspect::Depth:
return DXGI_FORMAT_R32_FLOAT;
case Aspect::Stencil:
return DXGI_FORMAT_R8_UINT;
default:
DAWN_UNREACHABLE();
}
default:
DAWN_ASSERT(HasOneBit(GetFormat().aspects));
return GetD3D12Format();
}
}
MaybeError Texture::SynchronizeImportedTextureBeforeUse() {
// Perform the wait only on the first call.
Device* device = ToBackend(GetDevice());
ID3D12CommandQueue* commandQueue = ToBackend(device->GetQueue())->GetCommandQueue();
for (const auto& fence : mWaitFences) {
DAWN_TRY(CheckHRESULT(commandQueue->Wait(ToBackend(fence.object)->GetD3DFence(),
fence.signaledValue),
"D3D12 fence wait"););
// Keep D3D12 fence alive since we'll clear the waitFences list below.
device->ReferenceUntilUnused(ToBackend(fence.object)->GetD3DFence());
}
mWaitFences.clear();
SharedTextureMemoryBase::PendingFenceList fences;
SharedTextureMemoryContents* contents = GetSharedTextureMemoryContents();
if (contents != nullptr) {
contents->AcquirePendingFences(&fences);
contents->SetLastUsageSerial(GetDevice()->GetQueue()->GetPendingCommandSerial());
}
for (const auto& fence : fences) {
DAWN_TRY(CheckHRESULT(
commandQueue->Wait(ToBackend(fence.object)->GetD3DFence(), fence.signaledValue),
"D3D12 fence wait"));
// Keep D3D12 fence alive until commands complete.
device->ReferenceUntilUnused(ToBackend(fence.object)->GetD3DFence());
}
return {};
}
MaybeError Texture::SynchronizeImportedTextureAfterUse() {
// In PIX's D3D12-only mode, there is no way to determine frame boundaries
// for WebGPU since Dawn does not manage DXGI swap chains. Without assistance,
// PIX will wait forever for a present that never happens.
// If we know we're dealing with a swapbuffer texture, inform PIX we've
// "presented" the texture so it can determine frame boundaries and use its
// contents for the UI.
Queue* queue = ToBackend(GetDevice()->GetQueue());
if (mSwapChainTexture) {
ID3D12SharingContract* d3dSharingContract = queue->GetSharingContract();
if (d3dSharingContract != nullptr) {
d3dSharingContract->Present(mResourceAllocation.GetD3D12Resource(), 0, 0);
}
}
// NextSerial() will be called after this - this is also checked in EndAccess().
mSignalFenceValue = queue->GetPendingCommandSerial();
return {};
}
void Texture::TrackUsageAndTransitionNow(CommandRecordingContext* commandContext,
wgpu::TextureUsage usage,
const SubresourceRange& range) {
TrackUsageAndTransitionNow(commandContext, D3D12TextureUsage(usage, GetFormat()), range);
}
void Texture::TrackUsageAndTransitionNow(CommandRecordingContext* commandContext,
D3D12_RESOURCE_STATES newState,
const SubresourceRange& range) {
if (mResourceAllocation.GetInfo().mMethod != AllocationMethod::kExternal) {
// Track the underlying heap to ensure residency.
Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap());
commandContext->TrackHeapUsage(heap, GetDevice()->GetQueue()->GetPendingCommandSerial());
}
std::vector<D3D12_RESOURCE_BARRIER> barriers;
uint32_t aspectCount = absl::popcount(static_cast<uint8_t>(range.aspects));
barriers.reserve(range.levelCount * range.layerCount * aspectCount);
TransitionUsageAndGetResourceBarrier(commandContext, &barriers, newState, range);
if (barriers.size()) {
commandContext->GetCommandList()->ResourceBarrier(barriers.size(), barriers.data());
}
}
void Texture::TransitionSubresourceRange(std::vector<D3D12_RESOURCE_BARRIER>* barriers,
const SubresourceRange& range,
StateAndDecay* state,
D3D12_RESOURCE_STATES newState,
ExecutionSerial pendingCommandSerial) const {
D3D12_RESOURCE_STATES lastState = state->lastState;
// If the transition is from-UAV-to-UAV, then a UAV barrier is needed.
// If one of the usages isn't UAV, then other barriers are used.
bool needsUAVBarrier = lastState == D3D12_RESOURCE_STATE_UNORDERED_ACCESS &&
newState == D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
if (needsUAVBarrier) {
D3D12_RESOURCE_BARRIER barrier;
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.UAV.pResource = GetD3D12Resource();
barriers->push_back(barrier);
return;
}
// Reuse the subresource(s) directly and avoid transition when it isn't needed, and
// return false.
if (lastState == newState) {
return;
}
// The COMMON state represents a state where no write operations can be pending, and
// where all pixels are uncompressed. This makes it possible to transition to and
// from some states without synchronization (i.e. without an explicit
// ResourceBarrier call). Textures can be implicitly promoted to 1) a single write
// state, or 2) multiple read states. Textures will implicitly decay to the COMMON
// state when all of the following are true: 1) the texture is accessed on a command
// list, 2) the ExecuteCommandLists call that uses that command list has ended, and
// 3) the texture was promoted implicitly to a read-only state and is still in that
// state.
// https://docs.microsoft.com/en-us/windows/desktop/direct3d12/using-resource-barriers-to-synchronize-resource-states-in-direct3d-12#implicit-state-transitions
// To track implicit decays, we must record the pending serial on which that
// transition will occur. When that texture is used again, the previously recorded
// serial must be compared to the last completed serial to determine if the texture
// has implicity decayed to the common state.
if (state->isValidToDecay && pendingCommandSerial > state->lastDecaySerial) {
lastState = D3D12_RESOURCE_STATE_COMMON;
}
// Update the tracked state.
state->lastState = newState;
// All simultaneous-access textures are qualified for an implicit promotion.
// Destination states that qualify for an implicit promotion for a
// non-simultaneous-access texture: NON_PIXEL_SHADER_RESOURCE,
// PIXEL_SHADER_RESOURCE, COPY_SRC, COPY_DEST.
{
const D3D12_RESOURCE_STATES kD3D12PromotableReadOnlyStates =
D3D12_RESOURCE_STATE_COPY_SOURCE | D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCE;
if (lastState == D3D12_RESOURCE_STATE_COMMON) {
if (IsSubset(newState, kD3D12PromotableReadOnlyStates) ||
mD3D12ResourceFlags & D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS) {
// Implicit texture state decays can only occur when the texture was implicitly
// transitioned to a read-only state. isValidToDecay is needed to differentiate
// between resources that were implictly or explicitly transitioned to a
// read-only state.
state->isValidToDecay = true;
state->lastDecaySerial = pendingCommandSerial;
return;
} else if (newState == D3D12_RESOURCE_STATE_COPY_DEST) {
state->isValidToDecay = false;
return;
}
}
}
D3D12_RESOURCE_BARRIER barrier;
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = GetD3D12Resource();
barrier.Transition.StateBefore = lastState;
barrier.Transition.StateAfter = newState;
// Currently Stencil8 is implemented with DXGI_FORMAT_D24_UNORM_S8_UINT, while we can only
// choose the stencil aspect for Stencil8 textures, so actually we cannot select the full
// subresource range on the Stencil8 textures.
bool isFullRange =
range.baseArrayLayer == 0 && range.baseMipLevel == 0 &&
range.layerCount == GetArrayLayers() && range.levelCount == GetNumMipLevels() &&
range.aspects == GetFormat().aspects && GetFormat().format != wgpu::TextureFormat::Stencil8;
// Use a single transition for all subresources if possible.
if (isFullRange) {
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
barriers->push_back(barrier);
} else {
for (Aspect aspect : IterateEnumMask(range.aspects)) {
for (uint32_t arrayLayer = 0; arrayLayer < range.layerCount; ++arrayLayer) {
for (uint32_t mipLevel = 0; mipLevel < range.levelCount; ++mipLevel) {
barrier.Transition.Subresource = GetSubresourceIndex(
range.baseMipLevel + mipLevel, range.baseArrayLayer + arrayLayer, aspect);
barriers->push_back(barrier);
}
}
}
}
state->isValidToDecay = false;
}
void Texture::HandleTransitionSpecialCases(CommandRecordingContext* commandContext) {
// Externally allocated textures can be written from other graphics queues. Hence, they must be
// acquired before command list submission to ensure work from the other queues has finished.
// See CommandRecordingContext::ExecuteCommandList.
if (mResourceAllocation.GetInfo().mMethod == AllocationMethod::kExternal) {
commandContext->AddToSharedTextureList(this);
}
}
void Texture::TransitionUsageAndGetResourceBarrier(CommandRecordingContext* commandContext,
std::vector<D3D12_RESOURCE_BARRIER>* barrier,
wgpu::TextureUsage usage,
const SubresourceRange& range) {
TransitionUsageAndGetResourceBarrier(commandContext, barrier,
D3D12TextureUsage(usage, GetFormat()), range);
}
void Texture::TransitionUsageAndGetResourceBarrier(CommandRecordingContext* commandContext,
std::vector<D3D12_RESOURCE_BARRIER>* barriers,
D3D12_RESOURCE_STATES newState,
const SubresourceRange& range) {
HandleTransitionSpecialCases(commandContext);
const ExecutionSerial pendingCommandSerial = GetDevice()->GetQueue()->GetPendingCommandSerial();
mSubresourceStateAndDecay.Update(range, [&](const SubresourceRange& updateRange,
StateAndDecay* state) {
TransitionSubresourceRange(barriers, updateRange, state, newState, pendingCommandSerial);
});
}
void Texture::TrackUsageAndGetResourceBarrierForPass(
CommandRecordingContext* commandContext,
std::vector<D3D12_RESOURCE_BARRIER>* barriers,
const TextureSubresourceSyncInfo& textureSyncInfos) {
if (mResourceAllocation.GetInfo().mMethod != AllocationMethod::kExternal) {
// Track the underlying heap to ensure residency.
Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap());
commandContext->TrackHeapUsage(heap, GetDevice()->GetQueue()->GetPendingCommandSerial());
}
HandleTransitionSpecialCases(commandContext);
const ExecutionSerial pendingCommandSerial = GetDevice()->GetQueue()->GetPendingCommandSerial();
mSubresourceStateAndDecay.Merge(
textureSyncInfos, [&](const SubresourceRange& mergeRange, StateAndDecay* state,
const TextureSyncInfo& syncInfo) {
// Skip if this subresource is not used during the current pass
if (syncInfo.usage == wgpu::TextureUsage::None) {
return;
}
D3D12_RESOURCE_STATES newState = D3D12TextureUsage(syncInfo.usage, GetFormat());
TransitionSubresourceRange(barriers, mergeRange, state, newState, pendingCommandSerial);
});
}
SubresourceStorage<Texture::StateAndDecay> Texture::InitialSubresourceStateAndDecay() const {
return {GetFormat().aspects,
GetArrayLayers(),
GetNumMipLevels(),
{D3D12_RESOURCE_STATES::D3D12_RESOURCE_STATE_COMMON, kMaxExecutionSerial, false}};
}
void Texture::ResetSubresourceStateAndDecayToCommon() {
mSubresourceStateAndDecay = InitialSubresourceStateAndDecay();
}
D3D12_RENDER_TARGET_VIEW_DESC Texture::GetRTVDescriptor(const Format& format,
uint32_t mipLevel,
uint32_t baseSlice,
uint32_t sliceCount) const {
D3D12_RENDER_TARGET_VIEW_DESC rtvDesc;
rtvDesc.Format = d3d::DXGITextureFormat(format.format);
if (IsMultisampledTexture()) {
DAWN_ASSERT(GetDimension() == wgpu::TextureDimension::e2D);
DAWN_ASSERT(GetNumMipLevels() == 1);
DAWN_ASSERT(sliceCount == 1);
DAWN_ASSERT(baseSlice == 0);
DAWN_ASSERT(mipLevel == 0);
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DMS;
return rtvDesc;
}
switch (GetDimension()) {
case wgpu::TextureDimension::e2D:
// Currently we always use D3D12_TEX2D_ARRAY_RTV because we cannot specify base
// array layer and layer count in D3D12_TEX2D_RTV. For 2D texture views, we treat
// them as 1-layer 2D array textures. (Just like how we treat SRVs)
// https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ns-d3d12-d3d12_tex2d_rtv
// https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ns-d3d12-d3d12_tex2d_array
// _rtv
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
rtvDesc.Texture2DArray.FirstArraySlice = baseSlice;
rtvDesc.Texture2DArray.ArraySize = sliceCount;
rtvDesc.Texture2DArray.MipSlice = mipLevel;
rtvDesc.Texture2DArray.PlaneSlice = 0;
break;
case wgpu::TextureDimension::e3D:
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE3D;
rtvDesc.Texture3D.MipSlice = mipLevel;
rtvDesc.Texture3D.FirstWSlice = baseSlice;
rtvDesc.Texture3D.WSize = sliceCount;
break;
case wgpu::TextureDimension::e1D:
case wgpu::TextureDimension::Undefined:
DAWN_UNREACHABLE();
break;
}
return rtvDesc;
}
D3D12_DEPTH_STENCIL_VIEW_DESC Texture::GetDSVDescriptor(uint32_t mipLevel,
uint32_t baseArrayLayer,
uint32_t layerCount,
Aspect aspects,
bool depthReadOnly,
bool stencilReadOnly) const {
D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc;
dsvDesc.Format = GetD3D12Format();
dsvDesc.Flags = D3D12_DSV_FLAG_NONE;
if (depthReadOnly && aspects & Aspect::Depth) {
dsvDesc.Flags |= D3D12_DSV_FLAG_READ_ONLY_DEPTH;
}
if (stencilReadOnly && aspects & Aspect::Stencil) {
dsvDesc.Flags |= D3D12_DSV_FLAG_READ_ONLY_STENCIL;
}
if (IsMultisampledTexture()) {
DAWN_ASSERT(GetNumMipLevels() == 1);
DAWN_ASSERT(layerCount == 1);
DAWN_ASSERT(baseArrayLayer == 0);
DAWN_ASSERT(mipLevel == 0);
dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DMS;
} else {
dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DARRAY;
dsvDesc.Texture2DArray.FirstArraySlice = baseArrayLayer;
dsvDesc.Texture2DArray.ArraySize = layerCount;
dsvDesc.Texture2DArray.MipSlice = mipLevel;
}
return dsvDesc;
}
MaybeError Texture::ClearTexture(CommandRecordingContext* commandContext,
const SubresourceRange& range,
TextureBase::ClearValue clearValue) {
ID3D12GraphicsCommandList* commandList = commandContext->GetCommandList();
Device* device = ToBackend(GetDevice());
uint8_t clearColor = (clearValue == TextureBase::ClearValue::Zero) ? 0 : 1;
float fClearColor = (clearValue == TextureBase::ClearValue::Zero) ? 0.f : 1.f;
if ((mD3D12ResourceFlags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) != 0) {
TrackUsageAndTransitionNow(commandContext, D3D12_RESOURCE_STATE_DEPTH_WRITE, range);
for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount;
++level) {
for (uint32_t layer = range.baseArrayLayer;
layer < range.baseArrayLayer + range.layerCount; ++layer) {
// Iterate the aspects individually to determine which clear flags to use.
D3D12_CLEAR_FLAGS clearFlags = {};
for (Aspect aspect : IterateEnumMask(range.aspects)) {
if (clearValue == TextureBase::ClearValue::Zero &&
IsSubresourceContentInitialized(
SubresourceRange::SingleMipAndLayer(level, layer, aspect))) {
// Skip lazy clears if already initialized.
continue;
}
switch (aspect) {
case Aspect::Depth:
clearFlags |= D3D12_CLEAR_FLAG_DEPTH;
break;
case Aspect::Stencil:
clearFlags |= D3D12_CLEAR_FLAG_STENCIL;
break;
default:
DAWN_UNREACHABLE();
}
}
if (clearFlags == 0) {
continue;
}
CPUDescriptorHeapAllocation dsvHandle;
DAWN_TRY_ASSIGN(
dsvHandle,
device->GetDepthStencilViewAllocator()->AllocateTransientCPUDescriptors());
const D3D12_CPU_DESCRIPTOR_HANDLE baseDescriptor = dsvHandle.GetBaseDescriptor();
D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc =
GetDSVDescriptor(level, layer, 1, range.aspects, false, false);
device->GetD3D12Device()->CreateDepthStencilView(GetD3D12Resource(), &dsvDesc,
baseDescriptor);
commandList->ClearDepthStencilView(baseDescriptor, clearFlags, fClearColor,
clearColor, 0, nullptr);
}
}
} else if ((mD3D12ResourceFlags & D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET) != 0) {
TrackUsageAndTransitionNow(commandContext, D3D12_RESOURCE_STATE_RENDER_TARGET, range);
const float clearColorRGBA[4] = {fClearColor, fClearColor, fClearColor, fClearColor};
DAWN_ASSERT(range.aspects == Aspect::Color);
for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount;
++level) {
for (uint32_t layer = range.baseArrayLayer;
layer < range.baseArrayLayer + range.layerCount; ++layer) {
if (clearValue == TextureBase::ClearValue::Zero &&
IsSubresourceContentInitialized(
SubresourceRange::SingleMipAndLayer(level, layer, Aspect::Color))) {
// Skip lazy clears if already initialized.
continue;
}
CPUDescriptorHeapAllocation rtvHeap;
DAWN_TRY_ASSIGN(
rtvHeap,
device->GetRenderTargetViewAllocator()->AllocateTransientCPUDescriptors());
const D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = rtvHeap.GetBaseDescriptor();
// For the subresources of 3d textures, range.baseArrayLayer must be 0 and
// range.layerCount must be 1, the sliceCount is the depthOrArrayLayers of the
// subresource virtual size, which must be 1 for 2d textures. When clearing RTV, we
// can use the 'layer' as baseSlice and the 'depthOrArrayLayers' as sliceCount to
// create RTV without checking the dimension.
D3D12_RENDER_TARGET_VIEW_DESC rtvDesc =
GetRTVDescriptor(GetFormat(), level, layer,
GetMipLevelSingleSubresourceVirtualSize(level, Aspect::Color)
.depthOrArrayLayers);
device->GetD3D12Device()->CreateRenderTargetView(GetD3D12Resource(), &rtvDesc,
rtvHandle);
commandList->ClearRenderTargetView(rtvHandle, clearColorRGBA, 0, nullptr);
}
}
} else {
DAWN_ASSERT(!IsMultisampledTexture());
// create temp buffer with clear color to copy to the texture image
TrackUsageAndTransitionNow(commandContext, D3D12_RESOURCE_STATE_COPY_DEST, range);
for (Aspect aspect : IterateEnumMask(range.aspects)) {
const TexelBlockInfo& blockInfo = GetFormat().GetAspectInfo(aspect).block;
Extent3D largestMipSize =
GetMipLevelSingleSubresourcePhysicalSize(range.baseMipLevel, aspect);
uint32_t bytesPerRow =
Align((largestMipSize.width / blockInfo.width) * blockInfo.byteSize,
kTextureBytesPerRowAlignment);
uint64_t bufferSize = bytesPerRow * (largestMipSize.height / blockInfo.height) *
largestMipSize.depthOrArrayLayers;
DynamicUploader* uploader = device->GetDynamicUploader();
UploadHandle uploadHandle;
DAWN_TRY_ASSIGN(
uploadHandle,
uploader->Allocate(bufferSize, device->GetQueue()->GetPendingCommandSerial(),
blockInfo.byteSize));
memset(uploadHandle.mappedBuffer, clearColor, bufferSize);
for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount;
++level) {
// compute d3d12 texture copy locations for texture and buffer
Extent3D copySize = GetMipLevelSingleSubresourcePhysicalSize(level, aspect);
for (uint32_t layer = range.baseArrayLayer;
layer < range.baseArrayLayer + range.layerCount; ++layer) {
if (clearValue == TextureBase::ClearValue::Zero &&
IsSubresourceContentInitialized(
SubresourceRange::SingleMipAndLayer(level, layer, aspect))) {
// Skip lazy clears if already initialized.
continue;
}
TextureCopy textureCopy;
textureCopy.texture = this;
textureCopy.origin = {0, 0, layer};
textureCopy.mipLevel = level;
textureCopy.aspect = aspect;
RecordBufferTextureCopyWithBufferHandle(
BufferTextureCopyDirection::B2T, commandList,
ToBackend(uploadHandle.stagingBuffer)->GetD3D12Resource(),
uploadHandle.startOffset, bytesPerRow, largestMipSize.height, textureCopy,
copySize);
}
}
}
}
if (clearValue == TextureBase::ClearValue::Zero) {
SetIsSubresourceContentInitialized(true, range);
GetDevice()->IncrementLazyClearCountForTesting();
}
return {};
}
void Texture::SetLabelHelper(const char* prefix) {
SetDebugName(ToBackend(GetDevice()), mResourceAllocation.GetD3D12Resource(), prefix,
GetLabel());
}
void Texture::SetLabelImpl() {
SetLabelHelper("Dawn_InternalTexture");
}
MaybeError Texture::EnsureSubresourceContentInitialized(CommandRecordingContext* commandContext,
const SubresourceRange& range) {
if (!ToBackend(GetDevice())->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
return {};
}
if (!IsSubresourceContentInitialized(range)) {
// If subresource has not been initialized, clear it to black as it could contain
// dirty bits from recycled memory
DAWN_TRY(ClearTexture(commandContext, range, TextureBase::ClearValue::Zero));
}
return {};
}
bool Texture::StateAndDecay::operator==(const Texture::StateAndDecay& other) const {
return lastState == other.lastState && lastDecaySerial == other.lastDecaySerial &&
isValidToDecay == other.isValidToDecay;
}
// static
Ref<TextureView> TextureView::Create(TextureBase* texture,
const TextureViewDescriptor* descriptor) {
return AcquireRef(new TextureView(texture, descriptor));
}
TextureView::TextureView(TextureBase* texture, const TextureViewDescriptor* descriptor)
: TextureViewBase(texture, descriptor) {
mSrvDesc.Format = d3d::DXGITextureFormat(descriptor->format);
mSrvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
UINT planeSlice = 0;
const Format& textureFormat = texture->GetFormat();
if (textureFormat.HasDepthOrStencil()) {
// Configure the SRV descriptor to reinterpret the texture allocated as
// TYPELESS as a single-plane shader-accessible view.
switch (textureFormat.format) {
case wgpu::TextureFormat::Depth32Float:
case wgpu::TextureFormat::Depth24Plus:
mSrvDesc.Format = DXGI_FORMAT_R32_FLOAT;
break;
case wgpu::TextureFormat::Depth16Unorm:
mSrvDesc.Format = DXGI_FORMAT_R16_UNORM;
break;
case wgpu::TextureFormat::Stencil8: {
Aspect aspects = SelectFormatAspects(textureFormat, descriptor->aspect);
DAWN_ASSERT(aspects != Aspect::None);
if (!HasZeroOrOneBits(aspects)) {
// A single aspect is not selected. The texture view must not be
// sampled.
mSrvDesc.Format = DXGI_FORMAT_UNKNOWN;
break;
}
switch (aspects) {
case Aspect::Depth:
planeSlice = 0;
mSrvDesc.Format = DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
break;
case Aspect::Stencil:
planeSlice = 1;
mSrvDesc.Format = DXGI_FORMAT_X24_TYPELESS_G8_UINT;
// Stencil is accessed using the .g component in the shader.
// Map it to the zeroth component to match other APIs.
mSrvDesc.Shader4ComponentMapping = D3D12_ENCODE_SHADER_4_COMPONENT_MAPPING(
D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_1,
D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_0,
D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_0,
D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_1);
break;
default:
DAWN_UNREACHABLE();
break;
}
break;
}
case wgpu::TextureFormat::Depth24PlusStencil8:
case wgpu::TextureFormat::Depth32FloatStencil8: {
Aspect aspects = SelectFormatAspects(textureFormat, descriptor->aspect);
DAWN_ASSERT(aspects != Aspect::None);
if (!HasZeroOrOneBits(aspects)) {
// A single aspect is not selected. The texture view must not be
// sampled.
mSrvDesc.Format = DXGI_FORMAT_UNKNOWN;
break;
}
switch (aspects) {
case Aspect::Depth:
planeSlice = 0;
mSrvDesc.Format = DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS;
break;
case Aspect::Stencil:
planeSlice = 1;
mSrvDesc.Format = DXGI_FORMAT_X32_TYPELESS_G8X24_UINT;
// Stencil is accessed using the .g component in the shader.
// Map it to the zeroth component to match other APIs.
mSrvDesc.Shader4ComponentMapping = D3D12_ENCODE_SHADER_4_COMPONENT_MAPPING(
D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_1,
D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_0,
D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_0,
D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_1);
break;
default:
DAWN_UNREACHABLE();
break;
}
break;
}
default:
DAWN_UNREACHABLE();
break;
}
}
// Per plane view formats must have the plane slice number be the index of the plane in the
// array of textures.
if (texture->GetFormat().IsMultiPlanar()) {
const Aspect planeAspect = ConvertViewAspect(GetFormat(), descriptor->aspect);
planeSlice = GetAspectIndex(planeAspect);
mSrvDesc.Format =
d3d::DXGITextureFormat(texture->GetFormat().GetAspectInfo(planeAspect).format);
}
// Currently we always use D3D12_TEX2D_ARRAY_SRV because we cannot specify base array layer
// and layer count in D3D12_TEX2D_SRV. For 2D texture views, we treat them as 1-layer 2D
// array textures.
// Multisampled textures may only be one array layer, so we use
// D3D12_SRV_DIMENSION_TEXTURE2DMS.
// https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ns-d3d12-d3d12_tex2d_srv
// https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ns-d3d12-d3d12_tex2d_array_srv
if (GetTexture()->IsMultisampledTexture()) {
switch (descriptor->dimension) {
case wgpu::TextureViewDimension::e2DArray:
DAWN_ASSERT(texture->GetArrayLayers() == 1);
[[fallthrough]];
case wgpu::TextureViewDimension::e2D:
DAWN_ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
mSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMS;
break;
default:
DAWN_UNREACHABLE();
}
} else {
switch (descriptor->dimension) {
case wgpu::TextureViewDimension::e1D:
mSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1D;
mSrvDesc.Texture1D.MipLevels = descriptor->mipLevelCount;
mSrvDesc.Texture1D.MostDetailedMip = descriptor->baseMipLevel;
mSrvDesc.Texture1D.ResourceMinLODClamp = 0;
break;
case wgpu::TextureViewDimension::e2D:
case wgpu::TextureViewDimension::e2DArray:
DAWN_ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
mSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY;
mSrvDesc.Texture2DArray.ArraySize = descriptor->arrayLayerCount;
mSrvDesc.Texture2DArray.FirstArraySlice = descriptor->baseArrayLayer;
mSrvDesc.Texture2DArray.MipLevels = descriptor->mipLevelCount;
mSrvDesc.Texture2DArray.MostDetailedMip = descriptor->baseMipLevel;
mSrvDesc.Texture2DArray.PlaneSlice = planeSlice;
mSrvDesc.Texture2DArray.ResourceMinLODClamp = 0;
break;
case wgpu::TextureViewDimension::Cube:
case wgpu::TextureViewDimension::CubeArray:
DAWN_ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
DAWN_ASSERT(descriptor->arrayLayerCount % 6 == 0);
mSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBEARRAY;
mSrvDesc.TextureCubeArray.First2DArrayFace = descriptor->baseArrayLayer;
mSrvDesc.TextureCubeArray.NumCubes = descriptor->arrayLayerCount / 6;
mSrvDesc.TextureCubeArray.MostDetailedMip = descriptor->baseMipLevel;
mSrvDesc.TextureCubeArray.MipLevels = descriptor->mipLevelCount;
mSrvDesc.TextureCubeArray.ResourceMinLODClamp = 0;
break;
case wgpu::TextureViewDimension::e3D:
DAWN_ASSERT(texture->GetDimension() == wgpu::TextureDimension::e3D);
mSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE3D;
mSrvDesc.Texture3D.MostDetailedMip = descriptor->baseMipLevel;
mSrvDesc.Texture3D.MipLevels = descriptor->mipLevelCount;
mSrvDesc.Texture3D.ResourceMinLODClamp = 0;
break;
case wgpu::TextureViewDimension::Undefined:
DAWN_UNREACHABLE();
}
}
}
DXGI_FORMAT TextureView::GetD3D12Format() const {
return d3d::DXGITextureFormat(GetFormat().format);
}
const D3D12_SHADER_RESOURCE_VIEW_DESC& TextureView::GetSRVDescriptor() const {
DAWN_ASSERT(mSrvDesc.Format != DXGI_FORMAT_UNKNOWN);
return mSrvDesc;
}
D3D12_RENDER_TARGET_VIEW_DESC TextureView::GetRTVDescriptor(uint32_t depthSlice) const {
DAWN_ASSERT(depthSlice < GetSingleSubresourceVirtualSize().depthOrArrayLayers);
// We have validated that the depthSlice in render pass's colorAttachments must be undefined for
// 2d RTVs, which value is set to 0. For 3d RTVs, the baseArrayLayer must be 0. So here we can
// simply use baseArrayLayer + depthSlice to specify the slice in RTVs without checking the
// view's dimension.
return ToBackend(GetTexture())
->GetRTVDescriptor(GetFormat(), GetBaseMipLevel(), GetBaseArrayLayer() + depthSlice,
GetLayerCount());
}
D3D12_DEPTH_STENCIL_VIEW_DESC TextureView::GetDSVDescriptor(bool depthReadOnly,
bool stencilReadOnly) const {
DAWN_ASSERT(GetLevelCount() == 1);
return ToBackend(GetTexture())
->GetDSVDescriptor(GetBaseMipLevel(), GetBaseArrayLayer(), GetLayerCount(), GetAspects(),
depthReadOnly, stencilReadOnly);
}
D3D12_UNORDERED_ACCESS_VIEW_DESC TextureView::GetUAVDescriptor() const {
D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc;
uavDesc.Format = GetD3D12Format();
DAWN_ASSERT(!GetTexture()->IsMultisampledTexture());
switch (GetDimension()) {
case wgpu::TextureViewDimension::e1D:
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1D;
uavDesc.Texture1D.MipSlice = GetBaseMipLevel();
break;
case wgpu::TextureViewDimension::e2D:
case wgpu::TextureViewDimension::e2DArray:
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY;
uavDesc.Texture2DArray.FirstArraySlice = GetBaseArrayLayer();
uavDesc.Texture2DArray.ArraySize = GetLayerCount();
uavDesc.Texture2DArray.MipSlice = GetBaseMipLevel();
uavDesc.Texture2DArray.PlaneSlice = 0;
break;
case wgpu::TextureViewDimension::e3D:
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D;
uavDesc.Texture3D.FirstWSlice = 0;
uavDesc.Texture3D.WSize =
std::max(1u, GetSingleSubresourceVirtualSize().depthOrArrayLayers);
uavDesc.Texture3D.MipSlice = GetBaseMipLevel();
break;
// Cube and Cubemap can't be used as storage texture. So there is no need to create UAV
// descriptor for them.
case wgpu::TextureViewDimension::Cube:
case wgpu::TextureViewDimension::CubeArray:
case wgpu::TextureViewDimension::Undefined:
DAWN_UNREACHABLE();
}
return uavDesc;
}
} // namespace dawn::native::d3d12