Unreviewed build fix for High Sierra.
[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)
46 {
47     if (!device.platformDevice()) {
48         LOG(WebGPU, "GPUBuffer::create(): Invalid GPUDevice!");
49         return false;
50     }
51
52     if (usage.containsAll({ GPUBufferUsage::Flags::MapWrite, GPUBufferUsage::Flags::MapRead })) {
53         LOG(WebGPU, "GPUBuffer::create(): 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::create(): 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(Ref<GPUDevice>&& device, GPUBufferDescriptor&& descriptor)
66 {
67     auto usage = OptionSet<GPUBufferUsage::Flags>::fromRaw(descriptor.usage);
68     if (!validateBufferUsage(device.get(), usage))
69         return nullptr;
70
71     // 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.
72
73     MTLResourceOptions resourceOptions = MTLResourceCPUCacheModeDefaultCache;
74
75     // Mappable buffers use shared storage allocation.
76     resourceOptions |= usage.containsAny({ GPUBufferUsage::Flags::MapWrite, GPUBufferUsage::Flags::MapRead }) ? MTLResourceStorageModeShared : MTLResourceStorageModePrivate;
77
78     RetainPtr<MTLBuffer> mtlBuffer;
79
80     BEGIN_BLOCK_OBJC_EXCEPTIONS;
81
82     mtlBuffer = adoptNS([device->platformDevice() newBufferWithLength:descriptor.size options:resourceOptions]);
83
84     END_BLOCK_OBJC_EXCEPTIONS;
85
86     if (!mtlBuffer) {
87         LOG(WebGPU, "GPUBuffer::create(): Unable to create MTLBuffer!");
88         return nullptr;
89     }
90
91     return adoptRef(*new GPUBuffer(WTFMove(mtlBuffer), descriptor, usage, WTFMove(device)));
92 }
93
94 GPUBuffer::GPUBuffer(RetainPtr<MTLBuffer>&& buffer, const GPUBufferDescriptor& descriptor, OptionSet<GPUBufferUsage::Flags> usage, Ref<GPUDevice>&& device)
95     : m_platformBuffer(WTFMove(buffer))
96     , m_device(WTFMove(device))
97     , m_byteLength(descriptor.size)
98     , m_usage(usage)
99 {
100 }
101
102 GPUBuffer::~GPUBuffer()
103 {
104     destroy();
105 }
106
107 bool GPUBuffer::isReadOnly() const
108 {
109     return m_usage.containsAny(readOnlyFlags);
110 }
111
112 GPUBuffer::State GPUBuffer::state() const
113 {
114     if (!m_platformBuffer)
115         return State::Destroyed;
116     if (m_mappingCallback)
117         return State::Mapped;
118
119     return State::Unmapped;
120 }
121
122 void GPUBuffer::setSubData(unsigned long offset, const JSC::ArrayBuffer& data)
123 {
124     if (!isTransferDestination() || state() != State::Unmapped) {
125         LOG(WebGPU, "GPUBuffer::setSubData(): Invalid operation!");
126         return;
127     }
128
129 #if PLATFORM(MAC)
130     if (offset % 4 || data.byteLength() % 4) {
131         LOG(WebGPU, "GPUBuffer::setSubData(): Data must be aligned to a multiple of 4 bytes!");
132         return;
133     }
134 #endif
135
136     auto subDataLength = checkedSum<unsigned long>(data.byteLength(), offset);
137     if (subDataLength.hasOverflowed() || subDataLength.unsafeGet() > m_byteLength) {
138         LOG(WebGPU, "GPUBuffer::setSubData(): Invalid offset or data size!");
139         return;
140     }
141
142     if (m_subDataBuffers.isEmpty()) {
143         BEGIN_BLOCK_OBJC_EXCEPTIONS;
144         m_subDataBuffers.append(adoptNS([m_platformBuffer.get().device newBufferWithLength:m_byteLength options:MTLResourceCPUCacheModeDefaultCache]));
145         END_BLOCK_OBJC_EXCEPTIONS;
146     }
147
148     __block auto stagingMtlBuffer = m_subDataBuffers.takeLast();
149
150     if (!stagingMtlBuffer || stagingMtlBuffer.get().length < data.byteLength()) {
151         LOG(WebGPU, "GPUBuffer::setSubData(): Unable to get staging buffer for provided data!");
152         return;
153     }
154
155     memcpy(stagingMtlBuffer.get().contents, data.data(), data.byteLength());
156
157     BEGIN_BLOCK_OBJC_EXCEPTIONS;
158
159     auto commandBuffer = retainPtr([m_device->getQueue()->platformQueue() commandBuffer]);
160     auto blitEncoder = retainPtr([commandBuffer blitCommandEncoder]);
161
162     [blitEncoder copyFromBuffer:stagingMtlBuffer.get() sourceOffset:0 toBuffer:m_platformBuffer.get() destinationOffset:offset size:stagingMtlBuffer.get().length];
163     [blitEncoder endEncoding];
164
165     if (isMappable())
166         commandBufferCommitted(commandBuffer.get());
167
168     auto protectedThis = makeRefPtr(this);
169     [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer>) {
170         protectedThis->reuseSubDataBuffer(WTFMove(stagingMtlBuffer));
171     }];
172     [commandBuffer commit];
173
174     END_BLOCK_OBJC_EXCEPTIONS;
175 }
176
177 #if USE(METAL)
178 void GPUBuffer::commandBufferCommitted(MTLCommandBuffer *commandBuffer)
179 {
180     ++m_numScheduledCommandBuffers;
181
182     auto protectedThis = makeRefPtr(this);
183     [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer>) {
184         protectedThis->commandBufferCompleted();
185     }];
186 }
187
188 void GPUBuffer::commandBufferCompleted()
189 {
190     ASSERT(m_numScheduledCommandBuffers);
191
192     if (m_numScheduledCommandBuffers == 1 && state() == State::Mapped) {
193         callOnMainThread([this, protectedThis = makeRef(*this)] () {
194             runMappingCallback();
195         });
196     }
197
198     --m_numScheduledCommandBuffers;
199 }
200
201 void GPUBuffer::reuseSubDataBuffer(RetainPtr<MTLBuffer>&& buffer)
202 {
203     m_subDataBuffers.append(WTFMove(buffer));
204 }
205 #endif // USE(METAL)
206
207 void GPUBuffer::registerMappingCallback(MappingCallback&& callback, bool isRead)
208 {
209     // Reject if request is invalid.
210     if (isRead && !isMapReadable()) {
211         LOG(WebGPU, "GPUBuffer::mapReadAsync(): Invalid operation!");
212         callback(nullptr);
213         return;
214     }
215     if (!isRead && !isMapWriteable()) {
216         LOG(WebGPU, "GPUBuffer::mapWriteAsync(): Invalid operation!");
217         callback(nullptr);
218         return;
219     }
220
221     ASSERT(!m_mappingCallback && !m_mappingCallbackTask.hasPendingTask());
222
223     // An existing callback means this buffer is in the mapped state.
224     m_mappingCallback = PendingMappingCallback::create(WTFMove(callback));
225
226     // If GPU is not using this buffer, run the callback ASAP.
227     if (!m_numScheduledCommandBuffers) {
228         m_mappingCallbackTask.scheduleTask([this, protectedThis = makeRef(*this)] () mutable {
229             runMappingCallback();
230         });
231     }
232 }
233
234 void GPUBuffer::runMappingCallback()
235 {
236     if (m_mappingCallback)
237         m_mappingCallback->callback(isMapRead() ? stagingBufferForRead() : stagingBufferForWrite());
238 }
239
240 JSC::ArrayBuffer* GPUBuffer::stagingBufferForRead()
241 {
242     if (!m_stagingBuffer)
243         m_stagingBuffer = ArrayBuffer::create(m_platformBuffer.get().contents, m_byteLength);
244     else
245         memcpy(m_stagingBuffer->data(), m_platformBuffer.get().contents, m_byteLength);
246
247     return m_stagingBuffer.get();
248 }
249
250 JSC::ArrayBuffer* GPUBuffer::stagingBufferForWrite()
251 {
252     m_stagingBuffer = ArrayBuffer::create(1, m_byteLength);
253     return m_stagingBuffer.get();
254 }
255
256 void GPUBuffer::unmap()
257 {
258     if (!isMappable()) {
259         LOG(WebGPU, "GPUBuffer::unmap(): Buffer is not mappable!");
260         return;
261     }
262
263     if (m_stagingBuffer && isMapWrite()) {
264         ASSERT(m_platformBuffer);
265         memcpy(m_platformBuffer.get().contents, m_stagingBuffer->data(), m_byteLength);
266         m_stagingBuffer = nullptr;
267     }
268
269     if (m_mappingCallback) {
270         m_mappingCallbackTask.cancelTask();
271         m_mappingCallback->callback(nullptr);
272         m_mappingCallback = nullptr;
273     }
274 }
275
276 void GPUBuffer::destroy()
277 {
278     if (state() == State::Mapped)
279         unmap();
280
281     m_platformBuffer = nullptr;
282 }
283
284 } // namespace WebCore
285
286 #endif // ENABLE(WEBGPU)