| // 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 "src/dawn/native/Buffer.h" | |
| #include <atomic> | |
| #include <cstdio> | |
| #include <cstring> | |
| #include <limits> | |
| #include <optional> | |
| #include <string> | |
| #include <utility> | |
| #include "absl/container/flat_hash_map.h" | |
| #include "absl/strings/str_format.h" | |
| #include "dawn/native/ObjectType_autogen.h" | |
| #include "dawn/native/ValidationUtils_autogen.h" | |
| #include "dawn/platform/DawnPlatform.h" | |
| #include "partition_alloc/pointers/raw_ptr.h" | |
| #include "src/dawn/common/Constants.h" | |
| #include "src/dawn/common/StringViewUtils.h" | |
| #include "src/dawn/native/Adapter.h" | |
| #include "src/dawn/native/CallbackTaskManager.h" | |
| #include "src/dawn/native/ChainUtils.h" | |
| #include "src/dawn/native/Commands.h" | |
| #include "src/dawn/native/Device.h" | |
| #include "src/dawn/native/DynamicUploader.h" | |
| #include "src/dawn/native/Error.h" | |
| #include "src/dawn/native/ErrorData.h" | |
| #include "src/dawn/native/EventManager.h" | |
| #include "src/dawn/native/Instance.h" | |
| #include "src/dawn/native/PhysicalDevice.h" | |
| #include "src/dawn/native/Queue.h" | |
| #include "src/dawn/native/SystemEvent.h" | |
| #include "src/dawn/native/TexelBufferView.h" | |
| #include "src/dawn/platform/tracing/TraceEvent.h" | |
| #include "src/utils/assert.h" | |
| #include "src/utils/compiler.h" | |
| #include "src/utils/heap_array.h" | |
| #include "src/utils/numeric.h" | |
| namespace dawn::native { | |
| namespace { | |
| constexpr const char kConcurrentUseError[] = "Concurrent buffer operations are not allowed"; | |
| class ErrorBuffer final : public BufferBase { | |
| public: | |
| ErrorBuffer(DeviceBase* device, const BufferDescriptor* descriptor) | |
| : BufferBase(device, descriptor, ObjectBase::kError) { | |
| mAllocatedSize = descriptor->size; | |
| if (device->GetState() == DeviceBase::State::Destroyed) { | |
| // If the device is destroyed, it can't track the object. Initialize mSelfTrackList | |
| // instead for use below. | |
| mSelfTrackList.emplace(); | |
| } | |
| // Track the ErrorBuffer for destruction. If the device is destroyed, this will track it on | |
| // an internal list so it can be unmapped when the buffer is destroyed. | |
| // TODO(crbug.com/42241190): Calling device.Destroy() *again* won't unmap this buffer. | |
| // Need to fix this, OR change the spec to disallow mapping-at-creation after the device | |
| // is destroyed. (Note it should always be allowed on *non-destroyed* lost devices.) | |
| GetObjectTrackingList()->Track(this); | |
| } | |
| private: | |
| bool IsCPUWritableAtCreation() const override { return true; } | |
| MaybeError MapAtCreationImpl() override { | |
| DAWN_CHECK(!mFakeMappedData); | |
| uint64_t size = GetSize(); | |
| if (size < std::numeric_limits<size_t>::max()) { | |
| mFakeMappedData = | |
| // SAFETY: Frontend is responsible for initializing MapAtCreation memory. | |
| DAWN_UNSAFE_BUFFERS( | |
| HeapArray<std::byte>::Uninit(checked_cast<size_t>(size), std::nothrow)); | |
| } | |
| if (!mFakeMappedData) { | |
| return DAWN_OUT_OF_MEMORY_ERROR( | |
| "Failed to allocate memory to map ErrorBuffer at creation."); | |
| } | |
| return {}; | |
| } | |
| MaybeError FinalizeMapImpl(BufferState newState) override { return {}; } | |
| MaybeError MapAsyncImpl(wgpu::MapMode mode, size_t offset, size_t size) override { | |
| DAWN_UNREACHABLE(); | |
| } | |
| Span<std::byte> GetMappedRangeImpl(size_t offset, size_t size) override { | |
| return mFakeMappedData.subspan(offset, size); | |
| } | |
| void UnmapImpl(BufferState oldState, BufferState newState) override { mFakeMappedData = {}; } | |
| ApiObjectList* GetObjectTrackingList() override { | |
| if (mSelfTrackList) { | |
| return &*mSelfTrackList; | |
| } | |
| return BufferBase::GetObjectTrackingList(); | |
| } | |
| HeapArray<std::byte> mFakeMappedData; | |
| std::optional<ApiObjectList> mSelfTrackList; | |
| }; | |
| // GetMappedRange on a zero-sized buffer returns a pointer to this value. | |
| static uint32_t sZeroSizedMappingData = 0xCAFED00D; | |
| // Validates the combinations of MapRead/MapWrite buffer usages with other usages against the device | |
| // features. | |
| MaybeValError ValidateBufferUsageCombinations(const DeviceBase* device, wgpu::BufferUsage usage) { | |
| // With `BufferMapExtendedUsages`, both MapRead and MapWrite can be combined with any other | |
| // buffer usage. | |
| if (device->HasFeature(Feature::BufferMapExtendedUsages)) { | |
| return {}; | |
| } | |
| if (usage & wgpu::BufferUsage::MapRead) { | |
| const wgpu::BufferUsage kMapReadAllowedUsages = | |
| wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst; | |
| DAWN_INVALID_IF( | |
| !IsSubset(usage, kMapReadAllowedUsages), | |
| "Buffer usages (%s) is invalid. If a buffer usage contains %s the only other allowed " | |
| "usage is %s.", | |
| usage, wgpu::BufferUsage::MapRead, wgpu::BufferUsage::CopyDst); | |
| } | |
| if (usage & wgpu::BufferUsage::MapWrite) { | |
| if (device->HasFeature(Feature::BufferMapWriteExtendedUsages)) { | |
| // MapWrite can be combined with any other usage except MapRead. | |
| DAWN_INVALID_IF( | |
| usage & wgpu::BufferUsage::MapRead, | |
| "Buffer usages (%s) is invalid. %s cannot be combined with %s when %s is enabled.", | |
| usage, wgpu::BufferUsage::MapWrite, wgpu::BufferUsage::MapRead, | |
| ToCppAPI(Feature::BufferMapWriteExtendedUsages)); | |
| } else { | |
| const wgpu::BufferUsage kMapWriteAllowedUsages = | |
| wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc; | |
| DAWN_INVALID_IF( | |
| !IsSubset(usage, kMapWriteAllowedUsages), | |
| "Buffer usages (%s) is invalid. If a buffer usage contains %s the only other " | |
| "allowed usage is %s.", | |
| usage, wgpu::BufferUsage::MapWrite, wgpu::BufferUsage::CopySrc); | |
| } | |
| } | |
| return {}; | |
| } | |
| } // anonymous namespace | |
| ResultOrValError<UnpackedPtr<TexelBufferViewDescriptor>> ValidateTexelBufferViewDescriptor( | |
| const BufferBase* buffer, | |
| const TexelBufferViewDescriptor* descriptor) { | |
| UnpackedPtr<TexelBufferViewDescriptor> desc; | |
| DAWN_TRY_ASSIGN(desc, ValidateAndUnpack(descriptor)); | |
| DAWN_INVALID_IF(!(buffer->GetUsage() & wgpu::BufferUsage::TexelBuffer), | |
| "Buffer usage (%s) missing TexelBuffer bit.", buffer->GetUsage()); | |
| uint64_t size = desc->size == wgpu::kWholeSize ? buffer->GetSize() - desc->offset : desc->size; | |
| DAWN_INVALID_IF(desc->offset > buffer->GetSize() || size > buffer->GetSize() - desc->offset, | |
| "Texel buffer view range (offset %u, size %u) exceeds buffer size %u.", | |
| desc->offset, size, buffer->GetSize()); | |
| const Format* formatInfo = nullptr; | |
| DAWN_TRY_ASSIGN(formatInfo, ValidateTexelBufferFormat(buffer->GetDevice(), desc->format)); | |
| uint32_t texelSize = formatInfo->GetAspectInfo(Aspect::Color).block.byteSize; | |
| DAWN_INVALID_IF(desc->offset % kTexelBufferOffsetAlignment != 0, | |
| "Texel buffer view offset (%u) must be a multiple of %u.", desc->offset, | |
| kTexelBufferOffsetAlignment); | |
| DAWN_INVALID_IF(desc->offset % texelSize != 0, | |
| "Texel buffer view offset (%u) must be %u-byte aligned.", desc->offset, | |
| texelSize); | |
| DAWN_INVALID_IF(size % texelSize != 0, | |
| "Texel buffer view size (%u) is not a multiple of texel size %u.", size, | |
| texelSize); | |
| return desc; | |
| } | |
| wgpu::BufferUsage ComputeInternalBufferUsages(const DeviceBase* device, | |
| wgpu::BufferUsage usage, | |
| size_t bufferSize) { | |
| // Add readonly storage usage if the buffer has a storage usage. The validation rules in | |
| // ValidateSyncScopeResourceUsage will make sure we don't use both at the same time. | |
| if (usage & wgpu::BufferUsage::Storage) { | |
| usage |= kReadOnlyStorageBuffer; | |
| } | |
| // Texel buffers support read-only access without requiring storage usage. | |
| if (usage & wgpu::BufferUsage::TexelBuffer) { | |
| usage |= kReadOnlyTexelBuffer; | |
| } | |
| // The query resolve buffer need to be used as a storage buffer in the internal compute | |
| // pipeline which does timestamp uint conversion for timestamp query, it requires the buffer | |
| // has Storage usage in the binding group. Implicitly add an InternalStorage usage which is | |
| // only compatible with InternalStorageBuffer binding type in BGL. It shouldn't be | |
| // compatible with StorageBuffer binding type and the query resolve buffer cannot be bound | |
| // as storage buffer if it's created without Storage usage. | |
| if (usage & wgpu::BufferUsage::QueryResolve) { | |
| usage |= kInternalStorageBuffer; | |
| } | |
| // We also add internal storage usage for Indirect buffers for some transformations before | |
| // DispatchIndirect calls on the backend (e.g. validations, support of [[num_workgroups]] on | |
| // D3D12), since these transformations involve binding them as storage buffers for use in a | |
| // compute pass. | |
| if (usage & wgpu::BufferUsage::Indirect) { | |
| usage |= kInternalStorageBuffer; | |
| } | |
| if (usage & wgpu::BufferUsage::CopyDst) { | |
| const bool useComputeForT2B = | |
| device->IsToggleEnabled(Toggle::UseBlitForDepth16UnormTextureToBufferCopy) || | |
| device->IsToggleEnabled(Toggle::UseBlitForDepth24PlusTextureToBufferCopy) || | |
| device->IsToggleEnabled(Toggle::UseBlitForDepth32FloatTextureToBufferCopy) || | |
| device->IsToggleEnabled(Toggle::UseBlitForStencilTextureToBufferCopy) || | |
| device->IsToggleEnabled(Toggle::UseBlitForSnormTextureToBufferCopy) || | |
| device->IsToggleEnabled(Toggle::UseBlitForBGRA8UnormTextureToBufferCopy) || | |
| device->IsToggleEnabled(Toggle::UseBlitForNonRGBAUnormTextureToBufferCopy) || | |
| device->IsToggleEnabled(Toggle::UseBlitForNonRGBAFloatTextureToBufferCopy) || | |
| device->IsToggleEnabled(Toggle::UseBlitForUintTextureToBufferCopy) || | |
| device->IsToggleEnabled(Toggle::UseBlitForSintTextureToBufferCopy) || | |
| device->IsToggleEnabled(Toggle::UseBlitForRGB9E5UfloatTextureCopy) || | |
| device->IsToggleEnabled(Toggle::UseBlitForRG11B10UfloatTextureCopy) || | |
| device->IsToggleEnabled(Toggle::UseBlitForFloat16TextureCopy) || | |
| device->IsToggleEnabled(Toggle::UseBlitForT2B); | |
| if (useComputeForT2B) { | |
| if (device->CanAddStorageUsageToBufferWithoutSideEffects(kInternalStorageBuffer, usage, | |
| bufferSize)) { | |
| // If the backend is ok with using this kind of buffer as storage buffer, we can add | |
| // Storage usage in order to write to it in compute shader. | |
| usage |= kInternalStorageBuffer; | |
| } | |
| // We also need CopySrc usage in order to copy to a temporary buffer. | |
| // The temporary buffer is needed in cases when offset doesn't satisfy certain | |
| // conditions. Or if it's not possible to add kInternalStorageBuffer usage to the | |
| // buffer. | |
| usage |= kInternalCopySrcBuffer; | |
| } | |
| } | |
| if ((usage & wgpu::BufferUsage::CopySrc) && device->IsToggleEnabled(Toggle::UseBlitForB2T)) { | |
| if (device->CanAddStorageUsageToBufferWithoutSideEffects(kReadOnlyStorageBuffer, usage, | |
| bufferSize)) { | |
| // If the backend is ok with using this kind of buffer as readonly storage buffer, | |
| // we can add Storage usage in order to read from it in pixel shader. | |
| usage |= kReadOnlyStorageBuffer; | |
| } | |
| } | |
| return usage; | |
| } | |
| class BufferBase::MapAsyncEvent final : public EventManager::TrackedEvent { | |
| public: | |
| // Create an event backed by the given queue execution serial. | |
| MapAsyncEvent(DeviceBase* device, | |
| WeakRef<BufferBase> buffer, | |
| const WGPUBufferMapCallbackInfo& callbackInfo, | |
| ExecutionSerial serial) | |
| : TrackedEvent(static_cast<wgpu::CallbackMode>(callbackInfo.mode), | |
| device->GetQueue(), | |
| serial), | |
| mBuffer(buffer), | |
| mCallback(callbackInfo.callback), | |
| mUserdata1(callbackInfo.userdata1), | |
| mUserdata2(callbackInfo.userdata2) { | |
| // `this` is used as a unique ID to match begin/end events for concurrent MapAsync calls. | |
| // It's not a problem that same memory address could be reused for a future MapAsync call | |
| // since it won't be concurrent with an earlier call. | |
| TRACE_EVENT_BEGIN(DAWN_TRACE_CATEGORY(), "Buffer::APIMapAsync", | |
| perfetto::NamedTrack::FromPointer("Buffer::APIMapAsync", this)); | |
| } | |
| // Create an event that's ready at creation (for errors, etc.) | |
| MapAsyncEvent(const WGPUBufferMapCallbackInfo& callbackInfo, | |
| const std::string& message, | |
| WGPUMapAsyncStatus status) | |
| : TrackedEvent(static_cast<wgpu::CallbackMode>(callbackInfo.mode), | |
| TrackedEvent::Completed{}), | |
| mStatus(status), | |
| mErrorMessage(message), | |
| mCallback(callbackInfo.callback), | |
| mUserdata1(callbackInfo.userdata1), | |
| mUserdata2(callbackInfo.userdata2) { | |
| DAWN_CHECK(mStatus != WGPUMapAsyncStatus_Success); | |
| } | |
| ~MapAsyncEvent() override { EnsureComplete(EventCompletionType::Shutdown); } | |
| // Notifies that buffer will be unmapped before map completes. If this runs it will be before | |
| // mutex is locked in Complete(). Complete() will run callback with status/message set here. | |
| // | |
| // `BufferBase::mPendingMapMutex` must locked before this function is called! | |
| void UnmapEarly(std::string_view abortMessage) { | |
| DAWN_CHECK(mStatus == WGPUMapAsyncStatus_Success); | |
| mStatus = WGPUMapAsyncStatus_Aborted; | |
| mErrorMessage = abortMessage; | |
| } | |
| private: | |
| void RunCallback(WGPUMapAsyncStatus status, std::string_view message) { | |
| mCallback(status, ToOutputStringView(message), mUserdata1.ExtractAsDangling(), | |
| mUserdata2.ExtractAsDangling()); | |
| } | |
| void Complete(EventCompletionType completionType) override { | |
| if (const auto* queueAndSerial = GetIfQueueAndSerial()) { | |
| if (auto queue = queueAndSerial->queue.Promote()) { | |
| TRACE_EVENT_END(DAWN_TRACE_CATEGORY(), | |
| perfetto::NamedTrack::FromPointer("Buffer::APIMapAsync", this)); | |
| } | |
| } | |
| if (completionType == EventCompletionType::Shutdown) { | |
| RunCallback(WGPUMapAsyncStatus_CallbackCancelled, | |
| "A valid external Instance reference no longer exists."); | |
| return; | |
| } | |
| // There are four paths through this code beyond this point: | |
| // 1. Event was created with completion serial and event completes before anything else | |
| // happens. This will acquire a strong ref, lock the mutex, reset `mPendingMapEvent`, | |
| // unlock the mutex, run FinalizeMap() and then run callback. | |
| // 2. Event was created with completion serial, the buffer was unmapped before now and | |
| // WeakRef is promoted successfully. This will lock the mutex, see `mStatus` is not | |
| // success, unlock the mutex and run the callback with the abort message. | |
| // 3. Event was created with completion serial, the buffer is unmapped before event | |
| // completes and weak ref fails to be promoted. The buffer was destroyed before this runs | |
| // but otherwise this finishes on the same path as #2. | |
| // 4. Event was created for an error and `mBuffer` was always null. This uses | |
| // `mErrorMessage`` and `mStatus` as set in the constructor when running the callback. | |
| Ref<BufferBase> buffer = mBuffer.Promote(); | |
| // Note that because the lock is a member of the Buffer, and because we need to hold the | |
| // lock for the duration of this function if it exists, we declare the lock after the Ref to | |
| // ensure that the lock is released before potentially the last Ref of the buffer is | |
| // dropped. This addresses a bug found in crbug.com/552762484. | |
| RecursiveMutex::AutoLock lock; | |
| if (buffer) { | |
| // Locking the mutex provides synchronization so that either path #1 or #2 is taken if | |
| // Complete() and Unmap() race on different threads. | |
| lock = RecursiveMutex::AutoLock(&buffer->mPendingMapMutex); | |
| if (mStatus == WGPUMapAsyncStatus_Success) { | |
| DAWN_CHECK(buffer->mPendingMapEvent == this); | |
| // Before completing a map event, we need to be certain that any work on the buffer | |
| // has actually completed. To guarantee this, any thread handling an execution | |
| // failure (e.g. Metal command buffer completion handler or Vulkan fence status | |
| // check) **must** mark the device lost before advancing the serial (which allows | |
| // events that assume successful execution, like this one, to complete). | |
| bool deviceLost = buffer->GetDevice()->IsLost(); // Atomic operation. | |
| if (deviceLost) { | |
| // The device was lost while MapAsync was pending. Ensure we don't complete the | |
| // MapAsync by aborting the map now before proceeding. | |
| std::ignore = buffer->UnmapEarly(BufferState::Unmapped, | |
| "Device was lost before MapAsync completed."); | |
| buffer->mState.notify_all(); // Notify the wait() in UnmapInternal(). | |
| } else { | |
| buffer->mPendingMapEvent = nullptr; | |
| } | |
| } | |
| } | |
| // UnmapEarly() either already ran or will never run so `mErrorMessage` is safe to access | |
| // without the mutex. | |
| if (mStatus != WGPUMapAsyncStatus_Success) { | |
| DAWN_CHECK(!mErrorMessage.empty()); | |
| RunCallback(mStatus, mErrorMessage); | |
| return; | |
| } | |
| DAWN_CHECK(buffer); | |
| MaybeError result = buffer->FinalizeMap(BufferState::Mapped); | |
| buffer->mState.notify_all(); // Notify the wait() in UnmapInternal(). | |
| if (result.IsError()) { | |
| std::unique_ptr<UnrecoverableError> error = result.AcquireError(); | |
| DAWN_CHECK(error->GetType() != InternalErrorType::Validation); | |
| std::string errorMsg = error->GetFormattedMessage(); | |
| buffer->GetDevice()->ConsumeError(std::move(error)); | |
| RunCallback(WGPUMapAsyncStatus_Error, errorMsg); | |
| } else { | |
| RunCallback(WGPUMapAsyncStatus_Success, ""); | |
| } | |
| } | |
| // MapAsyncEvent stores a WeakRef to the buffer so that it can access the mutex and update the | |
| // buffer's map state if it completes. | |
| WeakRef<BufferBase> mBuffer; | |
| // Both variables are set in error constructor or in UnmapEarly() if set. | |
| WGPUMapAsyncStatus mStatus = WGPUMapAsyncStatus_Success; | |
| std::string mErrorMessage = ""; | |
| WGPUBufferMapCallback mCallback = nullptr; | |
| raw_ptr<void> mUserdata1 = nullptr; | |
| raw_ptr<void> mUserdata2 = nullptr; | |
| }; | |
| ResultOrValError<UnpackedPtr<BufferDescriptor>> ValidateBufferDescriptor( | |
| DeviceBase* device, | |
| const BufferDescriptor* descriptor) { | |
| UnpackedPtr<BufferDescriptor> unpacked; | |
| DAWN_TRY_ASSIGN(unpacked, ValidateAndUnpack(descriptor)); | |
| if (!device->IsToggleEnabled(Toggle::AllowExperimentalAPIs)) { | |
| // Only the chained structs that are part of the stable ABI may be used. | |
| DAWN_TRY_CONTEXT(unpacked.ValidateStable(), | |
| "validating %s with allow_experimental_apis disabled", descriptor); | |
| } | |
| DAWN_TRY(ValidateBufferUsage(descriptor->usage)); | |
| if (const auto* hostMappedDesc = unpacked.Get<BufferHostMappedPointer>()) { | |
| uint32_t requiredAlignment = | |
| device->GetLimits().hostMappedPointerLimits.hostMappedPointerAlignment; | |
| DAWN_INVALID_IF(!device->HasFeature(Feature::HostMappedPointer), "%s requires %s.", | |
| hostMappedDesc->sType, ToCppAPI(Feature::HostMappedPointer)); | |
| DAWN_INVALID_IF(!IsAligned(static_cast<uint32_t>(descriptor->size), requiredAlignment), | |
| "Buffer size (%u) wrapping host-mapped memory was not aligned to %u.", | |
| descriptor->size, requiredAlignment); | |
| DAWN_INVALID_IF(!IsPtrAligned(hostMappedDesc->pointer, requiredAlignment), | |
| "Host-mapped memory pointer (%p) was not aligned to %u.", | |
| hostMappedDesc->pointer, requiredAlignment); | |
| // TODO(dawn:2018) consider allowing the host-mapped buffers to be mapped through WebGPU. | |
| DAWN_INVALID_IF( | |
| descriptor->mappedAtCreation, | |
| "Buffer created from host-mapped pointer requires mappedAtCreation to be false."); | |
| } | |
| wgpu::BufferUsage usage = descriptor->usage; | |
| DAWN_INVALID_IF(usage == wgpu::BufferUsage::None, "Buffer usages must not be 0."); | |
| if (usage & wgpu::BufferUsage::TexelBuffer) { | |
| DAWN_INVALID_IF(!device->AreTexelBuffersEnabled(), "%s is not enabled.", | |
| wgpu::WGSLLanguageFeatureName::TexelBuffers); | |
| } | |
| DAWN_TRY(ValidateBufferUsageCombinations(device, usage)); | |
| DAWN_INVALID_IF(descriptor->mappedAtCreation && descriptor->size % 4 != 0, | |
| "Buffer is mapped at creation but its size (%u) is not a multiple of 4.", | |
| descriptor->size); | |
| uint64_t maxBufferSize = device->GetLimits().v1.maxBufferSize; | |
| DAWN_INVALID_IF(descriptor->size > maxBufferSize, | |
| "Buffer size (%u) exceeds the max buffer size limit (%u).%s", descriptor->size, | |
| maxBufferSize, | |
| DAWN_INCREASE_LIMIT_MESSAGE(device->GetAdapter()->GetLimits().v1, maxBufferSize, | |
| descriptor->size)); | |
| return unpacked; | |
| } | |
| // Buffer | |
| // static | |
| bool BufferBase::IsMappedState(BufferState state) { | |
| return state == BufferBase::BufferState::Mapped || | |
| state == BufferBase::BufferState::MappedAtCreation; | |
| } | |
| BufferBase::BufferBase(DeviceBase* device, const UnpackedPtr<BufferDescriptor>& descriptor) | |
| : SharedResource(device, descriptor->label), | |
| mSize(descriptor->size), | |
| mUsage(descriptor->usage), | |
| mInternalUsage(ComputeInternalBufferUsages(device, | |
| descriptor->usage, | |
| checked_cast<size_t>(descriptor->size))), | |
| mIsHostMapped(descriptor.Has<BufferHostMappedPointer>()) { | |
| GetObjectTrackingList()->Track(this); | |
| } | |
| BufferBase::BufferBase(DeviceBase* device, | |
| const BufferDescriptor* descriptor, | |
| ObjectBase::ErrorTag tag) | |
| : SharedResource(device, tag, descriptor->label), | |
| mSize(descriptor->size), | |
| mUsage(descriptor->usage), | |
| mInternalUsage(descriptor->usage), | |
| mState(BufferState::Unmapped) {} | |
| BufferBase::~BufferBase() { | |
| BufferState state = mState.load(std::memory_order::acquire); | |
| DAWN_CHECK(state == BufferState::Unmapped || state == BufferState::Destroyed || | |
| state == BufferState::SharedMemoryNoAccess); | |
| DAWN_CHECK(mMappedRange.data() == nullptr); | |
| } | |
| void BufferBase::DestroyImpl(DestroyReason reason) { | |
| // If the initial state is Unmapped the compared_exchange() should be successful. If not the | |
| // current state has been loaded in `state` and loop body handles anything needed like unmapping | |
| // the buffer. | |
| BufferState state = BufferState::Unmapped; | |
| while ( | |
| !mState.compare_exchange_weak(state, BufferState::Destroyed, std::memory_order::acq_rel)) { | |
| switch (state) { | |
| case BufferState::Mapped: | |
| case BufferState::PendingMap: | |
| case BufferState::MappedAtCreation: { | |
| std::ignore = | |
| GetDevice()->ConsumedError(UnmapInternal(true), "calling %s.Destroy().", this); | |
| // The buffer state should be unmapped after UnmapInternal() returns. Use that state | |
| // in the next compare exchange but another thread can update the state causing this | |
| // loop to run again. | |
| state = BufferState::Unmapped; | |
| break; | |
| } | |
| case BufferState::InUse: { | |
| // This is never supposed to happen but another operation is happening concurrently | |
| // with API Destroy() call. | |
| std::unique_ptr<ValidationError> err = DAWN_VALIDATION_ERROR(kConcurrentUseError); | |
| GetDevice()->ConsumeError(std::move(err), "calling %s.Destroy().", this); | |
| while (mState.load(std::memory_order::acquire) == BufferState::InUse) { | |
| // Spin loop instead of wait() to avoid overhead of signal in map/unmap. | |
| } | |
| break; | |
| } | |
| case BufferState::Destroyed: | |
| DAWN_UNREACHABLE(); | |
| case BufferState::SharedMemoryNoAccess: { | |
| // Since we are destroying this buffer, we need to begin access on it so that we | |
| // can handle it as a normal buffer, assuming it is unmapped. Otherwise, if the | |
| // buffer was mapped at creation and never unmapped, OnBeginAccess will transition | |
| // |mState| to |MappedAtCreation| and this loop will run one more time to unmap it | |
| // appropriately. | |
| OnBeginAccess(); | |
| state = BufferState::Unmapped; | |
| break; | |
| } | |
| case BufferState::Unmapped: | |
| // Buffer is ready to be destroyed. | |
| break; | |
| } | |
| } | |
| mTexelBufferViews.Destroy(DestroyReason::EarlyDestroy); | |
| } | |
| std::optional<DeviceGuard> BufferBase::UseDeviceGuardForDestroy() { | |
| // Backends with thread-safe DestroyImpl() methods can override this to return nullopt. | |
| return GetDevice()->GetGuard(); | |
| } | |
| // static | |
| Ref<BufferBase> BufferBase::MakeError(DeviceBase* device, const BufferDescriptor* descriptor) { | |
| return AcquireRef(new ErrorBuffer(device, descriptor)); | |
| } | |
| ObjectType BufferBase::GetType() const { | |
| return ObjectType::Buffer; | |
| } | |
| uint64_t BufferBase::GetSize() const { | |
| return mSize; | |
| } | |
| uint64_t BufferBase::GetAllocatedSize() const { | |
| // The backend must initialize this value. | |
| return mAllocatedSize.value(); | |
| } | |
| wgpu::BufferUsage BufferBase::GetInternalUsage() const { | |
| DAWN_CHECK(!IsError()); | |
| return mInternalUsage; | |
| } | |
| wgpu::BufferUsage BufferBase::GetUsage() const { | |
| DAWN_CHECK(!IsError()); | |
| return mUsage; | |
| } | |
| wgpu::BufferUsage BufferBase::APIGetUsage() const { | |
| return mUsage; | |
| } | |
| wgpu::BufferMapState BufferBase::APIGetMapState() const { | |
| switch (mState.load(std::memory_order::acquire)) { | |
| case BufferState::Mapped: | |
| case BufferState::MappedAtCreation: | |
| return wgpu::BufferMapState::Mapped; | |
| case BufferState::PendingMap: | |
| return wgpu::BufferMapState::Pending; | |
| case BufferState::Unmapped: | |
| DAWN_CHECK(!mIsHostMapped); | |
| ABSL_FALLTHROUGH_INTENDED; | |
| case BufferState::Destroyed: | |
| case BufferState::InUse: | |
| case BufferState::SharedMemoryNoAccess: | |
| return wgpu::BufferMapState::Unmapped; | |
| default: | |
| DAWN_UNREACHABLE(); | |
| } | |
| } | |
| MaybeError BufferBase::FinalizeMap(BufferState newState) { | |
| // There are only 2 valid transitions: | |
| // 1) Nominal: PendingMap -> Mapped | |
| // 2) MappedAtCreation case because initial state is unmapped: Unmapped -> MappedAtCreation. | |
| BufferState oldState = mState.load(std::memory_order::acquire); | |
| DAWN_CHECK((oldState == BufferState::PendingMap && newState == BufferState::Mapped) || | |
| (oldState == BufferState::Unmapped && newState == BufferState::MappedAtCreation)); | |
| DAWN_TRY_WITH_CLEANUP(FinalizeMapImpl(newState), | |
| { mState.store(BufferState::Unmapped, std::memory_order::release); }); | |
| // Get the full allocated range on MappedAtCreation and not just the user visible range. | |
| size_t rangeSize = mMapSize; | |
| if (newState == BufferState::MappedAtCreation) { | |
| DAWN_ASSERT(mMapOffset == 0u); | |
| rangeSize = checked_cast<size_t>(GetAllocatedSize()); | |
| } | |
| if (mStagingBuffer) { | |
| mMappedRange = mStagingBuffer->GetMappedRangeImpl(mMapOffset, rangeSize); | |
| } else if (GetSize() == 0) { | |
| mMappedRange = ByteSpanFromRef(sZeroSizedMappingData); | |
| } else { | |
| mMappedRange = GetMappedRangeImpl(mMapOffset, rangeSize); | |
| } | |
| mState.store(newState, std::memory_order::release); | |
| return {}; | |
| } | |
| bool BufferBase::TryMapAtCreation(bool fakeOOMAtNativeMap) { | |
| MaybeError mapResult = | |
| fakeOOMAtNativeMap | |
| ? DAWN_OUT_OF_MEMORY_ERROR("DawnFakeBufferOOMForTesting fakeOOMAtNativeMap") | |
| : MapAtCreation(); | |
| if (mapResult.IsError()) { | |
| auto error = mapResult.AcquireError(); | |
| GetDevice()->EmitLog(wgpu::LoggingType::Error, error->GetFormattedMessage()); | |
| return false; | |
| } | |
| return true; | |
| } | |
| MaybeError BufferBase::MapAtCreation() { | |
| bool usingStagingBuffer = false; | |
| DAWN_TRY_ASSIGN(usingStagingBuffer, MapAtCreationInternal()); | |
| if (GetSize() == 0) { | |
| return {}; | |
| } | |
| DeviceBase* device = GetDevice(); | |
| // Don't zero-initialize buffers created from shared buffer memory at creation time. | |
| // They will be initialized in `BeginAccess()` based on the `initialized` flag in | |
| // `wgpu::SharedBufferMemoryBeginAccessDescriptor`. | |
| if (mSharedResourceMemoryContents != nullptr) { | |
| return {}; | |
| } | |
| if (device->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse) && | |
| !device->IsToggleEnabled(Toggle::DisableLazyClearForMappedAtCreationBuffer)) { | |
| // The staging buffer is created with `MappedAtCreation == true` and the main buffer will | |
| // actually get initialized when the staging data is copied in. (But we mark the main buffer | |
| // as initialized now.) | |
| if (!usingStagingBuffer) { | |
| GetFullMappedAllocatedRange().FillBytes(std::byte{0}); | |
| device->IncrementLazyClearCountForTesting(); | |
| } | |
| } else if (device->IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting)) { | |
| GetFullMappedAllocatedRange().FillBytes(std::byte{1}); | |
| } | |
| // Mark the buffer as initialized since we don't want to later clear it using the GPU since that | |
| // would overwrite what the client wrote using the CPU. | |
| SetInitialized(true); | |
| return {}; | |
| } | |
| ResultOrError<bool> BufferBase::MapAtCreationInternal() { | |
| DAWN_CHECK(mState.load(std::memory_order::acquire) == BufferState::Unmapped); | |
| Ref<BufferBase> stagingBuffer; | |
| // 0-sized buffers are not supposed to be written to. Return back any non-null pointer. | |
| // Skip handling 0-sized buffers so we don't try to map them in the backend. | |
| if (mSize != 0) { | |
| // Mappable buffers don't use a staging buffer and are just as if mapped through | |
| // MapAsync. | |
| if (IsCPUWritableAtCreation()) { | |
| DAWN_TRY(MapAtCreationImpl()); | |
| } else { | |
| // If any of these fail, the buffer will be deleted and replaced with an error | |
| // buffer. The staging buffer is used to return mappable data to initialize the | |
| // buffer contents. Allocate one as large as the real buffer size so that every byte | |
| // is initialized. | |
| // TODO(crbug.com/dawn/828): Suballocate and reuse memory from a larger staging | |
| // buffer so we don't create many small buffers. | |
| BufferDescriptor stagingBufferDesc = {}; | |
| stagingBufferDesc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::MapWrite; | |
| stagingBufferDesc.size = Align(GetAllocatedSize(), 4); | |
| stagingBufferDesc.mappedAtCreation = true; | |
| stagingBufferDesc.label = "Dawn_MappedAtCreationStaging"; | |
| DAWN_TRY_ASSIGN(stagingBuffer, GetDevice()->CreateBuffer(&stagingBufferDesc)); | |
| } | |
| } | |
| // Only set the state to mapped at creation if we did not fail any point in this helper. | |
| // Otherwise, if we override the default unmapped state before succeeding to create a | |
| // staging buffer, we will have issues when we try to destroy the buffer. | |
| mMapMode = wgpu::MapMode::Write; | |
| mMapOffset = 0; | |
| mMapSize = checked_cast<size_t>(mSize); | |
| mAllocatedMapSize = checked_cast<size_t>(GetAllocatedSize()); | |
| mStagingBuffer = std::move(stagingBuffer); | |
| mIsMappedAtCreation = true; | |
| DAWN_TRY(FinalizeMap(BufferState::MappedAtCreation)); | |
| return mStagingBuffer != nullptr; | |
| } | |
| BufferBase::BufferState BufferBase::GetState() const { | |
| return mState.load(std::memory_order::acquire); | |
| } | |
| wgpu::MapMode BufferBase::MapMode() const { | |
| return mMapMode; | |
| } | |
| size_t BufferBase::MapOffset() const { | |
| return mMapOffset; | |
| } | |
| size_t BufferBase::MapSize() const { | |
| return mMapSize; | |
| } | |
| BufferBase::ScopedUseBuffer BufferBase::UseInternal() { | |
| auto fromState = mState.exchange(BufferState::InUse, std::memory_order::acq_rel); | |
| DAWN_CHECK(fromState == BufferState::Unmapped); | |
| return ScopedUseBuffer(this); | |
| } | |
| ResultOrValError<BufferBase::ScopedUseBuffer> BufferBase::ValidateCanUseOnQueueNow() { | |
| DAWN_CHECK(!IsError()); | |
| switch (BufferState state = mState.load(std::memory_order::acquire)) { | |
| case BufferState::Destroyed: | |
| return DAWN_VALIDATION_ERROR("%s used in submit while destroyed.", this); | |
| case BufferState::Mapped: | |
| case BufferState::MappedAtCreation: | |
| return DAWN_VALIDATION_ERROR("%s used in submit while mapped.", this); | |
| case BufferState::PendingMap: | |
| return DAWN_VALIDATION_ERROR("%s used in submit while pending map.", this); | |
| case BufferState::SharedMemoryNoAccess: | |
| return DAWN_VALIDATION_ERROR("%s used in submit without shared memory access.", this); | |
| case BufferState::InUse: | |
| return DAWN_VALIDATION_ERROR(kConcurrentUseError); | |
| case BufferState::Unmapped: | |
| DAWN_TRY(TransitionState(state, BufferState::InUse)); | |
| return ScopedUseBuffer(this); | |
| } | |
| DAWN_UNREACHABLE(); | |
| } | |
| void BufferBase::FinishUse() { | |
| BufferState fromState = mState.exchange(BufferState::Unmapped, std::memory_order::acq_rel); | |
| DAWN_CHECK(fromState == BufferState::InUse); | |
| } | |
| Future BufferBase::APIMapAsync(wgpu::MapMode mode, | |
| size_t offset, | |
| size_t size, | |
| const WGPUBufferMapCallbackInfo& callbackInfo) { | |
| // TODO(crbug.com/dawn/2052): Once we always return a future, change this to log to the instance | |
| // (note, not raise a validation error to the device) and return the null future. | |
| DAWN_CHECK(callbackInfo.nextInChain == nullptr); | |
| Ref<MapAsyncEvent> event; | |
| { | |
| // Handle the defaulting of size required by WebGPU, even if in webgpu_cpp.h it is not | |
| // possible to default the function argument (because there is the callback later in the | |
| // argument list) | |
| if ((size == wgpu::kWholeMapSize) && (offset <= mSize)) { | |
| size = checked_cast<size_t>(mSize - offset); | |
| } | |
| WGPUMapAsyncStatus errorStatus = WGPUMapAsyncStatus_Aborted; | |
| MaybeValError maybeValError = [&]() -> MaybeValError { | |
| DAWN_TRY(GetDevice()->ValidateIsAlive()); | |
| errorStatus = WGPUMapAsyncStatus_Error; | |
| DAWN_TRY(ValidateMapAsync(mode, offset, size)); | |
| switch (mState.load(std::memory_order::acquire)) { | |
| case BufferState::Mapped: | |
| case BufferState::MappedAtCreation: | |
| return DAWN_VALIDATION_ERROR("%s is already mapped.", this); | |
| case BufferState::InUse: | |
| return DAWN_VALIDATION_ERROR(kConcurrentUseError); | |
| case BufferState::PendingMap: | |
| return DAWN_VALIDATION_ERROR("%s already has an outstanding map pending.", | |
| this); | |
| case BufferState::Destroyed: | |
| return DAWN_VALIDATION_ERROR("%s is destroyed.", this); | |
| case BufferState::SharedMemoryNoAccess: | |
| return DAWN_VALIDATION_ERROR("%s used without shared memory access.", this); | |
| case BufferState::Unmapped: | |
| break; | |
| } | |
| DAWN_TRY(TransitionState(BufferState::Unmapped, BufferState::InUse)); | |
| return {}; | |
| }(); | |
| auto EmitError = [&](auto error) { | |
| event = AcquireRef(new MapAsyncEvent(callbackInfo, error->GetMessage(), errorStatus)); | |
| GetDevice()->ConsumeError(std::move(error), "calling %s.MapAsync(%s, %u, %u, ...).", | |
| this, mode, offset, size); | |
| }; | |
| if (maybeValError.IsError()) { | |
| EmitError(maybeValError.AcquireError()); | |
| } else { | |
| MaybeError maybeError = [&]() -> MaybeError { | |
| DAWN_TRY_WITH_CLEANUP(MapAsyncImpl(mode, offset, size), { | |
| // Reset state since an error stopped this from reaching pending map state. | |
| mState.store(BufferState::Unmapped, std::memory_order::release); | |
| }); | |
| return {}; | |
| }(); | |
| if (maybeError.IsError()) { | |
| EmitError(maybeError.AcquireError()); | |
| } else { | |
| mMapMode = mode; | |
| mMapOffset = offset; | |
| mMapSize = size; | |
| mAllocatedMapSize = size; | |
| event = AcquireRef( | |
| new MapAsyncEvent(GetDevice(), this, callbackInfo, mLastUsageSerial)); | |
| DAWN_CHECK(mMappedRange.data() == nullptr); | |
| DAWN_CHECK(!mPendingMapEvent); | |
| mPendingMapEvent = event; | |
| mState.store(BufferState::PendingMap, std::memory_order::release); | |
| } | |
| } | |
| } | |
| DAWN_CHECK(event); | |
| FutureID futureID = GetInstance()->GetEventManager()->TrackEvent(std::move(event)); | |
| return {futureID}; | |
| } | |
| void* BufferBase::APIGetMappedRange(size_t offset, size_t size) { | |
| return GetMappedRangeInternal(offset, size, true).value_or(Span<std::byte>{}).data(); | |
| } | |
| const void* BufferBase::APIGetConstMappedRange(size_t offset, size_t size) { | |
| return GetMappedRangeInternal(offset, size, false).value_or(Span<std::byte>{}).data(); | |
| } | |
| wgpu::Status BufferBase::APIWriteMappedRange(size_t offset, Span<const std::byte> data) { | |
| std::optional<Span<std::byte>> range = GetMappedRangeInternal(offset, data.size(), true); | |
| if (!range.has_value()) { | |
| return wgpu::Status::Error; | |
| } | |
| range->CopyFrom(data); | |
| return wgpu::Status::Success; | |
| } | |
| wgpu::Status BufferBase::APIReadMappedRange(size_t offset, Span<std::byte> data) { | |
| std::optional<Span<std::byte>> range = GetMappedRangeInternal(offset, data.size(), false); | |
| if (!range.has_value()) { | |
| return wgpu::Status::Error; | |
| } | |
| data.CopyFrom(range.value()); | |
| return wgpu::Status::Success; | |
| } | |
| Span<std::byte> BufferBase::GetMappedRange(size_t offset, size_t size) { | |
| std::optional<Span<std::byte>> range = GetMappedRangeInternal(offset, size, true); | |
| DAWN_ASSERT(range.has_value()); | |
| return range.value(); | |
| } | |
| std::optional<Span<std::byte>> BufferBase::GetMappedRangeInternal(size_t offset, | |
| size_t size, | |
| bool writable) { | |
| if (size == wgpu::kWholeMapSize) { | |
| // This can underflow but CanGetMappedRange will return false because offset is too large. | |
| size = checked_cast<size_t>(mSize) - offset; | |
| } | |
| if (!CanGetMappedRange(writable, offset, size)) { | |
| return {}; | |
| } | |
| DAWN_ASSERT(offset >= mMapOffset); | |
| return mMappedRange.subspan(offset - mMapOffset, size); | |
| } | |
| Span<std::byte> BufferBase::GetFullMappedAllocatedRange() { | |
| DAWN_ASSERT(mMappedRange.size() == GetAllocatedSize()); | |
| DAWN_ASSERT(mState == BufferState::MappedAtCreation); | |
| return mMappedRange; | |
| } | |
| void BufferBase::APIDestroy() { | |
| auto deviceGuard = UseDeviceGuardForDestroy(); | |
| Destroy(); | |
| } | |
| uint64_t BufferBase::APIGetSize() const { | |
| return mSize; | |
| } | |
| MaybeError BufferBase::CopyFromStagingBuffer() { | |
| DAWN_CHECK(mStagingBuffer != nullptr && mSize != 0); | |
| auto deviceGuard = GetDevice()->GetGuard(); | |
| DAWN_TRY( | |
| GetDevice()->CopyFromStagingToBuffer(mStagingBuffer.Get(), 0, this, 0, GetAllocatedSize())); | |
| mStagingBuffer = nullptr; | |
| return GetDevice()->GetDynamicUploader()->OnStagingMemoryFreePendingOnSubmit( | |
| GetAllocatedSize()); | |
| } | |
| void BufferBase::APIUnmap() { | |
| MaybeUnknownError unmapError = [&]() -> MaybeUnknownError { | |
| DAWN_TRY(ValidateUnmap()); | |
| DAWN_TRY(UnmapInternal(false)); | |
| DAWN_TRY(GetDevice()->GetDynamicUploader()->MaybeSubmitPendingCommands()); | |
| return {}; | |
| }(); | |
| std::ignore = GetDevice()->ConsumedError(std::move(unmapError), "calling %s.Unmap().", this); | |
| } | |
| MaybeUnknownError BufferBase::Unmap(bool forDestroy) { | |
| switch (mState.load(std::memory_order::acquire)) { | |
| case BufferState::Mapped: | |
| DAWN_TRY(TransitionState(BufferState::Mapped, BufferState::InUse)); | |
| mMappedRange = {}; | |
| UnmapImpl(BufferState::Mapped, | |
| forDestroy ? BufferState::Destroyed : BufferState::Unmapped); | |
| break; | |
| case BufferState::MappedAtCreation: | |
| DAWN_TRY(TransitionState(BufferState::MappedAtCreation, BufferState::InUse)); | |
| mMappedRange = {}; | |
| mIsMappedAtCreation = false; | |
| if (mStagingBuffer != nullptr) { | |
| if (forDestroy) { | |
| // No need to upload staging contents if the buffer is being destroyed. | |
| mStagingBuffer = nullptr; | |
| } else { | |
| DAWN_TRY_WITH_CLEANUP(CopyFromStagingBuffer(), { | |
| mState.store(BufferState::MappedAtCreation, std::memory_order::release); | |
| }); | |
| } | |
| } | |
| if (mSize != 0 && IsCPUWritableAtCreation()) { | |
| UnmapImpl(BufferState::MappedAtCreation, | |
| forDestroy ? BufferState::Destroyed : BufferState::Unmapped); | |
| } | |
| break; | |
| case BufferState::InUse: | |
| return DAWN_VALIDATION_ERROR(kConcurrentUseError); | |
| case BufferState::Unmapped: | |
| return {}; | |
| case BufferState::SharedMemoryNoAccess: | |
| return DAWN_VALIDATION_ERROR("%s unmapped without shared memory access.", this); | |
| case BufferState::PendingMap: | |
| case BufferState::Destroyed: | |
| // UnmapInternal() already handled waiting for PendingMap to be done so there must have | |
| // been a concurrent operation that changes state between the two atomic loads. | |
| return DAWN_VALIDATION_ERROR(kConcurrentUseError); | |
| } | |
| mState.store(BufferState::Unmapped, std::memory_order::release); | |
| return {}; | |
| } | |
| Ref<BufferBase::MapAsyncEvent> BufferBase::UnmapEarly(BufferState newState, | |
| std::string_view abortMessage) { | |
| DAWN_ASSERT(mPendingMapMutex.IsLockedByCurrentThread()); | |
| if (!mPendingMapEvent) { | |
| return nullptr; | |
| } | |
| mPendingMapEvent->UnmapEarly(abortMessage); | |
| BufferState exchangedState = mState.exchange(newState, std::memory_order::acq_rel); | |
| DAWN_CHECK(exchangedState == BufferState::PendingMap); | |
| // Don't mState.notify_all(), we might not be ready to do that yet. The caller handles it. | |
| return std::move(mPendingMapEvent); | |
| } | |
| MaybeUnknownError BufferBase::UnmapInternal(bool forDestroy) { | |
| BufferState state = mState.load(std::memory_order::acquire); | |
| // If the buffer is already destroyed, we don't need to do anything. | |
| if (state == BufferState::Destroyed) { | |
| return {}; | |
| } | |
| if (state == BufferState::PendingMap) { | |
| Ref<MapAsyncEvent> event; | |
| { | |
| RecursiveMutex::AutoLock lock(&mPendingMapMutex); | |
| // `mPendingMapEvent` is always reset while holding the mutex. If Complete() ran and | |
| // already reset the event then map is about to complete. If not, reset here and do an | |
| // early unmap. | |
| // This modifies status in MapAsyncEvent which signals the map has been aborted. It | |
| // must happen with mutex locked. | |
| event = UnmapEarly(BufferState::InUse, | |
| forDestroy ? "Buffer was destroyed before mapping was resolved." | |
| : "Buffer was unmapped before mapping was resolved."); | |
| } | |
| if (event) { | |
| // Continue early unmap after releasing the mutex. | |
| UnmapImpl(BufferState::PendingMap, | |
| forDestroy ? BufferState::Destroyed : BufferState::Unmapped); | |
| mState.store(BufferState::Unmapped, std::memory_order::release); | |
| GetInstance()->GetEventManager()->SetFutureReady(event.Get()); | |
| return {}; | |
| } | |
| // Wait for the notify_all() in Complete() so we know that FinalizeMap() is done before | |
| // falling through to a regular unmap. | |
| mState.wait(BufferState::PendingMap, std::memory_order::acquire); | |
| } | |
| // Wait for any active MapAsyncEvent callback to finish. Just acquiring and releasing the lock | |
| // is sufficient because MapAsyncEvent::Complete transitions the state and holds the lock | |
| // throughout the state transition and the callback exactly once, therefore if we can acquire | |
| // the lock, the callback must have completed. | |
| { | |
| RecursiveMutex::AutoLock lock(&mPendingMapMutex); | |
| } | |
| DAWN_TRY(Unmap(forDestroy)); | |
| return {}; | |
| } | |
| MaybeValError BufferBase::ValidateMapAsync(wgpu::MapMode mode, size_t offset, size_t size) const { | |
| DAWN_TRY(GetDevice()->ValidateObject(this)); | |
| DAWN_INVALID_IF(mIsHostMapped, "Host-mapped %s cannot be mapped again.", this); | |
| DAWN_INVALID_IF(uint64_t{offset} > mSize, | |
| "Mapping offset (%u) is larger than the size (%u) of %s.", offset, mSize, this); | |
| DAWN_INVALID_IF(offset % 8 != 0, "Offset (%u) must be a multiple of 8.", offset); | |
| DAWN_INVALID_IF(size % 4 != 0, "Size (%u) must be a multiple of 4.", size); | |
| DAWN_INVALID_IF(uint64_t{size} > mSize - uint64_t{offset}, | |
| "Mapping range (offset:%u, size: %u) doesn't fit in the size (%u) of %s.", | |
| offset, size, mSize, this); | |
| // If/when we allow multiple map modes at the same time for a map call, relax the restrictions | |
| // that using a switch/case implicitly implies for the bitmask. | |
| switch (mode) { | |
| case wgpu::MapMode::Read: { | |
| DAWN_INVALID_IF(!(mInternalUsage & wgpu::BufferUsage::MapRead), | |
| "The buffer usages (%s) do not contain %s.", mInternalUsage, | |
| wgpu::BufferUsage::MapRead); | |
| break; | |
| } | |
| case wgpu::MapMode::Write: { | |
| DAWN_INVALID_IF(!(mInternalUsage & wgpu::BufferUsage::MapWrite), | |
| "The buffer usages (%s) do not contain %s.", mInternalUsage, | |
| wgpu::BufferUsage::MapWrite); | |
| break; | |
| } | |
| default: | |
| return DAWN_VALIDATION_ERROR("Map mode (%s) is not one of %s or %s.", mode, | |
| wgpu::MapMode::Write, wgpu::MapMode::Read); | |
| } | |
| return {}; | |
| } | |
| bool BufferBase::CanGetMappedRange(bool writable, size_t offset, size_t size) const { | |
| // Note that: | |
| // | |
| // - We don't check that the device is alive because the application can ask for the | |
| // mapped pointer before it knows, and even Dawn knows, that the device was lost, and | |
| // still needs to work properly. | |
| // - We don't check that the object is alive because we need to return mapped pointers | |
| // for error buffers too. | |
| switch (mState.load(std::memory_order::acquire)) { | |
| // Writeable Buffer::GetMappedRange is always allowed when mapped at creation. | |
| case BufferState::MappedAtCreation: | |
| break; | |
| case BufferState::Mapped: | |
| DAWN_CHECK(bool{mMapMode & wgpu::MapMode::Read} ^ | |
| bool{mMapMode & wgpu::MapMode::Write}); | |
| if (writable && (mMapMode & wgpu::MapMode::Write) == 0) { | |
| GetDevice()->EmitLog( | |
| wgpu::LoggingType::Error, | |
| "GetMappedRange: Mapping is read-only. Use GetConstMappedRange instead."); | |
| return false; | |
| } | |
| break; | |
| case BufferState::PendingMap: | |
| case BufferState::Unmapped: | |
| case BufferState::InUse: | |
| case BufferState::SharedMemoryNoAccess: | |
| case BufferState::Destroyed: | |
| return false; | |
| } | |
| if (offset % 8 != 0 || offset < mMapOffset || offset > mSize) { | |
| return false; | |
| } | |
| // Defaulting should have already been done. If not we'll fail the map on the next line. | |
| DAWN_ASSERT(size != wgpu::kWholeMapSize); | |
| if (size % 4 != 0 || size > mMapSize) { | |
| return false; | |
| } | |
| size_t offsetInMappedRange = offset - mMapOffset; | |
| if (offsetInMappedRange > mMapSize - size) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| MaybeValError BufferBase::ValidateUnmap() const { | |
| DAWN_TRY(GetDevice()->ValidateIsAlive()); | |
| DAWN_INVALID_IF(mIsHostMapped, "Persistently mapped buffer cannot be unmapped."); | |
| return {}; | |
| } | |
| bool BufferBase::NeedsInitialization() const { | |
| return !mIsDataInitialized && GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse); | |
| } | |
| void BufferBase::MarkUsedInPendingCommands() { | |
| // TODO(crbug.com/422741977): Consider storing the pending serial once, perhaps in a | |
| // CommandRecordingContextBase, so that we don't need to load the atomic value repeatedly. | |
| MarkUsedInPendingCommands(GetDevice()->GetQueue()->GetPendingCommandSerial()); | |
| } | |
| void BufferBase::MarkUsedInPendingCommands(ExecutionSerial pendingSerial) { | |
| DAWN_ASSERT(!GetDevice()->IsValidationEnabled() || | |
| mState.load(std::memory_order::relaxed) == BufferState::InUse); | |
| DAWN_ASSERT(pendingSerial >= mLastUsageSerial); | |
| mLastUsageSerial = pendingSerial; | |
| } | |
| ExecutionSerial BufferBase::GetLastUsageSerial() const { | |
| return mLastUsageSerial; | |
| } | |
| MaybeError BufferBase::UploadData(uint64_t bufferOffset, Span<const std::byte> data) { | |
| if (data.empty()) { | |
| return {}; | |
| } | |
| return GetDevice()->GetDynamicUploader()->WithUploadReservation( | |
| data.size(), kCopyBufferToBufferOffsetAlignment, | |
| [&](UploadReservation reservation) -> MaybeError { | |
| reservation.mappedData.CopyFrom(data); | |
| return GetDevice()->CopyFromStagingToBuffer(reservation.buffer.Get(), | |
| reservation.offsetInBuffer, this, | |
| bufferOffset, data.size()); | |
| }); | |
| } | |
| ExecutionSerial BufferBase::OnEndAccess() { | |
| mState.store(BufferState::SharedMemoryNoAccess, std::memory_order::release); | |
| ExecutionSerial lastUsageSerial = mLastUsageSerial; | |
| mLastUsageSerial = kBeginningOfGPUTime; | |
| return lastUsageSerial; | |
| } | |
| void BufferBase::OnBeginAccess() { | |
| if (mIsMappedAtCreation) { | |
| mState.store(BufferState::MappedAtCreation, std::memory_order::release); | |
| } else { | |
| mState.store(BufferState::Unmapped, std::memory_order::release); | |
| } | |
| } | |
| bool BufferBase::HasAccess() const { | |
| return mState.load(std::memory_order::acquire) != BufferState::SharedMemoryNoAccess; | |
| } | |
| bool BufferBase::IsDestroyed() const { | |
| return mState.load(std::memory_order::acquire) == BufferState::Destroyed; | |
| } | |
| void BufferBase::SetInitialized(bool initialized) { | |
| mIsDataInitialized = initialized; | |
| } | |
| bool BufferBase::IsResourceInitialized() const { | |
| return mIsDataInitialized; | |
| } | |
| bool BufferBase::IsFullBufferRange(uint64_t offset, uint64_t size) const { | |
| return offset == 0 && size == GetSize(); | |
| } | |
| void BufferBase::DumpMemoryStatistics(MemoryDump* dump, const char* prefix) const { | |
| DAWN_CHECK(IsAlive() && !IsError()); | |
| std::string name = absl::StrFormat("%s/buffer_%p", prefix, static_cast<const void*>(this)); | |
| dump->AddScalar(name.c_str(), MemoryDump::kNameSize, MemoryDump::kUnitsBytes, | |
| GetAllocatedSize()); | |
| dump->AddString(name.c_str(), "label", GetLabel()); | |
| dump->AddString(name.c_str(), "usage", absl::StrFormat("%s", GetInternalUsage())); | |
| } | |
| ResultOrUnknownError<Ref<TexelBufferViewBase>> BufferBase::CreateTexelView( | |
| const TexelBufferViewDescriptor* descriptor) { | |
| DAWN_CHECK(descriptor != nullptr); | |
| return GetDevice()->CreateTexelBufferView(this, descriptor); | |
| } | |
| TexelBufferViewBase* BufferBase::APICreateTexelView(const TexelBufferViewDescriptor* descriptor) { | |
| DeviceBase* device = GetDevice(); | |
| Ref<TexelBufferViewBase> result; | |
| if (device->ConsumedError(CreateTexelView(descriptor), &result, | |
| "calling %s.CreateTexelView(%s).", this, descriptor)) { | |
| result = TexelBufferViewBase::MakeError(device, descriptor ? descriptor->label : nullptr); | |
| } | |
| return ReturnToAPI(std::move(result)); | |
| } | |
| ApiObjectList* BufferBase::GetTexelBufferViewTrackingList() { | |
| return &mTexelBufferViews; | |
| } | |
| MaybeValError BufferBase::TransitionState(BufferState currentState, BufferState desiredState) { | |
| if (mState.compare_exchange_strong(currentState, desiredState, std::memory_order::acq_rel)) { | |
| return {}; | |
| } | |
| return DAWN_VALIDATION_ERROR(kConcurrentUseError); | |
| } | |
| BufferBase::ScopedUseBuffer::ScopedUseBuffer() = default; | |
| BufferBase::ScopedUseBuffer::ScopedUseBuffer(BufferBase* buffer) : mBuffer(buffer) { | |
| DAWN_CHECK(mBuffer); | |
| DAWN_ASSERT(mBuffer->mState.load(std::memory_order::relaxed) == BufferState::InUse); | |
| } | |
| BufferBase::ScopedUseBuffer::~ScopedUseBuffer() { | |
| if (mBuffer) { | |
| mBuffer->FinishUse(); | |
| } | |
| } | |
| BufferBase::ScopedUseBuffer::ScopedUseBuffer(ScopedUseBuffer&& other) : mBuffer(other.mBuffer) { | |
| other.mBuffer = nullptr; | |
| } | |
| BufferBase::ScopedUseBuffer& BufferBase::ScopedUseBuffer::operator=(ScopedUseBuffer&& other) { | |
| mBuffer = other.mBuffer; | |
| other.mBuffer = nullptr; | |
| return *this; | |
| } | |
| void BufferBase::ScopedUseBuffer::Release() { | |
| DAWN_CHECK(mBuffer); | |
| mBuffer = nullptr; | |
| } | |
| } // namespace dawn::native |