blob: d26473b596d37d0d565762e38c02a3741f23aa32 [file] [log] [blame]
/*
* Copyright (C) 2018 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.
*/
#include "config.h"
#include "GPUBuffer.h"
#if ENABLE(WEBGPU)
#import "GPUBufferDescriptor.h"
#import "GPUDevice.h"
#import "Logging.h"
#import <JavaScriptCore/ArrayBuffer.h>
#import <Metal/Metal.h>
#import <wtf/BlockObjCExceptions.h>
#import <wtf/CheckedArithmetic.h>
#import <wtf/MainThread.h>
namespace WebCore {
static constexpr auto readOnlyFlags = OptionSet<GPUBufferUsage::Flags> { GPUBufferUsage::Flags::Index, GPUBufferUsage::Flags::Vertex, GPUBufferUsage::Flags::Uniform, GPUBufferUsage::Flags::TransferSource };
bool GPUBuffer::validateBufferUsage(GPUDevice& device, OptionSet<GPUBufferUsage::Flags> usage, GPUErrorScopes& errorScopes)
{
if (!device.platformDevice()) {
LOG(WebGPU, "GPUBuffer::tryCreate(): Invalid GPUDevice!");
return false;
}
if (usage.containsAll({ GPUBufferUsage::Flags::MapWrite, GPUBufferUsage::Flags::MapRead })) {
errorScopes.generateError("GPUBuffer::tryCreate(): Buffer cannot have both MAP_READ and MAP_WRITE usage!");
return false;
}
if (usage.containsAny(readOnlyFlags) && (usage & GPUBufferUsage::Flags::Storage)) {
LOG(WebGPU, "GPUBuffer::tryCreate(): Buffer cannot have both STORAGE and a read-only usage!");
return false;
}
return true;
}
RefPtr<GPUBuffer> GPUBuffer::tryCreate(Ref<GPUDevice>&& device, const GPUBufferDescriptor& descriptor, GPUBufferMappedOption isMapped, Ref<GPUErrorScopes>&& errorScopes)
{
// MTLBuffer size (NSUInteger) is 32 bits on some platforms.
NSUInteger size = 0;
if (!WTF::convertSafely(descriptor.size, size)) {
errorScopes->generateError("", GPUErrorFilter::OutOfMemory);
return nullptr;
}
auto usage = OptionSet<GPUBufferUsage::Flags>::fromRaw(descriptor.usage);
if (!validateBufferUsage(device.get(), usage, errorScopes))
return nullptr;
#if PLATFORM(MAC)
// copyBufferToBuffer calls require 4-byte alignment. "Unmapping" a mapped-on-creation GPUBuffer
// that is otherwise unmappable requires such a copy to upload data.
if (isMapped == GPUBufferMappedOption::IsMapped
&& !usage.containsAny({ GPUBufferUsage::Flags::MapWrite, GPUBufferUsage::Flags::MapRead })
&& descriptor.size % 4) {
LOG(WebGPU, "GPUBuffer::tryCreate(): Data must be aligned to a multiple of 4 bytes!");
return nullptr;
}
#endif
// 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.
MTLResourceOptions resourceOptions = MTLResourceCPUCacheModeDefaultCache;
// Mappable buffers use shared storage allocation.
resourceOptions |= usage.containsAny({ GPUBufferUsage::Flags::MapWrite, GPUBufferUsage::Flags::MapRead }) ? MTLResourceStorageModeShared : MTLResourceStorageModePrivate;
RetainPtr<MTLBuffer> mtlBuffer;
BEGIN_BLOCK_OBJC_EXCEPTIONS;
mtlBuffer = adoptNS([device->platformDevice() newBufferWithLength:static_cast<NSUInteger>(descriptor.size) options:resourceOptions]);
END_BLOCK_OBJC_EXCEPTIONS;
if (!mtlBuffer) {
errorScopes->generateError("", GPUErrorFilter::OutOfMemory);
return nullptr;
}
return adoptRef(*new GPUBuffer(WTFMove(mtlBuffer), WTFMove(device), size, usage, isMapped, WTFMove(errorScopes)));
}
GPUBuffer::GPUBuffer(RetainPtr<MTLBuffer>&& buffer, Ref<GPUDevice>&& device, size_t size, OptionSet<GPUBufferUsage::Flags> usage, GPUBufferMappedOption isMapped, Ref<GPUErrorScopes>&& errorScopes)
: GPUObjectBase(WTFMove(errorScopes))
, m_platformBuffer(WTFMove(buffer))
, m_device(WTFMove(device))
, m_byteLength(size)
, m_usage(usage)
, m_isMappedFromCreation(isMapped == GPUBufferMappedOption::IsMapped)
{
}
GPUBuffer::~GPUBuffer()
{
destroy();
}
bool GPUBuffer::isReadOnly() const
{
return m_usage.containsAny(readOnlyFlags);
}
GPUBuffer::State GPUBuffer::state() const
{
if (!m_platformBuffer)
return State::Destroyed;
if (m_isMappedFromCreation || m_mappingCallback)
return State::Mapped;
return State::Unmapped;
}
JSC::ArrayBuffer* GPUBuffer::mapOnCreation()
{
ASSERT(m_isMappedFromCreation);
return stagingBufferForWrite();
}
#if USE(METAL)
void GPUBuffer::commandBufferCommitted(MTLCommandBuffer *commandBuffer)
{
++m_numScheduledCommandBuffers;
auto protectedThis = makeRefPtr(this);
BEGIN_BLOCK_OBJC_EXCEPTIONS;
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer>) {
protectedThis->commandBufferCompleted();
}];
END_BLOCK_OBJC_EXCEPTIONS;
}
void GPUBuffer::commandBufferCompleted()
{
ASSERT(m_numScheduledCommandBuffers);
if (m_numScheduledCommandBuffers == 1 && state() == State::Mapped) {
callOnMainThread([this, protectedThis = makeRef(*this)] () {
runMappingCallback();
});
}
--m_numScheduledCommandBuffers;
}
#endif // USE(METAL)
void GPUBuffer::registerMappingCallback(MappingCallback&& callback, bool isRead)
{
// Reject if request is invalid.
if (isRead && !isMapReadable()) {
LOG(WebGPU, "GPUBuffer::mapReadAsync(): Invalid operation!");
callback(nullptr);
return;
}
if (!isRead && !isMapWriteable()) {
LOG(WebGPU, "GPUBuffer::mapWriteAsync(): Invalid operation!");
callback(nullptr);
return;
}
ASSERT(!m_mappingCallback && !m_mappingCallbackTask.hasPendingTask());
// An existing callback means this buffer is in the mapped state.
m_mappingCallback = PendingMappingCallback::create(WTFMove(callback));
// If GPU is not using this buffer, run the callback ASAP.
if (!m_numScheduledCommandBuffers) {
m_mappingCallbackTask.scheduleTask([this, protectedThis = makeRef(*this)] () mutable {
runMappingCallback();
});
}
}
void GPUBuffer::runMappingCallback()
{
if (m_mappingCallback)
m_mappingCallback->callback(isMapRead() ? stagingBufferForRead() : stagingBufferForWrite());
}
JSC::ArrayBuffer* GPUBuffer::stagingBufferForRead()
{
if (!m_stagingBuffer)
m_stagingBuffer = ArrayBuffer::create(m_platformBuffer.get().contents, m_byteLength);
else
memcpy(m_stagingBuffer->data(), m_platformBuffer.get().contents, m_byteLength);
return m_stagingBuffer.get();
}
JSC::ArrayBuffer* GPUBuffer::stagingBufferForWrite()
{
m_stagingBuffer = ArrayBuffer::create(1, m_byteLength);
return m_stagingBuffer.get();
}
void GPUBuffer::copyStagingBufferToGPU()
{
MTLCommandQueue *queue;
if (!m_device->tryGetQueue() || !(queue = m_device->tryGetQueue()->platformQueue()))
return;
RetainPtr<MTLBuffer> stagingMtlBuffer;
BEGIN_BLOCK_OBJC_EXCEPTIONS;
// GPUBuffer creation validation ensures m_byteSize fits in NSUInteger.
stagingMtlBuffer = adoptNS([m_device->platformDevice() newBufferWithLength:static_cast<NSUInteger>(m_byteLength) options:MTLResourceCPUCacheModeDefaultCache]);
END_BLOCK_OBJC_EXCEPTIONS;
if (!stagingMtlBuffer) {
LOG(WebGPU, "GPUBuffer::unmap(): Unable to create staging buffer!");
return;
}
memcpy(stagingMtlBuffer.get().contents, m_stagingBuffer->data(), m_byteLength);
BEGIN_BLOCK_OBJC_EXCEPTIONS;
auto commandBuffer = retainPtr([queue commandBuffer]);
auto blitEncoder = retainPtr([commandBuffer blitCommandEncoder]);
[blitEncoder copyFromBuffer:stagingMtlBuffer.get() sourceOffset:0 toBuffer:m_platformBuffer.get() destinationOffset:0 size:static_cast<NSUInteger>(m_byteLength)];
[blitEncoder endEncoding];
[commandBuffer commit];
END_BLOCK_OBJC_EXCEPTIONS;
}
void GPUBuffer::unmap()
{
if (!m_isMappedFromCreation && !isMappable()) {
LOG(WebGPU, "GPUBuffer::unmap(): Invalid operation: buffer is not mappable!");
return;
}
if (m_stagingBuffer) {
if (isMappable()) {
// MAP_WRITE and MAP_READ buffers have shared, CPU-accessible storage.
ASSERT(m_platformBuffer && m_platformBuffer.get().contents);
memcpy(m_platformBuffer.get().contents, m_stagingBuffer->data(), m_byteLength);
} else if (m_isMappedFromCreation)
copyStagingBufferToGPU();
m_isMappedFromCreation = false;
m_stagingBuffer = nullptr;
}
if (m_mappingCallback) {
m_mappingCallbackTask.cancelTask();
m_mappingCallback->callback(nullptr);
m_mappingCallback = nullptr;
}
}
void GPUBuffer::destroy()
{
if (state() == State::Mapped)
unmap();
m_platformBuffer = nullptr;
}
} // namespace WebCore
#endif // ENABLE(WEBGPU)