blob: e54cc29a4a5ab2dc1de26a13742e5f0f4989c4e8 [file] [log] [blame]
/*
* Copyright (C) 2019 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "GPUBindGroupAllocator.h"
#if ENABLE(WEBGPU)
#import "GPUErrorScopes.h"
#import <Metal/Metal.h>
#import <wtf/BlockObjCExceptions.h>
#import <wtf/CheckedArithmetic.h>
namespace WebCore {
Ref<GPUBindGroupAllocator> GPUBindGroupAllocator::create(GPUErrorScopes& errors)
{
return adoptRef(*new GPUBindGroupAllocator(errors));
}
GPUBindGroupAllocator::GPUBindGroupAllocator(GPUErrorScopes& errors)
: m_errorScopes(makeRef(errors))
{
}
#if USE(METAL)
Optional<GPUBindGroupAllocator::ArgumentBufferOffsets> GPUBindGroupAllocator::allocateAndSetEncoders(MTLArgumentEncoder *vertex, MTLArgumentEncoder *fragment, MTLArgumentEncoder *compute)
{
id<MTLDevice> device = nil;
auto checkedOffset = Checked<NSUInteger>(m_lastOffset);
if (vertex) {
device = vertex.device;
checkedOffset += vertex.encodedLength;
}
if (fragment) {
device = fragment.device;
checkedOffset += fragment.encodedLength;
}
if (compute) {
device = compute.device;
checkedOffset += compute.encodedLength;
}
// No arguments; nothing to be done.
if (!device)
return { };
if (checkedOffset.hasOverflowed()) {
m_errorScopes->generateError("", GPUErrorFilter::OutOfMemory);
return { };
}
auto newOffset = checkedOffset.unsafeGet();
if (m_argumentBuffer && newOffset > m_argumentBuffer.get().length) {
if (!reallocate(newOffset))
return { };
} else if (!m_argumentBuffer) {
// Based off mimimum allocation for a shared-memory MTLBuffer on macOS.
NSUInteger minimumSize = 4096;
if (minimumSize < newOffset)
minimumSize = newOffset;
BEGIN_BLOCK_OBJC_EXCEPTIONS;
m_argumentBuffer = adoptNS([device newBufferWithLength:minimumSize options:0]);
END_BLOCK_OBJC_EXCEPTIONS;
if (!m_argumentBuffer) {
m_errorScopes->generateError("", GPUErrorFilter::OutOfMemory);
return { };
}
}
ArgumentBufferOffsets offsets;
// Math in the following section is guarded against overflow by newOffset calculation.
BEGIN_BLOCK_OBJC_EXCEPTIONS;
if (vertex) {
offsets.vertex = m_lastOffset;
[vertex setArgumentBuffer:m_argumentBuffer.get() offset:*offsets.vertex];
}
if (fragment) {
offsets.fragment = (vertex ? vertex.encodedLength : 0) + m_lastOffset;
[fragment setArgumentBuffer:m_argumentBuffer.get() offset:*offsets.fragment];
}
if (compute) {
offsets.compute = (vertex ? vertex.encodedLength : 0) + (fragment ? fragment.encodedLength : 0) + m_lastOffset;
[compute setArgumentBuffer:m_argumentBuffer.get() offset:*offsets.compute];
}
END_BLOCK_OBJC_EXCEPTIONS;
m_lastOffset = newOffset;
return offsets;
}
// FIXME: https://bugs.webkit.org/show_bug.cgi?id=200657, https://bugs.webkit.org/show_bug.cgi?id=200658 Optimize reallocation and reset behavior.
bool GPUBindGroupAllocator::reallocate(NSUInteger newOffset)
{
MTLBuffer *newBuffer = nil;
auto newLength = Checked<NSUInteger>(m_argumentBuffer.get().length);
while (newLength < newOffset) {
newLength *= 1.25;
if (newLength.hasOverflowed()) {
newLength = std::numeric_limits<NSUInteger>::max();
break;
}
}
BEGIN_BLOCK_OBJC_EXCEPTIONS;
newBuffer = [m_argumentBuffer.get().device newBufferWithLength:newLength.unsafeGet() options:0];
memcpy(newBuffer.contents, m_argumentBuffer.get().contents, m_argumentBuffer.get().length);
END_BLOCK_OBJC_EXCEPTIONS;
if (!newBuffer) {
m_errorScopes->generateError("", GPUErrorFilter::OutOfMemory);
return false;
}
m_argumentBuffer = adoptNS(newBuffer);
return true;
}
void GPUBindGroupAllocator::tryReset()
{
if (!hasOneRef())
return;
m_argumentBuffer = nullptr;
m_lastOffset = 0;
}
#endif // USE(METAL)
} // namespace WebCore
#endif // ENABLE(WEBGPU)