Bug 1754556 - Update WebGPU external image resource only after present is complete. r=kvark

This patch ensures that we only update the external image resource for
WebGPU when there has been an actual change for the resource. In order
to guarantee this, we wait for the present to complete, and only then
issue the update. WebRenderBridgeChild::SendResourceUpdates will also
trigger a frame generation if any resources were changed, which means we
don't need to trigger a paint on the frame itself anymore.

Note that we still have a race condition when we write into the
MemoryTextureHost while in PresentCallback, and the renderer thread may
be accessing the pixel data to upload to the GPU.

Differential Revision: https://phabricator.services.mozilla.com/D138349
This commit is contained in:
Andrew Osmond 2022-02-11 01:26:42 +00:00
parent 99a2412c5e
commit 5e0eefe182
13 changed files with 68 additions and 43 deletions

View File

@ -1210,8 +1210,8 @@ void HTMLCanvasElement::InvalidateCanvasContent(const gfx::Rect* damageRect) {
}
if (localData && wr::AsUint64(localData->mImageKey)) {
localData->mDirty = true;
frame->SchedulePaint(nsIFrame::PAINT_COMPOSITE_ONLY);
// When the readback is complete, we will schedule a resource update.
localData->RequestFrameReadback();
} else if (renderer) {
renderer->SetDirty();
frame->SchedulePaint(nsIFrame::PAINT_COMPOSITE_ONLY);

View File

@ -31,9 +31,7 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasContext)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
CanvasContext::CanvasContext()
: mExternalImageId(layers::CompositorManagerChild::GetInstance()
->GetNextExternalImageId()) {}
CanvasContext::CanvasContext() = default;
CanvasContext::~CanvasContext() {
Cleanup();
@ -78,8 +76,10 @@ void CanvasContext::Configure(const dom::GPUCanvasConfiguration& aDesc) {
}
gfx::IntSize actualSize(mWidth, mHeight);
mTexture = aDesc.mDevice->InitSwapChain(aDesc, mExternalImageId, mGfxFormat,
&actualSize);
mExternalImageId.emplace(
layers::CompositorManagerChild::GetInstance()->GetNextExternalImageId());
mTexture = aDesc.mDevice->InitSwapChain(aDesc, mExternalImageId.ref(),
mGfxFormat, &actualSize);
mTexture->mTargetCanvasElement = mCanvasElement;
mBridge = aDesc.mDevice->GetBridge();
mGfxSize = actualSize;
@ -101,9 +101,10 @@ wr::ImageKey CanvasContext::CreateImageKey(
}
void CanvasContext::Unconfigure() {
if (mBridge && mBridge->IsOpen()) {
mBridge->SendSwapChainDestroy(mExternalImageId);
if (mBridge && mBridge->IsOpen() && mExternalImageId) {
mBridge->SendSwapChainDestroy(mExternalImageId.ref());
}
mExternalImageId.reset();
mBridge = nullptr;
mTexture = nullptr;
mGfxFormat = gfx::SurfaceFormat::UNKNOWN;
@ -129,7 +130,7 @@ bool CanvasContext::UpdateWebRenderLocalCanvasData(
aCanvasData->mGpuBridge = mBridge.get();
aCanvasData->mGpuTextureId = mTexture->mId;
aCanvasData->mExternalImageId = mExternalImageId;
aCanvasData->mExternalImageId = mExternalImageId.ref();
aCanvasData->mFormat = mGfxFormat;
return true;
}

View File

@ -47,7 +47,7 @@ class CanvasContext final : public nsICanvasRenderingContextInternal,
wr::ImageDescriptor MakeImageDescriptor() const;
wr::ExternalImageId mExternalImageId;
Maybe<wr::ExternalImageId> mExternalImageId;
public: // nsICanvasRenderingContextInternal
int32_t GetWidth() override { return mWidth; }

View File

@ -64,7 +64,7 @@ parent:
async RenderPipelineDestroy(RawId selfId);
async ImplicitLayoutDestroy(RawId implicitPlId, RawId[] implicitBglIds);
async DeviceCreateSwapChain(RawId selfId, RawId queueId, RGBDescriptor desc, RawId[] bufferIds, ExternalImageId externalId);
async SwapChainPresent(ExternalImageId externalId, RawId textureId, RawId commandEncoderId);
async SwapChainPresent(ExternalImageId externalId, RawId textureId, RawId commandEncoderId) returns (bool success);
async SwapChainDestroy(ExternalImageId externalId);
async DevicePushErrorScope(RawId selfId);

View File

@ -972,12 +972,12 @@ void WebGPUChild::DeviceCreateSwapChain(RawId aSelfId,
aExternalImageId);
}
void WebGPUChild::SwapChainPresent(wr::ExternalImageId aExternalImageId,
RawId aTextureId) {
RefPtr<SwapChainPromise> WebGPUChild::SwapChainPresent(
wr::ExternalImageId aExternalImageId, RawId aTextureId) {
// Hack: the function expects `DeviceId`, but it only uses it for `backend()`
// selection.
RawId encoderId = ffi::wgpu_client_make_encoder_id(mClient, aTextureId);
SendSwapChainPresent(aExternalImageId, aTextureId, encoderId);
return SendSwapChainPresent(aExternalImageId, aTextureId, encoderId);
}
void WebGPUChild::RegisterDevice(Device* const aDevice) {

View File

@ -28,6 +28,7 @@ using AdapterPromise =
MozPromise<ipc::ByteBuf, Maybe<ipc::ResponseRejectReason>, true>;
using PipelinePromise = MozPromise<RawId, ipc::ResponseRejectReason, true>;
using DevicePromise = MozPromise<bool, ipc::ResponseRejectReason, true>;
using SwapChainPromise = MozPromise<bool, ipc::ResponseRejectReason, true>;
struct PipelineCreationContext {
RawId mParentId = 0;
@ -101,7 +102,8 @@ class WebGPUChild final : public PWebGPUChild, public SupportsWeakPtr {
void DeviceCreateSwapChain(RawId aSelfId, const RGBDescriptor& aRgbDesc,
size_t maxBufferCount,
wr::ExternalImageId aExternalImageId);
void SwapChainPresent(wr::ExternalImageId aExternalImageId, RawId aTextureId);
RefPtr<SwapChainPromise> SwapChainPresent(
wr::ExternalImageId aExternalImageId, RawId aTextureId);
void RegisterDevice(Device* const aDevice);
void UnregisterDevice(RawId aId);

View File

@ -551,6 +551,7 @@ ipc::IPCResult WebGPUParent::RecvDeviceCreateSwapChain(
struct PresentRequest {
const ffi::WGPUGlobal* mContext;
RefPtr<PresentationData> mData;
WebGPUParent::SwapChainPresentResolver mResolver;
};
static void PresentCallback(ffi::WGPUBufferMapAsyncStatus status,
@ -580,25 +581,29 @@ static void PresentCallback(ffi::WGPUBufferMapAsyncStatus status,
dst += data->mTargetPitch;
ptr += data->mSourcePitch;
}
req->mResolver(true);
} else {
NS_WARNING("WebGPU present skipped: the swapchain is resized!");
req->mResolver(false);
}
wgpu_server_buffer_unmap(req->mContext, bufferId);
} else {
// TODO: better handle errors
NS_WARNING("WebGPU frame mapping failed!");
req->mResolver(false);
}
// free yourself
delete req;
}
ipc::IPCResult WebGPUParent::RecvSwapChainPresent(
wr::ExternalImageId aExternalId, RawId aTextureId,
RawId aCommandEncoderId) {
wr::ExternalImageId aExternalId, RawId aTextureId, RawId aCommandEncoderId,
SwapChainPresentResolver&& aResolver) {
// step 0: get the data associated with the swapchain
const auto& lookup = mCanvasMap.find(AsUint64(aExternalId));
if (lookup == mCanvasMap.end()) {
NS_WARNING("WebGPU presenting on a destroyed swap chain!");
aResolver(false);
return IPC_OK();
}
RefPtr<PresentationData> data = lookup->second.get();
@ -626,6 +631,7 @@ ipc::IPCResult WebGPUParent::RecvSwapChainPresent(
ffi::wgpu_server_device_create_buffer(mContext, data->mDeviceId, &desc,
bufferId, error.ToFFI());
if (ForwardError(data->mDeviceId, error)) {
aResolver(false);
return IPC_OK();
}
} else {
@ -641,6 +647,7 @@ ipc::IPCResult WebGPUParent::RecvSwapChainPresent(
("RecvSwapChainPresent with buffer %" PRIu64 "\n", bufferId));
if (!bufferId) {
// TODO: add a warning - no buffer are available!
aResolver(false);
return IPC_OK();
}
@ -652,6 +659,7 @@ ipc::IPCResult WebGPUParent::RecvSwapChainPresent(
&encoderDesc, aCommandEncoderId,
error.ToFFI());
if (ForwardError(data->mDeviceId, error)) {
aResolver(false);
return IPC_OK();
}
}
@ -681,6 +689,7 @@ ipc::IPCResult WebGPUParent::RecvSwapChainPresent(
ffi::wgpu_server_encoder_finish(mContext, aCommandEncoderId, &commandDesc,
error.ToFFI());
if (ForwardError(data->mDeviceId, error)) {
aResolver(false);
return IPC_OK();
}
}
@ -690,6 +699,7 @@ ipc::IPCResult WebGPUParent::RecvSwapChainPresent(
ffi::wgpu_server_queue_submit(mContext, data->mQueueId, &aCommandEncoderId,
1, error.ToFFI());
if (ForwardError(data->mDeviceId, error)) {
aResolver(false);
return IPC_OK();
}
}
@ -702,6 +712,7 @@ ipc::IPCResult WebGPUParent::RecvSwapChainPresent(
auto* const presentRequest = new PresentRequest{
mContext,
data,
std::move(aResolver),
};
ffi::WGPUBufferMapOperation mapOperation = {

View File

@ -69,8 +69,8 @@ class WebGPUParent final : public PWebGPUParent {
const nsTArray<RawId>& aBufferIds,
ExternalImageId aExternalId);
ipc::IPCResult RecvSwapChainPresent(wr::ExternalImageId aExternalId,
RawId aTextureId,
RawId aCommandEncoderId);
RawId aTextureId, RawId aCommandEncoderId,
SwapChainPresentResolver&& aResolver);
ipc::IPCResult RecvSwapChainDestroy(wr::ExternalImageId aExternalId);
ipc::IPCResult RecvDeviceAction(RawId aSelf, const ipc::ByteBuf& aByteBuf);

View File

@ -1517,7 +1517,6 @@ WebRenderCommandBuilder::WebRenderCommandBuilder(
void WebRenderCommandBuilder::Destroy() {
mLastCanvasDatas.Clear();
mLastLocalCanvasDatas.Clear();
ClearCachedResources();
}
@ -1529,14 +1528,10 @@ void WebRenderCommandBuilder::EmptyTransaction() {
canvas->UpdateCompositableClientForEmptyTransaction();
}
}
for (RefPtr<WebRenderLocalCanvasData> canvasData : mLastLocalCanvasDatas) {
canvasData->RefreshExternalImage();
canvasData->RequestFrameReadback();
}
}
bool WebRenderCommandBuilder::NeedsEmptyTransaction() {
return !mLastCanvasDatas.IsEmpty() || !mLastLocalCanvasDatas.IsEmpty();
return !mLastCanvasDatas.IsEmpty();
}
void WebRenderCommandBuilder::BuildWebRenderCommands(
@ -1554,7 +1549,6 @@ void WebRenderCommandBuilder::BuildWebRenderCommands(
mBuilderDumpIndex = 0;
mLastCanvasDatas.Clear();
mLastLocalCanvasDatas.Clear();
mLastAsr = nullptr;
mContainsSVGGroup = false;
MOZ_ASSERT(mDumpIndent == 0);
@ -2677,9 +2671,6 @@ void WebRenderCommandBuilder::RemoveUnusedAndResetWebRenderUserData() {
case WebRenderUserData::UserDataType::eCanvas:
mLastCanvasDatas.Remove(data->AsCanvasData());
break;
case WebRenderUserData::UserDataType::eLocalCanvas:
mLastLocalCanvasDatas.Remove(data->AsLocalCanvasData());
break;
case WebRenderUserData::UserDataType::eAnimation:
EffectCompositor::ClearIsRunningOnCompositor(
frame, GetDisplayItemTypeFromKey(data->GetDisplayItemKey()));

View File

@ -173,9 +173,6 @@ class WebRenderCommandBuilder final {
case WebRenderUserData::UserDataType::eCanvas:
mLastCanvasDatas.Insert(data->AsCanvasData());
break;
case WebRenderUserData::UserDataType::eLocalCanvas:
mLastLocalCanvasDatas.Insert(data->AsLocalCanvasData());
break;
default:
break;
}
@ -218,8 +215,6 @@ class WebRenderCommandBuilder final {
// Store of WebRenderCanvasData objects for use in empty transactions
CanvasDataSet mLastCanvasDatas;
// Store of WebRenderLocalCanvasData objects for use in empty transactions
LocalCanvasDataSet mLastLocalCanvasDatas;
wr::usize mBuilderDumpIndex;
wr::usize mDumpIndent;

View File

@ -401,20 +401,41 @@ WebRenderLocalCanvasData::~WebRenderLocalCanvasData() = default;
void WebRenderLocalCanvasData::RequestFrameReadback() {
if (mGpuBridge) {
mGpuBridge->SwapChainPresent(mExternalImageId, mGpuTextureId);
mGpuBridge->SwapChainPresent(mExternalImageId, mGpuTextureId)
->Then(
GetCurrentSerialEventTarget(), __func__,
[self = RefPtr{this},
externalImageId = mExternalImageId](bool aPresented) {
if (aPresented) {
self->RefreshExternalImage(externalImageId);
}
return webgpu::SwapChainPromise::CreateAndResolve(aPresented,
__func__);
},
[](const ipc::ResponseRejectReason& aReason) {
return webgpu::SwapChainPromise::CreateAndReject(aReason,
__func__);
});
}
}
void WebRenderLocalCanvasData::RefreshExternalImage() {
if (!mDirty) {
void WebRenderLocalCanvasData::RefreshExternalImage(
const wr::ExternalImageId& aExternalImageId) {
// Because we can be called from an async lambda, we need to verify that we
// remain in the WebRenderUserData table. If we are not, then we know that
// the cache was cleared, because of a tab switch/closure, or a device reset.
// Also if the external image ID changes, then we know we got reconfigured
// and are now using a different texture.
if (!(mExternalImageId == aExternalImageId) || !mTable->Contains(this)) {
return;
}
MOZ_ASSERT(!mManager->IsDestroyed());
const ImageIntRect dirtyRect(0, 0, mDescriptor.width, mDescriptor.height);
// Update the WR external image, forcing the composition of a new frame.
mManager->AsyncResourceUpdates().UpdatePrivateExternalImage(
mExternalImageId, mImageKey, mDescriptor, dirtyRect);
mDirty = false;
}
WebRenderRemoteData::WebRenderRemoteData(RenderRootStateManager* aManager,

View File

@ -318,7 +318,7 @@ class WebRenderLocalCanvasData : public WebRenderUserData {
static UserDataType Type() { return UserDataType::eLocalCanvas; }
void RequestFrameReadback();
void RefreshExternalImage();
void RefreshExternalImage(const wr::ExternalImageId& aExternalImageId);
// TODO: introduce a CanvasRenderer derivative to store here?
@ -328,7 +328,6 @@ class WebRenderLocalCanvasData : public WebRenderUserData {
wr::ImageKey mImageKey = {};
wr::ImageDescriptor mDescriptor;
gfx::SurfaceFormat mFormat = gfx::SurfaceFormat::UNKNOWN;
bool mDirty = false;
};
class WebRenderRemoteData : public WebRenderUserData {

View File

@ -221,8 +221,8 @@ class nsDisplayCanvas final : public nsPaintedDisplayItem {
imageKey = imageKeyMaybe.value();
} else {
imageKey = canvasContext->CreateImageKey(aManager);
aResources.AddPrivateExternalImage(canvasContext->mExternalImageId,
imageKey, imageDesc);
aResources.AddPrivateExternalImage(
canvasContext->mExternalImageId.ref(), imageKey, imageDesc);
}
{
@ -244,6 +244,11 @@ class nsDisplayCanvas final : public nsPaintedDisplayItem {
canvasData->mDescriptor = imageDesc;
canvasData->mImageKey = imageKey;
// When the caller calls webgpu::Queue::Submit, we request a frame
// readback to update the external image, but when we create the
// WebRenderLocalCanvasData for the first time, it hasn't had a chance
// to yet, so we need to explicitly do so here.
canvasData->RequestFrameReadback();
break;
}