cbfbdd8e1a8c8a0374b7fcf2d5f7672a4cf07eec
[WebKit-https.git] / Source / WebCore / platform / graphics / gpu / cocoa / GPUBindGroupMetal.mm
1 /*
2  * Copyright (C) 2019 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 #import "config.h"
27 #import "GPUBindGroup.h"
28
29 #if ENABLE(WEBGPU)
30
31 #import "GPUBindGroupBinding.h"
32 #import "GPUBindGroupDescriptor.h"
33 #import "GPUBindGroupLayout.h"
34 #import "GPUSampler.h"
35 #import "Logging.h"
36 #import <Metal/Metal.h>
37 #import <wtf/BlockObjCExceptions.h>
38 #import <wtf/CheckedArithmetic.h>
39 #import <wtf/Optional.h>
40
41 namespace WebCore {
42
43 static RetainPtr<MTLBuffer> tryCreateArgumentBuffer(MTLArgumentEncoder *encoder)
44 {
45     RetainPtr<MTLBuffer> buffer;
46     BEGIN_BLOCK_OBJC_EXCEPTIONS;
47     buffer = adoptNS([encoder.device newBufferWithLength:encoder.encodedLength options:0]);
48     [encoder setArgumentBuffer:buffer.get() offset:0];
49     END_BLOCK_OBJC_EXCEPTIONS;
50     return buffer;
51 }
52     
53 static Optional<GPUBufferBinding> tryGetResourceAsBufferBinding(const GPUBindingResource& resource, const char* const functionName)
54 {
55 #if LOG_DISABLED
56     UNUSED_PARAM(functionName);
57 #endif
58     if (!WTF::holds_alternative<GPUBufferBinding>(resource)) {
59         LOG(WebGPU, "%s: Resource is not a buffer type!", functionName);
60         return WTF::nullopt;
61     }
62     auto& bufferBinding = WTF::get<GPUBufferBinding>(resource);
63     if (!bufferBinding.buffer->platformBuffer()) {
64         LOG(WebGPU, "%s: Invalid MTLBuffer in GPUBufferBinding!", functionName);
65         return WTF::nullopt;
66     }
67     // MTLBuffer size (NSUInteger) is 32 bits on some platforms.
68     if (!WTF::isInBounds<NSUInteger>(bufferBinding.offset)) {
69         LOG(WebGPU, "%s: Buffer offset is too large!", functionName);
70         return WTF::nullopt;
71     }
72     return GPUBufferBinding { bufferBinding.buffer.copyRef(), bufferBinding.offset, bufferBinding.size };
73 }
74
75 static void setBufferOnEncoder(MTLArgumentEncoder *argumentEncoder, const GPUBufferBinding& bufferBinding, unsigned name, unsigned lengthName)
76 {
77     ASSERT(argumentEncoder && bufferBinding.buffer->platformBuffer());
78
79     BEGIN_BLOCK_OBJC_EXCEPTIONS;
80     // Bounds check when converting GPUBufferBinding ensures that NSUInteger cast of uint64_t offset is safe.
81     [argumentEncoder setBuffer:bufferBinding.buffer->platformBuffer() offset:static_cast<NSUInteger>(bufferBinding.offset) atIndex:name];
82     void* lengthPointer = [argumentEncoder constantDataAtIndex:lengthName];
83     memcpy(lengthPointer, &bufferBinding.size, sizeof(uint64_t));
84     END_BLOCK_OBJC_EXCEPTIONS;
85 }
86     
87 static MTLSamplerState *tryGetResourceAsMtlSampler(const GPUBindingResource& resource, const char* const functionName)
88 {
89 #if LOG_DISABLED
90     UNUSED_PARAM(functionName);
91 #endif
92     if (!WTF::holds_alternative<Ref<const GPUSampler>>(resource)) {
93         LOG(WebGPU, "%s: Resource is not a GPUSampler!", functionName);
94         return nullptr;
95     }
96     auto samplerState = WTF::get<Ref<const GPUSampler>>(resource)->platformSampler();
97     if (!samplerState) {
98         LOG(WebGPU, "%s: Invalid MTLSamplerState in GPUSampler binding!", functionName);
99         return nullptr;
100     }
101     return samplerState;
102 }
103
104 static void setSamplerOnEncoder(MTLArgumentEncoder *argumentEncoder, MTLSamplerState *sampler, unsigned index)
105 {
106     ASSERT(argumentEncoder && sampler);
107
108     BEGIN_BLOCK_OBJC_EXCEPTIONS;
109     [argumentEncoder setSamplerState:sampler atIndex:index];
110     END_BLOCK_OBJC_EXCEPTIONS;
111 }
112
113 static RefPtr<GPUTexture> tryGetResourceAsTexture(const GPUBindingResource& resource, const char* const functionName)
114     {
115 #if LOG_DISABLED
116     UNUSED_PARAM(functionName);
117 #endif
118     if (!WTF::holds_alternative<Ref<GPUTexture>>(resource)) {
119         LOG(WebGPU, "%s: Resource is not a GPUTextureView!", functionName);
120         return nullptr;
121     }
122     auto& textureRef = WTF::get<Ref<GPUTexture>>(resource);
123     if (!textureRef->platformTexture()) {
124         LOG(WebGPU, "%s: Invalid MTLTexture in GPUTextureView binding!", functionName);
125         return nullptr;
126     }
127     return textureRef.copyRef();
128 }
129
130 static void setTextureOnEncoder(MTLArgumentEncoder *argumentEncoder, MTLTexture *texture, unsigned index)
131 {
132     ASSERT(argumentEncoder && texture);
133
134     BEGIN_BLOCK_OBJC_EXCEPTIONS;
135     [argumentEncoder setTexture:texture atIndex:index];
136     END_BLOCK_OBJC_EXCEPTIONS;
137 }
138     
139 RefPtr<GPUBindGroup> GPUBindGroup::tryCreate(const GPUBindGroupDescriptor& descriptor)
140 {
141     const char* const functionName = "GPUBindGroup::tryCreate()";
142     
143     MTLArgumentEncoder *vertexEncoder = descriptor.layout->vertexEncoder();
144     MTLArgumentEncoder *fragmentEncoder = descriptor.layout->fragmentEncoder();
145     MTLArgumentEncoder *computeEncoder = descriptor.layout->computeEncoder();
146     
147     RetainPtr<MTLBuffer> vertexArgsBuffer;
148     if (vertexEncoder && !(vertexArgsBuffer = tryCreateArgumentBuffer(vertexEncoder))) {
149         LOG(WebGPU, "%s: Unable to create MTLBuffer for vertex argument buffer!", functionName);
150         return nullptr;
151     }
152     RetainPtr<MTLBuffer> fragmentArgsBuffer;
153     if (fragmentEncoder && !(fragmentArgsBuffer = tryCreateArgumentBuffer(fragmentEncoder))) {
154         LOG(WebGPU, "%s: Unable to create MTLBuffer for fragment argument buffer!", functionName);
155         return nullptr;
156     }
157     RetainPtr<MTLBuffer> computeArgsBuffer;
158     if (computeEncoder && !(computeArgsBuffer = tryCreateArgumentBuffer(computeEncoder))) {
159         LOG(WebGPU, "%s: Unable to create MTLBuffer for compute argument buffer!", functionName);
160         return nullptr;
161     }
162     
163     HashSet<Ref<GPUBuffer>> boundBuffers;
164     HashSet<Ref<GPUTexture>> boundTextures;
165
166     // Set each resource on each MTLArgumentEncoder it should be visible on.
167     const auto& layoutBindingsMap = descriptor.layout->bindingsMap();
168     for (const auto& resourceBinding : descriptor.bindings) {
169         auto index = resourceBinding.binding;
170         auto layoutIterator = layoutBindingsMap.find(index);
171         if (layoutIterator == layoutBindingsMap.end()) {
172             LOG(WebGPU, "%s: GPUBindGroupBinding %u not found in GPUBindGroupLayout!", functionName, index);
173             return nullptr;
174         }
175         auto layoutBinding = layoutIterator->value;
176         if (layoutBinding.externalBinding.visibility == GPUShaderStageBit::Flags::None)
177             continue;
178
179         bool isForVertex = layoutBinding.externalBinding.visibility & GPUShaderStageBit::Flags::Vertex;
180         bool isForFragment = layoutBinding.externalBinding.visibility & GPUShaderStageBit::Flags::Fragment;
181         bool isForCompute = layoutBinding.externalBinding.visibility & GPUShaderStageBit::Flags::Compute;
182
183         if (isForVertex && !vertexEncoder) {
184             LOG(WebGPU, "%s: No vertex argument encoder found for binding %u!", functionName, index);
185             return nullptr;
186         }
187         if (isForFragment && !fragmentEncoder) {
188             LOG(WebGPU, "%s: No fragment argument encoder found for binding %u!", functionName, index);
189             return nullptr;
190         }
191         if (isForCompute && !computeEncoder) {
192             LOG(WebGPU, "%s: No compute argument encoder found for binding %u!", functionName, index);
193             return nullptr;
194         }
195
196         auto handleBuffer = [&](unsigned internalLengthName) -> bool {
197             auto bufferResource = tryGetResourceAsBufferBinding(resourceBinding.resource, functionName);
198             if (!bufferResource)
199                 return false;
200             if (isForVertex)
201                 setBufferOnEncoder(vertexEncoder, *bufferResource, layoutBinding.internalName, internalLengthName);
202             if (isForFragment)
203                 setBufferOnEncoder(fragmentEncoder, *bufferResource, layoutBinding.internalName, internalLengthName);
204             if (isForCompute)
205                 setBufferOnEncoder(computeEncoder, *bufferResource, layoutBinding.internalName, internalLengthName);
206             boundBuffers.addVoid(bufferResource->buffer.copyRef());
207             return true;
208         };
209
210         auto success = WTF::visit(WTF::makeVisitor([&](GPUBindGroupLayout::UniformBuffer& uniformBuffer) -> bool {
211             return handleBuffer(uniformBuffer.internalLengthName);
212         }, [&](GPUBindGroupLayout::DynamicUniformBuffer& dynamicUniformBuffer) -> bool {
213             return handleBuffer(dynamicUniformBuffer.internalLengthName);
214         }, [&](GPUBindGroupLayout::Sampler&) -> bool {
215             auto samplerState = tryGetResourceAsMtlSampler(resourceBinding.resource, functionName);
216             if (!samplerState)
217                 return false;
218             if (isForVertex)
219                 setSamplerOnEncoder(vertexEncoder, samplerState, layoutBinding.internalName);
220             if (isForFragment)
221                 setSamplerOnEncoder(fragmentEncoder, samplerState, layoutBinding.internalName);
222             if (isForCompute)
223                 setSamplerOnEncoder(computeEncoder, samplerState, layoutBinding.internalName);
224             return true;
225         }, [&](GPUBindGroupLayout::SampledTexture&) -> bool {
226             auto textureResource = tryGetResourceAsTexture(resourceBinding.resource, functionName);
227             if (!textureResource)
228                 return false;
229             if (isForVertex)
230                 setTextureOnEncoder(vertexEncoder, textureResource->platformTexture(), layoutBinding.internalName);
231             if (isForFragment)
232                 setTextureOnEncoder(fragmentEncoder, textureResource->platformTexture(), layoutBinding.internalName);
233             if (isForCompute)
234                 setTextureOnEncoder(computeEncoder, textureResource->platformTexture(), layoutBinding.internalName);
235             boundTextures.addVoid(textureResource.releaseNonNull());
236             return true;
237         }, [&](GPUBindGroupLayout::StorageBuffer& storageBuffer) -> bool {
238             return handleBuffer(storageBuffer.internalLengthName);
239         }, [&](GPUBindGroupLayout::DynamicStorageBuffer& dynamicStorageBuffer) -> bool {
240             return handleBuffer(dynamicStorageBuffer.internalLengthName);
241         }), layoutBinding.internalBindingDetails);
242         if (!success)
243             return nullptr;
244     }
245     
246     return adoptRef(new GPUBindGroup(WTFMove(vertexArgsBuffer), WTFMove(fragmentArgsBuffer), WTFMove(computeArgsBuffer), WTFMove(boundBuffers), WTFMove(boundTextures)));
247 }
248     
249 GPUBindGroup::GPUBindGroup(RetainPtr<MTLBuffer>&& vertexBuffer, RetainPtr<MTLBuffer>&& fragmentBuffer, RetainPtr<MTLBuffer>&& computeBuffer, HashSet<Ref<GPUBuffer>>&& buffers, HashSet<Ref<GPUTexture>>&& textures)
250     : m_vertexArgsBuffer(WTFMove(vertexBuffer))
251     , m_fragmentArgsBuffer(WTFMove(fragmentBuffer))
252     , m_computeArgsBuffer(WTFMove(computeBuffer))
253     , m_boundBuffers(WTFMove(buffers))
254     , m_boundTextures(WTFMove(textures))
255 {
256 }
257     
258 } // namespace WebCore
259
260 #endif // ENABLE(WEBPGU)