2 * Copyright (C) 2018 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
27 #include "GPUBuffer.h"
31 #import "GPUBufferDescriptor.h"
34 #import <JavaScriptCore/ArrayBuffer.h>
35 #import <Metal/Metal.h>
36 #import <wtf/BlockObjCExceptions.h>
37 #import <wtf/BlockPtr.h>
38 #import <wtf/CheckedArithmetic.h>
39 #import <wtf/MainThread.h>
43 static constexpr auto readOnlyFlags = OptionSet<GPUBufferUsage::Flags> { GPUBufferUsage::Flags::Index, GPUBufferUsage::Flags::Vertex, GPUBufferUsage::Flags::Uniform, GPUBufferUsage::Flags::TransferSource };
46 bool GPUBuffer::validateBufferUsage(const GPUDevice& device, OptionSet<GPUBufferUsage::Flags> usage, GPUErrorScopes& errorScopes)
48 if (!device.platformDevice()) {
49 LOG(WebGPU, "GPUBuffer::tryCreate(): Invalid GPUDevice!");
53 if (usage.containsAll({ GPUBufferUsage::Flags::MapWrite, GPUBufferUsage::Flags::MapRead })) {
54 errorScopes.generatePrefixedError("Buffer cannot have both MAP_READ and MAP_WRITE usage!");
58 if (usage.containsAny(readOnlyFlags) && (usage & GPUBufferUsage::Flags::Storage)) {
59 LOG(WebGPU, "GPUBuffer::tryCreate(): Buffer cannot have both STORAGE and a read-only usage!");
66 RefPtr<GPUBuffer> GPUBuffer::tryCreate(GPUDevice& device, const GPUBufferDescriptor& descriptor, GPUBufferMappedOption isMapped, GPUErrorScopes& errorScopes)
68 // MTLBuffer size (NSUInteger) is 32 bits on some platforms.
70 if (!WTF::convertSafely(descriptor.size, size)) {
71 errorScopes.generateError("", GPUErrorFilter::OutOfMemory);
75 auto usage = OptionSet<GPUBufferUsage::Flags>::fromRaw(descriptor.usage);
76 if (!validateBufferUsage(device, usage, errorScopes))
80 // copyBufferToBuffer calls require 4-byte alignment. "Unmapping" a mapped-on-creation GPUBuffer
81 // that is otherwise unmappable requires such a copy to upload data.
82 if (isMapped == GPUBufferMappedOption::IsMapped
83 && !usage.containsAny({ GPUBufferUsage::Flags::MapWrite, GPUBufferUsage::Flags::MapRead })
84 && descriptor.size % 4) {
85 LOG(WebGPU, "GPUBuffer::tryCreate(): Data must be aligned to a multiple of 4 bytes!");
90 // FIXME: Metal best practices: Read-only one-time-use data less than 4 KB should not allocate a MTLBuffer and be used in [MTLCommandEncoder set*Bytes] calls instead.
92 MTLResourceOptions resourceOptions = MTLResourceCPUCacheModeDefaultCache;
94 // Mappable buffers use shared storage allocation.
95 resourceOptions |= usage.containsAny({ GPUBufferUsage::Flags::MapWrite, GPUBufferUsage::Flags::MapRead }) ? MTLResourceStorageModeShared : MTLResourceStorageModePrivate;
97 RetainPtr<MTLBuffer> mtlBuffer;
99 BEGIN_BLOCK_OBJC_EXCEPTIONS;
101 mtlBuffer = adoptNS([device.platformDevice() newBufferWithLength:static_cast<NSUInteger>(descriptor.size) options:resourceOptions]);
103 END_BLOCK_OBJC_EXCEPTIONS;
106 errorScopes.generateError("", GPUErrorFilter::OutOfMemory);
110 return adoptRef(*new GPUBuffer(WTFMove(mtlBuffer), device, size, usage, isMapped, errorScopes));
113 GPUBuffer::GPUBuffer(RetainPtr<MTLBuffer>&& buffer, GPUDevice& device, size_t size, OptionSet<GPUBufferUsage::Flags> usage, GPUBufferMappedOption isMapped, GPUErrorScopes& errorScopes)
114 : GPUObjectBase(makeRef(errorScopes))
115 , m_platformBuffer(WTFMove(buffer))
116 , m_device(makeRef(device))
119 , m_isMappedFromCreation(isMapped == GPUBufferMappedOption::IsMapped)
123 GPUBuffer::~GPUBuffer()
128 bool GPUBuffer::isReadOnly() const
130 return m_usage.containsAny(readOnlyFlags);
133 GPUBuffer::State GPUBuffer::state() const
135 if (!m_platformBuffer)
136 return State::Destroyed;
137 if (m_isMappedFromCreation || m_mappingCallback)
138 return State::Mapped;
140 return State::Unmapped;
143 JSC::ArrayBuffer* GPUBuffer::mapOnCreation()
145 ASSERT(m_isMappedFromCreation);
146 return stagingBufferForWrite();
150 void GPUBuffer::commandBufferCommitted(MTLCommandBuffer *commandBuffer)
152 ASSERT(isMainThread());
153 ++m_numScheduledCommandBuffers;
155 BEGIN_BLOCK_OBJC_EXCEPTIONS;
156 // Make sure |this| only gets ref'd / deref'd on the main thread since it is not ThreadSafeRefCounted.
157 [commandBuffer addCompletedHandler:makeBlockPtr([this, protectedThis = makeRef(*this)](id<MTLCommandBuffer>) mutable {
158 callOnMainThread([this, protectedThis = WTFMove(protectedThis)] {
159 commandBufferCompleted();
162 END_BLOCK_OBJC_EXCEPTIONS;
165 void GPUBuffer::commandBufferCompleted()
167 ASSERT(isMainThread());
168 ASSERT(m_numScheduledCommandBuffers);
170 if (m_numScheduledCommandBuffers == 1 && state() == State::Mapped)
171 runMappingCallback();
173 --m_numScheduledCommandBuffers;
177 void GPUBuffer::registerMappingCallback(MappingCallback&& callback, bool isRead)
179 // Reject if request is invalid.
180 if (isRead && !isMapReadable()) {
181 LOG(WebGPU, "GPUBuffer::mapReadAsync(): Invalid operation!");
185 if (!isRead && !isMapWriteable()) {
186 LOG(WebGPU, "GPUBuffer::mapWriteAsync(): Invalid operation!");
191 ASSERT(!m_mappingCallback && !m_mappingCallbackTask.hasPendingTask());
193 // An existing callback means this buffer is in the mapped state.
194 m_mappingCallback = PendingMappingCallback::create(WTFMove(callback));
196 // If GPU is not using this buffer, run the callback ASAP.
197 if (!m_numScheduledCommandBuffers) {
198 m_mappingCallbackTask.scheduleTask([this, protectedThis = makeRef(*this)] () mutable {
199 runMappingCallback();
204 void GPUBuffer::runMappingCallback()
206 if (m_mappingCallback)
207 m_mappingCallback->callback(isMapRead() ? stagingBufferForRead() : stagingBufferForWrite());
210 JSC::ArrayBuffer* GPUBuffer::stagingBufferForRead()
212 if (!m_stagingBuffer)
213 m_stagingBuffer = ArrayBuffer::create(m_platformBuffer.get().contents, m_byteLength);
215 memcpy(m_stagingBuffer->data(), m_platformBuffer.get().contents, m_byteLength);
217 return m_stagingBuffer.get();
220 JSC::ArrayBuffer* GPUBuffer::stagingBufferForWrite()
222 m_stagingBuffer = ArrayBuffer::create(1, m_byteLength);
223 return m_stagingBuffer.get();
226 void GPUBuffer::copyStagingBufferToGPU()
228 MTLCommandQueue *queue;
229 if (!m_device->tryGetQueue() || !(queue = m_device->tryGetQueue()->platformQueue()))
232 RetainPtr<MTLBuffer> stagingMtlBuffer;
234 BEGIN_BLOCK_OBJC_EXCEPTIONS;
235 // GPUBuffer creation validation ensures m_byteSize fits in NSUInteger.
236 stagingMtlBuffer = adoptNS([m_device->platformDevice() newBufferWithLength:static_cast<NSUInteger>(m_byteLength) options:MTLResourceCPUCacheModeDefaultCache]);
237 END_BLOCK_OBJC_EXCEPTIONS;
239 if (!stagingMtlBuffer) {
240 LOG(WebGPU, "GPUBuffer::unmap(): Unable to create staging buffer!");
244 memcpy(stagingMtlBuffer.get().contents, m_stagingBuffer->data(), m_byteLength);
246 BEGIN_BLOCK_OBJC_EXCEPTIONS;
248 auto commandBuffer = retainPtr([queue commandBuffer]);
249 auto blitEncoder = retainPtr([commandBuffer blitCommandEncoder]);
251 [blitEncoder copyFromBuffer:stagingMtlBuffer.get() sourceOffset:0 toBuffer:m_platformBuffer.get() destinationOffset:0 size:static_cast<NSUInteger>(m_byteLength)];
252 [blitEncoder endEncoding];
253 [commandBuffer commit];
255 END_BLOCK_OBJC_EXCEPTIONS;
258 void GPUBuffer::unmap()
260 if (!m_isMappedFromCreation && !isMappable()) {
261 LOG(WebGPU, "GPUBuffer::unmap(): Invalid operation: buffer is not mappable!");
265 if (m_stagingBuffer) {
267 // MAP_WRITE and MAP_READ buffers have shared, CPU-accessible storage.
268 ASSERT(m_platformBuffer && m_platformBuffer.get().contents);
269 memcpy(m_platformBuffer.get().contents, m_stagingBuffer->data(), m_byteLength);
270 } else if (m_isMappedFromCreation)
271 copyStagingBufferToGPU();
273 m_isMappedFromCreation = false;
274 m_stagingBuffer = nullptr;
277 if (m_mappingCallback) {
278 m_mappingCallbackTask.cancelTask();
279 m_mappingCallback->callback(nullptr);
280 m_mappingCallback = nullptr;
284 void GPUBuffer::destroy()
286 if (state() == State::Mapped)
289 m_platformBuffer = nullptr;
292 } // namespace WebCore
294 #endif // ENABLE(WEBGPU)