GPUBuffer seems to be ref'd / deref'd from multiple thread concurrently but is not...
[WebKit-https.git] / Source / WebCore / platform / graphics / gpu / cocoa / GPUBufferMetal.mm
1 /*
2  * Copyright (C) 2018 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
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.
12  *
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.
24  */
25
26 #include "config.h"
27 #include "GPUBuffer.h"
28
29 #if ENABLE(WEBGPU)
30
31 #import "GPUBufferDescriptor.h"
32 #import "GPUDevice.h"
33 #import "Logging.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>
40
41 namespace WebCore {
42
43 static constexpr auto readOnlyFlags = OptionSet<GPUBufferUsage::Flags> { GPUBufferUsage::Flags::Index, GPUBufferUsage::Flags::Vertex, GPUBufferUsage::Flags::Uniform, GPUBufferUsage::Flags::TransferSource };
44
45
46 bool GPUBuffer::validateBufferUsage(const GPUDevice& device, OptionSet<GPUBufferUsage::Flags> usage, GPUErrorScopes& errorScopes)
47 {
48     if (!device.platformDevice()) {
49         LOG(WebGPU, "GPUBuffer::tryCreate(): Invalid GPUDevice!");
50         return false;
51     }
52
53     if (usage.containsAll({ GPUBufferUsage::Flags::MapWrite, GPUBufferUsage::Flags::MapRead })) {
54         errorScopes.generatePrefixedError("Buffer cannot have both MAP_READ and MAP_WRITE usage!");
55         return false;
56     }
57
58     if (usage.containsAny(readOnlyFlags) && (usage & GPUBufferUsage::Flags::Storage)) {
59         LOG(WebGPU, "GPUBuffer::tryCreate(): Buffer cannot have both STORAGE and a read-only usage!");
60         return false;
61     }
62
63     return true;
64 }
65
66 RefPtr<GPUBuffer> GPUBuffer::tryCreate(GPUDevice& device, const GPUBufferDescriptor& descriptor, GPUBufferMappedOption isMapped, GPUErrorScopes& errorScopes)
67 {
68     // MTLBuffer size (NSUInteger) is 32 bits on some platforms.
69     NSUInteger size = 0;
70     if (!WTF::convertSafely(descriptor.size, size)) {
71         errorScopes.generateError("", GPUErrorFilter::OutOfMemory);
72         return nullptr;
73     }
74
75     auto usage = OptionSet<GPUBufferUsage::Flags>::fromRaw(descriptor.usage);
76     if (!validateBufferUsage(device, usage, errorScopes))
77         return nullptr;
78
79 #if PLATFORM(MAC)
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!");
86         return nullptr;
87     }
88 #endif
89
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.
91
92     MTLResourceOptions resourceOptions = MTLResourceCPUCacheModeDefaultCache;
93
94     // Mappable buffers use shared storage allocation.
95     resourceOptions |= usage.containsAny({ GPUBufferUsage::Flags::MapWrite, GPUBufferUsage::Flags::MapRead }) ? MTLResourceStorageModeShared : MTLResourceStorageModePrivate;
96
97     RetainPtr<MTLBuffer> mtlBuffer;
98
99     BEGIN_BLOCK_OBJC_EXCEPTIONS;
100
101     mtlBuffer = adoptNS([device.platformDevice() newBufferWithLength:static_cast<NSUInteger>(descriptor.size) options:resourceOptions]);
102
103     END_BLOCK_OBJC_EXCEPTIONS;
104
105     if (!mtlBuffer) {
106         errorScopes.generateError("", GPUErrorFilter::OutOfMemory);
107         return nullptr;
108     }
109
110     return adoptRef(*new GPUBuffer(WTFMove(mtlBuffer), device, size, usage, isMapped, errorScopes));
111 }
112
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))
117     , m_byteLength(size)
118     , m_usage(usage)
119     , m_isMappedFromCreation(isMapped == GPUBufferMappedOption::IsMapped)
120 {
121 }
122
123 GPUBuffer::~GPUBuffer()
124 {
125     destroy();
126 }
127
128 bool GPUBuffer::isReadOnly() const
129 {
130     return m_usage.containsAny(readOnlyFlags);
131 }
132
133 GPUBuffer::State GPUBuffer::state() const
134 {
135     if (!m_platformBuffer)
136         return State::Destroyed;
137     if (m_isMappedFromCreation || m_mappingCallback)
138         return State::Mapped;
139
140     return State::Unmapped;
141 }
142
143 JSC::ArrayBuffer* GPUBuffer::mapOnCreation()
144 {
145     ASSERT(m_isMappedFromCreation);
146     return stagingBufferForWrite();
147 }
148
149 #if USE(METAL)
150 void GPUBuffer::commandBufferCommitted(MTLCommandBuffer *commandBuffer)
151 {
152     ASSERT(isMainThread());
153     ++m_numScheduledCommandBuffers;
154
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();
160         });
161     }).get()];
162     END_BLOCK_OBJC_EXCEPTIONS;
163 }
164
165 void GPUBuffer::commandBufferCompleted()
166 {
167     ASSERT(isMainThread());
168     ASSERT(m_numScheduledCommandBuffers);
169
170     if (m_numScheduledCommandBuffers == 1 && state() == State::Mapped)
171         runMappingCallback();
172
173     --m_numScheduledCommandBuffers;
174 }
175 #endif // USE(METAL)
176
177 void GPUBuffer::registerMappingCallback(MappingCallback&& callback, bool isRead)
178 {
179     // Reject if request is invalid.
180     if (isRead && !isMapReadable()) {
181         LOG(WebGPU, "GPUBuffer::mapReadAsync(): Invalid operation!");
182         callback(nullptr);
183         return;
184     }
185     if (!isRead && !isMapWriteable()) {
186         LOG(WebGPU, "GPUBuffer::mapWriteAsync(): Invalid operation!");
187         callback(nullptr);
188         return;
189     }
190
191     ASSERT(!m_mappingCallback && !m_mappingCallbackTask.hasPendingTask());
192
193     // An existing callback means this buffer is in the mapped state.
194     m_mappingCallback = PendingMappingCallback::create(WTFMove(callback));
195
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();
200         });
201     }
202 }
203
204 void GPUBuffer::runMappingCallback()
205 {
206     if (m_mappingCallback)
207         m_mappingCallback->callback(isMapRead() ? stagingBufferForRead() : stagingBufferForWrite());
208 }
209
210 JSC::ArrayBuffer* GPUBuffer::stagingBufferForRead()
211 {
212     if (!m_stagingBuffer)
213         m_stagingBuffer = ArrayBuffer::create(m_platformBuffer.get().contents, m_byteLength);
214     else
215         memcpy(m_stagingBuffer->data(), m_platformBuffer.get().contents, m_byteLength);
216
217     return m_stagingBuffer.get();
218 }
219
220 JSC::ArrayBuffer* GPUBuffer::stagingBufferForWrite()
221 {
222     m_stagingBuffer = ArrayBuffer::create(1, m_byteLength);
223     return m_stagingBuffer.get();
224 }
225
226 void GPUBuffer::copyStagingBufferToGPU()
227 {
228     MTLCommandQueue *queue;
229     if (!m_device->tryGetQueue() || !(queue = m_device->tryGetQueue()->platformQueue()))
230         return;
231
232     RetainPtr<MTLBuffer> stagingMtlBuffer;
233
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;
238
239     if (!stagingMtlBuffer) {
240         LOG(WebGPU, "GPUBuffer::unmap(): Unable to create staging buffer!");
241         return;
242     }
243
244     memcpy(stagingMtlBuffer.get().contents, m_stagingBuffer->data(), m_byteLength);
245
246     BEGIN_BLOCK_OBJC_EXCEPTIONS;
247
248     auto commandBuffer = retainPtr([queue commandBuffer]);
249     auto blitEncoder = retainPtr([commandBuffer blitCommandEncoder]);
250
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];
254
255     END_BLOCK_OBJC_EXCEPTIONS;
256 }
257
258 void GPUBuffer::unmap()
259 {
260     if (!m_isMappedFromCreation && !isMappable()) {
261         LOG(WebGPU, "GPUBuffer::unmap(): Invalid operation: buffer is not mappable!");
262         return;
263     }
264
265     if (m_stagingBuffer) {
266         if (isMappable()) {
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();
272
273         m_isMappedFromCreation = false;
274         m_stagingBuffer = nullptr;
275     }
276
277     if (m_mappingCallback) {
278         m_mappingCallbackTask.cancelTask();
279         m_mappingCallback->callback(nullptr);
280         m_mappingCallback = nullptr;
281     }
282 }
283
284 void GPUBuffer::destroy()
285 {
286     if (state() == State::Mapped)
287         unmap();
288
289     m_platformBuffer = nullptr;
290 }
291
292 } // namespace WebCore
293
294 #endif // ENABLE(WEBGPU)