2b277dccb6459ae5691303003ef33d26336916c5
[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/CheckedArithmetic.h>
38 #import <wtf/MainThread.h>
39
40 namespace WebCore {
41
42 static constexpr auto readOnlyFlags = OptionSet<GPUBufferUsage::Flags> { GPUBufferUsage::Flags::Index, GPUBufferUsage::Flags::Vertex, GPUBufferUsage::Flags::Uniform, GPUBufferUsage::Flags::TransferSource };
43
44
45 bool GPUBuffer::validateBufferUsage(const GPUDevice& device, OptionSet<GPUBufferUsage::Flags> usage, GPUErrorScopes& errorScopes)
46 {
47     if (!device.platformDevice()) {
48         LOG(WebGPU, "GPUBuffer::tryCreate(): Invalid GPUDevice!");
49         return false;
50     }
51
52     if (usage.containsAll({ GPUBufferUsage::Flags::MapWrite, GPUBufferUsage::Flags::MapRead })) {
53         errorScopes.generatePrefixedError("Buffer cannot have both MAP_READ and MAP_WRITE usage!");
54         return false;
55     }
56
57     if (usage.containsAny(readOnlyFlags) && (usage & GPUBufferUsage::Flags::Storage)) {
58         LOG(WebGPU, "GPUBuffer::tryCreate(): Buffer cannot have both STORAGE and a read-only usage!");
59         return false;
60     }
61
62     return true;
63 }
64
65 RefPtr<GPUBuffer> GPUBuffer::tryCreate(GPUDevice& device, const GPUBufferDescriptor& descriptor, GPUBufferMappedOption isMapped, GPUErrorScopes& errorScopes)
66 {
67     // MTLBuffer size (NSUInteger) is 32 bits on some platforms.
68     NSUInteger size = 0;
69     if (!WTF::convertSafely(descriptor.size, size)) {
70         errorScopes.generateError("", GPUErrorFilter::OutOfMemory);
71         return nullptr;
72     }
73
74     auto usage = OptionSet<GPUBufferUsage::Flags>::fromRaw(descriptor.usage);
75     if (!validateBufferUsage(device, usage, errorScopes))
76         return nullptr;
77
78 #if PLATFORM(MAC)
79     // copyBufferToBuffer calls require 4-byte alignment. "Unmapping" a mapped-on-creation GPUBuffer
80     // that is otherwise unmappable requires such a copy to upload data.
81     if (isMapped == GPUBufferMappedOption::IsMapped
82         && !usage.containsAny({ GPUBufferUsage::Flags::MapWrite, GPUBufferUsage::Flags::MapRead })
83         && descriptor.size % 4) {
84         LOG(WebGPU, "GPUBuffer::tryCreate(): Data must be aligned to a multiple of 4 bytes!");
85         return nullptr;
86     }
87 #endif
88
89     // 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.
90
91     MTLResourceOptions resourceOptions = MTLResourceCPUCacheModeDefaultCache;
92
93     // Mappable buffers use shared storage allocation.
94     resourceOptions |= usage.containsAny({ GPUBufferUsage::Flags::MapWrite, GPUBufferUsage::Flags::MapRead }) ? MTLResourceStorageModeShared : MTLResourceStorageModePrivate;
95
96     RetainPtr<MTLBuffer> mtlBuffer;
97
98     BEGIN_BLOCK_OBJC_EXCEPTIONS;
99
100     mtlBuffer = adoptNS([device.platformDevice() newBufferWithLength:static_cast<NSUInteger>(descriptor.size) options:resourceOptions]);
101
102     END_BLOCK_OBJC_EXCEPTIONS;
103
104     if (!mtlBuffer) {
105         errorScopes.generateError("", GPUErrorFilter::OutOfMemory);
106         return nullptr;
107     }
108
109     return adoptRef(*new GPUBuffer(WTFMove(mtlBuffer), device, size, usage, isMapped, errorScopes));
110 }
111
112 GPUBuffer::GPUBuffer(RetainPtr<MTLBuffer>&& buffer, GPUDevice& device, size_t size, OptionSet<GPUBufferUsage::Flags> usage, GPUBufferMappedOption isMapped, GPUErrorScopes& errorScopes)
113     : GPUObjectBase(makeRef(errorScopes))
114     , m_platformBuffer(WTFMove(buffer))
115     , m_device(makeRef(device))
116     , m_byteLength(size)
117     , m_usage(usage)
118     , m_isMappedFromCreation(isMapped == GPUBufferMappedOption::IsMapped)
119 {
120 }
121
122 GPUBuffer::~GPUBuffer()
123 {
124     destroy();
125 }
126
127 bool GPUBuffer::isReadOnly() const
128 {
129     return m_usage.containsAny(readOnlyFlags);
130 }
131
132 GPUBuffer::State GPUBuffer::state() const
133 {
134     if (!m_platformBuffer)
135         return State::Destroyed;
136     if (m_isMappedFromCreation || m_mappingCallback)
137         return State::Mapped;
138
139     return State::Unmapped;
140 }
141
142 JSC::ArrayBuffer* GPUBuffer::mapOnCreation()
143 {
144     ASSERT(m_isMappedFromCreation);
145     return stagingBufferForWrite();
146 }
147
148 #if USE(METAL)
149 void GPUBuffer::commandBufferCommitted(MTLCommandBuffer *commandBuffer)
150 {
151     ++m_numScheduledCommandBuffers;
152
153     auto protectedThis = makeRefPtr(this);
154     BEGIN_BLOCK_OBJC_EXCEPTIONS;
155     [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer>) {
156         protectedThis->commandBufferCompleted();
157     }];
158     END_BLOCK_OBJC_EXCEPTIONS;
159 }
160
161 void GPUBuffer::commandBufferCompleted()
162 {
163     ASSERT(m_numScheduledCommandBuffers);
164
165     if (m_numScheduledCommandBuffers == 1 && state() == State::Mapped) {
166         callOnMainThread([this, protectedThis = makeRef(*this)] () {
167             runMappingCallback();
168         });
169     }
170
171     --m_numScheduledCommandBuffers;
172 }
173 #endif // USE(METAL)
174
175 void GPUBuffer::registerMappingCallback(MappingCallback&& callback, bool isRead)
176 {
177     // Reject if request is invalid.
178     if (isRead && !isMapReadable()) {
179         LOG(WebGPU, "GPUBuffer::mapReadAsync(): Invalid operation!");
180         callback(nullptr);
181         return;
182     }
183     if (!isRead && !isMapWriteable()) {
184         LOG(WebGPU, "GPUBuffer::mapWriteAsync(): Invalid operation!");
185         callback(nullptr);
186         return;
187     }
188
189     ASSERT(!m_mappingCallback && !m_mappingCallbackTask.hasPendingTask());
190
191     // An existing callback means this buffer is in the mapped state.
192     m_mappingCallback = PendingMappingCallback::create(WTFMove(callback));
193
194     // If GPU is not using this buffer, run the callback ASAP.
195     if (!m_numScheduledCommandBuffers) {
196         m_mappingCallbackTask.scheduleTask([this, protectedThis = makeRef(*this)] () mutable {
197             runMappingCallback();
198         });
199     }
200 }
201
202 void GPUBuffer::runMappingCallback()
203 {
204     if (m_mappingCallback)
205         m_mappingCallback->callback(isMapRead() ? stagingBufferForRead() : stagingBufferForWrite());
206 }
207
208 JSC::ArrayBuffer* GPUBuffer::stagingBufferForRead()
209 {
210     if (!m_stagingBuffer)
211         m_stagingBuffer = ArrayBuffer::create(m_platformBuffer.get().contents, m_byteLength);
212     else
213         memcpy(m_stagingBuffer->data(), m_platformBuffer.get().contents, m_byteLength);
214
215     return m_stagingBuffer.get();
216 }
217
218 JSC::ArrayBuffer* GPUBuffer::stagingBufferForWrite()
219 {
220     m_stagingBuffer = ArrayBuffer::create(1, m_byteLength);
221     return m_stagingBuffer.get();
222 }
223
224 void GPUBuffer::copyStagingBufferToGPU()
225 {
226     MTLCommandQueue *queue;
227     if (!m_device->tryGetQueue() || !(queue = m_device->tryGetQueue()->platformQueue()))
228         return;
229
230     RetainPtr<MTLBuffer> stagingMtlBuffer;
231
232     BEGIN_BLOCK_OBJC_EXCEPTIONS;
233     // GPUBuffer creation validation ensures m_byteSize fits in NSUInteger.
234     stagingMtlBuffer = adoptNS([m_device->platformDevice() newBufferWithLength:static_cast<NSUInteger>(m_byteLength) options:MTLResourceCPUCacheModeDefaultCache]);
235     END_BLOCK_OBJC_EXCEPTIONS;
236
237     if (!stagingMtlBuffer) {
238         LOG(WebGPU, "GPUBuffer::unmap(): Unable to create staging buffer!");
239         return;
240     }
241
242     memcpy(stagingMtlBuffer.get().contents, m_stagingBuffer->data(), m_byteLength);
243
244     BEGIN_BLOCK_OBJC_EXCEPTIONS;
245
246     auto commandBuffer = retainPtr([queue commandBuffer]);
247     auto blitEncoder = retainPtr([commandBuffer blitCommandEncoder]);
248
249     [blitEncoder copyFromBuffer:stagingMtlBuffer.get() sourceOffset:0 toBuffer:m_platformBuffer.get() destinationOffset:0 size:static_cast<NSUInteger>(m_byteLength)];
250     [blitEncoder endEncoding];
251     [commandBuffer commit];
252
253     END_BLOCK_OBJC_EXCEPTIONS;
254 }
255
256 void GPUBuffer::unmap()
257 {
258     if (!m_isMappedFromCreation && !isMappable()) {
259         LOG(WebGPU, "GPUBuffer::unmap(): Invalid operation: buffer is not mappable!");
260         return;
261     }
262
263     if (m_stagingBuffer) {
264         if (isMappable()) {
265             // MAP_WRITE and MAP_READ buffers have shared, CPU-accessible storage.
266             ASSERT(m_platformBuffer && m_platformBuffer.get().contents);
267             memcpy(m_platformBuffer.get().contents, m_stagingBuffer->data(), m_byteLength);
268         } else if (m_isMappedFromCreation)
269             copyStagingBufferToGPU();
270
271         m_isMappedFromCreation = false;
272         m_stagingBuffer = nullptr;
273     }
274
275     if (m_mappingCallback) {
276         m_mappingCallbackTask.cancelTask();
277         m_mappingCallback->callback(nullptr);
278         m_mappingCallback = nullptr;
279     }
280 }
281
282 void GPUBuffer::destroy()
283 {
284     if (state() == State::Mapped)
285         unmap();
286
287     m_platformBuffer = nullptr;
288 }
289
290 } // namespace WebCore
291
292 #endif // ENABLE(WEBGPU)