| // Copyright 2023 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/d3d11/BufferD3D11.h" | |
| #include <algorithm> | |
| #include <memory> | |
| #include <utility> | |
| #include <vector> | |
| #include "dawn/platform/DawnPlatform.h" | |
| #include "src/dawn/common/Constants.h" | |
| #include "src/dawn/common/Math.h" | |
| #include "src/dawn/native/ChainUtils.h" | |
| #include "src/dawn/native/CommandBuffer.h" | |
| #include "src/dawn/native/DynamicUploader.h" | |
| #include "src/dawn/native/d3d/D3DError.h" | |
| #include "src/dawn/native/d3d11/DeviceD3D11.h" | |
| #include "src/dawn/native/d3d11/PhysicalDeviceD3D11.h" | |
| #include "src/dawn/native/d3d11/QueueD3D11.h" | |
| #include "src/dawn/native/d3d11/UtilsD3D11.h" | |
| #include "src/dawn/platform/tracing/TraceEvent.h" | |
| #include "src/utils/assert.h" | |
| #include "src/utils/compiler.h" | |
| #include "src/utils/heap_array.h" | |
| namespace dawn::native::d3d11 { | |
| class ScopedCommandRecordingContext; | |
| namespace { | |
| // Max size for a CPU buffer. | |
| constexpr uint64_t kMaxCPUUploadBufferSize = 64 * 1024; | |
| constexpr wgpu::BufferUsage kCopyUsages = | |
| wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst | kInternalCopySrcBuffer; | |
| constexpr wgpu::BufferUsage kStagingUsages = kMappableBufferUsages | kCopyUsages; | |
| constexpr wgpu::BufferUsage kD3D11GPUWriteUsages = | |
| wgpu::BufferUsage::Storage | kInternalStorageBuffer | wgpu::BufferUsage::Indirect; | |
| // Resource usage Default Dynamic Immutable Staging | |
| // ------------------------------------------------------------ | |
| // GPU-read Yes Yes Yes Yes[1] | |
| // GPU-write Yes No No Yes[1] | |
| // CPU-read No No No Yes[1] | |
| // CPU-write No Yes No Yes[1] | |
| // ------------------------------------------------------------ | |
| // [1] GPU read or write of a resource with the D3D11_USAGE_STAGING usage is restricted to copy | |
| // operations. You use ID3D11DeviceContext::CopySubresourceRegion and | |
| // ID3D11DeviceContext::CopyResource for these copy operations. | |
| bool IsMappable(wgpu::BufferUsage usage) { | |
| return usage & kMappableBufferUsages; | |
| } | |
| bool IsUpload(wgpu::BufferUsage usage) { | |
| return usage & wgpu::BufferUsage::MapWrite && | |
| IsSubset(usage, kInternalCopySrcBuffer | wgpu::BufferUsage::CopySrc | | |
| wgpu::BufferUsage::MapWrite); | |
| } | |
| bool IsStaging(wgpu::BufferUsage usage) { | |
| // Must have at least MapWrite or MapRead bit | |
| return IsMappable(usage) && IsSubset(usage, kStagingUsages); | |
| } | |
| UINT D3D11BufferBindFlags(wgpu::BufferUsage usage) { | |
| UINT bindFlags = 0; | |
| if (usage & (wgpu::BufferUsage::Vertex)) { | |
| bindFlags |= D3D11_BIND_VERTEX_BUFFER; | |
| } | |
| if (usage & wgpu::BufferUsage::Index) { | |
| bindFlags |= D3D11_BIND_INDEX_BUFFER; | |
| } | |
| if (usage & (wgpu::BufferUsage::Uniform)) { | |
| bindFlags |= D3D11_BIND_CONSTANT_BUFFER; | |
| } | |
| if (usage & (wgpu::BufferUsage::Storage | kInternalStorageBuffer)) { | |
| DAWN_ASSERT(!IsMappable(usage)); | |
| bindFlags |= D3D11_BIND_UNORDERED_ACCESS; | |
| } | |
| if (usage & kReadOnlyStorageBuffer) { | |
| bindFlags |= D3D11_BIND_SHADER_RESOURCE; | |
| } | |
| // If the buffer only has CopySrc and CopyDst usages are used as staging buffers for copy. | |
| // Because D3D11 doesn't allow copying between buffer and texture, we will use compute shader | |
| // to copy data between buffer and texture. So the buffer needs to be bound as unordered access | |
| // view. | |
| if (IsSubset(usage, kCopyUsages)) { | |
| bindFlags |= D3D11_BIND_UNORDERED_ACCESS; | |
| } | |
| return bindFlags; | |
| } | |
| UINT D3D11BufferMiscFlags(wgpu::BufferUsage usage) { | |
| UINT miscFlags = 0; | |
| if (usage & (wgpu::BufferUsage::Storage | kInternalStorageBuffer | kReadOnlyStorageBuffer)) { | |
| miscFlags |= D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS; | |
| } | |
| if (usage & wgpu::BufferUsage::Indirect) { | |
| miscFlags |= D3D11_RESOURCE_MISC_DRAWINDIRECT_ARGS; | |
| } | |
| return miscFlags; | |
| } | |
| size_t D3D11BufferSizeAlignment(wgpu::BufferUsage usage) { | |
| if (usage & wgpu::BufferUsage::Uniform) { | |
| // https://learn.microsoft.com/en-us/windows/win32/api/d3d11_1/nf-d3d11_1-id3d11devicecontext1-vssetconstantbuffers1 | |
| // Each number of constants must be a multiple of 16 shader constants(sizeof(float) * 4 * | |
| // 16). | |
| return sizeof(float) * 4 * 16; | |
| } | |
| if (usage & (wgpu::BufferUsage::Storage | kInternalStorageBuffer | kReadOnlyStorageBuffer | | |
| wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc)) { | |
| // Unordered access buffers must be 4-byte aligned. | |
| // We also align 4 bytes for CopyDst buffer since it would be used in T2B compute shader. | |
| // And that shader needs to write 4-byte chunks. | |
| // Similarly, we need to align 4 bytes for CopySrc buffer since it would be used in B2T | |
| // shader that reads 4 byte chunks. | |
| return sizeof(uint32_t); | |
| } | |
| return 1; | |
| } | |
| bool CanUseCPUUploadBuffer(const Device* device, wgpu::BufferUsage usage, size_t bufferSize) { | |
| return IsUpload(usage) && bufferSize <= kMaxCPUUploadBufferSize && | |
| !device->IsToggleEnabled(Toggle::D3D11DisableCPUUploadBuffers); | |
| } | |
| bool CanMappableUseDefaultStorage(const Device* device, wgpu::BufferUsage usage) { | |
| DAWN_ASSERT(IsMappable(usage)); | |
| if (!device->GetDeviceInfo().supportsMapOnDefaultBuffer || | |
| device->IsToggleEnabled(Toggle::D3D11DisableMapOnDefaultBuffers)) { | |
| return false; | |
| } | |
| if (usage & wgpu::BufferUsage::Uniform) { | |
| return false; | |
| } | |
| if (!device->GetDeviceInfo().isUMA && (usage & wgpu::BufferUsage::MapWrite)) { | |
| // On non-UMA, prefer not using DEFAULT storage for MapWrite buffers because CPU writes | |
| // are likely slower when the storage resides in VRAM. | |
| return false; | |
| } | |
| // Default storage cannot be used with Vertex, Index or Indirect usage. | |
| // See | |
| // https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#5.6.1.3%20Map()%20on%20DEFAULT%20Buffers%20used%20as%20SRVs%20or%20UAVs | |
| return !(usage & | |
| (wgpu::BufferUsage::Vertex | wgpu::BufferUsage::Index | wgpu::BufferUsage::Indirect)); | |
| } | |
| constexpr size_t kConstantBufferUpdateAlignment = 16; | |
| wgpu::MapMode GetAutoMapMode(DeviceBase* device, wgpu::BufferUsage usage) { | |
| if (!device->IsToggleEnabled(Toggle::AutoMapBackendBuffer) || !IsMappable(usage)) { | |
| return wgpu::MapMode::None; | |
| } | |
| wgpu::MapMode mode = wgpu::MapMode::None; | |
| if (usage & wgpu::BufferUsage::MapWrite) { | |
| mode |= wgpu::MapMode::Write; | |
| } | |
| if (usage & wgpu::BufferUsage::MapRead) { | |
| mode |= wgpu::MapMode::Read; | |
| } | |
| return mode; | |
| } | |
| } // namespace | |
| // For CPU-to-GPU upload buffers(CopySrc|MapWrite), they can be emulated in the system memory, and | |
| // then written into the dest GPU buffer via ID3D11DeviceContext::UpdateSubresource. | |
| class UploadBuffer final : public Buffer { | |
| public: | |
| UploadBuffer(DeviceBase* device, const UnpackedPtr<BufferDescriptor>& descriptor) | |
| : Buffer(device, | |
| descriptor, | |
| /*internalMappableFlags=*/kMappableBufferUsages, | |
| /*autoMapMode=*/wgpu::MapMode::None) {} | |
| ~UploadBuffer() override = default; | |
| private: | |
| // BufferBase implementations | |
| MaybeError MapAtCreationImpl() override { | |
| mMappedData = mUploadData.data(); | |
| // MapAtCreation does the zeroization on the front-end side. | |
| return {}; | |
| } | |
| MaybeError MapAsyncImpl(wgpu::MapMode mode, size_t offset, size_t size) override { | |
| mMappedData = mUploadData.data(); | |
| return EnsureDataInitialized(nullptr); | |
| } | |
| void UnmapImpl(BufferState oldState, BufferState newState) override { mMappedData = nullptr; } | |
| // d3d11::Buffer implementations | |
| MaybeError InitializeInternal() override { | |
| mUploadData = | |
| // SAFETY: This allocation takes the place of a GPU allocation in a GPU-backed Buffer, | |
| // so its initialization is ensured in the same way. | |
| DAWN_UNSAFE_BUFFERS(HeapArray<uint8_t>::Uninit(GetAllocatedSize(), std::nothrow)); | |
| if (!mUploadData) { | |
| return DAWN_OUT_OF_MEMORY_ERROR("Failed to allocate memory for buffer uploading."); | |
| } | |
| return {}; | |
| } | |
| MaybeError MapInternal(const ScopedCommandRecordingContext*, wgpu::MapMode) override { | |
| mMappedData = mUploadData.data(); | |
| return {}; | |
| } | |
| void UnmapInternal(const ScopedCommandRecordingContext*) override { mMappedData = nullptr; } | |
| MaybeError ClearInternal(const ScopedCommandRecordingContext* commandContext, | |
| uint8_t clearValue, | |
| uint64_t offset, | |
| uint64_t size) override { | |
| std::ranges::fill(mUploadData.subspan(offset, size), clearValue); | |
| return {}; | |
| } | |
| MaybeError CopyToInternal(const ScopedCommandRecordingContext* commandContext, | |
| uint64_t sourceOffset, | |
| size_t size, | |
| Buffer* destination, | |
| uint64_t destinationOffset) override { | |
| return destination->WriteInternal(commandContext, destinationOffset, | |
| DAWN_UNSAFE_TODO(mUploadData.data() + sourceOffset), size, | |
| /*isInitialWrite=*/false); | |
| } | |
| MaybeError CopyFromD3DInternal(const ScopedCommandRecordingContext* commandContext, | |
| ID3D11Buffer* srcD3D11Buffer, | |
| uint64_t sourceOffset, | |
| size_t size, | |
| uint64_t destinationOffset) override { | |
| // Upload buffers shouldn't be copied to. | |
| DAWN_UNREACHABLE(); | |
| return {}; | |
| } | |
| MaybeError WriteInternal(const ScopedCommandRecordingContext* commandContext, | |
| uint64_t offset, | |
| const void* data, | |
| size_t size, | |
| bool isInitialWrite) override { | |
| // TODO(https://crbug.com/524406299): Use Span::CopyFrom. | |
| std::ranges::copy( | |
| DAWN_UNSAFE_TODO(Span<const uint8_t>{static_cast<const uint8_t*>(data), size}), | |
| mUploadData.subspan(offset).begin()); | |
| return {}; | |
| } | |
| HeapArray<uint8_t> mUploadData; | |
| }; | |
| bool CanAddStorageUsageToBufferWithoutSideEffects(const Device* device, | |
| wgpu::BufferUsage storageUsage, | |
| wgpu::BufferUsage originalUsage, | |
| size_t bufferSize) { | |
| // Don't support uniform buffers being used as storage buffer. Because D3D11 constant buffers | |
| // cannot be bound to SRV or UAV. Allowing them to be used as storage buffer would require some | |
| // workarounds including extra copies so it's better we prefer to not do that. | |
| if (originalUsage & wgpu::BufferUsage::Uniform) { | |
| return false; | |
| } | |
| // If buffer is small, we prefer CPU buffer for uploading so don't allow adding storage usage. | |
| if (CanUseCPUUploadBuffer(device, originalUsage, bufferSize)) { | |
| return false; | |
| } | |
| if (IsMappable(originalUsage) && CanMappableUseDefaultStorage(device, originalUsage)) { | |
| // If we can use DEFAULT storage for mappable buffers then adding Storage usage is OK. | |
| return true; | |
| } | |
| const bool requiresUAV = storageUsage & (wgpu::BufferUsage::Storage | kInternalStorageBuffer); | |
| // Check supports for writeable storage usage: | |
| if (requiresUAV) { | |
| // D3D11 mappable buffers cannot be used as UAV unless MapOnDefaultBuffers is supported. | |
| // So avoid that. | |
| return !(originalUsage & kMappableBufferUsages); | |
| } | |
| // Read-only storage buffer cannot be mapped for read unless MapOnDefaultBuffers is | |
| // supported. So avoid that. | |
| DAWN_ASSERT(storageUsage == kReadOnlyStorageBuffer); | |
| return !(originalUsage & wgpu::BufferUsage::MapRead); | |
| } | |
| // static | |
| ResultOrError<Ref<Buffer>> Buffer::Create(Device* device, | |
| const UnpackedPtr<BufferDescriptor>& descriptor, | |
| const ScopedCommandRecordingContext* commandContext, | |
| bool allowUploadBufferEmulation) { | |
| const auto actualUsage = | |
| ComputeInternalBufferUsages(device, descriptor->usage, descriptor->size); | |
| bool useUploadBuffer = allowUploadBufferEmulation; | |
| useUploadBuffer &= CanUseCPUUploadBuffer(device, actualUsage, descriptor->size); | |
| Ref<Buffer> buffer; | |
| if (useUploadBuffer) { | |
| buffer = AcquireRef(new UploadBuffer(device, descriptor)); | |
| } else { | |
| buffer = AcquireRef(new GPUUsableBuffer(device, descriptor)); | |
| } | |
| DAWN_TRY(buffer->Initialize(descriptor->mappedAtCreation, commandContext)); | |
| return buffer; | |
| } | |
| Buffer::Buffer(DeviceBase* device, | |
| const UnpackedPtr<BufferDescriptor>& descriptor, | |
| wgpu::BufferUsage internalMappableFlags, | |
| wgpu::MapMode autoMapMode) | |
| : BufferBase(device, descriptor), | |
| mInternalMappableFlags(internalMappableFlags), | |
| mAutoMapMode(autoMapMode) {} | |
| MaybeError Buffer::Initialize(bool mappedAtCreation, | |
| const ScopedCommandRecordingContext* commandContext) { | |
| // TODO(dawn:1705): handle mappedAtCreation for NonzeroClearResourcesOnCreationForTesting | |
| // Allocate at least 4 bytes so clamped accesses are always in bounds. | |
| uint64_t size = std::max(GetSize(), uint64_t(4u)); | |
| // The validation layer requires: | |
| // ByteWidth must be 12 or larger to be used with D3D11_RESOURCE_MISC_DRAWINDIRECT_ARGS. | |
| if (GetInternalUsage() & wgpu::BufferUsage::Indirect) { | |
| size = std::max(size, uint64_t(12u)); | |
| } | |
| size_t alignment = D3D11BufferSizeAlignment(GetInternalUsage()); | |
| // Check for overflow, bufferDescriptor.ByteWidth is a UINT. | |
| if (size > std::numeric_limits<UINT>::max() - alignment) { | |
| // Alignment would overlow. | |
| return DAWN_OUT_OF_MEMORY_ERROR("Buffer allocation is too large"); | |
| } | |
| mAllocatedSize = Align(size, alignment); | |
| DAWN_TRY(InitializeInternal()); | |
| SetLabelImpl(); | |
| // The buffers with mappedAtCreation == true will be initialized in | |
| // BufferBase::MapAtCreation(). | |
| if (!mappedAtCreation && | |
| GetDevice()->IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting)) { | |
| auto scopedUseDuringCreation = UseInternal(); | |
| if (commandContext) { | |
| DAWN_TRY(ClearWholeBuffer(commandContext, 1u)); | |
| } else { | |
| auto tmpCommandContext = | |
| ToBackend(GetDevice()->GetQueue()) | |
| ->GetScopedPendingCommandContext(QueueBase::SubmitMode::Normal); | |
| DAWN_TRY(ClearWholeBuffer(&tmpCommandContext, 1u)); | |
| } | |
| } | |
| // Mark padding as cleared if there's no padding. | |
| if (GetAllocatedSize() == GetSize()) { | |
| mPaddingCleared = true; | |
| } | |
| return {}; | |
| } | |
| Buffer::~Buffer() = default; | |
| bool Buffer::IsCPUWritableAtCreation() const { | |
| return IsCPUWritable(); | |
| } | |
| bool Buffer::IsCPUWritable() const { | |
| return mInternalMappableFlags & wgpu::BufferUsage::MapWrite; | |
| } | |
| bool Buffer::IsCPUReadable() const { | |
| return mInternalMappableFlags & wgpu::BufferUsage::MapRead; | |
| } | |
| MaybeError Buffer::MapAtCreationImpl() { | |
| DAWN_ASSERT(IsCPUWritableAtCreation()); | |
| // Use Try variant to avoid blocking if the CommandContext lock is already held (e.g., by | |
| // another thread or during Queue::Submit). MapAtCreation must return immediately with a | |
| // mappable pointer, so if the lock isn't available, we fall back to temporary storage and | |
| // defer the actual D3D11 buffer mapping until UnmapIfNeeded. At that point, the | |
| // CommandContext will already be acquired (e.g., during TrackUsage before GPU submission), | |
| // and we can safely map the real D3D11 buffer and transfer the temporary storage contents | |
| // to it via memcpy. | |
| std::optional<ScopedCommandRecordingContext> maybeCommandContext = | |
| ToBackend(GetDevice()->GetQueue()) | |
| ->TryGetScopedPendingCommandContext(QueueBase::SubmitMode::Normal); | |
| if (maybeCommandContext.has_value()) { | |
| return MapInternal(&maybeCommandContext.value(), mAutoMapMode | wgpu::MapMode::Write); | |
| } | |
| // Lock could not be acquired, use temporary storage instead. | |
| mMapAtCreationData = | |
| // SAFETY: Frontend is responsible for initializing MapAtCreation memory. | |
| DAWN_UNSAFE_BUFFERS(HeapArray<uint8_t>::Uninit(GetAllocatedSize(), std::nothrow)); | |
| mMappedData = mMapAtCreationData.data(); | |
| return {}; | |
| } | |
| MaybeError Buffer::MapInternal(const ScopedCommandRecordingContext* commandContext, | |
| wgpu::MapMode mode) { | |
| DAWN_UNREACHABLE(); | |
| return {}; | |
| } | |
| void Buffer::UnmapInternal(const ScopedCommandRecordingContext* commandContext) { | |
| DAWN_UNREACHABLE(); | |
| } | |
| MaybeError Buffer::UnmapIfNeeded(const ScopedCommandRecordingContext* commandContext) { | |
| if (mMappedData == nullptr) { | |
| return {}; | |
| } | |
| if (mMapAtCreationData) { | |
| // We used temporary storage for MapAtCreation, now copy it to the actual buffer | |
| mMappedData = nullptr; | |
| ScopedMap scopedMap; | |
| DAWN_TRY_ASSIGN(scopedMap, ScopedMap::Create(commandContext, this, wgpu::MapMode::Write)); | |
| DAWN_ASSERT(scopedMap.GetMappedData()); | |
| std::ranges::copy(mMapAtCreationData, scopedMap.GetMappedData()); | |
| mMapAtCreationData = {}; | |
| return {}; | |
| } | |
| UnmapInternal(commandContext); | |
| return {}; | |
| } | |
| MaybeError Buffer::TrackUsage(const ScopedCommandRecordingContext* commandContext, | |
| ExecutionSerial pendingSerial) { | |
| if (GetLastUsageSerial() == pendingSerial) { | |
| return {}; | |
| } | |
| // We need to unmap buffer before it can be used in the queue. | |
| DAWN_TRY(UnmapIfNeeded(commandContext)); | |
| MarkUsedInPendingCommands(pendingSerial); | |
| // If automatic mapping is enabled, schedule the buffer to be re-mapped after GPU usage. | |
| if (mAutoMapMode != wgpu::MapMode::None) { | |
| mMapRequest.mode = mAutoMapMode; | |
| ToBackend(GetDevice()->GetQueue())->ScheduleBufferMapping(&mMapRequest, pendingSerial); | |
| } | |
| return {}; | |
| } | |
| MaybeError Buffer::MapAsyncImpl(wgpu::MapMode mode, size_t offset, size_t size) { | |
| DAWN_ASSERT((mode == wgpu::MapMode::Write && IsCPUWritable()) || | |
| (mode == wgpu::MapMode::Read && IsCPUReadable())); | |
| // With automatic mapping, the buffer will be actually mapped/unmapped at queue | |
| // boundaries, so MapAsync is a no-op. | |
| if (mAutoMapMode != wgpu::MapMode::None) { | |
| // Lazily do the 1st map if the buffer is not used in any queue yet. | |
| if (GetLastUsageSerial() == kBeginningOfGPUTime && !mMappedData) { | |
| DAWN_TRY(MapAtCreationImpl()); | |
| } | |
| return {}; | |
| } | |
| auto deviceGuard = GetDevice()->GetGuard(); | |
| const ExecutionSerial lastUsageSerial = GetLastUsageSerial(); | |
| const ExecutionSerial completedSerial = GetDevice()->GetQueue()->GetCompletedCommandSerial(); | |
| // We may run into map stall in case that the buffer is still being used by previous submitted | |
| // commands. To avoid that, instead we ask Queue to do the map later when last usage serial has | |
| // passed. | |
| if (lastUsageSerial > completedSerial) { | |
| mMapRequest.mode = mode; | |
| ToBackend(GetDevice()->GetQueue())->ScheduleBufferMapping(&mMapRequest, lastUsageSerial); | |
| } else { | |
| auto commandContext = ToBackend(GetDevice()->GetQueue()) | |
| ->GetScopedPendingCommandContext(QueueBase::SubmitMode::Normal); | |
| DAWN_TRY(TryMapNow(&commandContext, completedSerial, mode)); | |
| } | |
| return {}; | |
| } | |
| // The difference between FinalizeMapImpl and TryMapNow is that: | |
| // - FinalizeMapImpl() is triggered by front-end's serial control. | |
| // - It's called after Queue::CheckAndUpdateCompletedSerials() and before user's mapping callback. | |
| // - FinalizeMapImpl() will always be called for each MapAsync regardless of whether automatic | |
| // mapping is enabled or not. | |
| // - TryMapNow() is triggered by Queue::CheckAndUpdateCompletedSerials(). | |
| // - If automatic mapping is disabled, it's scheduled for each MapAsync. | |
| // - else, it's scheduled once after the queue finishes using the buffer. | |
| MaybeError Buffer::TryMapNow(ScopedCommandRecordingContext* commandContext, | |
| ExecutionSerial completedSerial, | |
| wgpu::MapMode mode) { | |
| // If the buffer was used again after the remap was scheduled, skip this remap. | |
| // This should only happen with automatic mapping where the buffer is scheduled to be | |
| // re-mapped, but then used again in a subsequent submit before the original serial completed. | |
| if (completedSerial < GetLastUsageSerial()) { | |
| DAWN_ASSERT(mAutoMapMode != wgpu::MapMode::None); | |
| return {}; | |
| } | |
| DAWN_ASSERT(GetDevice()->IsLockedByCurrentThreadIfNeeded()); | |
| // Trigger any deferred unmaps. | |
| // TODO(crbug.com/345471009): Consider reuse the mapped pointer and skip mapping again if | |
| // the previous map mode is the same as the current map mode. | |
| DAWN_TRY(UnmapIfNeeded(commandContext)); | |
| // Map then initialize data using mapped pointer. | |
| // The mapped pointer is always writable because: | |
| // - If mode is Write, then it's already writable. | |
| // - If mode is Read, it's only possible to map staging buffer. In that case, | |
| // D3D11_MAP_READ_WRITE will be used, hence the mapped pointer will also be writable. | |
| // The memory will be initialized from the CPU, if needed, in FinalizeMapImpl. | |
| // | |
| // TODO(dawn:1705): make sure the map call is not blocked by the GPU operations. | |
| DAWN_TRY(MapInternal(commandContext, mode)); | |
| return {}; | |
| } | |
| MaybeError Buffer::FinalizeMapImpl(BufferState newState) { | |
| if (newState == BufferState::MappedAtCreation) { | |
| return {}; | |
| } | |
| DAWN_ASSERT(mMappedData); | |
| // Ensure data is initialized before completing the MapAsync event and giving it to the user. | |
| DAWN_TRY(EnsureDataInitialized(nullptr)); | |
| return {}; | |
| } | |
| void Buffer::UnmapImpl(BufferState oldState, BufferState newState) { | |
| DAWN_ASSERT(IsMappable(GetInternalUsage())); | |
| // With automatic mapping, the buffer stays mapped, so Unmap is a no-op. | |
| if (mAutoMapMode != wgpu::MapMode::None) { | |
| return; | |
| } | |
| // Cancel any pending scheduled map. Note we don't cancel here if newState is Destroyed, since | |
| // it should be handled in DestroyImpl instead. DestroyImpl knows whether the reason is early | |
| // destroy or dtor, and can decide to call CancelScheduledBufferMapping accordingly. | |
| if (newState != BufferState::Destroyed) { | |
| ToBackend(GetDevice()->GetQueue())->CancelScheduledBufferMapping(this); | |
| } | |
| // The actual unmap will be deferred until the buffer is used by the queue or we need to map | |
| // again. This avoids the need to lock the CommandContext here just to call D3D11's Unmap | |
| // function, and instead defers the call to a moment where the CommandContext is already | |
| // acquired. | |
| } | |
| void* Buffer::GetMappedPointerImpl() { | |
| // The frontend asks that the pointer returned is from the start of the resource | |
| // irrespective of the offset passed in MapAsyncImpl, which is what mMappedData is. | |
| return mMappedData; | |
| } | |
| void Buffer::DestroyImpl(DestroyReason reason) { | |
| // TODO(crbug.com/dawn/831): DestroyImpl is called from two places. | |
| // - It may be called if the buffer is explicitly destroyed with APIDestroy. | |
| // This case is NOT thread-safe and needs proper synchronization with other | |
| // simultaneous uses of the buffer. | |
| // - It may be called when the last ref to the buffer is dropped and the buffer | |
| // is implicitly destroyed. This case is thread-safe because there are no | |
| // other threads using the buffer since there are no other live refs. | |
| BufferBase::DestroyImpl(reason); | |
| // Cancel any pending map schedule. Even though front-end guarantees that Destroy() cannot run | |
| // in parallel with Queue operations, it doesn't do the same for Device::Tick(), | |
| // Instance::ProcessEvents() or WaitAny(). Thus a scheduled map triggered by those functions | |
| // would race with Destroy() if we don't do a cancel here. | |
| if (reason != DestroyReason::CppDestructor && IsMappable(GetInternalUsage())) { | |
| ToBackend(GetDevice()->GetQueue())->CancelScheduledBufferMapping(this); | |
| } | |
| // If buffer is still mapped, we need to unmap it before releasing the D3D11 resource. If we | |
| // don't do that, there might be some issues on certain drivers such as Intel's. | |
| // Note: The front-end guarantees that DestroyImpl cannot run concurrently with MapAsync, | |
| // UnmapImpl, or Queue operations, so accessing mMappedData here is safe. Additionally, since | |
| // no Queue operation can use this buffer anymore, it won't be scheduled for a remap after a | |
| // cancel above. | |
| if (mMappedData != nullptr && !mMapAtCreationData) { | |
| // We don't need to unmap if the mapping was done on a shadow copy because no real | |
| // buffer is mapped yet. | |
| ToBackend(GetDevice())->DeferUnmapDestroyedBuffer(GetD3D11MappedBuffer()); | |
| mMappedData = nullptr; | |
| } | |
| } | |
| std::optional<DeviceGuard> Buffer::UseDeviceGuardForDestroy() { | |
| return std::nullopt; | |
| } | |
| MaybeError Buffer::EnsureDataInitialized(const ScopedCommandRecordingContext* commandContext) { | |
| // Clear padding on first use, regardless of initialization state. | |
| DAWN_TRY(EnsurePaddingInitialized(commandContext)); | |
| if (!NeedsInitialization()) { | |
| return {}; | |
| } | |
| DAWN_TRY(InitializeToZero(commandContext)); | |
| return {}; | |
| } | |
| MaybeError Buffer::EnsureDataInitializedAsDestination( | |
| const ScopedCommandRecordingContext* commandContext, | |
| uint64_t offset, | |
| uint64_t size) { | |
| // Clear padding on first use as destination, regardless of initialization state. | |
| DAWN_TRY(EnsurePaddingInitialized(commandContext)); | |
| if (!NeedsInitialization()) { | |
| return {}; | |
| } | |
| if (IsFullBufferRange(offset, size)) { | |
| SetInitialized(true); | |
| return {}; | |
| } | |
| DAWN_TRY(InitializeToZero(commandContext)); | |
| return {}; | |
| } | |
| MaybeError Buffer::EnsureDataInitializedAsDestination( | |
| const ScopedCommandRecordingContext* commandContext, | |
| const CopyTextureToBufferCmd* copy) { | |
| // Clear padding on first use as destination, regardless of initialization state. | |
| DAWN_TRY(EnsurePaddingInitialized(commandContext)); | |
| if (!NeedsInitialization()) { | |
| return {}; | |
| } | |
| if (IsFullBufferOverwrittenInTextureToBufferCopy(copy)) { | |
| SetInitialized(true); | |
| } else { | |
| DAWN_TRY(InitializeToZero(commandContext)); | |
| } | |
| return {}; | |
| } | |
| MaybeError Buffer::InitializeToZero(const ScopedCommandRecordingContext* commandContext) { | |
| DAWN_ASSERT(NeedsInitialization()); | |
| DAWN_TRY(ClearWholeBuffer(commandContext, uint8_t(0u))); | |
| SetInitialized(true); | |
| GetDevice()->IncrementLazyClearCountForTesting(); | |
| return {}; | |
| } | |
| MaybeError Buffer::PredicatedClear(const ScopedSwapStateCommandRecordingContext* commandContext, | |
| ID3D11Predicate* predicate, | |
| uint8_t clearValue, | |
| uint64_t offset, | |
| uint64_t size) { | |
| DAWN_UNREACHABLE(); | |
| return {}; | |
| } | |
| MaybeError Buffer::Clear(const ScopedCommandRecordingContext* commandContext, | |
| uint8_t clearValue, | |
| uint64_t offset, | |
| uint64_t size) { | |
| DAWN_ASSERT(!mMappedData); | |
| if (size == 0) { | |
| return {}; | |
| } | |
| // For non-staging buffers, we can use UpdateSubresource to write the data. | |
| DAWN_TRY(EnsureDataInitializedAsDestination(commandContext, offset, size)); | |
| return ClearInternal(commandContext, clearValue, offset, size); | |
| } | |
| MaybeError Buffer::ClearWholeBuffer(const ScopedCommandRecordingContext* commandContext, | |
| uint8_t clearValue) { | |
| return ClearInternal(commandContext, clearValue, 0, GetAllocatedSize()); | |
| } | |
| MaybeError Buffer::ClearInternal(const ScopedCommandRecordingContext* commandContext, | |
| uint8_t clearValue, | |
| uint64_t offset, | |
| uint64_t size) { | |
| DAWN_ASSERT(size != 0); | |
| // TODO(dawn:1705): use a reusable zero staging buffer to clear the buffer to avoid this CPU to | |
| // GPU copy. | |
| std::vector<uint8_t> clearData(size, clearValue); | |
| return WriteInternal(commandContext, offset, clearData.data(), size, | |
| /*isInitialWrite=*/true); | |
| } | |
| MaybeError Buffer::EnsurePaddingInitialized(const ScopedCommandRecordingContext* commandContext) { | |
| if (mPaddingCleared) [[likely]] { | |
| return {}; | |
| } | |
| DAWN_TRY(ClearPaddingInternal(commandContext)); | |
| mPaddingCleared = true; | |
| return {}; | |
| } | |
| MaybeError Buffer::ClearPaddingInternal(const ScopedCommandRecordingContext* commandContext) { | |
| uint32_t paddingBytes = GetAllocatedSize() - GetSize(); | |
| if (paddingBytes == 0) { | |
| return {}; | |
| } | |
| uint32_t clearSize = paddingBytes; | |
| uint64_t clearOffset = GetSize(); | |
| DAWN_TRY(ClearInternal(commandContext, 0, clearOffset, clearSize)); | |
| return {}; | |
| } | |
| ComPtr<ID3D11Buffer> Buffer::GetD3D11MappedBuffer() { | |
| return nullptr; | |
| } | |
| MaybeError Buffer::Write(const ScopedCommandRecordingContext* commandContext, | |
| uint64_t offset, | |
| const void* data, | |
| size_t size) { | |
| DAWN_ASSERT(size != 0); | |
| // For non-staging buffers, we can use UpdateSubresource to write the data. | |
| DAWN_TRY(EnsureDataInitializedAsDestination(commandContext, offset, size)); | |
| return WriteInternal(commandContext, offset, data, size, /*isInitialWrite=*/false); | |
| } | |
| // static | |
| MaybeError Buffer::Copy(const ScopedCommandRecordingContext* commandContext, | |
| Buffer* source, | |
| uint64_t sourceOffset, | |
| size_t size, | |
| Buffer* destination, | |
| uint64_t destinationOffset) { | |
| DAWN_ASSERT(size != 0); | |
| DAWN_TRY(source->EnsureDataInitialized(commandContext)); | |
| DAWN_TRY( | |
| destination->EnsureDataInitializedAsDestination(commandContext, destinationOffset, size)); | |
| return source->CopyToInternal(commandContext, sourceOffset, size, destination, | |
| destinationOffset); | |
| } | |
| ResultOrError<Buffer::ScopedMap> Buffer::ScopedMap::Create( | |
| const ScopedCommandRecordingContext* commandContext, | |
| Buffer* buffer, | |
| wgpu::MapMode mode) { | |
| if (mode == wgpu::MapMode::Write && !buffer->IsCPUWritable()) { | |
| return ScopedMap(); | |
| } | |
| if (mode == wgpu::MapMode::Read && !buffer->IsCPUReadable()) { | |
| return ScopedMap(); | |
| } | |
| if (buffer->mMappedData) { | |
| return ScopedMap(commandContext, buffer, /*needsUnmap=*/false); | |
| } | |
| // The memory will be initialized from the CPU, if needed, in FinalizeMapImpl. | |
| DAWN_TRY(buffer->MapInternal(commandContext, mode)); | |
| return ScopedMap(commandContext, buffer, /*needsUnmap=*/true); | |
| } | |
| // ScopedMap | |
| Buffer::ScopedMap::ScopedMap() = default; | |
| Buffer::ScopedMap::ScopedMap(const ScopedCommandRecordingContext* commandContext, | |
| Buffer* buffer, | |
| bool needsUnmap) | |
| : mCommandContext(commandContext), mBuffer(buffer), mNeedsUnmap(needsUnmap) {} | |
| Buffer::ScopedMap::~ScopedMap() { | |
| Reset(); | |
| } | |
| Buffer::ScopedMap::ScopedMap(Buffer::ScopedMap&& other) { | |
| this->operator=(std::move(other)); | |
| } | |
| Buffer::ScopedMap& Buffer::ScopedMap::operator=(Buffer::ScopedMap&& other) { | |
| Reset(); | |
| mCommandContext = other.mCommandContext; | |
| mBuffer = other.mBuffer; | |
| mNeedsUnmap = other.mNeedsUnmap; | |
| other.mBuffer = nullptr; | |
| other.mNeedsUnmap = false; | |
| return *this; | |
| } | |
| void Buffer::ScopedMap::Reset() { | |
| if (mNeedsUnmap) { | |
| mBuffer->UnmapInternal(mCommandContext); | |
| } | |
| mCommandContext = nullptr; | |
| mBuffer = nullptr; | |
| mNeedsUnmap = false; | |
| } | |
| uint8_t* Buffer::ScopedMap::GetMappedData() const { | |
| return mBuffer ? static_cast<uint8_t*>(mBuffer->mMappedData) : nullptr; | |
| } | |
| // GPUUsableBuffer::Storage | |
| class GPUUsableBuffer::Storage : public RefCounted, NonCopyable { | |
| public: | |
| explicit Storage(ComPtr<ID3D11Buffer> d3d11Buffer) : mD3d11Buffer(std::move(d3d11Buffer)) { | |
| D3D11_BUFFER_DESC desc; | |
| mD3d11Buffer->GetDesc(&desc); | |
| mD3d11Usage = desc.Usage; | |
| mMappableCopyableFlags = wgpu::BufferUsage::CopySrc; | |
| switch (mD3d11Usage) { | |
| case D3D11_USAGE_STAGING: | |
| case D3D11_USAGE_DEFAULT: | |
| mMappableCopyableFlags |= wgpu::BufferUsage::CopyDst; | |
| break; | |
| default: | |
| break; | |
| } | |
| if (desc.CPUAccessFlags & D3D11_CPU_ACCESS_READ) { | |
| mMappableCopyableFlags |= wgpu::BufferUsage::MapRead; | |
| } | |
| if (desc.CPUAccessFlags & D3D11_CPU_ACCESS_WRITE) { | |
| mMappableCopyableFlags |= wgpu::BufferUsage::MapWrite; | |
| } | |
| mIsConstantBuffer = desc.BindFlags & D3D11_BIND_CONSTANT_BUFFER; | |
| } | |
| ID3D11Buffer* GetD3D11Buffer() { return mD3d11Buffer.Get(); } | |
| uint64_t GetRevision() const { return mRevision; } | |
| void SetRevision(uint64_t revision) { mRevision = revision; } | |
| bool IsFirstRevision() const { return mRevision == 0; } | |
| bool IsConstantBuffer() const { return mIsConstantBuffer; } | |
| bool IsCPUWritable() const { return mMappableCopyableFlags & wgpu::BufferUsage::MapWrite; } | |
| bool IsCPUReadable() const { return mMappableCopyableFlags & wgpu::BufferUsage::MapRead; } | |
| bool IsDynamic() const { return mD3d11Usage == D3D11_USAGE_DYNAMIC; } | |
| bool SupportsCopyDst() const { return mMappableCopyableFlags & wgpu::BufferUsage::CopyDst; } | |
| bool IsGPUWritable() const { return mD3d11Usage == D3D11_USAGE_DEFAULT; } | |
| private: | |
| ComPtr<ID3D11Buffer> mD3d11Buffer; | |
| uint64_t mRevision = 0; | |
| D3D11_USAGE mD3d11Usage; | |
| bool mIsConstantBuffer = false; | |
| wgpu::BufferUsage mMappableCopyableFlags; | |
| }; | |
| // GPUUsableBuffer | |
| GPUUsableBuffer::GPUUsableBuffer(DeviceBase* device, | |
| const UnpackedPtr<BufferDescriptor>& descriptor) | |
| : Buffer( | |
| device, | |
| descriptor, | |
| /*internalMappableFlags=*/ | |
| [](const UnpackedPtr<BufferDescriptor>& descriptor) { | |
| wgpu::BufferUsage mappableFlags = descriptor->usage & kMappableBufferUsages; | |
| if (descriptor->usage & wgpu::BufferUsage::MapRead) { | |
| // Staging buffer can be both mapped read & write. | |
| mappableFlags |= wgpu::BufferUsage::MapWrite; | |
| } | |
| return mappableFlags; | |
| }(descriptor), | |
| /*autoMapMode=*/ | |
| GetAutoMapMode(device, descriptor->usage)) {} | |
| GPUUsableBuffer::~GPUUsableBuffer() = default; | |
| void GPUUsableBuffer::DestroyImpl(DestroyReason reason) { | |
| // TODO(crbug.com/dawn/831): DestroyImpl is called from two places. | |
| // - It may be called if the buffer is explicitly destroyed with APIDestroy. | |
| // This case is NOT thread-safe and needs proper synchronization with other | |
| // simultaneous uses of the buffer. | |
| // - It may be called when the last ref to the buffer is dropped and the buffer | |
| // is implicitly destroyed. This case is thread-safe because there are no | |
| // other threads using the buffer since there are no other live refs. | |
| Buffer::DestroyImpl(reason); | |
| mSRVCache.clear(); | |
| mUAVCache.clear(); | |
| mLastUpdatedStorage = nullptr; | |
| mMappableStorage = nullptr; | |
| mStorages = {}; | |
| } | |
| void GPUUsableBuffer::SetLabelImpl() { | |
| for (auto ite = mStorages.begin(); ite != mStorages.end(); ++ite) { | |
| auto storageType = static_cast<StorageType>(std::distance(mStorages.begin(), ite)); | |
| SetStorageLabel(storageType); | |
| } | |
| } | |
| void GPUUsableBuffer::SetStorageLabel(StorageType storageType) { | |
| static constexpr ityp::array<GPUUsableBuffer::StorageType, const char*, | |
| static_cast<uint8_t>(StorageType::Count)> | |
| kStorageTypeStrings = { | |
| "Dawn_CPUWritableConstantBuffer", | |
| "Dawn_GPUCopyDstConstantBuffer", | |
| "Dawn_CPUWritableNonConstantBuffer", | |
| "Dawn_GPUWritableNonConstantBuffer", | |
| "Dawn_Staging", | |
| "Dawn_MappableAndGPUWritable", | |
| }; | |
| if (!mStorages[storageType]) { | |
| return; | |
| } | |
| SetDebugName(ToBackend(GetDevice()), mStorages[storageType]->GetD3D11Buffer(), | |
| kStorageTypeStrings[storageType], GetLabel()); | |
| } | |
| MaybeError GPUUsableBuffer::InitializeInternal() { | |
| mStorages = {}; | |
| wgpu::BufferUsage usagesToHandle = GetInternalUsage(); | |
| // If the buffer has MapRead usage, allocate a staging storage for both MapRead & MapWrite. | |
| // Example: If user creates a buffer with MapWrite + MapRead + Vertex usages: | |
| // - We will allocate: | |
| // - A storage for both MapWrite + MapRead. | |
| // - A storage for Vertex usage. | |
| // - This will require a copy for vertex input whenever the user maps the buffer (either for | |
| // writing or reading). | |
| // - This is acceptable since a buffer with both MapWrite + MapRead is rare. | |
| if ((usagesToHandle & wgpu::BufferUsage::MapRead) || IsStaging(usagesToHandle)) { | |
| DAWN_TRY_ASSIGN(mLastUpdatedStorage, GetOrCreateStorage(StorageType::Staging)); | |
| mMappableStorage = mLastUpdatedStorage; | |
| usagesToHandle &= ~kStagingUsages; | |
| } | |
| // We need to create a separate storage for uniform usage, because D3D11 doesn't allow constant | |
| // buffer to be used for other purposes. | |
| if (usagesToHandle & wgpu::BufferUsage::Uniform) { | |
| usagesToHandle &= | |
| ~(wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopySrc | kInternalCopySrcBuffer); | |
| // Since D3D11 doesn't allow both CPU & GPU to write to a buffer, we need separate | |
| // storages for CPU writes and GPU writes. | |
| if (usagesToHandle & wgpu::BufferUsage::MapWrite) { | |
| // Note: we favor CPU write over GPU write if MapWrite is present. If buffer has GPU | |
| // writable usages, the GPU writable storage will be lazily created later. | |
| usagesToHandle &= ~wgpu::BufferUsage::MapWrite; | |
| DAWN_TRY_ASSIGN(mLastUpdatedStorage, | |
| GetOrCreateStorage(StorageType::CPUWritableConstantBuffer)); | |
| mMappableStorage = mLastUpdatedStorage; | |
| } else { | |
| // For constant buffer, the only supported GPU op is copy. So create one storage for | |
| // that. | |
| usagesToHandle &= ~wgpu::BufferUsage::CopyDst; | |
| DAWN_TRY_ASSIGN(mLastUpdatedStorage, | |
| GetOrCreateStorage(StorageType::GPUCopyDstConstantBuffer)); | |
| } | |
| } | |
| if (usagesToHandle == wgpu::BufferUsage::None) { | |
| return {}; | |
| } | |
| // Create separate storage for non-constant buffer usages if required. | |
| if (usagesToHandle & wgpu::BufferUsage::MapWrite) { | |
| // Note: we only need one CPU writable storage. If there are both const buffer and | |
| // non-const buffer usages, we favor CPU writable const buffer first. Since that's most | |
| // likely the common use case where users want to update const buffer on CPU. | |
| DAWN_ASSERT(mMappableStorage == nullptr); | |
| usagesToHandle &= ~wgpu::BufferUsage::MapWrite; | |
| // If a buffer is created with both Storage and MapWrite usages, then | |
| // we will lazily create a GPU writable storage later. Note: we favor CPU writable | |
| // over GPU writable when creating non-constant buffer storage. This is to optimize | |
| // the most common cases where MapWrite buffers are mostly updated by CPU. | |
| DAWN_TRY_ASSIGN(mLastUpdatedStorage, | |
| GetOrCreateStorage(StorageType::CPUWritableNonConstantBuffer)); | |
| mMappableStorage = mLastUpdatedStorage; | |
| } else { | |
| usagesToHandle &= ~wgpu::BufferUsage::CopyDst; | |
| DAWN_TRY_ASSIGN(mLastUpdatedStorage, | |
| GetOrCreateStorage(StorageType::GPUWritableNonConstantBuffer)); | |
| } | |
| return {}; | |
| } | |
| ResultOrError<GPUUsableBuffer::Storage*> GPUUsableBuffer::GetOrCreateStorage( | |
| StorageType storageType) { | |
| if (mStorages[storageType]) { | |
| return mStorages[storageType].Get(); | |
| } | |
| Device* device = ToBackend(GetDevice()); | |
| auto AliasToMappableAndGPUWritableStorage = [&]() -> ResultOrError<Storage*> { | |
| // Share a single MappableAndGPUWritable storage. | |
| DAWN_TRY_ASSIGN(mStorages[storageType], | |
| GetOrCreateStorage(StorageType::MappableAndGPUWritable)); | |
| return mStorages[storageType].Get(); | |
| }; | |
| D3D11_BUFFER_DESC bufferDescriptor; | |
| bufferDescriptor.ByteWidth = GetAllocatedSize(); | |
| bufferDescriptor.StructureByteStride = 0; | |
| switch (storageType) { | |
| case StorageType::CPUWritableConstantBuffer: | |
| bufferDescriptor.Usage = D3D11_USAGE_DYNAMIC; | |
| bufferDescriptor.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; | |
| bufferDescriptor.BindFlags = D3D11_BIND_CONSTANT_BUFFER; | |
| bufferDescriptor.MiscFlags = 0; | |
| break; | |
| case StorageType::GPUCopyDstConstantBuffer: | |
| bufferDescriptor.Usage = D3D11_USAGE_DEFAULT; | |
| bufferDescriptor.CPUAccessFlags = 0; | |
| bufferDescriptor.BindFlags = D3D11_BIND_CONSTANT_BUFFER; | |
| bufferDescriptor.MiscFlags = 0; | |
| break; | |
| case StorageType::CPUWritableNonConstantBuffer: { | |
| // Need to exclude GPU writable usages because CPU writable buffer is not GPU writable | |
| // in D3D11. | |
| // TODO(crbug.com/479047477): We don't use MappableAndGPUWritable storage since | |
| // D3D11_USAGE_DYNAMIC seems to have better CPU write performance than | |
| // D3D11_USAGE_DEFAULT even on UMA devices. | |
| const auto nonUniformUsage = | |
| GetInternalUsage() & ~(wgpu::BufferUsage::Uniform | kD3D11GPUWriteUsages); | |
| bufferDescriptor.Usage = D3D11_USAGE_DYNAMIC; | |
| bufferDescriptor.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; | |
| bufferDescriptor.BindFlags = D3D11BufferBindFlags(nonUniformUsage); | |
| bufferDescriptor.MiscFlags = D3D11BufferMiscFlags(nonUniformUsage); | |
| if (bufferDescriptor.BindFlags == 0) { | |
| // Dynamic buffer requires at least one binding flag. If no binding flag is needed | |
| // (one example is MapWrite | QueryResolve), then use D3D11_BIND_INDEX_BUFFER. | |
| bufferDescriptor.BindFlags = D3D11_BIND_INDEX_BUFFER; | |
| DAWN_ASSERT(bufferDescriptor.MiscFlags == 0); | |
| } | |
| } break; | |
| case StorageType::GPUWritableNonConstantBuffer: { | |
| auto nonUniformUsage = GetInternalUsage() & ~wgpu::BufferUsage::Uniform; | |
| // Try to unify CPU and GPU writable storages if possible. | |
| if (IsMappable(nonUniformUsage) && | |
| CanMappableUseDefaultStorage(device, nonUniformUsage)) { | |
| return AliasToMappableAndGPUWritableStorage(); | |
| } | |
| // Need to exclude mapping usages. | |
| nonUniformUsage = nonUniformUsage & ~kMappableBufferUsages; | |
| bufferDescriptor.Usage = D3D11_USAGE_DEFAULT; | |
| bufferDescriptor.CPUAccessFlags = 0; | |
| bufferDescriptor.BindFlags = D3D11BufferBindFlags(nonUniformUsage); | |
| bufferDescriptor.MiscFlags = D3D11BufferMiscFlags(nonUniformUsage); | |
| } break; | |
| case StorageType::Staging: { | |
| auto nonUniformUsage = GetInternalUsage() & ~wgpu::BufferUsage::Uniform; | |
| // Try to use MapOnDefaultBuffers feature if possible. | |
| if (CanMappableUseDefaultStorage(device, nonUniformUsage)) { | |
| return AliasToMappableAndGPUWritableStorage(); | |
| } | |
| bufferDescriptor.Usage = D3D11_USAGE_STAGING; | |
| bufferDescriptor.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE; | |
| bufferDescriptor.BindFlags = 0; | |
| bufferDescriptor.MiscFlags = 0; | |
| } break; | |
| case StorageType::MappableAndGPUWritable: { | |
| const auto nonUniformUsage = | |
| GetInternalUsage() & ~(kMappableBufferUsages | wgpu::BufferUsage::Uniform); | |
| bufferDescriptor.Usage = D3D11_USAGE_DEFAULT; | |
| // TODO(crbug.com/479047477): Investigate the performance of including both read & | |
| // write accesses. | |
| bufferDescriptor.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE; | |
| bufferDescriptor.BindFlags = D3D11BufferBindFlags(nonUniformUsage); | |
| bufferDescriptor.MiscFlags = D3D11BufferMiscFlags(nonUniformUsage); | |
| } break; | |
| case StorageType::Count: | |
| DAWN_UNREACHABLE(); | |
| } | |
| ComPtr<ID3D11Buffer> buffer; | |
| DAWN_TRY(CheckOutOfMemoryHRESULT( | |
| ToBackend(GetDevice())->GetD3D11Device()->CreateBuffer(&bufferDescriptor, nullptr, &buffer), | |
| "ID3D11Device::CreateBuffer")); | |
| mStorages[storageType] = AcquireRef(new Storage(std::move(buffer))); | |
| SetStorageLabel(storageType); | |
| return mStorages[storageType].Get(); | |
| } | |
| ResultOrError<GPUUsableBuffer::Storage*> GPUUsableBuffer::GetOrCreateDstCopyableStorage() { | |
| if (mStorages[StorageType::GPUCopyDstConstantBuffer]) { | |
| return mStorages[StorageType::GPUCopyDstConstantBuffer].Get(); | |
| } | |
| if (mStorages[StorageType::GPUWritableNonConstantBuffer]) { | |
| return mStorages[StorageType::GPUWritableNonConstantBuffer].Get(); | |
| } | |
| if (GetInternalUsage() & wgpu::BufferUsage::Uniform) { | |
| return GetOrCreateStorage(StorageType::GPUCopyDstConstantBuffer); | |
| } | |
| return GetOrCreateStorage(StorageType::GPUWritableNonConstantBuffer); | |
| } | |
| MaybeError GPUUsableBuffer::SyncStorage(const ScopedCommandRecordingContext* commandContext, | |
| Storage* dstStorage) { | |
| DAWN_ASSERT(mLastUpdatedStorage); | |
| DAWN_ASSERT(dstStorage); | |
| if (mLastUpdatedStorage->GetRevision() == dstStorage->GetRevision()) { | |
| return {}; | |
| } | |
| DAWN_ASSERT(commandContext); | |
| // Must not have pending unmap. | |
| DAWN_CHECK(!mMappedData); | |
| if (dstStorage->SupportsCopyDst()) { | |
| commandContext->CopyResource(dstStorage->GetD3D11Buffer(), | |
| mLastUpdatedStorage->GetD3D11Buffer()); | |
| dstStorage->SetRevision(mLastUpdatedStorage->GetRevision()); | |
| return {}; | |
| } | |
| // TODO(42241146): This is a slow path. It's usually used by uncommon use cases: | |
| // - GPU writes a CPU writable buffer. | |
| DAWN_ASSERT(dstStorage->IsCPUWritable()); | |
| Storage* stagingStorage; | |
| DAWN_TRY_ASSIGN(stagingStorage, GetOrCreateStorage(StorageType::Staging)); | |
| DAWN_TRY(SyncStorage(commandContext, stagingStorage)); | |
| D3D11_MAPPED_SUBRESOURCE mappedSrcResource; | |
| DAWN_TRY(CheckHRESULT(commandContext->Map(stagingStorage->GetD3D11Buffer(), | |
| /*Subresource=*/0, D3D11_MAP_READ, | |
| /*MapFlags=*/0, &mappedSrcResource), | |
| "ID3D11DeviceContext::Map src")); | |
| auto MapAndCopy = [](const ScopedCommandRecordingContext* commandContext, ID3D11Buffer* dst, | |
| const void* srcData, size_t size) -> MaybeError { | |
| D3D11_MAPPED_SUBRESOURCE mappedDstResource; | |
| DAWN_TRY(CheckHRESULT(commandContext->Map(dst, | |
| /*Subresource=*/0, D3D11_MAP_WRITE_DISCARD, | |
| /*MapFlags=*/0, &mappedDstResource), | |
| "ID3D11DeviceContext::Map dst")); | |
| DAWN_UNSAFE_TODO(memcpy(mappedDstResource.pData, srcData, size)); | |
| commandContext->Unmap(dst, | |
| /*Subresource=*/0); | |
| return {}; | |
| }; | |
| auto result = MapAndCopy(commandContext, dstStorage->GetD3D11Buffer(), mappedSrcResource.pData, | |
| GetAllocatedSize()); | |
| commandContext->Unmap(stagingStorage->GetD3D11Buffer(), | |
| /*Subresource=*/0); | |
| if (result.IsError()) { | |
| return result; | |
| } | |
| dstStorage->SetRevision(mLastUpdatedStorage->GetRevision()); | |
| return {}; | |
| } | |
| void GPUUsableBuffer::IncrStorageRevAndMakeLatest( | |
| const ScopedCommandRecordingContext* commandContext, | |
| Storage* dstStorage) { | |
| DAWN_ASSERT(dstStorage->GetRevision() == mLastUpdatedStorage->GetRevision()); | |
| dstStorage->SetRevision(dstStorage->GetRevision() + 1); | |
| mLastUpdatedStorage = dstStorage; | |
| if (dstStorage->IsGPUWritable() && IsMappable(GetInternalUsage()) && | |
| dstStorage != mMappableStorage) { | |
| // If this buffer is mappable and the last updated storage is GPU writable, we need to | |
| // update the staging storage when the command buffer is flushed. | |
| // This is to make sure the staging storage will contain the up-to-date GPU modified data. | |
| // Note: we only do this if the mappable and GPU writable storages are separate. | |
| commandContext->AddBufferForSyncingWithCPU(this); | |
| } | |
| } | |
| MaybeError GPUUsableBuffer::SyncGPUWritesToStaging( | |
| const ScopedCommandRecordingContext* commandContext) { | |
| DAWN_ASSERT(IsMappable(GetInternalUsage())); | |
| // Only sync staging storage. Later other CPU writable storages can be updated by | |
| // copying from staging storage with Map(MAP_WRITE_DISCARD) which won't stall the CPU. | |
| // Otherwise, since CPU writable storages don't support CopyDst, it would require a CPU | |
| // stall in order to sync them here. | |
| Storage* stagingStorage; | |
| DAWN_TRY_ASSIGN(stagingStorage, GetOrCreateStorage(StorageType::Staging)); | |
| return SyncStorage(commandContext, stagingStorage); | |
| } | |
| MaybeError GPUUsableBuffer::MapInternal(const ScopedCommandRecordingContext* commandContext, | |
| wgpu::MapMode mode) { | |
| DAWN_ASSERT(!mMappedData); | |
| DAWN_ASSERT(mMappableStorage); | |
| if (mMappableStorage->IsDynamic()) { | |
| DAWN_ASSERT(mode == wgpu::MapMode::Write); | |
| // Dynamic buffer can only use D3D11_MAP_WRITE_NO_OVERWRITE | |
| mD3DMapTypeUsed = D3D11_MAP_WRITE_NO_OVERWRITE; | |
| } else { | |
| if (NeedsInitialization() || mode == (wgpu::MapMode::Read | wgpu::MapMode::Write)) { | |
| // Map buffer with D3D11_MAP_READ_WRITE because we need write permission to initialize | |
| // the buffer. | |
| // TODO(dawn:1705): investigate the performance impact of mapping with | |
| // D3D11_MAP_READ_WRITE. | |
| mD3DMapTypeUsed = D3D11_MAP_READ_WRITE; | |
| } else { | |
| if (mode & wgpu::MapMode::Read) { | |
| mD3DMapTypeUsed = D3D11_MAP_READ; | |
| } else { | |
| mD3DMapTypeUsed = D3D11_MAP_WRITE; | |
| } | |
| } | |
| } | |
| // Sync previously modified content before mapping. | |
| DAWN_TRY(SyncStorage(commandContext, mMappableStorage)); | |
| D3D11_MAPPED_SUBRESOURCE mappedResource; | |
| DAWN_TRY(CheckHRESULT(commandContext->Map(mMappableStorage->GetD3D11Buffer(), | |
| /*Subresource=*/0, mD3DMapTypeUsed, | |
| /*MapFlags=*/0, &mappedResource), | |
| "ID3D11DeviceContext::Map")); | |
| mMappedData = static_cast<uint8_t*>(mappedResource.pData); | |
| return {}; | |
| } | |
| void GPUUsableBuffer::UnmapInternal(const ScopedCommandRecordingContext* commandContext) { | |
| DAWN_ASSERT(mMappedData); | |
| DAWN_ASSERT(mMappableStorage); | |
| commandContext->Unmap(mMappableStorage->GetD3D11Buffer(), | |
| /*Subresource=*/0); | |
| mMappedData = nullptr; | |
| // Only increment revision if the buffer was mapped for writing. | |
| if (mD3DMapTypeUsed != D3D11_MAP_READ) { | |
| IncrStorageRevAndMakeLatest(commandContext, mMappableStorage); | |
| } | |
| } | |
| ResultOrError<ID3D11Buffer*> GPUUsableBuffer::GetD3D11ConstantBuffer( | |
| const ScopedCommandRecordingContext* commandContext) { | |
| auto* storage = mStorages[StorageType::CPUWritableConstantBuffer].Get(); | |
| if (storage && storage->GetRevision() == mLastUpdatedStorage->GetRevision()) { | |
| // The CPUWritableConstantBuffer is up to date, use it directly. | |
| return storage->GetD3D11Buffer(); | |
| } | |
| // In all other cases we are going to use the GPUCopyDstConstantBuffer because, either it is up | |
| // to date, or we need to update the ConstantBuffer data and doing a CopyResource on the GPU is | |
| // always more efficient than paths involving a memcpy (or potentially a stall). | |
| DAWN_TRY_ASSIGN(storage, GetOrCreateStorage(StorageType::GPUCopyDstConstantBuffer)); | |
| DAWN_TRY(SyncStorage(commandContext, storage)); | |
| return storage->GetD3D11Buffer(); | |
| } | |
| ResultOrError<ID3D11Buffer*> GPUUsableBuffer::GetD3D11NonConstantBuffer( | |
| const ScopedCommandRecordingContext* commandContext) { | |
| auto* storage = mStorages[StorageType::CPUWritableNonConstantBuffer].Get(); | |
| if (storage && storage->GetRevision() == mLastUpdatedStorage->GetRevision()) { | |
| // The CPUWritableNonConstantBuffer is up to date, use it directly. | |
| return storage->GetD3D11Buffer(); | |
| } | |
| // In all other cases we are going to use the GPUWritableNonConstantBuffe because, either it is | |
| // up to date, or we need to update the non-ConstantBuffer data and doing a CopyResource on the | |
| // GPU is always more efficient than paths involving a memcpy (or potentially a stall). | |
| DAWN_TRY_ASSIGN(storage, GetOrCreateStorage(StorageType::GPUWritableNonConstantBuffer)); | |
| DAWN_TRY(SyncStorage(commandContext, storage)); | |
| return storage->GetD3D11Buffer(); | |
| } | |
| ID3D11Buffer* GPUUsableBuffer::GetD3D11ConstantBufferForTesting() { | |
| if (!mStorages[StorageType::CPUWritableConstantBuffer] && | |
| !mStorages[StorageType::GPUCopyDstConstantBuffer]) { | |
| return nullptr; | |
| } | |
| auto tempCommandContext = ToBackend(GetDevice()->GetQueue()) | |
| ->GetScopedPendingCommandContext(QueueBase::SubmitMode::Normal); | |
| ID3D11Buffer* buffer; | |
| if (GetDevice()->ConsumedError(GetD3D11ConstantBuffer(&tempCommandContext), &buffer)) { | |
| return nullptr; | |
| } | |
| return buffer; | |
| } | |
| ID3D11Buffer* GPUUsableBuffer::GetD3D11NonConstantBufferForTesting() { | |
| if (!mStorages[StorageType::CPUWritableNonConstantBuffer] && | |
| !mStorages[StorageType::GPUWritableNonConstantBuffer]) { | |
| return nullptr; | |
| } | |
| auto tempCommandContext = ToBackend(GetDevice()->GetQueue()) | |
| ->GetScopedPendingCommandContext(QueueBase::SubmitMode::Normal); | |
| ID3D11Buffer* buffer; | |
| if (GetDevice()->ConsumedError(GetD3D11NonConstantBuffer(&tempCommandContext), &buffer)) { | |
| return nullptr; | |
| } | |
| return buffer; | |
| } | |
| ResultOrError<ComPtr<ID3D11ShaderResourceView>> | |
| GPUUsableBuffer::CreateD3D11ShaderResourceViewFromD3DBuffer(ID3D11Buffer* d3d11Buffer, | |
| uint64_t offset, | |
| uint64_t originalSize) { | |
| uint64_t size = Align(originalSize, 4); | |
| DAWN_CHECK(IsAligned(offset, 4u)); | |
| DAWN_CHECK(size <= GetAllocatedSize()); | |
| UINT firstElement = static_cast<UINT>(offset / 4); | |
| UINT numElements = static_cast<UINT>(size / 4); | |
| D3D11_SHADER_RESOURCE_VIEW_DESC desc; | |
| desc.Format = DXGI_FORMAT_R32_TYPELESS; | |
| desc.ViewDimension = D3D11_SRV_DIMENSION_BUFFEREX; | |
| desc.BufferEx.FirstElement = firstElement; | |
| desc.BufferEx.NumElements = numElements; | |
| desc.BufferEx.Flags = D3D11_BUFFEREX_SRV_FLAG_RAW; | |
| ComPtr<ID3D11ShaderResourceView> srv; | |
| DAWN_TRY(CheckHRESULT(ToBackend(GetDevice()) | |
| ->GetD3D11Device() | |
| ->CreateShaderResourceView(d3d11Buffer, &desc, &srv), | |
| "ShaderResourceView creation")); | |
| return std::move(srv); | |
| } | |
| ResultOrError<ComPtr<ID3D11UnorderedAccessView1>> | |
| GPUUsableBuffer::CreateD3D11UnorderedAccessViewFromD3DBuffer(ID3D11Buffer* d3d11Buffer, | |
| uint64_t offset, | |
| uint64_t originalSize) { | |
| uint64_t size = Align(originalSize, 4); | |
| DAWN_CHECK(IsAligned(offset, 4u)); | |
| DAWN_CHECK(size <= GetAllocatedSize()); | |
| UINT firstElement = static_cast<UINT>(offset / 4); | |
| UINT numElements = static_cast<UINT>(size / 4); | |
| D3D11_UNORDERED_ACCESS_VIEW_DESC1 desc; | |
| desc.Format = DXGI_FORMAT_R32_TYPELESS; | |
| desc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER; | |
| desc.Buffer.FirstElement = firstElement; | |
| desc.Buffer.NumElements = numElements; | |
| desc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_RAW; | |
| ComPtr<ID3D11UnorderedAccessView1> uav; | |
| DAWN_TRY(CheckHRESULT(ToBackend(GetDevice()) | |
| ->GetD3D11Device3() | |
| ->CreateUnorderedAccessView1(d3d11Buffer, &desc, &uav), | |
| "UnorderedAccessView creation")); | |
| return std::move(uav); | |
| } | |
| ResultOrError<ComPtr<ID3D11ShaderResourceView>> GPUUsableBuffer::UseAsSRV( | |
| const ScopedCommandRecordingContext* commandContext, | |
| uint64_t offset, | |
| uint64_t size) { | |
| ID3D11Buffer* d3dBuffer; | |
| DAWN_TRY_ASSIGN(d3dBuffer, GetD3D11NonConstantBuffer(commandContext)); | |
| auto key = std::make_tuple(d3dBuffer, offset, size); | |
| auto ite = mSRVCache.find(key); | |
| if (ite != mSRVCache.end()) { | |
| return ite->second; | |
| } | |
| ComPtr<ID3D11ShaderResourceView> srv; | |
| DAWN_TRY_ASSIGN(srv, CreateD3D11ShaderResourceViewFromD3DBuffer(d3dBuffer, offset, size)); | |
| mSRVCache[key] = srv; | |
| return std::move(srv); | |
| } | |
| ResultOrError<ComPtr<ID3D11UnorderedAccessView>> GPUUsableBuffer::UseAsUAV( | |
| const ScopedCommandRecordingContext* commandContext, | |
| uint64_t offset, | |
| uint64_t size) { | |
| Storage* storage = nullptr; | |
| DAWN_TRY_ASSIGN(storage, GetOrCreateStorage(StorageType::GPUWritableNonConstantBuffer)); | |
| DAWN_TRY(SyncStorage(commandContext, storage)); | |
| ComPtr<ID3D11UnorderedAccessView1> uav; | |
| { | |
| auto key = std::make_tuple(storage->GetD3D11Buffer(), offset, size); | |
| auto ite = mUAVCache.find(key); | |
| if (ite != mUAVCache.end()) { | |
| uav = ite->second; | |
| } else { | |
| DAWN_TRY_ASSIGN(uav, CreateD3D11UnorderedAccessViewFromD3DBuffer( | |
| storage->GetD3D11Buffer(), offset, size)); | |
| mUAVCache[key] = uav; | |
| } | |
| } | |
| // Since UAV will modify the storage's content, increment its revision. | |
| IncrStorageRevAndMakeLatest(commandContext, storage); | |
| return ComPtr<ID3D11UnorderedAccessView>(std::move(uav)); | |
| } | |
| MaybeError GPUUsableBuffer::UpdateD3D11ConstantBuffer( | |
| const ScopedCommandRecordingContext* commandContext, | |
| ID3D11Buffer* d3d11Buffer, | |
| bool firstTimeUpdate, | |
| uint64_t offset, | |
| const void* data, | |
| size_t size) { | |
| DAWN_ASSERT(size > 0); | |
| // For a full size write, UpdateSubresource1(D3D11_COPY_DISCARD) can be used to update | |
| // constant buffer. | |
| // WriteInternal() can be called with GetAllocatedSize(). We treat it as a full buffer write | |
| // as well. | |
| const bool fullSizeUpdate = size >= GetSize() && offset == 0; | |
| const bool canPartialUpdate = | |
| ToBackend(GetDevice())->GetDeviceInfo().supportsPartialConstantBufferUpdate; | |
| if (fullSizeUpdate || firstTimeUpdate) { | |
| const bool requiresFullAllocatedSizeWrite = !canPartialUpdate && !firstTimeUpdate; | |
| // Offset and size must be aligned with 16 for using UpdateSubresource1() on constant | |
| // buffer. | |
| size_t alignedOffset; | |
| if (offset < kConstantBufferUpdateAlignment - 1) { | |
| alignedOffset = 0; | |
| } else { | |
| DAWN_ASSERT(firstTimeUpdate); | |
| // For offset we align to lower value (<= offset). | |
| alignedOffset = Align(offset - (kConstantBufferUpdateAlignment - 1), | |
| kConstantBufferUpdateAlignment); | |
| } | |
| size_t alignedEnd; | |
| if (requiresFullAllocatedSizeWrite) { | |
| alignedEnd = GetAllocatedSize(); | |
| } else { | |
| alignedEnd = Align(offset + size, kConstantBufferUpdateAlignment); | |
| } | |
| size_t alignedSize = alignedEnd - alignedOffset; | |
| DAWN_CHECK((alignedSize % kConstantBufferUpdateAlignment) == 0); | |
| DAWN_CHECK(alignedSize <= GetAllocatedSize()); | |
| DAWN_ASSERT(offset >= alignedOffset); | |
| // Extra bytes on the left of offset we could write to. This is only valid if | |
| // firstTimeUpdate = true. | |
| size_t leftExtraBytes = offset - alignedOffset; | |
| DAWN_ASSERT(leftExtraBytes == 0 || firstTimeUpdate); | |
| // The layout of the buffer is like this: | |
| // |..........................| leftExtraBytes | data | ............... | | |
| // |<----------------- offset ---------------->|<-- size -->| | |
| // |<----- alignedOffset ---->|<--------- alignedSize --------->| | |
| HeapArray<uint8_t> alignedBuffer; | |
| if (size != alignedSize) { | |
| // SAFETY: The copy() should initialize all memory that actually gets read. | |
| alignedBuffer = DAWN_UNSAFE_BUFFERS(HeapArray<uint8_t>::Uninit(alignedSize)); | |
| std::ranges::copy( | |
| DAWN_UNSAFE_TODO(Span<const uint8_t>{static_cast<const uint8_t*>(data), size}), | |
| alignedBuffer.begin() + leftExtraBytes); | |
| data = alignedBuffer.data(); | |
| } | |
| D3D11_BOX dstBox; | |
| dstBox.left = static_cast<UINT>(alignedOffset); | |
| dstBox.top = 0; | |
| dstBox.front = 0; | |
| dstBox.right = static_cast<UINT>(alignedOffset + alignedSize); | |
| dstBox.bottom = 1; | |
| dstBox.back = 1; | |
| // For full buffer write, D3D11_COPY_DISCARD is used to avoid GPU CPU synchronization. | |
| commandContext->UpdateSubresource1(d3d11Buffer, /*DstSubresource=*/0, | |
| requiresFullAllocatedSizeWrite ? nullptr : &dstBox, data, | |
| /*SrcRowPitch=*/0, | |
| /*SrcDepthPitch=*/0, | |
| /*CopyFlags=*/D3D11_COPY_DISCARD); | |
| return {}; | |
| } | |
| // If copy offset and size are not 16 bytes aligned, we have to create a staging buffer for | |
| // transfer the data to constant buffer. | |
| Ref<BufferBase> stagingBuffer; | |
| DAWN_TRY_ASSIGN(stagingBuffer, ToBackend(GetDevice())->GetStagingBuffer(commandContext, size)); | |
| { | |
| auto scopedUseStaging = stagingBuffer->UseInternal(); | |
| DAWN_TRY(ToBackend(stagingBuffer) | |
| ->WriteInternal(commandContext, 0, data, size, | |
| /*isInitialWrite=*/true)); | |
| DAWN_TRY(ToBackend(stagingBuffer.Get()) | |
| ->CopyToInternal(commandContext, | |
| /*sourceOffset=*/0, | |
| /*size=*/size, this, offset)); | |
| } | |
| ToBackend(GetDevice())->ReturnStagingBuffer(std::move(stagingBuffer)); | |
| return {}; | |
| } | |
| MaybeError GPUUsableBuffer::WriteInternal(const ScopedCommandRecordingContext* commandContext, | |
| uint64_t offset, | |
| const void* data, | |
| size_t size, | |
| bool isInitialWrite) { | |
| if (size == 0) { | |
| return {}; | |
| } | |
| // Map the buffer if it is possible, so WriteInternal() can write to the mapped memory | |
| // directly. | |
| // TODO(crbug.com/345471009): Consider mapping the buffer for non-clearing writes when | |
| // it's not in use by the GPU. This would avoid allocating additional GPU storage. | |
| // However, checking GetLastUsageSerial() is unreliable here because Queue::Submit() | |
| // may have already updated it before entering this function. In practice, this is | |
| // uncommon for mappable buffers since users typically update them via MapAsync when | |
| // they know the buffer is idle. | |
| const bool alreadyMappedForWrite = mMappedData && mD3DMapTypeUsed != D3D11_MAP_READ; | |
| if ((IsCPUWritable() && isInitialWrite) || alreadyMappedForWrite) { | |
| // If buffer is already mapped, creating ScopedMap is a no-op. | |
| ScopedMap scopedMap; | |
| DAWN_TRY_ASSIGN(scopedMap, ScopedMap::Create(commandContext, this, wgpu::MapMode::Write)); | |
| DAWN_ASSERT(scopedMap.GetMappedData()); | |
| DAWN_UNSAFE_TODO(memcpy(scopedMap.GetMappedData() + offset, data, size)); | |
| return {}; | |
| } | |
| // Mark the buffer as used in pending commands if the mapping path above wasn't taken. | |
| // Mapped writes complete synchronously and don't require tracking. | |
| DAWN_TRY(TrackUsage(commandContext, GetDevice()->GetQueue()->GetPendingCommandSerial())); | |
| // WriteInternal() can be called with GetAllocatedSize(). We treat it as a full buffer write | |
| // as well. | |
| bool fullSizeWrite = size >= GetSize() && offset == 0; | |
| // Mapping buffer at this point would stall the CPU. We will create a GPU copyable | |
| // storage and use UpdateSubresource on it below instead. Note if we have both const buffer & | |
| // non-const buffer, we favor writing to non-const buffer, because it has no alignment | |
| // requirement. | |
| Storage* gpuCopyableStorage = mStorages[StorageType::GPUWritableNonConstantBuffer].Get(); | |
| if (!gpuCopyableStorage) { | |
| DAWN_TRY_ASSIGN(gpuCopyableStorage, GetOrCreateDstCopyableStorage()); | |
| } | |
| if (fullSizeWrite) { | |
| // If this is a full overwrite, no need to copy the old content. | |
| // Just need to copy the revision number. | |
| DAWN_ASSERT(mLastUpdatedStorage); | |
| gpuCopyableStorage->SetRevision(mLastUpdatedStorage->GetRevision()); | |
| } else { | |
| DAWN_TRY(SyncStorage(commandContext, gpuCopyableStorage)); | |
| } | |
| const bool firstTimeUpdate = gpuCopyableStorage->IsFirstRevision(); | |
| // We are going to write to the storage in all code paths, update the revision already. | |
| IncrStorageRevAndMakeLatest(commandContext, gpuCopyableStorage); | |
| if (gpuCopyableStorage->IsConstantBuffer()) { | |
| return UpdateD3D11ConstantBuffer(commandContext, gpuCopyableStorage->GetD3D11Buffer(), | |
| firstTimeUpdate, offset, data, size); | |
| } | |
| D3D11_BOX box; | |
| box.left = static_cast<UINT>(offset); | |
| box.top = 0; | |
| box.front = 0; | |
| box.right = static_cast<UINT>(offset + size); | |
| box.bottom = 1; | |
| box.back = 1; | |
| commandContext->UpdateSubresource1(gpuCopyableStorage->GetD3D11Buffer(), | |
| /*DstSubresource=*/0, | |
| /*pDstBox=*/&box, data, | |
| /*SrcRowPitch=*/0, | |
| /*SrcDepthPitch=*/0, | |
| /*CopyFlags=*/0); | |
| // No need to update constant buffer at this point, when command buffer wants to bind | |
| // the constant buffer in a render/compute pass, it will call GetD3D11ConstantBuffer() | |
| // and the constant buffer will be sync-ed there. WriteBuffer() cannot be called inside | |
| // render/compute pass so no need to sync here. | |
| return {}; | |
| } | |
| MaybeError GPUUsableBuffer::CopyToInternal(const ScopedCommandRecordingContext* commandContext, | |
| uint64_t sourceOffset, | |
| size_t size, | |
| Buffer* destination, | |
| uint64_t destinationOffset) { | |
| DAWN_TRY(TrackUsage(commandContext, GetDevice()->GetQueue()->GetPendingCommandSerial())); | |
| ID3D11Buffer* d3d11SourceBuffer = mLastUpdatedStorage->GetD3D11Buffer(); | |
| return destination->CopyFromD3DInternal(commandContext, d3d11SourceBuffer, sourceOffset, size, | |
| destinationOffset); | |
| } | |
| MaybeError GPUUsableBuffer::CopyFromD3DInternal(const ScopedCommandRecordingContext* commandContext, | |
| ID3D11Buffer* d3d11SourceBuffer, | |
| uint64_t sourceOffset, | |
| size_t size, | |
| uint64_t destinationOffset) { | |
| DAWN_TRY(TrackUsage(commandContext, GetDevice()->GetQueue()->GetPendingCommandSerial())); | |
| D3D11_BOX srcBox; | |
| srcBox.left = static_cast<UINT>(sourceOffset); | |
| srcBox.top = 0; | |
| srcBox.front = 0; | |
| srcBox.right = static_cast<UINT>(sourceOffset + size); | |
| srcBox.bottom = 1; | |
| srcBox.back = 1; | |
| Storage* gpuCopyableStorage; | |
| if (mLastUpdatedStorage->SupportsCopyDst()) { | |
| gpuCopyableStorage = mLastUpdatedStorage; | |
| } else { | |
| DAWN_TRY_ASSIGN(gpuCopyableStorage, GetOrCreateDstCopyableStorage()); | |
| DAWN_TRY(SyncStorage(commandContext, gpuCopyableStorage)); | |
| } | |
| commandContext->CopySubresourceRegion( | |
| gpuCopyableStorage->GetD3D11Buffer(), /*DstSubresource=*/0, | |
| /*DstX=*/destinationOffset, | |
| /*DstY=*/0, | |
| /*DstZ=*/0, d3d11SourceBuffer, /*SrcSubresource=*/0, &srcBox); | |
| IncrStorageRevAndMakeLatest(commandContext, gpuCopyableStorage); | |
| return {}; | |
| } | |
| MaybeError GPUUsableBuffer::PredicatedClear( | |
| const ScopedSwapStateCommandRecordingContext* commandContext, | |
| ID3D11Predicate* predicate, | |
| uint8_t clearValue, | |
| uint64_t offset, | |
| uint64_t size) { | |
| DAWN_ASSERT(size != 0); | |
| // Don't use mapping, mapping is not affected by ID3D11Predicate. | |
| // Allocate GPU writable storage and sync it. Note: we don't SetPredication() yet otherwise | |
| // it would affect the syncing. | |
| Storage* gpuWritableStorage; | |
| DAWN_TRY_ASSIGN(gpuWritableStorage, | |
| GetOrCreateStorage(StorageType::GPUWritableNonConstantBuffer)); | |
| DAWN_TRY(SyncStorage(commandContext, gpuWritableStorage)); | |
| // SetPredication() and clear the storage with UpdateSubresource1(). | |
| D3D11_BOX box; | |
| box.left = static_cast<UINT>(offset); | |
| box.top = 0; | |
| box.front = 0; | |
| box.right = static_cast<UINT>(offset + size); | |
| box.bottom = 1; | |
| box.back = 1; | |
| // TODO(350493305): Change function signature to accept a single uint64_t value. | |
| // So that we don't need to allocate a vector here. | |
| absl::InlinedVector<uint8_t, sizeof(uint64_t)> clearData(size, clearValue); | |
| // The update will *NOT* be performed if the predicate's data is false. | |
| commandContext->GetD3D11DeviceContext3()->SetPredication(predicate, false); | |
| commandContext->UpdateSubresource1(gpuWritableStorage->GetD3D11Buffer(), | |
| /*DstSubresource=*/0, | |
| /*pDstBox=*/&box, clearData.data(), | |
| /*SrcRowPitch=*/0, | |
| /*SrcDepthPitch=*/0, | |
| /*CopyFlags=*/0); | |
| commandContext->GetD3D11DeviceContext3()->SetPredication(nullptr, false); | |
| IncrStorageRevAndMakeLatest(commandContext, gpuWritableStorage); | |
| return {}; | |
| } | |
| ComPtr<ID3D11Buffer> GPUUsableBuffer::GetD3D11MappedBuffer() { | |
| return mMappedData ? mMappableStorage->GetD3D11Buffer() : nullptr; | |
| } | |
| } // namespace dawn::native::d3d11 |