/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "WebGLChild.h" #include "ClientWebGLContext.h" #include "mozilla/StaticPrefs_webgl.h" #include "WebGLMethodDispatcher.h" namespace mozilla::dom { WebGLChild::WebGLChild(ClientWebGLContext& context) : mContext(&context), mDefaultCmdsShmemSize(StaticPrefs::webgl_out_of_process_shmem_size()) {} WebGLChild::~WebGLChild() { Destroy(); } void WebGLChild::Destroy() { if (!CanSend()) { return; } if (mContext) { mContext->OnDestroyChild(this); } (void)Send__delete__(this); } void WebGLChild::ActorDestroy(ActorDestroyReason why) { mPendingCmdsShmem = {}; } // - Maybe> WebGLChild::AllocPendingCmdBytes( const size_t size, const size_t fyiAlignmentOverhead) { if (!mPendingCmdsShmem.Size()) { size_t capacity = mDefaultCmdsShmemSize; if (capacity < size) { capacity = size; } mPendingCmdsShmem = mozilla::ipc::BigBuffer::TryAlloc(capacity); if (!mPendingCmdsShmem.Size()) { NS_WARNING("Failed to alloc shmem for AllocPendingCmdBytes."); return {}; } mPendingCmdsPos = 0; mPendingCmdsAlignmentOverhead = 0; if (kIsDebug) { const auto ptr = mPendingCmdsShmem.Data(); const auto initialOffset = AlignmentOffset(kUniversalAlignment, ptr); MOZ_ALWAYS_TRUE(!initialOffset); } } const auto range = Range{mPendingCmdsShmem.AsSpan()}; auto itr = range.begin() + mPendingCmdsPos; const auto offset = AlignmentOffset(kUniversalAlignment, itr.get()); mPendingCmdsPos += offset; mPendingCmdsAlignmentOverhead += offset; const auto required = mPendingCmdsPos + size; if (required > range.length()) { FlushPendingCmds(); return AllocPendingCmdBytes(size, fyiAlignmentOverhead); } itr = range.begin() + mPendingCmdsPos; const auto remaining = Range{itr, range.end()}; mPendingCmdsPos += size; mPendingCmdsAlignmentOverhead += fyiAlignmentOverhead; return Some(Range{remaining.begin(), remaining.begin() + size}); } void WebGLChild::FlushPendingCmds() { if (!mPendingCmdsShmem.Size()) return; const auto byteSize = mPendingCmdsPos; SendDispatchCommands(std::move(mPendingCmdsShmem), byteSize); mPendingCmdsShmem = {}; mFlushedCmdInfo.flushes += 1; mFlushedCmdInfo.flushedCmdBytes += byteSize; mFlushedCmdInfo.overhead += mPendingCmdsAlignmentOverhead; // Handle flushesSinceLastCongestionCheck mFlushedCmdInfo.flushesSinceLastCongestionCheck += 1; constexpr auto START_CONGESTION_CHECK_THRESHOLD = 20; constexpr auto ASSUME_IPC_CONGESTION_THRESHOLD = 70; RefPtr self = this; size_t generation = self->mFlushedCmdInfo.congestionCheckGeneration; // When ClientWebGLContext uses async remote texture, sync GetFrontBuffer // message is not sent in ClientWebGLContext::GetFrontBuffer(). It causes a // case that a lot of async DispatchCommands messages are sent to // WebGLParent without calling ClientWebGLContext::GetFrontBuffer(). The // sending DispatchCommands messages could be faster than receiving message // at WebGLParent by WebGLParent::RecvDispatchCommands(). If it happens, // pending IPC messages could grow too much until out of resource. To detect // the messages congestion, async Ping message is used. If the Ping response // is not received until maybeIPCMessageCongestion, IPC message might be // congested at WebGLParent. Then sending sync SyncPing flushes all pending // messages. // Due to the async nature of the async ping, it is possible for the flush // check to exceed maybeIPCMessageCongestion, but that it it still bounded. if (mFlushedCmdInfo.flushesSinceLastCongestionCheck == START_CONGESTION_CHECK_THRESHOLD) { const auto eventTarget = RefPtr{GetCurrentSerialEventTarget()}; MOZ_ASSERT(eventTarget); if (!eventTarget) { NS_WARNING("GetCurrentSerialEventTarget()->nullptr in FlushPendingCmds."); } else { SendPing()->Then(eventTarget, __func__, [self, generation]() { if (generation == self->mFlushedCmdInfo.congestionCheckGeneration) { // Confirmed IPC messages congestion does not happen. // Reset flushesSinceLastCongestionCheck for next congestion check. self->mFlushedCmdInfo.flushesSinceLastCongestionCheck = 0; self->mFlushedCmdInfo.congestionCheckGeneration++; } }); } } else if (mFlushedCmdInfo.flushesSinceLastCongestionCheck > ASSUME_IPC_CONGESTION_THRESHOLD) { // IPC messages congestion might happen, send sync SyncPing for flushing // pending messages. SendSyncPing(); // Reset flushesSinceLastCongestionCheck for next congestion check. mFlushedCmdInfo.flushesSinceLastCongestionCheck = 0; mFlushedCmdInfo.congestionCheckGeneration++; } if (gl::GLContext::ShouldSpew()) { const auto overheadRatio = float(mPendingCmdsAlignmentOverhead) / (byteSize - mPendingCmdsAlignmentOverhead); const auto totalOverheadRatio = float(mFlushedCmdInfo.overhead) / (mFlushedCmdInfo.flushedCmdBytes - mFlushedCmdInfo.overhead); printf_stderr( "[WebGLChild] Flushed %zu (%zu=%.2f%% overhead) bytes." " (%zu (%.2f%% overhead) over %zu flushes)\n", byteSize, mPendingCmdsAlignmentOverhead, 100 * overheadRatio, mFlushedCmdInfo.flushedCmdBytes, 100 * totalOverheadRatio, mFlushedCmdInfo.flushes); } } // - mozilla::ipc::IPCResult WebGLChild::RecvJsWarning( const std::string& text) const { if (!mContext) return IPC_OK(); mContext->JsWarning(text); return IPC_OK(); } mozilla::ipc::IPCResult WebGLChild::RecvOnContextLoss( const webgl::ContextLossReason reason) const { if (!mContext) return IPC_OK(); mContext->OnContextLoss(reason); return IPC_OK(); } mozilla::ipc::IPCResult WebGLChild::RecvOnSyncComplete( const webgl::ObjectId id) const { if (!mContext) return IPC_OK(); mContext->OnSyncComplete(id); return IPC_OK(); } } // namespace mozilla::dom