| // Copyright 2025 The Dawn & Tint Authors |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // 1. Redistributions of source code must retain the above copyright notice, this |
| // list of conditions and the following disclaimer. |
| // |
| // 2. Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // |
| // 3. Neither the name of the copyright holder nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include "dawn/native/webgpu/BufferWGPU.h" |
| |
| #include <algorithm> |
| #include <string> |
| #include <utility> |
| |
| #include "dawn/common/StringViewUtils.h" |
| #include "dawn/native/Buffer.h" |
| #include "dawn/native/webgpu/CaptureContext.h" |
| #include "dawn/native/webgpu/DeviceWGPU.h" |
| #include "dawn/native/webgpu/QueueWGPU.h" |
| #include "dawn/native/webgpu/Serialization.h" |
| |
| namespace dawn::native::webgpu { |
| |
| // static |
| ResultOrError<Ref<Buffer>> Buffer::Create(Device* device, |
| const UnpackedPtr<BufferDescriptor>& descriptor) { |
| auto actualUsage = ComputeInternalBufferUsages(device, descriptor->usage, descriptor->size); |
| |
| // Make the inner buffer copyable for readback if possible. |
| if (!(actualUsage & wgpu::BufferUsage::MapRead)) { |
| actualUsage |= wgpu::BufferUsage::CopySrc; |
| } |
| |
| // Resolve internal usages to regular ones. |
| if (actualUsage & kInternalStorageBuffer) { |
| actualUsage &= ~kInternalStorageBuffer; |
| actualUsage |= wgpu::BufferUsage::Storage; |
| } |
| if (actualUsage & kReadOnlyStorageBuffer) { |
| actualUsage &= ~kReadOnlyStorageBuffer; |
| actualUsage |= wgpu::BufferUsage::Storage; |
| } |
| if (actualUsage & kInternalCopySrcBuffer) { |
| actualUsage &= ~kInternalCopySrcBuffer; |
| actualUsage |= wgpu::BufferUsage::CopySrc; |
| } |
| |
| WGPUBufferDescriptor desc = WGPU_BUFFER_DESCRIPTOR_INIT; |
| desc.label = ToOutputStringView(descriptor->label); |
| desc.usage = ToAPI(actualUsage); |
| desc.size = descriptor->size; |
| desc.mappedAtCreation = descriptor->mappedAtCreation; |
| |
| WGPUBuffer innerBuffer = device->wgpu.deviceCreateBuffer(device->GetInnerHandle(), &desc); |
| if (innerBuffer == nullptr) { |
| // innerBuffer can be nullptr when mappedAtCreation == true and fails. |
| // Return an error buffer. |
| const BufferDescriptor* rawDescriptor = *descriptor; |
| return ToBackend(BufferBase::MakeError(device, rawDescriptor)); |
| } |
| |
| Ref<Buffer> buffer = AcquireRef(new Buffer(device, descriptor, innerBuffer)); |
| return std::move(buffer); |
| } |
| |
| Buffer::Buffer(Device* device, |
| const UnpackedPtr<BufferDescriptor>& descriptor, |
| WGPUBuffer innerBuffer) |
| : BufferBase(device, descriptor), |
| RecordableObject(schema::ObjectType::Buffer), |
| ObjectWGPU(device->wgpu.bufferRelease) { |
| mInnerHandle = innerBuffer; |
| mAllocatedSize = GetSize(); |
| } |
| |
| bool Buffer::IsCPUWritableAtCreation() const { |
| return ToBackend(GetDevice())->wgpu.bufferGetMapState(mInnerHandle) == |
| WGPUBufferMapState_Mapped; |
| } |
| |
| MaybeError Buffer::MapAtCreationImpl() { |
| mMappedData = ToBackend(GetDevice())->wgpu.bufferGetMappedRange(mInnerHandle, 0, GetSize()); |
| return {}; |
| } |
| |
| MaybeError Buffer::MapAsyncImpl(wgpu::MapMode mode, size_t offset, size_t size) { |
| struct MapAsyncResult { |
| WGPUMapAsyncStatus status; |
| std::string message; |
| } mapAsyncResult = {}; |
| |
| WGPUBufferMapCallbackInfo innerCallbackInfo = {}; |
| innerCallbackInfo.mode = WGPUCallbackMode_WaitAnyOnly; |
| innerCallbackInfo.callback = [](WGPUMapAsyncStatus status, WGPUStringView message, |
| void* result_param, void* userdata_param) { |
| MapAsyncResult* result = reinterpret_cast<MapAsyncResult*>(result_param); |
| result->status = status; |
| result->message = ToString(message); |
| }; |
| innerCallbackInfo.userdata1 = &mapAsyncResult; |
| innerCallbackInfo.userdata2 = this; |
| |
| auto& wgpu = ToBackend(GetDevice())->wgpu; |
| |
| // TODO(crbug.com/413053623): We do not have a way to efficiently process the async event |
| // on the inner webgpu layer. For now we simply wait on the future. |
| WGPUFutureWaitInfo waitInfo = {}; |
| waitInfo.future = wgpu.bufferMapAsync(mInnerHandle, static_cast<WGPUMapMode>(mode), offset, |
| size, innerCallbackInfo); |
| wgpu.instanceWaitAny(ToBackend(GetDevice())->GetInnerInstance(), 1, &waitInfo, UINT64_MAX); |
| |
| if (mapAsyncResult.status != WGPUMapAsyncStatus_Success) { |
| return DAWN_INTERNAL_ERROR(mapAsyncResult.message); |
| } |
| |
| // The frontend asks that the pointer returned by GetMappedPointer is from the start of |
| // the resource but WGPU gives us the pointer at offset. Remove the offset. |
| if (bool{mode & wgpu::MapMode::Write}) { |
| mMappedData = |
| static_cast<uint8_t*>(wgpu.bufferGetMappedRange(mInnerHandle, offset, size)) - offset; |
| } else if (bool{mode & wgpu::MapMode::Read}) { |
| mMappedData = static_cast<uint8_t*>(const_cast<void*>( |
| wgpu.bufferGetConstMappedRange(mInnerHandle, offset, size))) - |
| offset; |
| } else { |
| DAWN_UNREACHABLE(); |
| } |
| return {}; |
| } |
| |
| MaybeError Buffer::FinalizeMapImpl(BufferState newState) { |
| return {}; |
| } |
| |
| void* Buffer::GetMappedPointerImpl() { |
| // The mapping offset has already been removed. |
| return mMappedData; |
| } |
| |
| void Buffer::UnmapImpl(BufferState oldState, BufferState newState) { |
| auto deviceGuard = GetDevice()->GetGuard(); |
| |
| if (IsMappedState(oldState) && MapMode() == wgpu::MapMode::Write && |
| newState != BufferState::Destroyed) { |
| // TODO(477349135): Optimize this by tracking the ranges. As it is we'll |
| // capture the entire buffer even if only a few bytes were updated. Instead |
| // of mNeedsCapture we could have mDirtySpans. When size is 0 there's nothing to do. |
| mNeedsCapture = true; |
| } |
| |
| if (mInnerHandle) { |
| ToBackend(GetDevice())->wgpu.bufferUnmap(mInnerHandle); |
| } |
| mMappedData = nullptr; |
| } |
| |
| void Buffer::DestroyImpl(DestroyReason reason) { |
| BufferBase::DestroyImpl(reason); |
| auto& wgpu = ToBackend(GetDevice())->wgpu; |
| wgpu.bufferDestroy(mInnerHandle); |
| } |
| |
| void Buffer::SetLabelImpl() { |
| ToBackend(GetDevice())->CaptureSetLabel(this, GetLabel()); |
| } |
| |
| MaybeError Buffer::AddReferenced(CaptureContext& captureContext) { |
| // Buffers do not reference other objects. |
| return {}; |
| } |
| |
| MaybeError Buffer::CaptureCreationParameters(CaptureContext& captureContext) { |
| schema::Buffer buf{{ |
| .size = GetSize(), |
| .usage = GetUsage(), |
| }}; |
| Serialize(captureContext, buf); |
| return {}; |
| } |
| |
| MaybeError Buffer::CaptureContentIfNeeded(CaptureContext& captureContext, |
| schema::ObjectId id, |
| bool newResource) { |
| // TODO(451338754): If it's a new resource and we know the buffer is all zero then don't |
| // capture. |
| wgpu::BufferUsage usage = GetUsage(); |
| if (!mNeedsCapture && !newResource) { |
| return {}; |
| } |
| |
| // A MapRead buffer is never used as input since it's only allowed CopyDst |
| // so we don't need its contents. |
| if (usage & wgpu::BufferUsage::MapRead) { |
| return {}; |
| } |
| |
| mNeedsCapture = false; |
| |
| return AddContentToCapture(captureContext); |
| } |
| |
| // TODO(451650604): We currently get at most 1mb at a time to keep memory usage down. |
| // Revisit for speed later. |
| MaybeError Buffer::AddContentToCapture(CaptureContext& captureContext) { |
| // TODO(473593119): Handle the unaligned trailing bytes. |
| // TODO(473568230): Support copies with unaligned size. |
| // copyBufferToBuffer requires 4 byte alignment for both size and offset which prevents |
| // us from copying the trailing bytes. writeBuffer has the same alignment requirements. |
| // so the user can't put bytes in via writeBuffer. mapAsync requires offset to be 8 byte |
| // aligned and size to be 4 bytes so the user can not set those last bytes with mapAsync. |
| // We can still access those bytes with copyBufferToTexture and copyTextureToBuffer though. |
| // For now, we just ignore the last 3 bytes. |
| uint64_t copyableSize = AlignDown(GetSize(), 4); |
| if (copyableSize == 0) { |
| return {}; |
| } |
| |
| struct MapAsyncResult { |
| WGPUMapAsyncStatus status; |
| std::string message; |
| } mapAsyncResult = {}; |
| |
| schema::RootCommandWriteBufferCmd cmd{{ |
| .data = {{ |
| .bufferId = captureContext.GetId(this), |
| .bufferOffset = 0, |
| .size = copyableSize, |
| }}, |
| }}; |
| Serialize(captureContext, cmd); |
| |
| WGPUBuffer srcBuffer = GetInnerHandle(); |
| WGPUBuffer copyBuffer = captureContext.GetCopyBuffer(); |
| WGPUQueue queue = ToBackend(GetDevice()->GetQueue())->GetInnerHandle(); |
| |
| Device* device = ToBackend(GetDevice()); |
| WGPUDevice innerDevice = device->GetInnerHandle(); |
| auto& wgpu = device->wgpu; |
| |
| CaptureContext::ScopedContentWriter writer(captureContext); |
| for (uint64_t offset = 0; offset < copyableSize; offset += CaptureContext::kCopyBufferSize) { |
| uint64_t copySize = std::min(CaptureContext::kCopyBufferSize, copyableSize - offset); |
| |
| WGPUCommandEncoder encoder = wgpu.deviceCreateCommandEncoder(innerDevice, nullptr); |
| wgpu.commandEncoderCopyBufferToBuffer(encoder, srcBuffer, offset, copyBuffer, 0, copySize); |
| WGPUCommandBuffer commandBuffer = wgpu.commandEncoderFinish(encoder, nullptr); |
| wgpu.queueSubmit(queue, 1, &commandBuffer); |
| wgpu.commandBufferRelease(commandBuffer); |
| wgpu.commandEncoderRelease(encoder); |
| |
| // Map the buffer to read back the content. |
| WGPUBufferMapCallbackInfo innerCallbackInfo = {}; |
| innerCallbackInfo.mode = WGPUCallbackMode_WaitAnyOnly; |
| innerCallbackInfo.callback = [](WGPUMapAsyncStatus status, WGPUStringView message, |
| void* result_param, void* userdata_param) { |
| MapAsyncResult* result = reinterpret_cast<MapAsyncResult*>(result_param); |
| result->status = status; |
| result->message = ToString(message); |
| }; |
| innerCallbackInfo.userdata1 = &mapAsyncResult; |
| innerCallbackInfo.userdata2 = this; |
| |
| // We read this back synchronously. I'm not sure we could do much more. |
| WGPUFutureWaitInfo waitInfo = {}; |
| waitInfo.future = |
| wgpu.bufferMapAsync(copyBuffer, WGPUMapMode_Read, 0, copySize, innerCallbackInfo); |
| wgpu.instanceWaitAny(device->GetInnerInstance(), 1, &waitInfo, UINT64_MAX); |
| |
| DAWN_ASSERT(mapAsyncResult.status == WGPUMapAsyncStatus_Success); |
| |
| if (mapAsyncResult.status != WGPUMapAsyncStatus_Success) { |
| return DAWN_INTERNAL_ERROR(mapAsyncResult.message); |
| } |
| |
| const void* data = wgpu.bufferGetConstMappedRange(copyBuffer, 0, copySize); |
| writer.WriteContentBytes(data, copySize); |
| wgpu.bufferUnmap(copyBuffer); |
| } |
| |
| return {}; |
| } |
| |
| } // namespace dawn::native::webgpu |