1989 lines
67 KiB
C++
1989 lines
67 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "WebGPUParent.h"
|
|
|
|
#include <unordered_set>
|
|
|
|
#include "ExternalTexture.h"
|
|
#include "mozilla/ScopeExit.h"
|
|
#include "mozilla/dom/WebGPUBinding.h"
|
|
#include "mozilla/gfx/FileHandleWrapper.h"
|
|
#include "mozilla/gfx/gfxVars.h"
|
|
#include "mozilla/layers/CompositorThread.h"
|
|
#include "mozilla/layers/ImageDataSerializer.h"
|
|
#include "mozilla/layers/RemoteTextureMap.h"
|
|
#include "mozilla/layers/TextureHost.h"
|
|
#include "mozilla/layers/WebRenderImageHost.h"
|
|
#include "mozilla/layers/WebRenderTextureHost.h"
|
|
#include "mozilla/webgpu/SharedTexture.h"
|
|
#include "mozilla/webgpu/ffi/wgpu.h"
|
|
|
|
#if defined(XP_WIN)
|
|
# include "mozilla/gfx/DeviceManagerDx.h"
|
|
# include "mozilla/webgpu/SharedTextureD3D11.h"
|
|
#endif
|
|
|
|
#if defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID)
|
|
# include "mozilla/webgpu/SharedTextureDMABuf.h"
|
|
#endif
|
|
|
|
#if defined(XP_MACOSX)
|
|
# include "mozilla/webgpu/SharedTextureMacIOSurface.h"
|
|
#endif
|
|
|
|
namespace mozilla::webgpu {
|
|
|
|
const uint64_t POLL_TIME_MS = 100;
|
|
|
|
static mozilla::LazyLogModule sLogger("WebGPU");
|
|
|
|
namespace ffi {
|
|
|
|
extern bool wgpu_server_use_shared_texture_for_swap_chain(
|
|
WGPUWebGPUParentPtr aParent, WGPUSwapChainId aSwapChainId) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
|
|
return parent->UseSharedTextureForSwapChain(aSwapChainId);
|
|
}
|
|
|
|
extern void wgpu_server_disable_shared_texture_for_swap_chain(
|
|
WGPUWebGPUParentPtr aParent, WGPUSwapChainId aSwapChainId) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
|
|
parent->DisableSharedTextureForSwapChain(aSwapChainId);
|
|
}
|
|
|
|
extern bool wgpu_server_ensure_shared_texture_for_swap_chain(
|
|
WGPUWebGPUParentPtr aParent, WGPUSwapChainId aSwapChainId,
|
|
WGPUDeviceId aDeviceId, WGPUTextureId aTextureId, uint32_t aWidth,
|
|
uint32_t aHeight, struct WGPUTextureFormat aFormat,
|
|
WGPUTextureUsages aUsage) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
|
|
return parent->EnsureSharedTextureForSwapChain(
|
|
aSwapChainId, aDeviceId, aTextureId, aWidth, aHeight, aFormat, aUsage);
|
|
}
|
|
|
|
extern void wgpu_server_ensure_shared_texture_for_readback(
|
|
WGPUWebGPUParentPtr aParent, WGPUSwapChainId aSwapChainId,
|
|
WGPUDeviceId aDeviceId, WGPUTextureId aTextureId, uint32_t aWidth,
|
|
uint32_t aHeight, struct WGPUTextureFormat aFormat,
|
|
WGPUTextureUsages aUsage) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
|
|
parent->EnsureSharedTextureForReadBackPresent(
|
|
aSwapChainId, aDeviceId, aTextureId, aWidth, aHeight, aFormat, aUsage);
|
|
}
|
|
|
|
#ifdef XP_WIN
|
|
extern void* wgpu_server_get_shared_texture_handle(WGPUWebGPUParentPtr aParent,
|
|
WGPUTextureId aId) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
|
|
auto texture = parent->GetSharedTexture(aId);
|
|
if (!texture) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return nullptr;
|
|
}
|
|
|
|
auto* textureD3D11 = texture->AsSharedTextureD3D11();
|
|
if (!textureD3D11) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return nullptr;
|
|
}
|
|
void* sharedHandle = textureD3D11->GetSharedTextureHandle();
|
|
if (!sharedHandle) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
gfxCriticalNoteOnce << "Failed to get shared handle";
|
|
return nullptr;
|
|
}
|
|
|
|
return sharedHandle;
|
|
}
|
|
#endif
|
|
|
|
#if defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID)
|
|
extern int32_t wgpu_server_get_dma_buf_fd(WGPUWebGPUParentPtr aParent,
|
|
WGPUTextureId aId) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
|
|
auto texture = parent->GetSharedTexture(aId);
|
|
if (!texture) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return -1;
|
|
}
|
|
|
|
auto* textureDMABuf = texture->AsSharedTextureDMABuf();
|
|
if (!textureDMABuf) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return -1;
|
|
}
|
|
auto fd = textureDMABuf->CloneDmaBufFd();
|
|
// fd should be closed by the caller.
|
|
return fd.release();
|
|
}
|
|
|
|
extern "C" bool wgpu_server_get_linux_dmabuf_modifiers(
|
|
const uint64_t** aModifiers, uint32_t* aModifierCount) {
|
|
if (!aModifiers || !aModifierCount) {
|
|
return false;
|
|
}
|
|
|
|
*aModifiers = nullptr;
|
|
*aModifierCount = 0;
|
|
|
|
// Vulkan B8G8R8A8_UNORM maps to the exported ARGB dmabuf format on
|
|
// little-endian Linux.
|
|
const auto& modifiers = mozilla::gfx::gfxVars::DMABufModifiersARGB();
|
|
if (modifiers.IsEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
*aModifiers = modifiers.Elements();
|
|
*aModifierCount = static_cast<uint32_t>(modifiers.Length());
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
#if defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID)
|
|
extern const WGPUVkImageHandle* wgpu_server_get_vk_image_handle(
|
|
WGPUWebGPUParentPtr aParent, WGPUTextureId aId) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
|
|
auto texture = parent->GetSharedTexture(aId);
|
|
if (!texture) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return nullptr;
|
|
}
|
|
|
|
auto* textureDMABuf = texture->AsSharedTextureDMABuf();
|
|
if (!textureDMABuf) {
|
|
return nullptr;
|
|
}
|
|
return textureDMABuf->GetHandle();
|
|
}
|
|
#endif
|
|
|
|
#if defined(XP_MACOSX)
|
|
extern uint32_t wgpu_server_get_external_io_surface_id(
|
|
WGPUWebGPUParentPtr aParent, WGPUTextureId aId) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
|
|
auto texture = parent->GetSharedTexture(aId);
|
|
if (!texture) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return 0;
|
|
}
|
|
|
|
auto* textureIOSurface = texture->AsSharedTextureMacIOSurface();
|
|
if (!textureIOSurface) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return 0;
|
|
}
|
|
return textureIOSurface->GetIOSurfaceId();
|
|
}
|
|
#endif
|
|
|
|
extern void wgpu_server_remove_shared_texture(WGPUWebGPUParentPtr aParent,
|
|
WGPUTextureId aId) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
parent->RemoveSharedTexture(aId);
|
|
}
|
|
|
|
extern bool wgpu_parent_is_external_texture_enabled() {
|
|
return gfx::gfxVars::AllowWebGPUExternalTexture();
|
|
}
|
|
|
|
extern ffi::WGPUExternalTextureDescriptorFromSource
|
|
wgpu_parent_external_texture_source_get_external_texture_descriptor(
|
|
void* aParent, WGPUExternalTextureSourceId aId,
|
|
ffi::WGPUPredefinedColorSpace aDestColorSpace) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
const auto& source = parent->GetExternalTextureSource(aId);
|
|
return source.GetExternalTextureDescriptor(aDestColorSpace);
|
|
}
|
|
|
|
extern void wgpu_parent_destroy_external_texture_source(
|
|
WGPUWebGPUParentPtr aParent, WGPUExternalTextureSourceId aId) {
|
|
auto* const parent = static_cast<WebGPUParent*>(aParent);
|
|
parent->DestroyExternalTextureSource(aId);
|
|
}
|
|
|
|
extern void wgpu_parent_drop_external_texture_source(
|
|
WGPUWebGPUParentPtr aParent, WGPUExternalTextureSourceId aId) {
|
|
auto* const parent = static_cast<WebGPUParent*>(aParent);
|
|
parent->DropExternalTextureSource(aId);
|
|
}
|
|
|
|
extern void wgpu_server_dealloc_buffer_shmem(WGPUWebGPUParentPtr aParent,
|
|
WGPUBufferId aId) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
parent->DeallocBufferShmem(aId);
|
|
}
|
|
|
|
extern void wgpu_server_pre_device_drop(WGPUWebGPUParentPtr aParent,
|
|
WGPUDeviceId aId) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
parent->PreDeviceDrop(aId);
|
|
}
|
|
|
|
extern void wgpu_server_set_buffer_map_data(
|
|
WGPUWebGPUParentPtr aParent, WGPUDeviceId aDeviceId, WGPUBufferId aBufferId,
|
|
bool aHasMapFlags, uint64_t aMappedOffset, uint64_t aMappedSize,
|
|
uintptr_t aShmemIndex) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
|
|
auto mapping = parent->mTempMappings.at(aShmemIndex);
|
|
|
|
auto data = WebGPUParent::BufferMapData{
|
|
mapping, aHasMapFlags, aMappedOffset, aMappedSize, aDeviceId,
|
|
};
|
|
|
|
parent->mSharedMemoryMap.insert({aBufferId, std::move(data)});
|
|
}
|
|
|
|
extern void wgpu_server_device_push_error_scope(WGPUWebGPUParentPtr aParent,
|
|
WGPUDeviceId aDeviceId,
|
|
uint8_t aFilter) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
parent->DevicePushErrorScope(aDeviceId, (dom::GPUErrorFilter)aFilter);
|
|
}
|
|
|
|
extern void wgpu_server_device_pop_error_scope(WGPUWebGPUParentPtr aParent,
|
|
WGPUDeviceId aDeviceId,
|
|
uint8_t* aOutType,
|
|
nsCString* aOutMessage) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
auto result = parent->DevicePopErrorScope(aDeviceId);
|
|
*aOutType = (uint8_t)result.resultType;
|
|
*aOutMessage = std::move(result.message);
|
|
}
|
|
|
|
extern void wgpu_parent_buffer_unmap(WGPUWebGPUParentPtr aParent,
|
|
WGPUDeviceId aDeviceId,
|
|
WGPUBufferId aBufferId, bool aFlush) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
parent->BufferUnmap(aDeviceId, aBufferId, aFlush);
|
|
}
|
|
|
|
extern void wgpu_parent_queue_submit(
|
|
WGPUWebGPUParentPtr aParent, WGPUDeviceId aDeviceId, WGPUQueueId aQueueId,
|
|
const WGPUCommandBufferId* aCommandBufferIds,
|
|
uintptr_t aCommandBufferIdsLength, const WGPUTextureId* aTextureIds,
|
|
uintptr_t aTextureIdsLength,
|
|
const WGPUExternalTextureSourceId* aExternalTextureSourceIds,
|
|
uintptr_t aExternalTextureSourceIdsLength) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
auto command_buffers = Span(aCommandBufferIds, aCommandBufferIdsLength);
|
|
auto textures = Span(aTextureIds, aTextureIdsLength);
|
|
auto externalTextureSources =
|
|
Span(aExternalTextureSourceIds, aExternalTextureSourceIdsLength);
|
|
parent->QueueSubmit(aQueueId, aDeviceId, command_buffers, textures,
|
|
externalTextureSources);
|
|
}
|
|
|
|
extern void wgpu_parent_create_swap_chain(
|
|
WGPUWebGPUParentPtr aParent, WGPUDeviceId aDeviceId, WGPUQueueId aQueueId,
|
|
int32_t aWidth, int32_t aHeight, WGPUSurfaceFormat aFormat,
|
|
const WGPUBufferId* aBufferIds, uintptr_t aBufferIdsLength,
|
|
WGPURemoteTextureOwnerId aRemoteTextureOwnerId,
|
|
bool aUseSharedTextureInSwapChain) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
auto buffer_ids_span = Span(aBufferIds, aBufferIdsLength);
|
|
auto buffer_ids = nsTArray<RawId>(aBufferIdsLength);
|
|
for (const RawId id : buffer_ids_span) {
|
|
buffer_ids.AppendElement(id);
|
|
}
|
|
auto size = gfx::IntSize(aWidth, aHeight);
|
|
auto format = gfx::SurfaceFormat(aFormat);
|
|
auto desc = layers::RGBDescriptor(size, format, gfx::ColorSpace2::SRGB,
|
|
gfx::TransferFunction::SRGB);
|
|
auto owner = layers::RemoteTextureOwnerId{aRemoteTextureOwnerId};
|
|
parent->DeviceCreateSwapChain(aDeviceId, aQueueId, desc, buffer_ids, owner,
|
|
aUseSharedTextureInSwapChain);
|
|
}
|
|
|
|
extern void wgpu_parent_swap_chain_present(
|
|
WGPUWebGPUParentPtr aParent, WGPUTextureId aTextureId,
|
|
WGPUCommandEncoderId aCommandEncoderId,
|
|
WGPUCommandBufferId aCommandBufferId, WGPURemoteTextureId aRemoteTextureId,
|
|
WGPURemoteTextureOwnerId aRemoteTextureOwnerId) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
auto remote_texture = layers::RemoteTextureId{aRemoteTextureId};
|
|
auto owner = layers::RemoteTextureOwnerId{aRemoteTextureOwnerId};
|
|
parent->SwapChainPresent(aTextureId, aCommandEncoderId, aCommandBufferId,
|
|
remote_texture, owner);
|
|
}
|
|
|
|
extern void wgpu_parent_swap_chain_drop(
|
|
WGPUWebGPUParentPtr aParent, WGPURemoteTextureOwnerId aRemoteTextureOwnerId,
|
|
WGPURemoteTextureTxnType aTxnType, WGPURemoteTextureTxnId aTxnId) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
auto owner = layers::RemoteTextureOwnerId{aRemoteTextureOwnerId};
|
|
parent->SwapChainDrop(owner, aTxnType, aTxnId);
|
|
}
|
|
|
|
#ifdef XP_WIN
|
|
extern void wgpu_parent_get_compositor_device_luid(
|
|
struct WGPUFfiLUID* aOutLuid) {
|
|
auto luid = WebGPUParent::GetCompositorDeviceLuid();
|
|
if (luid.isSome()) {
|
|
*aOutLuid = luid.extract();
|
|
} else {
|
|
aOutLuid = nullptr;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
extern void wgpu_parent_post_request_device(WGPUWebGPUParentPtr aParent,
|
|
WGPUDeviceId aDeviceId) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
parent->PostAdapterRequestDevice(aDeviceId);
|
|
}
|
|
|
|
extern ffi::WGPUBufferMapClosure wgpu_parent_build_buffer_map_closure(
|
|
WGPUWebGPUParentPtr aParent, RawId aDeviceId, RawId aBufferId,
|
|
ffi::WGPUHostMap aMode, uint64_t aOffset, uint64_t aSize) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
|
|
std::unique_ptr<WebGPUParent::MapRequest> request(
|
|
new WebGPUParent::MapRequest{parent, aDeviceId, aBufferId, aMode, aOffset,
|
|
aSize});
|
|
|
|
ffi::WGPUBufferMapClosure closure = {
|
|
&WebGPUParent::MapCallback,
|
|
reinterpret_cast<uint8_t*>(request.release())};
|
|
|
|
return closure;
|
|
}
|
|
|
|
extern ffi::WGPUSubmittedWorkDoneClosure
|
|
wgpu_parent_build_submitted_work_done_closure(WGPUWebGPUParentPtr aParent,
|
|
WGPUQueueId aQueueId) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
|
|
std::unique_ptr<WebGPUParent::OnSubmittedWorkDoneRequest> request(
|
|
new WebGPUParent::OnSubmittedWorkDoneRequest{parent, aQueueId});
|
|
|
|
ffi::WGPUSubmittedWorkDoneClosure closure = {
|
|
&WebGPUParent::OnSubmittedWorkDoneCallback,
|
|
reinterpret_cast<uint8_t*>(request.release())};
|
|
|
|
return closure;
|
|
}
|
|
|
|
extern void wgpu_parent_handle_error(WGPUWebGPUParentPtr aParent,
|
|
WGPUDeviceId aDeviceId,
|
|
WGPUErrorBufferType aTy,
|
|
const nsCString* aMessage) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
|
|
dom::GPUErrorFilter ty;
|
|
switch (aTy) {
|
|
case ffi::WGPUErrorBufferType_Internal:
|
|
ty = dom::GPUErrorFilter::Internal;
|
|
break;
|
|
case ffi::WGPUErrorBufferType_Validation:
|
|
ty = dom::GPUErrorFilter::Validation;
|
|
break;
|
|
case ffi::WGPUErrorBufferType_OutOfMemory:
|
|
ty = dom::GPUErrorFilter::Out_of_memory;
|
|
break;
|
|
default:
|
|
MOZ_CRASH("invalid `ErrorBufferType`");
|
|
}
|
|
|
|
parent->ReportError(aDeviceId, ty, *aMessage);
|
|
}
|
|
|
|
extern void wgpu_parent_send_server_message(WGPUWebGPUParentPtr aParent,
|
|
struct WGPUByteBuf* aMessage) {
|
|
auto* parent = static_cast<WebGPUParent*>(aParent);
|
|
auto* message = FromFFI(aMessage);
|
|
if (!parent->SendServerMessage(std::move(*message))) {
|
|
NS_ERROR("SendServerMessage failed");
|
|
}
|
|
}
|
|
|
|
} // namespace ffi
|
|
|
|
ErrorBuffer::ErrorBuffer() { mMessageUtf8[0] = 0; }
|
|
|
|
ErrorBuffer::~ErrorBuffer() { MOZ_ASSERT(!mAwaitingGetError); }
|
|
|
|
ffi::WGPUErrorBuffer ErrorBuffer::ToFFI() {
|
|
mAwaitingGetError = true;
|
|
ffi::WGPUErrorBuffer errorBuf = {&mType, mMessageUtf8, BUFFER_SIZE,
|
|
&mDeviceId};
|
|
return errorBuf;
|
|
}
|
|
|
|
ffi::WGPUErrorBufferType ErrorBuffer::GetType() { return mType; }
|
|
|
|
Maybe<dom::GPUErrorFilter> ErrorBuffer::ErrorTypeToFilterType(
|
|
ffi::WGPUErrorBufferType aType) {
|
|
switch (aType) {
|
|
case ffi::WGPUErrorBufferType_None:
|
|
case ffi::WGPUErrorBufferType_DeviceLost:
|
|
return {};
|
|
case ffi::WGPUErrorBufferType_Internal:
|
|
return Some(dom::GPUErrorFilter::Internal);
|
|
case ffi::WGPUErrorBufferType_Validation:
|
|
return Some(dom::GPUErrorFilter::Validation);
|
|
case ffi::WGPUErrorBufferType_OutOfMemory:
|
|
return Some(dom::GPUErrorFilter::Out_of_memory);
|
|
case ffi::WGPUErrorBufferType_Sentinel:
|
|
break;
|
|
}
|
|
|
|
MOZ_CRASH("invalid `ErrorBufferType`");
|
|
}
|
|
|
|
Maybe<ErrorBuffer::Error> ErrorBuffer::GetError() {
|
|
mAwaitingGetError = false;
|
|
if (mType == ffi::WGPUErrorBufferType_DeviceLost) {
|
|
// This error is for a lost device, so we return an Error struct
|
|
// with the isDeviceLost bool set to true. It doesn't matter what
|
|
// GPUErrorFilter type we use, so we just use Validation. The error
|
|
// will not be reported.
|
|
return Some(Error{dom::GPUErrorFilter::Validation, true,
|
|
nsCString{mMessageUtf8}, mDeviceId});
|
|
}
|
|
auto filterType = ErrorTypeToFilterType(mType);
|
|
if (!filterType) {
|
|
return {};
|
|
}
|
|
return Some(Error{*filterType, false, nsCString{mMessageUtf8}, mDeviceId});
|
|
}
|
|
|
|
void ErrorBuffer::CoerceValidationToInternal() {
|
|
if (mType == ffi::WGPUErrorBufferType_Validation) {
|
|
mType = ffi::WGPUErrorBufferType_Internal;
|
|
}
|
|
}
|
|
|
|
struct PendingSwapChainDrop {
|
|
layers::RemoteTextureTxnType mTxnType;
|
|
layers::RemoteTextureTxnId mTxnId;
|
|
};
|
|
|
|
class PresentationData {
|
|
NS_INLINE_DECL_REFCOUNTING(PresentationData);
|
|
|
|
public:
|
|
WeakPtr<WebGPUParent> mParent;
|
|
bool mUseSharedTextureInSwapChain;
|
|
const RawId mDeviceId;
|
|
const RawId mQueueId;
|
|
Maybe<RawId> mLastSubmittedTextureId;
|
|
const layers::RGBDescriptor mDesc;
|
|
|
|
uint64_t mSubmissionIndex = 0;
|
|
|
|
std::deque<std::shared_ptr<SharedTexture>> mRecycledSharedTextures;
|
|
|
|
std::unordered_set<layers::RemoteTextureId, layers::RemoteTextureId::HashFn>
|
|
mWaitingReadbackTexturesForPresent;
|
|
Maybe<PendingSwapChainDrop> mPendingSwapChainDrop;
|
|
|
|
const uint32_t mBufferStride;
|
|
const size_t mBufferSize;
|
|
std::vector<RawId> mUnassignedBufferIds;
|
|
std::vector<RawId> mAvailableBufferIds;
|
|
std::vector<RawId> mQueuedBufferIds;
|
|
|
|
bool mReadbackSnapshotCallbackCalled = false;
|
|
|
|
PresentationData(WebGPUParent* aParent, bool aUseSharedTextureInSwapChain,
|
|
RawId aDeviceId, RawId aQueueId,
|
|
const layers::RGBDescriptor& aDesc, uint32_t aBufferStride,
|
|
size_t aBufferSize, const nsTArray<RawId>& aBufferIds)
|
|
: mParent(aParent),
|
|
mUseSharedTextureInSwapChain(aUseSharedTextureInSwapChain),
|
|
mDeviceId(aDeviceId),
|
|
mQueueId(aQueueId),
|
|
mDesc(aDesc),
|
|
mBufferStride(aBufferStride),
|
|
mBufferSize(aBufferSize) {
|
|
MOZ_COUNT_CTOR(PresentationData);
|
|
|
|
for (const RawId id : aBufferIds) {
|
|
mUnassignedBufferIds.push_back(id);
|
|
}
|
|
}
|
|
|
|
private:
|
|
~PresentationData() { MOZ_COUNT_DTOR(PresentationData); }
|
|
};
|
|
|
|
WebGPUParent::WebGPUParent(const dom::ContentParentId& aContentId)
|
|
: mContentId(aContentId), mContext(ffi::wgpu_server_new(this)) {
|
|
mTimer.Start(base::TimeDelta::FromMilliseconds(POLL_TIME_MS), this,
|
|
&WebGPUParent::MaintainDevices);
|
|
}
|
|
|
|
WebGPUParent::~WebGPUParent() = default;
|
|
|
|
void WebGPUParent::MaintainDevices() {
|
|
ffi::wgpu_server_poll_all_devices(mContext.get(), false);
|
|
}
|
|
|
|
void WebGPUParent::LoseDevice(const RawId aDeviceId,
|
|
dom::GPUDeviceLostReason aReason,
|
|
const nsACString& aMessage) {
|
|
if (mActiveDeviceIds.Contains(aDeviceId)) {
|
|
mActiveDeviceIds.Remove(aDeviceId);
|
|
}
|
|
// Check to see if we've already sent a DeviceLost message to aDeviceId.
|
|
if (mLostDeviceIds.Contains(aDeviceId)) {
|
|
return;
|
|
}
|
|
|
|
// If the connection has been dropped, there is nobody to receive
|
|
// the DeviceLost message anyway.
|
|
if (!CanSend()) {
|
|
return;
|
|
}
|
|
|
|
if (!SendDeviceLost(aDeviceId, aReason, aMessage)) {
|
|
NS_ERROR("SendDeviceLost failed");
|
|
return;
|
|
}
|
|
|
|
mLostDeviceIds.Insert(aDeviceId);
|
|
}
|
|
|
|
bool WebGPUParent::ForwardError(ErrorBuffer& aError) {
|
|
if (auto error = aError.GetError()) {
|
|
// If this is a error has isDeviceLost true, then instead of reporting
|
|
// the error, we swallow it and call LoseDevice if we have an
|
|
// aDeviceID. This is to comply with the spec declaration in
|
|
// https://gpuweb.github.io/gpuweb/#lose-the-device
|
|
// "No errors are generated after device loss."
|
|
if (error->isDeviceLost) {
|
|
if (error->deviceId) {
|
|
LoseDevice(error->deviceId, dom::GPUDeviceLostReason::Unknown,
|
|
error->message);
|
|
}
|
|
} else {
|
|
ReportError(error->deviceId, error->type, error->message);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Generate an error on the Device timeline of aDeviceId.
|
|
// aMessage is interpreted as UTF-8.
|
|
void WebGPUParent::ReportError(RawId aDeviceId, const GPUErrorFilter aType,
|
|
const nsCString& aMessage) {
|
|
// find the appropriate error scope
|
|
if (aDeviceId) {
|
|
const auto& itr = mErrorScopeStackByDevice.find(aDeviceId);
|
|
if (itr != mErrorScopeStackByDevice.end()) {
|
|
auto& stack = itr->second;
|
|
for (auto& scope : Reversed(stack)) {
|
|
if (scope.filter != aType) {
|
|
continue;
|
|
}
|
|
if (!scope.firstMessage) {
|
|
scope.firstMessage = Some(aMessage);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
// No error scope found, so fall back to the uncaptured error handler
|
|
if (!SendUncapturedError(aDeviceId, aType, aMessage)) {
|
|
NS_ERROR("SendDeviceUncapturedError failed");
|
|
}
|
|
}
|
|
|
|
struct OnDeviceLostRequest {
|
|
WeakPtr<WebGPUParent> mParent;
|
|
RawId mDeviceId;
|
|
};
|
|
|
|
static void DeviceLostCleanupCallback(uint8_t* aUserData) {
|
|
auto req = std::unique_ptr<OnDeviceLostRequest>(
|
|
reinterpret_cast<OnDeviceLostRequest*>(aUserData));
|
|
}
|
|
|
|
/* static */ void WebGPUParent::DeviceLostCallback(uint8_t* aUserData,
|
|
uint8_t aReason,
|
|
const char* aMessage) {
|
|
auto req = std::unique_ptr<OnDeviceLostRequest>(
|
|
reinterpret_cast<OnDeviceLostRequest*>(aUserData));
|
|
if (!req->mParent) {
|
|
// Parent is dead, never mind.
|
|
return;
|
|
}
|
|
|
|
RawId deviceId = req->mDeviceId;
|
|
|
|
// NOTE: Based on `u8` discriminant values provided for `DeviceLostReason` in
|
|
// `wgpu_bindings`.
|
|
dom::GPUDeviceLostReason reason;
|
|
switch (aReason) {
|
|
case 0:
|
|
reason = dom::GPUDeviceLostReason::Unknown;
|
|
break;
|
|
case 1:
|
|
reason = dom::GPUDeviceLostReason::Destroyed;
|
|
break;
|
|
default:
|
|
MOZ_CRASH_UNSAFE_PRINTF(
|
|
"invalid `aReason` from device lost callback: %hhu", aReason);
|
|
break;
|
|
}
|
|
nsAutoCString message(aMessage);
|
|
req->mParent->LoseDevice(deviceId, reason, message);
|
|
|
|
auto it = req->mParent->mDeviceFenceHandles.find(deviceId);
|
|
if (it != req->mParent->mDeviceFenceHandles.end()) {
|
|
req->mParent->mDeviceFenceHandles.erase(it);
|
|
}
|
|
}
|
|
|
|
void WebGPUParent::PostAdapterRequestDevice(RawId aDeviceId) {
|
|
mErrorScopeStackByDevice.insert({aDeviceId, {}});
|
|
|
|
std::unique_ptr<OnDeviceLostRequest> request(
|
|
new OnDeviceLostRequest{this, aDeviceId});
|
|
ffi::WGPUDeviceLostClosure closure = {
|
|
&DeviceLostCallback, &DeviceLostCleanupCallback,
|
|
reinterpret_cast<uint8_t*>(request.release())};
|
|
ffi::wgpu_server_set_device_lost_callback(mContext.get(), aDeviceId, closure);
|
|
|
|
#if defined(XP_WIN)
|
|
HANDLE handle =
|
|
wgpu_server_get_device_fence_handle(mContext.get(), aDeviceId);
|
|
if (handle) {
|
|
RefPtr<gfx::FileHandleWrapper> fenceHandle =
|
|
new gfx::FileHandleWrapper(UniqueFileHandle(handle));
|
|
mDeviceFenceHandles.emplace(aDeviceId, std::move(fenceHandle));
|
|
}
|
|
#endif
|
|
|
|
MOZ_ASSERT(!mActiveDeviceIds.Contains(aDeviceId));
|
|
mActiveDeviceIds.Insert(aDeviceId);
|
|
}
|
|
|
|
void WebGPUParent::PreDeviceDrop(RawId aDeviceId) {
|
|
if (mActiveDeviceIds.Contains(aDeviceId)) {
|
|
mActiveDeviceIds.Remove(aDeviceId);
|
|
}
|
|
mErrorScopeStackByDevice.erase(aDeviceId);
|
|
mLostDeviceIds.Remove(aDeviceId);
|
|
}
|
|
|
|
WebGPUParent::BufferMapData* WebGPUParent::GetBufferMapData(RawId aBufferId) {
|
|
const auto iter = mSharedMemoryMap.find(aBufferId);
|
|
if (iter == mSharedMemoryMap.end()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return &iter->second;
|
|
}
|
|
|
|
static const char* MapStatusString(ffi::WGPUBufferMapAsyncStatus status) {
|
|
switch (status) {
|
|
case ffi::WGPUBufferMapAsyncStatus_Success:
|
|
return "Success";
|
|
case ffi::WGPUBufferMapAsyncStatus_AlreadyMapped:
|
|
return "Already mapped";
|
|
case ffi::WGPUBufferMapAsyncStatus_MapAlreadyPending:
|
|
return "Map is already pending";
|
|
case ffi::WGPUBufferMapAsyncStatus_ContextLost:
|
|
return "Context lost";
|
|
case ffi::WGPUBufferMapAsyncStatus_Invalid:
|
|
return "Invalid buffer";
|
|
case ffi::WGPUBufferMapAsyncStatus_InvalidRange:
|
|
return "Invalid range";
|
|
case ffi::WGPUBufferMapAsyncStatus_InvalidAlignment:
|
|
return "Invalid alignment";
|
|
case ffi::WGPUBufferMapAsyncStatus_InvalidUsageFlags:
|
|
return "Invalid usage flags";
|
|
case ffi::WGPUBufferMapAsyncStatus_Error:
|
|
return "Map failed";
|
|
case ffi::WGPUBufferMapAsyncStatus_Sentinel: // For -Wswitch
|
|
break;
|
|
}
|
|
|
|
MOZ_CRASH("Bad ffi::WGPUBufferMapAsyncStatus");
|
|
}
|
|
|
|
void WebGPUParent::MapCallback(uint8_t* aUserData,
|
|
ffi::WGPUBufferMapAsyncStatus aStatus) {
|
|
auto req =
|
|
std::unique_ptr<MapRequest>(reinterpret_cast<MapRequest*>(aUserData));
|
|
|
|
if (!req->mParent) {
|
|
return;
|
|
}
|
|
if (!req->mParent->CanSend()) {
|
|
return;
|
|
}
|
|
|
|
ipc::ByteBuf bb;
|
|
|
|
if (aStatus != ffi::WGPUBufferMapAsyncStatus_Success) {
|
|
// A buffer map operation that fails with a DeviceError gets
|
|
// mapped to the ContextLost status. If we have this status, we
|
|
// need to lose the device.
|
|
if (aStatus == ffi::WGPUBufferMapAsyncStatus_ContextLost) {
|
|
req->mParent->LoseDevice(
|
|
req->mDeviceId, dom::GPUDeviceLostReason::Unknown,
|
|
nsPrintfCString("Buffer %" PRIu64 " invalid", req->mBufferId));
|
|
}
|
|
auto error = nsPrintfCString("Mapping WebGPU buffer failed: %s",
|
|
MapStatusString(aStatus));
|
|
|
|
ffi::wgpu_server_pack_buffer_map_error(req->mBufferId, &error, ToFFI(&bb));
|
|
} else {
|
|
auto* mapData = req->mParent->GetBufferMapData(req->mBufferId);
|
|
MOZ_RELEASE_ASSERT(mapData);
|
|
|
|
auto size = req->mSize;
|
|
auto offset = req->mOffset;
|
|
|
|
if (req->mHostMap == ffi::WGPUHostMap_Read && size > 0) {
|
|
ErrorBuffer error;
|
|
const auto src = ffi::wgpu_server_buffer_get_mapped_range(
|
|
req->mParent->GetContext(), mapData->mDeviceId, req->mBufferId,
|
|
offset, size, error.ToFFI());
|
|
|
|
MOZ_RELEASE_ASSERT(!error.GetError());
|
|
|
|
MOZ_RELEASE_ASSERT(mapData->mShmem->Size() >= offset + size);
|
|
if (src.ptr != nullptr && src.length >= size) {
|
|
auto dst = mapData->mShmem->DataAsSpan<uint8_t>().Subspan(offset, size);
|
|
memcpy(dst.data(), src.ptr, size);
|
|
}
|
|
}
|
|
|
|
bool is_writable = req->mHostMap == ffi::WGPUHostMap_Write;
|
|
ffi::wgpu_server_pack_buffer_map_success(req->mBufferId, is_writable,
|
|
offset, size, ToFFI(&bb));
|
|
|
|
mapData->mMappedOffset = offset;
|
|
mapData->mMappedSize = size;
|
|
}
|
|
|
|
if (!req->mParent->SendServerMessage(std::move(bb))) {
|
|
NS_ERROR("SendServerMessage failed");
|
|
}
|
|
}
|
|
|
|
void WebGPUParent::BufferUnmap(RawId aDeviceId, RawId aBufferId, bool aFlush) {
|
|
MOZ_LOG(sLogger, LogLevel::Info,
|
|
("RecvBufferUnmap %" PRIu64 " flush=%d\n", aBufferId, aFlush));
|
|
|
|
auto* mapData = GetBufferMapData(aBufferId);
|
|
|
|
if (mapData && aFlush) {
|
|
uint64_t offset = mapData->mMappedOffset;
|
|
uint64_t size = mapData->mMappedSize;
|
|
|
|
ErrorBuffer getRangeError;
|
|
const auto mapped = ffi::wgpu_server_buffer_get_mapped_range(
|
|
mContext.get(), aDeviceId, aBufferId, offset, size,
|
|
getRangeError.ToFFI());
|
|
// We don't forward the error since `Buffer.unmap()` should not generate any
|
|
// errors. We only call `GetError` so that `ErrorBuffer`'s destructor
|
|
// doesn't assert.
|
|
getRangeError.GetError();
|
|
|
|
if (mapped.ptr != nullptr && mapped.length >= size) {
|
|
auto shmSize = mapData->mShmem->Size();
|
|
MOZ_RELEASE_ASSERT(offset <= shmSize);
|
|
MOZ_RELEASE_ASSERT(size <= shmSize - offset);
|
|
|
|
auto src = mapData->mShmem->DataAsSpan<uint8_t>().Subspan(offset, size);
|
|
memcpy(mapped.ptr, src.data(), size);
|
|
}
|
|
|
|
mapData->mMappedOffset = 0;
|
|
mapData->mMappedSize = 0;
|
|
}
|
|
|
|
ErrorBuffer unmapError;
|
|
ffi::wgpu_server_buffer_unmap(mContext.get(), aDeviceId, aBufferId,
|
|
unmapError.ToFFI());
|
|
// We don't forward the error since `Buffer.unmap()` should not generate any
|
|
// errors. We only call `GetError` so that `ErrorBuffer`'s destructor doesn't
|
|
// assert.
|
|
unmapError.GetError();
|
|
|
|
if (mapData && !mapData->mHasMapFlags) {
|
|
// We get here if the buffer was mapped at creation without map flags.
|
|
// We don't need the shared memory anymore.
|
|
DeallocBufferShmem(aBufferId);
|
|
}
|
|
}
|
|
|
|
void WebGPUParent::DeallocBufferShmem(RawId aBufferId) {
|
|
const auto iter = mSharedMemoryMap.find(aBufferId);
|
|
if (iter != mSharedMemoryMap.end()) {
|
|
mSharedMemoryMap.erase(iter);
|
|
}
|
|
}
|
|
|
|
void WebGPUParent::RemoveSharedTexture(RawId aTextureId) {
|
|
auto it = mSharedTextures.find(aTextureId);
|
|
if (it != mSharedTextures.end()) {
|
|
mSharedTextures.erase(it);
|
|
}
|
|
}
|
|
|
|
const ExternalTextureSourceHost& WebGPUParent::GetExternalTextureSource(
|
|
ffi::WGPUExternalTextureSourceId aId) const {
|
|
return mExternalTextureSources.at(aId);
|
|
}
|
|
|
|
void WebGPUParent::DestroyExternalTextureSource(RawId aSourceId) {
|
|
auto it = mExternalTextureSources.find(aSourceId);
|
|
if (it != mExternalTextureSources.end()) {
|
|
for (const auto viewId : it->second.ViewIds()) {
|
|
ffi::wgpu_server_texture_view_drop(mContext.get(), viewId);
|
|
}
|
|
for (const auto textureId : it->second.TextureIds()) {
|
|
ffi::wgpu_server_texture_destroy(mContext.get(), textureId);
|
|
ffi::wgpu_server_texture_drop(mContext.get(), textureId);
|
|
}
|
|
}
|
|
}
|
|
|
|
void WebGPUParent::DropExternalTextureSource(RawId aSourceId) {
|
|
auto it = mExternalTextureSources.find(aSourceId);
|
|
if (it != mExternalTextureSources.end()) {
|
|
mExternalTextureSources.erase(it);
|
|
}
|
|
}
|
|
|
|
void WebGPUParent::QueueSubmit(RawId aQueueId, RawId aDeviceId,
|
|
Span<const RawId> aCommandBuffers,
|
|
Span<const RawId> aTextureIds,
|
|
Span<const RawId> aExternalTextureSourceIds) {
|
|
for (const auto& textureId : aTextureIds) {
|
|
auto it = mSharedTextures.find(textureId);
|
|
if (it != mSharedTextures.end()) {
|
|
auto& sharedTexture = it->second;
|
|
sharedTexture->onBeforeQueueSubmit(aQueueId);
|
|
}
|
|
}
|
|
|
|
for (const auto& sourceId : aExternalTextureSourceIds) {
|
|
auto it = mExternalTextureSources.find(sourceId);
|
|
if (it != mExternalTextureSources.end()) {
|
|
auto& source = it->second;
|
|
if (!source.OnBeforeQueueSubmit(this, aDeviceId, aQueueId)) {
|
|
// If the above call failed we cannot submit the command buffers, as
|
|
// it would be invalid to read from the external textures.
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
ErrorBuffer error;
|
|
auto index = ffi::wgpu_server_queue_submit(
|
|
mContext.get(), aDeviceId, aQueueId,
|
|
{aCommandBuffers.Elements(), aCommandBuffers.Length()}, error.ToFFI());
|
|
// Check if index is valid. 0 means error.
|
|
if (index != 0) {
|
|
for (const auto& textureId : aTextureIds) {
|
|
auto it = mSharedTextures.find(textureId);
|
|
if (it != mSharedTextures.end()) {
|
|
auto& sharedTexture = it->second;
|
|
|
|
sharedTexture->SetSubmissionIndex(index);
|
|
// Update mLastSubmittedTextureId
|
|
auto ownerId = sharedTexture->GetOwnerId();
|
|
const auto& lookup = mPresentationDataMap.find(ownerId);
|
|
if (lookup != mPresentationDataMap.end()) {
|
|
RefPtr<PresentationData> data = lookup->second.get();
|
|
data->mLastSubmittedTextureId = Some(textureId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ForwardError(error);
|
|
}
|
|
|
|
void WebGPUParent::OnSubmittedWorkDoneCallback(uint8_t* userdata) {
|
|
auto req = std::unique_ptr<OnSubmittedWorkDoneRequest>(
|
|
reinterpret_cast<OnSubmittedWorkDoneRequest*>(userdata));
|
|
if (!req->mParent) {
|
|
return;
|
|
}
|
|
if (!req->mParent->CanSend()) {
|
|
return;
|
|
}
|
|
|
|
ipc::ByteBuf bb;
|
|
ffi::wgpu_server_pack_work_done(ToFFI(&bb), req->mQueueId);
|
|
if (!req->mParent->SendServerMessage(std::move(bb))) {
|
|
NS_ERROR("SendServerMessage failed");
|
|
}
|
|
}
|
|
|
|
// TODO: proper destruction
|
|
|
|
void WebGPUParent::DeviceCreateSwapChain(
|
|
RawId aDeviceId, RawId aQueueId, const RGBDescriptor& aDesc,
|
|
const nsTArray<RawId>& aBufferIds,
|
|
const layers::RemoteTextureOwnerId& aOwnerId,
|
|
bool aUseSharedTextureInSwapChain) {
|
|
switch (aDesc.format()) {
|
|
case gfx::SurfaceFormat::R8G8B8A8:
|
|
case gfx::SurfaceFormat::B8G8R8A8:
|
|
break;
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("Invalid surface format!");
|
|
return;
|
|
}
|
|
|
|
const auto bufferStrideWithMask =
|
|
Device::BufferStrideWithMask(aDesc.size(), aDesc.format());
|
|
if (!bufferStrideWithMask.isValid()) {
|
|
MOZ_ASSERT_UNREACHABLE("Invalid width / buffer stride!");
|
|
return;
|
|
}
|
|
|
|
constexpr uint32_t kBufferAlignmentMask = 0xff;
|
|
const uint32_t bufferStride =
|
|
bufferStrideWithMask.value() & ~kBufferAlignmentMask;
|
|
|
|
const auto rows = CheckedInt<uint32_t>(aDesc.size().height);
|
|
if (!rows.isValid()) {
|
|
MOZ_ASSERT_UNREACHABLE("Invalid height!");
|
|
return;
|
|
}
|
|
|
|
const auto bufferSize = CheckedInt<size_t>(rows.value()) * bufferStride;
|
|
if (!bufferSize.isValid()) {
|
|
MOZ_ASSERT_UNREACHABLE("Buffer size overflowed!");
|
|
return;
|
|
}
|
|
|
|
if (!mRemoteTextureOwner) {
|
|
mRemoteTextureOwner =
|
|
MakeRefPtr<layers::RemoteTextureOwnerClient>(OtherPid());
|
|
}
|
|
mRemoteTextureOwner->RegisterTextureOwner(aOwnerId);
|
|
|
|
auto data = MakeRefPtr<PresentationData>(
|
|
this, aUseSharedTextureInSwapChain, aDeviceId, aQueueId, aDesc,
|
|
bufferStride, bufferSize.value(), aBufferIds);
|
|
if (!mPresentationDataMap.emplace(aOwnerId, data).second) {
|
|
NS_ERROR("External image is already registered as WebGPU canvas!");
|
|
}
|
|
}
|
|
|
|
struct ReadbackPresentRequest {
|
|
ReadbackPresentRequest(
|
|
const ffi::WGPUGlobal* aContext, RefPtr<PresentationData>& aData,
|
|
RefPtr<layers::RemoteTextureOwnerClient>& aRemoteTextureOwner,
|
|
const layers::RemoteTextureId aTextureId,
|
|
const layers::RemoteTextureOwnerId aOwnerId)
|
|
: mContext(aContext),
|
|
mData(aData),
|
|
mRemoteTextureOwner(aRemoteTextureOwner),
|
|
mTextureId(aTextureId),
|
|
mOwnerId(aOwnerId) {}
|
|
|
|
const ffi::WGPUGlobal* mContext;
|
|
RefPtr<PresentationData> mData;
|
|
RefPtr<layers::RemoteTextureOwnerClient> mRemoteTextureOwner;
|
|
const layers::RemoteTextureId mTextureId;
|
|
const layers::RemoteTextureOwnerId mOwnerId;
|
|
};
|
|
|
|
static void ReadbackPresentCallback(uint8_t* userdata,
|
|
ffi::WGPUBufferMapAsyncStatus status) {
|
|
UniquePtr<ReadbackPresentRequest> req(
|
|
reinterpret_cast<ReadbackPresentRequest*>(userdata));
|
|
|
|
const auto onExit = mozilla::MakeScopeExit([&]() {
|
|
auto& waitingTextures = req->mData->mWaitingReadbackTexturesForPresent;
|
|
auto it = waitingTextures.find(req->mTextureId);
|
|
MOZ_ASSERT(it != waitingTextures.end());
|
|
if (it != waitingTextures.end()) {
|
|
waitingTextures.erase(it);
|
|
}
|
|
if (req->mData->mPendingSwapChainDrop.isSome() && waitingTextures.empty()) {
|
|
if (req->mData->mParent) {
|
|
auto& pendingDrop = req->mData->mPendingSwapChainDrop.ref();
|
|
req->mData->mParent->SwapChainDrop(req->mOwnerId, pendingDrop.mTxnType,
|
|
pendingDrop.mTxnId);
|
|
req->mData->mPendingSwapChainDrop = Nothing();
|
|
}
|
|
}
|
|
});
|
|
|
|
if (!req->mRemoteTextureOwner->IsRegistered(req->mOwnerId)) {
|
|
// SwapChain is already Destroyed
|
|
return;
|
|
}
|
|
|
|
RefPtr<PresentationData> data = req->mData;
|
|
// get the buffer ID
|
|
RawId bufferId;
|
|
{
|
|
bufferId = data->mQueuedBufferIds.back();
|
|
data->mQueuedBufferIds.pop_back();
|
|
}
|
|
|
|
// Ensure we'll make the bufferId available for reuse
|
|
data->mAvailableBufferIds.push_back(bufferId);
|
|
|
|
MOZ_LOG(sLogger, LogLevel::Info,
|
|
("ReadbackPresentCallback for buffer %" PRIu64 " status=%d\n",
|
|
bufferId, status));
|
|
// copy the data
|
|
if (status == ffi::WGPUBufferMapAsyncStatus_Success) {
|
|
const auto bufferSize = data->mBufferSize;
|
|
ErrorBuffer getRangeError;
|
|
const auto mapped = ffi::wgpu_server_buffer_get_mapped_range(
|
|
req->mContext, data->mDeviceId, bufferId, 0, bufferSize,
|
|
getRangeError.ToFFI());
|
|
getRangeError.CoerceValidationToInternal();
|
|
if (req->mData->mParent) {
|
|
req->mData->mParent->ForwardError(getRangeError);
|
|
}
|
|
if (auto innerError = getRangeError.GetError()) {
|
|
MOZ_LOG(sLogger, LogLevel::Info,
|
|
("WebGPU present: buffer get_mapped_range for internal "
|
|
"presentation readback failed: %s\n",
|
|
innerError->message.get()));
|
|
return;
|
|
}
|
|
|
|
const auto size = data->mDesc.size();
|
|
|
|
MOZ_RELEASE_ASSERT(mapped.length >= bufferSize);
|
|
auto textureData =
|
|
req->mRemoteTextureOwner->CreateOrRecycleBufferTextureData(
|
|
size, data->mDesc.format(), req->mOwnerId);
|
|
if (!textureData) {
|
|
gfxCriticalNoteOnce << "Failed to allocate BufferTextureData";
|
|
return;
|
|
}
|
|
layers::MappedTextureData mappedData;
|
|
if (textureData && textureData->BorrowMappedData(mappedData)) {
|
|
uint8_t* src = mapped.ptr;
|
|
uint8_t* dst = mappedData.data;
|
|
|
|
const size_t dst_stride = static_cast<size_t>(mappedData.stride);
|
|
// `mappedData.stride` is computed via
|
|
// `ImageDataSerializer::ComputeRGBStride` and returns 0 if it overflows
|
|
MOZ_RELEASE_ASSERT(dst_stride != 0);
|
|
|
|
const size_t src_stride = static_cast<size_t>(data->mBufferStride);
|
|
const size_t bytesPerRow =
|
|
static_cast<size_t>(data->mDesc.size().width) * 4;
|
|
MOZ_RELEASE_ASSERT(src_stride >= bytesPerRow);
|
|
MOZ_RELEASE_ASSERT(dst_stride >= bytesPerRow);
|
|
|
|
// The height is in bounds for both buffers since we just requested a new
|
|
// destination buffer with the same height of the source.
|
|
for (auto row = 0; row < size.height; ++row) {
|
|
memcpy(dst, src, bytesPerRow);
|
|
if (bytesPerRow < dst_stride) {
|
|
memset(dst + bytesPerRow, 0, dst_stride - bytesPerRow);
|
|
}
|
|
src += src_stride;
|
|
dst += dst_stride;
|
|
}
|
|
req->mRemoteTextureOwner->PushTexture(req->mTextureId, req->mOwnerId,
|
|
std::move(textureData));
|
|
} else {
|
|
NS_WARNING("WebGPU present skipped: the swapchain is resized!");
|
|
}
|
|
ErrorBuffer unmapError;
|
|
wgpu_server_buffer_unmap(req->mContext, data->mDeviceId, bufferId,
|
|
unmapError.ToFFI());
|
|
unmapError.CoerceValidationToInternal();
|
|
if (req->mData->mParent) {
|
|
req->mData->mParent->ForwardError(unmapError);
|
|
}
|
|
if (auto innerError = unmapError.GetError()) {
|
|
MOZ_LOG(sLogger, LogLevel::Info,
|
|
("WebGPU present: buffer unmap for internal presentation "
|
|
"readback failed: %s\n",
|
|
innerError->message.get()));
|
|
}
|
|
} else {
|
|
// TODO: better handle errors
|
|
NS_WARNING("WebGPU frame mapping failed!");
|
|
}
|
|
}
|
|
|
|
struct ReadbackSnapshotRequest {
|
|
ReadbackSnapshotRequest(const ffi::WGPUGlobal* aContext,
|
|
RefPtr<PresentationData>& aData,
|
|
ffi::WGPUBufferId aBufferId,
|
|
const ipc::Shmem& aDestShmem, size_t aDestStride)
|
|
: mContext(aContext),
|
|
mData(aData),
|
|
mBufferId(aBufferId),
|
|
mDestShmem(aDestShmem),
|
|
mDestStride(aDestStride) {}
|
|
|
|
const ffi::WGPUGlobal* mContext;
|
|
RefPtr<PresentationData> mData;
|
|
const ffi::WGPUBufferId mBufferId;
|
|
const ipc::Shmem& mDestShmem;
|
|
const size_t mDestStride;
|
|
};
|
|
|
|
static void ReadbackSnapshotCallback(uint8_t* userdata,
|
|
ffi::WGPUBufferMapAsyncStatus status) {
|
|
UniquePtr<ReadbackSnapshotRequest> req(
|
|
reinterpret_cast<ReadbackSnapshotRequest*>(userdata));
|
|
|
|
RefPtr<PresentationData> data = req->mData;
|
|
data->mReadbackSnapshotCallbackCalled = true;
|
|
|
|
// Ensure we'll make the bufferId available for reuse
|
|
data->mAvailableBufferIds.push_back(req->mBufferId);
|
|
|
|
MOZ_LOG(sLogger, LogLevel::Info,
|
|
("ReadbackSnapshotCallback for buffer %" PRIu64 " status=%d\n",
|
|
req->mBufferId, status));
|
|
if (status != ffi::WGPUBufferMapAsyncStatus_Success) {
|
|
return;
|
|
}
|
|
// copy the data
|
|
const auto bufferSize = data->mBufferSize;
|
|
ErrorBuffer getRangeError;
|
|
const auto mapped = ffi::wgpu_server_buffer_get_mapped_range(
|
|
req->mContext, data->mDeviceId, req->mBufferId, 0, bufferSize,
|
|
getRangeError.ToFFI());
|
|
getRangeError.CoerceValidationToInternal();
|
|
if (req->mData->mParent) {
|
|
req->mData->mParent->ForwardError(getRangeError);
|
|
}
|
|
if (auto innerError = getRangeError.GetError()) {
|
|
MOZ_LOG(sLogger, LogLevel::Info,
|
|
("WebGPU present: buffer get_mapped_range for internal "
|
|
"presentation readback failed: %s\n",
|
|
innerError->message.get()));
|
|
return;
|
|
}
|
|
|
|
MOZ_RELEASE_ASSERT(mapped.length >= bufferSize);
|
|
|
|
uint8_t* src = mapped.ptr;
|
|
uint8_t* dst = req->mDestShmem.get<uint8_t>();
|
|
|
|
const size_t src_stride = static_cast<size_t>(data->mBufferStride);
|
|
const size_t bytesPerRow = static_cast<size_t>(data->mDesc.size().width) * 4;
|
|
MOZ_RELEASE_ASSERT(src_stride >= bytesPerRow);
|
|
MOZ_RELEASE_ASSERT(req->mDestStride >= bytesPerRow);
|
|
|
|
// The height is in bounds for both buffers since we previously created a new
|
|
// destination buffer with the same height of the source.
|
|
for (auto row = 0; row < data->mDesc.size().height; ++row) {
|
|
memcpy(dst, src, bytesPerRow);
|
|
if (bytesPerRow < req->mDestStride) {
|
|
memset(dst + bytesPerRow, 0, req->mDestStride - bytesPerRow);
|
|
}
|
|
src += src_stride;
|
|
dst += req->mDestStride;
|
|
}
|
|
|
|
ErrorBuffer unmapError;
|
|
wgpu_server_buffer_unmap(req->mContext, data->mDeviceId, req->mBufferId,
|
|
unmapError.ToFFI());
|
|
unmapError.CoerceValidationToInternal();
|
|
if (req->mData->mParent) {
|
|
req->mData->mParent->ForwardError(unmapError);
|
|
}
|
|
if (auto innerError = unmapError.GetError()) {
|
|
MOZ_LOG(sLogger, LogLevel::Info,
|
|
("WebGPU snapshot: buffer unmap for internal presentation "
|
|
"readback failed: %s\n",
|
|
innerError->message.get()));
|
|
}
|
|
}
|
|
|
|
ipc::IPCResult WebGPUParent::GetFrontBufferSnapshot(
|
|
IProtocol* aProtocol, const layers::RemoteTextureOwnerId& aOwnerId,
|
|
const RawId& aCommandEncoderId, const RawId& aCommandBufferId,
|
|
Maybe<Shmem>& aShmem, gfx::IntSize& aSize, uint32_t& aByteStride) {
|
|
auto setOutParams = [&aShmem, &aSize, &aByteStride](Shmem&& shmem, auto size,
|
|
auto stride) {
|
|
aShmem.emplace(std::move(shmem));
|
|
aSize = size;
|
|
aByteStride = stride;
|
|
};
|
|
|
|
const auto& lookup = mPresentationDataMap.find(aOwnerId);
|
|
if (lookup == mPresentationDataMap.end()) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return IPC_OK();
|
|
}
|
|
|
|
RefPtr<PresentationData> data = lookup->second.get();
|
|
data->mReadbackSnapshotCallbackCalled = false;
|
|
|
|
const Maybe<int32_t> maybeStride =
|
|
layers::ImageDataSerializer::GetRGBStride(data->mDesc);
|
|
if (maybeStride.isNothing()) {
|
|
return IPC_OK();
|
|
}
|
|
const auto stride = maybeStride.value();
|
|
const auto& size = data->mDesc.size();
|
|
|
|
if (size.width > INT16_MAX || size.height > INT16_MAX || stride > INT16_MAX) {
|
|
return IPC_OK();
|
|
}
|
|
|
|
const auto len = CheckedInt<size_t>(size.height) * stride;
|
|
if (!len.isValid()) {
|
|
return IPC_OK();
|
|
}
|
|
|
|
Shmem shmem;
|
|
if (!AllocShmem(len.value(), &shmem)) {
|
|
return IPC_OK();
|
|
}
|
|
|
|
if (data->mLastSubmittedTextureId.isNothing()) {
|
|
// No commands referencing the canvas have ever been submitted, so it's
|
|
// blank.
|
|
memset(shmem.get<uint8_t>(), 0, shmem.Size<uint8_t>());
|
|
setOutParams(std::move(shmem), size, stride);
|
|
return IPC_OK();
|
|
}
|
|
|
|
auto it = mSharedTextures.find(data->mLastSubmittedTextureId.ref());
|
|
// Shared texture is already invalid and posted to RemoteTextureMap
|
|
if (it == mSharedTextures.end()) {
|
|
if (!mRemoteTextureOwner || !mRemoteTextureOwner->IsRegistered(aOwnerId)) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return IPC_OK();
|
|
}
|
|
if (!data->mUseSharedTextureInSwapChain) {
|
|
ffi::wgpu_server_device_poll(mContext.get(), data->mDeviceId, true);
|
|
}
|
|
mRemoteTextureOwner->GetLatestBufferSnapshot(aOwnerId, shmem, size, stride);
|
|
setOutParams(std::move(shmem), size, stride);
|
|
return IPC_OK();
|
|
}
|
|
|
|
// Readback synchronously
|
|
|
|
RawId bufferId = 0;
|
|
const auto bufferSize = data->mBufferSize;
|
|
|
|
// The swap chain owns exactly MAX_SWAPCHAIN_BUFFER_COUNT staging-buffer IDs.
|
|
// Each ID must always be in exactly one of mUnassignedBufferIds,
|
|
// mAvailableBufferIds, mQueuedBufferIds, or a pending
|
|
// ReadbackSnapshotCallback. In error cases, be sure to release the buffer ID
|
|
// back to the available pool.
|
|
const auto bufferIdGuard = mozilla::MakeScopeExit([&] {
|
|
if (bufferId) {
|
|
data->mAvailableBufferIds.push_back(bufferId);
|
|
}
|
|
});
|
|
|
|
// step 1: find an available staging buffer, or create one
|
|
{
|
|
if (!data->mAvailableBufferIds.empty()) {
|
|
bufferId = data->mAvailableBufferIds.back();
|
|
data->mAvailableBufferIds.pop_back();
|
|
} else if (!data->mUnassignedBufferIds.empty()) {
|
|
bufferId = data->mUnassignedBufferIds.back();
|
|
data->mUnassignedBufferIds.pop_back();
|
|
|
|
ffi::WGPUBufferUsages usage =
|
|
WGPUBufferUsages_COPY_DST | WGPUBufferUsages_MAP_READ;
|
|
|
|
ErrorBuffer error;
|
|
ffi::wgpu_server_device_create_buffer(mContext.get(), data->mDeviceId,
|
|
bufferId, nullptr, bufferSize,
|
|
usage, false, error.ToFFI());
|
|
if (ForwardError(error)) {
|
|
ffi::wgpu_server_buffer_drop(mContext.get(), bufferId);
|
|
data->mUnassignedBufferIds.push_back(bufferId);
|
|
bufferId = 0;
|
|
return IPC_OK();
|
|
}
|
|
} else {
|
|
bufferId = 0;
|
|
}
|
|
}
|
|
|
|
MOZ_LOG(sLogger, LogLevel::Info,
|
|
("GetFrontBufferSnapshot with buffer %" PRIu64 "\n", bufferId));
|
|
if (!bufferId) {
|
|
// TODO: add a warning - no buffer are available!
|
|
return IPC_OK();
|
|
}
|
|
|
|
// step 3: submit a copy command for the frame
|
|
ffi::WGPUCommandEncoderDescriptor encoderDesc = {};
|
|
{
|
|
ErrorBuffer error;
|
|
ffi::wgpu_server_device_create_encoder(mContext.get(), data->mDeviceId,
|
|
&encoderDesc, aCommandEncoderId,
|
|
error.ToFFI());
|
|
if (ForwardError(error)) {
|
|
return IPC_OK();
|
|
}
|
|
}
|
|
|
|
const ffi::WGPUTexelCopyTextureInfo texView = {
|
|
data->mLastSubmittedTextureId.ref(),
|
|
};
|
|
const ffi::WGPUTexelCopyBufferLayout bufLayout = {
|
|
0,
|
|
&data->mBufferStride,
|
|
nullptr,
|
|
};
|
|
const ffi::WGPUExtent3d extent = {
|
|
static_cast<uint32_t>(size.width),
|
|
static_cast<uint32_t>(size.height),
|
|
1,
|
|
};
|
|
|
|
{
|
|
ErrorBuffer error;
|
|
ffi::wgpu_server_encoder_copy_texture_to_buffer(
|
|
mContext.get(), data->mDeviceId, aCommandEncoderId, &texView, bufferId,
|
|
&bufLayout, &extent, error.ToFFI());
|
|
if (ForwardError(error)) {
|
|
ffi::wgpu_server_command_encoder_drop(mContext.get(), aCommandEncoderId);
|
|
return IPC_OK();
|
|
}
|
|
}
|
|
ffi::WGPUCommandBufferDescriptor commandDesc = {};
|
|
{
|
|
ErrorBuffer error;
|
|
ffi::wgpu_server_encoder_finish(mContext.get(), data->mDeviceId,
|
|
aCommandEncoderId, aCommandBufferId,
|
|
&commandDesc, error.ToFFI());
|
|
if (ForwardError(error)) {
|
|
ffi::wgpu_server_command_encoder_drop(mContext.get(), aCommandEncoderId);
|
|
ffi::wgpu_server_command_buffer_drop(mContext.get(), aCommandBufferId);
|
|
return IPC_OK();
|
|
}
|
|
}
|
|
|
|
{
|
|
ErrorBuffer error;
|
|
ffi::wgpu_server_queue_submit(mContext.get(), data->mDeviceId,
|
|
data->mQueueId, {&aCommandBufferId, 1},
|
|
error.ToFFI());
|
|
ffi::wgpu_server_command_encoder_drop(mContext.get(), aCommandEncoderId);
|
|
ffi::wgpu_server_command_buffer_drop(mContext.get(), aCommandBufferId);
|
|
if (ForwardError(error)) {
|
|
return IPC_OK();
|
|
}
|
|
}
|
|
|
|
auto snapshotRequest = MakeUnique<ReadbackSnapshotRequest>(
|
|
mContext.get(), data, bufferId, shmem, stride);
|
|
|
|
ffi::WGPUBufferMapAsyncStatus status = ffi::wgpu_server_buffer_map_blocking(
|
|
mContext.get(), data->mDeviceId, bufferId, 0, bufferSize,
|
|
ffi::WGPUHostMap_Read);
|
|
ReadbackSnapshotCallback(
|
|
reinterpret_cast<uint8_t*>(snapshotRequest.release()), status);
|
|
// bufferId was transferred to ReadbackSnapshotCallback.
|
|
bufferId = 0;
|
|
|
|
// Check if ReadbackSnapshotCallback is called.
|
|
MOZ_RELEASE_ASSERT(data->mReadbackSnapshotCallbackCalled == true);
|
|
|
|
setOutParams(std::move(shmem), size, stride);
|
|
return IPC_OK();
|
|
}
|
|
|
|
void WebGPUParent::PostSharedTexture(
|
|
const std::shared_ptr<SharedTexture>&& aSharedTexture,
|
|
const layers::RemoteTextureId aRemoteTextureId,
|
|
const layers::RemoteTextureOwnerId aOwnerId) {
|
|
const auto& lookup = mPresentationDataMap.find(aOwnerId);
|
|
if (lookup == mPresentationDataMap.end() || !mRemoteTextureOwner ||
|
|
!mRemoteTextureOwner->IsRegistered(aOwnerId)) {
|
|
NS_WARNING("WebGPU presenting on a destroyed swap chain!");
|
|
return;
|
|
}
|
|
|
|
const auto surfaceFormat = gfx::SurfaceFormat::B8G8R8A8;
|
|
const auto size = aSharedTexture->GetSize();
|
|
|
|
RefPtr<PresentationData> data = lookup->second.get();
|
|
|
|
Maybe<layers::SurfaceDescriptor> desc = aSharedTexture->ToSurfaceDescriptor();
|
|
if (!desc) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return;
|
|
}
|
|
|
|
mRemoteTextureOwner->PushTexture(aRemoteTextureId, aOwnerId, aSharedTexture,
|
|
size, surfaceFormat, *desc);
|
|
|
|
auto recycledTexture = mRemoteTextureOwner->GetRecycledSharedTexture(
|
|
size, surfaceFormat, desc->type(), aOwnerId);
|
|
if (recycledTexture) {
|
|
recycledTexture->CleanForRecycling();
|
|
data->mRecycledSharedTextures.push_back(recycledTexture);
|
|
}
|
|
}
|
|
|
|
RefPtr<gfx::FileHandleWrapper> WebGPUParent::GetDeviceFenceHandle(
|
|
const RawId aDeviceId) {
|
|
auto it = mDeviceFenceHandles.find(aDeviceId);
|
|
if (it == mDeviceFenceHandles.end()) {
|
|
return nullptr;
|
|
}
|
|
return it->second;
|
|
}
|
|
|
|
void WebGPUParent::SwapChainPresent(
|
|
RawId aTextureId, RawId aCommandEncoderId, RawId aCommandBufferId,
|
|
const layers::RemoteTextureId& aRemoteTextureId,
|
|
const layers::RemoteTextureOwnerId& aOwnerId) {
|
|
// step 0: get the data associated with the swapchain
|
|
const auto& lookup = mPresentationDataMap.find(aOwnerId);
|
|
if (lookup == mPresentationDataMap.end() || !mRemoteTextureOwner ||
|
|
!mRemoteTextureOwner->IsRegistered(aOwnerId)) {
|
|
NS_WARNING("WebGPU presenting on a destroyed swap chain!");
|
|
return;
|
|
}
|
|
|
|
RefPtr<PresentationData> data = lookup->second.get();
|
|
|
|
if (data->mUseSharedTextureInSwapChain) {
|
|
auto it = mSharedTextures.find(aTextureId);
|
|
if (it == mSharedTextures.end()) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return;
|
|
}
|
|
std::shared_ptr<SharedTexture> sharedTexture = it->second;
|
|
mSharedTextures.erase(it);
|
|
|
|
if (!sharedTexture->IsSubmitted()) {
|
|
mRemoteTextureOwner->PushDummyTexture(aRemoteTextureId, aOwnerId);
|
|
gfxCriticalNoteOnce << "Dummy texture is submitted";
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(sharedTexture->GetOwnerId() == aOwnerId);
|
|
|
|
PostSharedTexture(std::move(sharedTexture), aRemoteTextureId, aOwnerId);
|
|
return;
|
|
}
|
|
|
|
RawId bufferId = 0;
|
|
const auto& size = data->mDesc.size();
|
|
const auto bufferSize = data->mBufferSize;
|
|
|
|
// step 1: find an available staging buffer, or create one
|
|
{
|
|
if (!data->mAvailableBufferIds.empty()) {
|
|
bufferId = data->mAvailableBufferIds.back();
|
|
data->mAvailableBufferIds.pop_back();
|
|
} else if (!data->mUnassignedBufferIds.empty()) {
|
|
bufferId = data->mUnassignedBufferIds.back();
|
|
data->mUnassignedBufferIds.pop_back();
|
|
|
|
ffi::WGPUBufferUsages usage =
|
|
WGPUBufferUsages_COPY_DST | WGPUBufferUsages_MAP_READ;
|
|
|
|
ErrorBuffer error;
|
|
ffi::wgpu_server_device_create_buffer(mContext.get(), data->mDeviceId,
|
|
bufferId, nullptr, bufferSize,
|
|
usage, false, error.ToFFI());
|
|
if (ForwardError(error)) {
|
|
ffi::wgpu_server_buffer_drop(mContext.get(), bufferId);
|
|
data->mUnassignedBufferIds.push_back(bufferId);
|
|
return;
|
|
}
|
|
} else {
|
|
bufferId = 0;
|
|
}
|
|
|
|
if (bufferId) {
|
|
data->mQueuedBufferIds.insert(data->mQueuedBufferIds.begin(), bufferId);
|
|
}
|
|
}
|
|
|
|
MOZ_LOG(sLogger, LogLevel::Info,
|
|
("RecvSwapChainPresent with buffer %" PRIu64 "\n", bufferId));
|
|
if (!bufferId) {
|
|
// TODO: add a warning - no buffer are available!
|
|
return;
|
|
}
|
|
|
|
// step 3: submit a copy command for the frame
|
|
ffi::WGPUCommandEncoderDescriptor encoderDesc = {};
|
|
{
|
|
ErrorBuffer error;
|
|
ffi::wgpu_server_device_create_encoder(mContext.get(), data->mDeviceId,
|
|
&encoderDesc, aCommandEncoderId,
|
|
error.ToFFI());
|
|
if (ForwardError(error)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const ffi::WGPUTexelCopyTextureInfo texView = {
|
|
aTextureId,
|
|
};
|
|
const ffi::WGPUTexelCopyBufferLayout bufLayout = {
|
|
0,
|
|
&data->mBufferStride,
|
|
nullptr,
|
|
};
|
|
const ffi::WGPUExtent3d extent = {
|
|
static_cast<uint32_t>(size.width),
|
|
static_cast<uint32_t>(size.height),
|
|
1,
|
|
};
|
|
|
|
{
|
|
ErrorBuffer error;
|
|
ffi::wgpu_server_encoder_copy_texture_to_buffer(
|
|
mContext.get(), data->mDeviceId, aCommandEncoderId, &texView, bufferId,
|
|
&bufLayout, &extent, error.ToFFI());
|
|
if (ForwardError(error)) {
|
|
return;
|
|
}
|
|
}
|
|
ffi::WGPUCommandBufferDescriptor commandDesc = {};
|
|
{
|
|
ErrorBuffer error;
|
|
ffi::wgpu_server_encoder_finish(mContext.get(), data->mDeviceId,
|
|
aCommandEncoderId, aCommandBufferId,
|
|
&commandDesc, error.ToFFI());
|
|
if (ForwardError(error)) {
|
|
ffi::wgpu_server_command_encoder_drop(mContext.get(), aCommandEncoderId);
|
|
ffi::wgpu_server_command_buffer_drop(mContext.get(), aCommandBufferId);
|
|
return;
|
|
}
|
|
}
|
|
|
|
{
|
|
ErrorBuffer error;
|
|
ffi::wgpu_server_queue_submit(mContext.get(), data->mDeviceId,
|
|
data->mQueueId, {&aCommandBufferId, 1},
|
|
error.ToFFI());
|
|
ffi::wgpu_server_command_encoder_drop(mContext.get(), aCommandEncoderId);
|
|
ffi::wgpu_server_command_buffer_drop(mContext.get(), aCommandBufferId);
|
|
if (ForwardError(error)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
auto& waitingTextures = data->mWaitingReadbackTexturesForPresent;
|
|
auto it = waitingTextures.find(aRemoteTextureId);
|
|
MOZ_ASSERT(it == waitingTextures.end());
|
|
if (it == waitingTextures.end()) {
|
|
waitingTextures.emplace(aRemoteTextureId);
|
|
}
|
|
|
|
// step 4: request the pixels to be copied into the shared texture
|
|
// TODO: this isn't strictly necessary. When WR wants to Lock() the external
|
|
// texture,
|
|
// we can just give it the contents of the last mapped buffer instead of the
|
|
// copy.
|
|
auto presentRequest = MakeUnique<ReadbackPresentRequest>(
|
|
mContext.get(), data, mRemoteTextureOwner, aRemoteTextureId, aOwnerId);
|
|
|
|
ffi::WGPUBufferMapClosure closure = {
|
|
&ReadbackPresentCallback,
|
|
reinterpret_cast<uint8_t*>(presentRequest.release())};
|
|
|
|
ErrorBuffer error;
|
|
ffi::wgpu_server_buffer_map(mContext.get(), data->mDeviceId, bufferId, 0,
|
|
bufferSize, ffi::WGPUHostMap_Read, closure,
|
|
error.ToFFI());
|
|
if (ForwardError(error)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
void WebGPUParent::SwapChainDrop(const layers::RemoteTextureOwnerId& aOwnerId,
|
|
layers::RemoteTextureTxnType aTxnType,
|
|
layers::RemoteTextureTxnId aTxnId) {
|
|
const auto& lookup = mPresentationDataMap.find(aOwnerId);
|
|
MOZ_ASSERT(lookup != mPresentationDataMap.end());
|
|
if (lookup == mPresentationDataMap.end()) {
|
|
NS_WARNING("WebGPU presenting on a destroyed swap chain!");
|
|
return;
|
|
}
|
|
|
|
RefPtr<PresentationData> data = lookup->second.get();
|
|
|
|
auto waitingCount = data->mWaitingReadbackTexturesForPresent.size();
|
|
if (waitingCount > 0) {
|
|
// Defer SwapChainDrop until readback complete
|
|
data->mPendingSwapChainDrop = Some(PendingSwapChainDrop{aTxnType, aTxnId});
|
|
return;
|
|
}
|
|
|
|
if (mRemoteTextureOwner) {
|
|
if (aTxnType && aTxnId) {
|
|
mRemoteTextureOwner->WaitForTxn(aOwnerId, aTxnType, aTxnId);
|
|
}
|
|
mRemoteTextureOwner->UnregisterTextureOwner(aOwnerId);
|
|
}
|
|
|
|
mPresentationDataMap.erase(lookup);
|
|
|
|
for (const auto bid : data->mAvailableBufferIds) {
|
|
ffi::wgpu_server_buffer_drop(mContext.get(), bid);
|
|
data->mUnassignedBufferIds.push_back(bid);
|
|
}
|
|
for (const auto bid : data->mQueuedBufferIds) {
|
|
ffi::wgpu_server_buffer_drop(mContext.get(), bid);
|
|
data->mUnassignedBufferIds.push_back(bid);
|
|
}
|
|
|
|
ipc::ByteBuf bb;
|
|
ffi::wgpu_server_pack_free_swap_chain_buffer_ids(
|
|
ToFFI(&bb),
|
|
{data->mUnassignedBufferIds.data(), data->mUnassignedBufferIds.size()});
|
|
if (!SendServerMessage(std::move(bb))) {
|
|
NS_ERROR("SendServerMessage failed");
|
|
}
|
|
}
|
|
|
|
void WebGPUParent::ActorDestroy(ActorDestroyReason aWhy) {
|
|
mTimer.Stop();
|
|
mPresentationDataMap.clear();
|
|
if (mRemoteTextureOwner) {
|
|
mRemoteTextureOwner->UnregisterAllTextureOwners();
|
|
mRemoteTextureOwner = nullptr;
|
|
}
|
|
mActiveDeviceIds.Clear();
|
|
mContext = nullptr;
|
|
}
|
|
|
|
ipc::IPCResult WebGPUParent::RecvMessages(
|
|
uint32_t nrOfMessages, ipc::ByteBuf&& aSerializedMessages,
|
|
nsTArray<ipc::ByteBuf>&& aDataBuffers,
|
|
nsTArray<MutableSharedMemoryHandle>&& aShmems) {
|
|
MOZ_ASSERT(mTempMappings.empty());
|
|
|
|
mTempMappings.reserve(aShmems.Length());
|
|
|
|
nsTArray<ffi::WGPUFfiSlice_u8> shmem_mappings(aShmems.Length());
|
|
|
|
for (const auto& shmem : aShmems) {
|
|
auto mapping = shmem.Map();
|
|
|
|
auto* ptr = mapping.DataAs<uint8_t>();
|
|
auto len = mapping.Size();
|
|
ffi::WGPUFfiSlice_u8 byte_slice{ptr, len};
|
|
shmem_mappings.AppendElement(std::move(byte_slice));
|
|
|
|
// `aShmem` may be an invalid handle, however this will simply result in an
|
|
// invalid mapping with 0 size, which we use safely.
|
|
mTempMappings.push_back(
|
|
std::make_shared<ipc::SharedMemoryMapping>(std::move(mapping)));
|
|
}
|
|
|
|
ffi::WGPUFfiSlice_ByteBuf data_buffers{ToFFI(aDataBuffers.Elements()),
|
|
aDataBuffers.Length()};
|
|
|
|
ffi::WGPUFfiSlice_FfiSlice_u8 shmem_mapping_slices{shmem_mappings.Elements(),
|
|
shmem_mappings.Length()};
|
|
|
|
ffi::wgpu_server_messages(mContext.get(), nrOfMessages,
|
|
ToFFI(&aSerializedMessages), data_buffers,
|
|
shmem_mapping_slices);
|
|
|
|
mTempMappings.clear();
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
ipc::IPCResult WebGPUParent::RecvCreateExternalTextureSource(
|
|
RawId aDeviceId, RawId aQueueId, RawId aExternalTextureSourceId,
|
|
const ExternalTextureSourceDescriptor& aDesc) {
|
|
MOZ_RELEASE_ASSERT(mExternalTextureSources.find(aExternalTextureSourceId) ==
|
|
mExternalTextureSources.end());
|
|
mExternalTextureSources.emplace(
|
|
aExternalTextureSourceId,
|
|
ExternalTextureSourceHost::Create(this, aDeviceId, aQueueId, aDesc));
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
void WebGPUParent::DevicePushErrorScope(RawId aDeviceId,
|
|
const dom::GPUErrorFilter aFilter) {
|
|
const auto& itr = mErrorScopeStackByDevice.find(aDeviceId);
|
|
if (itr == mErrorScopeStackByDevice.end()) {
|
|
// Content can cause this simply by destroying a device and then
|
|
// calling `pushErrorScope`.
|
|
return;
|
|
}
|
|
auto& stack = itr->second;
|
|
|
|
// Let's prevent `while (true) { pushErrorScope(); }`.
|
|
constexpr size_t MAX_ERROR_SCOPE_STACK_SIZE = 1'000'000;
|
|
if (stack.size() >= MAX_ERROR_SCOPE_STACK_SIZE) {
|
|
nsPrintfCString m("pushErrorScope: Hit MAX_ERROR_SCOPE_STACK_SIZE of %zu",
|
|
MAX_ERROR_SCOPE_STACK_SIZE);
|
|
ReportError(aDeviceId, dom::GPUErrorFilter::Out_of_memory, m);
|
|
return;
|
|
}
|
|
|
|
const auto newScope = ErrorScope{aFilter};
|
|
stack.push_back(newScope);
|
|
}
|
|
|
|
PopErrorScopeResult WebGPUParent::DevicePopErrorScope(RawId aDeviceId) {
|
|
const auto popResult = [&]() {
|
|
const auto& itr = mErrorScopeStackByDevice.find(aDeviceId);
|
|
if (itr == mErrorScopeStackByDevice.end()) {
|
|
// Content can cause this simply by destroying a device and then
|
|
// calling `popErrorScope`.
|
|
return PopErrorScopeResult{PopErrorScopeResultType::DeviceLost};
|
|
}
|
|
|
|
auto& stack = itr->second;
|
|
if (!stack.size()) {
|
|
// Content can cause this simply by calling `popErrorScope` when
|
|
// there is no error scope pushed.
|
|
return PopErrorScopeResult{PopErrorScopeResultType::ThrowOperationError,
|
|
"popErrorScope on empty stack"_ns};
|
|
}
|
|
|
|
const auto& scope = stack.back();
|
|
const auto popLater = MakeScopeExit([&]() { stack.pop_back(); });
|
|
|
|
auto ret = PopErrorScopeResult{PopErrorScopeResultType::NoError};
|
|
if (scope.firstMessage) {
|
|
ret.message = *scope.firstMessage;
|
|
switch (scope.filter) {
|
|
case dom::GPUErrorFilter::Validation:
|
|
ret.resultType = PopErrorScopeResultType::ValidationError;
|
|
break;
|
|
case dom::GPUErrorFilter::Out_of_memory:
|
|
ret.resultType = PopErrorScopeResultType::OutOfMemory;
|
|
break;
|
|
case dom::GPUErrorFilter::Internal:
|
|
ret.resultType = PopErrorScopeResultType::InternalError;
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}();
|
|
return popResult;
|
|
}
|
|
|
|
bool WebGPUParent::UseSharedTextureForSwapChain(
|
|
ffi::WGPUSwapChainId aSwapChainId) {
|
|
auto ownerId = layers::RemoteTextureOwnerId{aSwapChainId._0};
|
|
const auto& lookup = mPresentationDataMap.find(ownerId);
|
|
if (lookup == mPresentationDataMap.end()) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return false;
|
|
}
|
|
|
|
RefPtr<PresentationData> data = lookup->second.get();
|
|
|
|
return data->mUseSharedTextureInSwapChain;
|
|
}
|
|
|
|
void WebGPUParent::DisableSharedTextureForSwapChain(
|
|
ffi::WGPUSwapChainId aSwapChainId) {
|
|
auto ownerId = layers::RemoteTextureOwnerId{aSwapChainId._0};
|
|
const auto& lookup = mPresentationDataMap.find(ownerId);
|
|
if (lookup == mPresentationDataMap.end()) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return;
|
|
}
|
|
|
|
RefPtr<PresentationData> data = lookup->second.get();
|
|
|
|
if (data->mUseSharedTextureInSwapChain) {
|
|
gfxCriticalNote << "Disable SharedTexture for SwapChain: "
|
|
<< aSwapChainId._0;
|
|
}
|
|
|
|
data->mUseSharedTextureInSwapChain = false;
|
|
}
|
|
|
|
static bool SwapChainFormatMatches(
|
|
gfx::SurfaceFormat aSurfaceFormat,
|
|
const ffi::WGPUTextureFormat& aTextureFormat) {
|
|
switch (aSurfaceFormat) {
|
|
case gfx::SurfaceFormat::B8G8R8A8:
|
|
return aTextureFormat.tag == ffi::WGPUTextureFormat_Bgra8Unorm ||
|
|
aTextureFormat.tag == ffi::WGPUTextureFormat_Bgra8UnormSrgb;
|
|
case gfx::SurfaceFormat::R8G8B8A8:
|
|
return aTextureFormat.tag == ffi::WGPUTextureFormat_Rgba8Unorm ||
|
|
aTextureFormat.tag == ffi::WGPUTextureFormat_Rgba8UnormSrgb;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool WebGPUParent::EnsureSharedTextureForSwapChain(
|
|
ffi::WGPUSwapChainId aSwapChainId, ffi::WGPUDeviceId aDeviceId,
|
|
ffi::WGPUTextureId aTextureId, uint32_t aWidth, uint32_t aHeight,
|
|
struct ffi::WGPUTextureFormat aFormat, ffi::WGPUTextureUsages aUsage) {
|
|
auto ownerId = layers::RemoteTextureOwnerId{aSwapChainId._0};
|
|
const auto& lookup = mPresentationDataMap.find(ownerId);
|
|
if (lookup == mPresentationDataMap.end()) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return false;
|
|
}
|
|
|
|
RefPtr<PresentationData> data = lookup->second.get();
|
|
if (!data->mUseSharedTextureInSwapChain) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return false;
|
|
}
|
|
|
|
MOZ_RELEASE_ASSERT(aWidth == static_cast<uint32_t>(data->mDesc.size().width));
|
|
MOZ_RELEASE_ASSERT(aHeight ==
|
|
static_cast<uint32_t>(data->mDesc.size().height));
|
|
MOZ_RELEASE_ASSERT(SwapChainFormatMatches(data->mDesc.format(), aFormat));
|
|
|
|
// Recycled SharedTexture if it exists.
|
|
if (!data->mRecycledSharedTextures.empty()) {
|
|
std::shared_ptr<SharedTexture> texture =
|
|
data->mRecycledSharedTextures.front();
|
|
// Check if the texture is recyclable.
|
|
if (texture->mWidth == aWidth && texture->mHeight == aHeight &&
|
|
texture->mFormat.tag == aFormat.tag && texture->mUsage == aUsage) {
|
|
texture->SetOwnerId(ownerId);
|
|
data->mRecycledSharedTextures.pop_front();
|
|
mSharedTextures.emplace(aTextureId, texture);
|
|
return true;
|
|
}
|
|
data->mRecycledSharedTextures.clear();
|
|
}
|
|
|
|
auto sharedTexture = CreateSharedTexture(ownerId, aDeviceId, aTextureId,
|
|
aWidth, aHeight, aFormat, aUsage);
|
|
return static_cast<bool>(sharedTexture);
|
|
}
|
|
|
|
void WebGPUParent::EnsureSharedTextureForReadBackPresent(
|
|
ffi::WGPUSwapChainId aSwapChainId, ffi::WGPUDeviceId aDeviceId,
|
|
ffi::WGPUTextureId aTextureId, uint32_t aWidth, uint32_t aHeight,
|
|
struct ffi::WGPUTextureFormat aFormat, ffi::WGPUTextureUsages aUsage) {
|
|
auto ownerId = layers::RemoteTextureOwnerId{aSwapChainId._0};
|
|
const auto& lookup = mPresentationDataMap.find(ownerId);
|
|
if (lookup == mPresentationDataMap.end()) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return;
|
|
}
|
|
|
|
RefPtr<PresentationData> data = lookup->second.get();
|
|
if (data->mUseSharedTextureInSwapChain) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return;
|
|
}
|
|
|
|
MOZ_RELEASE_ASSERT(aWidth == static_cast<uint32_t>(data->mDesc.size().width));
|
|
MOZ_RELEASE_ASSERT(aHeight ==
|
|
static_cast<uint32_t>(data->mDesc.size().height));
|
|
MOZ_RELEASE_ASSERT(SwapChainFormatMatches(data->mDesc.format(), aFormat));
|
|
|
|
UniquePtr<SharedTexture> texture =
|
|
SharedTextureReadBackPresent::Create(aWidth, aHeight, aFormat, aUsage);
|
|
if (!texture) {
|
|
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
|
|
return;
|
|
}
|
|
|
|
texture->SetOwnerId(ownerId);
|
|
std::shared_ptr<SharedTexture> shared(texture.release());
|
|
mSharedTextures[aTextureId] = shared;
|
|
}
|
|
|
|
std::shared_ptr<SharedTexture> WebGPUParent::CreateSharedTexture(
|
|
const layers::RemoteTextureOwnerId& aOwnerId, ffi::WGPUDeviceId aDeviceId,
|
|
ffi::WGPUTextureId aTextureId, uint32_t aWidth, uint32_t aHeight,
|
|
const struct ffi::WGPUTextureFormat aFormat,
|
|
ffi::WGPUTextureUsages aUsage) {
|
|
MOZ_RELEASE_ASSERT(mSharedTextures.find(aTextureId) == mSharedTextures.end());
|
|
|
|
UniquePtr<SharedTexture> texture =
|
|
SharedTexture::Create(this, aDeviceId, aWidth, aHeight, aFormat, aUsage);
|
|
if (!texture) {
|
|
return nullptr;
|
|
}
|
|
|
|
texture->SetOwnerId(aOwnerId);
|
|
std::shared_ptr<SharedTexture> shared(texture.release());
|
|
mSharedTextures.emplace(aTextureId, shared);
|
|
|
|
return shared;
|
|
}
|
|
|
|
std::shared_ptr<SharedTexture> WebGPUParent::GetSharedTexture(
|
|
ffi::WGPUTextureId aId) {
|
|
auto it = mSharedTextures.find(aId);
|
|
if (it == mSharedTextures.end()) {
|
|
return nullptr;
|
|
}
|
|
return it->second;
|
|
}
|
|
|
|
#if defined(XP_WIN)
|
|
/* static */
|
|
Maybe<ffi::WGPUFfiLUID> WebGPUParent::GetCompositorDeviceLuid() {
|
|
const RefPtr<ID3D11Device> d3d11Device =
|
|
gfx::DeviceManagerDx::Get()->GetCompositorDevice();
|
|
if (!d3d11Device) {
|
|
gfxCriticalNoteOnce << "CompositorDevice does not exist";
|
|
return Nothing();
|
|
}
|
|
|
|
RefPtr<IDXGIDevice> dxgiDevice;
|
|
d3d11Device->QueryInterface((IDXGIDevice**)getter_AddRefs(dxgiDevice));
|
|
|
|
RefPtr<IDXGIAdapter> dxgiAdapter;
|
|
dxgiDevice->GetAdapter(getter_AddRefs(dxgiAdapter));
|
|
|
|
DXGI_ADAPTER_DESC desc;
|
|
if (FAILED(dxgiAdapter->GetDesc(&desc))) {
|
|
gfxCriticalNoteOnce << "Failed to get DXGI_ADAPTER_DESC";
|
|
return Nothing();
|
|
}
|
|
|
|
return Some(
|
|
ffi::WGPUFfiLUID{desc.AdapterLuid.LowPart, desc.AdapterLuid.HighPart});
|
|
}
|
|
#endif
|
|
|
|
#if defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID)
|
|
VkImageHandle::~VkImageHandle() {
|
|
if (!mParent) {
|
|
return;
|
|
}
|
|
auto* context = mParent->GetContext();
|
|
if (context && mParent->IsDeviceActive(mDeviceId) && mVkImageHandle) {
|
|
wgpu_vkimage_destroy(context, mDeviceId, mVkImageHandle);
|
|
}
|
|
wgpu_vkimage_delete(mVkImageHandle);
|
|
}
|
|
|
|
VkSemaphoreHandle::~VkSemaphoreHandle() {
|
|
if (!mParent) {
|
|
return;
|
|
}
|
|
auto* context = mParent->GetContext();
|
|
if (context && mParent->IsDeviceActive(mDeviceId) && mVkSemaphoreHandle) {
|
|
wgpu_vksemaphore_destroy(context, mDeviceId, mVkSemaphoreHandle);
|
|
}
|
|
wgpu_vksemaphore_delete(mVkSemaphoreHandle);
|
|
}
|
|
#endif
|
|
|
|
} // namespace mozilla::webgpu
|