mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-16 14:55:47 +00:00
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:
parent
99a2412c5e
commit
5e0eefe182
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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; }
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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 = {
|
||||
|
@ -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);
|
||||
|
@ -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()));
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user