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